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:
- Unklarheit darüber, was mit Dokumenten passiert, die der User mitsendet
- Dokumente werden immer extrahiert, auch wenn Extraktion nicht nötig ist
- Fehlende Analyse, welche Dokumente extrahiert werden müssen und was extrahiert werden soll
- Bei Bildern ist unklar, ob das Bild gerendert werden soll oder der Text aus dem Bild extrahiert werden soll
- 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:
detectedLanguage: Sprache erkennennormalizedRequest: Normalisierte Anweisung (ohne Context)intent: Kurze Kern-AnfragecontextItems: 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
contextItemwird als separatesChatDocumenterstellt - Wird in Component Storage gespeichert
- Wird mit User-uploaded Dokumenten kombiniert
Ergebnis:
self.services.currentUserContextItems = contextItems- Context-Items gespeichertself.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: booldetectedLanguage: 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.processAction 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.processwird 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) zuChatDocument[](vollständige Objekte mit fileId, fileName, mimeType) - Input:
DocumentReferenceListmit 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 PipelineExtractorRegistry.resolve(): Findet passenden Extractorextractor.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 FallbackchunkContentPartForAi(): Chunked große Parts für Model-LimitsaiObjects._callWithModel(): Führt AI-Call ausmergeChunkResults(): Merged Chunk-ErgebnissemergePartResults(): 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 JSONJsonResponseHandler.mergeSectionsIntelligently(): Merged Sections_defineKpisFromPrompt(): Definiert KPIs für TrackingJsonResponseHandler.extractKpiValuesFromJson(): Extrahiert KPI-WerteJsonResponseHandler.validateKpiProgression(): Validiert KPI-FortschrittbuildContinuationContext(): 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:
-
Document Resolution Layer
getChatDocumentsFromDocumentList(): Konvertiert Referenzen zu Objekten- Zuständigkeit: Metadaten-Auflösung
-
Extraction Layer
extractContent(): Orchestriert ExtractionrunExtraction(): Pipeline-OrchestrierungExtractorRegistry: Extractor-Verwaltungextractor.extract(): Format-spezifische Extraktion- Zuständigkeit: Dokument → ContentParts
-
Prompt Building Layer
buildExtractionPrompt(): Extraction PromptbuildGenerationPrompt(): Generation Prompt_parseExtractionIntent(): Intent-Analyse- Zuständigkeit: Prompt-Erstellung
-
AI Processing Layer
processContentPartsWithAi(): ContentParts-VerarbeitungprocessContentPartWithFallback(): Einzelne Part-VerarbeitungchunkContentPartForAi(): Chunking für große PartsaiObjects._callWithModel(): AI-Call-Ausführung- Zuständigkeit: ContentParts → AI-Response
-
Generation Layer
_callAiWithLooping(): Generation mit Looping_extractSectionsFromResponse(): JSON-ParsingJsonResponseHandler: Section-Merging, KPI-Tracking_buildFinalResultFromSections(): Final Assembly- Zuständigkeit: AI-Response → JSON-Struktur
-
Rendering Layer
renderReport(): Rendering-OrchestrierunggetRendererForFormat(): Renderer-Auswahlrenderer.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
documentListvorhanden 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]mitContentPart[]- 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:
AiCallOptionswird erstelltcallAiContentwird aufgerufen mitcontentParts
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
contentPartsvorhanden 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",
priceCHF=sum(r.priceCHF 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
contentPartswerden nicht direkt verwendet - Nur extrahierter Text von Bildern wird verwendet
Phase 5: Action Result Processing
5.1 Result Extraction
Was passiert:
AiResponsewird zuActionResultkonvertiert- 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
ChatMessagepersistiert - 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:
- PDF wird extrahiert → Bilder werden als
ContentPartmittypeGroup="image"erstellt - Bilder werden mit Vision-Modellen analysiert → Text wird extrahiert
- 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
ExtractionOptionsenthä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"]undmetadata["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.datawird 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_contentist 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
documentIdoderparentId - ✅ Sortierung nach
partIndex,pageIndex, odersheetIndex - ✅ 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
AiCallResponsewird nachidsortiert, 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
...
)
idist neue UUID, keine Original-ReihenfolgeorderBy="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:
mergeChunkResultswurde durchmergePartResults(chunkResults)ersetzt - ✅ BEGRÜNDUNG:
mergePartResultsakzeptiertList[AiCallResponse](Zeile 780) undchunkResultsist 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:
mergeChunkResultswurde durchmergePartResultsersetztmergePartResultsakzeptiertList[AiCallResponse](Zeile 780)chunkResultsistList[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
documentListvorhanden 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?
-
intentsals 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)
- ✅ Mehrere Intents gleichzeitig möglich (z.B.
-
extractionPromptstattextractionType(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
-
Kein
renderingInstructionsnö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
base64Datain JSON-Struktur (automatisch)
-
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
extractionPrompterstellt - Wenn
extractionPromptvorhanden, wird dieser als Prompt für Extraktion verwendet - Wenn
extractionPromptNone, 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
base64Datainelements) - 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
base64Datain 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:
extractionPromptwird für Vision-Analyse verwendet- Text wird extrahiert und in
extracted_contentbereitgestellt - 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:
contentPartswerden IMMER extrahiert (Bilder → Text via Vision-Modelle)extractionResponse.contententhä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:
buildGenerationPrompterhält nurextracted_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:
-
DocumentIntent-Übergabe:
# In callAiContent: documentIntents: Optional[List[DocumentIntent]] = None # In buildGenerationPrompt: documentIntents: Optional[List[DocumentIntent]] = None -
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:
-
Bestehendes Bild (
image_source="existing"):- Platzhalter:
image_reference_id - Integration: Code (automatisch, kein AI-Call)
- Beispiel: Bild aus
contentPartsmit"render" in intents
- Platzhalter:
-
Bild generieren (
image_sourcenicht gesetzt oder"generate"):- Platzhalter:
image_prompt(erforderlich) odergeneration_hint(Fallback) - Integration: AI (IMAGE_GENERATE Operation)
- Beispiel: "Erzeuge ein Bild zum Thema Kochen ohne Fleisch"
- Platzhalter:
4. Text-Content durch AI:
- Für
content_type: "paragraph"→ AI generiert Text - Für
content_type: "heading"→ AI generiert Überschrift - Verwendet
extracted_contentunduserPrompt
✅ 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:
- Struktur-Generierung: AI erzeugt Struktur mit Platzhaltern (
elements: []) - Platzhalter für Bilder:
- Bestehendes Bild:
image_source="existing"+image_reference_id→ Code integriert automatisch - Bild generieren:
image_sourcenicht 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)
- Bestehendes Bild:
- 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_contentunduserPrompt
- Rendering: Renderer verwendet
base64Dataaus 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:
- DocumentIntent-Übergabe: Intent-Informationen werden nicht von
analyzeDocumentIntentbis zubuildGenerationPromptübergeben - Bild-Assets: Bilder werden IMMER extrahiert (Text), nicht als Assets bereitgestellt
- Conditional Extraction: Keine Unterscheidung zwischen "extract" und "render" - alles wird extrahiert
- 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
-
Implementierung von
analyzeDocumentIntent- AI-basierte Analyse des User Prompts
- Klassifizierung von Dokumenten nach Verwendungszweck
-
Erweiterung von
ExtractionOptions- Neues Feld:
documentIntent: Optional[DocumentIntent] - Conditional Extraction basierend auf
intents - Verwendung von
extractionPromptwenn vorhanden
- Neues Feld:
-
Erweiterung von
ContentPart- Neues Feld:
documentIntent: Optional[DocumentIntent] - Metadaten enthalten Intent-Informationen
- Unterscheidung zwischen Text-Extraktion und Rendering-Anforderungen
- Neues Feld:
-
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:
-
PDF wird extrahiert (
extractContent)- PDF-Extractor extrahiert alle Bilder als
ContentPartmittypeGroup="image" - Bilder werden als base64-encoded Daten bereitgestellt
- PDF-Extractor extrahiert alle Bilder als
-
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)
-
Extraction Prompt wird erstellt (
buildExtractionPrompt)- Generischer Prompt: "Extract content from documents"
- Keine spezifische Anweisung für Bild-Rendering
-
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
-
Rendering (
renderReport)- Renderer kann keine Bilder rendern, da nur Text vorhanden ist
- Generierte Bilder können nicht integriert werden
Probleme
-
Bilder werden analysiert statt gerendert
- Aktuell: Bilder → Vision-Analyse → Text-Beschreibung
- Benötigt: Bilder → Asset-Bereitstellung → Rendering
-
Keine Möglichkeit, Bilder als Assets zu behalten
- ContentParts werden nur für Text-Extraktion verwendet
- Keine separate Asset-Pipeline
-
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:
StructureGeneratorerzeugt Struktur mit Platzhaltern für Bilder- Für bestehende Bilder:
image_source="render"+image_reference_id - Für fehlende Bilder:
image_sourcenicht 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_sourcenicht gesetzt, mitimage_prompt) - AI füllt Text-Sections mit
extracted_content
5. Rendering:
- Renderer verwendet
base64Dataaus JSON-Struktur (bereits integriert) - Keine separate Asset-Pipeline nötig
Erforderliche Code-Änderungen
-
DocumentIntentimplementieren:class DocumentIntent: documentId: str intents: List[str] # ["extract", "render", "reference"] extractionPrompt: Optional[str] reasoning: str -
callAiContenterweitern: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 }) -
StructureGenerator.generateStructureerweitern:# 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": [] } -
ContentGenerator._generateImageSectionerweitern: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! -
cachedContenterweitern:# In callAiContent: cachedContent = { "extracted_content": extracted_content, # Text "imageDocuments": imageDocuments # ← Bilder für Rendering }
✅ KEINE Änderungen nötig:
- ❌
ExtractionOptionserweitern (nicht nötig - DocumentIntent reicht) - ❌
ContentParterweitern (nicht nötig - Platzhalter-System verwendet) - ❌
renderReporterweitern (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:
-
Action 1:
ai.process- Content extrahierenai.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
-
Action 2:
ai.process- Content analysieren und Dokumente trennenai.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
-
Action 3:
ai.webResearch- Adressen recherchierenai.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
-
Action 4:
ai.process- Dokumente generieren mit Adressenai.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
documentsArray - Generiert mehrere Dokumente im
documentsArray - Integriert Adressen aus Web-Research
- ✅ Multi-Dokument-Generierung: JSON-Struktur unterstützt
-
Action 5:
sharepoint.uploadDocument- Dokumente hochladensharepoint.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.processkann Content extrahieren - ✅
ai.processkann Content analysieren - ✅
ai.webResearchexistiert bereits - ✅
sharepoint.uploadDocumentexistiert bereits - ✅ Multi-Dokument-Generierung ist unterstützt (
documentsArray im JSON) - ✅ Dynamic Mode: Task Planning funktioniert (Actions werden iterativ geplant)
- ✅ Dynamic Mode: Ergebnisse zwischen Actions werden über
AVAILABLE_DOCUMENTS_INDEXübergeben
❌ Was fehlt:
- ❌
renderReportrendert 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
documentsArray - 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:
_planSelectwählt nächste Action basierend auf Context - ✅ BEREITS IMPLEMENTIERT:
context.executedActionsspeichert Action-History - ✅ BEREITS IMPLEMENTIERT:
context.nextActionGuidancekann nächste Action vorgeben - ✅ BEREITS IMPLEMENTIERT: Dokumente werden über
AVAILABLE_DOCUMENTS_INDEXverfügbar gemacht - ✅ BEREITS IMPLEMENTIERT:
requiredInputDocumentswird zudocumentListkonvertiert - ✅ BEREITS IMPLEMENTIERT: Ergebnisse zwischen Actions werden über
AVAILABLE_DOCUMENTS_INDEXübergeben
Code-Stellen:
- Action Planning:
modeDynamic.pyZeile 260-360 (_planSelect) - Action Execution:
modeDynamic.pyZeile 435-640 (_actExecute) - Document Index:
placeholderFactory.pyZeile 425-431 (extractAvailableDocumentsIndex) - Document Availability:
mainServiceChat.pyZeile 762-829 (getAvailableDocuments) - Action History:
modeDynamic.pyZeile 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:
- Step 1: AI wählt
ai.process- PDF extrahieren (_planSelect) - Step 1:
ai.processwird ausgeführt (_actExecute) - Step 1: Ergebnisse werden als ChatMessage gespeichert
- Step 1: Dokumente werden über
getAvailableDocumentsverfügbar gemacht - Step 2:
_planSelecterhältAVAILABLE_DOCUMENTS_INDEXmit Dokumenten von Step 1 - Step 2: AI wählt
ai.processmitrequiredInputDocuments: ["docItem:doc_1:extracted.pdf"] - Step 2:
requiredInputDocumentswird zudocumentListkonvertiert - Step 2:
ai.processerhält Dokumente von Step 1
✅ Dynamic Mode unterstützt Multi-Action-Workflows korrekt implementiert!
2. SharePoint-Action:
- ✅ BEREITS IMPLEMENTIERT:
sharepoint.uploadDocumentAction existiert - ✅ Action ID:
sharepoint.uploadDocument - ✅ Kann direkt im Workflow verwendet werden
3. Web-Research-Action:
- ✅ BEREITS IMPLEMENTIERT:
ai.webResearchAction existiert - ✅ Kann direkt im Workflow verwendet werden
4. Multi-Dokument-Generierung:
- ✅ BEREITS IMPLEMENTIERT: JSON-Struktur unterstützt
documentsArray - ✅ AI kann mehrere Dokumente im
documentsArray generieren - ❌ PROBLEM:
renderReportrendert aktuell nur das erste Dokument - ❌ PROBLEM: Renderer verwenden
_extractSectionswelches 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:
ai.process- PDF extrahierenai.process- Content analysieren und Dokumente trennen (Multi-Dokument JSON)ai.webResearch- Adressen recherchieren (Loop für jede Firma)ai.process- Dokumente generieren mit Adressen (Multi-Dokument JSON)renderReport- Alle Dokumente rendern (muss erweitert werden - aktuell nur erstes Dokument)sharepoint.uploadDocument- Dokumente hochladen (bereits implementiert)
Fazit
Der aktuelle Prozess ist teilweise chaotisch, weil:
- Extraktion immer erfolgt, ohne Analyse der Notwendigkeit
- Bilder immer analysiert werden, auch wenn Rendering gewünscht ist
- Keine klare Trennung zwischen Extraktion und Asset-Bereitstellung
- Intent-Analyse fehlt komplett
- Multi-Dokument-Rendering fehlt:
renderReportrendert nur das erste Dokument- Renderer verwenden nur das erste Dokument aus
documentsArray
Was bereits funktioniert:
- ✅ Multi-Dokument-Generierung (JSON unterstützt
documentsArray) - ✅ SharePoint-Integration (
sharepoint.uploadDocumentAction existiert) - ✅ Web-Research-Integration (
ai.webResearchAction existiert) - ✅ Workflow-System für Multi-Action-Workflows
Was fehlt:
- Pre-Extraction Analysis zur Bestimmung des Dokument-Intents (
DocumentIntent) - Conditional Extraction basierend auf Intent
- Selektive Bild-Behandlung (Text-Extraktion vs. Asset-Bereitstellung)
- Multi-Dokument-Rendering:
- ❌
renderReportmuss erweitert werden für alle Dokumente - ❌ Renderer
_extractSectionsmuss 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:
- Pre-Extraction Analysis zur Bestimmung des Dokument-Intents (
DocumentIntent) - Conditional Extraction basierend auf Intent
- Selektive Bild-Behandlung (Text-Extraktion vs. Asset-Bereitstellung)
- Multi-Dokument-Rendering implementieren:
renderReporterweitern für alle Dokumente- Renderer erweitern für Multi-Dokument-Support
- 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 gespeichertself.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
documentsArray-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
documentsArray - 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_strategyist immer "single_document"- Dokument-Grenzen aus AI-Response gehen verloren
4. Rendering: Nur erstes Dokument
Stelle: renderReport()
Was passiert:
- Validiert, dass
documentsArray 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?
-
Pre-Analyse des Prompts:
- Explizite Analyse, ob mehrere Dokumente benötigt werden
- Setzen von
split_strategybasierend auf Intent
-
Section-Tracking:
- Sections müssen mit Dokument-ID markiert werden
- Dokument-Grenzen müssen erhalten bleiben
-
Multi-Dokument-Assembly:
_buildFinalResultFromSections()muss mehrere Dokumente unterstützen- Sections müssen nach Dokument gruppiert werden
-
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:
- Bilder werden analysiert statt gerendert
- Original-Bilder gehen verloren
- 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
documentsArray) - ✅ SharePoint-Action (
sharepoint.uploadDocumentexistiert) - ✅ Web-Research-Action (
ai.webResearchexistiert) - ✅ Workflow kann Actions sequenziell ausführen
Hauptprobleme:
- ⚠️ Keine automatische Trennseiten-Erkennung (kann durch AI-Analyse erfolgen)
- ❌
renderReportrendert nur das erste Dokument (muss erweitert werden) - ✅ Task Planning funktioniert bereits (Dynamic Mode implementiert)
- ✅ Ergebnisse zwischen Actions werden bereits übergeben (über
AVAILABLE_DOCUMENTS_INDEX)
Erforderliche Änderungen:
- ❌ KRITISCH:
renderReporterweitern für Multi-Dokument-Rendering - ❌ KRITISCH: Renderer
_extractSectionserweitern für alle Dokumente - ⚠️ Task Planning könnte optimiert werden (funktioniert bereits, aber könnte verbessert werden)
Gemeinsame Erkenntnisse
Beide Prompts zeigen:
- Fehlende Intent-Analyse: System weiß nicht, was mit Dokumenten gemacht werden soll
- Rigide Extraktion: Immer gleicher Prozess, keine Anpassung
- Fehlende Features: Spezifische Use Cases werden nicht unterstützt
- 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:
renderReportund 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:
- Multi-Dokument-Generierung: ✅ Bereits implementiert (JSON
documentsArray) - Multi-Dokument-Rendering: ❌ Fehlt -
renderReportrendert nur erstes Dokument - SharePoint/Web-Research: ✅ Bereits implementiert
- Intent-Analyse: ❌ Fehlt komplett - benötigt
DocumentIntentSystem - Bild-Asset-Pipeline: ❌ Fehlt - benötigt Platzhalter-System mit Code-Integration