wiki/appdoc/analysis_ai_workflow.md
2025-12-25 00:09:38 +01:00

107 KiB

AI Workflow Analysis: Document Management im Kontext von Workflows

Übersicht

Diese Analyse beschreibt den vollständigen Ablauf eines AI-Calls im Kontext eines Workflows, speziell im Hinblick auf das Dokumenten-Management. Der Fokus liegt auf der Klärung, was mit Dokumenten passiert, die der User mitsendet, und wo Entscheidungen über Extraktion vs. direkte Verwendung getroffen werden.

Problemstellung

Aktuelle Probleme:

  1. Unklarheit darüber, was mit Dokumenten passiert, die der User mitsendet
  2. Dokumente werden immer extrahiert, auch wenn Extraktion nicht nötig ist
  3. Fehlende Analyse, welche Dokumente extrahiert werden müssen und was extrahiert werden soll
  4. Bei Bildern ist unklar, ob das Bild gerendert werden soll oder der Text aus dem Bild extrahiert werden soll
  5. Der Prozess scheint chaotisch - es fehlt ein klarer Prozess innerhalb eines AI-Calls

Workflow-Ablauf: Von User Input bis Action Execution

Phase 0: User Input Analyse und Context-Extraktion

Datei: workflowManager.py

0.1 Prompt-Analyse und Context-Extraktion (_sendFirstMessage)

Was passiert:

  • BEVOR der eigentliche Workflow startet, wird der User-Prompt analysiert
  • AI analysiert den Prompt und unterscheidet zwischen:
    • Prompt (Anweisungen, was gemacht werden soll)
    • Context (Inhalte, die als separate Dokumente behandelt werden sollen)

Code-Stelle:

# Analyze the user's input to detect language, normalize request, extract intent, and offload bulky context into documents

Analyzer Prompt:

  • AI analysiert den User-Prompt in einem Schritt:
    1. detectedLanguage: Sprache erkennen
    2. normalizedRequest: Normalisierte Anweisung (ohne Context)
    3. intent: Kurze Kern-Anfrage
    4. contextItems: Große Datenblöcke, die als separate Dokumente extrahiert werden sollen

Was wird als Context extrahiert:

  • Große Literal-Inhalte
  • Lange Listen/Tabellen
  • Code/JSON-Blöcke
  • Transkripte
  • CSV-Fragmente
  • Detaillierte Specs

Regeln:

  • Wenn Gesamtinhalt < 10% der Model-Max-Tokens: KEINE Extraktion, alles bleibt im Intent
  • Wenn Gesamtinhalt > 10%: Extraktion von großen Teilen in contextItems
  • Kritische Referenzen (URLs, Dateinamen) bleiben im Intent

Dokument-Erstellung:

  • Jedes contextItem wird als separates ChatDocument erstellt
  • Wird in Component Storage gespeichert
  • Wird mit User-uploaded Dokumenten kombiniert

Ergebnis:

  • self.services.currentUserContextItems = contextItems - Context-Items gespeichert
  • self.services.currentUserPromptNormalized = normalizedRequest - Normalisierter Prompt (ohne Context)
  • createdDocs[] - Erstellte Context-Dokumente werden mit User-Dokumenten kombiniert

Wichtig:

  • Diese Phase passiert BEVOR Complexity Detection
  • Context-Dokumente sind dann verfügbar für alle nachfolgenden Phasen
  • Der normalisierte Prompt (ohne Context) wird für alle weiteren Schritte verwendet

Phase 1: User Input Verarbeitung

Datei: workflowProcessor.py

1.1 Complexity Detection (detectComplexity)

Was passiert:

  • User Input wird analysiert, um Komplexität zu bestimmen
  • Sprache wird erkannt
  • Es wird geprüft, ob Workflow-History benötigt wird

Dokumenten-Behandlung:

  • Dokumente werden nur gezählt (len(documents))
  • Dokument-Typen werden erfasst (mimeType)
  • KEINE Extraktion - nur Metadaten-Analyse

Code-Stelle:

async def detectComplexity(self, prompt: str, documents: Optional[List[ChatDocument]] = None) -> tuple[str, bool, Optional[str]]:

Ergebnis:

  • complexity: "simple" | "moderate" | "complex"
  • needsWorkflowHistory: bool
  • detectedLanguage: str (ISO 639-1)

1.2 Fast Path (optional)

Was passiert:

  • Bei "simple" Requests wird Fast Path verwendet
  • Direkter AI-Call ohne Dokumenten-Extraktion
  • Dokumente werden NICHT verarbeitet

Code-Stelle:

async def fastPathExecute(self, prompt: str, documents: Optional[List[ChatDocument]] = None, userLanguage: Optional[str] = None) -> ActionResult:

Dokumenten-Behandlung:

  • Dokumente werden ignoriert (contentParts=None)
  • Nur Text-Response wird generiert

Phase 2: Workflow Planning

2.1 Task Plan Generation (generateTaskPlan)

Was passiert:

  • High-level Task Plan wird generiert
  • Tasks werden identifiziert
  • Actions werden geplant

Dokumenten-Behandlung:

  • Dokumente werden NICHT extrahiert
  • Nur Referenzen werden verwendet

Code-Stelle:

async def generateTaskPlan(self, userInput: str, workflow: ChatWorkflow) -> TaskPlan:

2.2 Action Generation (generateActionItems)

Was passiert:

  • Actions für Tasks werden generiert
  • ai.process Action kann generiert werden

Dokumenten-Behandlung:

  • Dokument-Referenzen werden in Action-Parameter eingefügt
  • KEINE Extraktion - nur Referenzen

Phase 3: Action Execution - ai.process

Datei: methodAi/actions/process.py

3.1 Action Start (process) - Entry Point

Was passiert:

  • Action ai.process wird ausgeführt
  • Parameter werden extrahiert: aiPrompt, documentList, resultType
  • Progress Tracking wird initialisiert

Code-Stelle:

@action
async def process(self, parameters: Dict[str, Any]) -> ActionResult:

Funktions-Aufruf-Hierarchie:

process() [Entry Point]
├─→ getChatDocumentsFromDocumentList() [Chat Service]
│   └─→ Konvertiert DocumentReferenceList zu ChatDocument[]
│
├─→ extractContent() [Extraction Service]
│   ├─→ runExtraction() [Extraction Pipeline]
│   │   ├─→ ExtractorRegistry.resolve() [Registry]
│   │   │   └─→ Findet passenden Extractor (PDF, DOCX, etc.)
│   │   ├─→ extractor.extract() [Extractor-spezifisch]
│   │   │   └─→ Erstellt ContentPart[] (text, image, table, etc.)
│   │   └─→ applyMerging() [Merging Strategy]
│   │       └─→ Merged ContentParts nach Strategie
│   └─→ Gibt List[ContentExtracted] zurück
│
├─→ callAiContent() [AI Service]
│   ├─→ buildExtractionPrompt() [Prompt Builder]
│   │   ├─→ _parseExtractionIntent() [Intent Analysis]
│   │   │   └─→ AI-Call zur Intent-Analyse
│   │   └─→ Erstellt Extraction Prompt
│   │
│   ├─→ callAi() [AI Service Router]
│   │   └─→ processContentPartsWithAi() [Extraction Service]
│   │       ├─→ processContentPartWithFallback() [Per Part]
│   │       │   ├─→ chunkContentPartForAi() [Wenn zu groß]
│   │       │   │   └─→ ChunkerRegistry.resolve() [Registry]
│   │       │   ├─→ aiObjects._callWithModel() [AI Call]
│   │       │   │   └─→ Vision Models für Bilder
│   │       │   └─→ mergeChunkResults() [Wenn gechunkt]
│   │       └─→ mergePartResults() [Merge All Parts]
│   │           └─→ applyMerging() [Merging Strategy]
│   │
│   ├─→ buildGenerationPrompt() [Prompt Builder]
│   │   └─→ Erstellt Generation Prompt mit extracted_content
│   │
│   ├─→ _callAiWithLooping() [AI Service]
│   │   ├─→ callAi() [AI Call]
│   │   ├─→ _extractSectionsFromResponse() [JSON Parsing]
│   │   │   ├─→ extractJsonString() [JSON Utils]
│   │   │   ├─→ repairBrokenJson() [JSON Utils]
│   │   │   ├─→ extractSectionsFromDocument() [JSON Utils]
│   │   │   └─→ JsonResponseHandler.mergeSectionsIntelligently() [Merging]
│   │   ├─→ _defineKpisFromPrompt() [KPI Tracking]
│   │   ├─→ JsonResponseHandler.extractKpiValuesFromJson() [KPI Extraction]
│   │   ├─→ JsonResponseHandler.validateKpiProgression() [KPI Validation]
│   │   ├─→ buildContinuationContext() [Continuation]
│   │   └─→ _buildFinalResultFromSections() [Final Assembly]
│   │
│   └─→ renderReport() [Generation Service]
│       ├─→ getRendererForFormat() [Renderer Selection]
│       └─→ renderer.render() [Format-spezifisch]
│
└─→ ActionResult.isSuccess() [Return]
    └─→ ActionDocument[] mit gerenderten Dokumenten

3.1.1 Sub-Funktionen im Detail

A. Document Resolution

  • Funktion: getChatDocumentsFromDocumentList()
  • Service: Chat Service
  • Was passiert: Konvertiert DocumentReferenceList (String-Referenzen) zu ChatDocument[] (vollständige Objekte mit fileId, fileName, mimeType)
  • Input: DocumentReferenceList mit String-Referenzen
  • Output: List[ChatDocument] mit vollständigen Metadaten

B. Content Extraction Pipeline

  • Funktion: extractContent()
  • Service: Extraction Service (serviceExtraction/mainServiceExtraction.py)
  • Was passiert:
    • Lädt Dokument-Bytes aus Component Storage
    • Findet passenden Extractor über Registry
    • Extrahiert ContentParts (text, image, table, structure, etc.)
    • Wendet Merging-Strategie an
  • Sub-Funktionen:
    • runExtraction(): Orchestriert Extraction Pipeline
    • ExtractorRegistry.resolve(): Findet passenden Extractor
    • extractor.extract(): Extrahiert ContentParts (Extractor-spezifisch)
    • applyMerging(): Merged ContentParts nach Strategie

C. Extraction Prompt Building

  • Funktion: buildExtractionPrompt()
  • Service: Extraction Service (serviceExtraction/subPromptBuilderExtraction.py)
  • Was passiert:
    • Analysiert User Prompt für Extraction Intent
    • Erstellt strukturierten Extraction Prompt
    • Integriert Format-spezifische Guidelines (via Renderer)
  • Sub-Funktionen:
    • _parseExtractionIntent(): AI-basierte Intent-Analyse
    • Renderer-Integration für Format-Guidelines

D. Content Parts Processing

  • Funktion: processContentPartsWithAi()
  • Service: Extraction Service (serviceExtraction/mainServiceExtraction.py)
  • Was passiert:
    • Verarbeitet jeden ContentPart einzeln
    • Bilder werden mit Vision-Modellen analysiert
    • Text wird extrahiert
    • Ergebnisse werden gemerged
  • Sub-Funktionen:
    • processContentPartWithFallback(): Verarbeitet einzelnen Part mit Fallback
    • chunkContentPartForAi(): Chunked große Parts für Model-Limits
    • aiObjects._callWithModel(): Führt AI-Call aus
    • mergeChunkResults(): Merged Chunk-Ergebnisse
    • mergePartResults(): Merged alle Part-Ergebnisse

E. Generation Prompt Building

  • Funktion: buildGenerationPrompt()
  • Service: Generation Service (serviceGeneration/subPromptBuilderGeneration.py)
  • Was passiert:
    • Erstellt Generation Prompt mit extracted_content
    • Integriert JSON Template
    • Handhabt Continuation Context für Looping
  • Input: outputFormat, userPrompt, extracted_content, continuationContext
  • Output: Kompletter Generation Prompt String

F. AI Generation mit Looping

  • Funktion: _callAiWithLooping()
  • Service: AI Service (serviceAi/mainServiceAi.py)
  • Was passiert:
    • Führt AI-Call aus
    • Repariert broken JSON automatisch
    • Merged Sections über mehrere Iterationen
    • Trackt KPIs für große Dokumente
  • Sub-Funktionen:
    • callAi(): Führt AI-Call aus
    • _extractSectionsFromResponse(): Extrahiert Sections aus JSON
    • JsonResponseHandler.mergeSectionsIntelligently(): Merged Sections
    • _defineKpisFromPrompt(): Definiert KPIs für Tracking
    • JsonResponseHandler.extractKpiValuesFromJson(): Extrahiert KPI-Werte
    • JsonResponseHandler.validateKpiProgression(): Validiert KPI-Fortschritt
    • buildContinuationContext(): Erstellt Continuation Context
    • _buildFinalResultFromSections(): Baut finales JSON zusammen

G. Document Rendering

  • Funktion: renderReport()
  • Service: Generation Service (serviceGeneration/mainServiceGeneration.py)
  • Was passiert:
    • Wählt passenden Renderer für Format
    • Rendert JSON-Struktur zu finalem Dokument
    • Handhabt Bilder, Tabellen, Strukturen format-spezifisch
  • Sub-Funktionen:
    • getRendererForFormat(): Findet Renderer (PDF, DOCX, XLSX, etc.)
    • renderer.render(): Format-spezifisches Rendering

