fixes
This commit is contained in:
parent
3cdd212606
commit
64590aa61e
14 changed files with 6478 additions and 3735 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,376 +0,0 @@
|
|||
# Parallel Processing Refactoring Concept
|
||||
|
||||
## Current State (Sequential)
|
||||
|
||||
### Chapter Sections Structure Generation (`_generateChapterSectionsStructure`)
|
||||
- **Current**: Processes chapters sequentially, one after another
|
||||
- **Flow**:
|
||||
1. Iterate through documents
|
||||
2. For each document, iterate through chapters
|
||||
3. For each chapter, generate sections structure using AI
|
||||
4. Update progress after each chapter
|
||||
|
||||
### Section Content Generation (`_fillChapterSections`)
|
||||
- **Current**: Processes chapters sequentially, sections within each chapter sequentially
|
||||
- **Flow**:
|
||||
1. Iterate through documents
|
||||
2. For each document, iterate through chapters
|
||||
3. For each chapter, iterate through sections
|
||||
4. For each section, generate content using AI
|
||||
5. Update progress after each section
|
||||
|
||||
## Desired State (Parallel)
|
||||
|
||||
### Chapter Sections Structure Generation
|
||||
- **Target**: Process all chapters in parallel
|
||||
- **Requirements**:
|
||||
- Maintain chapter order in final result
|
||||
- Each chapter can be processed independently
|
||||
- Progress updates should reflect parallel processing
|
||||
- Errors in one chapter should not stop others
|
||||
|
||||
### Section Content Generation
|
||||
- **Target**: Process sections within each chapter in parallel
|
||||
- **Requirements**:
|
||||
- Maintain section order within each chapter
|
||||
- Sections within a chapter can be processed independently
|
||||
- Chapters still processed sequentially (to maintain order)
|
||||
- Progress updates should reflect parallel processing
|
||||
- Errors in one section should not stop others
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### Phase 1: Chapter Sections Structure Generation Parallelization
|
||||
|
||||
#### Step 1.1: Extract Single Chapter Processing
|
||||
- **Create**: `_generateSingleChapterSectionsStructure()` method
|
||||
- **Purpose**: Process one chapter independently
|
||||
- **Parameters**:
|
||||
- `chapter`: Chapter dict
|
||||
- `chapterIndex`: Index for ordering
|
||||
- `chapterId`, `chapterLevel`, `chapterTitle`: Chapter metadata
|
||||
- `generationHint`: Generation instructions
|
||||
- `contentPartIds`, `contentPartInstructions`: Content part info
|
||||
- `contentParts`: Full content parts list
|
||||
- `userPrompt`: User's original prompt
|
||||
- `language`: Language for generation
|
||||
- `parentOperationId`: For progress logging
|
||||
- **Returns**: None (modifies chapter dict in place)
|
||||
- **Error Handling**: Logs errors, raises exception to be caught by caller
|
||||
|
||||
#### Step 1.2: Refactor Main Method
|
||||
- **Modify**: `_generateChapterSectionsStructure()`
|
||||
- **Changes**:
|
||||
1. Collect all chapters with their indices
|
||||
2. Create async tasks for each chapter using `_generateSingleChapterSectionsStructure`
|
||||
3. Use `asyncio.gather()` to execute all tasks in parallel
|
||||
4. Process results in order (using `zip` with original order)
|
||||
5. Handle errors per chapter (don't fail entire operation)
|
||||
6. Update progress after each chapter completes
|
||||
|
||||
#### Step 1.3: Progress Reporting
|
||||
- **Maintain**: Overall progress tracking
|
||||
- **Update**: Progress after each chapter completes (not sequentially)
|
||||
- **Format**: "Chapter X/Y completed" or "Chapter X/Y error"
|
||||
|
||||
### Phase 2: Section Content Generation Parallelization
|
||||
|
||||
#### Step 2.1: Extract Single Section Processing
|
||||
- **Create**: `_processSingleSection()` method
|
||||
- **Purpose**: Process one section independently
|
||||
- **Parameters**:
|
||||
- `section`: Section dict
|
||||
- `sectionIndex`: Index for ordering
|
||||
- `totalSections`: Total sections in chapter
|
||||
- `chapterIndex`: Chapter index
|
||||
- `totalChapters`: Total chapters
|
||||
- `chapterId`: Chapter ID
|
||||
- `chapterOperationId`: Chapter progress operation ID
|
||||
- `fillOperationId`: Overall fill operation ID
|
||||
- `contentParts`: Full content parts list
|
||||
- `userPrompt`: User's original prompt
|
||||
- `all_sections_list`: All sections for context
|
||||
- `language`: Language for generation
|
||||
- `calculateOverallProgress`: Function to calculate overall progress
|
||||
- **Returns**: `List[Dict[str, Any]]` (elements for the section)
|
||||
- **Error Handling**: Returns error element instead of raising
|
||||
|
||||
#### Step 2.2: Extract Section Processing Logic
|
||||
- **Create**: Helper methods for different processing paths:
|
||||
- `_processSectionAggregation()`: Handle aggregation path (multiple parts)
|
||||
- `_processSectionGeneration()`: Handle generation without parts (only generationHint)
|
||||
- `_processSectionParts()`: Handle individual part processing
|
||||
- **Purpose**: Keep logic organized and reusable
|
||||
|
||||
#### Step 2.3: Refactor Main Method
|
||||
- **Modify**: `_fillChapterSections()`
|
||||
- **Changes**:
|
||||
1. Keep sequential chapter processing (maintains order)
|
||||
2. For each chapter, collect all sections with indices
|
||||
3. Create async tasks for each section using `_processSingleSection`
|
||||
4. Use `asyncio.gather()` to execute all section tasks in parallel
|
||||
5. Process results in order (using `zip` with original order)
|
||||
6. Assign elements to sections in correct order
|
||||
7. Update progress after each section completes
|
||||
8. Handle errors per section (don't fail entire chapter)
|
||||
|
||||
#### Step 2.4: Progress Reporting
|
||||
- **Maintain**: Hierarchical progress tracking
|
||||
- **Update**:
|
||||
- Section progress: After each section completes
|
||||
- Chapter progress: After all sections in chapter complete
|
||||
- Overall progress: After each section/chapter completes
|
||||
- **Format**: "Chapter X/Y, Section A/B completed"
|
||||
|
||||
## Key Considerations
|
||||
|
||||
### Order Preservation
|
||||
- **Chapters**: Must maintain document order → process chapters sequentially
|
||||
- **Sections**: Must maintain chapter order → process sections sequentially within chapter
|
||||
- **Solution**: Use `asyncio.gather()` with ordered task list, then `zip` results with original order
|
||||
|
||||
### Error Handling
|
||||
- **Chapters**: Error in one chapter should not stop others
|
||||
- **Sections**: Error in one section should not stop others
|
||||
- **Solution**: Use `return_exceptions=True` in `asyncio.gather()`, check `isinstance(result, Exception)`
|
||||
|
||||
### Progress Reporting
|
||||
- **Challenge**: Progress updates happen out of order
|
||||
- **Solution**: Update progress when each task completes, not sequentially
|
||||
- **Format**: Show completed count, not sequential position
|
||||
|
||||
### Shared State
|
||||
- **Chapters**: Modify chapter dicts in place (safe, each chapter is independent)
|
||||
- **Sections**: Return elements, assign to sections in order (safe, each section is independent)
|
||||
- **Content Parts**: Read-only, passed to all tasks (safe)
|
||||
|
||||
### Dependencies
|
||||
- **Chapters**: No dependencies between chapters
|
||||
- **Sections**: No dependencies between sections (each is self-contained)
|
||||
- **Solution**: All tasks can run truly in parallel
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Clean Current Code
|
||||
1. Ensure current sequential implementation is correct
|
||||
2. Fix any existing bugs
|
||||
3. Verify all tests pass
|
||||
|
||||
### Step 2: Implement Chapter Parallelization
|
||||
1. Create `_generateSingleChapterSectionsStructure()` method
|
||||
2. Extract chapter processing logic
|
||||
3. Refactor `_generateChapterSectionsStructure()` to use parallel processing
|
||||
4. Test with single chapter
|
||||
5. Test with multiple chapters
|
||||
6. Verify order preservation
|
||||
7. Verify error handling
|
||||
|
||||
### Step 3: Implement Section Parallelization
|
||||
1. Create `_processSingleSection()` method
|
||||
2. Extract section processing logic into helper methods
|
||||
3. Refactor `_fillChapterSections()` to use parallel processing for sections
|
||||
4. Test with single section
|
||||
5. Test with multiple sections
|
||||
6. Test with multiple chapters
|
||||
7. Verify order preservation
|
||||
8. Verify error handling
|
||||
|
||||
### Step 4: Testing & Validation
|
||||
1. Test with various document structures
|
||||
2. Test error scenarios
|
||||
3. Verify progress reporting accuracy
|
||||
4. Performance testing (compare sequential vs parallel)
|
||||
5. Verify final output order matches input order
|
||||
|
||||
## Code Structure
|
||||
|
||||
### New Methods to Create
|
||||
|
||||
```python
|
||||
async def _generateSingleChapterSectionsStructure(
|
||||
self,
|
||||
chapter: Dict[str, Any],
|
||||
chapterIndex: int,
|
||||
chapterId: str,
|
||||
chapterLevel: int,
|
||||
chapterTitle: str,
|
||||
generationHint: str,
|
||||
contentPartIds: List[str],
|
||||
contentPartInstructions: Dict[str, Any],
|
||||
contentParts: List[ContentPart],
|
||||
userPrompt: str,
|
||||
language: str,
|
||||
parentOperationId: str
|
||||
) -> None:
|
||||
"""Generate sections structure for a single chapter (used for parallel processing)."""
|
||||
# Extract logic from current sequential loop
|
||||
# Modify chapter dict in place
|
||||
# Handle errors internally, raise if critical
|
||||
|
||||
async def _processSingleSection(
|
||||
self,
|
||||
section: Dict[str, Any],
|
||||
sectionIndex: int,
|
||||
totalSections: int,
|
||||
chapterIndex: int,
|
||||
totalChapters: int,
|
||||
chapterId: str,
|
||||
chapterOperationId: str,
|
||||
fillOperationId: str,
|
||||
contentParts: List[ContentPart],
|
||||
userPrompt: str,
|
||||
all_sections_list: List[Dict[str, Any]],
|
||||
language: str,
|
||||
calculateOverallProgress: Callable
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Process a single section and return its elements."""
|
||||
# Extract logic from current sequential loop
|
||||
# Return elements list
|
||||
# Return error element on failure (don't raise)
|
||||
|
||||
async def _processSectionAggregation(
|
||||
self,
|
||||
section: Dict[str, Any],
|
||||
sectionId: str,
|
||||
sectionTitle: str,
|
||||
sectionIndex: int,
|
||||
totalSections: int,
|
||||
chapterId: str,
|
||||
chapterOperationId: str,
|
||||
fillOperationId: str,
|
||||
contentPartIds: List[str],
|
||||
contentFormats: Dict[str, str],
|
||||
contentParts: List[ContentPart],
|
||||
userPrompt: str,
|
||||
generationHint: str,
|
||||
all_sections_list: List[Dict[str, Any]],
|
||||
language: str
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Process section with aggregation (multiple parts together)."""
|
||||
# Extract aggregation logic
|
||||
# Return elements list
|
||||
|
||||
async def _processSectionGeneration(
|
||||
self,
|
||||
section: Dict[str, Any],
|
||||
sectionId: str,
|
||||
sectionTitle: str,
|
||||
sectionIndex: int,
|
||||
totalSections: int,
|
||||
chapterId: str,
|
||||
chapterOperationId: str,
|
||||
fillOperationId: str,
|
||||
contentType: str,
|
||||
userPrompt: str,
|
||||
generationHint: str,
|
||||
all_sections_list: List[Dict[str, Any]],
|
||||
language: str
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Process section generation without content parts (only generationHint)."""
|
||||
# Extract generation logic
|
||||
# Return elements list
|
||||
|
||||
async def _processSectionParts(
|
||||
self,
|
||||
section: Dict[str, Any],
|
||||
sectionId: str,
|
||||
sectionTitle: str,
|
||||
sectionIndex: int,
|
||||
totalSections: int,
|
||||
chapterId: str,
|
||||
chapterOperationId: str,
|
||||
fillOperationId: str,
|
||||
contentPartIds: List[str],
|
||||
contentFormats: Dict[str, str],
|
||||
contentParts: List[ContentPart],
|
||||
contentType: str,
|
||||
useAiCall: bool,
|
||||
generationHint: str,
|
||||
userPrompt: str,
|
||||
all_sections_list: List[Dict[str, Any]],
|
||||
language: str
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Process individual parts in a section."""
|
||||
# Extract individual part processing logic
|
||||
# Return elements list
|
||||
```
|
||||
|
||||
### Modified Methods
|
||||
|
||||
```python
|
||||
async def _generateChapterSectionsStructure(
|
||||
self,
|
||||
chapterStructure: Dict[str, Any],
|
||||
contentParts: List[ContentPart],
|
||||
userPrompt: str,
|
||||
parentOperationId: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Generate sections structure for all chapters in parallel."""
|
||||
# Collect chapters with indices
|
||||
# Create tasks
|
||||
# Execute in parallel
|
||||
# Process results in order
|
||||
# Update progress
|
||||
|
||||
async def _fillChapterSections(
|
||||
self,
|
||||
chapterStructure: Dict[str, Any],
|
||||
contentParts: List[ContentPart],
|
||||
userPrompt: str,
|
||||
fillOperationId: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Fill sections with content, processing sections in parallel within each chapter."""
|
||||
# Process chapters sequentially
|
||||
# For each chapter, process sections in parallel
|
||||
# Maintain order
|
||||
# Update progress
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
1. Test `_generateSingleChapterSectionsStructure` independently
|
||||
2. Test `_processSingleSection` independently
|
||||
3. Test helper methods independently
|
||||
|
||||
### Integration Tests
|
||||
1. Test parallel chapter processing with multiple chapters
|
||||
2. Test parallel section processing with multiple sections
|
||||
3. Test error handling (one chapter/section fails)
|
||||
4. Test order preservation
|
||||
|
||||
### Performance Tests
|
||||
1. Measure sequential vs parallel execution time
|
||||
2. Verify parallel processing is faster
|
||||
3. Check resource usage (memory, CPU)
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
### Risks
|
||||
1. **Order not preserved**: Use `zip` with original order
|
||||
2. **Race conditions**: No shared mutable state between tasks
|
||||
3. **Progress reporting incorrect**: Update progress when tasks complete
|
||||
4. **Errors not handled**: Use `return_exceptions=True` and check results
|
||||
5. **Performance degradation**: Test and measure, fallback to sequential if needed
|
||||
|
||||
### Safety Measures
|
||||
1. Keep sequential implementation as fallback (commented out)
|
||||
2. Add feature flag to enable/disable parallel processing
|
||||
3. Extensive logging for debugging
|
||||
4. Gradual rollout (test with small datasets first)
|
||||
|
||||
## Migration Path
|
||||
|
||||
1. **Phase 1**: Implement chapter parallelization, test thoroughly
|
||||
2. **Phase 2**: Implement section parallelization, test thoroughly
|
||||
3. **Phase 3**: Enable both in production with monitoring
|
||||
4. **Phase 4**: Remove sequential fallback code (if stable)
|
||||
|
||||
## Notes
|
||||
|
||||
- All async methods must use `await` correctly
|
||||
- Progress updates happen asynchronously (may appear out of order in logs)
|
||||
- Final result order is guaranteed by processing results in order
|
||||
- Error handling is per-task, not global
|
||||
- No shared mutable state between parallel tasks (read-only contentParts, independent chapter/section dicts)
|
||||
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
# Module Structure - serviceAi
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das `mainServiceAi.py` Modul wurde in mehrere Submodule aufgeteilt, um die Übersichtlichkeit zu verbessern.
|
||||
|
||||
## Modulstruktur
|
||||
|
||||
### Hauptmodul
|
||||
- **mainServiceAi.py** (~800 Zeilen)
|
||||
- Initialisierung (`__init__`, `create`, `ensureAiObjectsInitialized`)
|
||||
- Public API (`callAiPlanning`, `callAiContent`)
|
||||
- Routing zu Submodulen
|
||||
- Helper-Methoden
|
||||
|
||||
### Submodule
|
||||
|
||||
1. **subJsonResponseHandling.py** (bereits vorhanden)
|
||||
- JSON Response Merging
|
||||
- Section Merging
|
||||
- Fragment Detection
|
||||
|
||||
2. **subResponseParsing.py** (~200 Zeilen)
|
||||
- `ResponseParser.extractSectionsFromResponse()` - Extrahiert Sections aus AI-Responses
|
||||
- `ResponseParser.shouldContinueGeneration()` - Entscheidet ob Generation fortgesetzt werden soll
|
||||
- `ResponseParser._isStuckInLoop()` - Loop-Detection
|
||||
- `ResponseParser.extractDocumentMetadata()` - Extrahiert Metadaten
|
||||
- `ResponseParser.buildFinalResultFromSections()` - Baut finales JSON
|
||||
|
||||
3. **subDocumentIntents.py** (~300 Zeilen)
|
||||
- `DocumentIntentAnalyzer.clarifyDocumentIntents()` - Analysiert Dokument-Intents
|
||||
- `DocumentIntentAnalyzer.resolvePreExtractedDocument()` - Löst pre-extracted Dokumente auf
|
||||
- `DocumentIntentAnalyzer._buildIntentAnalysisPrompt()` - Baut Intent-Analyse-Prompt
|
||||
|
||||
4. **subContentExtraction.py** (~600 Zeilen)
|
||||
- `ContentExtractor.extractAndPrepareContent()` - Extrahiert und bereitet Content vor
|
||||
- `ContentExtractor.extractTextFromImage()` - Vision AI für Bilder
|
||||
- `ContentExtractor.processTextContentWithAi()` - AI-Verarbeitung von Text
|
||||
- `ContentExtractor._isBinary()` - Helper für Binary-Check
|
||||
|
||||
5. **subStructureGeneration.py** (~200 Zeilen)
|
||||
- `StructureGenerator.generateStructure()` - Generiert Dokument-Struktur
|
||||
- `StructureGenerator._buildStructurePrompt()` - Baut Struktur-Prompt
|
||||
|
||||
6. **subStructureFilling.py** (~400 Zeilen)
|
||||
- `StructureFiller.fillStructure()` - Füllt Struktur mit Content
|
||||
- `StructureFiller._buildSectionGenerationPrompt()` - Baut Section-Generation-Prompt
|
||||
- `StructureFiller._findContentPartById()` - Helper für ContentPart-Suche
|
||||
- `StructureFiller._needsAggregation()` - Entscheidet ob Aggregation nötig
|
||||
|
||||
7. **subAiCallLooping.py** (~400 Zeilen)
|
||||
- `AiCallLooper.callAiWithLooping()` - Haupt-Looping-Logik
|
||||
- `AiCallLooper._defineKpisFromPrompt()` - KPI-Definition
|
||||
|
||||
## Verwendung
|
||||
|
||||
Alle Submodule werden über das Hauptmodul `AiService` verwendet:
|
||||
|
||||
```python
|
||||
# Initialisierung
|
||||
aiService = await AiService.create(serviceCenter)
|
||||
|
||||
# Submodule werden automatisch initialisiert
|
||||
# aiService.responseParser
|
||||
# aiService.intentAnalyzer
|
||||
# aiService.contentExtractor
|
||||
# etc.
|
||||
```
|
||||
|
||||
## Migration
|
||||
|
||||
Die öffentliche API bleibt unverändert. Interne Methoden wurden in Submodule verschoben:
|
||||
|
||||
- `_extractSectionsFromResponse` → `responseParser.extractSectionsFromResponse`
|
||||
- `_clarifyDocumentIntents` → `intentAnalyzer.clarifyDocumentIntents`
|
||||
- `_extractAndPrepareContent` → `contentExtractor.extractAndPrepareContent`
|
||||
- etc.
|
||||
|
||||
|
|
@ -222,18 +222,6 @@ Respond with ONLY a JSON object in this exact format:
|
|||
prompt, options, debugPrefix, promptBuilder, promptArgs, operationId, userPrompt, contentParts, useCaseId
|
||||
)
|
||||
|
||||
async def _defineKpisFromPrompt(
|
||||
self,
|
||||
userPrompt: str,
|
||||
rawJsonString: Optional[str],
|
||||
continuationContext: Dict[str, Any],
|
||||
debugPrefix: str = "kpi"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Delegate to AiCallLooper."""
|
||||
return await self.aiCallLooper._defineKpisFromPrompt(
|
||||
userPrompt, rawJsonString, continuationContext, debugPrefix
|
||||
)
|
||||
|
||||
# JSON merging logic moved to subJsonResponseHandling.py
|
||||
|
||||
def _extractSectionsFromResponse(
|
||||
|
|
|
|||
529
modules/services/serviceAi/merge_1.txt
Normal file
529
modules/services/serviceAi/merge_1.txt
Normal file
|
|
@ -0,0 +1,529 @@
|
|||
================================================================================
|
||||
JSON MERGE OPERATION #1
|
||||
================================================================================
|
||||
Timestamp: 2026-01-04T15:18:28.448964
|
||||
|
||||
INPUT:
|
||||
Accumulated length: 36937 chars
|
||||
New Fragment length: 36843 chars
|
||||
Accumulated: 223 lines (showing first 5 and last 5)
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"type": "table",
|
||||
"content": {
|
||||
... (213 lines omitted) ...
|
||||
["2111", "18433", "2112", "18439", "2113", "18443", "2114", "18451", "2115", "18457", "2116", "18461", "2117", "18481", "2118", "18493", "2119", "18503", "2120", "18517"],
|
||||
["2121", "18521", "2122", "18523", "2123", "18539", "2124", "18541", "2125", "18553", "2126", "18583", "2127", "18587", "2128", "18593", "2129", "18617", "2130", "18637"],
|
||||
["2131", "18661", "2132", "18671", "2133", "18679", "2134", "18691", "2135", "18701", "2136", "18713", "2137", "18719", "2138", "18731", "2139", "18743", "2140", "18749"],
|
||||
["2141", "18757", "2142", "18773", "2143", "18787", "2144", "18793", "2145", "18797", "2146", "18803", "2147", "18839", "2148", "18859", "2149", "18869", "2150", "18899"],
|
||||
["2151", "189
|
||||
New Fragment: 209 lines (showing first 5 and last 5)
|
||||
```json
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"type": "table",
|
||||
... (199 lines omitted) ...
|
||||
["4061", "38569", "4062", "38593", "4063", "38603", "4064", "38609", "4065", "38611", "4066", "38629", "4067", "38639", "4068", "38651", "4069", "38653", "4070", "38669"],
|
||||
["4071", "38671", "4072", "38677", "4073", "38693", "4074", "38699", "4075", "38707", "4076", "38711", "4077", "38713", "4078", "38723", "4079", "38729", "4080", "38737"],
|
||||
["4081", "38747", "4082", "38749", "4083", "38767", "4084", "38783", "4085", "38791", "4086", "38803", "4087", "38821", "4088", "38833", "4089", "38839", "4090", "38851"],
|
||||
["4091", "38861", "4092", "38867", "4093", "38873", "4094", "38891", "4095", "38903", "4096", "38917", "4097", "38921", "4098", "38923", "4099", "38933", "4100", "38953"],
|
||||
["4101", "38959", "4102", "38971", "4103", "38977", "4104", "38993", "4105", "39019", "4106", "39023", "4107
|
||||
|
||||
|
||||
Normalized Accumulated (36937 chars)
|
||||
(showing first 5 and last 5 of 223 lines)
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"type": "table",
|
||||
"content": {
|
||||
... (213 lines omitted) ...
|
||||
["2111", "18433", "2112", "18439", "2113", "18443", "2114", "18451", "2115", "18457", "2116", "18461", "2117", "18481", "2118", "18493", "2119", "18503", "2120", "18517"],
|
||||
["2121", "18521", "2122", "18523", "2123", "18539", "2124", "18541", "2125", "18553", "2126", "18583", "2127", "18587", "2128", "18593", "2129", "18617", "2130", "18637"],
|
||||
["2131", "18661", "2132", "18671", "2133", "18679", "2134", "18691", "2135", "18701", "2136", "18713", "2137", "18719", "2138", "18731", "2139", "18743", "2140", "18749"],
|
||||
["2141", "18757", "2142", "18773", "2143", "18787", "2144", "18793", "2145", "18797", "2146", "18803", "2147", "18839", "2148", "18859", "2149", "18869", "2150", "18899"],
|
||||
["2151", "189
|
||||
|
||||
Normalized New Fragment (36835 chars)
|
||||
(showing first 5 and last 5 of 208 lines)
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"type": "table",
|
||||
"content": {
|
||||
... (198 lines omitted) ...
|
||||
["4061", "38569", "4062", "38593", "4063", "38603", "4064", "38609", "4065", "38611", "4066", "38629", "4067", "38639", "4068", "38651", "4069", "38653", "4070", "38669"],
|
||||
["4071", "38671", "4072", "38677", "4073", "38693", "4074", "38699", "4075", "38707", "4076", "38711", "4077", "38713", "4078", "38723", "4079", "38729", "4080", "38737"],
|
||||
["4081", "38747", "4082", "38749", "4083", "38767", "4084", "38783", "4085", "38791", "4086", "38803", "4087", "38821", "4088", "38833", "4089", "38839", "4090", "38851"],
|
||||
["4091", "38861", "4092", "38867", "4093", "38873", "4094", "38891", "4095", "38903", "4096", "38917", "4097", "38921", "4098", "38923", "4099", "38933", "4100", "38953"],
|
||||
["4101", "38959", "4102", "38971", "4103", "38977", "4104", "38993", "4105", "39019", "4106", "39023", "4107
|
||||
STEP: PHASE 1
|
||||
Description: Finding overlap between JSON strings
|
||||
⏳ In progress...
|
||||
|
||||
Overlap Detection (string):
|
||||
Overlap length: 0
|
||||
⚠️ No overlap detected - appending all
|
||||
|
||||
⚠️ NO OVERLAP FOUND - This indicates iterations should stop
|
||||
Closing JSON and returning final result
|
||||
|
||||
Closed JSON (36944 chars):
|
||||
==============================================================================
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"type": "table",
|
||||
"content": {
|
||||
"headers": ["Nr.1", "Primzahl1", "Nr.2", "Primzahl2", "Nr.3", "Primzahl3", "Nr.4", "Primzahl4", "Nr.5", "Primzahl5", "Nr.6", "Primzahl6", "Nr.7", "Primzahl7", "Nr.8", "Primzahl8", "Nr.9", "Primzahl9", "Nr.10", "Primzahl10"],
|
||||
"rows": [
|
||||
["1", "2", "2", "3", "3", "5", "4", "7", "5", "11", "6", "13", "7", "17", "8", "19", "9", "23", "10", "29"],
|
||||
["11", "31", "12", "37", "13", "41", "14", "43", "15", "47", "16", "53", "17", "59", "18", "61", "19", "67", "20", "71"],
|
||||
["21", "73", "22", "79", "23", "83", "24", "89", "25", "97", "26", "101", "27", "103", "28", "107", "29", "109", "30", "113"],
|
||||
["31", "127", "32", "131", "33", "137", "34", "139", "35", "149", "36", "151", "37", "157", "38", "163", "39", "167", "40", "173"],
|
||||
["41", "179", "42", "181", "43", "191", "44", "193", "45", "197", "46", "199", "47", "211", "48", "223", "49", "227", "50", "229"],
|
||||
["51", "233", "52", "239", "53", "241", "54", "251", "55", "257", "56", "263", "57", "269", "58", "271", "59", "277", "60", "281"],
|
||||
["61", "283", "62", "293", "63", "307", "64", "311", "65", "313", "66", "317", "67", "331", "68", "337", "69", "347", "70", "349"],
|
||||
["71", "353", "72", "359", "73", "367", "74", "373", "75", "379", "76", "383", "77", "389", "78", "397", "79", "401", "80", "409"],
|
||||
["81", "419", "82", "421", "83", "431", "84", "433", "85", "439", "86", "443", "87", "449", "88", "457", "89", "461", "90", "463"],
|
||||
["91", "467", "92", "479", "93", "487", "94", "491", "95", "499", "96", "503", "97", "509", "98", "521", "99", "523", "100", "541"],
|
||||
["101", "547", "102", "557", "103", "563", "104", "569", "105", "571", "106", "577", "107", "587", "108", "593", "109", "599", "110", "601"],
|
||||
["111", "607", "112", "613", "113", "617", "114", "619", "115", "631", "116", "641", "117", "643", "118", "647", "119", "653", "120", "659"],
|
||||
["121", "661", "122", "673", "123", "677", "124", "683", "125", "691", "126", "701", "127", "709", "128", "719", "129", "727", "130", "733"],
|
||||
["131", "739", "132", "743", "133", "751", "134", "757", "135", "761", "136", "769", "137", "773", "138", "787", "139", "797", "140", "809"],
|
||||
["141", "811", "142", "821", "143", "823", "144", "827", "145", "829", "146", "839", "147", "853", "148", "857", "149", "859", "150", "863"],
|
||||
["151", "877", "152", "881", "153", "883", "154", "887", "155", "907", "156", "911", "157", "919", "158", "929", "159", "937", "160", "941"],
|
||||
["161", "947", "162", "953", "163", "967", "164", "971", "165", "977", "166", "983", "167", "991", "168", "997", "169", "1009", "170", "1013"],
|
||||
["171", "1019", "172", "1021", "173", "1031", "174", "1033", "175", "1039", "176", "1049", "177", "1051", "178", "1061", "179", "1063", "180", "1069"],
|
||||
["181", "1087", "182", "1091", "183", "1093", "184", "1097", "185", "1103", "186", "1109", "187", "1117", "188", "1123", "189", "1129", "190", "1151"],
|
||||
["191", "1153", "192", "1163", "193", "1171", "194", "1181", "195", "1187", "196", "1193", "197", "1201", "198", "1213", "199", "1217", "200", "1223"],
|
||||
["201", "1229", "202", "1231", "203", "1237", "204", "1249", "205", "1259", "206", "1277", "207", "1279", "208", "1283", "209", "1289", "210", "1291"],
|
||||
["211", "1297", "212", "1301", "213", "1303", "214", "1307", "215", "1319", "216", "1321", "217", "1327", "218", "1361", "219", "1367", "220", "1373"],
|
||||
["221", "1381", "222", "1399", "223", "1409", "224", "1423", "225", "1427", "226", "1429", "227", "1433", "228", "1439", "229", "1447", "230", "1451"],
|
||||
["231", "1453", "232", "1459", "233", "1471", "234", "1481", "235", "1483", "236", "1487", "237", "1489", "238", "1493", "239", "1499", "240", "1511"],
|
||||
["241", "1523", "242", "1531", "243", "1543", "244", "1549", "245", "1553", "246", "1559", "247", "1567", "248", "1571", "249", "1579", "250", "1583"],
|
||||
["251", "1597", "252", "1601", "253", "1607", "254", "1609", "255", "1613", "256", "1619", "257", "1621", "258", "1627", "259", "1637", "260", "1657"],
|
||||
["261", "1663", "262", "1667", "263", "1669", "264", "1693", "265", "1697", "266", "1699", "267", "1709", "268", "1721", "269", "1723", "270", "1733"],
|
||||
["271", "1741", "272", "1747", "273", "1753", "274", "1759", "275", "1777", "276", "1783", "277", "1787", "278", "1789", "279", "1801", "280", "1811"],
|
||||
["281", "1823", "282", "1831", "283", "1847", "284", "1861", "285", "1867", "286", "1871", "287", "1873", "288", "1877", "289", "1879", "290", "1889"],
|
||||
["291", "1901", "292", "1907", "293", "1913", "294", "1931", "295", "1933", "296", "1949", "297", "1951", "298", "1973", "299", "1979", "300", "1987"],
|
||||
["301", "1993", "302", "1997", "303", "1999", "304", "2003", "305", "2011", "306", "2017", "307", "2027", "308", "2029", "309", "2039", "310", "2053"],
|
||||
["311", "2063", "312", "2069", "313", "2081", "314", "2083", "315", "2087", "316", "2089", "317", "2099", "318", "2111", "319", "2113", "320", "2129"],
|
||||
["321", "2131", "322", "2137", "323", "2141", "324", "2143", "325", "2153", "326", "2161", "327", "2179", "328", "2203", "329", "2207", "330", "2213"],
|
||||
["331", "2221", "332", "2237", "333", "2239", "334", "2243", "335", "2251", "336", "2267", "337", "2269", "338", "2273", "339", "2281", "340", "2287"],
|
||||
["341", "2293", "342", "2297", "343", "2309", "344", "2311", "345", "2333", "346", "2339", "347", "2341", "348", "2347", "349", "2351", "350", "2357"],
|
||||
["351", "2371", "352", "2377", "353", "2381", "354", "2383", "355", "2389", "356", "2393", "357", "2399", "358", "2411", "359", "2417", "360", "2423"],
|
||||
["361", "2437", "362", "2441", "363", "2447", "364", "2459", "365", "2467", "366", "2473", "367", "2477", "368", "2503", "369", "2521", "370", "2531"],
|
||||
["371", "2539", "372", "2543", "373", "2549", "374", "2551", "375", "2557", "376", "2579", "377", "2591", "378", "2593", "379", "2609", "380", "2617"],
|
||||
["381", "2621", "382", "2633", "383", "2647", "384", "2657", "385", "2659", "386", "2663", "387", "2671", "388", "2677", "389", "2683", "390", "2687"],
|
||||
["391", "2689", "392", "2693", "393", "2699", "394", "2707", "395", "2711", "396", "2713", "397", "2719", "398", "2729", "399", "2731", "400", "2741"],
|
||||
["401", "2749", "402", "2753", "403", "2767", "404", "2777", "405", "2789", "406", "2791", "407", "2797", "408", "2801", "409", "2803", "410", "2819"],
|
||||
["411", "2833", "412", "2837", "413", "2843", "414", "2851", "415", "2857", "416", "2861", "417", "2879", "418", "2887", "419", "2897", "420", "2903"],
|
||||
["421", "2909", "422", "2917", "423", "2927", "424", "2939", "425", "2953", "426", "2957", "427", "2963", "428", "2969", "429", "2971", "430", "2999"],
|
||||
["431", "3001", "432", "3011", "433", "3019", "434", "3023", "435", "3037", "436", "3041", "437", "3049", "438", "3061", "439", "3067", "440", "3079"],
|
||||
["441", "3083", "442", "3089", "443", "3109", "444", "3119", "445", "3121", "446", "3137", "447", "3163", "448", "3167", "449", "3169", "450", "3181"],
|
||||
["451", "3187", "452", "3191", "453", "3203", "454", "3209", "455", "3217", "456", "3221", "457", "3229", "458", "3251", "459", "3253", "460", "3257"],
|
||||
["461", "3259", "462", "3271", "463", "3299", "464", "3301", "465", "3307", "466", "3313", "467", "3319", "468", "3323", "469", "3329", "470", "3331"],
|
||||
["471", "3343", "472", "3347", "473", "3359", "474", "3361", "475", "3371", "476", "3373", "477", "3389", "478", "3391", "479", "3407", "480", "3413"],
|
||||
["481", "3433", "482", "3449", "483", "3457", "484", "3461", "485", "3463", "486", "3467", "487", "3469", "488", "3491", "489", "3499", "490", "3511"],
|
||||
["491", "3517", "492", "3527", "493", "3529", "494", "3533", "495", "3539", "496", "3541", "497", "3547", "498", "3557", "499", "3559", "500", "3571"],
|
||||
["501", "3581", "502", "3583", "503", "3593", "504", "3607", "505", "3613", "506", "3617", "507", "3623", "508", "3631", "509", "3637", "510", "3643"],
|
||||
["511", "3659", "512", "3671", "513", "3673", "514", "3677", "515", "3691", "516", "3697", "517", "3701", "518", "3709", "519", "3719", "520", "3727"],
|
||||
["521", "3733", "522", "3739", "523", "3761", "524", "3767", "525", "3769", "526", "3779", "527", "3793", "528", "3797", "529", "3803", "530", "3821"],
|
||||
["531", "3823", "532", "3833", "533", "3847", "534", "3851", "535", "3853", "536", "3863", "537", "3877", "538", "3881", "539", "3889", "540", "3907"],
|
||||
["541", "3911", "542", "3917", "543", "3919", "544", "3923", "545", "3929", "546", "3931", "547", "3943", "548", "3947", "549", "3967", "550", "3989"],
|
||||
["551", "4001", "552", "4003", "553", "4007", "554", "4013", "555", "4019", "556", "4021", "557", "4027", "558", "4049", "559", "4051", "560", "4057"],
|
||||
["561", "4073", "562", "4079", "563", "4091", "564", "4093", "565", "4099", "566", "4111", "567", "4127", "568", "4129", "569", "4133", "570", "4139"],
|
||||
["571", "4153", "572", "4157", "573", "4159", "574", "4177", "575", "4201", "576", "4211", "577", "4217", "578", "4219", "579", "4229", "580", "4231"],
|
||||
["581", "4241", "582", "4243", "583", "4253", "584", "4259", "585", "4261", "586", "4271", "587", "4273", "588", "4283", "589", "4289", "590", "4297"],
|
||||
["591", "4327", "592", "4337", "593", "4339", "594", "4349", "595", "4357", "596", "4363", "597", "4373", "598", "4391", "599", "4397", "600", "4409"],
|
||||
["601", "4421", "602", "4423", "603", "4441", "604", "4447", "605", "4451", "606", "4457", "607", "4463", "608", "4481", "609", "4483", "610", "4493"],
|
||||
["611", "4507", "612", "4513", "613", "4517", "614", "4519", "615", "4523", "616", "4547", "617", "4549", "618", "4561", "619", "4567", "620", "4583"],
|
||||
["621", "4591", "622", "4597", "623", "4603", "624", "4621", "625", "4637", "626", "4639", "627", "4643", "628", "4649", "629", "4651", "630", "4657"],
|
||||
["631", "4663", "632", "4673", "633", "4679", "634", "4691", "635", "4703", "636", "4721", "637", "4723", "638", "4729", "639", "4733", "640", "4751"],
|
||||
["641", "4759", "642", "4783", "643", "4787", "644", "4789", "645", "4793", "646", "4799", "647", "4801", "648", "4813", "649", "4817", "650", "4831"],
|
||||
["651", "4861", "652", "4871", "653", "4877", "654", "4889", "655", "4903", "656", "4909", "657", "4919", "658", "4931", "659", "4933", "660", "4937"],
|
||||
["661", "4943", "662", "4951", "663", "4957", "664", "4967", "665", "4969", "666", "4973", "667", "4987", "668", "4993", "669", "4999", "670", "5003"],
|
||||
["671", "5009", "672", "5011", "673", "5021", "674", "5023", "675", "5039", "676", "5051", "677", "5059", "678", "5077", "679", "5081", "680", "5087"],
|
||||
["681", "5099", "682", "5101", "683", "5107", "684", "5113", "685", "5119", "686", "5147", "687", "5153", "688", "5167", "689", "5171", "690", "5179"],
|
||||
["691", "5189", "692", "5197", "693", "5209", "694", "5227", "695", "5231", "696", "5233", "697", "5237", "698", "5261", "699", "5273", "700", "5279"],
|
||||
["701", "5281", "702", "5297", "703", "5303", "704", "5309", "705", "5323", "706", "5333", "707", "5347", "708", "5351", "709", "5381", "710", "5387"],
|
||||
["711", "5393", "712", "5399", "713", "5407", "714", "5413", "715", "5417", "716", "5419", "717", "5431", "718", "5437", "719", "5441", "720", "5443"],
|
||||
["721", "5449", "722", "5471", "723", "5477", "724", "5479", "725", "5483", "726", "5501", "727", "5503", "728", "5507", "729", "5519", "730", "5521"],
|
||||
["731", "5527", "732", "5531", "733", "5557", "734", "5563", "735", "5569", "736", "5573", "737", "5581", "738", "5591", "739", "5623", "740", "5639"],
|
||||
["741", "5641", "742", "5647", "743", "5651", "744", "5653", "745", "5657", "746", "5659", "747", "5669", "748", "5683", "749", "5689", "750", "5693"],
|
||||
["751", "5701", "752", "5711", "753", "5717", "754", "5737", "755", "5741", "756", "5743", "757", "5749", "758", "5779", "759", "5783", "760", "5791"],
|
||||
["761", "5801", "762", "5807", "763", "5813", "764", "5821", "765", "5827", "766", "5839", "767", "5843", "768", "5849", "769", "5851", "770", "5857"],
|
||||
["771", "5861", "772", "5867", "773", "5869", "774", "5879", "775", "5881", "776", "5897", "777", "5903", "778", "5923", "779", "5927", "780", "5939"],
|
||||
["781", "5953", "782", "5981", "783", "5987", "784", "6007", "785", "6011", "786", "6029", "787", "6037", "788", "6043", "789", "6047", "790", "6053"],
|
||||
["791", "6067", "792", "6073", "793", "6079", "794", "6089", "795", "6091", "796", "6101", "797", "6113", "798", "6121", "799", "6131", "800", "6133"],
|
||||
["801", "6143", "802", "6151", "803", "6163", "804", "6173", "805", "6197", "806", "6199", "807", "6203", "808", "6211", "809", "6217", "810", "6221"],
|
||||
["811", "6229", "812", "6247", "813", "6257", "814", "6263", "815", "6269", "816", "6271", "817", "6277", "818", "6287", "819", "6299", "820", "6301"],
|
||||
["821", "6311", "822", "6317", "823", "6323", "824", "6329", "825", "6337", "826", "6343", "827", "6353", "828", "6359", "829", "6361", "830", "6367"],
|
||||
["831", "6373", "832", "6379", "833", "6389", "834", "6397", "835", "6421", "836", "6427", "837", "6449", "838", "6451", "839", "6469", "840", "6473"],
|
||||
["841", "6481", "842", "6491", "843", "6521", "844", "6529", "845", "6547", "846", "6551", "847", "6553", "848", "6563", "849", "6569", "850", "6571"],
|
||||
["851", "6577", "852", "6581", "853", "6599", "854", "6607", "855", "6619", "856", "6637", "857", "6653", "858", "6659", "859", "6661", "860", "6673"],
|
||||
["861", "6679", "862", "6689", "863", "6691", "864", "6701", "865", "6703", "866", "6709", "867", "6719", "868", "6733", "869", "6737", "870", "6761"],
|
||||
["871", "6763", "872", "6779", "873", "6781", "874", "6791", "875", "6793", "876", "6803", "877", "6823", "878", "6827", "879", "6829", "880", "6833"],
|
||||
["881", "6841", "882", "6857", "883", "6863", "884", "6869", "885", "6871", "886", "6883", "887", "6899", "888", "6907", "889", "6911", "890", "6917"],
|
||||
["891", "6947", "892", "6949", "893", "6959", "894", "6961", "895", "6967", "896", "6971", "897", "6977", "898", "6983", "899", "6991", "900", "6997"],
|
||||
["901", "7001", "902", "7013", "903", "7019", "904", "7027", "905", "7039", "906", "7043", "907", "7057", "908", "7069", "909", "7079", "910", "7103"],
|
||||
["911", "7109", "912", "7121", "913", "7127", "914", "7129", "915", "7151", "916", "7159", "917", "7177", "918", "7187", "919", "7193", "920", "7207"],
|
||||
["921", "7211", "922", "7213", "923", "7219", "924", "7229", "925", "7237", "926", "7243", "927", "7247", "928", "7253", "929", "7283", "930", "7297"],
|
||||
["931", "7307", "932", "7309", "933", "7321", "934", "7331", "935", "7333", "936", "7349", "937", "7351", "938", "7369", "939", "7393", "940", "7411"],
|
||||
["941", "7417", "942", "7433", "943", "7451", "944", "7457", "945", "7459", "946", "7477", "947", "7481", "948", "7487", "949", "7489", "950", "7499"],
|
||||
["951", "7507", "952", "7517", "953", "7523", "954", "7529", "955", "7537", "956", "7541", "957", "7547", "958", "7549", "959", "7559", "960", "7561"],
|
||||
["961", "7573", "962", "7577", "963", "7583", "964", "7589", "965", "7591", "966", "7603", "967", "7607", "968", "7621", "969", "7639", "970", "7643"],
|
||||
["971", "7649", "972", "7669", "973", "7673", "974", "7681", "975", "7687", "976", "7691", "977", "7699", "978", "7703", "979", "7717", "980", "7723"],
|
||||
["981", "7727", "982", "7741", "983", "7753", "984", "7757", "985", "7759", "986", "7789", "987", "7793", "988", "7817", "989", "7823", "990", "7829"],
|
||||
["991", "7841", "992", "7853", "993", "7867", "994", "7873", "995", "7877", "996", "7879", "997", "7883", "998", "7901", "999", "7907", "1000", "7919"],
|
||||
["1001", "7927", "1002", "7933", "1003", "7937", "1004", "7949", "1005", "7951", "1006", "7963", "1007", "7993", "1008", "8009", "1009", "8011", "1010", "8017"],
|
||||
["1011", "8039", "1012", "8053", "1013", "8059", "1014", "8069", "1015", "8081", "1016", "8087", "1017", "8089", "1018", "8093", "1019", "8101", "1020", "8111"],
|
||||
["1021", "8117", "1022", "8123", "1023", "8147", "1024", "8161", "1025", "8167", "1026", "8171", "1027", "8179", "1028", "8191", "1029", "8209", "1030", "8219"],
|
||||
["1031", "8221", "1032", "8231", "1033", "8233", "1034", "8237", "1035", "8243", "1036", "8263", "1037", "8269", "1038", "8273", "1039", "8287", "1040", "8291"],
|
||||
["1041", "8293", "1042", "8297", "1043", "8311", "1044", "8317", "1045", "8329", "1046", "8353", "1047", "8363", "1048", "8369", "1049", "8377", "1050", "8387"],
|
||||
["1051", "8389", "1052", "8419", "1053", "8423", "1054", "8429", "1055", "8431", "1056", "8443", "1057", "8447", "1058", "8461", "1059", "8467", "1060", "8501"],
|
||||
["1061", "8513", "1062", "8521", "1063", "8527", "1064", "8537", "1065", "8539", "1066", "8543", "1067", "8563", "1068", "8573", "1069", "8581", "1070", "8597"],
|
||||
["1071", "8599", "1072", "8609", "1073", "8623", "1074", "8627", "1075", "8629", "1076", "8641", "1077", "8647", "1078", "8663", "1079", "8669", "1080", "8677"],
|
||||
["1081", "8681", "1082", "8689", "1083", "8693", "1084", "8699", "1085", "8707", "1086", "8713", "1087", "8719", "1088", "8731", "1089", "8737", "1090", "8741"],
|
||||
["1091", "8747", "1092", "8753", "1093", "8761", "1094", "8779", "1095", "8783", "1096", "8803", "1097", "8807", "1098", "8819", "1099", "8821", "1100", "8831"],
|
||||
["1101", "8837", "1102", "8839", "1103", "8849", "1104", "8861", "1105", "8863", "1106", "8867", "1107", "8887", "1108", "8893", "1109", "8923", "1110", "8929"],
|
||||
["1111", "8933", "1112", "8941", "1113", "8951", "1114", "8963", "1115", "8969", "1116", "8971", "1117", "8999", "1118", "9001", "1119", "9007", "1120", "9011"],
|
||||
["1121", "9013", "1122", "9029", "1123", "9041", "1124", "9043", "1125", "9049", "1126", "9059", "1127", "9067", "1128", "9091", "1129", "9103", "1130", "9109"],
|
||||
["1131", "9127", "1132", "9133", "1133", "9137", "1134", "9151", "1135", "9157", "1136", "9161", "1137", "9173", "1138", "9181", "1139", "9187", "1140", "9199"],
|
||||
["1141", "9203", "1142", "9209", "1143", "9221", "1144", "9227", "1145", "9239", "1146", "9241", "1147", "9257", "1148", "9277", "1149", "9281", "1150", "9283"],
|
||||
["1151", "9293", "1152", "9311", "1153", "9319", "1154", "9323", "1155", "9337", "1156", "9341", "1157", "9343", "1158", "9349", "1159", "9371", "1160", "9377"],
|
||||
["1161", "9391", "1162", "9397", "1163", "9403", "1164", "9413", "1165", "9419", "1166", "9421", "1167", "9431", "1168", "9433", "1169", "9437", "1170", "9439"],
|
||||
["1171", "9461", "1172", "9463", "1173", "9467", "1174", "9473", "1175", "9479", "1176", "9491", "1177", "9497", "1178", "9511", "1179", "9521", "1180", "9533"],
|
||||
["1181", "9539", "1182", "9547", "1183", "9551", "1184", "9587", "1185", "9601", "1186", "9613", "1187", "9619", "1188", "9623", "1189", "9629", "1190", "9631"],
|
||||
["1191", "9643", "1192", "9649", "1193", "9661", "1194", "9677", "1195", "9679", "1196", "9689", "1197", "9697", "1198", "9719", "1199", "9721", "1200", "9733"],
|
||||
["1201", "9739", "1202", "9743", "1203", "9749", "1204", "9767", "1205", "9769", "1206", "9781", "1207", "9787", "1208", "9791", "1209", "9803", "1210", "9811"],
|
||||
["1211", "9817", "1212", "9829", "1213", "9833", "1214", "9839", "1215", "9851", "1216", "9857", "1217", "9859", "1218", "9871", "1219", "9883", "1220", "9887"],
|
||||
["1221", "9901", "1222", "9907", "1223", "9923", "1224", "9929", "1225", "9931", "1226", "9941", "1227", "9949", "1228", "9967", "1229", "9973", "1230", "10007"],
|
||||
["1231", "10009", "1232", "10037", "1233", "10039", "1234", "10061", "1235", "10067", "1236", "10069", "1237", "10079", "1238", "10091", "1239", "10093", "1240", "10099"],
|
||||
["1241", "10103", "1242", "10111", "1243", "10133", "1244", "10139", "1245", "10141", "1246", "10151", "1247", "10159", "1248", "10163", "1249", "10169", "1250", "10177"],
|
||||
["1251", "10181", "1252", "10193", "1253", "10211", "1254", "10223", "1255", "10243", "1256", "10247", "1257", "10253", "1258", "10259", "1259", "10267", "1260", "10271"],
|
||||
["1261", "10273", "1262", "10289", "1263", "10301", "1264", "10303", "1265", "10313", "1266", "10321", "1267", "10331", "1268", "10333", "1269", "10337", "1270", "10343"],
|
||||
["1271", "10357", "1272", "10369", "1273", "10391", "1274", "10399", "1275", "10427", "1276", "10429", "1277", "10433", "1278", "10453", "1279", "10457", "1280", "10459"],
|
||||
["1281", "10463", "1282", "10477", "1283", "10487", "1284", "10499", "1285", "10501", "1286", "10513", "1287", "10529", "1288", "10531", "1289", "10559", "1290", "10567"],
|
||||
["1291", "10589", "1292", "10597", "1293", "10601", "1294", "10607", "1295", "10613", "1296", "10627", "1297", "10631", "1298", "10639", "1299", "10651", "1300", "10657"],
|
||||
["1301", "10663", "1302", "10667", "1303", "10687", "1304", "10691", "1305", "10709", "1306", "10711", "1307", "10723", "1308", "10729", "1309", "10733", "1310", "10739"],
|
||||
["1311", "10753", "1312", "10771", "1313", "10781", "1314", "10789", "1315", "10799", "1316", "10831", "1317", "10837", "1318", "10847", "1319", "10853", "1320", "10859"],
|
||||
["1321", "10861", "1322", "10867", "1323", "10883", "1324", "10889", "1325", "10891", "1326", "10903", "1327", "10909", "1328", "10937", "1329", "10939", "1330", "10949"],
|
||||
["1331", "10957", "1332", "10973", "1333", "10979", "1334", "10987", "1335", "10993", "1336", "11003", "1337", "11027", "1338", "11047", "1339", "11057", "1340", "11059"],
|
||||
["1341", "11069", "1342", "11071", "1343", "11083", "1344", "11087", "1345", "11093", "1346", "11113", "1347", "11117", "1348", "11119", "1349", "11131", "1350", "11149"],
|
||||
["1351", "11159", "1352", "11161", "1353", "11171", "1354", "11173", "1355", "11177", "1356", "11197", "1357", "11213", "1358", "11239", "1359", "11243", "1360", "11251"],
|
||||
["1361", "11257", "1362", "11261", "1363", "11273", "1364", "11279", "1365", "11287", "1366", "11299", "1367", "11311", "1368", "11317", "1369", "11321", "1370", "11329"],
|
||||
["1371", "11351", "1372", "11353", "1373", "11369", "1374", "11383", "1375", "11393", "1376", "11399", "1377", "11411", "1378", "11423", "1379", "11437", "1380", "11443"],
|
||||
["1381", "11447", "1382", "11467", "1383", "11471", "1384", "11483", "1385", "11489", "1386", "11491", "1387", "11497", "1388", "11503", "1389", "11519", "1390", "11527"],
|
||||
["1391", "11549", "1392", "11551", "1393", "11579", "1394", "11587", "1395", "11593", "1396", "11597", "1397", "11617", "1398", "11621", "1399", "11633", "1400", "11657"],
|
||||
["1401", "11677", "1402", "11681", "1403", "11689", "1404", "11699", "1405", "11701", "1406", "11717", "1407", "11719", "1408", "11731", "1409", "11743", "1410", "11777"],
|
||||
["1411", "11779", "1412", "11783", "1413", "11789", "1414", "11801", "1415", "11807", "1416", "11813", "1417", "11821", "1418", "11827", "1419", "11831", "1420", "11833"],
|
||||
["1421", "11839", "1422", "11863", "1423", "11867", "1424", "11887", "1425", "11897", "1426", "11903", "1427", "11909", "1428", "11923", "1429", "11927", "1430", "11933"],
|
||||
["1431", "11939", "1432", "11941", "1433", "11953", "1434", "11959", "1435", "11969", "1436", "11971", "1437", "11981", "1438", "11987", "1439", "12007", "1440", "12011"],
|
||||
["1441", "12037", "1442", "12041", "1443", "12043", "1444", "12049", "1445", "12071", "1446", "12073", "1447", "12097", "1448", "12101", "1449", "12107", "1450", "12109"],
|
||||
["1451", "12113", "1452", "12119", "1453", "12143", "1454", "12149", "1455", "12157", "1456", "12161", "1457", "12163", "1458", "12197", "1459", "12203", "1460", "12211"],
|
||||
["1461", "12227", "1462", "12239", "1463", "12241", "1464", "12251", "1465", "12253", "1466", "12263", "1467", "12269", "1468", "12277", "1469", "12281", "1470", "12289"],
|
||||
["1471", "12301", "1472", "12323", "1473", "12329", "1474", "12343", "1475", "12347", "1476", "12373", "1477", "12377", "1478", "12379", "1479", "12391", "1480", "12401"],
|
||||
["1481", "12409", "1482", "12413", "1483", "12421", "1484", "12433", "1485", "12437", "1486", "12451", "1487", "12457", "1488", "12473", "1489", "12479", "1490", "12487"],
|
||||
["1491", "12491", "1492", "12497", "1493", "12503", "1494", "12511", "1495", "12517", "1496", "12527", "1497", "12539", "1498", "12541", "1499", "12547", "1500", "12553"],
|
||||
["1501", "12569", "1502", "12577", "1503", "12583", "1504", "12589", "1505", "12601", "1506", "12611", "1507", "12613", "1508", "12619", "1509", "12637", "1510", "12641"],
|
||||
["1511", "12647", "1512", "12653", "1513", "12659", "1514", "12671", "1515", "12689", "1516", "12697", "1517", "12703", "1518", "12713", "1519", "12721", "1520", "12739"],
|
||||
["1521", "12743", "1522", "12757", "1523", "12763", "1524", "12781", "1525", "12791", "1526", "12799", "1527", "12809", "1528", "12821", "1529", "12823", "1530", "12829"],
|
||||
["1531", "12841", "1532", "12853", "1533", "12889", "1534", "12893", "1535", "12899", "1536", "12907", "1537", "12911", "1538", "12917", "1539", "12919", "1540", "12923"],
|
||||
["1541", "12941", "1542", "12953", "1543", "12959", "1544", "12967", "1545", "12973", "1546", "12979", "1547", "12983", "1548", "13001", "1549", "13003", "1550", "13007"],
|
||||
["1551", "13009", "1552", "13033", "1553", "13037", "1554", "13043", "1555", "13049", "1556", "13063", "1557", "13093", "1558", "13099", "1559", "13103", "1560", "13109"],
|
||||
["1561", "13121", "1562", "13127", "1563", "13147", "1564", "13151", "1565", "13159", "1566", "13163", "1567", "13171", "1568", "13177", "1569", "13183", "1570", "13187"],
|
||||
["1571", "13217", "1572", "13219", "1573", "13229", "1574", "13241", "1575", "13249", "1576", "13259", "1577", "13267", "1578", "13291", "1579", "13297", "1580", "13309"],
|
||||
["1581", "13313", "1582", "13327", "1583", "13331", "1584", "13337", "1585", "13339", "1586", "13367", "1587", "13381", "1588", "13397", "1589", "13399", "1590", "13411"],
|
||||
["1591", "13417", "1592", "13421", "1593", "13441", "1594", "13451", "1595", "13457", "1596", "13463", "1597", "13469", "1598", "13477", "1599", "13487", "1600", "13499"],
|
||||
["1601", "13513", "1602", "13523", "1603", "13537", "1604", "13553", "1605", "13567", "1606", "13577", "1607", "13591", "1608", "13597", "1609", "13613", "1610", "13619"],
|
||||
["1611", "13627", "1612", "13633", "1613", "13649", "1614", "13669", "1615", "13679", "1616", "13681", "1617", "13687", "1618", "13691", "1619", "13693", "1620", "13697"],
|
||||
["1621", "13709", "1622", "13711", "1623", "13721", "1624", "13723", "1625", "13729", "1626", "13751", "1627", "13757", "1628", "13759", "1629", "13763", "1630", "13781"],
|
||||
["1631", "13789", "1632", "13799", "1633", "13807", "1634", "13829", "1635", "13831", "1636", "13841", "1637", "13859", "1638", "13873", "1639", "13877", "1640", "13879"],
|
||||
["1641", "13883", "1642", "13901", "1643", "13903", "1644", "13907", "1645", "13913", "1646", "13921", "1647", "13931", "1648", "13933", "1649", "13963", "1650", "13967"],
|
||||
["1651", "13997", "1652", "13999", "1653", "14009", "1654", "14011", "1655", "14029", "1656", "14033", "1657", "14051", "1658", "14057", "1659", "14071", "1660", "14081"],
|
||||
["1661", "14083", "1662", "14087", "1663", "14107", "1664", "14143", "1665", "14149", "1666", "14153", "1667", "14159", "1668", "14173", "1669", "14177", "1670", "14197"],
|
||||
["1671", "14207", "1672", "14221", "1673", "14243", "1674", "14249", "1675", "14251", "1676", "14281", "1677", "14293", "1678", "14303", "1679", "14321", "1680", "14323"],
|
||||
["1681", "14327", "1682", "14341", "1683", "14347", "1684", "14369", "1685", "14387", "1686", "14389", "1687", "14401", "1688", "14407", "1689", "14411", "1690", "14419"],
|
||||
["1691", "14423", "1692", "14431", "1693", "14437", "1694", "14447", "1695", "14449", "1696", "14461", "1697", "14479", "1698", "14489", "1699", "14503", "1700", "14519"],
|
||||
["1701", "14533", "1702", "14537", "1703", "14543", "1704", "14549", "1705", "14551", "1706", "14557", "1707", "14561", "1708", "14563", "1709", "14591", "1710", "14593"],
|
||||
["1711", "14621", "1712", "14627", "1713", "14629", "1714", "14633", "1715", "14639", "1716", "14653", "1717", "14657", "1718", "14669", "1719", "14683", "1720", "14699"],
|
||||
["1721", "14713", "1722", "14717", "1723", "14723", "1724", "14731", "1725", "14737", "1726", "14741", "1727", "14747", "1728", "14753", "1729", "14759", "1730", "14767"],
|
||||
["1731", "14771", "1732", "14779", "1733", "14783", "1734", "14797", "1735", "14813", "1736", "14821", "1737", "14827", "1738", "14831", "1739", "14843", "1740", "14851"],
|
||||
["1741", "14867", "1742", "14869", "1743", "14879", "1744", "14887", "1745", "14891", "1746", "14897", "1747", "14923", "1748", "14929", "1749", "14939", "1750", "14947"],
|
||||
["1751", "14951", "1752", "14957", "1753", "14969", "1754", "14983", "1755", "15013", "1756", "15017", "1757", "15031", "1758", "15053", "1759", "15061", "1760", "15073"],
|
||||
["1761", "15077", "1762", "15083", "1763", "15091", "1764", "15101", "1765", "15107", "1766", "15121", "1767", "15131", "1768", "15137", "1769", "15139", "1770", "15149"],
|
||||
["1771", "15161", "1772", "15173", "1773", "15187", "1774", "15193", "1775", "15199", "1776", "15217", "1777", "15227", "1778", "15233", "1779", "15241", "1780", "15259"],
|
||||
["1781", "15263", "1782", "15269", "1783", "15271", "1784", "15277", "1785", "15287", "1786", "15289", "1787", "15299", "1788", "15307", "1789", "15313", "1790", "15319"],
|
||||
["1791", "15329", "1792", "15331", "1793", "15349", "1794", "15359", "1795", "15361", "1796", "15373", "1797", "15377", "1798", "15383", "1799", "15391", "1800", "15401"],
|
||||
["1801", "15413", "1802", "15427", "1803", "15439", "1804", "15443", "1805", "15451", "1806", "15461", "1807", "15467", "1808", "15473", "1809", "15493", "1810", "15497"],
|
||||
["1811", "15511", "1812", "15527", "1813", "15541", "1814", "15551", "1815", "15559", "1816", "15569", "1817", "15581", "1818", "15583", "1819", "15601", "1820", "15607"],
|
||||
["1821", "15619", "1822", "15629", "1823", "15641", "1824", "15643", "1825", "15647", "1826", "15649", "1827", "15661", "1828", "15667", "1829", "15671", "1830", "15679"],
|
||||
["1831", "15683", "1832", "15727", "1833", "15731", "1834", "15733", "1835", "15737", "1836", "15739", "1837", "15749", "1838", "15761", "1839", "15767", "1840", "15773"],
|
||||
["1841", "15787", "1842", "15791", "1843", "15797", "1844", "15803", "1845", "15809", "1846", "15817", "1847", "15823", "1848", "15859", "1849", "15877", "1850", "15881"],
|
||||
["1851", "15887", "1852", "15889", "1853", "15901", "1854", "15907", "1855", "15913", "1856", "15919", "1857", "15923", "1858", "15937", "1859", "15959", "1860", "15971"],
|
||||
["1861", "15973", "1862", "15991", "1863", "16001", "1864", "16007", "1865", "16033", "1866", "16057", "1867", "16061", "1868", "16063", "1869", "16067", "1870", "16069"],
|
||||
["1871", "16073", "1872", "16087", "1873", "16091", "1874", "16097", "1875", "16103", "1876", "16111", "1877", "16127", "1878", "16139", "1879", "16141", "1880", "16183"],
|
||||
["1881", "16187", "1882", "16189", "1883", "16193", "1884", "16217", "1885", "16223", "1886", "16229", "1887", "16231", "1888", "16249", "1889", "16253", "1890", "16267"],
|
||||
["1891", "16273", "1892", "16301", "1893", "16319", "1894", "16333", "1895", "16339", "1896", "16349", "1897", "16361", "1898", "16363", "1899", "16369", "1900", "16381"],
|
||||
["1901", "16411", "1902", "16417", "1903", "16421", "1904", "16427", "1905", "16433", "1906", "16447", "1907", "16451", "1908", "16453", "1909", "16477", "1910", "16481"],
|
||||
["1911", "16487", "1912", "16493", "1913", "16519", "1914", "16529", "1915", "16547", "1916", "16553", "1917", "16561", "1918", "16567", "1919", "16573", "1920", "16603"],
|
||||
["1921", "16607", "1922", "16619", "1923", "16631", "1924", "16633", "1925", "16649", "1926", "16651", "1927", "16657", "1928", "16661", "1929", "16673", "1930", "16691"],
|
||||
["1931", "16693", "1932", "16699", "1933", "16703", "1934", "16729", "1935", "16741", "1936", "16747", "1937", "16759", "1938", "16763", "1939", "16787", "1940", "16811"],
|
||||
["1941", "16823", "1942", "16829", "1943", "16831", "1944", "16843", "1945", "16871", "1946", "16879", "1947", "16883", "1948", "16889", "1949", "16901", "1950", "16903"],
|
||||
["1951", "16921", "1952", "16927", "1953", "16931", "1954", "16937", "1955", "16943", "1956", "16963", "1957", "16979", "1958", "16981", "1959", "16987", "1960", "16993"],
|
||||
["1961", "17011", "1962", "17021", "1963", "17027", "1964", "17029", "1965", "17033", "1966", "17041", "1967", "17047", "1968", "17053", "1969", "17077", "1970", "17093"],
|
||||
["1971", "17099", "1972", "17107", "1973", "17117", "1974", "17123", "1975", "17137", "1976", "17159", "1977", "17167", "1978", "17183", "1979", "17189", "1980", "17191"],
|
||||
["1981", "17203", "1982", "17207", "1983", "17209", "1984", "17231", "1985", "17239", "1986", "17257", "1987", "17291", "1988", "17293", "1989", "17299", "1990", "17317"],
|
||||
["1991", "17321", "1992", "17327", "1993", "17333", "1994", "17341", "1995", "17351", "1996", "17359", "1997", "17377", "1998", "17383", "1999", "17387", "2000", "17389"],
|
||||
["2001", "17393", "2002", "17401", "2003", "17417", "2004", "17419", "2005", "17431", "2006", "17443", "2007", "17449", "2008", "17467", "2009", "17471", "2010", "17477"],
|
||||
["2011", "17483", "2012", "17489", "2013", "17491", "2014", "17497", "2015", "17509", "2016", "17519", "2017", "17539", "2018", "17551", "2019", "17569", "2020", "17573"],
|
||||
["2021", "17579", "2022", "17581", "2023", "17597", "2024", "17599", "2025", "17609", "2026", "17623", "2027", "17627", "2028", "17657", "2029", "17659", "2030", "17669"],
|
||||
["2031", "17681", "2032", "17683", "2033", "17707", "2034", "17713", "2035", "17729", "2036", "17737", "2037", "17747", "2038", "17749", "2039", "17761", "2040", "17783"],
|
||||
["2041", "17789", "2042", "17791", "2043", "17807", "2044", "17827", "2045", "17837", "2046", "17839", "2047", "17851", "2048", "17863", "2049", "17881", "2050", "17891"],
|
||||
["2051", "17903", "2052", "17909", "2053", "17911", "2054", "17921", "2055", "17923", "2056", "17929", "2057", "17939", "2058", "17957", "2059", "17959", "2060", "17971"],
|
||||
["2061", "17977", "2062", "17981", "2063", "17987", "2064", "17989", "2065", "18013", "2066", "18041", "2067", "18043", "2068", "18047", "2069", "18049", "2070", "18059"],
|
||||
["2071", "18061", "2072", "18077", "2073", "18089", "2074", "18097", "2075", "18119", "2076", "18121", "2077", "18127", "2078", "18131", "2079", "18133", "2080", "18143"],
|
||||
["2081", "18149", "2082", "18169", "2083", "18181", "2084", "18191", "2085", "18199", "2086", "18211", "2087", "18217", "2088", "18223", "2089", "18229", "2090", "18233"],
|
||||
["2091", "18251", "2092", "18253", "2093", "18257", "2094", "18269", "2095", "18287", "2096", "18289", "2097", "18301", "2098", "18307", "2099", "18311", "2100", "18313"],
|
||||
["2101", "18329", "2102", "18341", "2103", "18353", "2104", "18367", "2105", "18371", "2106", "18379", "2107", "18397", "2108", "18401", "2109", "18413", "2110", "18427"],
|
||||
["2111", "18433", "2112", "18439", "2113", "18443", "2114", "18451", "2115", "18457", "2116", "18461", "2117", "18481", "2118", "18493", "2119", "18503", "2120", "18517"],
|
||||
["2121", "18521", "2122", "18523", "2123", "18539", "2124", "18541", "2125", "18553", "2126", "18583", "2127", "18587", "2128", "18593", "2129", "18617", "2130", "18637"],
|
||||
["2131", "18661", "2132", "18671", "2133", "18679", "2134", "18691", "2135", "18701", "2136", "18713", "2137", "18719", "2138", "18731", "2139", "18743", "2140", "18749"],
|
||||
["2141", "18757", "2142", "18773", "2143", "18787", "2144", "18793", "2145", "18797", "2146", "18803", "2147", "18839", "2148", "18859", "2149", "18869", "2150", "18899"],
|
||||
["2151", "189"]]}}]}
|
||||
==============================================================================
|
||||
|
||||
================================================================================
|
||||
MERGE RESULT: ✅ SUCCESS
|
||||
================================================================================
|
||||
Final result length: 36944 chars
|
||||
Final result (COMPLETE):
|
||||
================================================================================
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"type": "table",
|
||||
"content": {
|
||||
"headers": ["Nr.1", "Primzahl1", "Nr.2", "Primzahl2", "Nr.3", "Primzahl3", "Nr.4", "Primzahl4", "Nr.5", "Primzahl5", "Nr.6", "Primzahl6", "Nr.7", "Primzahl7", "Nr.8", "Primzahl8", "Nr.9", "Primzahl9", "Nr.10", "Primzahl10"],
|
||||
"rows": [
|
||||
["1", "2", "2", "3", "3", "5", "4", "7", "5", "11", "6", "13", "7", "17", "8", "19", "9", "23", "10", "29"],
|
||||
["11", "31", "12", "37", "13", "41", "14", "43", "15", "47", "16", "53", "17", "59", "18", "61", "19", "67", "20", "71"],
|
||||
["21", "73", "22", "79", "23", "83", "24", "89", "25", "97", "26", "101", "27", "103", "28", "107", "29", "109", "30", "113"],
|
||||
["31", "127", "32", "131", "33", "137", "34", "139", "35", "149", "36", "151", "37", "157", "38", "163", "39", "167", "40", "173"],
|
||||
["41", "179", "42", "181", "43", "191", "44", "193", "45", "197", "46", "199", "47", "211", "48", "223", "49", "227", "50", "229"],
|
||||
["51", "233", "52", "239", "53", "241", "54", "251", "55", "257", "56", "263", "57", "269", "58", "271", "59", "277", "60", "281"],
|
||||
["61", "283", "62", "293", "63", "307", "64", "311", "65", "313", "66", "317", "67", "331", "68", "337", "69", "347", "70", "349"],
|
||||
["71", "353", "72", "359", "73", "367", "74", "373", "75", "379", "76", "383", "77", "389", "78", "397", "79", "401", "80", "409"],
|
||||
["81", "419", "82", "421", "83", "431", "84", "433", "85", "439", "86", "443", "87", "449", "88", "457", "89", "461", "90", "463"],
|
||||
["91", "467", "92", "479", "93", "487", "94", "491", "95", "499", "96", "503", "97", "509", "98", "521", "99", "523", "100", "541"],
|
||||
["101", "547", "102", "557", "103", "563", "104", "569", "105", "571", "106", "577", "107", "587", "108", "593", "109", "599", "110", "601"],
|
||||
["111", "607", "112", "613", "113", "617", "114", "619", "115", "631", "116", "641", "117", "643", "118", "647", "119", "653", "120", "659"],
|
||||
["121", "661", "122", "673", "123", "677", "124", "683", "125", "691", "126", "701", "127", "709", "128", "719", "129", "727", "130", "733"],
|
||||
["131", "739", "132", "743", "133", "751", "134", "757", "135", "761", "136", "769", "137", "773", "138", "787", "139", "797", "140", "809"],
|
||||
["141", "811", "142", "821", "143", "823", "144", "827", "145", "829", "146", "839", "147", "853", "148", "857", "149", "859", "150", "863"],
|
||||
["151", "877", "152", "881", "153", "883", "154", "887", "155", "907", "156", "911", "157", "919", "158", "929", "159", "937", "160", "941"],
|
||||
["161", "947", "162", "953", "163", "967", "164", "971", "165", "977", "166", "983", "167", "991", "168", "997", "169", "1009", "170", "1013"],
|
||||
["171", "1019", "172", "1021", "173", "1031", "174", "1033", "175", "1039", "176", "1049", "177", "1051", "178", "1061", "179", "1063", "180", "1069"],
|
||||
["181", "1087", "182", "1091", "183", "1093", "184", "1097", "185", "1103", "186", "1109", "187", "1117", "188", "1123", "189", "1129", "190", "1151"],
|
||||
["191", "1153", "192", "1163", "193", "1171", "194", "1181", "195", "1187", "196", "1193", "197", "1201", "198", "1213", "199", "1217", "200", "1223"],
|
||||
["201", "1229", "202", "1231", "203", "1237", "204", "1249", "205", "1259", "206", "1277", "207", "1279", "208", "1283", "209", "1289", "210", "1291"],
|
||||
["211", "1297", "212", "1301", "213", "1303", "214", "1307", "215", "1319", "216", "1321", "217", "1327", "218", "1361", "219", "1367", "220", "1373"],
|
||||
["221", "1381", "222", "1399", "223", "1409", "224", "1423", "225", "1427", "226", "1429", "227", "1433", "228", "1439", "229", "1447", "230", "1451"],
|
||||
["231", "1453", "232", "1459", "233", "1471", "234", "1481", "235", "1483", "236", "1487", "237", "1489", "238", "1493", "239", "1499", "240", "1511"],
|
||||
["241", "1523", "242", "1531", "243", "1543", "244", "1549", "245", "1553", "246", "1559", "247", "1567", "248", "1571", "249", "1579", "250", "1583"],
|
||||
["251", "1597", "252", "1601", "253", "1607", "254", "1609", "255", "1613", "256", "1619", "257", "1621", "258", "1627", "259", "1637", "260", "1657"],
|
||||
["261", "1663", "262", "1667", "263", "1669", "264", "1693", "265", "1697", "266", "1699", "267", "1709", "268", "1721", "269", "1723", "270", "1733"],
|
||||
["271", "1741", "272", "1747", "273", "1753", "274", "1759", "275", "1777", "276", "1783", "277", "1787", "278", "1789", "279", "1801", "280", "1811"],
|
||||
["281", "1823", "282", "1831", "283", "1847", "284", "1861", "285", "1867", "286", "1871", "287", "1873", "288", "1877", "289", "1879", "290", "1889"],
|
||||
["291", "1901", "292", "1907", "293", "1913", "294", "1931", "295", "1933", "296", "1949", "297", "1951", "298", "1973", "299", "1979", "300", "1987"],
|
||||
["301", "1993", "302", "1997", "303", "1999", "304", "2003", "305", "2011", "306", "2017", "307", "2027", "308", "2029", "309", "2039", "310", "2053"],
|
||||
["311", "2063", "312", "2069", "313", "2081", "314", "2083", "315", "2087", "316", "2089", "317", "2099", "318", "2111", "319", "2113", "320", "2129"],
|
||||
["321", "2131", "322", "2137", "323", "2141", "324", "2143", "325", "2153", "326", "2161", "327", "2179", "328", "2203", "329", "2207", "330", "2213"],
|
||||
["331", "2221", "332", "2237", "333", "2239", "334", "2243", "335", "2251", "336", "2267", "337", "2269", "338", "2273", "339", "2281", "340", "2287"],
|
||||
["341", "2293", "342", "2297", "343", "2309", "344", "2311", "345", "2333", "346", "2339", "347", "2341", "348", "2347", "349", "2351", "350", "2357"],
|
||||
["351", "2371", "352", "2377", "353", "2381", "354", "2383", "355", "2389", "356", "2393", "357", "2399", "358", "2411", "359", "2417", "360", "2423"],
|
||||
["361", "2437", "362", "2441", "363", "2447", "364", "2459", "365", "2467", "366", "2473", "367", "2477", "368", "2503", "369", "2521", "370", "2531"],
|
||||
["371", "2539", "372", "2543", "373", "2549", "374", "2551", "375", "2557", "376", "2579", "377", "2591", "378", "2593", "379", "2609", "380", "2617"],
|
||||
["381", "2621", "382", "2633", "383", "2647", "384", "2657", "385", "2659", "386", "2663", "387", "2671", "388", "2677", "389", "2683", "390", "2687"],
|
||||
["391", "2689", "392", "2693", "393", "2699", "394", "2707", "395", "2711", "396", "2713", "397", "2719", "398", "2729", "399", "2731", "400", "2741"],
|
||||
["401", "2749", "402", "2753", "403", "2767", "404", "2777", "405", "2789", "406", "2791", "407", "2797", "408", "2801", "409", "2803", "410", "2819"],
|
||||
["411", "2833", "412", "2837", "413", "2843", "414", "2851", "415", "2857", "416", "2861", "417", "2879", "418", "2887", "419", "2897", "420", "2903"],
|
||||
["421", "2909", "422", "2917", "423", "2927", "424", "2939", "425", "2953", "426", "2957", "427", "2963", "428", "2969", "429", "2971", "430", "2999"],
|
||||
["431", "3001", "432", "3011", "433", "3019", "434", "3023", "435", "3037", "436", "3041", "437", "3049", "438", "3061", "439", "3067", "440", "3079"],
|
||||
["441", "3083", "442", "3089", "443", "3109", "444", "3119", "445", "3121", "446", "3137", "447", "3163", "448", "3167", "449", "3169", "450", "3181"],
|
||||
["451", "3187", "452", "3191", "453", "3203", "454", "3209", "455", "3217", "456", "3221", "457", "3229", "458", "3251", "459", "3253", "460", "3257"],
|
||||
["461", "3259", "462", "3271", "463", "3299", "464", "3301", "465", "3307", "466", "3313", "467", "3319", "468", "3323", "469", "3329", "470", "3331"],
|
||||
["471", "3343", "472", "3347", "473", "3359", "474", "3361", "475", "3371", "476", "3373", "477", "3389", "478", "3391", "479", "3407", "480", "3413"],
|
||||
["481", "3433", "482", "3449", "483", "3457", "484", "3461", "485", "3463", "486", "3467", "487", "3469", "488", "3491", "489", "3499", "490", "3511"],
|
||||
["491", "3517", "492", "3527", "493", "3529", "494", "3533", "495", "3539", "496", "3541", "497", "3547", "498", "3557", "499", "3559", "500", "3571"],
|
||||
["501", "3581", "502", "3583", "503", "3593", "504", "3607", "505", "3613", "506", "3617", "507", "3623", "508", "3631", "509", "3637", "510", "3643"],
|
||||
["511", "3659", "512", "3671", "513", "3673", "514", "3677", "515", "3691", "516", "3697", "517", "3701", "518", "3709", "519", "3719", "520", "3727"],
|
||||
["521", "3733", "522", "3739", "523", "3761", "524", "3767", "525", "3769", "526", "3779", "527", "3793", "528", "3797", "529", "3803", "530", "3821"],
|
||||
["531", "3823", "532", "3833", "533", "3847", "534", "3851", "535", "3853", "536", "3863", "537", "3877", "538", "3881", "539", "3889", "540", "3907"],
|
||||
["541", "3911", "542", "3917", "543", "3919", "544", "3923", "545", "3929", "546", "3931", "547", "3943", "548", "3947", "549", "3967", "550", "3989"],
|
||||
["551", "4001", "552", "4003", "553", "4007", "554", "4013", "555", "4019", "556", "4021", "557", "4027", "558", "4049", "559", "4051", "560", "4057"],
|
||||
["561", "4073", "562", "4079", "563", "4091", "564", "4093", "565", "4099", "566", "4111", "567", "4127", "568", "4129", "569", "4133", "570", "4139"],
|
||||
["571", "4153", "572", "4157", "573", "4159", "574", "4177", "575", "4201", "576", "4211", "577", "4217", "578", "4219", "579", "4229", "580", "4231"],
|
||||
["581", "4241", "582", "4243", "583", "4253", "584", "4259", "585", "4261", "586", "4271", "587", "4273", "588", "4283", "589", "4289", "590", "4297"],
|
||||
["591", "4327", "592", "4337", "593", "4339", "594", "4349", "595", "4357", "596", "4363", "597", "4373", "598", "4391", "599", "4397", "600", "4409"],
|
||||
["601", "4421", "602", "4423", "603", "4441", "604", "4447", "605", "4451", "606", "4457", "607", "4463", "608", "4481", "609", "4483", "610", "4493"],
|
||||
["611", "4507", "612", "4513", "613", "4517", "614", "4519", "615", "4523", "616", "4547", "617", "4549", "618", "4561", "619", "4567", "620", "4583"],
|
||||
["621", "4591", "622", "4597", "623", "4603", "624", "4621", "625", "4637", "626", "4639", "627", "4643", "628", "4649", "629", "4651", "630", "4657"],
|
||||
["631", "4663", "632", "4673", "633", "4679", "634", "4691", "635", "4703", "636", "4721", "637", "4723", "638", "4729", "639", "4733", "640", "4751"],
|
||||
["641", "4759", "642", "4783", "643", "4787", "644", "4789", "645", "4793", "646", "4799", "647", "4801", "648", "4813", "649", "4817", "650", "4831"],
|
||||
["651", "4861", "652", "4871", "653", "4877", "654", "4889", "655", "4903", "656", "4909", "657", "4919", "658", "4931", "659", "4933", "660", "4937"],
|
||||
["661", "4943", "662", "4951", "663", "4957", "664", "4967", "665", "4969", "666", "4973", "667", "4987", "668", "4993", "669", "4999", "670", "5003"],
|
||||
["671", "5009", "672", "5011", "673", "5021", "674", "5023", "675", "5039", "676", "5051", "677", "5059", "678", "5077", "679", "5081", "680", "5087"],
|
||||
["681", "5099", "682", "5101", "683", "5107", "684", "5113", "685", "5119", "686", "5147", "687", "5153", "688", "5167", "689", "5171", "690", "5179"],
|
||||
["691", "5189", "692", "5197", "693", "5209", "694", "5227", "695", "5231", "696", "5233", "697", "5237", "698", "5261", "699", "5273", "700", "5279"],
|
||||
["701", "5281", "702", "5297", "703", "5303", "704", "5309", "705", "5323", "706", "5333", "707", "5347", "708", "5351", "709", "5381", "710", "5387"],
|
||||
["711", "5393", "712", "5399", "713", "5407", "714", "5413", "715", "5417", "716", "5419", "717", "5431", "718", "5437", "719", "5441", "720", "5443"],
|
||||
["721", "5449", "722", "5471", "723", "5477", "724", "5479", "725", "5483", "726", "5501", "727", "5503", "728", "5507", "729", "5519", "730", "5521"],
|
||||
["731", "5527", "732", "5531", "733", "5557", "734", "5563", "735", "5569", "736", "5573", "737", "5581", "738", "5591", "739", "5623", "740", "5639"],
|
||||
["741", "5641", "742", "5647", "743", "5651", "744", "5653", "745", "5657", "746", "5659", "747", "5669", "748", "5683", "749", "5689", "750", "5693"],
|
||||
["751", "5701", "752", "5711", "753", "5717", "754", "5737", "755", "5741", "756", "5743", "757", "5749", "758", "5779", "759", "5783", "760", "5791"],
|
||||
["761", "5801", "762", "5807", "763", "5813", "764", "5821", "765", "5827", "766", "5839", "767", "5843", "768", "5849", "769", "5851", "770", "5857"],
|
||||
["771", "5861", "772", "5867", "773", "5869", "774", "5879", "775", "5881", "776", "5897", "777", "5903", "778", "5923", "779", "5927", "780", "5939"],
|
||||
["781", "5953", "782", "5981", "783", "5987", "784", "6007", "785", "6011", "786", "6029", "787", "6037", "788", "6043", "789", "6047", "790", "6053"],
|
||||
["791", "6067", "792", "6073", "793", "6079", "794", "6089", "795", "6091", "796", "6101", "797", "6113", "798", "6121", "799", "6131", "800", "6133"],
|
||||
["801", "6143", "802", "6151", "803", "6163", "804", "6173", "805", "6197", "806", "6199", "807", "6203", "808", "6211", "809", "6217", "810", "6221"],
|
||||
["811", "6229", "812", "6247", "813", "6257", "814", "6263", "815", "6269", "816", "6271", "817", "6277", "818", "6287", "819", "6299", "820", "6301"],
|
||||
["821", "6311", "822", "6317", "823", "6323", "824", "6329", "825", "6337", "826", "6343", "827", "6353", "828", "6359", "829", "6361", "830", "6367"],
|
||||
["831", "6373", "832", "6379", "833", "6389", "834", "6397", "835", "6421", "836", "6427", "837", "6449", "838", "6451", "839", "6469", "840", "6473"],
|
||||
["841", "6481", "842", "6491", "843", "6521", "844", "6529", "845", "6547", "846", "6551", "847", "6553", "848", "6563", "849", "6569", "850", "6571"],
|
||||
["851", "6577", "852", "6581", "853", "6599", "854", "6607", "855", "6619", "856", "6637", "857", "6653", "858", "6659", "859", "6661", "860", "6673"],
|
||||
["861", "6679", "862", "6689", "863", "6691", "864", "6701", "865", "6703", "866", "6709", "867", "6719", "868", "6733", "869", "6737", "870", "6761"],
|
||||
["871", "6763", "872", "6779", "873", "6781", "874", "6791", "875", "6793", "876", "6803", "877", "6823", "878", "6827", "879", "6829", "880", "6833"],
|
||||
["881", "6841", "882", "6857", "883", "6863", "884", "6869", "885", "6871", "886", "6883", "887", "6899", "888", "6907", "889", "6911", "890", "6917"],
|
||||
["891", "6947", "892", "6949", "893", "6959", "894", "6961", "895", "6967", "896", "6971", "897", "6977", "898", "6983", "899", "6991", "900", "6997"],
|
||||
["901", "7001", "902", "7013", "903", "7019", "904", "7027", "905", "7039", "906", "7043", "907", "7057", "908", "7069", "909", "7079", "910", "7103"],
|
||||
["911", "7109", "912", "7121", "913", "7127", "914", "7129", "915", "7151", "916", "7159", "917", "7177", "918", "7187", "919", "7193", "920", "7207"],
|
||||
["921", "7211", "922", "7213", "923", "7219", "924", "7229", "925", "7237", "926", "7243", "927", "7247", "928", "7253", "929", "7283", "930", "7297"],
|
||||
["931", "7307", "932", "7309", "933", "7321", "934", "7331", "935", "7333", "936", "7349", "937", "7351", "938", "7369", "939", "7393", "940", "7411"],
|
||||
["941", "7417", "942", "7433", "943", "7451", "944", "7457", "945", "7459", "946", "7477", "947", "7481", "948", "7487", "949", "7489", "950", "7499"],
|
||||
["951", "7507", "952", "7517", "953", "7523", "954", "7529", "955", "7537", "956", "7541", "957", "7547", "958", "7549", "959", "7559", "960", "7561"],
|
||||
["961", "7573", "962", "7577", "963", "7583", "964", "7589", "965", "7591", "966", "7603", "967", "7607", "968", "7621", "969", "7639", "970", "7643"],
|
||||
["971", "7649", "972", "7669", "973", "7673", "974", "7681", "975", "7687", "976", "7691", "977", "7699", "978", "7703", "979", "7717", "980", "7723"],
|
||||
["981", "7727", "982", "7741", "983", "7753", "984", "7757", "985", "7759", "986", "7789", "987", "7793", "988", "7817", "989", "7823", "990", "7829"],
|
||||
["991", "7841", "992", "7853", "993", "7867", "994", "7873", "995", "7877", "996", "7879", "997", "7883", "998", "7901", "999", "7907", "1000", "7919"],
|
||||
["1001", "7927", "1002", "7933", "1003", "7937", "1004", "7949", "1005", "7951", "1006", "7963", "1007", "7993", "1008", "8009", "1009", "8011", "1010", "8017"],
|
||||
["1011", "8039", "1012", "8053", "1013", "8059", "1014", "8069", "1015", "8081", "1016", "8087", "1017", "8089", "1018", "8093", "1019", "8101", "1020", "8111"],
|
||||
["1021", "8117", "1022", "8123", "1023", "8147", "1024", "8161", "1025", "8167", "1026", "8171", "1027", "8179", "1028", "8191", "1029", "8209", "1030", "8219"],
|
||||
["1031", "8221", "1032", "8231", "1033", "8233", "1034", "8237", "1035", "8243", "1036", "8263", "1037", "8269", "1038", "8273", "1039", "8287", "1040", "8291"],
|
||||
["1041", "8293", "1042", "8297", "1043", "8311", "1044", "8317", "1045", "8329", "1046", "8353", "1047", "8363", "1048", "8369", "1049", "8377", "1050", "8387"],
|
||||
["1051", "8389", "1052", "8419", "1053", "8423", "1054", "8429", "1055", "8431", "1056", "8443", "1057", "8447", "1058", "8461", "1059", "8467", "1060", "8501"],
|
||||
["1061", "8513", "1062", "8521", "1063", "8527", "1064", "8537", "1065", "8539", "1066", "8543", "1067", "8563", "1068", "8573", "1069", "8581", "1070", "8597"],
|
||||
["1071", "8599", "1072", "8609", "1073", "8623", "1074", "8627", "1075", "8629", "1076", "8641", "1077", "8647", "1078", "8663", "1079", "8669", "1080", "8677"],
|
||||
["1081", "8681", "1082", "8689", "1083", "8693", "1084", "8699", "1085", "8707", "1086", "8713", "1087", "8719", "1088", "8731", "1089", "8737", "1090", "8741"],
|
||||
["1091", "8747", "1092", "8753", "1093", "8761", "1094", "8779", "1095", "8783", "1096", "8803", "1097", "8807", "1098", "8819", "1099", "8821", "1100", "8831"],
|
||||
["1101", "8837", "1102", "8839", "1103", "8849", "1104", "8861", "1105", "8863", "1106", "8867", "1107", "8887", "1108", "8893", "1109", "8923", "1110", "8929"],
|
||||
["1111", "8933", "1112", "8941", "1113", "8951", "1114", "8963", "1115", "8969", "1116", "8971", "1117", "8999", "1118", "9001", "1119", "9007", "1120", "9011"],
|
||||
["1121", "9013", "1122", "9029", "1123", "9041", "1124", "9043", "1125", "9049", "1126", "9059", "1127", "9067", "1128", "9091", "1129", "9103", "1130", "9109"],
|
||||
["1131", "9127", "1132", "9133", "1133", "9137", "1134", "9151", "1135", "9157", "1136", "9161", "1137", "9173", "1138", "9181", "1139", "9187", "1140", "9199"],
|
||||
["1141", "9203", "1142", "9209", "1143", "9221", "1144", "9227", "1145", "9239", "1146", "9241", "1147", "9257", "1148", "9277", "1149", "9281", "1150", "9283"],
|
||||
["1151", "9293", "1152", "9311", "1153", "9319", "1154", "9323", "1155", "9337", "1156", "9341", "1157", "9343", "1158", "9349", "1159", "9371", "1160", "9377"],
|
||||
["1161", "9391", "1162", "9397", "1163", "9403", "1164", "9413", "1165", "9419", "1166", "9421", "1167", "9431", "1168", "9433", "1169", "9437", "1170", "9439"],
|
||||
["1171", "9461", "1172", "9463", "1173", "9467", "1174", "9473", "1175", "9479", "1176", "9491", "1177", "9497", "1178", "9511", "1179", "9521", "1180", "9533"],
|
||||
["1181", "9539", "1182", "9547", "1183", "9551", "1184", "9587", "1185", "9601", "1186", "9613", "1187", "9619", "1188", "9623", "1189", "9629", "1190", "9631"],
|
||||
["1191", "9643", "1192", "9649", "1193", "9661", "1194", "9677", "1195", "9679", "1196", "9689", "1197", "9697", "1198", "9719", "1199", "9721", "1200", "9733"],
|
||||
["1201", "9739", "1202", "9743", "1203", "9749", "1204", "9767", "1205", "9769", "1206", "9781", "1207", "9787", "1208", "9791", "1209", "9803", "1210", "9811"],
|
||||
["1211", "9817", "1212", "9829", "1213", "9833", "1214", "9839", "1215", "9851", "1216", "9857", "1217", "9859", "1218", "9871", "1219", "9883", "1220", "9887"],
|
||||
["1221", "9901", "1222", "9907", "1223", "9923", "1224", "9929", "1225", "9931", "1226", "9941", "1227", "9949", "1228", "9967", "1229", "9973", "1230", "10007"],
|
||||
["1231", "10009", "1232", "10037", "1233", "10039", "1234", "10061", "1235", "10067", "1236", "10069", "1237", "10079", "1238", "10091", "1239", "10093", "1240", "10099"],
|
||||
["1241", "10103", "1242", "10111", "1243", "10133", "1244", "10139", "1245", "10141", "1246", "10151", "1247", "10159", "1248", "10163", "1249", "10169", "1250", "10177"],
|
||||
["1251", "10181", "1252", "10193", "1253", "10211", "1254", "10223", "1255", "10243", "1256", "10247", "1257", "10253", "1258", "10259", "1259", "10267", "1260", "10271"],
|
||||
["1261", "10273", "1262", "10289", "1263", "10301", "1264", "10303", "1265", "10313", "1266", "10321", "1267", "10331", "1268", "10333", "1269", "10337", "1270", "10343"],
|
||||
["1271", "10357", "1272", "10369", "1273", "10391", "1274", "10399", "1275", "10427", "1276", "10429", "1277", "10433", "1278", "10453", "1279", "10457", "1280", "10459"],
|
||||
["1281", "10463", "1282", "10477", "1283", "10487", "1284", "10499", "1285", "10501", "1286", "10513", "1287", "10529", "1288", "10531", "1289", "10559", "1290", "10567"],
|
||||
["1291", "10589", "1292", "10597", "1293", "10601", "1294", "10607", "1295", "10613", "1296", "10627", "1297", "10631", "1298", "10639", "1299", "10651", "1300", "10657"],
|
||||
["1301", "10663", "1302", "10667", "1303", "10687", "1304", "10691", "1305", "10709", "1306", "10711", "1307", "10723", "1308", "10729", "1309", "10733", "1310", "10739"],
|
||||
["1311", "10753", "1312", "10771", "1313", "10781", "1314", "10789", "1315", "10799", "1316", "10831", "1317", "10837", "1318", "10847", "1319", "10853", "1320", "10859"],
|
||||
["1321", "10861", "1322", "10867", "1323", "10883", "1324", "10889", "1325", "10891", "1326", "10903", "1327", "10909", "1328", "10937", "1329", "10939", "1330", "10949"],
|
||||
["1331", "10957", "1332", "10973", "1333", "10979", "1334", "10987", "1335", "10993", "1336", "11003", "1337", "11027", "1338", "11047", "1339", "11057", "1340", "11059"],
|
||||
["1341", "11069", "1342", "11071", "1343", "11083", "1344", "11087", "1345", "11093", "1346", "11113", "1347", "11117", "1348", "11119", "1349", "11131", "1350", "11149"],
|
||||
["1351", "11159", "1352", "11161", "1353", "11171", "1354", "11173", "1355", "11177", "1356", "11197", "1357", "11213", "1358", "11239", "1359", "11243", "1360", "11251"],
|
||||
["1361", "11257", "1362", "11261", "1363", "11273", "1364", "11279", "1365", "11287", "1366", "11299", "1367", "11311", "1368", "11317", "1369", "11321", "1370", "11329"],
|
||||
["1371", "11351", "1372", "11353", "1373", "11369", "1374", "11383", "1375", "11393", "1376", "11399", "1377", "11411", "1378", "11423", "1379", "11437", "1380", "11443"],
|
||||
["1381", "11447", "1382", "11467", "1383", "11471", "1384", "11483", "1385", "11489", "1386", "11491", "1387", "11497", "1388", "11503", "1389", "11519", "1390", "11527"],
|
||||
["1391", "11549", "1392", "11551", "1393", "11579", "1394", "11587", "1395", "11593", "1396", "11597", "1397", "11617", "1398", "11621", "1399", "11633", "1400", "11657"],
|
||||
["1401", "11677", "1402", "11681", "1403", "11689", "1404", "11699", "1405", "11701", "1406", "11717", "1407", "11719", "1408", "11731", "1409", "11743", "1410", "11777"],
|
||||
["1411", "11779", "1412", "11783", "1413", "11789", "1414", "11801", "1415", "11807", "1416", "11813", "1417", "11821", "1418", "11827", "1419", "11831", "1420", "11833"],
|
||||
["1421", "11839", "1422", "11863", "1423", "11867", "1424", "11887", "1425", "11897", "1426", "11903", "1427", "11909", "1428", "11923", "1429", "11927", "1430", "11933"],
|
||||
["1431", "11939", "1432", "11941", "1433", "11953", "1434", "11959", "1435", "11969", "1436", "11971", "1437", "11981", "1438", "11987", "1439", "12007", "1440", "12011"],
|
||||
["1441", "12037", "1442", "12041", "1443", "12043", "1444", "12049", "1445", "12071", "1446", "12073", "1447", "12097", "1448", "12101", "1449", "12107", "1450", "12109"],
|
||||
["1451", "12113", "1452", "12119", "1453", "12143", "1454", "12149", "1455", "12157", "1456", "12161", "1457", "12163", "1458", "12197", "1459", "12203", "1460", "12211"],
|
||||
["1461", "12227", "1462", "12239", "1463", "12241", "1464", "12251", "1465", "12253", "1466", "12263", "1467", "12269", "1468", "12277", "1469", "12281", "1470", "12289"],
|
||||
["1471", "12301", "1472", "12323", "1473", "12329", "1474", "12343", "1475", "12347", "1476", "12373", "1477", "12377", "1478", "12379", "1479", "12391", "1480", "12401"],
|
||||
["1481", "12409", "1482", "12413", "1483", "12421", "1484", "12433", "1485", "12437", "1486", "12451", "1487", "12457", "1488", "12473", "1489", "12479", "1490", "12487"],
|
||||
["1491", "12491", "1492", "12497", "1493", "12503", "1494", "12511", "1495", "12517", "1496", "12527", "1497", "12539", "1498", "12541", "1499", "12547", "1500", "12553"],
|
||||
["1501", "12569", "1502", "12577", "1503", "12583", "1504", "12589", "1505", "12601", "1506", "12611", "1507", "12613", "1508", "12619", "1509", "12637", "1510", "12641"],
|
||||
["1511", "12647", "1512", "12653", "1513", "12659", "1514", "12671", "1515", "12689", "1516", "12697", "1517", "12703", "1518", "12713", "1519", "12721", "1520", "12739"],
|
||||
["1521", "12743", "1522", "12757", "1523", "12763", "1524", "12781", "1525", "12791", "1526", "12799", "1527", "12809", "1528", "12821", "1529", "12823", "1530", "12829"],
|
||||
["1531", "12841", "1532", "12853", "1533", "12889", "1534", "12893", "1535", "12899", "1536", "12907", "1537", "12911", "1538", "12917", "1539", "12919", "1540", "12923"],
|
||||
["1541", "12941", "1542", "12953", "1543", "12959", "1544", "12967", "1545", "12973", "1546", "12979", "1547", "12983", "1548", "13001", "1549", "13003", "1550", "13007"],
|
||||
["1551", "13009", "1552", "13033", "1553", "13037", "1554", "13043", "1555", "13049", "1556", "13063", "1557", "13093", "1558", "13099", "1559", "13103", "1560", "13109"],
|
||||
["1561", "13121", "1562", "13127", "1563", "13147", "1564", "13151", "1565", "13159", "1566", "13163", "1567", "13171", "1568", "13177", "1569", "13183", "1570", "13187"],
|
||||
["1571", "13217", "1572", "13219", "1573", "13229", "1574", "13241", "1575", "13249", "1576", "13259", "1577", "13267", "1578", "13291", "1579", "13297", "1580", "13309"],
|
||||
["1581", "13313", "1582", "13327", "1583", "13331", "1584", "13337", "1585", "13339", "1586", "13367", "1587", "13381", "1588", "13397", "1589", "13399", "1590", "13411"],
|
||||
["1591", "13417", "1592", "13421", "1593", "13441", "1594", "13451", "1595", "13457", "1596", "13463", "1597", "13469", "1598", "13477", "1599", "13487", "1600", "13499"],
|
||||
["1601", "13513", "1602", "13523", "1603", "13537", "1604", "13553", "1605", "13567", "1606", "13577", "1607", "13591", "1608", "13597", "1609", "13613", "1610", "13619"],
|
||||
["1611", "13627", "1612", "13633", "1613", "13649", "1614", "13669", "1615", "13679", "1616", "13681", "1617", "13687", "1618", "13691", "1619", "13693", "1620", "13697"],
|
||||
["1621", "13709", "1622", "13711", "1623", "13721", "1624", "13723", "1625", "13729", "1626", "13751", "1627", "13757", "1628", "13759", "1629", "13763", "1630", "13781"],
|
||||
["1631", "13789", "1632", "13799", "1633", "13807", "1634", "13829", "1635", "13831", "1636", "13841", "1637", "13859", "1638", "13873", "1639", "13877", "1640", "13879"],
|
||||
["1641", "13883", "1642", "13901", "1643", "13903", "1644", "13907", "1645", "13913", "1646", "13921", "1647", "13931", "1648", "13933", "1649", "13963", "1650", "13967"],
|
||||
["1651", "13997", "1652", "13999", "1653", "14009", "1654", "14011", "1655", "14029", "1656", "14033", "1657", "14051", "1658", "14057", "1659", "14071", "1660", "14081"],
|
||||
["1661", "14083", "1662", "14087", "1663", "14107", "1664", "14143", "1665", "14149", "1666", "14153", "1667", "14159", "1668", "14173", "1669", "14177", "1670", "14197"],
|
||||
["1671", "14207", "1672", "14221", "1673", "14243", "1674", "14249", "1675", "14251", "1676", "14281", "1677", "14293", "1678", "14303", "1679", "14321", "1680", "14323"],
|
||||
["1681", "14327", "1682", "14341", "1683", "14347", "1684", "14369", "1685", "14387", "1686", "14389", "1687", "14401", "1688", "14407", "1689", "14411", "1690", "14419"],
|
||||
["1691", "14423", "1692", "14431", "1693", "14437", "1694", "14447", "1695", "14449", "1696", "14461", "1697", "14479", "1698", "14489", "1699", "14503", "1700", "14519"],
|
||||
["1701", "14533", "1702", "14537", "1703", "14543", "1704", "14549", "1705", "14551", "1706", "14557", "1707", "14561", "1708", "14563", "1709", "14591", "1710", "14593"],
|
||||
["1711", "14621", "1712", "14627", "1713", "14629", "1714", "14633", "1715", "14639", "1716", "14653", "1717", "14657", "1718", "14669", "1719", "14683", "1720", "14699"],
|
||||
["1721", "14713", "1722", "14717", "1723", "14723", "1724", "14731", "1725", "14737", "1726", "14741", "1727", "14747", "1728", "14753", "1729", "14759", "1730", "14767"],
|
||||
["1731", "14771", "1732", "14779", "1733", "14783", "1734", "14797", "1735", "14813", "1736", "14821", "1737", "14827", "1738", "14831", "1739", "14843", "1740", "14851"],
|
||||
["1741", "14867", "1742", "14869", "1743", "14879", "1744", "14887", "1745", "14891", "1746", "14897", "1747", "14923", "1748", "14929", "1749", "14939", "1750", "14947"],
|
||||
["1751", "14951", "1752", "14957", "1753", "14969", "1754", "14983", "1755", "15013", "1756", "15017", "1757", "15031", "1758", "15053", "1759", "15061", "1760", "15073"],
|
||||
["1761", "15077", "1762", "15083", "1763", "15091", "1764", "15101", "1765", "15107", "1766", "15121", "1767", "15131", "1768", "15137", "1769", "15139", "1770", "15149"],
|
||||
["1771", "15161", "1772", "15173", "1773", "15187", "1774", "15193", "1775", "15199", "1776", "15217", "1777", "15227", "1778", "15233", "1779", "15241", "1780", "15259"],
|
||||
["1781", "15263", "1782", "15269", "1783", "15271", "1784", "15277", "1785", "15287", "1786", "15289", "1787", "15299", "1788", "15307", "1789", "15313", "1790", "15319"],
|
||||
["1791", "15329", "1792", "15331", "1793", "15349", "1794", "15359", "1795", "15361", "1796", "15373", "1797", "15377", "1798", "15383", "1799", "15391", "1800", "15401"],
|
||||
["1801", "15413", "1802", "15427", "1803", "15439", "1804", "15443", "1805", "15451", "1806", "15461", "1807", "15467", "1808", "15473", "1809", "15493", "1810", "15497"],
|
||||
["1811", "15511", "1812", "15527", "1813", "15541", "1814", "15551", "1815", "15559", "1816", "15569", "1817", "15581", "1818", "15583", "1819", "15601", "1820", "15607"],
|
||||
["1821", "15619", "1822", "15629", "1823", "15641", "1824", "15643", "1825", "15647", "1826", "15649", "1827", "15661", "1828", "15667", "1829", "15671", "1830", "15679"],
|
||||
["1831", "15683", "1832", "15727", "1833", "15731", "1834", "15733", "1835", "15737", "1836", "15739", "1837", "15749", "1838", "15761", "1839", "15767", "1840", "15773"],
|
||||
["1841", "15787", "1842", "15791", "1843", "15797", "1844", "15803", "1845", "15809", "1846", "15817", "1847", "15823", "1848", "15859", "1849", "15877", "1850", "15881"],
|
||||
["1851", "15887", "1852", "15889", "1853", "15901", "1854", "15907", "1855", "15913", "1856", "15919", "1857", "15923", "1858", "15937", "1859", "15959", "1860", "15971"],
|
||||
["1861", "15973", "1862", "15991", "1863", "16001", "1864", "16007", "1865", "16033", "1866", "16057", "1867", "16061", "1868", "16063", "1869", "16067", "1870", "16069"],
|
||||
["1871", "16073", "1872", "16087", "1873", "16091", "1874", "16097", "1875", "16103", "1876", "16111", "1877", "16127", "1878", "16139", "1879", "16141", "1880", "16183"],
|
||||
["1881", "16187", "1882", "16189", "1883", "16193", "1884", "16217", "1885", "16223", "1886", "16229", "1887", "16231", "1888", "16249", "1889", "16253", "1890", "16267"],
|
||||
["1891", "16273", "1892", "16301", "1893", "16319", "1894", "16333", "1895", "16339", "1896", "16349", "1897", "16361", "1898", "16363", "1899", "16369", "1900", "16381"],
|
||||
["1901", "16411", "1902", "16417", "1903", "16421", "1904", "16427", "1905", "16433", "1906", "16447", "1907", "16451", "1908", "16453", "1909", "16477", "1910", "16481"],
|
||||
["1911", "16487", "1912", "16493", "1913", "16519", "1914", "16529", "1915", "16547", "1916", "16553", "1917", "16561", "1918", "16567", "1919", "16573", "1920", "16603"],
|
||||
["1921", "16607", "1922", "16619", "1923", "16631", "1924", "16633", "1925", "16649", "1926", "16651", "1927", "16657", "1928", "16661", "1929", "16673", "1930", "16691"],
|
||||
["1931", "16693", "1932", "16699", "1933", "16703", "1934", "16729", "1935", "16741", "1936", "16747", "1937", "16759", "1938", "16763", "1939", "16787", "1940", "16811"],
|
||||
["1941", "16823", "1942", "16829", "1943", "16831", "1944", "16843", "1945", "16871", "1946", "16879", "1947", "16883", "1948", "16889", "1949", "16901", "1950", "16903"],
|
||||
["1951", "16921", "1952", "16927", "1953", "16931", "1954", "16937", "1955", "16943", "1956", "16963", "1957", "16979", "1958", "16981", "1959", "16987", "1960", "16993"],
|
||||
["1961", "17011", "1962", "17021", "1963", "17027", "1964", "17029", "1965", "17033", "1966", "17041", "1967", "17047", "1968", "17053", "1969", "17077", "1970", "17093"],
|
||||
["1971", "17099", "1972", "17107", "1973", "17117", "1974", "17123", "1975", "17137", "1976", "17159", "1977", "17167", "1978", "17183", "1979", "17189", "1980", "17191"],
|
||||
["1981", "17203", "1982", "17207", "1983", "17209", "1984", "17231", "1985", "17239", "1986", "17257", "1987", "17291", "1988", "17293", "1989", "17299", "1990", "17317"],
|
||||
["1991", "17321", "1992", "17327", "1993", "17333", "1994", "17341", "1995", "17351", "1996", "17359", "1997", "17377", "1998", "17383", "1999", "17387", "2000", "17389"],
|
||||
["2001", "17393", "2002", "17401", "2003", "17417", "2004", "17419", "2005", "17431", "2006", "17443", "2007", "17449", "2008", "17467", "2009", "17471", "2010", "17477"],
|
||||
["2011", "17483", "2012", "17489", "2013", "17491", "2014", "17497", "2015", "17509", "2016", "17519", "2017", "17539", "2018", "17551", "2019", "17569", "2020", "17573"],
|
||||
["2021", "17579", "2022", "17581", "2023", "17597", "2024", "17599", "2025", "17609", "2026", "17623", "2027", "17627", "2028", "17657", "2029", "17659", "2030", "17669"],
|
||||
["2031", "17681", "2032", "17683", "2033", "17707", "2034", "17713", "2035", "17729", "2036", "17737", "2037", "17747", "2038", "17749", "2039", "17761", "2040", "17783"],
|
||||
["2041", "17789", "2042", "17791", "2043", "17807", "2044", "17827", "2045", "17837", "2046", "17839", "2047", "17851", "2048", "17863", "2049", "17881", "2050", "17891"],
|
||||
["2051", "17903", "2052", "17909", "2053", "17911", "2054", "17921", "2055", "17923", "2056", "17929", "2057", "17939", "2058", "17957", "2059", "17959", "2060", "17971"],
|
||||
["2061", "17977", "2062", "17981", "2063", "17987", "2064", "17989", "2065", "18013", "2066", "18041", "2067", "18043", "2068", "18047", "2069", "18049", "2070", "18059"],
|
||||
["2071", "18061", "2072", "18077", "2073", "18089", "2074", "18097", "2075", "18119", "2076", "18121", "2077", "18127", "2078", "18131", "2079", "18133", "2080", "18143"],
|
||||
["2081", "18149", "2082", "18169", "2083", "18181", "2084", "18191", "2085", "18199", "2086", "18211", "2087", "18217", "2088", "18223", "2089", "18229", "2090", "18233"],
|
||||
["2091", "18251", "2092", "18253", "2093", "18257", "2094", "18269", "2095", "18287", "2096", "18289", "2097", "18301", "2098", "18307", "2099", "18311", "2100", "18313"],
|
||||
["2101", "18329", "2102", "18341", "2103", "18353", "2104", "18367", "2105", "18371", "2106", "18379", "2107", "18397", "2108", "18401", "2109", "18413", "2110", "18427"],
|
||||
["2111", "18433", "2112", "18439", "2113", "18443", "2114", "18451", "2115", "18457", "2116", "18461", "2117", "18481", "2118", "18493", "2119", "18503", "2120", "18517"],
|
||||
["2121", "18521", "2122", "18523", "2123", "18539", "2124", "18541", "2125", "18553", "2126", "18583", "2127", "18587", "2128", "18593", "2129", "18617", "2130", "18637"],
|
||||
["2131", "18661", "2132", "18671", "2133", "18679", "2134", "18691", "2135", "18701", "2136", "18713", "2137", "18719", "2138", "18731", "2139", "18743", "2140", "18749"],
|
||||
["2141", "18757", "2142", "18773", "2143", "18787", "2144", "18793", "2145", "18797", "2146", "18803", "2147", "18839", "2148", "18859", "2149", "18869", "2150", "18899"],
|
||||
["2151", "189"]]}}]}
|
||||
================================================================================
|
||||
|
|
@ -86,8 +86,6 @@ class AiCallLooper:
|
|||
iteration = 0
|
||||
allSections = [] # Accumulate all sections across iterations
|
||||
lastRawResponse = None # Store last raw JSON response for continuation
|
||||
documentMetadata = None # Store document metadata (title, filename) from first iteration
|
||||
accumulationState = None # Track accumulation state for string accumulation
|
||||
accumulatedDirectJson = [] # Accumulate JSON strings for direct return use cases (chapter_structure, code_structure)
|
||||
|
||||
# Get parent operation ID for iteration operations (parentId should be operationId, not log entry ID)
|
||||
|
|
@ -113,28 +111,17 @@ class AiCallLooper:
|
|||
# This ensures continuation prompts are built even when JSON is so broken that no sections can be extracted
|
||||
if (len(allSections) > 0 or lastRawResponse) and promptBuilder and promptArgs:
|
||||
# This is a continuation - build continuation context with raw JSON and rebuild prompt
|
||||
continuationContext = buildContinuationContext(allSections, lastRawResponse)
|
||||
continuationContext = buildContinuationContext(allSections, lastRawResponse, useCaseId)
|
||||
if not lastRawResponse:
|
||||
logger.warning(f"Iteration {iteration}: No previous response available for continuation!")
|
||||
|
||||
# For section_content, pass all promptArgs (it uses buildSectionPromptWithContinuation which needs all args)
|
||||
# For other use cases (chapter_structure, code_structure), filter to only accepted parameters
|
||||
if useCaseId == "section_content":
|
||||
# Pass all promptArgs plus continuationContext for section_content
|
||||
iterationPrompt = await promptBuilder(**promptArgs, continuationContext=continuationContext)
|
||||
else:
|
||||
# Filter promptArgs to only include parameters that buildGenerationPrompt accepts
|
||||
# buildGenerationPrompt accepts: outputFormat, userPrompt, title, extracted_content, continuationContext, services
|
||||
filteredPromptArgs = {
|
||||
k: v for k, v in promptArgs.items()
|
||||
if k in ['outputFormat', 'userPrompt', 'title', 'extracted_content', 'services']
|
||||
}
|
||||
# Always include services if available
|
||||
if not filteredPromptArgs.get('services') and hasattr(self, 'services'):
|
||||
filteredPromptArgs['services'] = self.services
|
||||
|
||||
# Rebuild prompt with continuation context using the provided prompt builder
|
||||
iterationPrompt = await promptBuilder(**filteredPromptArgs, continuationContext=continuationContext)
|
||||
# Unified prompt builder call: All prompt builders accept continuationContext and **kwargs
|
||||
# Each builder extracts only the parameters it needs from kwargs
|
||||
# This ensures consistent architecture across all use cases
|
||||
if not promptArgs.get('services') and hasattr(self, 'services'):
|
||||
promptArgs['services'] = self.services
|
||||
|
||||
iterationPrompt = await promptBuilder(continuationContext=continuationContext, **promptArgs)
|
||||
else:
|
||||
# First iteration - use original prompt
|
||||
iterationPrompt = prompt
|
||||
|
|
@ -251,11 +238,16 @@ class AiCallLooper:
|
|||
pass
|
||||
|
||||
# Handle use cases that return JSON directly (no section extraction needed)
|
||||
directReturnUseCases = ["section_content", "chapter_structure", "code_structure", "code_content", "image_batch"]
|
||||
directReturnUseCases = ["section_content", "chapter_structure", "code_structure", "code_content"]
|
||||
if useCaseId in directReturnUseCases:
|
||||
# For chapter_structure, code_structure, and section_content, check completeness and support looping
|
||||
loopingUseCases = ["chapter_structure", "code_structure", "section_content"]
|
||||
# For chapter_structure, code_structure, section_content, and code_content, check completeness and support looping
|
||||
loopingUseCases = ["chapter_structure", "code_structure", "section_content", "code_content"]
|
||||
if useCaseId in loopingUseCases:
|
||||
# CRITICAL: Check if JSON string is incomplete BEFORE parsing
|
||||
# If JSON is truncated, it will be closed for parsing, making it appear complete
|
||||
# So we need to check the original string, not the parsed JSON
|
||||
isStringIncomplete = self._isJsonStringIncomplete(extractedJsonForUseCase if extractedJsonForUseCase else result)
|
||||
|
||||
# If parsing failed (e.g., invalid JSON with comments or truncated JSON), continue looping to get valid JSON
|
||||
if not parsedJsonForUseCase:
|
||||
logger.info(f"Iteration {iteration}: Use case '{useCaseId}' - JSON parsing failed (likely incomplete/truncated), continuing iteration to complete")
|
||||
|
|
@ -268,8 +260,12 @@ class AiCallLooper:
|
|||
self.services.chat.progressLogFinish(iterationOperationId, True)
|
||||
continue
|
||||
|
||||
# Check completeness if we have parsed JSON
|
||||
isComplete = JsonResponseHandler.isJsonComplete(parsedJsonForUseCase)
|
||||
# Check completeness: Use string-based check if available, otherwise fall back to parsed JSON check
|
||||
if isStringIncomplete:
|
||||
isComplete = False
|
||||
else:
|
||||
# Check completeness if we have parsed JSON
|
||||
isComplete = JsonResponseHandler.isJsonComplete(parsedJsonForUseCase)
|
||||
|
||||
if not isComplete:
|
||||
logger.warning(f"Iteration {iteration}: Use case '{useCaseId}' - JSON is incomplete, continuing for continuation")
|
||||
|
|
@ -294,22 +290,45 @@ class AiCallLooper:
|
|||
|
||||
# Step 1: Merge all JSON strings using existing overlap detection
|
||||
mergedJsonString = allJsonStrings[0] if allJsonStrings else ""
|
||||
hasOverlap = True # Track if any overlap was found
|
||||
for jsonStr in allJsonStrings[1:]:
|
||||
mergedJsonString = JsonResponseHandler.mergeJsonStringsWithOverlap(mergedJsonString, jsonStr)
|
||||
mergedJsonString, hasOverlapInMerge = JsonResponseHandler.mergeJsonStringsWithOverlap(mergedJsonString, jsonStr)
|
||||
# If no overlap found in any merge, stop iterations
|
||||
if not hasOverlapInMerge:
|
||||
hasOverlap = False
|
||||
logger.info(f"Iteration {iteration}: No overlap found during merge - stopping iterations and closing JSON")
|
||||
break
|
||||
|
||||
# Step 2: Try to parse the merged string
|
||||
extracted = extractJsonString(mergedJsonString)
|
||||
parsed, parseErr, _ = tryParseJson(extracted)
|
||||
|
||||
if parseErr is None and parsed:
|
||||
# Parsing succeeded - normalize and use
|
||||
normalized = self._normalizeJsonStructure(parsed, useCaseId)
|
||||
parsedJsonForUseCase = normalized
|
||||
result = json.dumps(normalized, indent=2, ensure_ascii=False)
|
||||
# If no overlap was found, mark as complete and use closed JSON
|
||||
if not hasOverlap:
|
||||
isComplete = True
|
||||
# JSON is already closed by mergeJsonStringsWithOverlap when no overlap
|
||||
# Use the merged (closed) JSON string directly
|
||||
result = mergedJsonString
|
||||
# Try to parse it to get parsedJsonForUseCase
|
||||
try:
|
||||
extracted = extractJsonString(mergedJsonString)
|
||||
parsed, parseErr, _ = tryParseJson(extracted)
|
||||
if parseErr is None and parsed:
|
||||
normalized = self._normalizeJsonStructure(parsed, useCaseId)
|
||||
parsedJsonForUseCase = normalized
|
||||
result = json.dumps(normalized, indent=2, ensure_ascii=False)
|
||||
except Exception:
|
||||
pass # Use string result if parsing fails
|
||||
else:
|
||||
# Parsing failed - try to extract partial data for section_content
|
||||
if useCaseId == "section_content":
|
||||
# Use existing mergeDeepStructures approach: parse what we can from each part
|
||||
# Overlap found - continue with normal processing
|
||||
# Step 2: Try to parse the merged string
|
||||
extracted = extractJsonString(mergedJsonString)
|
||||
parsed, parseErr, _ = tryParseJson(extracted)
|
||||
|
||||
if parseErr is None and parsed:
|
||||
# Parsing succeeded - normalize and use
|
||||
normalized = self._normalizeJsonStructure(parsed, useCaseId)
|
||||
parsedJsonForUseCase = normalized
|
||||
result = json.dumps(normalized, indent=2, ensure_ascii=False)
|
||||
else:
|
||||
# Parsing failed - try to extract partial data using Deep-Structure-Merging
|
||||
# This fallback works for all use cases: parse what we can from each part
|
||||
allParsed = []
|
||||
for jsonStr in allJsonStrings:
|
||||
extracted = extractJsonString(jsonStr)
|
||||
|
|
@ -319,12 +338,12 @@ class AiCallLooper:
|
|||
allParsed.append(normalized)
|
||||
|
||||
if allParsed:
|
||||
# Use existing mergeDeepStructures for intelligent merging
|
||||
# Use mergeDeepStructures for intelligent merging across all use cases
|
||||
if len(allParsed) > 1:
|
||||
mergedJsonObj = allParsed[0]
|
||||
for nextObj in allParsed[1:]:
|
||||
mergedJsonObj = JsonResponseHandler.mergeDeepStructures(
|
||||
mergedJsonObj, nextObj, iteration, f"section_content.merge"
|
||||
mergedJsonObj, nextObj, iteration, f"{useCaseId}.merge"
|
||||
)
|
||||
else:
|
||||
mergedJsonObj = allParsed[0]
|
||||
|
|
@ -334,18 +353,37 @@ class AiCallLooper:
|
|||
else:
|
||||
# All parsing failed - use string merge result
|
||||
result = mergedJsonString
|
||||
else:
|
||||
# Not section_content - use string merge result
|
||||
result = mergedJsonString
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed data-based merge, falling back to string merging: {e}")
|
||||
# Fallback to string merging
|
||||
mergedJsonString = accumulatedDirectJson[0] if accumulatedDirectJson else result
|
||||
hasOverlap = True # Track if any overlap was found
|
||||
for prevJson in accumulatedDirectJson[1:]:
|
||||
mergedJsonString = JsonResponseHandler.mergeJsonStringsWithOverlap(mergedJsonString, prevJson)
|
||||
mergedJsonString = JsonResponseHandler.mergeJsonStringsWithOverlap(mergedJsonString, result)
|
||||
mergedJsonString, hasOverlapInMerge = JsonResponseHandler.mergeJsonStringsWithOverlap(mergedJsonString, prevJson)
|
||||
if not hasOverlapInMerge:
|
||||
hasOverlap = False
|
||||
logger.info(f"Iteration {iteration}: No overlap found during fallback merge - stopping iterations")
|
||||
break
|
||||
if hasOverlap:
|
||||
mergedJsonString, hasOverlapInMerge = JsonResponseHandler.mergeJsonStringsWithOverlap(mergedJsonString, result)
|
||||
if not hasOverlapInMerge:
|
||||
hasOverlap = False
|
||||
logger.info(f"Iteration {iteration}: No overlap found in final fallback merge - stopping iterations")
|
||||
result = mergedJsonString
|
||||
|
||||
# If no overlap was found, mark as complete and use closed JSON
|
||||
if not hasOverlap:
|
||||
isComplete = True
|
||||
# JSON is already closed by mergeJsonStringsWithOverlap when no overlap
|
||||
# Try to parse it to get parsedJsonForUseCase
|
||||
try:
|
||||
extractedMerged = extractJsonString(result)
|
||||
parsedMerged, parseError, _ = tryParseJson(extractedMerged)
|
||||
if parseError is None and parsedMerged:
|
||||
parsedJsonForUseCase = parsedMerged
|
||||
except Exception:
|
||||
pass # Use string result if parsing fails
|
||||
|
||||
# Try to parse the string-merged result
|
||||
try:
|
||||
extractedMerged = extractJsonString(result)
|
||||
|
|
@ -375,233 +413,6 @@ class AiCallLooper:
|
|||
self.services.utils.writeDebugFile(final_json, f"{debugPrefix}_final_result")
|
||||
|
||||
return final_json
|
||||
|
||||
# Extract sections from response (handles both valid and broken JSON)
|
||||
# Only for document generation (JSON responses)
|
||||
# CRITICAL: Pass allSections and accumulationState to enable string accumulation
|
||||
extractedSections, wasJsonComplete, parsedResult, accumulationState = self.responseParser.extractSectionsFromResponse(
|
||||
result, iteration, debugPrefix, allSections, accumulationState
|
||||
)
|
||||
|
||||
# CRITICAL: Merge sections BEFORE KPI validation
|
||||
# This ensures sections are preserved even if KPI validation fails
|
||||
if extractedSections:
|
||||
allSections = JsonResponseHandler.mergeSectionsIntelligently(allSections, extractedSections, iteration)
|
||||
|
||||
# Define KPIs if we just entered accumulation mode (iteration 1, incomplete JSON)
|
||||
if accumulationState and accumulationState.isAccumulationMode and iteration == 1 and not accumulationState.kpis:
|
||||
logger.info(f"Iteration {iteration}: Defining KPIs for accumulation tracking")
|
||||
continuationContext = buildContinuationContext(allSections, result)
|
||||
# Pass raw response string from first iteration for KPI definition
|
||||
kpiDefinitions = await self._defineKpisFromPrompt(
|
||||
userPrompt or prompt,
|
||||
result, # Pass raw JSON string from first iteration
|
||||
continuationContext,
|
||||
debugPrefix
|
||||
)
|
||||
# Initialize KPIs with currentValue = 0
|
||||
accumulationState.kpis = [{**kpi, "currentValue": 0} for kpi in kpiDefinitions]
|
||||
logger.info(f"Defined {len(accumulationState.kpis)} KPIs: {[kpi.get('id') for kpi in accumulationState.kpis]}")
|
||||
|
||||
# Extract and validate KPIs (if in accumulation mode with KPIs defined)
|
||||
if accumulationState and accumulationState.isAccumulationMode and accumulationState.kpis:
|
||||
# For KPI extraction, prefer accumulated JSON string over repaired JSON
|
||||
# because repairBrokenJson may lose data (e.g., empty rows array when JSON is incomplete)
|
||||
updatedKpis = []
|
||||
|
||||
# First try to extract from parsedResult (repaired JSON)
|
||||
if parsedResult:
|
||||
try:
|
||||
updatedKpis = JsonResponseHandler.extractKpiValuesFromJson(
|
||||
parsedResult,
|
||||
accumulationState.kpis
|
||||
)
|
||||
# Check if we got meaningful values (non-zero)
|
||||
hasValidValues = any(kpi.get("currentValue", 0) > 0 for kpi in updatedKpis)
|
||||
if not hasValidValues and accumulationState.accumulatedJsonString:
|
||||
# Repaired JSON has empty values, try accumulated string
|
||||
logger.debug("Repaired JSON has empty KPI values, trying accumulated JSON string")
|
||||
updatedKpis = JsonResponseHandler.extractKpiValuesFromIncompleteJson(
|
||||
accumulationState.accumulatedJsonString,
|
||||
accumulationState.kpis
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(f"Error extracting KPIs from parsedResult: {e}")
|
||||
updatedKpis = []
|
||||
|
||||
# If no parsedResult or extraction failed, try accumulated string
|
||||
if not updatedKpis and accumulationState.accumulatedJsonString:
|
||||
try:
|
||||
updatedKpis = JsonResponseHandler.extractKpiValuesFromIncompleteJson(
|
||||
accumulationState.accumulatedJsonString,
|
||||
accumulationState.kpis
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug(f"Error extracting KPIs from accumulated JSON string: {e}")
|
||||
updatedKpis = []
|
||||
|
||||
if updatedKpis:
|
||||
shouldProceed, reason = JsonResponseHandler.validateKpiProgression(
|
||||
accumulationState,
|
||||
updatedKpis
|
||||
)
|
||||
|
||||
if not shouldProceed:
|
||||
logger.warning(f"Iteration {iteration}: KPI validation failed: {reason}")
|
||||
if iterationOperationId:
|
||||
self.services.chat.progressLogFinish(iterationOperationId, False)
|
||||
if operationId:
|
||||
self.services.chat.progressLogUpdate(operationId, 0.9, f"KPI validation failed: {reason} ({iteration} iterations)")
|
||||
break
|
||||
|
||||
# Update KPIs in accumulation state
|
||||
accumulationState.kpis = updatedKpis
|
||||
logger.info(f"Iteration {iteration}: KPIs updated: {[(kpi.get('id'), kpi.get('currentValue')) for kpi in updatedKpis]}")
|
||||
|
||||
# Check if all KPIs completed
|
||||
allCompleted = True
|
||||
for kpi in updatedKpis:
|
||||
targetValue = kpi.get("targetValue", 0)
|
||||
currentValue = kpi.get("currentValue", 0)
|
||||
if currentValue < targetValue:
|
||||
allCompleted = False
|
||||
break
|
||||
|
||||
if allCompleted:
|
||||
logger.info(f"Iteration {iteration}: All KPIs completed, finishing accumulation")
|
||||
wasJsonComplete = True # Mark as complete to exit loop
|
||||
|
||||
# CRITICAL: Handle JSON fragments (continuation content)
|
||||
# Fragment merging happens inside extractSectionsFromResponse
|
||||
# If merge fails (returns wasJsonComplete=True), stop iterations and complete JSON
|
||||
if not extractedSections and allSections:
|
||||
if wasJsonComplete:
|
||||
# Merge failed - stop iterations, complete JSON with available data
|
||||
logger.error(f"Iteration {iteration}: ❌ MERGE FAILED - Stopping iterations, completing JSON with available data")
|
||||
if iterationOperationId:
|
||||
self.services.chat.progressLogFinish(iterationOperationId, False)
|
||||
if operationId:
|
||||
self.services.chat.progressLogUpdate(operationId, 0.9, f"Merge failed, completing JSON ({iteration} iterations)")
|
||||
break
|
||||
|
||||
# Fragment was detected and merged successfully
|
||||
logger.info(f"Iteration {iteration}: JSON fragment detected and merged, continuing")
|
||||
# Don't break - fragment was merged, continue to get more content if needed
|
||||
# Check if we should continue based on JSON completeness
|
||||
shouldContinue = self.responseParser.shouldContinueGeneration(
|
||||
allSections,
|
||||
iteration,
|
||||
wasJsonComplete,
|
||||
result
|
||||
)
|
||||
if shouldContinue:
|
||||
if iterationOperationId:
|
||||
self.services.chat.progressLogUpdate(iterationOperationId, 0.8, "Fragment merged, continuing")
|
||||
self.services.chat.progressLogFinish(iterationOperationId, True)
|
||||
continue
|
||||
else:
|
||||
# Done - fragment was merged and JSON is complete
|
||||
if iterationOperationId:
|
||||
self.services.chat.progressLogFinish(iterationOperationId, True)
|
||||
if operationId:
|
||||
self.services.chat.progressLogUpdate(operationId, 0.95, f"Generation complete ({iteration} iterations, fragment merged)")
|
||||
logger.info(f"Generation complete after {iteration} iterations: fragment merged")
|
||||
break
|
||||
|
||||
# Extract document metadata from first iteration if available
|
||||
if iteration == 1 and parsedResult and not documentMetadata:
|
||||
documentMetadata = self.responseParser.extractDocumentMetadata(parsedResult)
|
||||
|
||||
# Update progress after parsing
|
||||
if iterationOperationId:
|
||||
if extractedSections:
|
||||
self.services.chat.progressLogUpdate(iterationOperationId, 0.8, f"Extracted {len(extractedSections)} sections")
|
||||
|
||||
if not extractedSections:
|
||||
# CRITICAL: If JSON was incomplete/broken, continue even if no sections extracted
|
||||
# This allows the AI to retry and complete the broken JSON
|
||||
if not wasJsonComplete:
|
||||
logger.warning(f"Iteration {iteration}: No sections extracted from broken JSON, continuing for another attempt")
|
||||
continue
|
||||
# If JSON was complete but no sections extracted - check if it was a fragment
|
||||
# Fragments are handled above, so if we get here and it's complete, it's an error
|
||||
logger.warning(f"Iteration {iteration}: No sections extracted from complete JSON, stopping")
|
||||
break
|
||||
|
||||
# NOTE: Section merging now happens BEFORE KPI validation (see above)
|
||||
# This ensures sections are preserved even if KPI validation fails
|
||||
|
||||
# Calculate total bytes in merged content for progress display
|
||||
merged_json_str = json.dumps(allSections, indent=2, ensure_ascii=False)
|
||||
totalBytesGenerated = len(merged_json_str.encode('utf-8'))
|
||||
|
||||
# Update main operation with byte progress
|
||||
if operationId:
|
||||
# Format bytes for display
|
||||
if totalBytesGenerated < 1024:
|
||||
bytesDisplay = f"{totalBytesGenerated}B"
|
||||
elif totalBytesGenerated < 1024 * 1024:
|
||||
bytesDisplay = f"{totalBytesGenerated / 1024:.1f}kB"
|
||||
else:
|
||||
bytesDisplay = f"{totalBytesGenerated / (1024 * 1024):.1f}MB"
|
||||
# Estimate progress based on iterations (rough estimate)
|
||||
estimatedProgress = min(0.9, 0.4 + (iteration * 0.1))
|
||||
self.services.chat.progressLogUpdate(operationId, estimatedProgress, f"Pipeline: {bytesDisplay} (iteration {iteration})")
|
||||
|
||||
# Log merged sections for debugging
|
||||
# For section content generation: skip merged sections debug files (only one prompt/response needed)
|
||||
isSectionContent = "_section_" in debugPrefix
|
||||
if not isSectionContent:
|
||||
self.services.utils.writeDebugFile(merged_json_str, f"{debugPrefix}_merged_sections_iteration_{iteration}")
|
||||
|
||||
# Check if we should continue (completion detection)
|
||||
# Simple logic: JSON completeness determines continuation
|
||||
shouldContinue = self.responseParser.shouldContinueGeneration(
|
||||
allSections,
|
||||
iteration,
|
||||
wasJsonComplete,
|
||||
result
|
||||
)
|
||||
|
||||
if shouldContinue:
|
||||
# Finish iteration operation (will continue with next iteration)
|
||||
if iterationOperationId:
|
||||
# Show byte progress in iteration completion
|
||||
iterBytes = len(result.encode('utf-8')) if result else 0
|
||||
if iterBytes < 1024:
|
||||
iterBytesDisplay = f"{iterBytes}B"
|
||||
elif iterBytes < 1024 * 1024:
|
||||
iterBytesDisplay = f"{iterBytes / 1024:.1f}kB"
|
||||
else:
|
||||
iterBytesDisplay = f"{iterBytes / (1024 * 1024):.1f}MB"
|
||||
self.services.chat.progressLogUpdate(iterationOperationId, 0.95, f"Completed ({iterBytesDisplay})")
|
||||
self.services.chat.progressLogFinish(iterationOperationId, True)
|
||||
continue
|
||||
else:
|
||||
# Done - finish iteration and update main operation
|
||||
if iterationOperationId:
|
||||
# Show final byte count
|
||||
finalBytes = len(merged_json_str.encode('utf-8'))
|
||||
if finalBytes < 1024:
|
||||
finalBytesDisplay = f"{finalBytes}B"
|
||||
elif finalBytes < 1024 * 1024:
|
||||
finalBytesDisplay = f"{finalBytes / 1024:.1f}kB"
|
||||
else:
|
||||
finalBytesDisplay = f"{finalBytes / (1024 * 1024):.1f}MB"
|
||||
self.services.chat.progressLogUpdate(iterationOperationId, 0.95, f"Complete ({finalBytesDisplay})")
|
||||
self.services.chat.progressLogFinish(iterationOperationId, True)
|
||||
if operationId:
|
||||
# Show final size in main operation
|
||||
finalBytes = len(merged_json_str.encode('utf-8'))
|
||||
if finalBytes < 1024:
|
||||
finalBytesDisplay = f"{finalBytes}B"
|
||||
elif finalBytes < 1024 * 1024:
|
||||
finalBytesDisplay = f"{finalBytes / 1024:.1f}kB"
|
||||
else:
|
||||
finalBytesDisplay = f"{finalBytes / (1024 * 1024):.1f}MB"
|
||||
self.services.chat.progressLogUpdate(operationId, 0.95, f"Generation complete: {finalBytesDisplay} ({iteration} iterations, {len(allSections)} sections)")
|
||||
logger.info(f"Generation complete after {iteration} iterations: {len(allSections)} sections")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in AI call iteration {iteration}: {str(e)}")
|
||||
|
|
@ -612,20 +423,121 @@ class AiCallLooper:
|
|||
if iteration >= maxIterations:
|
||||
logger.warning(f"AI call stopped after maximum iterations ({maxIterations})")
|
||||
|
||||
# CRITICAL: Complete any incomplete structures in sections before building final result
|
||||
# This ensures JSON is properly closed even if merge failed or iterations stopped early
|
||||
allSections = JsonResponseHandler.completeIncompleteStructures(allSections)
|
||||
# This code path is never reached because all use cases are in directReturnUseCases
|
||||
# and return early at line 417. This code would only execute for use cases that
|
||||
# require section extraction, but no such use cases are currently registered.
|
||||
logger.error(f"Unexpected code path: reached end of loop without return for use case '{useCaseId}'")
|
||||
return result if result else ""
|
||||
|
||||
def _isJsonStringIncomplete(self, jsonString: str) -> bool:
|
||||
"""
|
||||
Check if JSON string is incomplete (truncated) BEFORE closing/parsing.
|
||||
|
||||
# Build final result from accumulated sections
|
||||
final_result = self.responseParser.buildFinalResultFromSections(allSections, documentMetadata)
|
||||
This is critical because if JSON is truncated, closing it makes it appear complete,
|
||||
but we need to detect the truncation to continue iteration.
|
||||
|
||||
# Write final result to debug file
|
||||
# For section content generation: skip final_result debug file (response already written)
|
||||
isSectionContent = "_section_" in debugPrefix
|
||||
if not isSectionContent:
|
||||
self.services.utils.writeDebugFile(final_result, f"{debugPrefix}_final_result")
|
||||
Args:
|
||||
jsonString: JSON string to check
|
||||
|
||||
Returns:
|
||||
True if JSON string appears incomplete/truncated, False otherwise
|
||||
"""
|
||||
if not jsonString or not jsonString.strip():
|
||||
return False
|
||||
|
||||
return final_result
|
||||
from modules.shared.jsonUtils import stripCodeFences, normalizeJsonText
|
||||
|
||||
# Normalize JSON string
|
||||
normalized = stripCodeFences(normalizeJsonText(jsonString)).strip()
|
||||
if not normalized:
|
||||
return False
|
||||
|
||||
# Find first '{' or '[' to start
|
||||
startIdx = -1
|
||||
for i, char in enumerate(normalized):
|
||||
if char in '{[':
|
||||
startIdx = i
|
||||
break
|
||||
|
||||
if startIdx == -1:
|
||||
return False
|
||||
|
||||
jsonContent = normalized[startIdx:]
|
||||
|
||||
# Check if structures are balanced (all opened structures are closed)
|
||||
braceCount = 0
|
||||
bracketCount = 0
|
||||
inString = False
|
||||
escapeNext = False
|
||||
|
||||
for char in jsonContent:
|
||||
if escapeNext:
|
||||
escapeNext = False
|
||||
continue
|
||||
|
||||
if char == '\\':
|
||||
escapeNext = True
|
||||
continue
|
||||
|
||||
if char == '"':
|
||||
inString = not inString
|
||||
continue
|
||||
|
||||
if not inString:
|
||||
if char == '{':
|
||||
braceCount += 1
|
||||
elif char == '}':
|
||||
braceCount -= 1
|
||||
elif char == '[':
|
||||
bracketCount += 1
|
||||
elif char == ']':
|
||||
bracketCount -= 1
|
||||
|
||||
# If structures are unbalanced, JSON is incomplete
|
||||
if braceCount > 0 or bracketCount > 0:
|
||||
return True
|
||||
|
||||
# Check if JSON ends with incomplete value (e.g., unclosed string, incomplete number, trailing comma)
|
||||
trimmed = jsonContent.rstrip()
|
||||
if not trimmed:
|
||||
return False
|
||||
|
||||
# Check for trailing comma (might indicate incomplete)
|
||||
if trimmed.endswith(','):
|
||||
# Trailing comma might indicate incomplete, but could also be valid
|
||||
# Check if there's a closing bracket/brace after the comma
|
||||
return False # Trailing comma alone doesn't mean incomplete
|
||||
|
||||
# Check if ends with incomplete string (odd number of quotes)
|
||||
quoteCount = jsonContent.count('"')
|
||||
if quoteCount % 2 == 1:
|
||||
# Odd number of quotes - string is not closed
|
||||
return True
|
||||
|
||||
# Check if ends mid-value (e.g., ends with "417 instead of "4170. 41719"])
|
||||
# Look for patterns that suggest truncation:
|
||||
# - Ends with incomplete number (e.g., "417)
|
||||
# - Ends with incomplete array element (e.g., ["417)
|
||||
# - Ends with incomplete object property (e.g., {"key": "val)
|
||||
|
||||
# If JSON parses successfully without closing, it's complete
|
||||
from modules.shared.jsonUtils import tryParseJson
|
||||
parsed, parseErr, _ = tryParseJson(jsonContent)
|
||||
if parseErr is None:
|
||||
# Parses successfully - it's complete
|
||||
return False
|
||||
|
||||
# If it doesn't parse, try closing it and see if that helps
|
||||
from modules.shared.jsonUtils import closeJsonStructures
|
||||
closed = closeJsonStructures(jsonContent)
|
||||
parsedClosed, parseErrClosed, _ = tryParseJson(closed)
|
||||
|
||||
if parseErrClosed is None:
|
||||
# Only parses after closing - it was incomplete
|
||||
return True
|
||||
|
||||
# Doesn't parse even after closing - might be malformed, but assume incomplete to be safe
|
||||
return True
|
||||
|
||||
def _normalizeJsonStructure(self, parsed: Any, useCaseId: str) -> Any:
|
||||
"""
|
||||
|
|
@ -645,9 +557,19 @@ class AiCallLooper:
|
|||
# Check if list contains strings (invalid format) or element objects
|
||||
if parsed and isinstance(parsed[0], str):
|
||||
# Invalid format - list of strings instead of elements
|
||||
# This shouldn't happen, but we'll log a warning and return empty structure
|
||||
logger.warning(f"Invalid response format: received list of strings instead of elements array. Expected {{'elements': [...]}} structure.")
|
||||
return {"elements": []}
|
||||
# Try to convert strings to paragraph elements as fallback
|
||||
# This can happen if AI returns raw text instead of structured JSON
|
||||
logger.debug(f"Received list of strings instead of elements array, converting to paragraph elements")
|
||||
elements = []
|
||||
for text in parsed:
|
||||
if isinstance(text, str) and text.strip():
|
||||
elements.append({
|
||||
"type": "paragraph",
|
||||
"content": {
|
||||
"text": text.strip()
|
||||
}
|
||||
})
|
||||
return {"elements": elements} if elements else {"elements": []}
|
||||
else:
|
||||
# Convert plain list of elements to elements structure
|
||||
return {"elements": parsed}
|
||||
|
|
@ -664,99 +586,4 @@ class AiCallLooper:
|
|||
|
||||
# For other use cases, return as-is (they have their own structures)
|
||||
return parsed
|
||||
|
||||
async def _defineKpisFromPrompt(
|
||||
self,
|
||||
userPrompt: str,
|
||||
rawJsonString: Optional[str],
|
||||
continuationContext: Dict[str, Any],
|
||||
debugPrefix: str = "kpi"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Make separate AI call to define KPIs based on user prompt and incomplete JSON.
|
||||
|
||||
Args:
|
||||
userPrompt: Original user prompt
|
||||
rawJsonString: Raw JSON string from first iteration response
|
||||
continuationContext: Continuation context (not used for JSON, kept for compatibility)
|
||||
debugPrefix: Prefix for debug file names
|
||||
|
||||
Returns:
|
||||
List of KPI definitions: [{"id": str, "description": str, "jsonPath": str, "targetValue": int}, ...]
|
||||
"""
|
||||
# Use raw JSON string from first iteration response
|
||||
if rawJsonString:
|
||||
# Remove markdown code fences if present
|
||||
from modules.shared.jsonUtils import stripCodeFences
|
||||
incompleteJson = stripCodeFences(rawJsonString.strip())
|
||||
else:
|
||||
incompleteJson = "Not available"
|
||||
|
||||
kpiDefinitionPrompt = f"""Analyze the user request and incomplete JSON to define KPIs (Key Performance Indicators) for tracking progress.
|
||||
|
||||
User Request:
|
||||
{userPrompt}
|
||||
|
||||
Delivered JSON part:
|
||||
{incompleteJson}
|
||||
|
||||
Task: Define which JSON items should be tracked to measure completion progress.
|
||||
|
||||
IMPORTANT: Analyze the Delivered JSON part structure to understand what is being tracked:
|
||||
1. Identify the structure type (table with rows, list with items, etc.)
|
||||
2. Determine what the jsonPath actually counts (number of rows, number of items, etc.)
|
||||
3. Calculate targetValue based on what is being tracked, NOT the total quantity requested
|
||||
|
||||
For each trackable item, provide:
|
||||
- id: Unique identifier (use descriptive name)
|
||||
- description: What this KPI measures (be specific about what is counted)
|
||||
- jsonPath: Path to extract value from JSON (use dot notation with array indices, e.g., "documents[0].sections[1].elements[0].rows")
|
||||
- targetValue: Target value to reach (integer) - MUST match what jsonPath actually tracks (rows count, items count, etc.)
|
||||
|
||||
Return ONLY valid JSON in this format:
|
||||
{{
|
||||
"kpis": [
|
||||
{{
|
||||
"id": "unique_id",
|
||||
"description": "Description of what is measured",
|
||||
"jsonPath": "path.to.value",
|
||||
"targetValue": 0
|
||||
}}
|
||||
]
|
||||
}}
|
||||
|
||||
If no trackable items can be identified, return: {{"kpis": []}}
|
||||
"""
|
||||
|
||||
try:
|
||||
request = AiCallRequest(
|
||||
prompt=kpiDefinitionPrompt,
|
||||
options=AiCallOptions(
|
||||
operationType=OperationTypeEnum.DATA_ANALYSE,
|
||||
priority=PriorityEnum.SPEED,
|
||||
processingMode=ProcessingModeEnum.BASIC
|
||||
)
|
||||
)
|
||||
|
||||
# Write KPI definition prompt to debug file
|
||||
self.services.utils.writeDebugFile(kpiDefinitionPrompt, f"{debugPrefix}_kpi_definition_prompt")
|
||||
|
||||
checkWorkflowStopped(self.services)
|
||||
response = await self.aiService.callAi(request)
|
||||
|
||||
# Write KPI definition response to debug file
|
||||
self.services.utils.writeDebugFile(response.content, f"{debugPrefix}_kpi_definition_response")
|
||||
|
||||
# Parse response
|
||||
extracted = extractJsonString(response.content)
|
||||
kpiResponse = json.loads(extracted)
|
||||
|
||||
kpiDefinitions = kpiResponse.get("kpis", [])
|
||||
logger.info(f"Defined {len(kpiDefinitions)} KPIs for tracking")
|
||||
|
||||
return kpiDefinitions
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to define KPIs: {e}, continuing without KPI tracking")
|
||||
return []
|
||||
|
||||
|
|
|
|||
2049
modules/services/serviceAi/subJsonMerger.py
Normal file
2049
modules/services/serviceAi/subJsonMerger.py
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -18,7 +18,7 @@ class LoopingUseCase:
|
|||
"""Configuration for a specific looping use case."""
|
||||
|
||||
# Identification
|
||||
useCaseId: str # "section_content", "chapter_structure", "document_structure", "code_structure", "code_content", "image_batch"
|
||||
useCaseId: str # "section_content", "chapter_structure", "code_structure", "code_content"
|
||||
|
||||
# JSON Format Detection
|
||||
jsonTemplate: Dict[str, Any] # Expected JSON structure template
|
||||
|
|
@ -145,24 +145,7 @@ class LoopingUseCaseRegistry:
|
|||
requiresExtraction=False
|
||||
))
|
||||
|
||||
# Use Case 3: Document Structure Generation
|
||||
# Returns JSON with "documents[0].sections" structure, requires extraction and accumulation
|
||||
self.register(LoopingUseCase(
|
||||
useCaseId="document_structure",
|
||||
jsonTemplate={"documents": [{"sections": []}]},
|
||||
detectionKeys=["sections"],
|
||||
detectionPath="documents[0].sections",
|
||||
initialPromptBuilder=None,
|
||||
continuationPromptBuilder=None,
|
||||
accumulator=None, # Will use default accumulator
|
||||
merger=None, # Will use default merger
|
||||
continuationContextBuilder=None,
|
||||
resultBuilder=None, # Will use default result builder
|
||||
supportsAccumulation=True,
|
||||
requiresExtraction=True
|
||||
))
|
||||
|
||||
# Use Case 4: Code Structure Generation (NEW)
|
||||
# Use Case 3: Code Structure Generation
|
||||
self.register(LoopingUseCase(
|
||||
useCaseId="code_structure",
|
||||
jsonTemplate={
|
||||
|
|
@ -211,21 +194,5 @@ class LoopingUseCaseRegistry:
|
|||
requiresExtraction=False
|
||||
))
|
||||
|
||||
# Use Case 6: Image Batch Generation (NEW)
|
||||
self.register(LoopingUseCase(
|
||||
useCaseId="image_batch",
|
||||
jsonTemplate={"images": []},
|
||||
detectionKeys=["images"],
|
||||
detectionPath="images",
|
||||
initialPromptBuilder=None,
|
||||
continuationPromptBuilder=None,
|
||||
accumulator=None, # Direct return
|
||||
merger=None,
|
||||
continuationContextBuilder=None,
|
||||
resultBuilder=None,
|
||||
supportsAccumulation=False,
|
||||
requiresExtraction=False
|
||||
))
|
||||
|
||||
logger.info(f"Registered {len(self.useCases)} default looping use cases")
|
||||
|
||||
|
|
|
|||
|
|
@ -812,16 +812,18 @@ class StructureFiller:
|
|||
)
|
||||
else:
|
||||
async def buildSectionPromptWithContinuation(
|
||||
section: Dict[str, Any],
|
||||
contentParts: List[ContentPart],
|
||||
userPrompt: str,
|
||||
generationHint: str,
|
||||
allSections: List[Dict[str, Any]],
|
||||
sectionIndex: int,
|
||||
isAggregation: bool,
|
||||
continuationContext: Dict[str, Any],
|
||||
services: Any
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""Build section prompt with continuation context. Extracts section-specific parameters from kwargs."""
|
||||
# Extract parameters from kwargs (for section_content use case)
|
||||
section = kwargs.get("section")
|
||||
contentParts = kwargs.get("contentParts", [])
|
||||
userPrompt = kwargs.get("userPrompt", "")
|
||||
generationHint = kwargs.get("generationHint", "")
|
||||
allSections = kwargs.get("allSections", [])
|
||||
sectionIndex = kwargs.get("sectionIndex", 0)
|
||||
isAggregation = kwargs.get("isAggregation", False)
|
||||
basePrompt = self._buildSectionGenerationPrompt(
|
||||
section=section,
|
||||
contentParts=contentParts,
|
||||
|
|
@ -833,25 +835,81 @@ class StructureFiller:
|
|||
language=language
|
||||
)
|
||||
|
||||
continuationInfo = continuationContext.get("delivered_summary", "")
|
||||
cutOffElement = continuationContext.get("cut_off_element", "")
|
||||
# Extract JSON structure context for continuation
|
||||
incompletePart = continuationContext.get("incomplete_part", "")
|
||||
lastRawJson = continuationContext.get("last_raw_json", "")
|
||||
|
||||
# Build overlap context: extract last ~100 characters from the response for overlap
|
||||
overlapContext = ""
|
||||
if lastRawJson:
|
||||
# Get last 100 characters for overlap
|
||||
overlapContext = lastRawJson[-100:].strip()
|
||||
|
||||
# Build unified context showing structure hierarchy with cut point
|
||||
# This combines structure template, last complete part, and incomplete part in one view
|
||||
unifiedContext = ""
|
||||
if lastRawJson:
|
||||
# Find break position in raw JSON
|
||||
if incompletePart:
|
||||
breakPos = lastRawJson.find(incompletePart)
|
||||
if breakPos == -1:
|
||||
# Try to find where JSON ends
|
||||
breakPos = len(lastRawJson.rstrip())
|
||||
else:
|
||||
# No incomplete part found - assume end of JSON
|
||||
breakPos = len(lastRawJson.rstrip())
|
||||
|
||||
# Build intelligent context showing hierarchy
|
||||
from modules.shared.jsonUtils import _buildIncompleteContext
|
||||
unifiedContext = _buildIncompleteContext(lastRawJson, breakPos)
|
||||
elif incompletePart:
|
||||
# Fallback: use incomplete part directly
|
||||
unifiedContext = incompletePart
|
||||
else:
|
||||
unifiedContext = "Unable to extract context - response was completely broken"
|
||||
|
||||
# Use the SAME template structure as in initial prompt
|
||||
# Get contentType and contentStructureExample exactly like in _buildSectionGenerationPrompt
|
||||
contentType = section.get("content_type", "paragraph")
|
||||
contentStructureExample = self._getContentStructureExample(contentType)
|
||||
|
||||
# Build the exact same JSON structure template as in initial prompt
|
||||
structureTemplate = f"""JSON Structure Template:
|
||||
{{
|
||||
"elements": [
|
||||
{{
|
||||
"type": "{contentType}",
|
||||
"content": {contentStructureExample}
|
||||
}}
|
||||
]
|
||||
}}
|
||||
|
||||
"""
|
||||
|
||||
continuationPrompt = f"""{basePrompt}
|
||||
|
||||
--- CONTINUATION REQUEST ---
|
||||
The previous JSON response was incomplete. Please continue from where it stopped.
|
||||
The previous JSON response was incomplete. Continue from where it stopped.
|
||||
|
||||
PREVIOUSLY DELIVERED SUMMARY:
|
||||
{continuationInfo}
|
||||
{structureTemplate}Context showing structure hierarchy with cut point:
|
||||
{unifiedContext}
|
||||
|
||||
LAST INCOMPLETE ELEMENT:
|
||||
{cutOffElement}
|
||||
Overlap Requirement:
|
||||
To ensure proper merging, your response MUST start by repeating approximately the last 100 characters from the previous response, then continue with new content.
|
||||
|
||||
TASK: Continue generating the JSON elements array from where it was cut off.
|
||||
Complete the incomplete element and continue with remaining elements.
|
||||
Last ~100 characters from previous response (repeat these at the start):
|
||||
{overlapContext if overlapContext else "No overlap context available"}
|
||||
|
||||
Return ONLY the continuation JSON (starting from the incomplete element).
|
||||
The JSON should be a fragment that can be merged with the previous response."""
|
||||
TASK:
|
||||
1. Start your response by repeating the last ~100 characters shown above (for overlap/merging)
|
||||
2. Complete the incomplete element shown in the context above (marked with CUT POINT)
|
||||
3. Continue generating the remaining content following the JSON structure template above
|
||||
4. Return ONLY valid JSON following the structure template - no overlap/continuation wrapper objects
|
||||
|
||||
CRITICAL:
|
||||
- Your response must be valid JSON matching the structure template above
|
||||
- Start with overlap (~100 chars) then continue seamlessly
|
||||
- Complete the incomplete element and continue with remaining elements"""
|
||||
return continuationPrompt
|
||||
|
||||
options = AiCallOptions(
|
||||
|
|
@ -1040,16 +1098,18 @@ The JSON should be a fragment that can be merged with the previous response."""
|
|||
isAggregation = False
|
||||
|
||||
async def buildSectionPromptWithContinuation(
|
||||
section: Dict[str, Any],
|
||||
contentParts: List[ContentPart],
|
||||
userPrompt: str,
|
||||
generationHint: str,
|
||||
allSections: List[Dict[str, Any]],
|
||||
sectionIndex: int,
|
||||
isAggregation: bool,
|
||||
continuationContext: Dict[str, Any],
|
||||
services: Any
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""Build section prompt with continuation context. Extracts section-specific parameters from kwargs."""
|
||||
# Extract parameters from kwargs (for section_content use case)
|
||||
section = kwargs.get("section")
|
||||
contentParts = kwargs.get("contentParts", [])
|
||||
userPrompt = kwargs.get("userPrompt", "")
|
||||
generationHint = kwargs.get("generationHint", "")
|
||||
allSections = kwargs.get("allSections", [])
|
||||
sectionIndex = kwargs.get("sectionIndex", 0)
|
||||
isAggregation = kwargs.get("isAggregation", False)
|
||||
basePrompt = self._buildSectionGenerationPrompt(
|
||||
section=section,
|
||||
contentParts=contentParts,
|
||||
|
|
@ -1061,25 +1121,81 @@ The JSON should be a fragment that can be merged with the previous response."""
|
|||
language=language
|
||||
)
|
||||
|
||||
continuationInfo = continuationContext.get("delivered_summary", "")
|
||||
cutOffElement = continuationContext.get("cut_off_element", "")
|
||||
# Extract JSON structure context for continuation
|
||||
incompletePart = continuationContext.get("incomplete_part", "")
|
||||
lastRawJson = continuationContext.get("last_raw_json", "")
|
||||
|
||||
# Build overlap context: extract last ~100 characters from the response for overlap
|
||||
overlapContext = ""
|
||||
if lastRawJson:
|
||||
# Get last 100 characters for overlap
|
||||
overlapContext = lastRawJson[-100:].strip()
|
||||
|
||||
# Build unified context showing structure hierarchy with cut point
|
||||
# This combines structure template, last complete part, and incomplete part in one view
|
||||
unifiedContext = ""
|
||||
if lastRawJson:
|
||||
# Find break position in raw JSON
|
||||
if incompletePart:
|
||||
breakPos = lastRawJson.find(incompletePart)
|
||||
if breakPos == -1:
|
||||
# Try to find where JSON ends
|
||||
breakPos = len(lastRawJson.rstrip())
|
||||
else:
|
||||
# No incomplete part found - assume end of JSON
|
||||
breakPos = len(lastRawJson.rstrip())
|
||||
|
||||
# Build intelligent context showing hierarchy
|
||||
from modules.shared.jsonUtils import _buildIncompleteContext
|
||||
unifiedContext = _buildIncompleteContext(lastRawJson, breakPos)
|
||||
elif incompletePart:
|
||||
# Fallback: use incomplete part directly
|
||||
unifiedContext = incompletePart
|
||||
else:
|
||||
unifiedContext = "Unable to extract context - response was completely broken"
|
||||
|
||||
# Use the SAME template structure as in initial prompt
|
||||
# Get contentType and contentStructureExample exactly like in _buildSectionGenerationPrompt
|
||||
contentType = section.get("content_type", "paragraph")
|
||||
contentStructureExample = self._getContentStructureExample(contentType)
|
||||
|
||||
# Build the exact same JSON structure template as in initial prompt
|
||||
structureTemplate = f"""JSON Structure Template:
|
||||
{{
|
||||
"elements": [
|
||||
{{
|
||||
"type": "{contentType}",
|
||||
"content": {contentStructureExample}
|
||||
}}
|
||||
]
|
||||
}}
|
||||
|
||||
"""
|
||||
|
||||
continuationPrompt = f"""{basePrompt}
|
||||
|
||||
--- CONTINUATION REQUEST ---
|
||||
The previous JSON response was incomplete. Please continue from where it stopped.
|
||||
The previous JSON response was incomplete. Continue from where it stopped.
|
||||
|
||||
PREVIOUSLY DELIVERED SUMMARY:
|
||||
{continuationInfo}
|
||||
{structureTemplate}Context showing structure hierarchy with cut point:
|
||||
{unifiedContext}
|
||||
|
||||
LAST INCOMPLETE ELEMENT:
|
||||
{cutOffElement}
|
||||
Overlap Requirement:
|
||||
To ensure proper merging, your response MUST start by repeating approximately the last 100 characters from the previous response, then continue with new content.
|
||||
|
||||
TASK: Continue generating the JSON elements array from where it was cut off.
|
||||
Complete the incomplete element and continue with remaining elements.
|
||||
Last ~100 characters from previous response (repeat these at the start):
|
||||
{overlapContext if overlapContext else "No overlap context available"}
|
||||
|
||||
Return ONLY the continuation JSON (starting from the incomplete element).
|
||||
The JSON should be a fragment that can be merged with the previous response."""
|
||||
TASK:
|
||||
1. Start your response by repeating the last ~100 characters shown above (for overlap/merging)
|
||||
2. Complete the incomplete element shown in the context above (marked with CUT POINT)
|
||||
3. Continue generating the remaining content following the JSON structure template above
|
||||
4. Return ONLY valid JSON following the structure template - no overlap/continuation wrapper objects
|
||||
|
||||
CRITICAL:
|
||||
- Your response must be valid JSON matching the structure template above
|
||||
- Start with overlap (~100 chars) then continue seamlessly
|
||||
- Complete the incomplete element and continue with remaining elements"""
|
||||
return continuationPrompt
|
||||
|
||||
options = AiCallOptions(
|
||||
|
|
@ -1343,16 +1459,19 @@ The JSON should be a fragment that can be merged with the previous response."""
|
|||
isAggregation = False
|
||||
|
||||
async def buildSectionPromptWithContinuation(
|
||||
section: Dict[str, Any],
|
||||
contentParts: List[ContentPart],
|
||||
userPrompt: str,
|
||||
generationHint: str,
|
||||
allSections: List[Dict[str, Any]],
|
||||
sectionIndex: int,
|
||||
isAggregation: bool,
|
||||
continuationContext: Dict[str, Any],
|
||||
services: Any
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""Build section prompt with continuation context. Extracts section-specific parameters from kwargs."""
|
||||
# Extract parameters from kwargs (for section_content use case)
|
||||
section = kwargs.get("section")
|
||||
contentParts = kwargs.get("contentParts", [])
|
||||
userPrompt = kwargs.get("userPrompt", "")
|
||||
generationHint = kwargs.get("generationHint", "")
|
||||
allSections = kwargs.get("allSections", [])
|
||||
sectionIndex = kwargs.get("sectionIndex", 0)
|
||||
isAggregation = kwargs.get("isAggregation", False)
|
||||
services = kwargs.get("services")
|
||||
basePrompt = self._buildSectionGenerationPrompt(
|
||||
section=section,
|
||||
contentParts=contentParts,
|
||||
|
|
@ -1364,25 +1483,83 @@ The JSON should be a fragment that can be merged with the previous response."""
|
|||
language=language
|
||||
)
|
||||
|
||||
continuationInfo = continuationContext.get("delivered_summary", "")
|
||||
cutOffElement = continuationContext.get("cut_off_element", "")
|
||||
# Extract JSON structure context for continuation
|
||||
templateStructure = continuationContext.get("template_structure", "")
|
||||
lastCompletePart = continuationContext.get("last_complete_part", "")
|
||||
incompletePart = continuationContext.get("incomplete_part", "")
|
||||
structureContext = continuationContext.get("structure_context", "")
|
||||
lastRawJson = continuationContext.get("last_raw_json", "")
|
||||
|
||||
# Build overlap context: extract last ~100 characters from the response for overlap
|
||||
overlapContext = ""
|
||||
if lastRawJson:
|
||||
# Get last 100 characters for overlap
|
||||
overlapContext = lastRawJson[-100:].strip()
|
||||
|
||||
# Build unified context showing structure hierarchy with cut point
|
||||
unifiedContext = ""
|
||||
if lastRawJson:
|
||||
# Find break position in raw JSON
|
||||
if incompletePart:
|
||||
breakPos = lastRawJson.find(incompletePart)
|
||||
if breakPos == -1:
|
||||
# Try to find where JSON ends
|
||||
breakPos = len(lastRawJson.rstrip())
|
||||
else:
|
||||
# No incomplete part found - assume end of JSON
|
||||
breakPos = len(lastRawJson.rstrip())
|
||||
|
||||
# Build intelligent context showing hierarchy
|
||||
from modules.shared.jsonUtils import _buildIncompleteContext
|
||||
unifiedContext = _buildIncompleteContext(lastRawJson, breakPos)
|
||||
elif incompletePart:
|
||||
# Fallback: use incomplete part directly
|
||||
unifiedContext = incompletePart
|
||||
else:
|
||||
unifiedContext = "Unable to extract context - response was completely broken"
|
||||
|
||||
# Use the SAME template structure as in initial prompt
|
||||
# Get contentType and contentStructureExample exactly like in _buildSectionGenerationPrompt
|
||||
contentType = section.get("content_type", "paragraph")
|
||||
contentStructureExample = self._getContentStructureExample(contentType)
|
||||
|
||||
# Build the exact same JSON structure template as in initial prompt
|
||||
structureTemplate = f"""JSON Structure Template:
|
||||
{{
|
||||
"elements": [
|
||||
{{
|
||||
"type": "{contentType}",
|
||||
"content": {contentStructureExample}
|
||||
}}
|
||||
]
|
||||
}}
|
||||
|
||||
"""
|
||||
|
||||
continuationPrompt = f"""{basePrompt}
|
||||
|
||||
--- CONTINUATION REQUEST ---
|
||||
The previous JSON response was incomplete. Please continue from where it stopped.
|
||||
The previous JSON response was incomplete. Continue from where it stopped.
|
||||
|
||||
PREVIOUSLY DELIVERED SUMMARY:
|
||||
{continuationInfo}
|
||||
{structureTemplate}Context showing structure hierarchy with cut point:
|
||||
{unifiedContext}
|
||||
|
||||
LAST INCOMPLETE ELEMENT:
|
||||
{cutOffElement}
|
||||
Overlap Requirement:
|
||||
To ensure proper merging, your response MUST start by repeating approximately the last 100 characters from the previous response, then continue with new content.
|
||||
|
||||
TASK: Continue generating the JSON elements array from where it was cut off.
|
||||
Complete the incomplete element and continue with remaining elements.
|
||||
Last ~100 characters from previous response (repeat these at the start):
|
||||
{overlapContext if overlapContext else "No overlap context available"}
|
||||
|
||||
Return ONLY the continuation JSON (starting from the incomplete element).
|
||||
The JSON should be a fragment that can be merged with the previous response."""
|
||||
TASK:
|
||||
1. Start your response by repeating the last ~100 characters shown above (for overlap/merging)
|
||||
2. Complete the incomplete element shown in the context above (marked with CUT POINT)
|
||||
3. Continue generating the remaining content following the JSON structure template above
|
||||
4. Return ONLY valid JSON following the structure template - no overlap/continuation wrapper objects
|
||||
|
||||
CRITICAL:
|
||||
- Your response must be valid JSON matching the structure template above
|
||||
- Start with overlap (~100 chars) then continue seamlessly
|
||||
- Complete the incomplete element and continue with remaining elements"""
|
||||
return continuationPrompt
|
||||
|
||||
options = AiCallOptions(
|
||||
|
|
|
|||
|
|
@ -112,7 +112,12 @@ class StructureGenerator:
|
|||
continuationContext: Optional[Dict[str, Any]] = None,
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""Build chapter structure prompt with optional continuation context."""
|
||||
"""Build chapter structure prompt with optional continuation context. Extracts chapter-specific parameters from kwargs."""
|
||||
# Extract parameters from kwargs (for chapter_structure use case)
|
||||
userPrompt = kwargs.get("userPrompt", "")
|
||||
contentParts = kwargs.get("contentParts", [])
|
||||
outputFormat = kwargs.get("outputFormat", "txt")
|
||||
|
||||
basePrompt = self._buildChapterStructurePrompt(
|
||||
userPrompt=userPrompt,
|
||||
contentParts=contentParts,
|
||||
|
|
|
|||
594
modules/services/serviceAi/test_json_merger.py
Normal file
594
modules/services/serviceAi/test_json_merger.py
Normal file
|
|
@ -0,0 +1,594 @@
|
|||
# Copyright (c) 2025 Patrick Motsch
|
||||
# All rights reserved.
|
||||
"""
|
||||
Test cases for JSON merger with different use cases and random cuts.
|
||||
|
||||
Tests the robustness of the JSON merger by:
|
||||
1. Creating test JSON for different use cases
|
||||
2. Cutting it randomly at various points
|
||||
3. Running the merger for each piece
|
||||
4. Checking completeness against original
|
||||
"""
|
||||
|
||||
import json
|
||||
import random
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
from typing import Dict, Any, List, Tuple
|
||||
|
||||
# Add project root to Python path
|
||||
# Find project root by looking for gateway/modules structure
|
||||
currentFile = os.path.abspath(__file__)
|
||||
currentDir = os.path.dirname(currentFile)
|
||||
|
||||
# Navigate up from: gateway/modules/services/serviceAi/test_json_merger.py
|
||||
# To project root: D:\Athi\Local\Web\poweron
|
||||
# Try different levels up
|
||||
candidates = [
|
||||
os.path.abspath(os.path.join(currentDir, '../../../../')), # From gateway/modules/services/serviceAi
|
||||
os.path.abspath(os.path.join(currentDir, '../../..')), # Alternative
|
||||
os.path.abspath(os.path.join(currentDir, '../..')), # Another alternative
|
||||
]
|
||||
|
||||
projectRoot = None
|
||||
for candidate in candidates:
|
||||
gatewayModulesPath = os.path.join(candidate, 'gateway', 'modules')
|
||||
if os.path.exists(gatewayModulesPath):
|
||||
projectRoot = candidate
|
||||
break
|
||||
|
||||
# If still not found, try to find by looking for gateway directory
|
||||
if projectRoot is None:
|
||||
searchDir = currentDir
|
||||
for _ in range(10): # Max 10 levels up
|
||||
gatewayPath = os.path.join(searchDir, 'gateway')
|
||||
if os.path.exists(gatewayPath) and os.path.exists(os.path.join(gatewayPath, 'modules')):
|
||||
projectRoot = searchDir
|
||||
break
|
||||
parent = os.path.dirname(searchDir)
|
||||
if parent == searchDir: # Reached root
|
||||
break
|
||||
searchDir = parent
|
||||
|
||||
if projectRoot is None:
|
||||
raise RuntimeError(f"Could not find project root. Current file: {currentFile}")
|
||||
|
||||
# Add gateway directory to Python path (not project root)
|
||||
gatewayPath = os.path.join(projectRoot, 'gateway')
|
||||
if gatewayPath not in sys.path:
|
||||
sys.path.insert(0, gatewayPath)
|
||||
|
||||
# Verify the path is correct
|
||||
modulesPath = os.path.join(projectRoot, 'gateway', 'modules')
|
||||
if not os.path.exists(modulesPath):
|
||||
raise RuntimeError(f"Project root verification failed. Expected gateway/modules at: {modulesPath}")
|
||||
|
||||
try:
|
||||
from modules.services.serviceAi.subJsonResponseHandling import JsonResponseHandler
|
||||
from modules.services.serviceAi.subJsonMerger import JsonMergeLogger
|
||||
from modules.shared.jsonUtils import (
|
||||
normalizeJsonText, stripCodeFences, closeJsonStructures, tryParseJson,
|
||||
extractJsonStructureContext
|
||||
)
|
||||
except ImportError as e:
|
||||
# Try to help debug
|
||||
print(f"Import error: {e}")
|
||||
print(f"Project root: {projectRoot}")
|
||||
print(f"Gateway path: {gatewayPath}")
|
||||
print(f"Python path (first 3): {sys.path[:3]}")
|
||||
print(f"Looking for modules at: {modulesPath}")
|
||||
print(f"Exists: {os.path.exists(modulesPath)}")
|
||||
if os.path.exists(modulesPath):
|
||||
print(f"Contents: {os.listdir(modulesPath)[:5]}")
|
||||
raise
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def createTestJsonForUseCase(useCaseId: str, size: int = 100) -> Dict[str, Any]:
|
||||
"""
|
||||
Create test JSON for a specific use case.
|
||||
|
||||
Args:
|
||||
useCaseId: Use case ID (section_content, chapter_structure, etc.)
|
||||
size: Size of test data (number of elements/rows/items)
|
||||
|
||||
Returns:
|
||||
Test JSON dictionary
|
||||
"""
|
||||
if useCaseId == "section_content":
|
||||
# Create table with rows
|
||||
elements = [{
|
||||
"type": "table",
|
||||
"content": {
|
||||
"headers": ["Year", "Value"],
|
||||
"rows": [[str(1947 + i), str(10000 + i * 100)] for i in range(size)]
|
||||
}
|
||||
}]
|
||||
return {"elements": elements}
|
||||
|
||||
elif useCaseId == "chapter_structure":
|
||||
chapters = [{
|
||||
"id": f"chapter_{i}",
|
||||
"title": f"Chapter {i}",
|
||||
"level": 1
|
||||
} for i in range(size)]
|
||||
return {"documents": [{"chapters": chapters}]}
|
||||
|
||||
elif useCaseId == "code_structure":
|
||||
files = [{
|
||||
"id": f"file_{i}",
|
||||
"filename": f"file_{i}.py",
|
||||
"fileType": "python",
|
||||
"functions": [f"function_{i}_{j}" for j in range(5)]
|
||||
} for i in range(size)]
|
||||
return {"files": files}
|
||||
|
||||
elif useCaseId == "code_content":
|
||||
files = [{
|
||||
"id": f"file_{i}",
|
||||
"content": f"# File {i}\ndef function_{i}():\n pass\n" * 10,
|
||||
"functions": [{"name": f"function_{i}_{j}", "line": j * 3} for j in range(5)]
|
||||
} for i in range(size)]
|
||||
return {"files": files}
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unknown use case: {useCaseId}")
|
||||
|
||||
|
||||
def cutJsonRandomly(jsonString: str, numCuts: int = 5, overlapSize: int = 100) -> List[str]:
|
||||
"""
|
||||
Cut JSON string RANDOMLY at different points WITH OVERLAP between fragments.
|
||||
Each fragment overlaps with the previous one to help merging.
|
||||
|
||||
Args:
|
||||
jsonString: JSON string to cut
|
||||
numCuts: Number of cuts to make
|
||||
overlapSize: Size of overlap between fragments (in characters)
|
||||
|
||||
Returns:
|
||||
List of JSON fragments with overlap
|
||||
"""
|
||||
fragments = []
|
||||
currentPos = 0
|
||||
totalLength = len(jsonString)
|
||||
|
||||
if totalLength == 0:
|
||||
return []
|
||||
|
||||
# First fragment: from start to first cut point
|
||||
if numCuts > 0:
|
||||
# First cut point (between 20% and 40% of total)
|
||||
firstCutPoint = random.randint(int(totalLength * 0.2), int(totalLength * 0.4))
|
||||
fragment = jsonString[:firstCutPoint]
|
||||
fragments.append(fragment)
|
||||
currentPos = firstCutPoint
|
||||
else:
|
||||
# No cuts - return whole string
|
||||
return [jsonString]
|
||||
|
||||
# Subsequent fragments: each starts with overlap from previous, then continues
|
||||
for i in range(numCuts - 1):
|
||||
if currentPos >= totalLength:
|
||||
break
|
||||
|
||||
# Calculate overlap start (go back overlapSize from current position)
|
||||
overlapStart = max(0, currentPos - overlapSize)
|
||||
|
||||
# Calculate next cut point (between 20% and 40% of remaining)
|
||||
remaining = totalLength - currentPos
|
||||
if remaining < overlapSize * 2:
|
||||
# Not enough remaining - add rest as last fragment
|
||||
fragment = jsonString[overlapStart:]
|
||||
fragments.append(fragment)
|
||||
break
|
||||
|
||||
# Next cut point from current position
|
||||
nextCutPoint = currentPos + random.randint(int(remaining * 0.2), int(remaining * 0.4))
|
||||
nextCutPoint = min(nextCutPoint, totalLength)
|
||||
|
||||
# Fragment: from overlap start to next cut point
|
||||
fragment = jsonString[overlapStart:nextCutPoint]
|
||||
fragments.append(fragment)
|
||||
|
||||
currentPos = nextCutPoint
|
||||
|
||||
# Add remaining as last fragment (with overlap)
|
||||
if currentPos < totalLength:
|
||||
overlapStart = max(0, currentPos - overlapSize)
|
||||
fragment = jsonString[overlapStart:]
|
||||
fragments.append(fragment)
|
||||
|
||||
return fragments
|
||||
|
||||
|
||||
def testMergerWithFragments(
|
||||
originalJson: Dict[str, Any],
|
||||
fragments: List[str],
|
||||
useCaseId: str
|
||||
) -> Tuple[bool, Dict[str, Any], str]:
|
||||
"""
|
||||
Test merger by merging fragments sequentially.
|
||||
|
||||
Args:
|
||||
originalJson: Original complete JSON
|
||||
fragments: List of JSON fragments to merge
|
||||
useCaseId: Use case ID
|
||||
|
||||
Returns:
|
||||
Tuple of (success, merged_json, error_message)
|
||||
"""
|
||||
if not fragments:
|
||||
return False, {}, "No fragments provided"
|
||||
|
||||
# Log structure context for each fragment (especially incomplete ones)
|
||||
print(f"\n{'='*60}")
|
||||
print(f"FRAGMENT ANALYSIS (use case: {useCaseId})")
|
||||
print(f"{'='*60}")
|
||||
|
||||
for fragIdx, fragment in enumerate(fragments):
|
||||
print(f"\nFragment {fragIdx + 1}/{len(fragments)}:")
|
||||
print(f" Length: {len(fragment)} chars")
|
||||
|
||||
# Extract structure context for this fragment
|
||||
try:
|
||||
structureContext = extractJsonStructureContext(fragment, useCaseId)
|
||||
|
||||
templateStructure = structureContext.get("template_structure", "")
|
||||
lastCompletePart = structureContext.get("last_complete_part", "")
|
||||
incompletePart = structureContext.get("incomplete_part", "")
|
||||
structureContextJson = structureContext.get("structure_context", "")
|
||||
|
||||
# Check if fragment is incomplete
|
||||
normalized = stripCodeFences(normalizeJsonText(fragment)).strip()
|
||||
parsed, parseErr, _ = tryParseJson(normalized)
|
||||
isIncomplete = parseErr is not None or (parsed is None)
|
||||
|
||||
if isIncomplete:
|
||||
print(f" Status: INCOMPLETE (cut off)")
|
||||
print(f" Template Structure:")
|
||||
if templateStructure:
|
||||
# Show first few lines of template
|
||||
templateLines = templateStructure.split('\n')
|
||||
templateLinesToShow = templateLines[:5]
|
||||
for line in templateLinesToShow:
|
||||
print(f" {line}")
|
||||
if len(templateLines) > 5:
|
||||
remainingLines = len(templateLines) - 5
|
||||
print(f" ... ({remainingLines} more lines)")
|
||||
else:
|
||||
print(f" (not available)")
|
||||
|
||||
print(f" Structure Context:")
|
||||
if structureContextJson:
|
||||
# Show structure context
|
||||
contextLines = structureContextJson.split('\n')
|
||||
contextLinesToShow = contextLines[:5]
|
||||
for line in contextLinesToShow:
|
||||
print(f" {line}")
|
||||
if len(contextLines) > 5:
|
||||
remainingContextLines = len(contextLines) - 5
|
||||
print(f" ... ({remainingContextLines} more lines)")
|
||||
else:
|
||||
print(f" (not available)")
|
||||
|
||||
print(f" Last Complete Part:")
|
||||
if lastCompletePart:
|
||||
# Show last complete part (truncated if too long)
|
||||
if len(lastCompletePart) > 200:
|
||||
print(f" {lastCompletePart[:200]}... ({len(lastCompletePart)} chars total)")
|
||||
else:
|
||||
print(f" {lastCompletePart}")
|
||||
else:
|
||||
print(f" (not available)")
|
||||
|
||||
print(f" Incomplete Part:")
|
||||
if incompletePart:
|
||||
# Show incomplete part (truncated if too long)
|
||||
if len(incompletePart) > 200:
|
||||
print(f" {incompletePart[:200]}... ({len(incompletePart)} chars total)")
|
||||
else:
|
||||
print(f" {incompletePart}")
|
||||
else:
|
||||
print(f" (not available)")
|
||||
else:
|
||||
print(f" Status: COMPLETE")
|
||||
if structureContextJson:
|
||||
print(f" Structure Context:")
|
||||
contextLines = structureContextJson.split('\n')
|
||||
contextLinesToShow = contextLines[:3]
|
||||
for line in contextLinesToShow:
|
||||
print(f" {line}")
|
||||
if len(contextLines) > 3:
|
||||
remainingContextLines = len(contextLines) - 3
|
||||
print(f" ... ({remainingContextLines} more lines)")
|
||||
except Exception as e:
|
||||
print(f" Error extracting structure context: {e}")
|
||||
|
||||
print(f"\n{'='*60}\n")
|
||||
|
||||
# Start with first fragment
|
||||
accumulated = fragments[0]
|
||||
|
||||
# Merge each subsequent fragment
|
||||
for i, fragment in enumerate(fragments[1:], 1):
|
||||
try:
|
||||
accumulated, hasOverlap = JsonResponseHandler.mergeJsonStringsWithOverlap(
|
||||
accumulated, fragment
|
||||
)
|
||||
# Log if no overlap was found (iterations would stop in real scenario)
|
||||
if not hasOverlap:
|
||||
print(f" ⚠️ Fragment {i}: No overlap found - iterations would stop here")
|
||||
|
||||
# Check if result is empty (should never happen)
|
||||
if not accumulated or accumulated.strip() in ['{"elements": []}', '{}', '']:
|
||||
return False, {}, f"Merge {i} returned empty JSON"
|
||||
|
||||
except Exception as e:
|
||||
return False, {}, f"Merge {i} failed with error: {str(e)}"
|
||||
|
||||
# Parse merged result
|
||||
try:
|
||||
# Normalize and try to parse
|
||||
normalized = stripCodeFences(normalizeJsonText(accumulated)).strip()
|
||||
|
||||
# Try to parse directly
|
||||
parsed, parseErr, _ = tryParseJson(normalized)
|
||||
|
||||
if parseErr is not None:
|
||||
# Try closing structures if incomplete
|
||||
try:
|
||||
closed = closeJsonStructures(normalized)
|
||||
parsed, parseErr2, _ = tryParseJson(closed)
|
||||
if parseErr2 is not None:
|
||||
# Try to extract valid JSON prefix
|
||||
# JsonResponseHandler is already imported at module level
|
||||
validPrefix = JsonResponseHandler._extractValidJsonPrefix(normalized)
|
||||
if validPrefix:
|
||||
parsed, parseErr3, _ = tryParseJson(validPrefix)
|
||||
if parseErr3 is not None:
|
||||
return False, {}, f"Final parse error: {str(parseErr3)}"
|
||||
else:
|
||||
return False, {}, f"Final parse error: {str(parseErr2)}"
|
||||
except Exception as parseErr:
|
||||
return False, {}, f"Final parse error: {str(parseErr)}"
|
||||
|
||||
if not parsed:
|
||||
return False, {}, "Final parse returned None"
|
||||
|
||||
# CRITICAL: Ensure parsed is a dict, not a list
|
||||
# If it's a list, wrap it in the expected structure based on use case
|
||||
if isinstance(parsed, list):
|
||||
# Try to normalize list to expected structure
|
||||
if useCaseId == "section_content":
|
||||
# List of elements - wrap in elements structure
|
||||
parsed = {"elements": parsed}
|
||||
elif useCaseId == "chapter_structure":
|
||||
# List of chapters - wrap in documents structure
|
||||
parsed = {"documents": [{"chapters": parsed}]}
|
||||
elif useCaseId == "code_structure":
|
||||
# List of files - wrap in files structure
|
||||
parsed = {"files": parsed}
|
||||
elif useCaseId == "code_content":
|
||||
# List of files - wrap in files structure
|
||||
parsed = {"files": parsed}
|
||||
else:
|
||||
# Unknown use case - try to wrap as elements
|
||||
parsed = {"elements": parsed}
|
||||
|
||||
# Ensure it's a dict now
|
||||
if not isinstance(parsed, dict):
|
||||
return False, {}, f"Final parse returned unexpected type: {type(parsed).__name__}"
|
||||
|
||||
return True, parsed, ""
|
||||
|
||||
except Exception as e:
|
||||
return False, {}, f"Final parse failed: {str(e)}"
|
||||
|
||||
|
||||
def compareJsonCompleteness(
|
||||
original: Dict[str, Any],
|
||||
merged: Dict[str, Any],
|
||||
useCaseId: str
|
||||
) -> Tuple[bool, str]:
|
||||
"""
|
||||
Compare merged JSON with original to check completeness.
|
||||
|
||||
Args:
|
||||
original: Original JSON
|
||||
merged: Merged JSON (must be a dict)
|
||||
useCaseId: Use case ID
|
||||
|
||||
Returns:
|
||||
Tuple of (is_complete, message)
|
||||
"""
|
||||
# CRITICAL: Ensure merged is a dict
|
||||
if not isinstance(merged, dict):
|
||||
return False, f"Merged JSON is not a dict, got {type(merged).__name__}"
|
||||
|
||||
if useCaseId == "section_content":
|
||||
origElements = original.get("elements", [])
|
||||
mergedElements = merged.get("elements", [])
|
||||
|
||||
if not isinstance(origElements, list):
|
||||
return False, f"Original elements is not a list: {type(origElements).__name__}"
|
||||
if not isinstance(mergedElements, list):
|
||||
return False, f"Merged elements is not a list: {type(mergedElements).__name__}"
|
||||
|
||||
if len(mergedElements) < len(origElements):
|
||||
return False, f"Missing elements: {len(origElements)} expected, {len(mergedElements)} found"
|
||||
|
||||
# Check table rows
|
||||
if origElements and mergedElements:
|
||||
origTable = origElements[0] if isinstance(origElements[0], dict) else {}
|
||||
mergedTable = mergedElements[0] if isinstance(mergedElements[0], dict) else {}
|
||||
|
||||
if not origTable or not mergedTable:
|
||||
return False, f"Table structure missing: origTable={bool(origTable)}, mergedTable={bool(mergedTable)}"
|
||||
|
||||
origRows = origTable.get("content", {}).get("rows", []) if isinstance(origTable.get("content"), dict) else origTable.get("rows", [])
|
||||
mergedRows = mergedTable.get("content", {}).get("rows", []) if isinstance(mergedTable.get("content"), dict) else mergedTable.get("rows", [])
|
||||
|
||||
if not isinstance(origRows, list):
|
||||
return False, f"Original rows is not a list: {type(origRows).__name__}"
|
||||
if not isinstance(mergedRows, list):
|
||||
return False, f"Merged rows is not a list: {type(mergedRows).__name__}"
|
||||
|
||||
if len(mergedRows) < len(origRows):
|
||||
return False, f"Missing rows: {len(origRows)} expected, {len(mergedRows)} found"
|
||||
|
||||
return True, "Complete"
|
||||
|
||||
elif useCaseId == "chapter_structure":
|
||||
origChapters = original.get("documents", [{}])[0].get("chapters", [])
|
||||
mergedChapters = merged.get("documents", [{}])[0].get("chapters", [])
|
||||
|
||||
if len(mergedChapters) < len(origChapters):
|
||||
return False, f"Missing chapters: {len(origChapters)} expected, {len(mergedChapters)} found"
|
||||
|
||||
return True, "Complete"
|
||||
|
||||
elif useCaseId == "code_structure":
|
||||
origFiles = original.get("files", [])
|
||||
mergedFiles = merged.get("files", [])
|
||||
|
||||
if len(mergedFiles) < len(origFiles):
|
||||
return False, f"Missing files: {len(origFiles)} expected, {len(mergedFiles)} found"
|
||||
|
||||
return True, "Complete"
|
||||
|
||||
elif useCaseId == "code_content":
|
||||
origFiles = original.get("files", [])
|
||||
mergedFiles = merged.get("files", [])
|
||||
|
||||
if len(mergedFiles) < len(origFiles):
|
||||
return False, f"Missing files: {len(origFiles)} expected, {len(mergedFiles)} found"
|
||||
|
||||
return True, "Complete"
|
||||
|
||||
else:
|
||||
return False, f"Unknown use case: {useCaseId}"
|
||||
|
||||
|
||||
def runTestForUseCase(useCaseId: str, size: int = 50, numTests: int = 10) -> Dict[str, Any]:
|
||||
"""
|
||||
Run multiple tests for a use case with random cuts.
|
||||
|
||||
Args:
|
||||
useCaseId: Use case ID
|
||||
size: Size of test data
|
||||
numTests: Number of test runs
|
||||
|
||||
Returns:
|
||||
Test results dictionary
|
||||
"""
|
||||
results = {
|
||||
"useCaseId": useCaseId,
|
||||
"size": size,
|
||||
"numTests": numTests,
|
||||
"passed": 0,
|
||||
"failed": 0,
|
||||
"errors": []
|
||||
}
|
||||
|
||||
for testNum in range(numTests):
|
||||
try:
|
||||
# Create test JSON
|
||||
originalJson = createTestJsonForUseCase(useCaseId, size)
|
||||
originalString = json.dumps(originalJson, indent=2, ensure_ascii=False)
|
||||
|
||||
# Cut randomly
|
||||
fragments = cutJsonRandomly(originalString, numCuts=random.randint(3, 7))
|
||||
|
||||
# Test merger
|
||||
success, mergedJson, errorMsg = testMergerWithFragments(
|
||||
originalJson, fragments, useCaseId
|
||||
)
|
||||
|
||||
if not success:
|
||||
results["failed"] += 1
|
||||
results["errors"].append(f"Test {testNum + 1}: {errorMsg}")
|
||||
continue
|
||||
|
||||
# Check completeness
|
||||
isComplete, completenessMsg = compareJsonCompleteness(
|
||||
originalJson, mergedJson, useCaseId
|
||||
)
|
||||
|
||||
if isComplete:
|
||||
results["passed"] += 1
|
||||
else:
|
||||
results["failed"] += 1
|
||||
results["errors"].append(f"Test {testNum + 1}: {completenessMsg}")
|
||||
|
||||
except Exception as e:
|
||||
results["failed"] += 1
|
||||
results["errors"].append(f"Test {testNum + 1}: Exception - {str(e)}")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def runAllTests():
|
||||
"""Run tests for all use cases."""
|
||||
useCases = [
|
||||
"section_content",
|
||||
"chapter_structure",
|
||||
"code_structure",
|
||||
"code_content"
|
||||
]
|
||||
|
||||
allResults = []
|
||||
|
||||
for useCaseId in useCases:
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Testing use case: {useCaseId}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
# Initialize log file for this use case
|
||||
# Initialize log file (overwrite on each test run)
|
||||
logFileName = f"json_merger_{useCaseId}.txt"
|
||||
JsonMergeLogger.initializeLogFile(logFileName)
|
||||
print(f"Log file: {logFileName}")
|
||||
|
||||
results = runTestForUseCase(useCaseId, size=50, numTests=10)
|
||||
allResults.append(results)
|
||||
|
||||
print(f"Passed: {results['passed']}/{results['numTests']}")
|
||||
print(f"Failed: {results['failed']}/{results['numTests']}")
|
||||
|
||||
if results["errors"]:
|
||||
print("\nErrors:")
|
||||
for error in results["errors"][:5]: # Show first 5 errors
|
||||
print(f" - {error}")
|
||||
|
||||
# Summary
|
||||
print(f"\n{'='*60}")
|
||||
print("SUMMARY")
|
||||
print(f"{'='*60}")
|
||||
|
||||
totalPassed = sum(r["passed"] for r in allResults)
|
||||
totalFailed = sum(r["failed"] for r in allResults)
|
||||
totalTests = sum(r["numTests"] for r in allResults)
|
||||
|
||||
print(f"Total tests: {totalTests}")
|
||||
print(f"Passed: {totalPassed}")
|
||||
print(f"Failed: {totalFailed}")
|
||||
print(f"Success rate: {totalPassed / totalTests * 100:.1f}%")
|
||||
|
||||
return allResults
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Set up logging - use WARNING level to reduce noise from jsonUtils
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
|
||||
# Run tests
|
||||
results = runAllTests()
|
||||
|
||||
# Save results to file (in project root)
|
||||
resultsFile = os.path.join(projectRoot, "test_json_merger_results.json")
|
||||
with open(resultsFile, "w", encoding="utf-8") as f:
|
||||
json.dump(results, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"\nResults saved to {resultsFile}")
|
||||
|
|
@ -640,6 +640,7 @@ Return ONLY valid JSON matching the request above.
|
|||
```
|
||||
"""
|
||||
|
||||
# Build base prompt
|
||||
contentPrompt = f"""# TASK: Generate Code File Content
|
||||
|
||||
Generate complete, executable code for the file: {filename}
|
||||
|
|
@ -678,6 +679,130 @@ Return ONLY valid JSON in this format:
|
|||
}}
|
||||
"""
|
||||
|
||||
# Build continuation prompt builder
|
||||
async def buildCodeContentPromptWithContinuation(
|
||||
continuationContext: Optional[Dict[str, Any]] = None,
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""Build code content prompt with optional continuation context. Extracts code-specific parameters from kwargs."""
|
||||
# Extract parameters from kwargs (for code_content use case)
|
||||
filename = kwargs.get("filename", "")
|
||||
fileType = kwargs.get("fileType", "")
|
||||
functions = kwargs.get("functions", [])
|
||||
classes = kwargs.get("classes", [])
|
||||
dependencies = kwargs.get("dependencies", [])
|
||||
metadata = kwargs.get("metadata", {})
|
||||
userPrompt = kwargs.get("userPrompt", "")
|
||||
contentParts = kwargs.get("contentParts", [])
|
||||
contextInfo = kwargs.get("contextInfo", "")
|
||||
|
||||
# Rebuild base prompt (same as initial prompt)
|
||||
userRequestSection = ""
|
||||
if userPrompt:
|
||||
userRequestSection = f"""
|
||||
## ORIGINAL USER REQUEST
|
||||
```
|
||||
{userPrompt}
|
||||
```
|
||||
"""
|
||||
|
||||
contentPartsSection = ""
|
||||
if contentParts:
|
||||
relevantParts = []
|
||||
for part in contentParts:
|
||||
usageHint = part.metadata.get('usageHint', '').lower()
|
||||
originalFileName = part.metadata.get('originalFileName', '').lower()
|
||||
filenameLower = filename.lower()
|
||||
|
||||
if (filenameLower in usageHint or
|
||||
filenameLower in originalFileName or
|
||||
part.metadata.get('contentFormat') == 'reference' or
|
||||
(part.data and len(str(part.data).strip()) > 0)):
|
||||
relevantParts.append(part)
|
||||
|
||||
if relevantParts:
|
||||
contentPartsSection = "\n## AVAILABLE CONTENT PARTS\n"
|
||||
for i, part in enumerate(relevantParts, 1):
|
||||
contentFormat = part.metadata.get("contentFormat", "unknown")
|
||||
originalFileName = part.metadata.get('originalFileName', 'N/A')
|
||||
contentPartsSection += f"\n{i}. ContentPart ID: {part.id}\n"
|
||||
contentPartsSection += f" Format: {contentFormat}\n"
|
||||
contentPartsSection += f" Type: {part.typeGroup}\n"
|
||||
contentPartsSection += f" Original file name: {originalFileName}\n"
|
||||
contentPartsSection += f" Usage hint: {part.metadata.get('usageHint', 'N/A')}\n"
|
||||
if part.data and isinstance(part.data, str) and len(part.data) < 2000:
|
||||
contentPartsSection += f" Content preview: {part.data[:500]}...\n"
|
||||
|
||||
basePrompt = f"""# TASK: Generate Code File Content
|
||||
|
||||
Generate complete, executable code for the file: {filename}
|
||||
{userRequestSection}## FILE SPECIFICATIONS
|
||||
|
||||
File Type: {fileType}
|
||||
Language: {metadata.get('language', 'python') if metadata else 'python'}
|
||||
{contentPartsSection}
|
||||
|
||||
Required functions:
|
||||
{json.dumps(functions, indent=2) if functions else 'None specified'}
|
||||
|
||||
Required classes:
|
||||
{json.dumps(classes, indent=2) if classes else 'None specified'}
|
||||
|
||||
Dependencies on other files: {', '.join(dependencies) if dependencies else 'None'}
|
||||
{contextInfo}
|
||||
|
||||
Generate complete, production-ready code with:
|
||||
1. Proper imports (including imports from other files in the project if dependencies exist)
|
||||
2. All required functions and classes
|
||||
3. Error handling
|
||||
4. Documentation/docstrings
|
||||
5. Type hints where appropriate
|
||||
|
||||
Return ONLY valid JSON in this format:
|
||||
{{
|
||||
"files": [
|
||||
{{
|
||||
"filename": "{filename}",
|
||||
"content": "// Complete code here",
|
||||
"functions": {json.dumps(functions, indent=2) if functions else '[]'},
|
||||
"classes": {json.dumps(classes, indent=2) if classes else '[]'}
|
||||
}}
|
||||
]
|
||||
}}
|
||||
"""
|
||||
|
||||
if continuationContext:
|
||||
# Add continuation instructions
|
||||
deliveredSummary = continuationContext.get("delivered_summary", "")
|
||||
elementBeforeCutoff = continuationContext.get("element_before_cutoff", "")
|
||||
cutOffElement = continuationContext.get("cut_off_element", "")
|
||||
|
||||
continuationText = f"{deliveredSummary}\n\n"
|
||||
continuationText += "⚠️ CONTINUATION: Response was cut off. Generate ONLY the remaining content that comes AFTER the reference elements below.\n\n"
|
||||
|
||||
if elementBeforeCutoff:
|
||||
continuationText += "# REFERENCE: Last complete element (already delivered - DO NOT repeat):\n"
|
||||
continuationText += f"{elementBeforeCutoff}\n\n"
|
||||
|
||||
if cutOffElement:
|
||||
continuationText += "# REFERENCE: Incomplete element (cut off here - DO NOT repeat):\n"
|
||||
continuationText += f"{cutOffElement}\n\n"
|
||||
|
||||
continuationText += "⚠️ CRITICAL: The elements above are REFERENCE ONLY. They are already delivered.\n"
|
||||
continuationText += "Generate ONLY what comes AFTER these elements. DO NOT regenerate the entire JSON structure.\n"
|
||||
continuationText += "Continue generating the remaining code content now.\n\n"
|
||||
|
||||
return f"""{basePrompt}
|
||||
|
||||
--- CONTINUATION REQUEST ---
|
||||
|
||||
{continuationText}
|
||||
|
||||
Continue generating the remaining code content now.
|
||||
"""
|
||||
else:
|
||||
return basePrompt
|
||||
|
||||
# Use generic looping system with code_content use case
|
||||
options = AiCallOptions(
|
||||
operationType=OperationTypeEnum.DATA_GENERATE,
|
||||
|
|
@ -687,6 +812,19 @@ Return ONLY valid JSON in this format:
|
|||
contentJson = await self.services.ai.callAiWithLooping(
|
||||
prompt=contentPrompt,
|
||||
options=options,
|
||||
promptBuilder=buildCodeContentPromptWithContinuation,
|
||||
promptArgs={
|
||||
"filename": filename,
|
||||
"fileType": fileType,
|
||||
"functions": functions,
|
||||
"classes": classes,
|
||||
"dependencies": dependencies,
|
||||
"metadata": metadata,
|
||||
"userPrompt": userPrompt,
|
||||
"contentParts": contentParts,
|
||||
"contextInfo": contextInfo,
|
||||
"services": self.services
|
||||
},
|
||||
useCaseId="code_content",
|
||||
debugPrefix=f"code_content_{fileStructure.get('id', 'file')}",
|
||||
)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue