wiki/c-work/1-plan/2026-04-ai-reports-theming-and-pipeline.md
2026-04-29 23:12:53 +02:00

34 KiB
Raw Blame History

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):

{
  "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

{ "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 <img> trivial <img> in <td> trivial <img> in <li> --
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)

# 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

flowchart LR
  Node[ai.* Node Parameters] -->|"requireNeutralization, allowedModels"| Action[methodAi/actions/*.py]
  Action -->|"sets request.options.allowedModels<br/>+ request.requireNeutralization"| Gate[mainServiceAi._call]
  Gate --> EffMod[_calculateEffectiveModels<br/>RBAC AND Workflow AND Node]
  Gate --> EffProv[_calculateEffectiveProviders<br/>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<br/>requireNeutralization+allowedModels] -->|"per-request"| Ctx[ServiceCenterContext]
  WorkspaceSet[GET/PUT /workspace/.../user-settings<br/>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 <img>.
  • 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: <img> direkt in <td> / <p> / <li> -- 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.allowedModelsrequest.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_<format>.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 <li> 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
  • 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