diff --git a/modules/services/serviceAi/subAiCallLooping-flow.md b/modules/services/serviceAi/subAiCallLooping-flow.md index e68b2f3c..0a7ac854 100644 --- a/modules/services/serviceAi/subAiCallLooping-flow.md +++ b/modules/services/serviceAi/subAiCallLooping-flow.md @@ -57,7 +57,7 @@ When an AI response is too large, it may be truncated (cut) at an arbitrary poin **Function:** `self.aiService.callAi(request)` - Returns `response.content` as `result` -- Store raw response: `lastRawResponse = result` +- NOTE: Do NOT update `lastRawResponse` yet! (only after successful merge) ### Step 4: MERGE @@ -73,10 +73,12 @@ ELSE: IF hasOverlap = False (MERGE FAILED): → mergeFailCount++ → If mergeFailCount >= 3: return lastValidCompletePart (fallback) - → Else: continue (retry with unchanged jsonBase) + → 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) diff --git a/modules/services/serviceAi/subAiCallLooping.py b/modules/services/serviceAi/subAiCallLooping.py index f8eef9b9..6427b2e0 100644 --- a/modules/services/serviceAi/subAiCallLooping.py +++ b/modules/services/serviceAi/subAiCallLooping.py @@ -307,8 +307,9 @@ class AiCallLooper: self.services.chat.progressLogFinish(iterationOperationId, True) return result - # Store raw response for continuation (even if broken) - lastRawResponse = result + # NOTE: Do NOT update lastRawResponse here! + # lastRawResponse should only be updated after successful merge + # This ensures retry iterations use the correct base context # Handle use cases that return JSON directly (no section extraction needed) # Check if use case supports direct return (all registered use cases do) @@ -387,6 +388,11 @@ class AiCallLooper: candidateJson = mergedJsonString logger.debug(f"Iteration {iteration}: Merge succeeded, candidateJson ({len(candidateJson)} chars)") + # Update lastRawResponse ONLY after we have a valid candidateJson + # (first iteration or successful merge - NOT on merge failure!) + # This ensures retry iterations use the correct base context + lastRawResponse = candidateJson + # Try direct parse of candidate try: extracted = extractJsonString(candidateJson) diff --git a/modules/shared/jsonContinuation.py b/modules/shared/jsonContinuation.py index 324b133d..a0ea2ea5 100644 --- a/modules/shared/jsonContinuation.py +++ b/modules/shared/jsonContinuation.py @@ -333,8 +333,9 @@ class JsonAnalyzer: cutPos = len(self.jsonStr) # Build both hierarchy contexts from the SAME structure BEFORE generating complete part - # Generate hierarchy context WITHOUT budget (full structure for internal use) - hierarchyContext = self._renderFromStructure(structure) + # CRITICAL: hierarchyContext must be the EXACT original JSON (for merge overlap detection!) + # The rendered version would have different formatting, breaking overlap matching + hierarchyContext = self.jsonStr # Generate hierarchy context WITH budget (for prompts) - uses same structure hierarchyContextForPrompt = self._renderWithBudgetFromStructure(structure, cutPos)