3.1.2 Baustein-Separation: Übersicht

Die Sub-Funktionen sind sauber separiert in folgende Bausteine:

  1. Document Resolution Layer

    • getChatDocumentsFromDocumentList(): Konvertiert Referenzen zu Objekten
    • Zuständigkeit: Metadaten-Auflösung
  2. Extraction Layer

    • extractContent(): Orchestriert Extraction
    • runExtraction(): Pipeline-Orchestrierung
    • ExtractorRegistry: Extractor-Verwaltung
    • extractor.extract(): Format-spezifische Extraktion
    • Zuständigkeit: Dokument → ContentParts
  3. Prompt Building Layer

    • buildExtractionPrompt(): Extraction Prompt
    • buildGenerationPrompt(): Generation Prompt
    • _parseExtractionIntent(): Intent-Analyse
    • Zuständigkeit: Prompt-Erstellung
  4. AI Processing Layer

    • processContentPartsWithAi(): ContentParts-Verarbeitung
    • processContentPartWithFallback(): Einzelne Part-Verarbeitung
    • chunkContentPartForAi(): Chunking für große Parts
    • aiObjects._callWithModel(): AI-Call-Ausführung
    • Zuständigkeit: ContentParts → AI-Response
  5. Generation Layer

    • _callAiWithLooping(): Generation mit Looping
    • _extractSectionsFromResponse(): JSON-Parsing
    • JsonResponseHandler: Section-Merging, KPI-Tracking
    • _buildFinalResultFromSections(): Final Assembly
    • Zuständigkeit: AI-Response → JSON-Struktur
  6. Rendering Layer

    • renderReport(): Rendering-Orchestrierung
    • getRendererForFormat(): Renderer-Auswahl
    • renderer.render(): Format-spezifisches Rendering
    • Zuständigkeit: JSON-Struktur → Finales Dokument

Vorteile der Separation:

  • Klare Zuständigkeiten: Jeder Baustein hat eine spezifische Aufgabe
  • Wiederverwendbarkeit: Bausteine können einzeln verwendet werden
  • Testbarkeit: Jeder Baustein kann isoliert getestet werden
  • Wartbarkeit: Änderungen sind lokalisiert

Aktuelle Probleme:

  • Fehlende Intent-Analyse: Keine Pre-Extraction Analysis
  • Automatische Extraktion: Immer, ohne Analyse
  • Bild-Behandlung: Immer Vision-Analyse, keine Asset-Bereitstellung

3.2 Dokument-Extraktion (PROBLEM-BEREICH)

Was passiert:

  • KRITISCH: Dokumente werden IMMER extrahiert, wenn documentList vorhanden ist
  • Extraktion erfolgt OHNE Analyse, ob Extraktion nötig ist
  • Standard-ExtractionOptions werden verwendet

Code-Stelle:

# If contentParts not provided but documentList is, extract content first
if not contentParts and documentList.references:
    self.services.chat.progressLogUpdate(operationId, 0.3, "Extracting content from documents")
    
    # Get ChatDocuments
    chatDocuments = self.services.chat.getChatDocumentsFromDocumentList(documentList)
    if not chatDocuments:
        logger.warning("No documents found in documentList")
    else:
        logger.info(f"Extracting content from {len(chatDocuments)} documents")
        
        # Prepare extraction options (use defaults if not provided)
        extractionOptions = parameters.get("extractionOptions")
        if not extractionOptions:
            extractionOptions = ExtractionOptions(
                prompt="Extract all content from the document",
                mergeStrategy=MergeStrategy(
                    mergeType="concatenate",
                    groupBy="typeGroup",
                    orderBy="id"
                ),
                processDocumentsIndividually=True
            )
        
        # Extract content using extraction service with hierarchical progress logging
        # Pass operationId for per-document progress tracking
        extractedResults = self.services.extraction.extractContent(chatDocuments, extractionOptions, operationId=operationId)
        
        # Combine all ContentParts from all extracted results
        contentParts = []
        for extracted in extractedResults:
            if extracted.parts:
                contentParts.extend(extracted.parts)
        
        logger.info(f"Extracted {len(contentParts)} content parts from {len(extractedResults)} documents")

Problem:

  • Extraktion erfolgt automatisch ohne Analyse der User-Intention
  • Standard-Prompt: "Extract all content from the document"
  • Keine Unterscheidung zwischen:
    • Dokumenten, die extrahiert werden müssen (z.B. PDF mit Text)
    • Dokumenten, die direkt verwendet werden sollen (z.B. Bilder für Rendering)
    • Dokumenten, die nur als Referenz dienen

Extraction Service (extractContent):

Datei: serviceExtraction/mainServiceExtraction.py

Was passiert:

  • Dokumente werden durch Extraction Pipeline verarbeitet
  • ContentParts werden erstellt (text, table, image, structure, etc.)
  • Bilder werden als typeGroup="image" erkannt
  • Text wird als typeGroup="text" erkannt

Code-Stelle:

def extractContent(
    self, 
    documents: List[ChatDocument], 
    options: ExtractionOptions,
    operationId: Optional[str] = None,
    parentOperationId: Optional[str] = None
) -> List[ContentExtracted]:

Ergebnis:

  • List[ContentExtracted] mit ContentPart[]
  • Jeder ContentPart hat:
    • typeGroup: "text" | "table" | "image" | "structure" | "container" | "binary"
    • mimeType: z.B. "image/png", "text/plain"
    • data: Content-Daten (Text als String, Bilder als base64)

3.3 AI Call Preparation

Was passiert:

  • AiCallOptions wird erstellt
  • callAiContent wird aufgerufen mit contentParts

Code-Stelle:

# Use unified callAiContent method with contentParts (extraction is now separate)
aiResponse = await self.services.ai.callAiContent(
    prompt=aiPrompt,
    options=options,
    contentParts=contentParts,  # Already extracted (or None if no documents)
    outputFormat=output_format,
    parentOperationId=operationId
)

Phase 4: AI Service - callAiContent

Datei: serviceAi/mainServiceAi.py

4.1 Entry Point (callAiContent)

Was passiert:

  • Unified AI content processing
  • Entscheidet zwischen verschiedenen Operation Types

Code-Stelle:

async def callAiContent(
    self,
    prompt: str,
    options: AiCallOptions,
    contentParts: Optional[List[ContentPart]] = None,
    outputFormat: Optional[str] = None,
    title: Optional[str] = None,
    parentOperationId: Optional[str] = None  # Parent operation ID for hierarchical logging
) -> AiResponse:

4.2 Content Parts Processing (PROBLEM-BEREICH)

Was passiert:

  • Wenn contentParts vorhanden sind, werden sie verarbeitet
  • PROBLEM: Es wird IMMER ein Extraction Prompt erstellt, auch wenn nicht klar ist, was extrahiert werden soll

Code-Stelle:

# Process contentParts for generation prompt (if provided)
# Use generic callWithContentParts() which handles all content types (images, text, etc.)
# This automatically processes images with vision models and merges all results
if contentParts:
    # Filter out binary/other parts that shouldn't be processed
    processableParts = []
    skippedParts = []
    for p in contentParts:
        if p.typeGroup in ["image", "text", "table", "structure"] or (p.mimeType and (p.mimeType.startswith("image/") or p.mimeType.startswith("text/"))):
            processableParts.append(p)
        else:
            skippedParts.append(p)
    
    if skippedParts:
        logger.debug(f"Skipping {len(skippedParts)} binary/other parts from document generation")
    
    if processableParts:
        # Count images for progress update
        imageCount = len([p for p in processableParts if p.typeGroup == "image" or (p.mimeType and p.mimeType.startswith("image/"))])
        if imageCount > 0:
            self.services.chat.progressLogUpdate(aiOperationId, 0.25, f"Extracting data from {imageCount} images using vision models")
        
        # Build proper extraction prompt using buildExtractionPrompt
        # This creates a focused extraction prompt, not the user's generation prompt
        from modules.services.serviceExtraction.subPromptBuilderExtraction import buildExtractionPrompt
        
        # Determine renderer for format-specific guidelines
        renderer = None
        if outputFormat:
            try:
                from modules.services.serviceGeneration.mainServiceGeneration import GenerationService
                generationService = GenerationService(self.services)
                renderer = generationService.getRendererForFormat(outputFormat)
            except Exception as e:
                logger.debug(f"Could not get renderer for format {outputFormat}: {e}")
        
        extractionPrompt = await buildExtractionPrompt(
            outputFormat=outputFormat or "txt",
            userPrompt=prompt,  # User's prompt as context for what to extract
            title=title or "Document",
            aiService=self if hasattr(self, 'aiObjects') and self.aiObjects else None,
            services=self.services,
            renderer=renderer
        )
        
        logger.info(f"Processing {len(processableParts)} content parts ({imageCount} images) with extraction prompt")
        
        # Use DATA_EXTRACT operation type for extraction
        extractionOptions = AiCallOptions(
            operationType=OperationTypeEnum.DATA_EXTRACT,  # Use DATA_EXTRACT for extraction
            compressPrompt=options.compressPrompt,
            compressContext=options.compressContext
        )
        
        extractionRequest = AiCallRequest(
            prompt=extractionPrompt,  # Use proper extraction prompt, not user's generation prompt
            context="",
            options=extractionOptions,
            contentParts=processableParts
        )
        
        # Write debug file for extraction prompt (all parts)
        self.services.utils.writeDebugFile(extractionPrompt, "content_extraction_prompt")
        
        # Call generic content parts processor - handles images, text, chunking, merging
        extractionResponse = await self.callAi(extractionRequest)
        
        # Write debug file for extraction response
        if extractionResponse.content:
            self.services.utils.writeDebugFile(extractionResponse.content, "content_extraction_response")
        else:
            self.services.utils.writeDebugFile(f"Error: No content returned (errorCount={extractionResponse.errorCount})", "content_extraction_response")
            logger.warning(f"Content extraction returned no content (errorCount={extractionResponse.errorCount})")
        
        # Use extracted content directly for generation prompt
        if extractionResponse.errorCount == 0 and extractionResponse.content:
            # The extracted content is already merged and ready to use
            content_for_generation = extractionResponse.content
            logger.info(f"Successfully extracted content from {len(processableParts)} parts ({len(extractionResponse.content)} chars) for document generation")
        else:
            # Extraction failed - use placeholders
            logger.warning(f"Content extraction failed, using placeholders")
            placeholderParts = []
            for p in processableParts:
                placeholderParts.append(f"[{p.typeGroup}: {p.label} - Extraction failed]")
            content_for_generation = "\n\n".join(placeholderParts) if placeholderParts else None
    else:
        content_for_generation = None
        logger.debug("No processable parts found in contentParts")
else:
    content_for_generation = None

Problem:

  • Bilder werden IMMER mit Vision-Modellen analysiert (Text-Extraktion)
  • Keine Unterscheidung zwischen:
    • Bildern, die gerendert werden sollen (z.B. Logo im Bericht)
    • Bildern, deren Text extrahiert werden soll (z.B. Screenshot mit Text)

4.3 Extraction Prompt Building

Datei: serviceExtraction/subPromptBuilderExtraction.py

Was passiert:

  • Extraction Prompt wird erstellt
  • User Prompt wird analysiert, um Intent zu extrahieren
  • PROBLEM: Intent-Analyse ist sehr generisch

Code-Stelle:

async def buildExtractionPrompt(
    outputFormat: str,
    userPrompt: str,
    title: str,
    aiService=None,
    services=None,
    renderer: _RendererLike = None
) -> str:

Extraction Intent Parsing:

async def _parseExtractionIntent(userPrompt: str, outputFormat: str, aiService=None, services=None) -> str:
    """
    Parse user prompt to extract the core extraction intent.
    """
    if not aiService:
        return f"Extract content from the provided documents and create a {outputFormat} report."
    
    try:
        analysis_prompt = f"""
Analyze this user request and extract the core extraction intent:

User request: "{userPrompt}"
Target format: {outputFormat}

Extract the main intent and requirements for document processing. Focus on:
1. What content needs to be extracted
2. How it should be organized
3. Any specific requirements or preferences

Respond with a clear, concise statement of the extraction intent.
"""
        request_options = AiCallOptions()
        request_options.operationType = OperationTypeEnum.DATA_GENERATE
        
        request = AiCallRequest(prompt=analysis_prompt, context="", options=request_options)
        response = await aiService.aiObjects.call(request)
        
        if response and response.content:
            return response.content.strip()
        else:
            return f"Extract content from the provided documents and create a {outputFormat} report."
            
    except Exception as e:
        services.utils.debugLogToFile(f"Extraction intent analysis failed: {str(e)}", "PROMPT_BUILDER")
        return f"Extract content from the provided documents and create a {outputFormat} report."

Problem:

  • Intent-Analyse ist sehr generisch
  • Keine spezifische Analyse für Bilder (rendern vs. Text extrahieren)
  • Keine Analyse, welche Dokumente überhaupt extrahiert werden müssen

4.4 Content Parts Processing mit AI

Datei: serviceExtraction/mainServiceExtraction.py

Was passiert:

  • ContentParts werden mit AI verarbeitet
  • Bilder werden mit Vision-Modellen analysiert
  • Text wird extrahiert
  • Ergebnisse werden gemerged

Code-Stelle:

