34 KiB
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:
- 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, harteslang="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. - 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. - 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 (
_mediaToolsundsubDocumentUtility) -- Drift-Risiko. - Agent ruft
renderReportohneaiServiceauf (_mediaTools.py282-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 vonstring, hin zuinline-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
AiCallRequestreingefuettert 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 ParameterrequireNeutralization(boolean) undallowedModels(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).
- Style: Generisches Style-Schema, vom Agent pro Render-Call
fuellbar (
- 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 aufsubDocumentUtility.markdownToDocumentJsonumleiten. Image-Resolution-Logik (Z. 241-277, loest fileId -> base64 aus KnowledgeStore/Chat) VERBLEIBT in_mediaToolsals Post- Processing-Schritt nach dem Parser-Aufruf.renderDocument-Tool-Schema ergaenzt um optionalenstyle-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 undcellContent.gateway/modules/datamodels/datamodelAi.py--AiCallOptions.allowedModels: Optional[List[str]](Whitelist; analogallowedProvidersZ. 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-- StandardparameterrequireNeutralization+allowedModelszu allenai.*-Nodes (gemeinsamer Helper_AI_COMMON_PARAMS).gateway/modules/workflows/methods/methodAi/actions/*.py-- Node- Parameter durchreichen in denAiCallRequest.gateway/modules/serviceCenter/services/serviceAgent/conversationManager.py-- Workspace-Kontext-Defaults fuerrequireNeutralization/allowedModelsaus Workspace-Config inServiceCenterContextsetzen.
- Frontend:
- FlowEditor: Pro AI-Node die zwei neuen Parameter im Properties-
Panel anzeigen (
requireNeutralizationToggle,allowedModelsMulti-Select). Hilfetexte. - Workspace-Settings: bestehender Neutralisierungs-Toggle bleibt;
neuer Multi-Select fuer
allowedModels(Default: alle verfuegbaren Modelle des Mandate). - HINWEIS: Request-Model heisst
WorkspaceInputRequest(NICHTWorkspaceUserInput; sieherouteFeatureWorkspace.pyZ. 102-113). - Style-Vorschau (klein): Optional Snippet im FlowEditor, das den
style-Parameter einesfile.create/renderDocument-Nodes visualisieren kann (deferred).
- FlowEditor: Pro AI-Node die zwei neuen Parameter im Properties-
Panel anzeigen (
- 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:  und 
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 |
| 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.requireNeutralizationexistiert (datamodelAi.pyZ. 177).AiCallOptions.allowedProvidersexistiert (datamodelAi.pyZ. 164) -- Provider-Whitelist auf Anbieter-Ebene (z.B.anthropic,openai,private-llm).Workflow.allowedProviderswird ueber__getattr__durchgereicht (mainServiceAi.pyZ. 89)._calculateEffectiveProviders()macht RBAC ∩ Workflow.allowedProviders und schreibt das inrequest.options.allowedProviders(mainServiceAi.pyZ. 175-178).WorkspaceInputRequest.allowedProviders+requireNeutralizationwerden vom Frontend pro Request mitgegeben (routeFeatureWorkspace.pyZ. 111-113) und in denServiceCenterContextgesetzt (Z. 713-719).ServiceCenterContext.requireNeutralizationexistiert (context.pyZ. 23).GET /api/system/ai-modelsliefert die verfuegbaren Modelle (routeSystem.pyZ. 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, Defaultfalse).allowedModels(list[string], Default[]= "alle").- (
allowedProviderskoennte spaeter analog folgen -- bewusst NICHT in diesem Plan, weil heute nur Workflow-Level eingestellt wird.)
- Im AI-Workspace (Default-Persistenz):
requireNeutralization: heute schon imWorkspaceUserInput-- bleibt, jetzt aber persistent als User-Default.allowedProviders: heute schon imWorkspaceUserInput-- bleibt, jetzt ebenfalls persistent als User-Default.allowedModels(NEU): zusaetzliches Feld inWorkspaceUserInput+ UI-Feld in den Workspace-Einstellungen.- Persistenz-Entscheidung (siehe Entscheidungs-Tabelle): Phase 5a
kommt mit neuem Endpoint
GET/PUT /api/workspace/{instanceId}/user- settingsfuer 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 zuallowedProviders).Workflow.allowedModels: Optional[List[str]] = None(analog zuWorkflow.allowedProviders).
- Validierung am zentralen AI-Gate (
mainServiceAi.py):requireNeutralizationfolgt bestehender OR-Logik (keine Aenderung).- Neue private Funktion
_calculateEffectiveModels()analog zu_calculateEffectiveProviders(): RBAC ∩ Workflow.allowedModels ∩ Node.allowedModels (Node setzt es ueberrequest.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.markdownToDocumentJsonzur einzigen Quelle machen (module-level Funktion, Z. 12+)._mediaTools._markdownToDocumentJson(nested in_registerMediaTools, Z. 27-163) ENTFERNEN. StattdessensubDocumentUtility.markdownToDocumentJsonaufrufen. 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- Feldtext: str-- Clean-Break). -bullet_list.items: List[List[InlineRun]]. -table.cellneu:cellContent: List[InlineRun]. - Altes String-basiertes Format wird NICHT mehr unterstuetzt. - MD-Parser im konsolidierten Helper:
- Inline
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.pymit 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 Parameterstyle(object) wird inmetadata.stylegemappt. - Tool-Beschreibung dokumentiert das Schema mit Beispielen.
Phase 3 -- Renderer-Refactor (generische Style-Komponenten)
- DOCX (
rendererDocx.py): -_createTableRowXml(~ 644-697) Header-Farbe/-Schrift ausstyle.table. -_renderJsonImage(~ 1016-1082) Default-Breite ausstyle.image.defaultWidthPt. - Headings ausstyle.headings.hX. - Inline-Run-Renderer fuer Paragraph/Cell. - Page-Margins / Header-Logo / Footer-Text ausstyle.page. -templateName: corporate|minimaldeprecaten (1 Release lang warnen, dann raus). - XLSX (
rendererXlsx.py): -_renderJsonImageBildbreite ausstyle.image. - Tabellen-Header ausstyle.table. - Inline-Bilder per Cell-Anchor (siehe Phase 4). - PPTX (
rendererPptx.py): - Slide-Size ausstyle.page. - Title/Body ausstyle.headings. - Inline-Bilder als zusaetzliche Shape (Fallback). - HTML (
rendererHtml.py): -langausmetadata.language. - Inline-CSS ausstylegeneriert (_generateCssStylesumbauen). - Inline-Bilder als<img>. - PDF (
rendererPdf.py): - Sample-Style-Set ausstyle. - Page-Margins ausstyle.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.Imagemit 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 zuallowedProvidersin Z. 164 -- KEIN neues Feld inAiCallRequest).gateway/modules/datamodels/datamodelAuto.py(oder wo immer dasWorkflow-Modell liegt): -Workflow.allowedModels: Optional[List[str]] = None(analogWorkflow.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/callEmbeddinganalog zur Provider-Logik aufrufen und inrequest.options.allowedModelsschreiben. -_pickModelfiltert anschliessend AND-verknuepft auf Provider UND Model-Whitelist. - Fail-fastRuntimeErrorwenn die Filterkette leer wird, mit Audit-Log-Zeile (modelle, provider, rbac-permitted, workflow, node)._ServicesAdapter.__getattr__inmainServiceAi.py(Z. 88-90) erweitern: Tuple-Liste um"allowedModels"ergaenzen (damitservice.allowedModelsanalogservice.allowedProvidersaus dem Workflow durchgereicht wird).
Phase 5a -- Workspace User-Settings (Persistenz fuer alle drei Felder)
- Neue persistente Settings pro User x Workspace-Instanz.
Tabelle
WorkspaceUserSettingsinpoweron_app(dort woFeatureAccess,FeatureInstanceetc. 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.pyZ. 102-113).requireNeutralizationundallowedProviderssind dort schon vorhanden (Z. 111-112).- In
_runWorkspaceAgent(Z. 693+):allowedModelsebenso durchreichen wie heuteallowedProviders(Z. 713-717) -- in dasrequest.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-SelectallowedProviders(Optionen ausGET /api/system/ai-models-> distinct connectorTypes). - Multi-SelectallowedModels(Optionen ausGET /api/system/ai-models-> displayNames). - Beim Workspace-Open: Settings viaGET .../user-settingsladen, Defaults in den Workspace-State setzen. - "Speichern"-Button im Settings-Panel ruftPUT .../user- settingsmit allen drei Feldern. - Bei jedem Workspace-Call: aktuelle Settings alsWorkspaceUserInput-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 vonAI_NODESals*_AI_COMMON_PARAMSanhaengen.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")-> inrequest.requireNeutralizationsetzen. -allowedModels = parameters.get("allowedModels", [])-> inrequest.options.allowedModelssetzen. - Helper-Funktion_applyCommonAiParams(parameters, request)inmethodAi/_common.py(neu) zum Reduzieren von Boilerplate.- FlowEditor (Frontend):
- Neuer
frontendType: "modelMultiSelect"-- holt verfuegbare Modelle ueberGET /api/system/ai-modelsund 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-Parameterstyle-Block setzen (Finanzreport-Defaults: dunkelblaue Headings, konservatives Calibri etc.) -- damit Trustee out-of-the-box gut aussieht. Der in Plan B gesetztedocumentTheme: "finance"wird als Hint fuer den Agent im Prompt benutzt (NICHT als Renderer-Parameter); der Agent soll daraus seinenstyle-Block ableiten. requireNeutralization: false(User-Entscheid: Buchhaltungsdaten duerfen nicht anonymisiert werden, sonst Fantasie-Zahlen).allowedModelspro 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  wurde besprochen ..., When DOCX gerendert, Then erscheint das Bild inline mitten im Paragraph |
must |
| 4 | Given Markdown-Tabellenzelle |  |, 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 | done |
| T2 | 3 | integration | ja | gateway/tests/serviceGeneration/test_inline_image_paragraph.py | done |
| T3 | 4 | integration | ja | gateway/tests/serviceGeneration/test_inline_image_table_cell.py | done |
| T4 | 5 | integration | ja | gateway/tests/serviceGeneration/test_inline_image_list_item_html.py | done |
| T5 | 6 | integration | ja | gateway/tests/serviceGeneration/test_inline_image_pptx_fallback.py | done |
| T6 | 7 | integration | ja | gateway/tests/serviceAi/test_node_neutralization_flag.py | done |
| T7 | 8,9 | integration | ja | gateway/tests/serviceAi/test_allowed_models_whitelist.py | done |
| T8 | 10 | integration | ja | gateway/tests/features/workspace/test_user_settings_persistence.py | done |
| T9 | 11 | integration | ja | gateway/tests/serviceAi/test_workspace_propagates_settings.py | done |
| T10 | 12 | unit | ja | gateway/tests/serviceAi/test_effective_models_and_providers.py | done |
| T11 | 13 | unit | ja | gateway/tests/serviceCenter/test_md_to_json_consolidation.py | done |
| T12 | -- | unit | ja | frontend_nyla/src/components/flowEditor/tests/AiNodeProperties.test.tsx | deferred |
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 (TODO: bei naechster Wiki-Review-Runde)wiki/b-reference/gateway/workflow.md-- AI-Node-Standardparameter dokumentieren (TODO: bei naechster Wiki-Review-Runde)wiki/b-reference/platform/neutralization.md-- per-Node-Trigger ergaenzen (TODO: bei naechster Wiki-Review-Runde)wiki/TOPICS.mdggf. neuer Eintrag "AI-Node-Konfiguration"- Dieses Dokument ->
4-done/verschoben