From 0eaaeb35501ccb4b48b6d8a734c2efd61a45b93e Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Fri, 26 Dec 2025 00:16:14 +0100
Subject: [PATCH] refactored ai service container (3000 lines) with submodules,
and enhanced generation part with dynamic chapters
---
...ation_concept_generation_structure_rev4.md | 881 ++++++++++++++++++
1 file changed, 881 insertions(+)
create mode 100644 appdoc/implementation_concept_generation_structure_rev4.md
diff --git a/appdoc/implementation_concept_generation_structure_rev4.md b/appdoc/implementation_concept_generation_structure_rev4.md
new file mode 100644
index 0000000..c0d3e81
--- /dev/null
+++ b/appdoc/implementation_concept_generation_structure_rev4.md
@@ -0,0 +1,881 @@
+# Implementierungskonzept: Chapter-basierte Generierungs-Struktur
+
+## Übersicht
+
+Wechsel von section-basierter zu **chapter-basierter Struktur** zur Lösung folgender Probleme:
+
+1. Section-Generierungs-Prompts kennen den Standard-JSON-Schema nicht
+2. Gemischte Element-Typen können nicht korrekt verarbeitet werden
+3. Sections sind zu starr - können nicht mehrere Element-Typen enthalten
+
+## Kritische Analyse: Aggregation mehrerer ContentParts
+
+**Problem identifiziert:**
+- Bestimmte `content_type` (z.B. `table`, `bullet_list`) benötigen Aggregation mehrerer ContentParts
+- Beispiel: 20 Spesenbelege → eine Excel-Tabelle
+- Aktuell: Jeder ContentPart wird einzeln verarbeitet → keine Aggregation möglich
+
+**Lösung implementiert:**
+- Generische `_needsAggregation()` Funktion erkennt Aggregations-Bedarf
+- Wenn Aggregation nötig: Alle ContentParts zusammen an `callAi` übergeben
+- Verwendet `callAi` statt `callAiPlanning` für ContentParts-Unterstützung
+- Automatisches Chunking funktioniert auch bei aggregierten Parts
+
+**Unterstützte Aggregations-Typen:**
+- `table`: Mehrere Parts → eine Tabelle (z.B. Excel-Liste)
+- `bullet_list`: Mehrere Parts → eine Liste
+- Weitere Typen können einfach hinzugefügt werden
+
+## Kernprinzipien
+
+1. **Chapter-basierte Struktur**: Struktur-Generierung definiert Chapters, nicht Sections
+2. **Chapters enthalten Sections**: Jedes Chapter kann mehrere Sections unterschiedlicher Typen enthalten
+3. **Standard JSON Schema in Prompts**: Chapter-Generierungs-Prompts enthalten vollständiges Standard-JSON-Schema
+4. **Flexible Content-Verarbeitung**: Chapters können gemischte ContentParts enthalten
+5. **Hierarchische Überschriften**: Chapters sind hierarchische Überschriften (Level 1, 2, 3, etc.)
+
+---
+
+## Architektur
+
+### Chapters als Helper-Struktur
+
+Chapters sind eine intermediate Helper-Struktur für die Generierung. Die finale Output-Struktur bleibt unverändert:
+
+**Finale Output-Struktur:**
+```
+Document
+ └── Sections[]
+ └── content_type
+ └── elements[]
+```
+
+**Chapter-Struktur (Helper):**
+```
+ChapterStructure
+ └── Chapters[]
+ └── level, title
+ └── contentPartIds[]
+ └── contentPartInstructions{}
+ └── generationHint
+ └── Sections[] (generiert)
+ └── content_type
+ └── elements[]
+```
+
+**Wichtig:**
+- Chapter = Container zur Generierung eines Dokument-Teils mit Sections
+- Jedes Chapter hat eine vordefinierte Heading-Section (Chapter-Title + Level)
+- Finale Output-Struktur hat keine Chapters - nur Sections
+- Chapters werden zu Sections geflatten für das finale Output
+
+---
+
+## Workflow-Phasen
+
+**Wichtig - Debug-File-Logging:**
+- Alle AI-Calls und Responses werden in Debug-Files geloggt
+- Prompts: `{operationType}_{identifier}_prompt.txt`
+- Responses: `{operationType}_{identifier}_response.txt`
+- Beispiele:
+ - Phase 5C: `chapter_structure_generation_prompt.txt` / `chapter_structure_generation_response.txt`
+ - Phase 5D.1: `chapter_structure_{chapterId}_prompt.txt` / `chapter_structure_{chapterId}_response.txt`
+ - Phase 5D.2: `section_content_{sectionId}_prompt.txt` / `section_content_{sectionId}_response.txt`
+
+---
+
+### Phase 5B: Content Extraction
+
+**Was passiert:**
+- Extrahiert Content basierend auf Intents
+- Bereitet ContentParts mit Metadaten vor
+- Alle Extraktionen passieren VOR Struktur-Generierung
+
+**Output:**
+- Liste von ContentParts mit vollständigen Metadaten
+
+---
+
+### Phase 5C: Chapter-Struktur-Generierung
+
+**Was passiert:**
+- Generiert Chapter-Struktur (Table of Contents)
+- Definiert für jedes Chapter:
+ - Level, Title
+ - contentPartIds
+ - contentPartInstructions
+ - generationHint
+
+**Input:**
+- `userPrompt`: User-Anfrage
+- `contentParts`: Alle vorbereiteten ContentParts (bereits extrahiert)
+- `outputFormat`: Ziel-Format
+
+**Process:**
+```python
+async def _generateChapterStructure(
+ self,
+ userPrompt: str,
+ contentParts: List[ContentPart],
+ outputFormat: str,
+ parentOperationId: str
+) -> Dict[str, Any]:
+ structurePrompt = self._buildChapterStructurePrompt(
+ userPrompt=userPrompt,
+ contentParts=contentParts,
+ outputFormat=outputFormat
+ )
+
+ # Debug: Log Prompt
+ self.services.utils.writeDebugFile(
+ structurePrompt,
+ "chapter_structure_generation_prompt"
+ )
+
+ aiResponse = await self.services.ai.callAiPlanning(
+ prompt=structurePrompt
+ )
+
+ # Debug: Log Response
+ self.services.utils.writeDebugFile(
+ aiResponse,
+ "chapter_structure_generation_response"
+ )
+
+ structure = json.loads(
+ self.services.utils.jsonExtractString(aiResponse)
+ )
+
+ return structure
+```
+
+**Prompt-Format:**
+```
+USER REQUEST: {userPrompt}
+
+AVAILABLE CONTENT PARTS:
+{contentPartsIndex}
+
+TASK: Generiere Chapter-Struktur für die zu generierenden Dokumente.
+
+Für jedes Chapter:
+- chapter id
+- level (1, 2, 3, etc.)
+- title
+- contentPartIds: [Liste von ContentPart-IDs]
+- contentPartInstructions: {
+ "partId": {
+ "instruction": "Wie Content strukturiert werden soll"
+ }
+}
+- generationHint: Beschreibung des Inhalts
+
+RETURN JSON:
+{
+ "metadata": {...},
+ "documents": [{
+ "chapters": [
+ {
+ "id": "chapter_1",
+ "level": 1,
+ "title": "Introduction",
+ "contentPartIds": ["part_ext_1"],
+ "contentPartInstructions": {...},
+ "generationHint": "...",
+ "sections": []
+ }
+ ]
+ }]
+}
+```
+
+**Output-Struktur:**
+```json
+{
+ "metadata": {"title": "...", "language": "de"},
+ "documents": [{
+ "chapters": [
+ {
+ "id": "chapter_summary",
+ "level": 1,
+ "title": "Summary",
+ "contentPartIds": ["extracted_doc1_part1"],
+ "contentPartInstructions": {
+ "extracted_doc1_part1": {
+ "instruction": "Erstelle Zusammenfassungsparagraph"
+ }
+ },
+ "generationHint": "Create summary",
+ "sections": []
+ }
+ ]
+ }]
+}
+```
+
+---
+
+### Phase 5D: Chapter-Content-Generierung
+
+**Zwei-Phasen-Ansatz:**
+
+#### Phase 5D.1: Sections-Struktur generieren
+
+**Was passiert:**
+- Generiert Sections-Struktur für jedes Chapter (ohne Content)
+- Sections enthalten: content_type, contentPartIds, generationHint, useAiCall
+- AI setzt `useAiCall` Flag direkt im JSON
+
+**useAiCall Flag:**
+- `useAiCall = true` wenn:
+ - `content_type != "paragraph"` (Transformation nötig)
+ - Oder spezifische Anweisungen in contentPartInstructions (nur Teile verwenden)
+- `useAiCall = false` sonst (Content direkt einfügen)
+
+**Process:**
+```python
+async def _generateChapterStructure(
+ self,
+ chapterStructure: Dict[str, Any],
+ contentParts: List[ContentPart],
+ userPrompt: str,
+ parentOperationId: str
+) -> Dict[str, Any]:
+ for doc in chapterStructure.get("documents", []):
+ for chapter in doc.get("chapters", []):
+ chapterId = chapter.get("id", "unknown")
+ chapterPrompt = self._buildChapterStructurePrompt(
+ chapter=chapter,
+ contentPartIds=chapter.get("contentPartIds"),
+ contentPartInstructions=chapter.get("contentPartInstructions"),
+ userPrompt=userPrompt
+ )
+
+ # Debug: Log Prompt
+ self.services.utils.writeDebugFile(
+ chapterPrompt,
+ f"chapter_structure_{chapterId}_prompt"
+ )
+
+ aiResponse = await self.services.ai.callAiPlanning(
+ prompt=chapterPrompt
+ )
+
+ # Debug: Log Response
+ self.services.utils.writeDebugFile(
+ aiResponse,
+ f"chapter_structure_{chapterId}_response"
+ )
+
+ sectionsStructure = json.loads(
+ self.services.utils.jsonExtractString(aiResponse)
+ )
+
+ chapter["sections"] = sectionsStructure.get("sections", [])
+
+ # Setze useAiCall Flag (falls nicht von AI gesetzt)
+ for section in chapter["sections"]:
+ if "useAiCall" not in section:
+ contentType = section.get("content_type", "paragraph")
+ useAiCall = contentType != "paragraph"
+
+ # Prüfe contentPartInstructions
+ if not useAiCall:
+ for partId in section.get("contentPartIds", []):
+ instruction = contentPartInstructions.get(partId, {}).get("instruction", "")
+ if instruction and instruction.lower() not in ["include full text", "include all content"]:
+ useAiCall = True
+ break
+
+ section["useAiCall"] = useAiCall
+
+ return chapterStructure
+```
+
+**Prompt-Format:**
+```
+TASK: Generate Chapter Sections Structure
+
+CHAPTER METADATA:
+- Chapter ID: {chapterId}
+- Chapter Level: {chapterLevel}
+- Chapter Title: {chapterTitle}
+- Generation Hint: {generationHint}
+
+WICHTIG: Chapter hat bereits vordefinierte Heading-Section.
+Generiere NICHT eine Heading-Section für Chapter-Title!
+
+AVAILABLE CONTENT PARTS:
+{contentPartIds} # Nur IDs, KEINE Previews!
+
+Für jeden ContentPart:
+- ContentPart ID: {partId}
+- Format: {contentFormat}
+- Instruction: {contentPartInstructions[partId].instruction}
+
+STANDARD JSON SCHEMA FOR SECTIONS:
+[... Standard JSON Schema ...]
+
+Return JSON:
+{
+ "sections": [
+ {
+ "id": "section_1",
+ "content_type": "paragraph",
+ "contentPartIds": ["part_ext_1"],
+ "generationHint": "...",
+ "useAiCall": false, # AI setzt Flag direkt
+ "elements": []
+ }
+ ]
+}
+```
+
+#### Phase 5D.2: Sections mit ContentParts füllen
+
+**Was passiert:**
+- Füllt Sections separat mit ContentParts
+- Basierend auf `useAiCall` Flag:
+ - `useAiCall = true`: Separater AI-Call mit ContentPart(s) (Chunking bei großen Parts)
+ - `useAiCall = false`: Content direkt einfügen
+- Rendering/Reference content: Immer direkt ohne AI-Call
+
+**Aggregation mehrerer ContentParts:**
+- Bestimmte `content_type` benötigen Aggregation mehrerer Parts:
+ - `table`: Mehrere Parts → eine Tabelle (z.B. 20 Belege → Excel-Liste)
+ - `bullet_list`: Mehrere Parts → eine Liste
+ - `paragraph`: Kann auch aggregiert werden (z.B. Vergleich mehrerer Dokumente)
+- Wenn Aggregation nötig: Alle Parts zusammen an AI übergeben (nicht einzeln)
+- Verwendet `callAi` statt `callAiPlanning` für ContentParts-Unterstützung
+
+**Process:**
+```python
+async def _fillChapterSections(
+ self,
+ chapterStructure: Dict[str, Any],
+ contentParts: List[ContentPart],
+ userPrompt: str,
+ parentOperationId: str
+) -> Dict[str, Any]:
+ for doc in chapterStructure.get("documents", []):
+ for chapter in doc.get("chapters", []):
+ for section in chapter.get("sections", []):
+ elements = []
+ useAiCall = section.get("useAiCall", False)
+ contentType = section.get("content_type", "paragraph")
+ contentPartIds = section.get("contentPartIds", [])
+
+ # Prüfe ob Aggregation nötig ist
+ needsAggregation = self._needsAggregation(
+ contentType=contentType,
+ contentPartCount=len(contentPartIds)
+ )
+
+ if needsAggregation and useAiCall:
+ # Aggregation: Alle Parts zusammen verarbeiten
+ sectionParts = [
+ self._findContentPartById(pid, contentParts)
+ for pid in contentPartIds
+ ]
+ sectionParts = [p for p in sectionParts if p is not None]
+
+ if sectionParts:
+ sectionId = section.get("id", "unknown")
+ sectionPrompt = self._buildSectionContentPrompt(
+ section=section,
+ contentParts=sectionParts, # ALLE PARTS!
+ generationHint=section.get("generationHint"),
+ userPrompt=userPrompt
+ )
+
+ # Debug: Log Prompt
+ self.services.utils.writeDebugFile(
+ sectionPrompt,
+ f"section_content_{sectionId}_prompt"
+ )
+
+ # Verwende callAi für ContentParts-Unterstützung
+ request = AiCallRequest(
+ prompt=sectionPrompt,
+ contentParts=sectionParts, # ALLE PARTS!
+ options=AiCallOptions(
+ operationType=OperationTypeEnum.DATA_ANALYSE,
+ priority=PriorityEnum.BALANCED,
+ processingMode=ProcessingModeEnum.DETAILED
+ )
+ )
+ aiResponse = await self.services.ai.callAi(request)
+
+ # Debug: Log Response
+ self.services.utils.writeDebugFile(
+ aiResponse.content,
+ f"section_content_{sectionId}_response"
+ )
+
+ elements.extend(parseElements(aiResponse.content))
+
+ else:
+ # Einzelverarbeitung: Jeder Part einzeln
+ for partId in contentPartIds:
+ part = self._findContentPartById(partId, contentParts)
+ if not part:
+ continue
+
+ contentFormat = part.metadata.get("contentFormat")
+
+ if contentFormat == "extracted":
+ if useAiCall:
+ # AI-Call mit einzelnen ContentPart
+ sectionId = section.get("id", "unknown")
+ sectionPrompt = self._buildSectionContentPrompt(
+ section=section,
+ contentParts=[part], # EIN PART
+ generationHint=section.get("generationHint"),
+ userPrompt=userPrompt
+ )
+
+ # Debug: Log Prompt
+ self.services.utils.writeDebugFile(
+ sectionPrompt,
+ f"section_content_{sectionId}_prompt"
+ )
+
+ request = AiCallRequest(
+ prompt=sectionPrompt,
+ contentParts=[part],
+ options=AiCallOptions(...)
+ )
+ aiResponse = await self.services.ai.callAi(request)
+
+ # Debug: Log Response
+ self.services.utils.writeDebugFile(
+ aiResponse.content,
+ f"section_content_{sectionId}_response"
+ )
+
+ elements.extend(parseElements(aiResponse.content))
+ else:
+ # Content direkt einfügen
+ elements.append({
+ "type": "paragraph",
+ "content": part.data or ""
+ })
+
+ elif contentFormat == "reference":
+ elements.append({
+ "type": "reference",
+ "documentReference": part.metadata.get("documentReference")
+ })
+
+ elif contentFormat == "object":
+ elements.append({
+ "type": "image",
+ "base64Data": part.data
+ })
+
+ section["elements"] = elements
+
+ return chapterStructure
+
+def _needsAggregation(
+ self,
+ contentType: str,
+ contentPartCount: int
+) -> bool:
+ """
+ Bestimmt ob mehrere ContentParts aggregiert werden müssen.
+
+ Aggregation nötig wenn:
+ - content_type erfordert Aggregation (table, bullet_list)
+ - UND mehrere ContentParts vorhanden sind (> 1)
+
+ Args:
+ contentType: Section content_type
+ contentPartCount: Anzahl der ContentParts in dieser Section
+
+ Returns:
+ True wenn Aggregation nötig, False sonst
+ """
+ aggregationTypes = ["table", "bullet_list"]
+
+ if contentType in aggregationTypes and contentPartCount > 1:
+ return True
+
+ # Optional: Auch für paragraph wenn mehrere Parts vorhanden
+ # (z.B. Vergleich mehrerer Dokumente)
+ if contentType == "paragraph" and contentPartCount > 1:
+ # Prüfe generationHint für Hinweise auf Aggregation
+ # (z.B. "Vergleiche", "Zusammenfassung", "Liste")
+ return False # Standard: Keine Aggregation für paragraph
+
+ return False
+```
+
+**Prompt-Format (für AI-Call):**
+
+**Einzelverarbeitung (ein ContentPart):**
+```
+TASK: Generate Section Content
+
+SECTION METADATA:
+- Section ID: {sectionId}
+- Content Type: {contentType}
+- Generation Hint: {generationHint}
+
+CONTEXT:
+- User Request: {userPrompt}
+- What to generate: {generationHint}
+
+CONTENT PART:
+- ContentPart ID: {partId}
+- Format: extracted
+- ContentPart wird als Parameter übergeben (nicht im Prompt!)
+- Kann sehr groß sein (z.B. 200MB) → Chunking automatisch
+
+STANDARD JSON SCHEMA FOR ELEMENTS:
+[... Standard JSON Schema ...]
+
+Return JSON:
+{
+ "elements": [
+ {"type": "paragraph", "content": "..."},
+ {"type": "table", "headers": [...], "rows": [...]}
+ ]
+}
+```
+
+**Aggregation (mehrere ContentParts):**
+```
+TASK: Generate Section Content
+
+SECTION METADATA:
+- Section ID: {sectionId}
+- Content Type: {contentType} # z.B. "table"
+- Generation Hint: {generationHint} # z.B. "Erstelle Excel-Liste aller Spesenbelege"
+
+CONTEXT:
+- User Request: {userPrompt}
+- What to generate: {generationHint}
+
+CONTENT PARTS (Aggregation):
+- Anzahl: {contentPartCount} ContentParts
+- Alle ContentParts werden als Parameter übergeben (nicht im Prompt!)
+- Jeder Part kann sehr groß sein → Chunking automatisch
+- WICHTIG: Aggregiere ALLE Parts zu einem Element (z.B. eine Tabelle)
+
+ContentPart IDs:
+{contentPartIds} # Liste aller IDs
+
+STANDARD JSON SCHEMA FOR ELEMENTS:
+[... Standard JSON Schema ...]
+
+Return JSON:
+{
+ "elements": [
+ {
+ "type": "table",
+ "headers": ["Spalte1", "Spalte2", ...],
+ "rows": [
+ ["Daten aus Part 1", ...],
+ ["Daten aus Part 2", ...],
+ ...
+ ]
+ }
+ ]
+}
+```
+
+**Hauptfunktion:**
+```python
+async def _generateChapterContent(
+ self,
+ chapterStructure: Dict[str, Any],
+ contentParts: List[ContentPart],
+ userPrompt: str,
+ parentOperationId: str
+) -> Dict[str, Any]:
+ # Phase 5D.1: Sections-Struktur generieren
+ structureWithSections = await self._generateChapterStructure(
+ chapterStructure, contentParts, userPrompt, parentOperationId
+ )
+
+ # Phase 5D.2: Sections mit ContentParts füllen
+ filledStructure = await self._fillChapterSections(
+ structureWithSections, contentParts, userPrompt, parentOperationId
+ )
+
+ return filledStructure
+```
+
+---
+
+## Standard JSON Schema
+
+### Supported Section Types
+
+```python
+supportedSectionTypes = [
+ "table",
+ "bullet_list",
+ "heading",
+ "paragraph",
+ "code_block",
+ "image"
+]
+```
+
+### Section Element Types
+
+1. **Standard Elements:**
+ - `heading`, `paragraph`, `table`, `bullet_list`, `code_block`, `image`
+
+2. **Special Elements:**
+ - `extracted_text`: Extrahierter Text mit Source
+ - `reference`: Dokument-Referenz
+
+---
+
+## Flattening: Chapters zu Sections
+
+**Wichtig:** Finale Output-Struktur hat keine Chapters - nur Sections.
+
+```python
+def flattenChapterStructureToSections(
+ chapterStructure: Dict[str, Any]
+) -> Dict[str, Any]:
+ result = {
+ "metadata": chapterStructure.get("metadata", {}),
+ "documents": []
+ }
+
+ for doc in chapterStructure.get("documents", []):
+ flattened_doc = {
+ "id": doc.get("id"),
+ "title": doc.get("title"),
+ "filename": doc.get("filename"),
+ "sections": []
+ }
+
+ for chapter in doc.get("chapters", []):
+ # 1. Vordefinierte Heading-Section
+ heading_section = {
+ "id": f"{chapter['id']}_heading",
+ "content_type": "heading",
+ "elements": [{
+ "type": "heading",
+ "content": chapter.get("title"),
+ "level": chapter.get("level", 1)
+ }]
+ }
+ flattened_doc["sections"].append(heading_section)
+
+ # 2. Generierte Sections
+ flattened_doc["sections"].extend(chapter.get("sections", []))
+
+ result["documents"].append(flattened_doc)
+
+ return result
+```
+
+---
+
+## Pydantic Models
+
+```python
+class ContentPartInstruction(BaseModel):
+ instruction: str = Field(
+ description="Anweisung, wie der bereits extrahierte Content strukturiert werden soll"
+ )
+
+class Chapter(BaseModel):
+ id: str
+ level: int = Field(ge=1, le=6)
+ title: str
+ contentPartIds: List[str] = Field(default_factory=list)
+ contentPartInstructions: Dict[str, ContentPartInstruction] = Field(default_factory=dict)
+ generationHint: str
+ sections: List[Dict[str, Any]] = Field(default_factory=list)
+
+class ChapterStructure(BaseModel):
+ metadata: Dict[str, Any]
+ documents: List[Dict[str, Any]]
+
+ def flattenToSections(self) -> Dict[str, Any]:
+ # Flattening-Logik
+ ...
+```
+
+---
+
+## Chunking für große ContentParts
+
+**Wichtig:** ContentParts können sehr groß sein (z.B. 200MB). Chunking passiert automatisch.
+
+**Flow:**
+1. `callAi` mit ContentParts → routet zu `processContentPartsWithAi`
+2. `processContentPartsWithAi` → `processContentPartWithFallback` für jeden Part
+3. Wenn Part zu groß → `chunkContentPartForAi` → Chunking passiert EINMAL
+4. Gechunkte Parts werden sequenziell verarbeitet
+5. `_callWithModel` macht kein weiteres Chunking
+
+**Keine Rekursion:**
+- Chunking passiert einmal pro ContentPart
+- Gechunkte Parts werden sequenziell verarbeitet (nicht rekursiv)
+- `_callWithModel` ruft nur Model auf (kein Chunking)
+
+---
+
+## Implementierungsanforderungen
+
+1. **Phase 5C**: Generiert Chapters statt Sections
+2. **Phase 5D.1**: Generiert Sections-Struktur mit useAiCall Flag
+3. **Phase 5D.2**: Füllt Sections basierend auf useAiCall Flag
+4. **Flattening**: Konvertiert Chapters zu finaler Section-Struktur
+5. **Pydantic Models**: ChapterStructure Model definieren
+6. **Standard-JSON-Schema**: In Chapter-Prompts enthalten
+7. **Renderer**: Bleiben unverändert (verwenden finale Section-Struktur)
+
+---
+
+## Wichtige Punkte
+
+1. **ContentParts Integration:**
+ - ContentParts kommen aus Phase 5B (bereits extrahiert)
+ - Phase 5D.1: Nur IDs im Prompt (keine Previews)
+ - Phase 5D.2: ContentParts als Parameter übergeben (nicht im Prompt)
+
+2. **useAiCall Flag:**
+ - AI setzt Flag direkt im JSON
+ - Fallback: Automatisch gesetzt basierend auf content_type und instructions
+ - Generisch, sprachunabhängig (keine Stichwort-Abfragen)
+
+3. **Aggregation mehrerer ContentParts:**
+ - Bestimmte content_types benötigen Aggregation (table, bullet_list)
+ - Wenn mehrere Parts vorhanden: Alle zusammen an AI übergeben
+ - Verwendet `callAi` statt `callAiPlanning` für ContentParts-Unterstützung
+ - Automatisches Chunking bei großen aggregierten Parts
+
+4. **Chunking:**
+ - Automatisch bei großen ContentParts
+ - Funktioniert auch bei Aggregation mehrerer Parts
+ - Keine Rekursion möglich
+ - Chunks werden sequenziell verarbeitet
+
+5. **Mehrere Dokumente:**
+ - Struktur unterstützt mehrere Dokumente mit eigenen Chapters
+
+6. **ContentPart Instructions:**
+ - ContentParts sind bereits extrahiert
+ - Instructions geben Kontext für Strukturierung
+ - Kein "usage" Feld (Format durch contentFormat klar)
+
+7. **Debug-File-Logging:**
+ - Alle AI-Calls und Responses werden in Debug-Files geloggt
+ - Prompts: `{operationType}_{identifier}_prompt.txt`
+ - Responses: `{operationType}_{identifier}_response.txt`
+ - Beispiele:
+ - Phase 5C: `chapter_structure_generation_prompt.txt` / `chapter_structure_generation_response.txt`
+ - Phase 5D.1: `chapter_structure_{chapterId}_prompt.txt` / `chapter_structure_{chapterId}_response.txt`
+ - Phase 5D.2: `section_content_{sectionId}_prompt.txt` / `section_content_{sectionId}_response.txt`
+
+---
+
+## Beispiel-Szenarien
+
+### Beispiel 1: Excel-Liste der Spesenbelege
+**User Prompt:** "Erstelle eine Excel-Liste der Spesenbelege"
+**Input:** 20 PDF-Dokumente, jedes mit einem Foto eines Beleges
+
+**Phase 5B:**
+- 20 PDFs werden extrahiert → 20 ContentParts (contentFormat: "extracted")
+
+**Phase 5C:**
+- Generiert Chapter mit allen 20 contentPartIds
+
+**Phase 5D.1:**
+- Generiert Section mit:
+ - `content_type: "table"`
+ - `contentPartIds: [part_1, ..., part_20]`
+ - `useAiCall: true`
+
+**Phase 5D.2:**
+- `_needsAggregation("table", 20)` → `True`
+- Alle 20 ContentParts werden zusammen an `callAi` übergeben
+- AI generiert eine Tabelle mit allen Belegdaten
+
+**Ergebnis:** ✅ Funktioniert mit Aggregationslogik
+
+---
+
+### Beispiel 2: Vergleich mehrerer Dokumente
+**User Prompt:** "Vergleiche die drei Verträge"
+**Input:** 3 PDF-Dokumente (Verträge)
+
+**Phase 5B:**
+- 3 PDFs werden extrahiert → 3 ContentParts
+
+**Phase 5C:**
+- Generiert Chapter mit allen 3 contentPartIds
+
+**Phase 5D.1:**
+- Generiert Section mit:
+ - `content_type: "table"` (für Vergleichstabelle)
+ - `contentPartIds: [part_1, part_2, part_3]`
+ - `useAiCall: true`
+
+**Phase 5D.2:**
+- `_needsAggregation("table", 3)` → `True`
+- Alle 3 ContentParts werden zusammen an `callAi` übergeben
+- AI generiert Vergleichstabelle
+
+**Ergebnis:** ✅ Funktioniert mit Aggregationslogik
+
+---
+
+### Beispiel 3: Liste von Produkten
+**User Prompt:** "Erstelle eine Liste aller Produkte aus den Katalogen"
+**Input:** 5 PDF-Dokumente (Produktkataloge)
+
+**Phase 5B:**
+- 5 PDFs werden extrahiert → 5 ContentParts
+
+**Phase 5C:**
+- Generiert Chapter mit allen 5 contentPartIds
+
+**Phase 5D.1:**
+- Generiert Section mit:
+ - `content_type: "bullet_list"`
+ - `contentPartIds: [part_1, ..., part_5]`
+ - `useAiCall: true`
+
+**Phase 5D.2:**
+- `_needsAggregation("bullet_list", 5)` → `True`
+- Alle 5 ContentParts werden zusammen an `callAi` übergeben
+- AI generiert eine Liste mit allen Produkten
+
+**Ergebnis:** ✅ Funktioniert mit Aggregationslogik
+
+---
+
+### Beispiel 4: Einzelnes Dokument
+**User Prompt:** "Zusammenfassung des Dokuments"
+**Input:** 1 PDF-Dokument
+
+**Phase 5B:**
+- 1 PDF wird extrahiert → 1 ContentPart
+
+**Phase 5C:**
+- Generiert Chapter mit 1 contentPartId
+
+**Phase 5D.1:**
+- Generiert Section mit:
+ - `content_type: "paragraph"`
+ - `contentPartIds: [part_1]`
+ - `useAiCall: true`
+
+**Phase 5D.2:**
+- `_needsAggregation("paragraph", 1)` → `False`
+- Einzelverarbeitung: 1 ContentPart wird an `callAi` übergeben
+- AI generiert Zusammenfassung
+
+**Ergebnis:** ✅ Funktioniert (keine Aggregation nötig)