async def processContentPartsWithAi(
    self, 
    request: AiCallRequest, 
    aiObjects,  # Pass interface for AI calls
    progressCallback=None
) -> AiCallResponse:
    """Process content parts with model-aware chunking and AI calls.
    
    Moved from interfaceAiObjects.callWithContentParts() - entry point for content parts processing.
    """
    prompt = request.prompt
    options = request.options
    contentParts = request.contentParts
    
    # Get failover models
    availableModels = modelRegistry.getAvailableModels()
    failoverModelList = modelSelector.getFailoverModelList(prompt, "", options, availableModels)
    
    if not failoverModelList:
        return self._createErrorResponse("No suitable models found", 0, 0)
    
    # Process each content part
    allResults = []
    for contentPart in contentParts:
        partResult = await self.processContentPartWithFallback(
            contentPart, prompt, options, failoverModelList, aiObjects, progressCallback
        )
        allResults.append(partResult)
    
    # Merge all results using unified mergePartResults
    mergedContent = self.mergePartResults(allResults)
    
    return AiCallResponse(
        content=mergedContent,
        modelName="multiple",
        priceUsd=sum(r.priceUsd for r in allResults),
        processingTime=sum(r.processingTime for r in allResults),
        bytesSent=sum(r.bytesSent for r in allResults),
        bytesReceived=sum(r.bytesReceived for r in allResults),
        errorCount=sum(r.errorCount for r in allResults)
    )

Bild-Verarbeitung:

async def processContentPartWithFallback(self, contentPart, prompt: str, options, failoverModelList, aiObjects, progressCallback=None) -> AiCallResponse:
    """Process a single content part with model-aware chunking and fallback.
    
    Moved from interfaceAiObjects.py - orchestrates chunking and merging.
    Calls aiObjects._callWithModel() for actual AI calls.
    """
    lastError = None
    
    # Check if this is an image - Vision models need special handling
    isImage = (contentPart.typeGroup == "image") or (contentPart.mimeType and contentPart.mimeType.startswith("image/"))
    
    # Determine the correct operation type based on content type
    actualOperationType = options.operationType
    if isImage:
        actualOperationType = OperationTypeEnum.IMAGE_ANALYSE
        # Get vision-capable models for images
        availableModels = modelRegistry.getAvailableModels()
        visionFailoverList = modelSelector.getFailoverModelList(prompt, "", AiCallOptions(operationType=actualOperationType), availableModels)
        if visionFailoverList:
            logger.debug(f"Using {len(visionFailoverList)} vision-capable models for image processing")
            failoverModelList = visionFailoverList

Problem:

  • Bilder werden IMMER mit IMAGE_ANALYSE behandelt (Text-Extraktion)
  • Keine Möglichkeit, Bilder für Rendering zu markieren

4.5 Generation Prompt Building

Datei: serviceGeneration/subPromptBuilderGeneration.py

Was passiert:

  • Generation Prompt wird erstellt
  • Extracted Content wird in Prompt eingefügt
  • JSON Template wird verwendet

Code-Stelle:

async def buildGenerationPrompt(
    outputFormat: str,
    userPrompt: str,
    title: str,
    extracted_content: str = None,
    continuationContext: Dict[str, Any] = None,
    services: Any = None
) -> str:

Extracted Content Integration:

if extracted_content:
    # If we have extracted content, put it FIRST and make it very clear it's the source data
    generationPrompt = f"""{'='*80}
USER REQUEST / USER PROMPT:
{'='*80}
{userPrompt}
{'='*80}
END OF USER REQUEST / USER PROMPT
{'='*80}

{'='*80}
⚠️ CRITICAL: USE THIS EXTRACTED CONTENT AS YOUR DATA SOURCE ⚠️
{'='*80}
The content below contains the ACTUAL DATA extracted from the source documents.
You MUST use this data - DO NOT generate fake or example data.
{'='*80}
EXTRACTED CONTENT FROM DOCUMENTS:
{'='*80}
{extracted_content}
{'='*80}
END OF EXTRACTED CONTENT
{'='*80}

LANGUAGE REQUIREMENT: All generated content must be in the language '{userLanguage}'. Generate all text, headings, paragraphs, and content in this language. If the extracted content is in a different language, translate it to '{userLanguage}' while preserving the structure and meaning.

Generate a VALID JSON response using the EXTRACTED CONTENT above as your data source.
The JSON structure template below shows ONLY the structure pattern - the example values are NOT real data.
You MUST use the actual data from EXTRACTED CONTENT above, NOT the example values from the template.

JSON structure template (structure only - use data from EXTRACTED CONTENT above):
{jsonTemplate}

Instructions:
- Return ONLY valid JSON (strict). No comments. No trailing commas. Use double quotes.
- Do NOT reuse example section IDs; create your own.
- CRITICAL: Use the ACTUAL DATA from EXTRACTED CONTENT above, NOT the example values from the template.
- Generate complete content based on the user request and the extracted content. Do NOT just give an instruction or comments. Deliver the complete response.
- All content must be in the language '{userLanguage}'.
- IMPORTANT: Set a meaningful "filename" in each document with appropriate file extension (e.g., "prime_numbers.txt", "report.docx", "data.json"). The filename should reflect the content and task objective.
- Output JSON only; no markdown fences or extra text.

Generate your complete response using the extracted content data.
"""

Problem:

  • Extracted Content enthält nur Text (auch von Bildern)
  • Bilder werden nicht als separate Assets für Rendering bereitgestellt
  • Keine Möglichkeit, Bilder direkt im generierten Dokument zu rendern

4.6 Document Generation mit Looping

Was passiert:

  • AI generiert JSON-Struktur
  • Looping-System repariert broken JSON
  • KPI-Tracking für große Dokumente

Code-Stelle:

async def _callAiWithLooping(
    self,
    prompt: str,
    options: AiCallOptions,
    debugPrefix: str = "ai_call",
    promptBuilder: Optional[callable] = None,
    promptArgs: Optional[Dict[str, Any]] = None,
    operationId: Optional[str] = None,
    userPrompt: Optional[str] = None
) -> str:

4.7 Rendering

Was passiert:

  • JSON wird zu finalem Dokument gerendert (PDF, DOCX, etc.)
  • Bilder werden aus JSON-Struktur gerendert (wenn vorhanden)

Code-Stelle:

try:
    from modules.services.serviceGeneration.mainServiceGeneration import GenerationService
    generationService = GenerationService(self.services)
    self.services.chat.progressLogUpdate(renderOperationId, 0.5, f"Rendering to {outputFormat} format")
    rendered_content, mime_type, _images = await generationService.renderReport(
        generated_data, outputFormat, extractedTitle or "Generated Document", prompt, self
    )
    self.services.chat.progressLogFinish(renderOperationId, True)
    
    # Determine document name
    if extractedFilename:
        documentName = extractedFilename
    elif extractedTitle and extractedTitle != "Generated Document":
        sanitized = re.sub(r"[^a-zA-Z0-9._-]", "_", extractedTitle)
        sanitized = re.sub(r"_+", "_", sanitized).strip("_")
        if sanitized:
            if not sanitized.lower().endswith(f".{outputFormat}"):
                documentName = f"{sanitized}.{outputFormat}"
            else:
                documentName = sanitized
        else:
            documentName = f"generated.{outputFormat}"
    else:
        documentName = f"generated.{outputFormat}"
    
    # Build document data
    docData = DocumentData(
        documentName=documentName,
        documentData=rendered_content,
        mimeType=mime_type,
        sourceJson=generated_data  # Preserve source JSON for structure validation
    )
    
    metadata = AiResponseMetadata(
        title=extractedTitle or title or "Generated Document",
        filename=extractedFilename,
        operationType=opType.value if opType else None
    )
    
    # Write JSON with proper formatting (not str() which can truncate)
    jsonStr = json.dumps(generated_data, indent=2, ensure_ascii=False)
    self.services.utils.writeDebugFile(jsonStr, "document_generation_response")
    self.services.chat.progressLogFinish(aiOperationId, True)
    
    return AiResponse(
        content=json.dumps(generated_data),
        metadata=metadata,
        documents=[docData]
    )

Problem:

  • Bilder müssen in JSON-Struktur enthalten sein, um gerendert zu werden
  • Original-Bilder aus contentParts werden nicht direkt verwendet
  • Nur extrahierter Text von Bildern wird verwendet

Phase 5: Action Result Processing

5.1 Result Extraction

Was passiert:

  • AiResponse wird zu ActionResult konvertiert
  • Dokumente werden extrahiert

Code-Stelle:

# Extract documents from AiResponse
if aiResponse.documents and len(aiResponse.documents) > 0:
    action_documents = []
    for doc in aiResponse.documents:
        validationMetadata = {
            "actionType": "ai.process",
            "resultType": normalized_result_type,
            "outputFormat": output_format,
            "hasDocuments": True,
            "documentCount": len(aiResponse.documents)
        }
        action_documents.append(ActionDocument(
            documentName=doc.documentName,
            documentData=doc.documentData,
            mimeType=doc.mimeType or output_mime_type,
            sourceJson=getattr(doc, 'sourceJson', None),  # Preserve source JSON for structure validation
            validationMetadata=validationMetadata
        ))
    
    final_documents = action_documents
else:
    # Text response - create document from content
    extension = output_extension.lstrip('.')
    meaningful_name = self._generateMeaningfulFileName(
        base_name="ai",
        extension=extension,
        action_name="result"
    )
    validationMetadata = {
        "actionType": "ai.process",
        "resultType": normalized_result_type,
        "outputFormat": output_format,
        "hasDocuments": False,
        "contentType": "text"
    }
    action_document = ActionDocument(
        documentName=meaningful_name,
        documentData=aiResponse.content,
        mimeType=output_mime_type,
        validationMetadata=validationMetadata
    )
    final_documents = [action_document]

5.2 Task Result Persistence

Was passiert:

  • Task Result wird als ChatMessage persistiert
  • Dokumente werden in Component Storage gespeichert
  • Referenzen werden für nachfolgende Tasks verfügbar gemacht

Code-Stelle:

async def persistTaskResult(self, taskResult: Any, workflow: ChatWorkflow, context: Optional[TaskContext] = None) -> ChatMessage:  # TaskResult -> ChatMessage

Erweiterte Analyse: Drei kritische Aspekte

1. Mehrere Extraction Options für dasselbe Dokument

Problem: Aktuell wird jedes Dokument nur einmal extrahiert. Es gibt keine Möglichkeit, für dasselbe Dokument mehrere Extraktionen durchzuführen.

Beispiel-Szenario:

  • User möchte: "Erstelle einen Bericht mit Bildern. Übernehme die Bilder als Assets UND extrahiere Text aus den Bildern für Legenden."

Aktueller Flow:

  1. PDF wird extrahiert → Bilder werden als ContentPart mit typeGroup="image" erstellt
  2. Bilder werden mit Vision-Modellen analysiert → Text wird extrahiert
  3. PROBLEM: Original-Bilder gehen verloren, nur Text bleibt übrig

Was fehlt:

  • Keine Möglichkeit, dasselbe Bild sowohl als Asset als auch für Text-Extraktion zu verwenden
  • Keine Duplizierung von ContentParts für verschiedene Extraction-Options
  • Keine parallele Verarbeitung mit unterschiedlichen Intents

Code-Analyse:

Extraction (extractContent):

def extractContent(
    self, 
    documents: List[ChatDocument], 
    options: ExtractionOptions,
    operationId: Optional[str] = None,
    parentOperationId: Optional[str] = None
) -> List[ContentExtracted]:
  • Jedes Dokument wird einmal durch die Pipeline geschickt
  • ExtractionOptions enthält nur eine Strategie
  • Keine Möglichkeit für mehrere Extraction-Passes

Content Parts Processing:

if contentParts:
    # Filter out binary/other parts that shouldn't be processed
    processableParts = []
    skippedParts = []
    for p in contentParts:
        if p.typeGroup in ["image", "text", "table", "structure"] or (p.mimeType and (p.mimeType.startswith("image/") or p.mimeType.startswith("text/"))):
            processableParts.append(p)
        else:
            skippedParts.append(p)
  • Bilder werden entweder analysiert oder übersprungen
  • Keine parallele Verarbeitung mit verschiedenen Intents

Empfohlene Lösung:

class ExtractionOptions:
    multiPassExtraction: Optional[List[ExtractionPass]] = None

class ExtractionPass:
    intent: str  # "image_asset" | "text_extraction" | "structure_extraction"
    filterTypeGroups: List[str]  # Welche typeGroups sollen verarbeitet werden
    operationType: OperationTypeEnum
    preserveOriginal: bool  # Original-Part behalten

# Beispiel:
extractionOptions = ExtractionOptions(
    multiPassExtraction=[
        ExtractionPass(
            intent="image_asset",
            filterTypeGroups=["image"],
            operationType=OperationTypeEnum.DATA_EXTRACT,
            preserveOriginal=True  # Bild als Asset behalten
        ),
        ExtractionPass(
            intent="text_extraction",
            filterTypeGroups=["image"],
            operationType=OperationTypeEnum.IMAGE_ANALYSE,
            preserveOriginal=False  # Text-Extraktion aus Bild
        )
    ]
)

2. Metadaten im Generation Prompt

Problem: Metadaten über Dokument-Herkunft gehen im Generation Prompt verloren.

Aktueller Flow:

1. Extraction Phase - Metadaten werden gespeichert:

