# AI Reports: Generisches Style-Management, AI-Call-Konfiguration, Inline-Bilder ## Beschreibung und Kontext Drei verzahnte Themen rund um AI-erzeugte Reports und AI-Calls in Workflows / Workspace: 1. **Style-Management generisch und Agent-getrieben.** Die heutigen Renderer (DOCX, XLSX, PPTX, HTML, PDF) haben hardcoded Farben (z.B. DOCX-Tabellen-Header `4472C4`), feste Bildbreiten, hartes `lang="en"` im HTML. Es gibt keinen Mechanismus, dass ein Agent pro Bericht Styling, Logo, Schrift, Farben mitgeben kann. Wir wollen **kein** fixes Template-/Theme-Mandate-CI-Konzept, sondern ein **generisches Style-Schema**, das der Agent fuellt -- die Renderer mappen daraus. 2. **Per-Call AI-Konfiguration: Neutralisierung + erlaubte Modelle.** Heute laufen AI-Calls aus Workflows ueber das zentrale AI-Gate (`mainServiceAi.py`); Neutralisierung wird via Feature/Session/Request- Flags gewaehlt, aber im FlowEditor ist sie pro Node nicht explizit einstellbar. Eine **Whitelist erlaubter Modelle pro Node** existiert ueberhaupt nicht. Im AI-Workspace soll beides automatisch aus dem Workspace-Kontext kommen, im FlowEditor pro Node. 3. **Inline-Bilder ueberall.** Die heutige MD->JSON-Pipeline (`_mediaTools._markdownToDocumentJson`, `subDocumentUtility.markdownToDocumentJson`) flacht alles auf Strings: Bilder muessen eine eigene Markdown-Zeile sein. Ein Bild in einer Tabellenzelle, mitten in einem Paragraph, in einer Listenzeile etc. geht heute nicht. Zusaetzliche technische Schuld: - MD->JSON ist **doppelt** implementiert (`_mediaTools` und `subDocumentUtility`) -- Drift-Risiko. - Agent ruft `renderReport` ohne `aiService` auf (`_mediaTools.py` 282-287), KI-Style-Anpassung im Renderer-Pfad ist damit inaktiv. Das wird durch den neuen Agent-Style-Pfad ohnehin obsolet. **Geschaeftstreiber:** Reports sind das visuelle Endprodukt, das User mit Kunden teilen. Ein Agent, der pro Bericht Layout-Entscheidungen trifft (Finanzreport vs. Marketing-Flyer vs. neutrales Memo), hebt die wahrgenommene Qualitaet deutlich. Per-Node-Neutralisierung + Modell-Whitelist machen Compliance und Kostenkontrolle pro Workflow explizit und auditierbar. ## Fokus und kritische Details - Markdown bleibt User-/Agent-Eingangs-Schicht. JSON ist nur das interne deterministisch erzeugte Zwischenformat. **Keine** Aenderung am MD- First-Konzept. - Style-Schema: muss generisch genug sein, dass die heutigen Renderer alle relevanten Visual-Konstanten daraus ziehen, aber **kein** branchenspezifisches Preset-Inventar mit harter Auswahl. - Defaults sind wichtig: wenn der Agent nichts angibt, muss der Renderer eine vernuenftige neutrale Default-Style-Set verwenden. - Inline-Bilder erfordern Schema-Aenderung beim `cell`/`paragraph`/ `bullet_list_item`-Content: weg von `string`, hin zu `inline-Run- Liste` (`text` | `image` | `link` | `bold` | `italic`). - **KEIN Backwards-Compat:** Altes String-basiertes Format wird NICHT weiter unterstuetzt. Renderer bekommen NUR das neue Inline-Run- Modell. Alte Workflow-Runs mit altem JSON-Output brechen bei erneutem Rendering -- akzeptabler Trade-off fuer saubere Codebasis. - Per-Node AI-Konfiguration darf das **zentrale AI-Gate** nicht umgehen: das Gate bleibt einzige Stelle, die Neutralisierung wirklich durchsetzt. Node-Parameter werden in den `AiCallRequest` reingefuettert und folgen den heutigen Praezedenzregeln (Feature -> Session -> Request, OR-Logik fuer Neutralisierung). ## Ziel und Nicht-Ziele - Ziel: - **Style:** Generisches Style-Schema, vom Agent pro Render-Call fuellbar (`metadata.style`). Renderer mappen einheitlich. Sensible Defaults wenn nichts gesetzt. - **AI-Call-Konfig:** Pro `ai.*`-Node im FlowEditor zwei neue Parameter `requireNeutralization` (boolean) und `allowedModels` (Liste). Workspace-Kontext fuellt sie automatisch fuer Workspace-Calls. Validierung am AI-Gate (Whitelist-Check vor Modellwahl). - **Inline-Bilder:** Bilder in Paragraph, Tabellenzelle, Listenzeile, Code-Block-Caption etc. moeglich. MD-Syntax erkennt sie an beliebiger Position. Renderer rendert sie inline wo das Format es kann; sonst dokumentierter Fallback. - **Konsolidierung:** 1× MD->JSON-Pipeline (Drift weg). - NICHT: Komplett neuer Renderer (z.B. WeasyPrint statt reportlab). - NICHT: Native XLSX-Charts via openpyxl ChartObjects (separater Plan). - NICHT: Mandate-CI-Konzept, Logo-Inheritance, Theme-Presets. - NICHT: User-konfigurierbare Custom-Themes via Admin-UI. - NICHT: Aenderung am bestehenden Neutralisierungs-Engine; nur Eingangs- Pfad ergaenzen. - NICHT: Backwards-Compat fuer altes JSON-Format (Clean-Break). ## Betroffene Module - Gateway: - `gateway/modules/serviceCenter/services/serviceAgent/coreTools/_mediaTools.py` -- `_markdownToDocumentJson` (nested in `_registerMediaTools`, Z. 27-163) entfernen, Aufruf auf `subDocumentUtility.markdownToDocumentJson` umleiten. Image-Resolution-Logik (Z. 241-277, loest fileId -> base64 aus KnowledgeStore/Chat) VERBLEIBT in `_mediaTools` als Post- Processing-Schritt nach dem Parser-Aufruf. `renderDocument`-Tool-Schema ergaenzt um optionalen `style`-Parameter. - `gateway/modules/serviceCenter/services/serviceGeneration/subDocumentUtility.py` -- alleinige MD->JSON-Funktion mit Inline-Run-Modell. - `gateway/modules/serviceCenter/services/serviceGeneration/mainServiceGeneration.py` -- `_resolveStyle(metadata.style) -> ResolvedStyle` (Defaults + Agent-Overrides). - `gateway/modules/serviceCenter/services/serviceGeneration/renderers/*` -- alle 5 Renderer auf Style-Lookup umstellen, Inline-Run-Renderer. NUR neues Format (InlineRun); altes String-Format wird nicht mehr unterstuetzt. - `gateway/modules/datamodels/datamodelJson.py` -- Schema-Erweiterung fuer Inline-Runs und `cellContent`. - `gateway/modules/datamodels/datamodelAi.py` -- `AiCallOptions.allowedModels: Optional[List[str]]` (Whitelist; analog `allowedProviders` Z. 164). - `gateway/modules/serviceCenter/services/serviceAi/mainServiceAi.py` -- `_calculateEffectiveModels()` (Z. ~1196, analog zu `_calculateEffectiveProviders`); Whitelist-Check vor Modellwahl, Fail-fast wenn keiner uebrig. `_ServicesAdapter.__getattr__` (Z. 88-90) um `"allowedModels"` ergaenzen. - `gateway/modules/features/graphicalEditor/nodeDefinitions/ai.py` -- Standardparameter `requireNeutralization` + `allowedModels` zu allen `ai.*`-Nodes (gemeinsamer Helper `_AI_COMMON_PARAMS`). - `gateway/modules/workflows/methods/methodAi/actions/*.py` -- Node- Parameter durchreichen in den `AiCallRequest`. - `gateway/modules/serviceCenter/services/serviceAgent/conversationManager.py` -- Workspace-Kontext-Defaults fuer `requireNeutralization` / `allowedModels` aus Workspace-Config in `ServiceCenterContext` setzen. - Frontend: - FlowEditor: Pro AI-Node die zwei neuen Parameter im Properties- Panel anzeigen (`requireNeutralization` Toggle, `allowedModels` Multi-Select). Hilfetexte. - Workspace-Settings: bestehender Neutralisierungs-Toggle bleibt; neuer Multi-Select fuer `allowedModels` (Default: alle verfuegbaren Modelle des Mandate). - HINWEIS: Request-Model heisst `WorkspaceInputRequest` (NICHT `WorkspaceUserInput`; siehe `routeFeatureWorkspace.py` Z. 102-113). - Style-Vorschau (klein): Optional Snippet im FlowEditor, das den `style`-Parameter eines `file.create`/`renderDocument`-Nodes visualisieren kann (deferred). - DB-Migration: nein im engeren Sinne. `Workflow`-Tabelle bekommt keinen neuen Pflicht-Spalten (Node-Parameter leben im Graph-JSON). `WorkspaceSettings.allowedModels`-Feld additiv via Auto-Init. ## Style-Schema (generisch, Agent-getrieben) Der Agent uebergibt einen `style`-Block in `metadata` (alle Felder optional, Renderer fallen auf Defaults zurueck): ```json { "metadata": { "language": "de", "documents": [...], "style": { "fonts": { "primary": "Calibri", "monospace": "Consolas" }, "colors": { "primary": "#0F3D7A", "secondary": "#1F2937", "accent": "#E11D48", "background": "#FFFFFF" }, "headings": { "h1": { "sizePt": 24, "weight": "bold", "color": "#0F3D7A", "spaceBeforePt": 12, "spaceAfterPt": 6 }, "h2": { "sizePt": 18, "weight": "bold", "color": "#0F3D7A" }, "h3": { "sizePt": 14, "weight": "bold" }, "h4": { "sizePt": 12, "weight": "bold" } }, "paragraph": { "sizePt": 11, "lineSpacing": 1.15 }, "table": { "headerBg": "#0F3D7A", "headerFg": "#FFFFFF", "rowBandingEven": "#F5F8FC", "rowBandingOdd": "#FFFFFF", "borderColor": "#CBD5E1", "borderWidthPt": 0.5 }, "list": { "bulletChar": "•", "indentPt": 18 }, "image": { "defaultWidthPt": 480, "alignment": "center" }, "page": { "format": "A4", "marginsPt": { "top": 60, "bottom": 60, "left": 60, "right": 60 }, "showPageNumbers": true, "headerHeight": 30, "footerHeight": 30, "headerLogo": { "fileId": "abc", "alignment": "right", "heightPt": 24 }, "headerText": "Quartalsbericht Q3/2026", "footerText": "Vertraulich -- Seite {page}/{pages}" } } } } ``` **Logo:** Es gibt keinen Mandate-Inheritance-Pfad. Wenn ein Logo in den Bericht soll, gibt der Agent eine `fileId` an (Datei muss zuvor via `generateImage`/`writeFile` oder DataSource-Download erzeugt sein und liegt bereits als `FileItem` vor). **Defaults (`gateway/modules/serviceCenter/services/serviceGeneration/styleDefaults.py`):** neutrale Default-Werte fuer alle Felder. `_resolveStyle(agentStyle) -> ResolvedStyle` macht ein deep-merge `defaults <- agentStyle`. Renderer sieht immer eine vollstaendig aufgeloeste Struktur. **Generische "Style-Komponenten" auf Renderer-Seite:** Pro visuellem Element (heading, paragraph, table, listItem, image, codeBlock, page) existiert eine Renderer-Funktion, die ausschliesslich die `ResolvedStyle` und den Content-Knoten konsumiert. So ist die Implementierung pro Format gleichfoermig (DOCX/XLSX/PPTX/HTML/PDF -- alle haben ein `renderHeading(headingNode, style.headings.hN)` etc.). ## Inline-Run-Modell (fuer Inline-Bilder ueberall) Heute: `paragraph.text: str`, `cell: str`, `bullet_list.items: [str]`. Neu: `inlineRuns: List[InlineRun]` mit ```json { "type": "text" | "image" | "link" | "bold" | "italic" | "code", "value": "...", "fileId": "...", // bei type=image "widthPt": 96, // optional bei type=image, default klein/inline "href": "..." // bei type=link } ``` Wenn der MD-Parser nur Text findet, entsteht ein einzelner Run mit `type: text`. Renderer bekommen einen Helper `_renderInlineRuns(runs, style, container)`, der pro Run das richtige Element produziert (Text-Run mit Style, Inline-Image-Anchor, Hyperlink-Run, etc.). **Kein** altes `text: str`-Feld -- Renderer arbeiten ausschliesslich mit `inlineRuns`. **Markdown-Erkennung:** `![alt](file:xyz)` und `![alt](file:xyz "100pt")` mitten in einem Paragraph oder in einer Tabellenzelle wird vom Parser zu einem `image`-Run. Listen-Items dasselbe. **Renderer-Faehigkeiten (Matrix):** | Format | Inline-Bild im Paragraph | Inline-Bild in Zelle | Inline-Bild in Listen-Item | Fallback | |--------|--------------------------|----------------------|-----------------------------|----------| | DOCX | nativ via `add_picture` | nativ via Cell-Paragraph | nativ | -- | | XLSX | Bild ueber Zelle anchorn | nativ Cell-Anchor | -- (kein Listen-Konzept) | Bild unter Zeile | | HTML | trivial `` | trivial `` in `` | trivial `` in `
  • ` | -- | | PPTX | nicht nativ in Text-Run | begrenzt | -- | Bild als separate Shape unter Text | | PDF | reportlab Inline-Image | reportlab Cell-Image | begrenzt | Bild in eigenem Flowable unter Text | ## AI-Call-Konfiguration pro Node (Neutralisierung + Modell-Whitelist) ### Was es heute schon gibt (wichtig fuer Plan-Verstaendnis) - **`AiCallRequest.requireNeutralization`** existiert (`datamodelAi.py` Z. 177). - **`AiCallOptions.allowedProviders`** existiert (`datamodelAi.py` Z. 164) -- Provider-Whitelist auf Anbieter-Ebene (z.B. `anthropic`, `openai`, `private-llm`). - **`Workflow.allowedProviders`** wird ueber `__getattr__` durchgereicht (`mainServiceAi.py` Z. 89). - **`_calculateEffectiveProviders()`** macht RBAC ∩ Workflow.allowedProviders und schreibt das in `request.options.allowedProviders` (`mainServiceAi.py` Z. 175-178). - **`WorkspaceInputRequest.allowedProviders`** + **`requireNeutralization`** werden vom Frontend pro Request mitgegeben (`routeFeatureWorkspace.py` Z. 111-113) und in den `ServiceCenterContext` gesetzt (Z. 713-719). - **`ServiceCenterContext.requireNeutralization`** existiert (`context.py` Z. 23). - **`GET /api/system/ai-models`** liefert die verfuegbaren Modelle (`routeSystem.py` Z. 937). ### Ziele dieses Plans (additiv zur bestehenden Provider-Logik) Wir fuehren `allowedModels` als **feinere Granularitaet neben** dem bestehenden `allowedProviders` ein -- nicht als Ersatz. Ein Provider darf erlaubt sein, aber nur bestimmte Modelle dieses Providers nutzbar (z.B. Provider `openai` erlaubt, aber nur `gpt-5-mini` nicht `gpt-5`). - **Pro `ai.*`-Node im FlowEditor (NEU):** - `requireNeutralization` (`boolean`, Default `false`). - `allowedModels` (`list[string]`, Default `[]` = "alle"). - (`allowedProviders` koennte spaeter analog folgen -- bewusst NICHT in diesem Plan, weil heute nur Workflow-Level eingestellt wird.) - **Im AI-Workspace (Default-Persistenz):** - `requireNeutralization`: heute schon im `WorkspaceUserInput` -- bleibt, jetzt aber **persistent** als User-Default. - `allowedProviders`: heute schon im `WorkspaceUserInput` -- bleibt, jetzt ebenfalls persistent als User-Default. - `allowedModels` (NEU): zusaetzliches Feld in `WorkspaceUserInput` + UI-Feld in den Workspace-Einstellungen. - **Persistenz-Entscheidung (siehe Entscheidungs-Tabelle):** Phase 5a kommt mit neuem Endpoint `GET/PUT /api/workspace/{instanceId}/user- settings` fuer ALLE DREI Felder als User-spezifische Defaults pro Workspace -- damit der User die Settings nicht bei jedem Browser- Open neu setzen muss. Frontend laedt Defaults beim Workspace-Open und schickt sie pro Call mit. - **Datenmodell-Erweiterung:** - `AiCallOptions.allowedModels: Optional[List[str]] = None` (analog zu `allowedProviders`). - `Workflow.allowedModels: Optional[List[str]] = None` (analog zu `Workflow.allowedProviders`). - **Validierung am zentralen AI-Gate (`mainServiceAi.py`):** - `requireNeutralization` folgt **bestehender** OR-Logik (keine Aenderung). - Neue private Funktion `_calculateEffectiveModels()` analog zu `_calculateEffectiveProviders()`: RBAC ∩ Workflow.allowedModels ∩ Node.allowedModels (Node setzt es ueber `request.options`). - **Effektive Filterkette:** Provider-Filter (heute) AND Model-Filter (neu) AND functionCall-Anforderung (heute). - Ist die gefilterte Liste leer -> Fail-fast mit `RuntimeError("No allowed model available for this call (allowedModels=[...], allowedProviders=[...])")`. ### Beispiel: AI-Node-Definition (gekuerzt) ```python # in gateway/modules/features/graphicalEditor/nodeDefinitions/ai.py # (allowedProviders existiert bereits auf Workflow-Ebene; hier nur die # zwei NEUEN Felder pro AI-Node) _AI_COMMON_PARAMS = [ {"name": "requireNeutralization", "type": "boolean", "required": False, "frontendType": "checkbox", "default": False, "description": t("Eingaben fuer diesen Call neutralisieren")}, {"name": "allowedModels", "type": "array", "required": False, "frontendType": "modelMultiSelect", "default": [], "description": t("Erlaubte LLM-Modelle (leer = alle erlaubten); zusaetzlicher AND-Filter ueber allowedProviders")}, ] # pro Node anhaengen, z.B. ai.prompt: { "id": "ai.prompt", ... "parameters": [ {"name": "aiPrompt", ...}, {"name": "resultType", ...}, {"name": "documentList", ...}, {"name": "simpleMode", ...}, *_AI_COMMON_PARAMS, ], ... } ``` ### Datenfluss ```mermaid flowchart LR Node[ai.* Node Parameters] -->|"requireNeutralization, allowedModels"| Action[methodAi/actions/*.py] Action -->|"sets request.options.allowedModels
    + request.requireNeutralization"| Gate[mainServiceAi._call] Gate --> EffMod[_calculateEffectiveModels
    RBAC AND Workflow AND Node] Gate --> EffProv[_calculateEffectiveProviders
    existing] EffMod --> Pick[_pickModel] EffProv --> Pick Pick -->|"empty?"| Fail[Fail-fast RuntimeError] Pick -->|"ok"| Neutral[_shouldNeutralize OR-Logik existing] Neutral --> LLM[Modell-Call] WorkspaceUI[WorkspaceUserInput
    requireNeutralization+allowedModels] -->|"per-request"| Ctx[ServiceCenterContext] WorkspaceSet[GET/PUT /workspace/.../user-settings
    NEU] -->|"defaults"| WorkspaceUI ``` ## Entscheidungen | Datum | Entscheidung | Begruendung | |-------|-------------|------------| | 2026-04-29 | KEIN fixer Theme-Katalog, KEIN Mandate-CI-Konzept | User-Korrektur: generisches Style-Management vom Agent gefuellt | | 2026-04-29 | Style-Schema generisch in `metadata.style`, Defaults zentral | Agent kann pro Bericht entscheiden; Renderer haben einheitliche Mapping-Schicht | | 2026-04-29 | Inline-Run-Modell statt String-basierter Content | Voraussetzung fuer "Bilder ueberall" | | 2026-04-29 | Per-Node `requireNeutralization` + `allowedModels` als Standardparameter ALLER `ai.*`-Nodes | Keine Sonder-Felder pro Node-Typ; im FlowEditor konsistent | | 2026-04-29 | `allowedModels` ergaenzt das bestehende `allowedProviders`, ersetzt es nicht | Provider = Anbieter-Ebene (existiert), Model = konkrete Modell-Ebene (neu, feiner) | | 2026-04-29 | `allowedModels` lebt in `AiCallOptions` (analog `allowedProviders`), NICHT direkt in `AiCallRequest` | Symmetrie zur bestehenden Provider-Whitelist | | 2026-04-29 | Neuer Workspace-User-Settings-Endpoint `GET/PUT /api/workspace/{instanceId}/user-settings` fuer ALLE DREI Felder (`requireNeutralization`, `allowedProviders`, `allowedModels`) | Heute werden `requireNeutralization`/`allowedProviders` pro Request aus dem Frontend mitgeliefert -- ohne Persistenz muesste der User die Settings bei jedem Browser-Open neu setzen. Konsistenz-Argument: wenn `allowedModels` persistiert wird, dann auch die anderen zwei -- sonst zwei Default-Mechanismen mit unterschiedlichem Verhalten. | | 2026-04-29 | `ServiceCenterContext.requireNeutralization` ist bereits gesetzt; analoge `allowedModels`-Propagation NICHT noetig (Frontend schickt es ohnehin pro Call) | Kontextfeld nur dort, wo Sub-Service ohne Frontend-Input es braucht | | 2026-04-29 | Whitelist-Check vor Modellwahl; leer -> Fail-fast | Compliance: Workflow soll **deterministisch** scheitern statt stillschweigend ein nicht-erlaubtes Modell zu nehmen | | 2026-04-29 | MD->JSON-Konsolidierung in `subDocumentUtility.py` | Bereits zentralerer Pfad als `_mediaTools` | | 2026-04-29 | Native XLSX-Charts via openpyxl spaeter | Heute reicht PNG-Embed; Chart-API ist eigene Komplexitaet | | 2026-04-29 | KEIN Backwards-Compat fuer altes JSON-Format | Clean-Break: alte Runs brechen, akzeptabler Trade-off fuer saubere Codebasis ohne Dispatch-Overhead | | 2026-04-29 | `WorkspaceUserSettings` in `poweron_app` (nicht `poweron_workspace`) | Dort leben User-spezifische Instanz-Daten (FeatureAccess etc.) | | 2026-04-29 | Image-Resolution-Logik verbleibt in `_mediaTools` (Post-Processing) | Nur der MD-Parser wird konsolidiert; fileId->base64-Auflosung ist kontextabhaengig (Chat-History, KnowledgeStore) | | 2026-04-29 | `documentTheme` (Plan B) wird Prompt-Hint, NICHT Renderer-Parameter | Renderer hat kein Theme-Konzept; Agent leitet Style aus Hint ab | ## Umsetzungs-Checkliste ### Phase 1 -- MD->JSON konsolidieren + Inline-Runs - [ ] `subDocumentUtility.markdownToDocumentJson` zur einzigen Quelle machen (module-level Funktion, Z. 12+). - [ ] `_mediaTools._markdownToDocumentJson` (nested in `_registerMediaTools`, Z. 27-163) ENTFERNEN. Stattdessen `subDocumentUtility.markdownToDocumentJson` aufrufen. Die Image- Resolution-Logik (Z. 241-277: fileId -> base64 aus KnowledgeStore/Chat) VERBLEIBT als Post-Processing nach dem Parser-Aufruf. - [ ] Schema-Erweiterung in `datamodels/datamodelJson.py`: - `paragraph.inlineRuns: List[InlineRun]` (KEIN Backwards-Compat- Feld `text: str` -- Clean-Break). - `bullet_list.items: List[List[InlineRun]]`. - `table.cell` neu: `cellContent: List[InlineRun]`. - Altes String-basiertes Format wird NICHT mehr unterstuetzt. - [ ] MD-Parser im konsolidierten Helper: - Inline `![alt](file:xyz)` an beliebiger Position erkennen -> `image`-Run. - Inline `[text](url)` -> `link`-Run. - `**bold**` / `*italic*` / `` `code` `` -> entsprechende Runs. - Tabellen-Zellen: voll geparst (inkl. Bild, Links, Formatierung). - [ ] Helper `_renderInlineRuns(runs, style, container)` als gemeinsame Spec; pro Renderer eigene Implementierung. ### Phase 2 -- Style-System - [ ] Defaults-Modul `gateway/modules/serviceCenter/services/serviceGeneration/styleDefaults.py` mit neutralen Default-Werten fuer alle Style-Felder. - [ ] Style-Resolver in `mainServiceGeneration.py`: `_resolveStyle(metadataStyle: dict | None) -> ResolvedStyle` (deep-merge defaults <- agentStyle). - [ ] Renderer auf Resolver umstellen: kein Renderer hat mehr eigene Default-Konstanten. - [ ] `renderDocument`-Tool (`_mediaTools.py`) Schema: - Optionaler Parameter `style` (object) wird in `metadata.style` gemappt. - Tool-Beschreibung dokumentiert das Schema mit Beispielen. ### Phase 3 -- Renderer-Refactor (generische Style-Komponenten) - [ ] DOCX (`rendererDocx.py`): - `_createTableRowXml` (~ 644-697) Header-Farbe/-Schrift aus `style.table`. - `_renderJsonImage` (~ 1016-1082) Default-Breite aus `style.image.defaultWidthPt`. - Headings aus `style.headings.hX`. - Inline-Run-Renderer fuer Paragraph/Cell. - Page-Margins / Header-Logo / Footer-Text aus `style.page`. - `templateName: corporate|minimal` deprecaten (1 Release lang warnen, dann raus). - [ ] XLSX (`rendererXlsx.py`): - `_renderJsonImage` Bildbreite aus `style.image`. - Tabellen-Header aus `style.table`. - Inline-Bilder per Cell-Anchor (siehe Phase 4). - [ ] PPTX (`rendererPptx.py`): - Slide-Size aus `style.page`. - Title/Body aus `style.headings`. - Inline-Bilder als zusaetzliche Shape (Fallback). - [ ] HTML (`rendererHtml.py`): - `lang` aus `metadata.language`. - Inline-CSS aus `style` generiert (`_generateCssStyles` umbauen). - Inline-Bilder als ``. - [ ] PDF (`rendererPdf.py`): - Sample-Style-Set aus `style`. - Page-Margins aus `style.page.marginsPt`. - Inline-Bilder als reportlab Inline-Image. ### Phase 4 -- Inline-Bilder ueberall (Renderer-Spezifika) - [ ] DOCX: Cell-Image via `cell.add_paragraph().add_run().add_picture(...)`. - [ ] DOCX/PDF: Inline-Image im Paragraph als Run-mit-Image. - [ ] XLSX: `openpyxl.drawing.image.Image` mit Cell-Anchor; pro Bild eine neue Image-Instance (openpyxl-Constraint). - [ ] HTML: `` direkt in `` / `

    ` / `

  • ` -- trivial. - [ ] PPTX: Inline-in-Text-Run nicht moeglich -> Image als zusaetzliche Shape unter dem Text-Frame, mit Warning im JSON-Output (`_renderingFallback: pptx-image-below-text`). - [ ] Tests pro Format: 1× Inline-Bild im Paragraph, 1× in Tabellenzelle, 1× in Listen-Item. ### Phase 5 -- AI-Call-Konfiguration pro Node (Datenmodell + Backend) - [ ] `gateway/modules/datamodels/datamodelAi.py`: - `AiCallOptions.allowedModels: Optional[List[str]] = None` (analog zu `allowedProviders` in Z. 164 -- KEIN neues Feld in `AiCallRequest`). - [ ] `gateway/modules/datamodels/datamodelAuto.py` (oder wo immer das `Workflow`-Modell liegt): - `Workflow.allowedModels: Optional[List[str]] = None` (analog `Workflow.allowedProviders`). - [ ] `gateway/modules/serviceCenter/services/serviceAi/mainServiceAi.py`: - Neue private Methode `_calculateEffectiveModels()` analog zu `_calculateEffectiveProviders()` (Z. ~1196). RBAC-Modelle ∩ `Workflow.allowedModels` ∩ `request.options.allowedModels`. - In `_call`/`_callStream`/`callEmbedding` analog zur Provider-Logik aufrufen und in `request.options.allowedModels` schreiben. - `_pickModel` filtert anschliessend AND-verknuepft auf Provider UND Model-Whitelist. - Fail-fast `RuntimeError` wenn die Filterkette leer wird, mit Audit-Log-Zeile (modelle, provider, rbac-permitted, workflow, node). - [ ] `_ServicesAdapter.__getattr__` in `mainServiceAi.py` (Z. 88-90) erweitern: Tuple-Liste um `"allowedModels"` ergaenzen (damit `service.allowedModels` analog `service.allowedProviders` aus dem Workflow durchgereicht wird). ### Phase 5a -- Workspace User-Settings (Persistenz fuer alle drei Felder) - [ ] Neue persistente Settings pro User × Workspace-Instanz. Tabelle `WorkspaceUserSettings` in **`poweron_app`** (dort wo `FeatureAccess`, `FeatureInstance` etc. leben; User-spezifische Instanz-Daten). Additiv via Auto-Init: - `instanceId UUID NOT NULL` - `userId UUID NOT NULL` - `requireNeutralization BOOL DEFAULT false` - `allowedProviders JSONB DEFAULT '[]'` - `allowedModels JSONB DEFAULT '[]'` - PK / Unique: (`instanceId`, `userId`). - [ ] Backend-Endpoints in `routeFeatureWorkspace.py`: - `GET /api/workspace/{instanceId}/user-settings` -- effektive Settings fuer current user (Auto-Insert mit Defaults wenn leer). - `PUT /api/workspace/{instanceId}/user-settings` -- speichern (alle drei Felder im Body). - [ ] `WorkspaceInputRequest.allowedModels: List[str] = Field(default_factory=list)` (additiv, `routeFeatureWorkspace.py` Z. 102-113). `requireNeutralization` und `allowedProviders` sind dort schon vorhanden (Z. 111-112). - [ ] In `_runWorkspaceAgent` (Z. 693+): `allowedModels` ebenso durchreichen wie heute `allowedProviders` (Z. 713-717) -- in das `request.options.allowedModels`-Feld schreiben (siehe Phase 5 Datenmodell). Pattern analog: `aiService.services.allowedModels = allowedModels`. - [ ] Frontend Workspace: - Workspace-Settings-Tab/Panel mit allen drei Feldern: - Toggle `requireNeutralization`. - Multi-Select `allowedProviders` (Optionen aus `GET /api/system/ai-models` -> distinct connectorTypes). - Multi-Select `allowedModels` (Optionen aus `GET /api/system/ai-models` -> displayNames). - Beim Workspace-Open: Settings via `GET .../user-settings` laden, Defaults in den Workspace-State setzen. - "Speichern"-Button im Settings-Panel ruft `PUT .../user- settings` mit allen drei Feldern. - Bei jedem Workspace-Call: aktuelle Settings als `WorkspaceUserInput`-Felder mitgeben (alle drei). ### Phase 5b -- AI-Nodes im FlowEditor - [ ] `gateway/modules/features/graphicalEditor/nodeDefinitions/ai.py`: - Modul-lokaler Helper `_AI_COMMON_PARAMS = [...]` mit den 2 neuen Parametern. - In ALLE 8 Eintraege von `AI_NODES` als `*_AI_COMMON_PARAMS` anhaengen. - [ ] `gateway/modules/workflows/methods/methodAi/actions/*.py`: - In jeder Action (`process.py`, `webResearch.py`, `summarizeDocument.py`, `translateDocument.py`, `convertDocument.py`, `generateDocument.py`, `generateCode.py`, `consolidate.py`): - `requireNeutralization = parameters.get("requireNeutralization")` -> in `request.requireNeutralization` setzen. - `allowedModels = parameters.get("allowedModels", [])` -> in `request.options.allowedModels` setzen. - Helper-Funktion `_applyCommonAiParams(parameters, request)` in `methodAi/_common.py` (neu) zum Reduzieren von Boilerplate. - [ ] FlowEditor (Frontend): - Neuer `frontendType: "modelMultiSelect"` -- holt verfuegbare Modelle ueber `GET /api/system/ai-models` und stellt Multi-Select dar. - Frontend-Generic-Form-Generator unterstuetzt diesen Type (Properties-Panel pro AI-Node). ### Phase 6 -- Trustee-Templates anpassen (entkoppelt vom Bau-Plan B) - [ ] In `mainTrustee.py`-Templates: neuen Node-Parameter `style`-Block setzen (Finanzreport-Defaults: dunkelblaue Headings, konservatives Calibri etc.) -- damit Trustee out-of-the-box gut aussieht. Der in Plan B gesetzte `documentTheme: "finance"` wird als Hint fuer den Agent im Prompt benutzt (NICHT als Renderer-Parameter); der Agent soll daraus seinen `style`-Block ableiten. - [ ] `requireNeutralization: true` falls Trustee-Daten sensibel (entscheidet Compliance-Doku, Default empfohlen). - [ ] `allowedModels` pro Trustee-Workflow optional pinned (z.B. nur private LLM fuer Finance-Daten). - [ ] `documentTheme`-Parameter im Budget-Template (aus Plan B) in den Prompt als Kontext-Hint einfuegen, statt als separaten Renderer-Parameter. Der Renderer kennt KEIN Theme-Konzept. ### Phase 7 -- Tests / Snapshots - [ ] Pro Format ein Snapshot-Test mit Custom-Style: `test_render_with_agent_style_.py`. - [ ] Inline-Bilder-Tests pro Format (siehe Phase 4). - [ ] AI-Gate-Tests: `test_allowed_models_filter.py` (Whitelist greift), `test_allowed_models_empty_fails.py` (Fail-fast), `test_workspace_propagates_neutralization.py`. ## Akzeptanzkriterien | # | Kriterium (Given-When-Then) | Prio | |---|-----------------------------|------| | 1 | Given Agent ruft `renderDocument` mit `style.colors.primary="#E11D48"`, When DOCX rendert, Then Headings nutzen diese Farbe | must | | 2 | Given Agent ruft `renderDocument` ohne `style`-Block, When Renderer arbeitet, Then werden neutrale Defaults verwendet (kein Crash) | must | | 3 | Given Markdown enthaelt `Bei der Sitzung ![Logo](file:abc) wurde besprochen ...`, When DOCX gerendert, Then erscheint das Bild inline mitten im Paragraph | must | | 4 | Given Markdown-Tabellenzelle `\| ![](file:xyz) \|`, When DOCX/HTML/XLSX gerendert, Then Bild liegt in der Zelle | must | | 5 | Given Markdown-Listen-Item mit Inline-Bild, When HTML gerendert, Then Bild im `
  • ` | should | | 6 | Given PPTX gerendert mit Inline-Bild im Paragraph, When Output inspiziert, Then Bild ist als separate Shape unterhalb mit Warning im Run-Log | should | | 7 | Given AI-Node mit `requireNeutralization: true`, When Workflow laeuft, Then Prompt/Context werden im AI-Gate neutralisiert (Beweis im Audit-Log) | must | | 8 | Given AI-Node mit `allowedModels: ["claude-4-7-opus"]`, When Workflow laeuft und nur dieses Modell verfuegbar, Then Call benutzt es | must | | 9 | Given AI-Node mit `allowedModels: ["nichtExistent"]`, When Workflow laeuft, Then Fail-fast mit Error-Message inkl. RBAC/Workflow/Node-Filter-Snapshot | must | | 10 | Given Workspace mit gespeicherten User-Settings `requireNeutralization=true, allowedProviders=["openai"], allowedModels=["gpt-5-mini"]`, When User Workspace neu oeffnet, Then sind ALLE drei Settings vorausgefuellt und werden bei jedem Call mitgeschickt | must | | 11 | Given Workspace mit `requireNeutralization: true`, When Sub-Agent-AI-Tools laufen, Then alle Calls werden neutralisiert (ServiceCenterContext propagiert) | must | | 12 | Given Provider-Whitelist `allowedProviders=["openai"]` UND Model-Whitelist `allowedModels=["claude-4-7-opus"]`, When Filterung laeuft, Then leere Liste -> Fail-fast (AND-Logik, nicht OR) | must | | 13 | Given alte Implementierungen `_mediaTools._markdownToDocumentJson`, When neu gebaut, Then ruft sie nur noch den Helper aus `subDocumentUtility` auf | must | ## Testplan | ID | AC | Art | Automatisiert | Repo-Pfad | Status | |----|----|-----|--------------|-----------|--------| | T1 | 1,2 | snapshot | ja | gateway/tests/serviceGeneration/test_agent_style_docx.py | pending | | T2 | 3 | integration | ja | gateway/tests/serviceGeneration/test_inline_image_paragraph.py | pending | | T3 | 4 | integration | ja | gateway/tests/serviceGeneration/test_inline_image_table_cell.py | pending | | T4 | 5 | integration | ja | gateway/tests/serviceGeneration/test_inline_image_list_item_html.py | pending | | T5 | 6 | integration | ja | gateway/tests/serviceGeneration/test_inline_image_pptx_fallback.py | pending | | T6 | 7 | integration | ja | gateway/tests/serviceAi/test_node_neutralization_flag.py | pending | | T7 | 8,9 | integration | ja | gateway/tests/serviceAi/test_allowed_models_whitelist.py | pending | | T8 | 10 | integration | ja | gateway/tests/features/workspace/test_user_settings_persistence.py | pending | | T9 | 11 | integration | ja | gateway/tests/serviceAi/test_workspace_propagates_settings.py | pending | | T10 | 12 | unit | ja | gateway/tests/serviceAi/test_effective_models_and_providers.py | pending | | T11 | 13 | unit | ja | gateway/tests/serviceCenter/test_md_to_json_consolidation.py | pending | | T12 | -- | unit | ja | frontend_nyla/src/components/flowEditor/__tests__/AiNodeProperties.test.tsx | pending | ## Links - Aktuelle Pipeline-Architektur: Subagent-Report 2026-04-29. - Renderer-Code: `gateway/modules/serviceCenter/services/serviceGeneration/renderers/`. - MD->JSON-Code: `gateway/modules/serviceCenter/services/serviceAgent/coreTools/_mediaTools.py`, `gateway/modules/serviceCenter/services/serviceGeneration/subDocumentUtility.py`. - AI-Gate: `gateway/modules/serviceCenter/services/serviceAi/mainServiceAi.py`. - Neutralisierung: `wiki/b-reference/platform/neutralization.md`. - Wiki: `wiki/b-reference/gateway/ai-agent.md`, `wiki/b-reference/gateway/agent-file-bridge.md`, `wiki/b-reference/gateway/workflow.md`. ## Abschluss - [ ] `wiki/b-reference/gateway/ai-agent.md` -- Style-System + per-call Konfiguration ergaenzen - [ ] `wiki/b-reference/gateway/workflow.md` -- AI-Node-Standardparameter dokumentieren - [ ] `wiki/b-reference/platform/neutralization.md` -- per-Node-Trigger ergaenzen (Praezedenzkette: Feature -> Workspace -> Node -> Request) - [ ] `wiki/TOPICS.md` ggf. neuer Eintrag "AI-Node-Konfiguration" - [ ] Dieses Dokument -> `z-archive/` verschoben