# AI Call Iteration Flow - JSON Merging System This document describes the iteration flow for handling large JSON responses from AI that may be truncated and need to be merged across multiple iterations. ## Overview When an AI response is too large, it may be truncated (cut) at an arbitrary point. The iteration system: 1. Detects incomplete JSON 2. Requests continuation from the AI 3. Merges the continuation with the existing JSON 4. Repeats until complete or max failures reached --- ## Key Variables | Variable | Type | Purpose | |----------|------|---------| | `jsonBase` | `str \| None` | The merged JSON string (CUT version for overlap matching) | | `candidateJson` | `str` | Temporary holder for merged result until validated | | `lastValidCompletePart` | `str \| None` | Fallback - last successfully parsed CLOSED JSON | | `lastOverlapContext` | `str` | Context for retry/continuation prompts | | `lastHierarchyContextForPrompt` | `str` | Context for retry/continuation prompts | | `mergeFailCount` | `int` | Global counter (max 3 failures) | --- ## Key Distinction: hierarchyContext vs completePart | Field | Description | Use Case | |-------|-------------|----------| | `hierarchyContext` | **CUT JSON** - truncated at cut point | Used as `jsonBase` for merging with next AI fragment | | `completePart` | **CLOSED JSON** - all structures properly closed | Used for validation, parsing, and fallback | **Why this matters:** - The next AI fragment starts with an **overlap** that matches the CUT point - If we used `completePart` (closed), the overlap detection would FAIL - We must use `hierarchyContext` (cut) so overlap matching works correctly --- ## Flow Steps ### Step 1: BUILD PROMPT **Location:** `subAiCallLooping.py` lines 163-212 **Function:** `buildContinuationContext()` from `modules/shared/jsonUtils.py` - **First iteration:** Use original prompt - **Continuation:** `buildContinuationContext(allSections, lastRawResponse, ...)` - Internally calls `getContexts(lastRawResponse)` to get overlap/hierarchy - Builds continuation prompt with `overlapContext` + `hierarchyContextForPrompt` ### Step 2: CALL AI **Location:** `subAiCallLooping.py` lines 214-299 **Function:** `self.aiService.callAi(request)` - Returns `response.content` as `result` - NOTE: Do NOT update `lastRawResponse` yet! (only after successful merge) ### Step 4: MERGE **Location:** `subAiCallLooping.py` lines 338-396 **Function:** `JsonResponseHandler.mergeJsonStringsWithOverlap()` from `modules/services/serviceAi/subJsonResponseHandling.py` ``` IF first iteration (jsonBase is None): → candidateJson = result ELSE: → mergedJsonString, hasOverlap = mergeJsonStringsWithOverlap(jsonBase, result) IF hasOverlap = False (MERGE FAILED): → mergeFailCount++ → If mergeFailCount >= 3: return lastValidCompletePart (fallback) → Else: continue (retry with unchanged jsonBase AND lastRawResponse!) ELSE: → candidateJson = mergedJsonString (don't update jsonBase yet!) → lastRawResponse = candidateJson (ONLY after first iteration or successful merge!) TRY DIRECT PARSE of candidateJson: IF parse succeeds: → jsonBase = candidateJson (commit) → FINISHED! Return normalized result ELSE: → Proceed to Step 5 ``` ### Step 5: GET CONTEXTS **Location:** `subAiCallLooping.py` lines 420-427 **Function:** `getContexts()` from `modules/shared/jsonContinuation.py` ```python contexts = getContexts(candidateJson) ``` Returns `JsonContinuationContexts`: - `overlapContext`: `""` if JSON is complete (no cut point) - `hierarchyContext`: CUT JSON (for merging with next fragment) - `hierarchyContextForPrompt`: CUT JSON with budget limits (for prompts) - `completePart`: CLOSED JSON (repaired if needed) - `jsonParsingSuccess`: `True` if completePart is valid JSON **Enhancement:** If original JSON is already complete → `overlapContext = ""` This signals "JSON is complete, no more continuation needed" ### Step 6: DECIDE **Location:** `subAiCallLooping.py` lines 429-528 #### Case A: `jsonParsingSuccess=true` AND `overlapContext=""` **→ FINISHED** - JSON is complete (no cut point) - `jsonBase = contexts.completePart` (use CLOSED version for final result) - Return `completePart` as result #### Case B: `jsonParsingSuccess=true` AND `overlapContext!=""` **→ CONTINUE to next iteration** - JSON parseable but has cut point - `jsonBase = contexts.hierarchyContext` ← **CUT version for next merge!** - `lastValidCompletePart = contexts.completePart` ← **CLOSED version for fallback** - Store contexts for next prompt - `mergeFailCount = 0` (reset on success) - `lastRawResponse = jsonBase` - Continue to next iteration #### Case C: `jsonParsingSuccess=false` **→ RETRY with same prompt** - Do NOT update `jsonBase` (keep previous valid state) - `mergeFailCount++` - If `mergeFailCount >= 3`: return `lastValidCompletePart` (fallback) - Else: continue (retry with unchanged jsonBase/lastRawResponse) --- ## Flow Diagram ``` ┌───────────────────────────────────────────────────────────────┐ │ ITERATION START │ └───────────────────────────┬───────────────────────────────────┘ │ ┌───────────────────────────▼───────────────────────────────────┐ │ STEP 1: BUILD PROMPT │ │ - First: original prompt │ │ - Next: buildContinuationContext(lastRawResponse) │ └───────────────────────────┬───────────────────────────────────┘ │ ┌───────────────────────────▼───────────────────────────────────┐ │ STEP 2: CALL AI → result │ └───────────────────────────┬───────────────────────────────────┘ │ ┌───────────────────────────▼───────────────────────────────────┐ │ STEP 4: MERGE jsonBase + result → candidateJson │ └───────────────────────────┬───────────────────────────────────┘ │ ┌────────────▼────────────┐ │ Merge OK? │ └────────────┬────────────┘ │ ┌─────────────────────┼─────────────────────┐ │ NO │ YES │ ▼ ▼ │ ┌──────────────┐ ┌──────────────────┐ │ │ fails++ │ │ TRY DIRECT PARSE │ │ │ if >=3: │ │ of candidateJson │ │ │ RETURN │ └────────┬─────────┘ │ │ fallback │ │ │ │ else: RETRY │ ┌────────▼─────────┐ │ │ (continue) │ │ Parse OK? │ │ └──────────────┘ └────────┬─────────┘ │ │ │ ┌─────────────────────┼─────────────────────┐ │ YES │ NO │ ▼ ▼ │ ┌──────────────┐ ┌──────────────────────────────┐ │ FINISHED ✓ │ │ STEP 5: getContexts() │ │ Return │ │ → jsonParsingSuccess │ │ normalized │ │ → overlapContext │ │ result │ └────────────┬─────────────────┘ └──────────────┘ │ ┌────────────▼────────────────────┐ │ STEP 6: DECIDE │ └────────────┬────────────────────┘ │ ┌────────────────────────────┼────────────────────────────┐ │ │ │ ▼ ▼ ▼ ┌───────────────────┐ ┌───────────────────────┐ ┌───────────────────┐ │ success=true │ │ success=true │ │ success=false │ │ overlap="" │ │ overlap!="" │ │ │ │ ───────────── │ │ ───────────────── │ │ ───────────── │ │ FINISHED ✓ │ │ CONTINUE │ │ RETRY │ │ │ │ │ │ │ │ jsonBase = │ │ jsonBase = │ │ jsonBase unchanged│ │ completePart │ │ hierarchyContext │ │ fails++ │ │ (CLOSED) │ │ (CUT for merge!) │ │ │ │ │ │ │ │ if >=3: fallback │ │ Return result │ │ fallback = │ │ else: retry │ │ │ │ completePart │ │ │ │ │ │ (CLOSED) │ │ │ │ │ │ │ │ │ │ │ │ Next iteration → │ │ │ └───────────────────┘ └───────────────────────┘ └───────────────────┘ ``` --- ## Files Involved | File | Purpose | |------|---------| | `modules/services/serviceAi/subAiCallLooping.py` | Main iteration loop | | `modules/shared/jsonContinuation.py` | `getContexts()` - context extraction & repair | | `modules/shared/jsonUtils.py` | `buildContinuationContext()` - prompt building | | `modules/services/serviceAi/subJsonResponseHandling.py` | `mergeJsonStringsWithOverlap()` | | `modules/services/serviceAi/subJsonMerger.py` | `ModularJsonMerger` - actual merge logic | | `modules/datamodels/datamodelAi.py` | `JsonContinuationContexts` model | --- ## Error Handling ### Merge Failures - Max 3 consecutive failures allowed - On failure: retry with unchanged `jsonBase` (previous valid state) - After 3 failures: return `lastValidCompletePart` as fallback ### Parse Failures - If `getContexts()` cannot produce valid JSON: increment fail counter - Retry with same prompt (don't update jsonBase) - After 3 failures: return `lastValidCompletePart` as fallback ### Fallback Strategy - `lastValidCompletePart` stores the last successfully parsed CLOSED JSON - Always available as fallback when things go wrong - Ensures we return valid JSON even after multiple failures