# Attach document id and MIME type to parts if missing
for p in ec.parts:
    if "documentId" not in p.metadata:
        p.metadata["documentId"] = documentData["id"] or str(uuid.uuid4())
    if "documentMimeType" not in p.metadata:
        p.metadata["documentMimeType"] = documentData["mimeType"]
  • ContentParts haben metadata["documentId"] und metadata["documentMimeType"]

2. Content Parts Processing - Metadaten bleiben erhalten:

def _convertToContentParts(
    self, partResults: Union[List[PartResult], List[AiCallResponse]]
) -> List[ContentPart]:
    # ...
    metadata={
        **part_result.originalPart.metadata,  # ← Metadaten werden übernommen
        "aiResult": True,
        "partIndex": part_result.partIndex,
        "documentId": part_result.documentId,  # ← documentId bleibt erhalten
        ...
    }
  • Metadaten bleiben in ContentParts erhalten

3. Merging - Metadaten bleiben erhalten:

def mergePartResults(
    self,
    partResults: Union[List[PartResult], List[AiCallResponse]],
    options: Optional[AiCallOptions] = None
) -> str:
    # ...
    merge_strategy = MergeStrategy(
        useIntelligentMerging=True,
        groupBy="documentId",  # ← Gruppierung nach documentId
        orderBy="partIndex",
        mergeType="concatenate"
    )
    # ...
    final_content = "\n\n".join([part.data for part in merged_parts])  # ← NUR data wird verwendet!
  • PROBLEM: Beim Merging zu String gehen Metadaten verloren!
  • Nur part.data wird verwendet, Metadaten werden nicht in den String übernommen

4. Generation Prompt - Keine Metadaten:

if extracted_content:
    generationPrompt = f"""
...
EXTRACTED CONTENT FROM DOCUMENTS:
{'='*80}
{extracted_content}  # ← Nur Text, keine Metadaten!
{'='*80}
END OF EXTRACTED CONTENT
...
"""
  • extracted_content ist nur Text-String
  • Keine Metadaten über Dokument-Herkunft
  • Generation kann nicht unterscheiden, welcher Content von welchem Dokument stammt

Code-Stelle, wo Metadaten verloren gehen:

# Convert back to string
final_content = "\n\n".join([part.data for part in merged_parts])

Empfohlene Lösung:

def mergePartResults(...) -> str:
    # ...
    merged_parts = applyMerging(content_parts, merge_strategy)
    
    # Erweitertes Format mit Metadaten
    content_sections = []
    for part in merged_parts:
        doc_id = part.metadata.get("documentId", "unknown")
        doc_mime = part.metadata.get("documentMimeType", "unknown")
        label = part.label or "content"
        
        section = f"""
[SOURCE: documentId={doc_id}, mimeType={doc_mime}, label={label}]
{part.data}
[END SOURCE]
"""
        content_sections.append(section)
    
    final_content = "\n\n".join(content_sections)
    return final_content.strip()

3. Korrekte Zusammenführung aller Parts

Frage: Wird sichergestellt, dass alle Parts korrekt zusammengefügt werden?

Analyse der Merging-Logik:

A. Merging-Strategien:

1. TextMerger (mergerText.py):

class TextMerger:
    def merge(self, parts: List[ContentPart], strategy: MergeStrategy) -> List[ContentPart]:
        # Group parts
        groups = self._groupParts(parts, groupBy)  # ← Gruppierung nach documentId/parentId
        
        merged: List[ContentPart] = []
        for groupKey, groupParts in groups.items():
            # Sort within group
            sortedParts = self._sortParts(groupParts, orderBy)  # ← Sortierung nach partIndex
            
            # Merge respecting maxSize
            if maxSize > 0:
                merged.extend(self._mergeWithSizeLimit(sortedParts, maxSize))
            else:
                merged.extend(self._mergeGroup(sortedParts, groupKey))
        
        return merged
  • Gruppierung nach documentId oder parentId
  • Sortierung nach partIndex, pageIndex, oder sheetIndex
  • Size-Limits werden respektiert

2. IntelligentTokenAwareMerger (subMerger.py):

class IntelligentTokenAwareMerger:
    def mergeChunksIntelligently(self, chunks: List[ContentPart], prompt: str = "") -> List[ContentPart]:
        # Group chunks by document and type for semantic coherence
        groupedChunks = self._groupChunksByDocumentAndType(chunks)  # ← Gruppierung nach docId + typeGroup
        
        mergedParts = []
        for groupKey, groupChunks in groupedChunks.items():
            # Merge chunks within this group optimally
            groupMerged = self._mergeGroupOptimally(groupChunks, availableTokens)
            mergedParts.extend(groupMerged)
        
        return mergedParts
  • Gruppierung nach documentId + typeGroup
  • Token-bewusste Optimierung
  • ⚠️ PROBLEM: Sortierung nach Original-Reihenfolge wird nicht explizit erhalten

3. mergePartResults:

def mergePartResults(
    self,
    partResults: Union[List[PartResult], List[AiCallResponse]],
    options: Optional[AiCallOptions] = None
) -> str:
    # ...
    if isinstance(partResults[0], PartResult):
        merge_strategy = MergeStrategy(
            useIntelligentMerging=True,
            groupBy="documentId",  # ← Gruppierung nach Dokument
            orderBy="partIndex",   # ← Sortierung nach Part-Index
            mergeType="concatenate"
        )
    else:
        merge_strategy = MergeStrategy(
            useIntelligentMerging=True,
            groupBy="typeGroup",   # ← Gruppierung nach Typ
            orderBy="id",          # ← Sortierung nach ID
            mergeType="concatenate"
        )
    
    merged_parts = applyMerging(content_parts, merge_strategy)
    final_content = "\n\n".join([part.data for part in merged_parts])
  • Strategie-basierte Gruppierung und Sortierung
  • ⚠️ PROBLEM: Bei AiCallResponse wird nach id sortiert, nicht nach Original-Reihenfolge

B. Potenzielle Probleme:

1. Reihenfolge-Verlust bei AiCallResponse:

elif isinstance(partResults[0], AiCallResponse):
    # Logic from interfaceAiObjects (from content parts processing)
    for i, result in enumerate(partResults):
        if result.content:
            content_part = ContentPart(
                id=str(uuid.uuid4()),  # ← Neue UUID, keine Original-Reihenfolge!
                parentId=None,
                label=f"ai_result_{i}",  # ← Index in Label, aber nicht für Sortierung verwendet
                ...
            )
  • id ist neue UUID, keine Original-Reihenfolge
  • orderBy="id" sortiert nach UUID, nicht nach Verarbeitungs-Reihenfolge

2. Chunking kann Reihenfolge durcheinander bringen:

async def chunkContentPartForAi(self, contentPart, model, options, prompt: str = "") -> List[Dict[str, Any]]:
    # ...
    chunks = chunker.chunk(contentPart, chunkingOptions)
    return chunks
  • Chunks haben parentId, aber keine explizite Reihenfolge innerhalb des Chunks

3. Merging bei Chunks:

chunkResults = []
for idx, chunk in enumerate(chunks):
    chunkNum = idx + 1
    chunkData = chunk.get('data', '')
    logger.info(f"Processing chunk {chunkNum}/{len(chunks)} with model {model.name}")
    
    try:
        chunkResponse = await aiObjects._callWithModel(model, prompt, chunkData, options)
        chunkResults.append(chunkResponse)  # ← Reihenfolge durch Listen-Append erhalten
        logger.info(f"✅ Chunk {chunkNum}/{len(chunks)} processed successfully")
    except Exception as e:
        logger.error(f"❌ Error processing chunk {chunkNum}/{len(chunks)}: {str(e)}")
        raise

# Merge chunk results
mergedContent = self.mergeChunkResults(chunkResults)
  • Chunk-Responses werden sequenziell in Liste gespeichert (chunkResults.append(chunkResponse))
  • Reihenfolge wird durch Listen-Reihenfolge erhalten
  • BEHOBEN: mergeChunkResults wurde durch mergePartResults(chunkResults) ersetzt
  • BEGRÜNDUNG: mergePartResults akzeptiert List[AiCallResponse] (Zeile 780) und chunkResults ist genau das
  • KONSISTENZ: Verwendet jetzt die gleiche Merging-Funktion wie processContentPartsWithAi (Zeile 1111)

C. Was funktioniert:

Gruppierung: Parts werden nach documentId gruppiert Sortierung: Bei PartResult wird nach partIndex sortiert Size-Limits: Werden respektiert Token-Optimierung: IntelligentTokenAwareMerger optimiert AI-Calls

D. Was problematisch ist:

⚠️ AiCallResponse: Reihenfolge geht verloren (Sortierung nach UUID statt Index) ⚠️ Chunks: Reihenfolge innerhalb von Chunks nicht explizit gesichert ⚠️ Metadaten: Gehen beim Merging zu String verloren mergeChunkResults: Bug behoben - wurde durch mergePartResults ersetzt

E. Zusammenfassung Merging-Logik:

Aspekt Status Details
Gruppierung Funktioniert Nach documentId oder typeGroup
Sortierung (PartResult) Funktioniert Nach partIndex, pageIndex, sheetIndex
Sortierung (AiCallResponse) ⚠️ Problematisch Nach UUID statt Original-Index
Chunk-Reihenfolge ⚠️ Unklar Listen-Reihenfolge erhalten, aber keine explizite Validierung
Metadaten-Erhaltung Geht verloren Beim Merging zu String gehen Metadaten verloren
Size-Limits Funktioniert Werden respektiert
Token-Optimierung Funktioniert IntelligentTokenAwareMerger optimiert AI-Calls
mergeChunkResults BEHOBEN Wurde durch mergePartResults ersetzt (Zeile 1041)

Fazit Merging:

  • Grundlegende Merging-Logik ist vorhanden und funktioniert
  • Kritische Probleme: Metadaten gehen verloren, AiCallResponse-Reihenfolge unklar
  • BEHOBEN: mergeChunkResults wurde durch mergePartResults ersetzt
    • mergePartResults akzeptiert List[AiCallResponse] (Zeile 780)
    • chunkResults ist List[AiCallResponse] (Zeile 1031)
    • Konsistente Verwendung der gleichen Merging-Funktion wie processContentPartsWithAi (Zeile 1111)

Empfohlene Verbesserungen:

# 1. Reihenfolge explizit speichern
content_part = ContentPart(
    id=str(uuid.uuid4()),
    parentId=None,
    label=f"ai_result_{i}",
    metadata={
        "originalIndex": i,  # ← Explizite Reihenfolge
        "processingOrder": i,
        ...
    }
)

# 2. Sortierung nach originalIndex
merge_strategy = MergeStrategy(
    groupBy="documentId",
    orderBy="originalIndex",  # ← Statt "id"
    mergeType="concatenate"
)

# 3. Chunk-Reihenfolge explizit
for idx, chunk in enumerate(chunks):
    chunk["metadata"] = {
        "chunkIndex": idx,
        "parentId": contentPart.id,
        "totalChunks": len(chunks)
    }

Zusammenfassung: Aktuelle Probleme

Problem 1: Automatische Extraktion ohne Analyse

Aktueller Zustand:

  • Dokumente werden IMMER extrahiert, wenn documentList vorhanden ist
  • Keine Analyse, ob Extraktion nötig ist
  • Standard-ExtractionOptions: "Extract all content from the document"

Beispiel-Szenarien, wo Extraktion nicht nötig ist:

  • User möchte Dokumente nur als Referenz verwenden
  • User möchte Dokumente direkt rendern (z.B. Bilder in Bericht einfügen)
  • User möchte Dokumente nur analysieren (nicht extrahieren)

Problem 2: Unklare Bild-Behandlung

Aktueller Zustand:

  • Bilder werden IMMER mit Vision-Modellen analysiert (Text-Extraktion)
  • Keine Unterscheidung zwischen:
    • Bildern, die gerendert werden sollen (Logo, Diagramm, Screenshot als Bild)
    • Bildern, deren Text extrahiert werden soll (Screenshot mit Text-Inhalt)

Beispiel-Szenarien:

  • Szenario A: User möchte Bericht mit Logo-Bildern generieren

    • Aktuell: Logo wird analysiert, Text wird extrahiert (falsch!)
    • Erwartet: Logo wird als Bild-Asset für Rendering bereitgestellt
  • Szenario B: User möchte Text aus Screenshot extrahieren

    • Aktuell: Screenshot wird analysiert, Text wird extrahiert (richtig!)
    • Erwartet: Text wird extrahiert, Screenshot kann optional auch gerendert werden

Problem 3: Fehlende Intent-Analyse

Aktueller Zustand:

  • Intent-Analyse ist sehr generisch
  • Keine spezifische Analyse für:
    • Welche Dokumente extrahiert werden müssen
    • Was aus Dokumenten extrahiert werden soll
    • Wie Bilder behandelt werden sollen

Was fehlt:

  • Analyse der User-Intention bezüglich Dokumenten
  • Klassifizierung von Dokumenten nach Verwendungszweck
  • Entscheidungslogik für Extraktion vs. direkte Verwendung

Problem 4: Fehlende Dokument-Metadaten

Aktueller Zustand:

  • Dokumente haben nur Basis-Metadaten (mimeType, fileName)
  • Keine Metadaten für:
    • Verwendungszweck (extrahieren, rendern, referenzieren)
    • Extraktions-Intent (Text, Struktur, Bilder)
    • Rendering-Intent (Bild als Asset, Bild als Text)

Empfohlene Lösung: Document Intent Analysis

