# Überarbeitetes Implementierungskonzept: AI Workflow System ## Übersicht Dieses Dokument definiert ein überarbeitetes, klares Implementierungskonzept für das AI Workflow System, das alle identifizierten Probleme adressiert und einen korrekten Metadatenfluss durch die gesamte Pipeline sicherstellt. ## Kernprinzipien 1. **Single Source of Truth**: Alle Dokument-Verarbeitungslogik ist im AI Service Center zentralisiert 2. **Klarer Metadatenfluss**: Jeder ContentPart trägt vollständige Metadaten über Herkunft, Format und Verwendungszweck 3. **Format-Klarheit**: ContentParts zeigen explizit ihr Format an (reference, object oder extracted text) 4. **Intent-getriebene Verarbeitung**: Dokument-Verarbeitung wird durch explizite Intent-Analyse gesteuert 5. **Einheitlicher AI-Call**: Alle AI-Actions verwenden denselben zugrundeliegenden AI-Call-Mechanismus, unterscheiden sich nur in Parametern 6. **Harmonisierte Debug-Logs**: Alle Debug-Datei-Aufrufe sind vereinheitlicht und konsistent 7. **Korrekte ChatLog-Hierarchie**: Alle ChatLog-Nachrichten haben korrekte Parent/Child-Referenzen --- ## Workflow-Phasen ### Phase 1+2: User Input, Intent-Analyse & Komplexitäts-Erkennung (Kombiniert) **Location**: `workflowManager._sendFirstMessage()` + `workflowProcessor.detectComplexity()` **Was passiert:** 1. User sendet Prompt mit Dokumenten 2. **Kombinierte AI-Analyse** in einem AI-Call: - **Intent-Analyse**: Identifiziert User-Intentionen und extrahiert Context vom Prompt in weitere Dokumente - **Komplexitäts-Erkennung**: Bestimmt Fast Track vs Regular Track 3. Resultat: Klare User-Intention + Dokumente + Komplexitäts-Bewertung **Kombinierter AI-Call:** ```python async def _analyzeUserInputAndComplexity( self, userPrompt: str, documents: List[ChatDocument] ) -> Dict[str, Any]: """ Kombinierte Analyse: Intent + Komplexität in einem AI-Call. """ analysisPrompt = f""" Analysiere die User-Anfrage und bestimme in einem Durchgang: 1. detectedLanguage: ISO 639-1 Sprachcode (z.B. de, en) 2. normalizedRequest: Vollständige, explizite Umformulierung der User-Anfrage 3. intent: Kurze Kern-Anfrage für High-Level-Routing 4. contextItems: Große Datenblöcke, die als separate Dokumente extrahiert werden sollen 5. complexity: "simple" | "moderate" | "complex" 6. needsWorkflowHistory: bool (ob Workflow-History benötigt wird) 7. fastTrack: bool (ob Fast Track möglich ist) User-Anfrage: "{userPrompt}" Dokumente: {len(documents)} Dokumente vorhanden Antworte mit JSON: {{ "detectedLanguage": "de", "normalizedRequest": "...", "intent": "...", "contextItems": [...], "complexity": "complex", "needsWorkflowHistory": false, "fastTrack": false }} """ # Debug-Log self.services.utils.writeDebugFile(analysisPrompt, "user_input_analysis_prompt") # AI-Call aiResponse = await self.services.ai.callAiContent( prompt=analysisPrompt, options=AiCallOptions(operationType=OperationTypeEnum.DATA_GENERATE), outputFormat="json" ) # Debug-Log self.services.utils.writeDebugFile(aiResponse.content, "user_input_analysis_response") # Parse Result result = json.loads(self.services.utils.jsonExtractString(aiResponse.content)) return result ``` **Vorteile der Kombination:** - **Weniger AI-Calls**: Ein Call statt zwei - **Konsistente Analyse**: Beide Analysen verwenden denselben Kontext - **Bessere Performance**: Schneller, weniger Kosten - **Klarere Struktur**: Ein Analyse-Schritt statt zwei **Output:** - `normalizedRequest`: User-Prompt ohne Context - `detectedLanguage`: Sprachcode - `contextItems`: Große Content-Blöcke als separate Dokumente - `intent`: High-Level-Kern-Anfrage - `complexity`: "simple" | "moderate" | "complex" - `needsWorkflowHistory`: bool - `fastTrack`: bool **Dokumente nach Phase 1+2:** - Original User-uploaded Dokumente - Context-extrahierten Dokumente (aus Prompt-Analyse) - Alle Dokumente haben: `fileId`, `fileName`, `mimeType`, `fileSize` **Code-Referenz:** ```362:530:poweron/gateway/modules/workflows/workflowManager.py # Analyze the user's input to detect language, normalize request, extract intent, and offload bulky context into documents ``` --- ### Phase 3: Task Planning **Location**: `workflowProcessor.generateTaskPlan()` **Was passiert:** - Trennt verschiedene User-Intentionen in einzelne Tasks - Jeder Task repräsentiert ein distinctes Ziel - Tasks sind unabhängig, können aber auf Ergebnisse anderer Tasks referenzieren **Task-Struktur:** ```python Task { id: str objective: str # Was soll erreicht werden requiredDocuments: List[DocumentReference] # Welche Dokumente werden benötigt expectedOutput: str # Erwartetes Output-Format/Typ } ``` **Code-Referenz:** ```42:103:poweron/gateway/modules/workflows/processing/workflowProcessor.py async def generateTaskPlan(self, userInput: str, workflow: ChatWorkflow) -> TaskPlan: ``` --- ### Phase 4: Task Execution (Iterative Action Execution) **Location**: `modeDynamic.executeTask()` **Was passiert:** - Identifiziert und führt iterativ Actions aus - Jede Action erhält: - Task-Objective - Verfügbare Dokumente (von vorherigen Actions) - **Context von vergangenen Rounds und Tasks**: - `context.executedActions`: Liste aller bereits ausgeführten Actions - `context.workflowHistory`: History aus vorherigen Rounds - `context.taskHistory`: History aus vorherigen Tasks - `AVAILABLE_DOCUMENTS_INDEX`: Alle verfügbaren Dokumente aus vorherigen Actions - `context.nextActionGuidance`: Vorgabe für nächste Action (von Refinement-Entscheidung) **Action Types:** - `ai.*` Actions: Alle verwenden unified AI Service Center - `context.*` Actions: Dokument-Extraktion/Management - `jira.*`, `sharepoint.*`, etc.: Externe Service-Actions **Action Planner & Refinement Planner:** - **Action Planner** (`_planSelect`): Wählt nächste Action basierend auf: - Task-Objective - Verfügbare Dokumente (`AVAILABLE_DOCUMENTS_INDEX`) - Action-History (`context.executedActions`) - Workflow-History (`context.workflowHistory`) - Task-History (`context.taskHistory`) - `context.nextActionGuidance` (falls vorhanden) - **Refinement Planner**: Entscheidet über nächste Action nach Content-Validation: - Nutzt Validation-Ergebnisse - Kann `context.nextActionGuidance` setzen - Berücksichtigt Action-History für adaptive Entscheidungen **Diese Phase bleibt unverändert** - sie funktioniert korrekt. **Code-Referenz:** ```435:640:poweron/gateway/modules/workflows/processing/modes/modeDynamic.py async def _actExecute(self, context, selection, taskStep, workflow, step): ``` **Context-Struktur:** ```python TaskContext { workflowId: str executedActions: List[Dict] # [{action: "...", parameters: {...}, step: 1}, ...] workflowHistory: List[Dict] # History aus vorherigen Rounds taskHistory: List[Dict] # History aus vorherigen Tasks nextActionGuidance: Optional[Dict] # Vorgabe für nächste Action # ... weitere Context-Felder } ``` --- ### Phase 5: AI Action Execution (Zentrale Verarbeitung) **Location**: `serviceAi.mainServiceAi.callAiContent()` **Dies ist der Kern des überarbeiteten Konzepts.** Alle AI-Actions (`ai.process`, `ai.generateDocument`, `ai.summarizeDocument`, etc.) routen durch diese einzige Funktion. **Kernprinzip**: AI-Actions unterscheiden sich nur in **Parametern**, nicht in **Logik**. Die Dokument-Erstellungslogik ist hier zentralisiert. --- ## Phase 5 Detailliert: AI Service Center Verarbeitung ### 5A: Dokument-Intent-Klärung **Was passiert:** - Für jedes Dokument wird dessen Verwendungszweck bestimmt: - **Extract**: Content-Extraktion benötigt (Text, Struktur, etc.) - **Reference**: Dokument-Referenz/Attachment (keine Extraktion) - **Render**: Image/Binary soll als-is gerendert werden **Input:** - `documentList`: Liste der zu verarbeitenden Dokumente - `userPrompt`: User-Anfrage - `actionParameters`: Action-spezifische Parameter (z.B. `resultType`, `outputFormat`) **Process:** ```python async def _clarifyDocumentIntents( self, documents: List[ChatDocument], userPrompt: str, actionParameters: Dict[str, Any], parentOperationId: str # Für ChatLog-Hierarchie ) -> List[DocumentIntent]: """ Analysiert, welche Dokumente Extraktion vs Referenz benötigen. Gibt DocumentIntent für jedes Dokument zurück. """ # Erstelle Operation-ID für Intent-Analyse intentOperationId = f"{parentOperationId}_intent_analysis" # Starte ChatLog mit Parent-Referenz self.services.chat.progressLogStart( intentOperationId, "Document Intent Analysis", "Intent Analysis", f"Analyzing {len(documents)} documents", parentOperationId=parentOperationId # Parent-Referenz! ) try: # AI-basierte Analyse des User-Prompts + Dokumente intentPrompt = self._buildIntentAnalysisPrompt(userPrompt, documents, actionParameters) # Debug-Log (harmonisiert) self.services.utils.writeDebugFile(intentPrompt, "document_intent_analysis_prompt") # AI-Call aiResponse = await self.services.ai.callAiContent( prompt=intentPrompt, options=AiCallOptions(operationType=OperationTypeEnum.DATA_GENERATE), outputFormat="json", parentOperationId=intentOperationId # Parent-Referenz für AI-Call-Logs ) # Debug-Log (harmonisiert) self.services.utils.writeDebugFile(aiResponse.content, "document_intent_analysis_response") # Parse Result intentsData = json.loads(self.services.utils.jsonExtractString(aiResponse.content)) documentIntents = [DocumentIntent(**intent) for intent in intentsData.get("intents", [])] # ChatLog abschließen self.services.chat.progressLogFinish(intentOperationId, True) return documentIntents except Exception as e: self.services.chat.progressLogFinish(intentOperationId, False) raise ``` **Output:** ```python [ DocumentIntent( documentId="doc_1", intents=["extract"], extractionPrompt="Extract all text content, preserving structure", reasoning="User needs text content for document generation" ), DocumentIntent( documentId="doc_2", intents=["render"], extractionPrompt=None, reasoning="Image should be rendered as visual element" ), DocumentIntent( documentId="doc_3", intents=["reference"], extractionPrompt=None, reasoning="Document is only used as reference, no extraction needed" ) ] ``` **⚠️ WICHTIG: Unterschied zum alten Purpose-System** Das alte System in `subDocumentPurposeAnalyzer.py` verwendet viele spezifische "purpose"-Tags (`extract_text_content`, `include_image`, `analyze_image_vision`, etc.) und muss durch dieses einfache DocumentIntent-System ersetzt werden. | Alt (subDocumentPurposeAnalyzer) | Neu (DocumentIntent) | |-----------------------------------|----------------------| | `purpose: "extract_text_content"` | `intents: ["extract"]` | | `purpose: "include_image"` | `intents: ["render"]` | | `purpose: "analyze_image_vision"` | `intents: ["extract"]` mit `extractionMethod="vision"` | | `purpose: "use_as_reference"` | `intents: ["reference"]` | | **Ein Purpose pro Dokument** | **Mehrere Intents pro Dokument möglich!** | | Viele spezifische Tags | Nur 3 Basis-Intents: extract, render, reference | **Intent-Analyse-Prompt-Format:** ``` USER REQUEST: {userPrompt} DOCUMENTS TO ANALYZE: {documentList} TASK: For each document, determine its intents (can be multiple): - "extract": Content extraction needed (text, structure, OCR, etc.) - "render": Image/binary should be rendered as-is (visual element) - "reference": Document reference/attachment (no extraction, just reference) RETURN JSON: { "intents": [ { "documentId": "doc_1", "intents": ["extract"], # Array - can contain multiple! "extractionPrompt": "Extract all text content, preserving structure", "reasoning": "User needs text content for document generation" }, { "documentId": "doc_2", "intents": ["extract", "render"], # Both! Image needs text extraction AND visual rendering "extractionPrompt": "Extract text content from image using vision AI", "reasoning": "Image contains text that needs extraction, but also should be rendered visually" }, { "documentId": "doc_3", "intents": ["reference"], "extractionPrompt": null, "reasoning": "Document is only used as reference, no extraction needed" } ] } ``` **Debug-Logging (harmonisiert):** ```python # Immer ohne Checks - Funktion ist IMMER verfügbar self.services.utils.writeDebugFile( json.dumps([intent.dict() for intent in documentIntents], indent=2), "document_intent_analysis_result" ) ``` --- ### 5B: Content-Extraktion & Vorbereitung **Was passiert:** - Extrahiert Content von Dokumenten, die Extraktion benötigen - Bereitet ContentParts mit vollständigen Metadaten vor - Behandelt drei Content-Formate explizit **Content Part Formate:** Jeder `ContentPart` kann in einem von drei Formaten sein: 1. **Document Reference** (`contentFormat="reference"`): ```python ContentPart( id="part_ref_1", typeGroup="reference", mimeType="application/pdf", data="", # Leer - nur Referenz metadata={ "contentFormat": "reference", "documentId": "doc_1", "documentReference": "docItem:doc_1:document.pdf", "intent": "reference", # Wo dieser Content benötigt wird "usageHint": "Include as attachment in section X" } ) ``` 2. **Object/Binary** (`contentFormat="object"`): ```python ContentPart( id="part_obj_1", typeGroup="image", mimeType="image/png", data="base64encodeddata...", # Base64-kodiertes Binary metadata={ "contentFormat": "object", "documentId": "doc_2", "intent": "render", # Wo dieser Content benötigt wird "usageHint": "Render as image in section Y", "originalFileName": "logo.png" } ) ``` 3. **Extracted Text** (`contentFormat="extracted"`): ```python ContentPart( id="part_ext_1", typeGroup="text", mimeType="text/plain", data="Extracted text content...", # Extrahierter Text metadata={ "contentFormat": "extracted", "documentId": "doc_1", "extractionPrompt": "Extract all text content", "intent": "extract", # Wo dieser Content benötigt wird "usageHint": "Use in paragraph sections", "extractionMethod": "vision" # oder "text", "ocr", etc. } ) ``` **Process:** ```python async def _extractAndPrepareContent( self, documents: List[ChatDocument], documentIntents: List[DocumentIntent], parentOperationId: str # Für ChatLog-Hierarchie ) -> List[ContentPart]: """ Extrahiert Content basierend auf Intents und bereitet ContentParts mit Metadaten vor. Gibt Liste von ContentParts im passenden Format zurück. WICHTIG: Ein Dokument kann mehrere ContentParts erzeugen, wenn mehrere Intents vorhanden sind. Beispiel: Bild mit intents=["extract", "render"] erzeugt: - ContentPart(contentFormat="object", ...) für Rendering - ContentPart(contentFormat="extracted", ...) für Text-Analyse """ # Erstelle Operation-ID für Extraktion extractionOperationId = f"{parentOperationId}_content_extraction" # Starte ChatLog mit Parent-Referenz self.services.chat.progressLogStart( extractionOperationId, "Content Extraction", "Extraction", f"Extracting from {len(documents)} documents", parentOperationId=parentOperationId # Parent-Referenz! ) try: allContentParts = [] for document in documents: intent = getIntentForDocument(document.id, documentIntents) # WICHTIG: Prüfe alle Intents - ein Dokument kann mehrere ContentParts erzeugen if "reference" in intent.intents: # Erstelle Reference ContentPart contentPart = ContentPart( id=f"ref_{document.id}", typeGroup="reference", mimeType=document.mimeType, data="", metadata={ "contentFormat": "reference", "documentId": document.id, "documentReference": f"docItem:{document.id}:{document.fileName}", "intent": "reference", "usageHint": f"Reference document: {document.fileName}" } ) allContentParts.append(contentPart) # WICHTIG: "render" und "extract" können beide vorhanden sein! # In diesem Fall erzeugen wir BEIDE ContentParts if "render" in intent.intents: # Für Images/Binary: extrahiere als Object if document.mimeType.startswith("image/") or isBinary(document.mimeType): # Lade Binary-Daten binaryData = await self.services.interfaceDbComponent.getFileData(document.fileId) base64Data = base64.b64encode(binaryData).decode('utf-8') contentPart = ContentPart( id=f"obj_{document.id}", typeGroup="image" if document.mimeType.startswith("image/") else "binary", mimeType=document.mimeType, data=base64Data, metadata={ "contentFormat": "object", "documentId": document.id, "intent": "render", "usageHint": f"Render as visual element: {document.fileName}", "originalFileName": document.fileName, # Verknüpfung zu extracted Part (falls vorhanden) "relatedExtractedPartId": f"ext_{document.id}" if "extract" in intent.intents else None } ) allContentParts.append(contentPart) if "extract" in intent.intents: # Extrahiere Content mit Extraction Service extractionPrompt = intent.extractionPrompt or "Extract all content from the document" # Debug-Log (harmonisiert) self.services.utils.writeDebugFile( extractionPrompt, f"content_extraction_prompt_{document.id}" ) # Führe Extraktion aus extractedResults = await self.services.extraction.extractContent( [document], ExtractionOptions( prompt=extractionPrompt, mergeStrategy=MergeStrategy(...) ), operationId=extractionOperationId, # Für ChatLog-Hierarchie parentOperationId=extractionOperationId # Parent-Referenz! ) # Konvertiere extrahierte Ergebnisse zu ContentParts mit Metadaten for extracted in extractedResults: for part in extracted.parts: # Markiere als extracted Format part.metadata.update({ "contentFormat": "extracted", "documentId": document.id, "extractionPrompt": extractionPrompt, "intent": "extract", "usageHint": f"Use extracted content from {document.fileName}", # Verknüpfung zu object Part (falls vorhanden) "relatedObjectPartId": f"obj_{document.id}" if "render" in intent.intents else None }) # Stelle sicher, dass ID eindeutig ist (falls object Part existiert) if "render" in intent.intents: part.id = f"ext_{document.id}_{part.id}" allContentParts.append(part) # Debug-Log (harmonisiert) self.services.utils.writeDebugFile( json.dumps([part.dict() for part in allContentParts], indent=2), "content_extraction_result" ) # ChatLog abschließen self.services.chat.progressLogFinish(extractionOperationId, True) return allContentParts except Exception as e: self.services.chat.progressLogFinish(extractionOperationId, False) raise ``` **Wichtige Erweiterung: Mehrfache ContentParts pro Dokument** ✅ **GRUNDLEGENDES DESIGN** **Dies ist das grundlegende Design!** Wenn ein Dokument mehrere Intents hat (z.B. `["extract", "render"]`), werden **mehrere ContentParts** erzeugt: **Beispiel: Bild mit `intents=["extract", "render"]`** ```python # ContentPart 1: Object für Rendering ContentPart( id="obj_img_1", contentFormat="object", data="base64...", metadata={ "documentId": "img_1", "intent": "render", "relatedExtractedPartId": "ext_img_1" # Verknüpfung } ) # ContentPart 2: Extracted für Text-Analyse ContentPart( id="ext_img_1", contentFormat="extracted", data="This image shows...", metadata={ "documentId": "img_1", "intent": "extract", "extractionMethod": "vision", "relatedObjectPartId": "obj_img_1" # Verknüpfung } ) ``` **Verknüpfung zwischen ContentParts:** - `relatedExtractedPartId`: Object-Part verweist auf extracted Part - `relatedObjectPartId`: Extracted Part verweist auf object Part - Ermöglicht Generation-Prompt, beide Formate zu verwenden **Spezielle Behandlung: Bereits extrahierter Content** Wenn ein Dokument bereits extrahierten Content enthält (von vorheriger Action): ```python # Prüfe Metadaten auf skipExtraction Flag if part.metadata.get("skipExtraction", False): # Content ist bereits extrahiert - verwende as-is # Stelle nur sicher, dass Metadaten vollständig sind part.metadata.update({ "contentFormat": "extracted", "sourceAction": part.metadata.get("sourceAction", "unknown"), "isPreExtracted": True }) # Verwende direkt ohne Re-Extraktion ``` **Debug-Logging (harmonisiert):** ```python # Immer ohne Checks - Funktion ist IMMER verfügbar self.services.utils.writeDebugFile(extractionPrompt, "content_extraction_prompt") self.services.utils.writeDebugFile( json.dumps([part.dict() for part in allContentParts], indent=2), "content_extraction_result" ) ``` --- ### 5C: Generierungs-Struktur-Definition **Was passiert:** - Definiert die Struktur des Result-JSON - Spezifiziert, welcher Content in welche Sections geht - Definiert Format für jede Section - **Unterstützt strukturierte Datenextraktion** (z.B. CSV mit spezifischen Spalten) **Input:** - `userPrompt`: User-Anfrage - `contentParts`: Alle vorbereiteten ContentParts mit Metadaten - `outputFormat`: Ziel-Format (html, docx, pdf, etc.) **Process:** ```python async def _generateStructure( self, userPrompt: str, contentParts: List[ContentPart], outputFormat: str, parentOperationId: str # Für ChatLog-Hierarchie ) -> Dict[str, Any]: """ Generiert Dokument-Struktur mit Sections. Jede Section spezifiziert: - Welcher Content sollte in dieser Section sein - Welche ContentParts zu verwenden sind - Format für jeden ContentPart """ # Erstelle Operation-ID für Struktur-Generierung structureOperationId = f"{parentOperationId}_structure_generation" # Starte ChatLog mit Parent-Referenz self.services.chat.progressLogStart( structureOperationId, "Structure Generation", "Structure", f"Generating structure for {outputFormat}", parentOperationId=parentOperationId # Parent-Referenz! ) try: # Baue Struktur-Prompt mit Content-Index structurePrompt = self._buildStructurePrompt( userPrompt=userPrompt, contentParts=contentParts, # Inkludiere Metadaten für jeden Part outputFormat=outputFormat ) # Debug-Log (harmonisiert) self.services.utils.writeDebugFile(structurePrompt, "document_generation_structure_prompt") # AI-Call für Struktur-Generierung aiResponse = await self.services.ai.callAiContent( prompt=structurePrompt, options=AiCallOptions( operationType=OperationTypeEnum.DATA_GENERATE, resultFormat="json" ), outputFormat="json", parentOperationId=structureOperationId # Parent-Referenz für AI-Call-Logs ) # Debug-Log (harmonisiert) self.services.utils.writeDebugFile(aiResponse.content, "document_generation_structure_response") # Parse Struktur (keine Validierung - klarer Code ohne zusätzliche Komplexität) structure = json.loads(self.services.utils.jsonExtractString(aiResponse.content)) # ChatLog abschließen self.services.chat.progressLogFinish(structureOperationId, True) return structure except Exception as e: self.services.chat.progressLogFinish(structureOperationId, False) raise ``` **Struktur-Prompt-Format:** **Standard-Format (für Dokument-Generierung):** ``` USER REQUEST: {userPrompt} AVAILABLE CONTENT PARTS: {contentPartsIndex} Für jeden ContentPart: - id: {part.id} - format: {part.metadata.contentFormat} # reference, object, oder extracted - type: {part.typeGroup} - source: {part.metadata.documentId} - usage hint: {part.metadata.usageHint} - data preview: {preview von part.data wenn extracted, oder "reference" oder "base64 object"} TASK: Generiere Dokument-Struktur mit Sections. Für jede Section, spezifiziere: - section id - content_type (heading, paragraph, image, table, etc.) - contentPartIds: [Liste von ContentPart-IDs zu verwenden] - contentFormat: Wie jeder ContentPart zu verwenden ist (reference, object, extracted) - generation_hint: Was AI für diese Section generieren soll - elements: [] (leer, wird in nächster Phase gefüllt) ``` **Strukturierte Datenextraktion (z.B. CSV):** ``` USER REQUEST: {userPrompt} AVAILABLE CONTENT PARTS: {contentPartsIndex} ERFORDERLICHE FELDER/SPALTEN: {requiredFields} # z.B. ["date", "shop", "CHF", "VAT", "Description"] TASK: Generiere Struktur für strukturierte Datenextraktion. Die Struktur muss folgende Felder enthalten: {requiredFields} Für jedes Feld, spezifiziere: - field_name: Name des Feldes - contentPartIds: [Liste von ContentPart-IDs, die Daten für dieses Feld enthalten] - extraction_hint: Wie Daten aus ContentParts extrahiert werden sollen - data_type: Erwarteter Datentyp (string, number, date, etc.) Die Struktur sollte ein Array von Datensätzen sein, wobei jeder Datensatz alle Felder enthält. **WICHTIG**: Phase 5C unterstützt explizit strukturierte Datenextraktion für Formate wie CSV, JSON, XLSX. ``` **Beispiel für strukturierte Datenextraktion (Expenses CSV):** ```json { "metadata": { "title": "Expenses Overview", "outputFormat": "csv", "fields": ["date", "shop", "CHF", "VAT", "Description"] }, "documents": [{ "id": "expenses_csv", "title": "Expenses CSV", "filename": "expenses.csv", "sections": [ { "id": "data_extraction", "content_type": "table", "generation_hint": "Extract structured expense data from all content parts", "contentPartIds": ["ext_pdf_1", "ext_pdf_2", ..., "ext_pdf_10"], "contentFormats": { "ext_pdf_1": "extracted", "ext_pdf_2": "extracted", ... }, "extractionFields": { "date": { "contentPartIds": ["ext_pdf_1", "ext_pdf_2", ...], "extractionHint": "Extract date from receipt/invoice", "dataType": "date" }, "shop": { "contentPartIds": ["ext_pdf_1", "ext_pdf_2", ...], "extractionHint": "Extract shop/vendor name", "dataType": "string" }, "CHF": { "contentPartIds": ["ext_pdf_1", "ext_pdf_2", ...], "extractionHint": "Extract amount in CHF", "dataType": "number" }, "VAT": { "contentPartIds": ["ext_pdf_1", "ext_pdf_2", ...], "extractionHint": "Extract VAT amount", "dataType": "number" }, "Description": { "contentPartIds": ["ext_pdf_1", "ext_pdf_2", ...], "extractionHint": "Extract item description", "dataType": "string" } }, "elements": [] } ] }] } ``` **Output-Struktur:** ```json { "metadata": { "title": "Document Title", "language": "de" }, "documents": [{ "id": "doc_1", "title": "Document Title", "filename": "document.html", "sections": [ { "id": "section_1", "content_type": "heading", "generation_hint": "Main title", "contentPartIds": [], "elements": [] }, { "id": "section_2", "content_type": "paragraph", "generation_hint": "Introduction paragraph", "contentPartIds": ["part_ext_1"], "contentFormats": { "part_ext_1": "extracted" # Verwende extrahierten Text }, "elements": [] }, { "id": "section_3", "content_type": "image", "generation_hint": "Logo image", "contentPartIds": ["part_obj_1"], "contentFormats": { "part_obj_1": "object" # Verwende base64 Object }, "elements": [] }, { "id": "section_4", "content_type": "paragraph", "generation_hint": "Reference to attachment", "contentPartIds": ["part_ref_1"], "contentFormats": { "part_ref_1": "reference" # Verwende Dokument-Referenz }, "elements": [] } ] }] } ``` --- ### 5D: Struktur-Abfüllen **Was passiert:** - Füllt jede Section mit tatsächlichem Content - Verwendet passendes Format basierend auf `contentFormat` Metadaten - Generiert AI-Content wo nötig **Process:** ```python async def _fillStructure( self, structure: Dict[str, Any], contentParts: List[ContentPart], userPrompt: str, parentOperationId: str # Für ChatLog-Hierarchie ) -> Dict[str, Any]: """ Füllt Struktur mit tatsächlichem Content. Für jede Section: - Wenn contentPartIds spezifiziert: Verwende ContentParts im spezifizierten Format - Wenn generation_hint spezifiziert: Generiere AI-Content **Implementierungsdetails:** - Sections werden **parallel generiert**, wenn möglich (Performance-Optimierung) - Fehlerhafte Sections werden mit Fehlermeldung gerendert (kein Abbruch des gesamten Prozesses) """ # Erstelle Operation-ID für Struktur-Abfüllen fillOperationId = f"{parentOperationId}_structure_filling" # Starte ChatLog mit Parent-Referenz self.services.chat.progressLogStart( fillOperationId, "Structure Filling", "Filling", f"Filling {len(structure.get('documents', [{}])[0].get('sections', []))} sections", parentOperationId=parentOperationId # Parent-Referenz! ) try: filledStructure = copy.deepcopy(structure) # Sammle alle Sections für parallele Verarbeitung sections_to_process = [] for doc in filledStructure.get("documents", []): for section in doc.get("sections", []): sections_to_process.append((doc, section)) # Parallele Section-Generierung import asyncio section_tasks = [] for doc, section in sections_to_process: section_tasks.append(self._fillSection(doc, section, contentParts, userPrompt, fillOperationId)) # Führe alle Sections parallel aus section_results = await asyncio.gather(*section_tasks, return_exceptions=True) # Verarbeite Ergebnisse (inkl. Fehlerbehandlung) for (doc, section), result in zip(sections_to_process, section_results): if isinstance(result, Exception): # Fehlerhafte Section mit Fehlermeldung rendern section["elements"] = [{ "type": "error", "message": f"Error generating section {section.get('id')}: {str(result)}", "sectionId": section.get("id") }] logger.error(f"Error filling section {section.get('id')}: {str(result)}") else: section["elements"] = result # Alte sequenzielle Implementierung (ersetzt durch parallele Version oben): # for doc in filledStructure.get("documents", []): # for section in doc.get("sections", []): sectionId = section.get("id") contentPartIds = section.get("contentPartIds", []) contentFormats = section.get("contentFormats", {}) generationHint = section.get("generation_hint") elements = [] # Verarbeite ContentParts for partId in contentPartIds: part = findContentPartById(partId, contentParts) if not part: continue contentFormat = contentFormats.get(partId, part.metadata.get("contentFormat")) if contentFormat == "reference": # Füge Dokument-Referenz hinzu elements.append({ "type": "reference", "documentReference": part.metadata.get("documentReference"), "label": part.metadata.get("usageHint", part.label) }) elif contentFormat == "object": # Füge base64 Object hinzu elements.append({ "type": part.typeGroup, # "image", "binary", etc. "base64Data": part.data, "mimeType": part.mimeType, "altText": part.metadata.get("usageHint", part.label) }) elif contentFormat == "extracted": # Füge extrahierten Text hinzu (kann in AI-Generierungs-Prompt verwendet werden) elements.append({ "type": "extracted_text", "content": part.data, "source": part.metadata.get("documentId"), "extractionPrompt": part.metadata.get("extractionPrompt") }) # Generiere AI-Content wenn nötig if generationHint: generationPrompt = self._buildSectionGenerationPrompt( section=section, contentParts=[findContentPartById(pid, contentParts) for pid in contentPartIds], userPrompt=userPrompt, generationHint=generationHint ) # Debug-Log (harmonisiert) self.services.utils.writeDebugFile( generationPrompt, f"section_generation_prompt_{sectionId}" ) # Erstelle Operation-ID für Section-Generierung sectionOperationId = f"{fillOperationId}_section_{sectionId}" # Starte ChatLog mit Parent-Referenz self.services.chat.progressLogStart( sectionOperationId, "Section Generation", "Section", f"Generating section {sectionId}", parentOperationId=fillOperationId # Parent-Referenz! ) try: # Generiere Content aiResponse = await self.services.ai.callAiContent( prompt=generationPrompt, options=AiCallOptions( operationType=OperationTypeEnum.DATA_GENERATE, resultFormat="json" ), outputFormat="json", parentOperationId=sectionOperationId # Parent-Referenz für AI-Call-Logs ) # Debug-Log (harmonisiert) self.services.utils.writeDebugFile( aiResponse.content, f"section_generation_response_{sectionId}" ) # Parse und füge zu elements hinzu generatedElements = json.loads( self.services.utils.jsonExtractString(aiResponse.content) ) if isinstance(generatedElements, list): elements.extend(generatedElements) elif isinstance(generatedElements, dict) and "elements" in generatedElements: elements.extend(generatedElements["elements"]) # ChatLog abschließen self.services.chat.progressLogFinish(sectionOperationId, True) except Exception as e: # Fehlerhafte Section mit Fehlermeldung rendern (kein Abbruch!) self.services.chat.progressLogFinish(sectionOperationId, False) elements.append({ "type": "error", "message": f"Error generating section {sectionId}: {str(e)}", "sectionId": sectionId }) logger.error(f"Error generating section {sectionId}: {str(e)}") # NICHT raise - Section wird mit Fehlermeldung gerendert section["elements"] = elements # ChatLog abschließen self.services.chat.progressLogFinish(fillOperationId, True) return filledStructure except Exception as e: self.services.chat.progressLogFinish(fillOperationId, False) raise ``` **Section-Generierungs-Prompt-Format:** ``` USER REQUEST: {userPrompt} SECTION TO GENERATE: {generationHint} AVAILABLE CONTENT FOR THIS SECTION: {contentPartsForSection} Für jeden ContentPart: - Format: {contentFormat} - Content: {part.data wenn extracted, oder Beschreibung wenn reference/object} CRITICAL: Return ONLY a JSON object with an "elements" array. Jedes Element sollte dem content_type der Section entsprechen. ``` --- ### 5E: Rendering **Was passiert:** - Rendert die gefüllte JSON-Struktur zum Ziel-Format - Verwendet Rendering-Engines (PDF, DOCX, HTML, etc.) - **Multi-Dokument-Support**: Alle Dokumente im `documents` Array werden gerendert **Process:** ```python async def _renderResult( self, filledStructure: Dict[str, Any], outputFormat: str, title: str, userPrompt: str, parentOperationId: str # Für ChatLog-Hierarchie ) -> Tuple[bytes, str]: """ Rendert gefüllte Struktur zum Ziel-Format. Unterstützt Multi-Dokument-Rendering: Alle Dokumente werden gerendert. """ # Erstelle Operation-ID für Rendering renderOperationId = f"{parentOperationId}_rendering" # Starte ChatLog mit Parent-Referenz self.services.chat.progressLogStart( renderOperationId, "Content Rendering", "Rendering", f"Rendering to {outputFormat} format", parentOperationId=parentOperationId # Parent-Referenz! ) try: from modules.services.serviceGeneration.mainServiceGeneration import GenerationService generationService = GenerationService(self.services) # Multi-Dokument-Rendering documents = filledStructure.get("documents", []) if len(documents) == 1: # Einzelnes Dokument - wie bisher renderedContent, mimeType, images = await generationService.renderReport( filledStructure, outputFormat, title, userPrompt, self ) else: # Mehrere Dokumente - rendere alle # Option: Alle Sections zusammenführen und als ein Dokument rendern all_sections = [] for doc in documents: if "sections" in doc: all_sections.extend(doc.get("sections", [])) # Erstelle temporäres Dokument mit allen Sections merged_document = { "metadata": filledStructure["metadata"], "documents": [{ "id": "merged", "title": title, "filename": f"{title}.{outputFormat}", "sections": all_sections }] } renderedContent, mimeType, images = await generationService.renderReport( merged_document, outputFormat, title, userPrompt, self ) # ChatLog abschließen self.services.chat.progressLogFinish(renderOperationId, True) return renderedContent, mimeType except Exception as e: self.services.chat.progressLogFinish(renderOperationId, False) raise ``` **Code-Referenz:** ```322:352:poweron/gateway/modules/services/serviceGeneration/mainServiceGeneration.py async def renderReport(self, extractedContent: Dict[str, Any], outputFormat: str, title: str, userPrompt: str = None, aiService=None) -> tuple[str, str, List[Dict[str, Any]]]: ``` --- ## Überarbeitete callAiContent Funktion-Struktur ### Vereinfachte Hauptfunktion ```python async def callAiContent( self, prompt: str, options: AiCallOptions, contentParts: Optional[List[ContentPart]] = None, documentList: Optional[DocumentReferenceList] = None, documentIntents: Optional[List[DocumentIntent]] = None, outputFormat: Optional[str] = None, title: Optional[str] = None, parentOperationId: Optional[str] = None ) -> AiResponse: """ Einheitliche AI-Content-Verarbeitung - Single Entry Point für alle AI-Actions. Alle AI-Actions (ai.process, ai.generateDocument, etc.) routen hier durch. Sie unterscheiden sich nur in Parametern, nicht in Logik. """ await self.ensureAiObjectsInitialized() # Erstelle Operation-ID workflowId = self.services.workflow.id if self.services.workflow else f"no-workflow-{int(time.time())}" aiOperationId = f"ai_content_{workflowId}_{int(time.time())}" # Starte Progress-Tracking mit Parent-Referenz self.services.chat.progressLogStart( aiOperationId, "AI content processing", "Content Processing", f"Format: {outputFormat or 'text'}", parentOperationId=parentOperationId # Parent-Referenz! ) try: # Initialisiere Defaults if not outputFormat: outputFormat = "txt" opType = getattr(options, "operationType", None) if not opType: options.operationType = OperationTypeEnum.DATA_GENERATE opType = OperationTypeEnum.DATA_GENERATE # Route zu Operation-spezifischen Handlern if opType == OperationTypeEnum.IMAGE_GENERATE: return await self._handleImageGeneration(prompt, options, title, aiOperationId) if opType == OperationTypeEnum.WEB_SEARCH or opType == OperationTypeEnum.WEB_CRAWL: return await self._handleWebOperation(prompt, options, opType, aiOperationId) # Dokument-Generierungs-Pfad options.compressPrompt = False options.compressContext = False # Schritt 5A: Kläre Dokument-Intents documents = [] if documentList: documents = self.services.chat.getChatDocumentsFromDocumentList(documentList) if not documentIntents and documents: documentIntents = await self._clarifyDocumentIntents( documents, prompt, {"outputFormat": outputFormat}, aiOperationId # Parent-Referenz! ) # Schritt 5B: Extrahiere und bereite Content vor if documents: preparedContentParts = await self._extractAndPrepareContent( documents, documentIntents or [], aiOperationId # Parent-Referenz! ) # WICHTIG: Kein Caching - Content wird immer neu verarbeitet für Konsistenz # Merge mit bereitgestellten contentParts (falls vorhanden) if contentParts: # Prüfe auf pre-extracted Content for part in contentParts: if part.metadata.get("skipExtraction", False): # Bereits extrahiert - verwende as-is, stelle sicher dass Metadaten vollständig part.metadata.setdefault("contentFormat", "extracted") part.metadata.setdefault("isPreExtracted", True) preparedContentParts.extend(contentParts) contentParts = preparedContentParts # WICHTIG: Kein Caching - Content wird immer neu verarbeitet für Konsistenz # Schritt 5C: Generiere Struktur structure = await self._generateStructure( prompt, contentParts or [], outputFormat, aiOperationId # Parent-Referenz! ) # Schritt 5D: Fülle Struktur filledStructure = await self._fillStructure( structure, contentParts or [], prompt, aiOperationId # Parent-Referenz! ) # Schritt 5E: Rendere Resultat renderedContent, mimeType = await self._renderResult( filledStructure, outputFormat, title or "Generated Document", prompt, aiOperationId # Parent-Referenz! ) # Baue Response documentName = self._determineDocumentName(filledStructure, outputFormat, title) docData = DocumentData( documentName=documentName, documentData=renderedContent, mimeType=mimeType, sourceJson=filledStructure ) metadata = AiResponseMetadata( title=title or filledStructure.get("metadata", {}).get("title", "Generated Document"), operationType=opType.value ) # Debug-Log (harmonisiert) self.services.utils.writeDebugFile( json.dumps(filledStructure, indent=2, ensure_ascii=False), "document_generation_response" ) self.services.chat.progressLogFinish(aiOperationId, True) return AiResponse( content=json.dumps(filledStructure), metadata=metadata, documents=[docData] ) except Exception as e: logger.error(f"Error in callAiContent: {str(e)}") self.services.chat.progressLogFinish(aiOperationId, False) raise ``` --- ## ContentPart Metadaten-Schema ### Erforderliche Metadaten-Felder ```python ContentPart.metadata = { # Format-Identifikation (ERFORDERLICH) "contentFormat": Literal["reference", "object", "extracted"], # ERFORDERLICH # Dokument-Referenz (ERFORDERLICH für alle) "documentId": str, # Source-Dokument-ID # Für reference Format "documentReference": str, # z.B. "docItem:doc_1:file.pdf" # Für object Format "originalFileName": Optional[str], # Original-Dateiname # Für extracted Format "extractionPrompt": Optional[str], # Prompt verwendet für Extraktion "extractionMethod": Optional[str], # "vision", "text", "ocr", etc. # Verwendungs-Information "intent": str, # "extract", "render", "reference" "usageHint": str, # Wo/wie dieser Content verwendet werden soll # Pre-Extraction Flag "isPreExtracted": Optional[bool], # True wenn bereits von vorheriger Action extrahiert "skipExtraction": Optional[bool], # True wenn Extraktion übersprungen werden soll # Source-Tracking "sourceAction": Optional[str], # Welche Action diesen Part erstellt hat } ``` --- ## Harmonisierung & Cleanup: Debug-Datei-Log-Aufrufe ### Aktuelle Situation Debug-Datei-Log-Aufrufe sind über den Code verteilt mit unterschiedlichen Patterns: - Manche mit Checks: `if hasattr(self.services, 'utils') and hasattr(self.services.utils, 'writeDebugFile')` - Manche ohne Checks - Unterschiedliche Dateinamen-Patterns - Inkonsistente Verwendung ### Harmonisiertes Pattern **Regel**: Die Funktion `self.services.utils.writeDebugFile()` ist **IMMER verfügbar** - keine Checks nötig! **Standardisiertes Pattern:** ```python # Immer ohne Checks - Funktion ist IMMER verfügbar self.services.utils.writeDebugFile(content, filename) ``` **Harmonisierte Dateinamen:** - `user_input_analysis_prompt` / `user_input_analysis_response` - `document_intent_analysis_prompt` / `document_intent_analysis_response` / `document_intent_analysis_result` - `content_extraction_prompt_{documentId}` / `content_extraction_response` / `content_extraction_result` - `document_generation_structure_prompt` / `document_generation_structure_response` - `section_generation_prompt_{sectionId}` / `section_generation_response_{sectionId}` - `document_generation_response` (finales JSON) **Cleanup-Aufgaben:** 1. Entferne alle Checks: `if hasattr(...)` → Direkter Aufruf 2. Standardisiere alle Dateinamen nach obigem Pattern 3. Stelle sicher, dass alle AI-Prompts und Responses geloggt werden 4. Dokumentiere Dateinamen-Pattern im Code **Beispiel-Cleanup:** ```python # VORHER (inkonsistent): if self.services and hasattr(self.services, 'utils') and hasattr(self.services.utils, 'writeDebugFile'): try: self.services.utils.writeDebugFile(structurePrompt, "document_generation_structure_prompt") except Exception as e: logger.debug(f"Could not write debug file: {e}") # NACHHER (harmonisiert): # Immer ohne Checks - Funktion ist IMMER verfügbar self.services.utils.writeDebugFile(structurePrompt, "document_generation_structure_prompt") ``` --- ## Deep Analysis & Anpassung: ChatLog Parent/Child-Referenzen ### Aktuelle Situation ChatLog-Nachrichten haben teilweise fehlende oder inkorrekte Parent/Child-Referenzen: - Sub-Funktionen erstellen Logs ohne Parent-Referenz - Parent-Operation-ID wird nicht durchgereicht - Hierarchie geht verloren ### Korrekte Hierarchie-Struktur **Prinzip**: Jeder Log in einer Sub-Funktion ist ein **Child** des Logs in der aufrufenden Parent-Funktion. **Hierarchie-Beispiel:** ``` callAiContent (aiOperationId) ├── _clarifyDocumentIntents (intentOperationId, parent: aiOperationId) │ └── AI-Call für Intent-Analyse (parent: intentOperationId) ├── _extractAndPrepareContent (extractionOperationId, parent: aiOperationId) │ └── extractContent (parent: extractionOperationId) │ └── Per-Dokument-Logs (parent: extractionOperationId) ├── _generateStructure (structureOperationId, parent: aiOperationId) │ └── AI-Call für Struktur (parent: structureOperationId) ├── _fillStructure (fillOperationId, parent: aiOperationId) │ └── Per-Section-Logs (sectionOperationId, parent: fillOperationId) │ └── AI-Call für Section (parent: sectionOperationId) └── _renderResult (renderOperationId, parent: aiOperationId) └── renderReport (parent: renderOperationId) ``` ### Implementierungs-Regeln 1. **Jede Funktion, die Logs erstellt, muss `parentOperationId` Parameter haben:** ```python async def _clarifyDocumentIntents( self, ..., parentOperationId: str # ERFORDERLICH! ) -> List[DocumentIntent]: ``` 2. **Jede Operation-ID muss eindeutig sein:** ```python intentOperationId = f"{parentOperationId}_intent_analysis" ``` 3. **Jeder `progressLogStart` muss `parentOperationId` verwenden:** ```python self.services.chat.progressLogStart( intentOperationId, "Document Intent Analysis", "Intent Analysis", "...", parentOperationId=parentOperationId # ERFORDERLICH! ) ``` 4. **Jeder AI-Call muss `parentOperationId` weiterreichen:** ```python aiResponse = await self.services.ai.callAiContent( ..., parentOperationId=intentOperationId # Parent-Referenz! ) ``` 5. **Jeder Service-Call muss `parentOperationId` weiterreichen:** ```python extractedResults = await self.services.extraction.extractContent( ..., operationId=extractionOperationId, parentOperationId=extractionOperationId # Parent-Referenz! ) ``` ### Deep Analysis Aufgaben 1. **Analysiere alle Funktionen, die Logs erstellen:** - Identifiziere alle `progressLogStart` Aufrufe - Prüfe, ob `parentOperationId` gesetzt ist - Prüfe, ob `parentOperationId` durchgereicht wird 2. **Analysiere alle Sub-Funktionen:** - Identifiziere alle Funktionen, die andere Funktionen aufrufen, die Logs erstellen - Stelle sicher, dass `parentOperationId` durchgereicht wird 3. **Analysiere alle Service-Calls:** - Identifiziere alle Calls zu anderen Services (extraction, generation, etc.) - Stelle sicher, dass `parentOperationId` weitergegeben wird 4. **Validiere Hierarchie:** - Stelle sicher, dass jede Log-Hierarchie korrekt ist - Prüfe, dass keine Logs ohne Parent existieren (außer Root-Logs) ### Code-Beispiel: Korrekte Hierarchie ```python # Root-Funktion async def callAiContent(..., parentOperationId: Optional[str] = None): aiOperationId = f"ai_content_{workflowId}_{int(time.time())}" # Root-Log (kein Parent) self.services.chat.progressLogStart( aiOperationId, "AI content processing", "Content Processing", "...", parentOperationId=parentOperationId # Kann None sein für Root ) # Sub-Funktion mit Parent-Referenz documentIntents = await self._clarifyDocumentIntents( ..., parentOperationId=aiOperationId # Parent-Referenz! ) # Sub-Funktion async def _clarifyDocumentIntents(..., parentOperationId: str): intentOperationId = f"{parentOperationId}_intent_analysis" # Child-Log mit Parent-Referenz self.services.chat.progressLogStart( intentOperationId, "Document Intent Analysis", "Intent Analysis", "...", parentOperationId=parentOperationId # Parent-Referenz! ) # AI-Call mit Parent-Referenz aiResponse = await self.services.ai.callAiContent( ..., parentOperationId=intentOperationId # Parent-Referenz! ) # Log abschließen self.services.chat.progressLogFinish(intentOperationId, True) ``` --- ## Image-Handling: Drei Formate ### Format 1: Document Reference ```python ContentPart( typeGroup="image", mimeType="image/png", data="", # Leer metadata={ "contentFormat": "reference", "documentReference": "docItem:img_1:logo.png", "usageHint": "Include as attachment" } ) ``` **Verwendung**: Referenz auf Image-Dokument, kein Datentransfer. ### Format 2: Object (Base64) ```python ContentPart( typeGroup="image", mimeType="image/png", data="iVBORw0KGgoAAAANS...", # Base64-kodiert metadata={ "contentFormat": "object", "usageHint": "Render as visual element" } ) ``` **Verwendung**: Image-Daten eingebettet, bereit für Rendering. ### Format 3: Extracted Text ```python ContentPart( typeGroup="text", mimeType="text/plain", data="This image shows a red car...", # Extrahierte Beschreibung metadata={ "contentFormat": "extracted", "extractionPrompt": "Describe the image content", "extractionMethod": "vision", "originalImageId": "img_1" # Referenz auf Original } ) ``` **Verwendung**: Text extrahiert aus Image, für Verwendung in Text-Sections. **Wichtig**: In einem extrahierten ContentPart sollte klar sichtbar sein: - **Image als Object (base64)**: `contentFormat="object"`, `typeGroup="image"`, `data` enthält base64 - **Image als Reference**: `contentFormat="reference"`, `metadata.documentReference` enthält Referenz - **Image als Extracted Text**: `contentFormat="extracted"`, `typeGroup="text"`, `data` enthält Text, `metadata.originalImageId` enthält Referenz zum Original --- ## Migrations-Pfad ### Schritt 1: ContentPart-Modell erweitern - Füge `contentFormat` zum Metadaten-Schema hinzu - Stelle sicher, dass alle ContentParts Format spezifiziert haben ### Schritt 2: Document Intent Analysis überarbeiten ⚠️ **KRITISCH** **Problem**: `subDocumentPurposeAnalyzer.py` verwendet ein veraltetes "purpose"-System mit vielen Tags (`extract_text_content`, `include_image`, `analyze_image_vision`, etc.), das nicht mit dem DocumentIntent-System aus diesem Konzept übereinstimmt. **Lösung**: Ersetze das alte Purpose-System durch das DocumentIntent-System. **Zu entfernen**: - ❌ Altes Purpose-System: `extract_text_content`, `include_image`, `analyze_image_vision`, `use_as_template`, `use_as_reference`, `extract_data`, `attach`, `convert_format`, `translate`, `summarize`, `compare`, `merge`, `extract_tables_charts`, `use_for_styling`, `extract_metadata` - ❌ `DocumentPurposeAnalyzer` Klasse mit `analyzeDocumentPurposes()` Methode - ❌ Alle Purpose-Listen und Purpose-Tags im Code **Zu implementieren**: - ✅ `DocumentIntent` Modell verwenden (bereits definiert in `datamodelExtraction.py`) - ✅ `_clarifyDocumentIntents()` Funktion in `mainServiceAi.py` (wie in Phase 5A beschrieben) - ✅ Einfaches Intent-System: `intents=["extract", "render", "reference"]` - ✅ Jedes Dokument kann mehrere Intents haben: `intents=["extract", "render"]` für Bilder, die sowohl analysiert als auch gerendert werden sollen **Code-Änderungen**: ```python # ALT (subDocumentPurposeAnalyzer.py): purpose = "extract_text_content" # oder "include_image", "analyze_image_vision", etc. # NEU (DocumentIntent): documentIntent = DocumentIntent( documentId="doc_1", intents=["extract", "render"], # Einfach und klar! extractionPrompt="Extract text content from image", reasoning="Image needs both text extraction and visual rendering" ) ``` **Dateien zu überarbeiten**: - `subDocumentPurposeAnalyzer.py`: Entfernen oder komplett umschreiben auf DocumentIntent-System - Alle Stellen, die `analyzeDocumentPurposes()` aufrufen: Umstellen auf `_clarifyDocumentIntents()` - Alle Stellen, die "purpose" verwenden: Umstellen auf `intents` aus DocumentIntent ### Schritt 3: callAiContent refactoren - Implementiere 5A-5E Phasen - Entferne duplizierte Logik aus Action-Methoden - Zentralisiere alle Dokument-Verarbeitung - Implementiere korrekte Parent/Child-Referenzen - **Wichtig**: Verwende DocumentIntent-System aus Schritt 2, nicht das alte Purpose-System ### Schritt 4: Action-Methoden aktualisieren - Entferne Dokument-Verarbeitungslogik - Übergebe Parameter an callAiContent - Behalte nur Parameter-Vorbereitung ### Schritt 5: Extraction-Service aktualisieren - Stelle sicher, dass ContentParts vollständige Metadaten haben - Markiere pre-extracted Content korrekt - Implementiere korrekte Parent/Child-Referenzen ### Schritt 6: Generation-Service aktualisieren - Behandle drei Content-Formate - Verwende Metadaten für Format-Auswahl - Implementiere Multi-Dokument-Rendering - **Wichtig**: Entferne alle Referenzen zum alten Purpose-System ### Schritt 7: Debug-Logging harmonisieren - Entferne alle Checks: `if hasattr(...)` - Standardisiere alle Dateinamen - Stelle sicher, dass alle AI-Prompts/Responses geloggt werden ### Schritt 8: ChatLog-Hierarchie korrigieren - Analysiere alle Log-Erstellungen - Füge `parentOperationId` Parameter hinzu wo nötig - Stelle sicher, dass Parent-Referenzen korrekt durchgereicht werden - Validiere Hierarchie --- ## Vorteile des überarbeiteten Konzepts 1. **Klarere Trennung**: Jede Phase hat distincte Verantwortung 2. **Metadatenfluss**: Vollständige Metadaten in jedem Schritt 3. **Format-Klarheit**: Explizite Format-Indikation 4. **Zentralisierte Logik**: Single Source of Truth 5. **Debuggability**: Alle Prompts/Responses geloggt (harmonisiert) 6. **Flexibilität**: Unterstützt alle Use Cases 7. **Wartbarkeit**: Klare Struktur, einfach zu erweitern 8. **Korrekte Hierarchie**: Alle ChatLogs haben korrekte Parent/Child-Referenzen 9. **Bessere Performance**: Phase 1+2 kombiniert = weniger AI-Calls 10. **Context-Aware**: Action Planner nutzt Context von vergangenen Rounds/Tasks --- ## Use-Case-Analyse: Drei konkrete Szenarien ### Use Case 1: Expenses CSV - Strukturierte Datenextraktion aus PDFs mit Bildern **User Prompt:** > "combine all expenses documents (each with image in pdf) into a csv file with the columns date;shop;CHF;VAT;Description" **Input:** 10 PDF-Dateien (jede enthält Bilder mit Rechnungsdaten) **Erwartetes Verhalten:** **Phase 1+2: Intent-Analyse & Komplexität** - **Intent**: Strukturierte Datenextraktion aus mehreren PDFs, Kombination in CSV - **Komplexität**: "complex" (Multi-Dokument, strukturierte Extraktion, Aggregation) - **Fast Track**: false (Dokument-Verarbeitung benötigt) **Phase 3: Task Planning** - **Task 1**: Extrahiere strukturierte Daten aus allen PDFs - Objective: "Extract expense data (date, shop, CHF, VAT, description) from all PDF documents" - RequiredDocuments: Alle 10 PDFs - ExpectedOutput: "structured_data" **Phase 4: Task Execution** **Action 1: `context.extractContent`** - Extrahiert alle PDFs zu ContentParts - **Wichtig**: Bilder werden als `typeGroup="image"` mit `contentFormat="object"` (base64) erstellt - **Aber**: Für Datenextraktion benötigen wir auch Text-Extraktion aus Bildern **Action 2: `ai.process`** (mit spezifischem Prompt für strukturierte Datenextraktion) - **Input**: Alle extrahierten ContentParts - **Intent-Analyse (5A)**: - PDFs: `intents=["extract"]` (Text-Extraktion) - Bilder in PDFs: `intents=["extract"]` (OCR/Text-Extraktion für strukturierte Daten) - **ExtractionPrompt für Bilder**: "Extract structured expense data: date, shop name, amount in CHF, VAT amount, description" - **Content-Extraktion (5B)**: - PDF-Text wird extrahiert: `contentFormat="extracted"` - Bilder werden analysiert (OCR/Vision): `contentFormat="extracted"` mit `extractionMethod="vision"` - **Wichtig**: Bilder haben beide Formate möglich: - `contentFormat="object"` für visuelle Darstellung (falls benötigt) - `contentFormat="extracted"` für Text-Extraktion (für CSV) - **Struktur-Generierung (5C)**: - Struktur definiert CSV-Format mit Spalten: date, shop, CHF, VAT, Description - **Struktur-Abfüllen (5D)**: - Extrahiert strukturierte Daten aus allen ContentParts - Kombiniert Daten aus allen 10 PDFs - **Rendering (5E)**: - Rendert zu CSV-Format **Kritische Punkte:** - ✅ **Bilder müssen analysiert werden** (OCR/Vision) für strukturierte Datenextraktion - ✅ **Multi-Dokument-Verarbeitung**: Alle 10 PDFs müssen verarbeitet werden - ✅ **Strukturierte Datenextraktion**: Spezifische Felder müssen extrahiert werden - ✅ **Kombination**: Daten aus allen PDFs müssen in einem CSV kombiniert werden **Konzept-Abdeckung:** - ✅ Phase 5A: Intent-Analyse erkennt "extract" für Bilder (OCR benötigt) - ✅ Phase 5B: Bilder werden sowohl als "object" (base64) als auch als "extracted" (Text) behandelt - ✅ Phase 5C: Struktur definiert CSV-Format - ✅ Phase 5D: Extrahiert und kombiniert strukturierte Daten - ✅ Phase 5E: Rendert zu CSV **Mögliche Verbesserung:** - Konzept sollte explizit erwähnen: **Bilder können beide Formate gleichzeitig haben** (object für Rendering, extracted für Text-Analyse) --- ### Use Case 2: PowerPoint mit Bildern - Bilder rendern UND analysieren **User Prompt:** > "make a powerpoint slideshow for the customer about the product with the images integrated in the slides. the images also give information about the storyline" **Input:** 10 Bilder **Erwartetes Verhalten:** **Phase 1+2: Intent-Analyse & Komplexität** - **Intent**: PowerPoint-Generierung mit Bildern, Storyline-Analyse - **Komplexität**: "complex" (Multi-Bild, Generierung, Storyline-Analyse) - **Fast Track**: false **Phase 3: Task Planning** - **Task 1**: Generiere PowerPoint mit integrierten Bildern und Storyline - Objective: "Create PowerPoint presentation with images integrated in slides, analyze images for storyline information" - RequiredDocuments: Alle 10 Bilder - ExpectedOutput: "pptx" **Phase 4: Task Execution** **Action 1: `ai.generateDocument`** (oder `ai.process` mit `outputFormat="pptx"`) - **Input**: Alle 10 Bilder - **Intent-Analyse (5A)**: - Bilder: `intents=["extract", "render"]` (beide!) - **"render"**: Bilder müssen in Slides integriert werden - **"extract"**: Bilder müssen analysiert werden für Storyline-Information - **ExtractionPrompt**: "Analyze image content to extract storyline information, product features, and narrative elements" - **Content-Extraktion (5B)**: - **Für Rendering**: Bilder als `contentFormat="object"` (base64) für PowerPoint-Integration - **Für Storyline**: Bilder analysiert als `contentFormat="extracted"` (Text-Beschreibung) für Storyline-Generierung - **Wichtig**: Jedes Bild erzeugt **zwei ContentParts**: - `ContentPart(id="img_1_obj", contentFormat="object", ...)` - für Rendering - `ContentPart(id="img_1_ext", contentFormat="extracted", ...)` - für Storyline-Analyse - **Struktur-Generierung (5C)**: - Struktur definiert PowerPoint-Slides - Jede Slide kann Bilder enthalten (object-Format) - Storyline-Information aus extracted-Format wird für Slide-Content verwendet - **Struktur-Abfüllen (5D)**: - Bilder werden in Slides integriert (object-Format) - Storyline-Text wird aus extracted-Format generiert - AI generiert Slide-Content basierend auf Storyline - **Rendering (5E)**: - Rendert zu PPTX-Format mit integrierten Bildern **Kritische Punkte:** - ✅ **Bilder müssen beide Formate haben**: object (für Rendering) UND extracted (für Storyline) - ✅ **Multi-Bild-Verarbeitung**: Alle 10 Bilder müssen verarbeitet werden - ✅ **Storyline-Analyse**: Bilder müssen analysiert werden für narrative Elemente - ✅ **PowerPoint-Generierung**: Strukturierte Slide-Generierung mit Bildern **Konzept-Abdeckung:** - ✅ Phase 5A: Intent-Analyse erkennt beide Intents: `["extract", "render"]` - ✅ Phase 5B: Bilder werden in beiden Formaten erstellt (object + extracted) - ✅ Phase 5C: Struktur definiert PowerPoint-Format mit Bild-Platzhaltern - ✅ Phase 5D: Integriert Bilder (object) und verwendet Storyline (extracted) - ✅ Phase 5E: Rendert zu PPTX **Mögliche Verbesserung:** - Konzept sollte explizit erwähnen: **Ein Dokument kann mehrere ContentParts erzeugen** (z.B. Bild als object UND als extracted) --- ### Use Case 3: SharePoint-Analyse - Keine angehängten Dokumente **User Prompt:** > "make me an overview about all sharepoint folders and the summary of documents stored in each folder for sharepoint https://xxx.yy.zz" **Input:** Keine Dokumente angehängt **Erwartetes Verhalten:** **Phase 1+2: Intent-Analyse & Komplexität** - **Intent**: SharePoint-Integration, Folder-Analyse, Dokument-Zusammenfassungen - **Komplexität**: "complex" (Externe Integration, Multi-Step-Workflow) - **Fast Track**: false **Phase 3: Task Planning** - **Task 1**: Analysiere SharePoint-Struktur - Objective: "List all folders in SharePoint site and analyze document structure" - RequiredDocuments: Keine (SharePoint-URL im Prompt) - ExpectedOutput: "structured_overview" - **Task 2**: Generiere Zusammenfassungen - Objective: "Generate summaries for documents in each folder" - RequiredDocuments: Von Task 1 (SharePoint-Dokumente) - ExpectedOutput: "summary_document" **Phase 4: Task Execution** **Action 1: `sharepoint.listDocuments`** - Listet alle Ordner im SharePoint - Gibt Dokument-Referenzen zurück - **Wichtig**: Keine ContentParts erstellt - nur Referenzen **Action 2: `sharepoint.readDocuments`** (für jeden Ordner) - Liest Dokumente aus SharePoint - Erstellt ContentParts mit `contentFormat="reference"` (SharePoint-Referenzen) - Oder lädt Dokumente herunter und erstellt ContentParts **Action 3: `ai.process`** (für Zusammenfassungen) - **Input**: SharePoint-Dokument-Referenzen oder heruntergeladene Dokumente - **Intent-Analyse (5A)**: - Dokumente: `intents=["extract"]` (Zusammenfassung benötigt Extraktion) - **ExtractionPrompt**: "Extract key information and create summary" - **Content-Extraktion (5B)**: - Dokumente werden extrahiert: `contentFormat="extracted"` - **Struktur-Generierung (5C)**: - Struktur definiert Overview-Format mit Foldern und Zusammenfassungen - **Struktur-Abfüllen (5D)**: - Generiert Zusammenfassungen für jedes Dokument - Gruppiert nach Ordnern - **Rendering (5E)**: - Rendert zu Overview-Dokument (HTML, DOCX, etc.) **Kritische Punkte:** - ✅ **Keine angehängten Dokumente**: Workflow startet ohne Dokumente - ✅ **SharePoint-Integration**: Externe Service-Actions werden verwendet - ✅ **Multi-Step-Workflow**: Mehrere Actions sequenziell - ✅ **Dokument-Referenzen**: SharePoint-Dokumente werden als Referenzen behandelt **Konzept-Abdeckung:** - ✅ Phase 4: Action Planner kann externe Actions (sharepoint.*) verwenden - ✅ Phase 5A: Intent-Analyse funktioniert auch für SharePoint-Dokumente - ✅ Phase 5B: SharePoint-Dokumente können als "reference" oder "extracted" behandelt werden - ✅ Phase 5C-5E: Standard-Generierungsprozess funktioniert **Mögliche Verbesserung:** - Konzept sollte explizit erwähnen: **Workflows können ohne angehängte Dokumente starten** (Dokumente kommen von externen Services) --- ## Konzept-Verbesserungen basierend auf Use-Case-Analyse ### Verbesserung 1: Mehrfache ContentParts pro Dokument **Problem**: Ein Dokument (z.B. Bild) kann sowohl als "object" (für Rendering) als auch als "extracted" (für Text-Analyse) benötigt werden. **Lösung**: Phase 5B sollte explizit unterstützen, dass ein Dokument **mehrere ContentParts** erzeugen kann: ```python async def _extractAndPrepareContent(...): """ Ein Dokument kann mehrere ContentParts erzeugen, wenn mehrere Intents vorhanden sind. Beispiel: Bild mit intents=["extract", "render"] erzeugt: - ContentPart(contentFormat="object", ...) für Rendering - ContentPart(contentFormat="extracted", ...) für Text-Analyse """ for document in documents: intent = getIntentForDocument(document.id, documentIntents) # Wenn mehrere Intents: Erzeuge mehrere ContentParts if "render" in intent.intents and "extract" in intent.intents: # Erzeuge object-ContentPart für Rendering objectPart = ContentPart( id=f"obj_{document.id}", contentFormat="object", ... ) allContentParts.append(objectPart) # Erzeuge extracted-ContentPart für Text-Analyse extractedPart = await self._extractTextFromDocument(document, intent.extractionPrompt) extractedPart.id = f"ext_{document.id}" extractedPart.metadata["contentFormat"] = "extracted" extractedPart.metadata["originalDocumentId"] = document.id extractedPart.metadata["relatedContentPartId"] = objectPart.id # Verknüpfung allContentParts.append(extractedPart) ``` ### Verbesserung 2: Strukturierte Datenextraktion **Problem**: Use Case 1 benötigt strukturierte Datenextraktion (CSV mit spezifischen Spalten). **Lösung**: Phase 5C sollte explizit strukturierte Datenextraktion unterstützen: ```python async def _generateStructure(...): """ Für strukturierte Datenextraktion (z.B. CSV): - Struktur definiert explizit die erwarteten Felder/Spalten - ContentParts werden diesen Feldern zugeordnet """ # Prüfe ob strukturierte Extraktion benötigt wird if "extract structured data" in userPrompt.lower() or outputFormat == "csv": # Struktur definiert explizit Felder structurePrompt = f""" Extract structured data with fields: {fields} Map content parts to these fields. ... """ ``` ### Verbesserung 3: Externe Service-Integration **Problem**: Use Case 3 startet ohne Dokumente, Dokumente kommen von SharePoint. **Lösung**: Phase 4 sollte explizit dokumentieren, dass externe Actions Dokumente bereitstellen können: ```python # Action 1: sharepoint.listDocuments # Erstellt Dokument-Referenzen, die in AVAILABLE_DOCUMENTS_INDEX verfügbar werden # Action 2: sharepoint.readDocuments # Lädt Dokumente herunter und erstellt ContentParts # Diese werden dann in nachfolgenden Actions verwendet ``` --- ## Offene Fragen & Entscheidungen 1. ✅ **Multi-Dokument-Rendering**: In einem Call - Resultat kann mehrere Dokumente sein 2. ✅ **Section-Generierung Parallelisierung**: **Parallel, wenn möglich** - Sections können parallel generiert werden, um Performance zu verbessern 3. ✅ **Error-Handling**: **Fehlerhafte Sections mit Fehlermeldung rendern** - Wenn eine Section fehlschlägt, wird eine Fehlermeldung in der gerenderten Ausgabe angezeigt, statt den gesamten Prozess zu stoppen 4. ✅ **Caching**: **Nein, kein Caching** - Extrahierter Content wird nicht gecacht, um Konsistenz und Klarheit zu gewährleisten 5. ✅ **Validierung**: **Keine Validierung** - Es ist kein Validierungsprozess definiert, und es gibt keinen definierten Prozess für den Fall, dass eine Validierung fehlschlagen würde. Klarer Code ohne zusätzliche Komplexität. 6. ✅ **Mehrfache ContentParts**: **Ja, natürlich!** - Das ist das grundlegende Design! Ein Dokument kann mehrere ContentParts erzeugen (z.B. `object` + `extracted` für Bilder, die sowohl gerendert als auch analysiert werden sollen) 7. ✅ **Strukturierte Datenextraktion**: **Ja, Phase 5C soll dies unterstützen** - Phase 5C (Struktur-Generierung) soll explizit strukturierte Datenextraktion unterstützen (z.B. CSV mit spezifischen Spalten) --- ## Nächste Schritte 1. Review und Approve dieses Konzepts 2. Implementiere ContentPart-Metadaten-Erweiterungen 3. Refactore callAiContent nach 5A-5E Struktur 4. Aktualisiere Action-Methoden für zentralisierte Logik 5. Harmonisiere Debug-Logging (entferne Checks, standardisiere Namen) 6. Korrigiere ChatLog-Hierarchie (füge Parent-Referenzen hinzu) 7. Implementiere Multi-Dokument-Rendering 8. Teste mit verschiedenen Szenarien 9. Dokumentiere API-Änderungen