Phase 1: Pre-Extraction Analysis

Neue Funktion: analyzeDocumentIntent

Was passiert:

  • User Prompt wird analysiert
  • Dokument-Referenzen werden analysiert
  • Intent wird bestimmt (als Liste von Intents):
    • Extract: Dokument muss extrahiert werden (Text, Struktur)
    • Render: Dokument/Bilder müssen in generiertes Dokument integriert werden
    • Reference: Dokument dient nur als Referenz/Kontext
    • Mehrere Intents möglich: z.B. ["extract", "render"] für beide Anforderungen

Output:

DocumentIntent = {
    "documentId": str,
    "intents": List[str],  # Liste von Intents: ["extract", "render", "reference"] - mehrere möglich
    "extractionPrompt": Optional[str],  # Spezifischer Prompt für Extraktion (z.B. "Extract text from images for legends")
    "reasoning": str  # Erklärung für Debugging/Transparenz: Warum wurde dieser Intent gewählt?
}

Intents (möglich):

  • "extract": Dokument muss extrahiert werden (Text, Struktur, etc.)
  • "render": Dokument/Bilder müssen in generiertes Dokument integriert werden
  • "reference": Dokument dient nur als Referenz/Kontext

Beispiele:

# Beispiel 1: Bild im Bericht übernehmen UND Text extrahieren
{
    "documentId": "img_001",
    "intents": ["extract", "render"],
    "extractionPrompt": "Extract all text content from this image, including legends, labels, and descriptions",
    "reasoning": "User wants both: image rendered in report (standard process) AND text extracted for legends"
}

# Beispiel 2: Nur Text extrahieren
{
    "documentId": "pdf_001",
    "intents": ["extract"],
    "extractionPrompt": "Extract all text content, preserving structure and formatting",
    "reasoning": "User only needs text extraction, no rendering required"
}

# Beispiel 3: Nur als Referenz
{
    "documentId": "ref_001",
    "intents": ["reference"],
    "extractionPrompt": None,
    "reasoning": "Document is only used as context, no extraction or rendering needed"
}

Warum diese Struktur?

  1. intents als Liste (statt einzelner Wert + "hybrid"):

    • Mehrere Intents gleichzeitig möglich (z.B. ["extract", "render"])
    • Kein "hybrid" nötig - einfach beide Intents in Liste
    • Flexibler und klarer
    • Einfacher zu erweitern (neue Intents einfach hinzufügen)
  2. extractionPrompt statt extractionType (statt feste Kriterien):

    • Flexibler: Kann spezifische Anweisungen enthalten
    • Nicht auf feste Kriterien beschränkt ("text", "structure", "image_text")
    • AI kann genau bestimmen, was extrahiert werden soll
    • Beispiel: "Extract text from images for legends" statt nur "image_text"
    • Kann komplexe Anforderungen beschreiben
  3. Kein renderingInstructions nötig:

    • Bilder werden standardmäßig im JSON integriert und anschließend gerendert
    • Der Prozess ist klar: Wenn "render" in intents, werden Bilder in JSON-Struktur integriert
    • Keine separaten Anweisungen nötig - Standard-Verhalten
    • Renderer erwartet base64Data in JSON-Struktur (automatisch)
  4. reasoning:

    • Für Debugging und Transparenz
    • Erklärt, warum bestimmte Intents gewählt wurden
    • Hilft bei Fehlersuche und Optimierung
    • Kann für Logging und Monitoring verwendet werden

Phase 2: Conditional Extraction

Was passiert:

  • Extraktion erfolgt nur, wenn "extract" in intents
  • ExtractionOptions werden mit extractionPrompt erstellt
  • Wenn extractionPrompt vorhanden, wird dieser als Prompt für Extraktion verwendet
  • Wenn extractionPrompt None, wird Standard-Extraktion verwendet

Code-Beispiel:

if "extract" in documentIntent.intents:
    extractionOptions = ExtractionOptions(
        prompt=documentIntent.extractionPrompt or "Extract all content from the document",
        ...
    )
    extractedResults = self.extractContent(documents, extractionOptions)

Phase 3: Image Handling

Was passiert:

  • WICHTIG: Bilder werden standardmäßig aus JSON-Struktur gerendert (mit base64Data in elements)
  • Keine separate Asset-Pipeline vorhanden
  • Bilder müssen in der generierten JSON-Struktur enthalten sein

Für Bilder mit "render" in intents:

  • Bilder werden standardmäßig als base64Data in JSON-Struktur integriert
  • Keine speziellen Anweisungen nötig - Standard-Verhalten
  • AI integriert Bilder automatisch in generierte JSON-Struktur

Für Bilder mit "extract" in intents:

  • extractionPrompt wird für Vision-Analyse verwendet
  • Text wird extrahiert und in extracted_content bereitgestellt
  • Beispiel: "Extract all text from this image, including legends"

Kombination möglich:

  • intents: ["extract", "render"]
  • Bild wird analysiert (Text extrahiert) UND standardmäßig in JSON integriert

Phase 4: Generation Prompt Enhancement

⚠️ KRITISCHES PROBLEM: Aktuell NICHT implementiert!

Was SOLLTE passieren:

  • Generation Prompt sollte enthalten:
    • Extracted Content (Text, Struktur)
    • DocumentIntent-Informationen (welche Dokumente/Bilder gerendert werden sollen)
    • Referenzen (für Kontext)
  • Bilder sollten integriert werden: Wenn "render" in intents, sollten Bilder in JSON-Struktur integriert werden

Was AKTUELL passiert:

1. DocumentIntent-Informationen gehen verloren:

Code-Stelle (callAiContent):

# Process contentParts for generation prompt (if provided)
if contentParts:
    # Filter out binary/other parts that shouldn't be processed
    processableParts = []
    skippedParts = []
    for p in contentParts:
        if p.typeGroup in ["image", "text", "table", "structure"]:
            processableParts.append(p)
    
    # Build extraction prompt
    extractionPrompt = await buildExtractionPrompt(...)
    
    # Call extraction
    extractionResponse = await self.callAi(extractionRequest)
    
    # Use extracted content directly for generation prompt
    content_for_generation = extractionResponse.content  # ← NUR Text!

Problem:

  • contentParts werden IMMER extrahiert (Bilder → Text via Vision-Modelle)
  • extractionResponse.content enthält nur Text, keine Bild-Assets
  • KEINE DocumentIntent-Informationen werden übergeben
  • KEINE Information, welche Bilder gerendert werden sollen

2. Generation Prompt erhält keine Intent-Informationen:

Code-Stelle (buildGenerationPrompt):

generation_prompt = await buildGenerationPrompt(
    outputFormat, prompt, title, content_for_generation, None, self.services
)

buildGenerationPrompt Signatur:

async def buildGenerationPrompt(
    outputFormat: str,
    userPrompt: str,
    title: str,
    extracted_content: str = None,  # ← NUR Text
    continuationContext: Dict[str, Any] = None,
    services: Any = None
) -> str:

Problem:

  • buildGenerationPrompt erhält nur extracted_content (Text)
  • KEINE DocumentIntent-Informationen
  • KEINE Bild-Assets
  • KEINE Information über Rendering-Anforderungen

3. Generation Prompt enthält keine Bild-Anweisungen:

Code-Stelle (buildGenerationPrompt Inhalt):

if extracted_content:
    generationPrompt = f"""
EXTRACTED CONTENT FROM DOCUMENTS:
{extracted_content}  # ← NUR Text, keine Bilder, keine Intent-Info
...
"""

Problem:

  • Generation Prompt enthält nur Text
  • KEINE Anweisungen, Bilder zu integrieren
  • KEINE Bild-Assets verfügbar
  • AI kann Bilder nicht in JSON integrieren, weil:
    • Original-Bilder nicht verfügbar sind (nur Text-Extraktion)
    • Keine Anweisungen vorhanden sind
    • Keine Metadaten über Intent vorhanden sind

Was FEHLT für korrekte Implementierung:

  1. DocumentIntent-Übergabe:

    # In callAiContent:
    documentIntents: Optional[List[DocumentIntent]] = None
    
    # In buildGenerationPrompt:
    documentIntents: Optional[List[DocumentIntent]] = None
    
  2. Bild-Assets bereitstellen:

    # Für Bilder mit "render" in intents:
    imageAssets = []
    for contentPart in contentParts:
        if contentPart.typeGroup == "image":
            documentIntent = getIntentForDocument(contentPart.documentId)
            if "render" in documentIntent.intents:
                imageAssets.append({
                    "documentId": contentPart.documentId,
                    "base64Data": contentPart.data,
                    "altText": contentPart.label
                })
    

KORREKTE LOGIK: Struktur → Platzhalter → Code-Integration

Aktueller Flow (teilweise implementiert):

1. Struktur-Generierung (Phase 1):

async def generateStructure(...) -> Dict[str, Any]:
    # AI generiert Struktur mit leeren elements: []
    # Für bestehende Bilder: image_source="existing", image_reference_id="doc_id"

Struktur-Beispiel:

{
  "sections": [
    {
      "id": "section_image_existing",
      "content_type": "image",
      "image_source": "existing",  // ← Platzhalter für Code-Integration (bestehendes Bild)
      "image_reference_id": "doc_id_here",
      "elements": []  // ← Leer, wird vom Code gefüllt
    },
    {
      "id": "section_image_generate",
      "content_type": "image",
      // image_source NICHT gesetzt oder "generate" (default) ← Platzhalter für AI-Generierung
      "image_prompt": "A detailed description for image generation",  // ← Platzhalter: Prompt für Bild-Generierung
      "generation_hint": "Illustration for chapter 1",  // ← Optional: Fallback für image_prompt
      "complexity": "complex",
      "elements": []  // ← Leer, wird von AI (IMAGE_GENERATE) gefüllt
    },
    {
      "id": "section_paragraph_1",
      "content_type": "paragraph",
      "complexity": "simple",
      "elements": []  // ← Leer, wird von AI gefüllt
    }
  ]
}

Platzhalter für Bilder:

Platzhalter-Typ image_source Zusätzliche Felder Wer füllt?
Bestehendes Bild integrieren "existing" image_reference_id Code (automatisch)
Bild generieren Nicht gesetzt oder "generate" (default) image_prompt (erforderlich) oder generation_hint (Fallback) AI (IMAGE_GENERATE Operation)

2. Content-Abfüllen (Phase 2):

async def generateContent(...):
    # Iteriert durch Sections
    # Für jedes Element wird Content gefüllt

3. Image-Integration durch Code ODER AI-Generierung:

async def _generateImageSection(...):
    imageSource = section.get("image_source", "generate")  # ← Default: "generate"
    
    if imageSource == "existing":
        # ← CODE integriert bestehendes Bild automatisch!
        imageRefId = section.get("image_reference_id")
        imageDoc = findImageDocument(imageRefId)
        
        section["elements"] = [{
            "base64Data": imageDoc.get("base64Data"),  //  Direkt vom Code
            "altText": imageDoc.get("altText"),
            "mimeType": imageDoc.get("mimeType")
        }]
        return section  //  Kein AI-Call!
    
    # Generate new image (imageSource == "generate" oder nicht gesetzt)
    imagePrompt = section.get("image_prompt")
    if not imagePrompt:
        // Fallback: generation_hint verwenden
        generationHint = section.get("generation_hint", "")
        imagePrompt = f"Create a professional illustration: {generationHint}"
    
    //  AI generiert Bild mit IMAGE_GENERATE Operation
    options = AiCallOptions(
        operationType=OperationTypeEnum.IMAGE_GENERATE,
        resultFormat="base64"
    )
    aiResponse = await self.services.ai.callAiContent(
        prompt=imagePrompt,
        options=options,
        outputFormat="base64"
    )
    
    // Extrahiere base64Data aus AI-Response
    base64Data = extractBase64FromResponse(aiResponse)
    
    section["elements"] = [{
        "base64Data": base64Data,  //  Von AI generiert
        "altText": imagePrompt[:100],
        "mimeType": "image/png"
    }]
    return section

Zusammenfassung Image-Platzhalter:

  1. Bestehendes Bild (image_source="existing"):

    • Platzhalter: image_reference_id
    • Integration: Code (automatisch, kein AI-Call)
    • Beispiel: Bild aus contentParts mit "render" in intents
  2. Bild generieren (image_source nicht gesetzt oder "generate"):

    • Platzhalter: image_prompt (erforderlich) oder generation_hint (Fallback)
    • Integration: AI (IMAGE_GENERATE Operation)
    • Beispiel: "Erzeuge ein Bild zum Thema Kochen ohne Fleisch"

4. Text-Content durch AI:

  • Für content_type: "paragraph" → AI generiert Text
  • Für content_type: "heading" → AI generiert Überschrift
  • Verwendet extracted_content und userPrompt

Was FUNKTIONIERT:

  • Struktur wird zuerst generiert (mit Platzhaltern)
  • Bilder mit image_source="existing" werden automatisch vom Code integriert
  • Text-Sections werden von AI gefüllt

Was FEHLT für DocumentIntent-Integration:

1. Platzhalter für "render" in intents:

# In generateStructure:
# Für Bilder mit "render" in intents sollte Platzhalter erstellt werden:
{
    "id": "section_image_1",
    "content_type": "image",
    "image_source": "render",  # ← NEU: Statt "existing"
    "image_reference_id": contentPart.documentId,  # ← Referenz zu contentPart
    "elements": []
}

2. Code-Integration erweitern:

# In _generateImageSection:
if imageSource == "render":  # ← NEU
    # Finde contentPart basierend auf image_reference_id
    contentPart = findContentPartByDocumentId(image_reference_id)
    if contentPart and contentPart.typeGroup == "image":
        section["elements"] = [{
            "base64Data": contentPart.data,  # ← Direkt vom Code
            "altText": contentPart.label,
            "mimeType": contentPart.mimeType
        }]
        return section  # ← Kein AI-Call!

3. Conditional Extraction:

# In callAiContent (vor Extraction):
for contentPart in contentParts:
    documentIntent = getIntentForDocument(contentPart.documentId)
    
    if "extract" in documentIntent.intents:
        # Extract with extractionPrompt
        processableParts.append(contentPart)
    elif "render" in documentIntent.intents:
        # Keep as asset, don't extract
        # Add to cachedContent.imageDocuments for StructureGenerator
        imageDocuments.append({
            "id": contentPart.documentId,
            "base64Data": contentPart.data,
            "altText": contentPart.label,
            "mimeType": contentPart.mimeType
        })

Zusammenfassung der korrekten Logik:

  1. Struktur-Generierung: AI erzeugt Struktur mit Platzhaltern (elements: [])
  2. Platzhalter für Bilder:
    • Bestehendes Bild: image_source="existing" + image_reference_id → Code integriert automatisch
    • Bild generieren: image_source nicht gesetzt (default: "generate") + image_prompt → AI generiert mit IMAGE_GENERATE
    • Bild mit "render" in intents: image_source="render" + image_reference_id → Code integriert automatisch (noch nicht implementiert)
  3. Content-Abfüllen:
    • Bilder (existing/render): Code füllt automatisch ein (kein AI-Call)
    • Bilder (generate): AI generiert Bild mit IMAGE_GENERATE Operation
    • Text-Sections: AI füllt mit extracted_content und userPrompt
  4. Rendering: Renderer verwendet base64Data aus JSON-Struktur

Code-Stelle (aktuell):

def _renderJsonImage(self, doc: Document, image_data: Dict[str, Any], styles: Dict[str, Any]) -> None:
    """Render a JSON image to DOCX."""
    try:
        base64_data = image_data.get("base64Data", "")  # ← Erwartet base64Data in JSON
        alt_text = image_data.get("altText", "Image")
        
        if base64_data:
            image_bytes = base64.b64decode(base64_data)
            doc.add_picture(io.BytesIO(image_bytes), width=Inches(4))

AKTUELLER STATUS: NICHT IMPLEMENTIERT

Was FEHLT:

  1. DocumentIntent-Übergabe: Intent-Informationen werden nicht von analyzeDocumentIntent bis zu buildGenerationPrompt übergeben
  2. Bild-Assets: Bilder werden IMMER extrahiert (Text), nicht als Assets bereitgestellt
  3. Conditional Extraction: Keine Unterscheidung zwischen "extract" und "render" - alles wird extrahiert
  4. Generation Prompt: Erhält keine Intent-Informationen oder Bild-Assets

Konsequenz:

  • Die Aussage "Bilder werden standardmäßig integriert" ist NICHT sichergestellt
  • AI kann Bilder nicht in JSON integrieren, weil:
    • Original-Bilder nicht verfügbar sind (nur Text-Extraktion)
    • Keine Anweisungen vorhanden sind
    • Keine Metadaten über Intent vorhanden sind

Was IMPLEMENTIERT werden muss: Siehe Abschnitt "Was FEHLT für korrekte Implementierung" oben.

Nächste Schritte

  1. Implementierung von analyzeDocumentIntent

    • AI-basierte Analyse des User Prompts
    • Klassifizierung von Dokumenten nach Verwendungszweck
  2. Erweiterung von ExtractionOptions

    • Neues Feld: documentIntent: Optional[DocumentIntent]
    • Conditional Extraction basierend auf intents
    • Verwendung von extractionPrompt wenn vorhanden
  3. Erweiterung von ContentPart

    • Neues Feld: documentIntent: Optional[DocumentIntent]
    • Metadaten enthalten Intent-Informationen
    • Unterscheidung zwischen Text-Extraktion und Rendering-Anforderungen
  4. Erweiterung von buildGenerationPrompt

    • Integration von Image Assets
    • Separate Behandlung von Extracted Content und Assets

Flow-Diagramm: Aktueller vs. Empfohlener Flow

Aktueller Flow (Problem)

User Input + Documents
    ↓
Complexity Check (nur Metadaten)
    ↓
Workflow Planning
    ↓
Action: ai.process
    ↓
[AUTOMATISCHE EXTRAKTION] ← PROBLEM: Immer, ohne Analyse
    ↓
ContentParts (alle extrahiert)
    ↓
[EXTRACTION PROMPT] ← PROBLEM: Generisch, keine Intent-Analyse
    ↓
[BILDER MIT VISION ANALYSIERT] ← PROBLEM: Immer, auch wenn Rendering gewünscht
    ↓
Extracted Content (nur Text)
    ↓
Generation Prompt
    ↓
Document Generation
    ↓
Rendering (Bilder nur aus JSON)

Empfohlener Flow (Lösung)

User Input + Documents
    ↓
Complexity Check (nur Metadaten)
    ↓
Workflow Planning
    ↓
Action: ai.process
    ↓
[DOCUMENT INTENT ANALYSIS] ← NEU: Analyse, was mit Dokumenten gemacht werden soll
    ↓
    ├─→ Extract Intent → Conditional Extraction
    ├─→ Render Intent → Image Asset Preparation
    └─→ Reference Intent → Skip Extraction
    ↓
ContentParts (selektiv extrahiert/bereitgestellt)
    ↓
[EXTRACTION PROMPT] ← Verbessert: Basierend auf Intent
    ↓
[BILDER SELEKTIV BEHANDELT] ← Verbessert: Text-Extraktion ODER Asset-Bereitstellung
    ↓
Extracted Content + Image Assets
    ↓
Generation Prompt (mit Assets)
    ↓
Document Generation (mit Assets)
    ↓
Rendering (Bilder aus Assets + JSON)

Prompt-Analyse: Konkrete Use Cases

Prompt 1: Buch-Generierung mit Bildern

User Prompt:

"erzeuge ein buch mit den beigelegten bildern zum thema kochen ohne fleisch und integriere generierte bilder, wo bilder fehlen"

Input: 10 Bilder in einem PDF-Dokument

Aktueller Flow-Analyse

Was passiert aktuell:

  1. PDF wird extrahiert (extractContent)

    • PDF-Extractor extrahiert alle Bilder als ContentPart mit typeGroup="image"
    • Bilder werden als base64-encoded Daten bereitgestellt
  2. Bilder werden mit Vision-Modellen analysiert (processContentPartsWithAi)

    • PROBLEM: Alle Bilder werden analysiert, um Text zu extrahieren
    • Vision-Models beschreiben Bild-Inhalt als Text
    • Original-Bilder gehen verloren (nur Text-Beschreibung bleibt)
  3. Extraction Prompt wird erstellt (buildExtractionPrompt)

    • Generischer Prompt: "Extract content from documents"
    • Keine spezifische Anweisung für Bild-Rendering
  4. Generation Prompt enthält nur Text (buildGenerationPrompt)

    • Extracted Content enthält nur Text-Beschreibungen der Bilder
    • PROBLEM: Original-Bilder sind nicht verfügbar für Rendering
  5. Rendering (renderReport)

    • Renderer kann keine Bilder rendern, da nur Text vorhanden ist
    • Generierte Bilder können nicht integriert werden

Probleme

  1. Bilder werden analysiert statt gerendert

    • Aktuell: Bilder → Vision-Analyse → Text-Beschreibung
    • Benötigt: Bilder → Asset-Bereitstellung → Rendering
  2. Keine Möglichkeit, Bilder als Assets zu behalten

    • ContentParts werden nur für Text-Extraktion verwendet
    • Keine separate Asset-Pipeline
  3. Generierte Bilder können nicht integriert werden

    • Keine Möglichkeit, neue Bilder zu generieren und zu integrieren
    • Keine Bild-Generierung im Workflow

Was angepasst werden müsste

1. Pre-Extraction Analysis (DocumentIntent):

DocumentIntent = {
    "documentId": "pdf_1",
    "intents": ["extract", "render"],  # ← Liste statt "hybrid"
    "extractionPrompt": "Extract text content from images for legends and descriptions",
    "reasoning": "User möchte Buch mit Bildern generieren - Bilder müssen gerendert werden UND Text extrahiert"
}

2. Conditional Extraction:

  • PDF wird extrahiert
  • Für Bilder mit "extract" in intents: Vision-Analyse für Text-Extraktion
  • Für Bilder mit "render" in intents: Bilder werden NICHT extrahiert, bleiben als Assets
  • Original-Bild-Daten bleiben erhalten für Rendering

3. Struktur-Generierung mit Platzhaltern:

  • StructureGenerator erzeugt Struktur mit Platzhaltern für Bilder
  • Für bestehende Bilder: image_source="render" + image_reference_id
  • Für fehlende Bilder: image_source nicht gesetzt + image_prompt (AI generiert)

4. Content-Abfüllen:

  • Code integriert Bilder automatisch (bei image_source="render" oder "existing")
  • AI generiert fehlende Bilder (bei image_source nicht gesetzt, mit image_prompt)
  • AI füllt Text-Sections mit extracted_content

5. Rendering:

  • Renderer verwendet base64Data aus JSON-Struktur (bereits integriert)
  • Keine separate Asset-Pipeline nötig

Erforderliche Code-Änderungen

  1. DocumentIntent implementieren:

    class DocumentIntent:
        documentId: str
        intents: List[str]  # ["extract", "render", "reference"]
        extractionPrompt: Optional[str]
        reasoning: str
    
  2. callAiContent erweitern:

    async def callAiContent(
        ...,
        documentIntents: Optional[List[DocumentIntent]] = None  # ← NEU
    ):
        # Conditional Extraction basierend auf intents
        for contentPart in contentParts:
            documentIntent = getIntentForDocument(contentPart.documentId)
    
            if "extract" in documentIntent.intents:
                # Extract with extractionPrompt
                processableParts.append(contentPart)
            elif "render" in documentIntent.intents:
                # Keep as asset, add to cachedContent.imageDocuments
                imageDocuments.append({
                    "id": contentPart.documentId,
                    "base64Data": contentPart.data,
                    "altText": contentPart.label,
                    "mimeType": contentPart.mimeType
                })
    
  3. StructureGenerator.generateStructure erweitern:

    # Für Bilder mit "render" in intents:
    # Erstelle Platzhalter mit image_source="render"
    {
        "id": "section_image_1",
        "content_type": "image",
        "image_source": "render",  # ← NEU
        "image_reference_id": contentPart.documentId,
        "elements": []
    }
    
  4. ContentGenerator._generateImageSection erweitern:

    if imageSource == "render":  # ← NEU
        # Finde contentPart basierend auf image_reference_id
        contentPart = findContentPartByDocumentId(image_reference_id)
        if contentPart and contentPart.typeGroup == "image":
            section["elements"] = [{
                "base64Data": contentPart.data,  # ← Code integriert automatisch
                "altText": contentPart.label,
                "mimeType": contentPart.mimeType
            }]
            return section  # ← Kein AI-Call!
    
  5. cachedContent erweitern:

    # In callAiContent:
    cachedContent = {
        "extracted_content": extracted_content,  # Text
        "imageDocuments": imageDocuments  # ← Bilder für Rendering
    }
    

KEINE Änderungen nötig:

  • ExtractionOptions erweitern (nicht nötig - DocumentIntent reicht)
  • ContentPart erweitern (nicht nötig - Platzhalter-System verwendet)
  • renderReport erweitern (nicht nötig - Bilder bereits in JSON-Struktur)

Prompt 2: PDF-Splitting mit Web-Research

User Prompt:

"extrahiere aus dem pdf die einzelnen dokumente, welche mit trennseiten (leere seiten) auseinandergehalten werden, und speichere sie im sharepoint als separate dateien ab. ergänze aus dem web die adressen pro firma jedes dokumentes"

Input: PDF mit 600 Seiten

WICHTIG: Dieser Prompt sollte als Serie von Actions gelöst werden, NICHT als spezifische Logik im Code!

Lösung als Workflow-Actions

Workflow sollte folgende Actions ausführen:

  1. Action 1: ai.process - Content extrahieren

    ai.process(
        aiPrompt="Extract all content from the PDF, preserving page structure",
        documentList=[pdf_document],
        resultType="json"
    )
    
    • Extrahiert Content aus PDF
    • Erhält ContentParts mit Seiten-Informationen
  2. Action 2: ai.process - Content analysieren und Dokumente trennen

    ai.process(
        aiPrompt="""Analyze the extracted content and identify document boundaries.
        Documents are separated by empty pages (page breaks).
        For each document:
        1. Identify the document boundaries (start/end pages)
        2. Extract the company name
        3. Group content by document
    
        Return JSON structure with separate documents.""",
        documentList=[extracted_content_from_action1],
        resultType="json"
    )
    
    • Analysiert ContentParts
    • Identifiziert Trennseiten (leere Seiten)
    • Gruppiert Content nach Dokumenten
    • Extrahiert Firmen-Namen
  3. Action 3: ai.webResearch - Adressen recherchieren

    ai.webResearch(
        prompt="Find the address for company: {company_name}",
        country="CH",  # oder aus Context
        language="de"
    )
    
    • Für jede Firma: Web-Research für Adresse
    • Kann in Loop ausgeführt werden
  4. Action 4: ai.process - Dokumente generieren mit Adressen

    ai.process(
        aiPrompt="""Generate separate documents for each company.
        Include the company name, address (from web research), and all content.
        Create one document per company.""",
        documentList=[analyzed_documents_from_action2, addresses_from_action3],
        resultType="docx"  # oder pdf
    )
    
    • Multi-Dokument-Generierung: JSON-Struktur unterstützt documents Array
    • Generiert mehrere Dokumente im documents Array
    • Integriert Adressen aus Web-Research
  5. Action 5: sharepoint.uploadDocument - Dokumente hochladen

    sharepoint.uploadDocument(
        connectionReference="Microsoft connection",
        documentList=[generated_documents_from_action4],
        pathQuery="/sites/SiteName/FolderPath"  # Optional
    )
    

Funktioniert dies im aktuellen Workflow?

Was funktioniert:

  • Actions können sequenziell ausgeführt werden (Workflow-System)
  • ai.process kann Content extrahieren
  • ai.process kann Content analysieren
  • ai.webResearch existiert bereits
  • sharepoint.uploadDocument existiert bereits
  • Multi-Dokument-Generierung ist unterstützt (documents Array im JSON)
  • Dynamic Mode: Task Planning funktioniert (Actions werden iterativ geplant)
  • Dynamic Mode: Ergebnisse zwischen Actions werden über AVAILABLE_DOCUMENTS_INDEX übergeben

Was fehlt:

  • renderReport rendert nur das erste Dokument (muss erweitert werden)
  • ⚠️ Task Planning könnte verbessert werden (funktioniert bereits, aber könnte optimiert werden)

JSON-Struktur für Multi-Dokument-Generierung

Code-Stelle:

jsonTemplateDocument: str = """{
  "metadata": {...},
  "documents": [  // ← Array unterstützt mehrere Dokumente!
    {
      "id": "doc_1",
      "title": "...",
      "filename": "...",
      "sections": [...]
    }
    // ← Weitere Dokumente können hier hinzugefügt werden
  ]
}"""

Multi-Dokument-Generierung ist bereits unterstützt!

  • JSON-Template enthält documents Array
  • AI kann mehrere Dokumente generieren
  • Renderer kann mehrere Dokumente rendern

Was angepasst werden müsste

1. Task Planning im Dynamic Mode:

  • BEREITS IMPLEMENTIERT: Dynamic Mode generiert Actions iterativ (ein Action pro Step)
  • BEREITS IMPLEMENTIERT: _planSelect wählt nächste Action basierend auf Context
  • BEREITS IMPLEMENTIERT: context.executedActions speichert Action-History
  • BEREITS IMPLEMENTIERT: context.nextActionGuidance kann nächste Action vorgeben
  • BEREITS IMPLEMENTIERT: Dokumente werden über AVAILABLE_DOCUMENTS_INDEX verfügbar gemacht
  • BEREITS IMPLEMENTIERT: requiredInputDocuments wird zu documentList konvertiert
  • BEREITS IMPLEMENTIERT: Ergebnisse zwischen Actions werden über AVAILABLE_DOCUMENTS_INDEX übergeben

Code-Stellen:

  • Action Planning: modeDynamic.py Zeile 260-360 (_planSelect)
  • Action Execution: modeDynamic.py Zeile 435-640 (_actExecute)
  • Document Index: placeholderFactory.py Zeile 425-431 (extractAvailableDocumentsIndex)
  • Document Availability: mainServiceChat.py Zeile 762-829 (getAvailableDocuments)
  • Action History: modeDynamic.py Zeile 138-149 (context.executedActions)

Wie es funktioniert:

1. Action Planning (_planSelect):

async def _planSelect(self, context: TaskContext) -> Dict[str, Any]:
    # Prüft context.nextActionGuidance (von vorheriger Refinement-Entscheidung)
    # Oder verwendet AI um nächste Action zu wählen
    # AVAILABLE_DOCUMENTS_INDEX enthält alle Dokumente aus vorherigen Actions
    bundle = generateDynamicPlanSelectionPrompt(self.services, context, ...)
    # AI kann requiredInputDocuments mit docItem: oder docList: Referenzen angeben

2. Document Availability (getAvailableDocuments):

def getAvailableDocuments(self, workflow) -> str:
    # Sammelt ALLE Dokumente aus ALLEN Messages des Workflows
    # Inkludiert Dokumente aus vorherigen Actions
    # Formatiert als docItem:<documentId>:<filename> oder docList:<label>

3. Action Execution (_actExecute):

async def _actExecute(self, context, selection, ...):
    # requiredInputDocuments wird zu documentList konvertiert
    # documentList wird an Action übergeben
    # Action erhält Dokumente von vorherigen Actions

4. Action History (context.executedActions):

# Speichert alle ausgeführten Actions
context.executedActions.append({
    'action': actionName,
    'parameters': relevantParams,
    'step': step
})

Flow-Beispiel:

  1. Step 1: AI wählt ai.process - PDF extrahieren (_planSelect)
  2. Step 1: ai.process wird ausgeführt (_actExecute)
  3. Step 1: Ergebnisse werden als ChatMessage gespeichert
  4. Step 1: Dokumente werden über getAvailableDocuments verfügbar gemacht
  5. Step 2: _planSelect erhält AVAILABLE_DOCUMENTS_INDEX mit Dokumenten von Step 1
  6. Step 2: AI wählt ai.process mit requiredInputDocuments: ["docItem:doc_1:extracted.pdf"]
  7. Step 2: requiredInputDocuments wird zu documentList konvertiert
  8. Step 2: ai.process erhält Dokumente von Step 1

Dynamic Mode unterstützt Multi-Action-Workflows korrekt implementiert!

2. SharePoint-Action:

  • BEREITS IMPLEMENTIERT: sharepoint.uploadDocument Action existiert
  • Action ID: sharepoint.uploadDocument
  • Kann direkt im Workflow verwendet werden

3. Web-Research-Action:

  • BEREITS IMPLEMENTIERT: ai.webResearch Action existiert
  • Kann direkt im Workflow verwendet werden

4. Multi-Dokument-Generierung:

  • BEREITS IMPLEMENTIERT: JSON-Struktur unterstützt documents Array
  • AI kann mehrere Dokumente im documents Array generieren
  • PROBLEM: renderReport rendert aktuell nur das erste Dokument
  • PROBLEM: Renderer verwenden _extractSections welches nur das erste Dokument extrahiert

5. renderReport erweitern für Multi-Dokument-Rendering:

# Aktuell (Zeile 351-352):
single_doc = documents[0]  # ← Nur erstes Dokument
contentToRender = extractedContent  # ← Wird übergeben, aber Renderer nutzt nur erstes

# Erweitert:
# Option 1: Alle Dokumente in einem Output rendern (z.B. PDF mit mehreren Seiten)
for doc in documents:
    # Render jedes Dokument separat oder zusammen
    
# Option 2: Separate Outputs pro Dokument
rendered_documents = []
for doc in documents:
    doc_content = {"metadata": extractedContent["metadata"], "documents": [doc]}
    rendered, mime = await renderer.render(doc_content, doc.get("title", title), userPrompt, aiService)
    rendered_documents.append(rendered)

6. Renderer _extractSections erweitern:

# Aktuell (rendererBaseTemplate.py Zeile 79):
firstDoc = documents[0]  # ← Nur erstes Dokument
return firstDoc.get("sections", [])

# Erweitert:
# Alle Sections aus allen Dokumenten sammeln
all_sections = []
for doc in documents:
    if "sections" in doc:
        all_sections.extend(doc.get("sections", []))
return all_sections

7. Workflow-Orchestrierung:

  • Multi-Step Workflow mit Actions:
    1. ai.process - PDF extrahieren
    2. ai.process - Content analysieren und Dokumente trennen (Multi-Dokument JSON)
    3. ai.webResearch - Adressen recherchieren (Loop für jede Firma)
    4. ai.process - Dokumente generieren mit Adressen (Multi-Dokument JSON)
    5. renderReport - Alle Dokumente rendern (muss erweitert werden - aktuell nur erstes Dokument)
    6. sharepoint.uploadDocument - Dokumente hochladen (bereits implementiert)

Fazit

Der aktuelle Prozess ist teilweise chaotisch, weil:

  1. Extraktion immer erfolgt, ohne Analyse der Notwendigkeit
  2. Bilder immer analysiert werden, auch wenn Rendering gewünscht ist
  3. Keine klare Trennung zwischen Extraktion und Asset-Bereitstellung
  4. Intent-Analyse fehlt komplett
  5. Multi-Dokument-Rendering fehlt:
    • renderReport rendert nur das erste Dokument
    • Renderer verwenden nur das erste Dokument aus documents Array

Was bereits funktioniert:

  • Multi-Dokument-Generierung (JSON unterstützt documents Array)
  • SharePoint-Integration (sharepoint.uploadDocument Action existiert)
  • Web-Research-Integration (ai.webResearch Action existiert)
  • Workflow-System für Multi-Action-Workflows

Was fehlt:

  1. Pre-Extraction Analysis zur Bestimmung des Dokument-Intents (DocumentIntent)
  2. Conditional Extraction basierend auf Intent
  3. Selektive Bild-Behandlung (Text-Extraktion vs. Asset-Bereitstellung)
  4. Multi-Dokument-Rendering:
    • renderReport muss erweitert werden für alle Dokumente
    • Renderer _extractSections muss erweitert werden

Was bereits funktioniert (Dynamic Mode):

  • Task Planning für Multi-Action-Workflows (Dynamic Mode implementiert)
  • Ergebnisse zwischen Actions werden übergeben (über AVAILABLE_DOCUMENTS_INDEX)

Die Lösung erfordert:

  1. Pre-Extraction Analysis zur Bestimmung des Dokument-Intents (DocumentIntent)
  2. Conditional Extraction basierend auf Intent
  3. Selektive Bild-Behandlung (Text-Extraktion vs. Asset-Bereitstellung)
  4. Multi-Dokument-Rendering implementieren:
    • renderReport erweitern für alle Dokumente
    • Renderer erweitern für Multi-Dokument-Support
  5. Erweiterte Features:
    • Asset-Pipeline für Bilder (Platzhalter-System)
    • Seiten-basierte Gruppierung (optional, kann durch AI erfolgen)
    • Task Planning optimieren (funktioniert bereits, könnte verbessert werden)

Dies würde einen klaren, nachvollziehbaren Prozess schaffen, der flexibel auf verschiedene User-Intentionen reagiert und komplexe Use Cases unterstützt.

Context-Extraktion in separate Dokumente

Wo wird Context aus dem Prompt verwendet, um separate Dokumente zu extrahieren?

Kurze Antwort: In workflowManager._sendFirstMessage() - Der User-Prompt wird analysiert, um Context-Inhalte zu identifizieren und als separate Dokumente zu extrahieren.

Detaillierte Analyse

1. Context-Extraktion aus User-Prompt (WORKFLOW START)

Stelle: workflowManager._sendFirstMessage()

Was passiert:

  • BEVOR der Workflow startet, wird der User-Prompt analysiert
  • AI analysiert den Prompt und unterscheidet zwischen:
    • Prompt (Anweisungen, was gemacht werden soll)
    • Context (Inhalte, die als separate Dokumente behandelt werden sollen)

Code-Stelle:

# Analyze the user's input to detect language, normalize request, extract intent, and offload bulky context into documents

Analyzer Prompt:

analyzerPrompt = (
    "You are an input analyzer. From the user's message, perform ALL of the following in one pass:\n"
    "1) detectedLanguage: detect ISO 639-1 language code (e.g., de, en).\n"
    "2) normalizedRequest: full, explicit restatement of the user's request in the detected language; do NOT summarize; preserve ALL constraints and details.\n"
    "3) intent: concise single-paragraph core request in the detected language for high-level routing.\n"
    "4) contextItems: supportive data blocks to attach as separate documents if significantly larger than the intent (large literal content, long lists/tables, code/JSON blocks, transcripts, CSV fragments, detailed specs). Keep URLs in the intent unless they embed large pasted content.\n"
    ...
    "Rules:\n"
    "- If total content (intent + data) is < 10% of model max tokens, do not extract; return empty contextItems and keep intent compact and self-contained.\n"
    "- If content exceeds that threshold, move bulky parts into contextItems; keep intent short and clear.\n"
    "- Preserve critical references (URLs, filenames) in intent.\n"
    ...
    "  \"contextItems\": [\n"
    "    {\n"
    "      \"title\": \"User context 1\",\n"
    "      \"mimeType\": \"text/plain\",\n"
    "      \"content\": \"Full extracted content block here\"\n"
    "    }\n"
    "  ],\n"
    ...
)

Was wird extrahiert:

  • contextItems: Große Datenblöcke, die als separate Dokumente behandelt werden sollen:
    • Große Literal-Inhalte
    • Lange Listen/Tabellen
    • Code/JSON-Blöcke
    • Transkripte
    • CSV-Fragmente
    • Detaillierte Specs

Regeln:

  • Wenn Gesamtinhalt < 10% der Model-Max-Tokens: KEINE Extraktion, alles bleibt im Intent
  • Wenn Gesamtinhalt > 10%: Extraktion von großen Teilen in contextItems
  • Kritische Referenzen (URLs, Dateinamen) bleiben im Intent

Dokument-Erstellung:

# Create documents for context items
if contextItems and isinstance(contextItems, list):
    for idx, item in enumerate(contextItems):
        try:
            title = item.get('title') if isinstance(item, dict) else None
            mime = item.get('mimeType') if isinstance(item, dict) else None
            content = item.get('content') if isinstance(item, dict) else None
            if not content:
                continue
            fileName = (title or f"user_context_{idx+1}.txt").strip()
            mimeType = (mime or "text/plain").strip()

            # Neutralize content before storing if neutralization is enabled
            contentBytes = content.encode('utf-8')
            contentBytes = await self._neutralizeContentIfEnabled(contentBytes, mimeType)
            
            # Create file in component storage
            fileItem = self.services.interfaceDbComponent.createFile(
                name=fileName,
                mimeType=mimeType,
                content=contentBytes
            )
            # Persist file data
            self.services.interfaceDbComponent.createFileData(fileItem.id, contentBytes)
            
            # Get file info
            fileInfo = self.services.chat.getFileInfo(fileItem.id)
            
            # Create ChatDocument
            chatDoc = {
                "fileId": fileItem.id,
                "fileName": fileInfo.get("fileName", fileName) if fileInfo else fileName,
                "fileSize": fileInfo.get("size", len(contentBytes)) if fileInfo else len(contentBytes),
                "mimeType": fileInfo.get("mimeType", mimeType) if fileInfo else mimeType,
                "roundNumber": workflow.currentRound,
                "taskNumber": 0,
                "actionNumber": 0
            }
            createdDocs.append(chatDoc)

Ergebnis:

  • Context-Inhalte werden als separate ChatDocument[] erstellt
  • Diese werden mit den User-uploaded Dokumenten kombiniert
  • Der normalisierte Prompt (normalizedRequest) enthält nur noch die Anweisungen, nicht die Context-Inhalte

Speicherung:

  • self.services.currentUserContextItems = contextItems - Context-Items werden gespeichert
  • self.services.currentUserPromptNormalized = normalizedRequest - Normalisierter Prompt (ohne Context)

2. Prompt-Analyse für Multi-Dokument-Intent (AI-Generierung)

Stelle: buildExtractionPrompt() und buildGenerationPrompt()

Was passiert:

  • Der Prompt wird an die AI übergeben mit der Anweisung:
    • Extraction: "For single documents, create one document entry. For multi-document requests, create multiple document entries."
    • Generation: JSON Template zeigt documents Array-Struktur

Code-Stelle:

TASK: Extract the actual content from the document and organize it into documents. For single documents, create one document entry. For multi-document requests, create multiple document entries.

Problem:

  • KEINE explizite Code-Analyse des Prompts, um zu bestimmen, ob mehrere Dokumente benötigt werden
  • Die Entscheidung wird komplett der AI überlassen
  • Keine Pre-Analyse des User-Intents

2. JSON-Parsing: Extraktion von Sections

Stelle: extractSectionsFromDocument()

Was passiert:

  • Extrahiert Sections aus dem documents Array
  • PROBLEM: Kombiniert Sections aus allen Dokumenten in eine einzige Liste

Code-Stelle:

def extractSectionsFromDocument(documentData: Dict[str, Any]) -> List[Dict[str, Any]]:
    """
    Extract all sections from document data structure.
    Handles both flat and nested document structures.
    """
    if not isinstance(documentData, dict):
        return []
    
    # Try to extract sections from documents array
    if "documents" in documentData:
        all_sections = []
        for doc in documentData.get("documents", []):
            if isinstance(doc, dict) and "sections" in doc:
                sections = doc.get("sections", [])
                if isinstance(sections, list):
                    all_sections.extend(sections)  # ← PROBLEM: Kombiniert alle Sections
        return all_sections
    
    # Try to extract sections directly from root
    if "sections" in documentData:
        sections = documentData.get("sections", [])
        if isinstance(sections, list):
            return sections
    
    return []

Problem:

  • Sections aus mehreren Dokumenten werden kombiniert
  • Dokument-Grenzen gehen verloren
  • Keine Möglichkeit, später zu unterscheiden, welche Sections zu welchem Dokument gehören

3. Final Result Building: Nur ein Dokument

Stelle: _buildFinalResultFromSections()

Was passiert:

  • Baut finales JSON aus Sections zusammen
  • PROBLEM: Erstellt nur ein einzelnes Dokument, auch wenn die AI mehrere generiert hat

Code-Stelle:

def _buildFinalResultFromSections(
    self,
    allSections: List[Dict[str, Any]],
    documentMetadata: Optional[Dict[str, Any]] = None
) -> str:
    """
    Build final JSON result from accumulated sections.
    Uses AI-provided metadata (title, filename) if available.
    """
    if not allSections:
        return ""
    
    # Extract metadata from AI response if available
    title = "Generated Document"
    filename = "document.json"
    if documentMetadata:
        if documentMetadata.get("title"):
            title = documentMetadata["title"]
        if documentMetadata.get("filename"):
            filename = documentMetadata["filename"]
    
    # Build documents structure
    # Assuming single document for now  ← PROBLEM: Immer nur ein Dokument!
    documents = [{
        "id": "doc_1",
        "title": title,
        "filename": filename,
        "sections": allSections  # ← Alle Sections in einem Dokument
    }]
    
    result = {
        "metadata": {
            "split_strategy": "single_document",  # ← Immer "single_document"
            "source_documents": [],
            "extraction_method": "ai_generation"
        },
        "documents": documents
    }
    
    return json.dumps(result, indent=2)

Problem:

  • Kommentar: "Assuming single document for now"
  • Alle Sections werden in ein Dokument gepackt
  • split_strategy ist immer "single_document"
  • Dokument-Grenzen aus AI-Response gehen verloren

4. Rendering: Nur erstes Dokument

Stelle: renderReport()

Was passiert:

  • Validiert, dass documents Array vorhanden ist
  • PROBLEM: Verwendet nur das erste Dokument für Rendering

Code-Stelle:

documents = extractedContent["documents"]
if len(documents) == 0:
    raise ValueError("No documents found in 'documents' array")

# Use first document for rendering  ← PROBLEM: Nur erstes Dokument!
single_doc = documents[0]
if "sections" not in single_doc:
    raise ValueError("Document must contain 'sections' field")

Problem:

  • Nur documents[0] wird verwendet
  • Alle weiteren Dokumente werden ignoriert
  • Keine Multi-Dokument-Rendering-Logik

Zusammenfassung: Wo passiert was?

Phase Funktion Was passiert Problem
Prompt-Analyse buildExtractionPrompt() AI wird angewiesen, mehrere Dokumente zu erstellen Keine explizite Code-Analyse des Prompts
AI-Generierung AI Response AI kann mehrere Dokumente im JSON zurückgeben -
Section-Extraktion extractSectionsFromDocument() Sections aus allen Dokumenten werden kombiniert Dokument-Grenzen gehen verloren
Final Assembly _buildFinalResultFromSections() Erstellt nur ein Dokument mit allen Sections Multi-Dokument-Struktur geht verloren
Rendering renderReport() Rendert nur das erste Dokument Weitere Dokumente werden ignoriert

Was fehlt?

  1. Pre-Analyse des Prompts:

    • Explizite Analyse, ob mehrere Dokumente benötigt werden
    • Setzen von split_strategy basierend auf Intent
  2. Section-Tracking:

    • Sections müssen mit Dokument-ID markiert werden
    • Dokument-Grenzen müssen erhalten bleiben
  3. Multi-Dokument-Assembly:

    • _buildFinalResultFromSections() muss mehrere Dokumente unterstützen
    • Sections müssen nach Dokument gruppiert werden
  4. Multi-Dokument-Rendering:

    • renderReport() muss alle Dokumente rendern
    • Separate Dateien für jedes Dokument erstellen

Empfohlene Lösung

1. Section-Tracking erweitern:

def extractSectionsFromDocument(documentData: Dict[str, Any]) -> List[Dict[str, Any]]:
    all_sections = []
    if "documents" in documentData:
        for doc_idx, doc in enumerate(documentData.get("documents", [])):
            doc_id = doc.get("id", f"doc_{doc_idx}")
            if isinstance(doc, dict) and "sections" in doc:
                sections = doc.get("sections", [])
                if isinstance(sections, list):
                    # Markiere Sections mit Dokument-ID
                    for section in sections:
                        section["_documentId"] = doc_id
                        section["_documentIndex"] = doc_idx
                    all_sections.extend(sections)
    return all_sections

2. Multi-Dokument-Assembly:

def _buildFinalResultFromSections(
    self,
    allSections: List[Dict[str, Any]],
    documentMetadata: Optional[Dict[str, Any]] = None
) -> str:
    # Gruppiere Sections nach Dokument-ID
    sections_by_doc = {}
    for section in allSections:
        doc_id = section.get("_documentId", "doc_1")
        if doc_id not in sections_by_doc:
            sections_by_doc[doc_id] = []
        # Entferne Tracking-Felder
        clean_section = {k: v for k, v in section.items() if not k.startswith("_")}
        sections_by_doc[doc_id].append(clean_section)
    
    # Erstelle mehrere Dokumente
    documents = []
    for doc_id, sections in sections_by_doc.items():
        documents.append({
            "id": doc_id,
            "title": f"Document {doc_id}",
            "filename": f"document_{doc_id}.json",
            "sections": sections
        })
    
    result = {
        "metadata": {
            "split_strategy": "multi_document" if len(documents) > 1 else "single_document",
            "source_documents": [],
            "extraction_method": "ai_generation"
        },
        "documents": documents
    }
    
    return json.dumps(result, indent=2)

3. Multi-Dokument-Rendering:

async def renderReport(self, extractedContent: Dict[str, Any], ...):
    documents = extractedContent["documents"]
    
    if len(documents) == 1:
        # Einzelnes Dokument - wie bisher
        return await self._renderSingleDocument(documents[0], ...)
    else:
        # Mehrere Dokumente - rendere alle
        rendered_docs = []
        for doc in documents:
            rendered = await self._renderSingleDocument(doc, ...)
            rendered_docs.append(rendered)
        return rendered_docs

Zusammenfassung: Prompt-Analyse

Prompt 1: Buch-Generierung mit Bildern

Status: Funktioniert NICHT im aktuellen Code

Hauptprobleme:

  1. Bilder werden analysiert statt gerendert
  2. Original-Bilder gehen verloren
  3. Generierte Bilder können nicht integriert werden

Erforderliche Änderungen:

  • Asset-Pipeline für Bilder
  • Conditional Extraction (keine Vision-Analyse für Render-Bilder)
  • Bild-Generierung im Workflow
  • Renderer-Integration mit Assets

Prompt 2: PDF-Splitting mit Web-Research

Status: ⚠️ TEILWEISE FUNKTIONIERT im aktuellen Code

Was funktioniert:

  • Multi-Dokument-Generierung (JSON unterstützt documents Array)
  • SharePoint-Action (sharepoint.uploadDocument existiert)
  • Web-Research-Action (ai.webResearch existiert)
  • Workflow kann Actions sequenziell ausführen

Hauptprobleme:

  1. ⚠️ Keine automatische Trennseiten-Erkennung (kann durch AI-Analyse erfolgen)
  2. renderReport rendert nur das erste Dokument (muss erweitert werden)
  3. Task Planning funktioniert bereits (Dynamic Mode implementiert)
  4. Ergebnisse zwischen Actions werden bereits übergeben (über AVAILABLE_DOCUMENTS_INDEX)

Erforderliche Änderungen:

  • KRITISCH: renderReport erweitern für Multi-Dokument-Rendering
  • KRITISCH: Renderer _extractSections erweitern für alle Dokumente
  • ⚠️ Task Planning könnte optimiert werden (funktioniert bereits, aber könnte verbessert werden)

Gemeinsame Erkenntnisse

Beide Prompts zeigen:

  1. Fehlende Intent-Analyse: System weiß nicht, was mit Dokumenten gemacht werden soll
  2. Rigide Extraktion: Immer gleicher Prozess, keine Anpassung
  3. Fehlende Features: Spezifische Use Cases werden nicht unterstützt
  4. Keine Asset-Verwaltung: Bilder werden nicht als Assets behandelt

Lösungsansatz:

  • Pre-Extraction Analysis zur Intent-Bestimmung (DocumentIntent)
  • Conditional Processing basierend auf Intent
  • Asset-Pipeline für Bilder (Platzhalter-System mit Code-Integration)
  • Multi-Dokument-Rendering: renderReport und Renderer erweitern
  • Erweiterte Features:
    • SharePoint und Web-Research bereits vorhanden
    • Task Planning funktioniert bereits (Dynamic Mode)
    • ⚠️ Task Planning könnte optimiert werden (funktioniert bereits, aber könnte verbessert werden)

Kritische Erkenntnisse:

  1. Multi-Dokument-Generierung: Bereits implementiert (JSON documents Array)
  2. Multi-Dokument-Rendering: Fehlt - renderReport rendert nur erstes Dokument
  3. SharePoint/Web-Research: Bereits implementiert
  4. Intent-Analyse: Fehlt komplett - benötigt DocumentIntent System
  5. Bild-Asset-Pipeline: Fehlt - benötigt Platzhalter-System mit Code-Integration