implemented enhanced standardized ai looping system with usecases
This commit is contained in:
parent
64590aa61e
commit
1c0604106b
7 changed files with 610 additions and 584 deletions
|
|
@ -6,8 +6,6 @@ from enum import Enum
|
|||
|
||||
# Import ContentPart for runtime use (needed for Pydantic model rebuilding)
|
||||
from modules.datamodels.datamodelExtraction import ContentPart
|
||||
# Import JSON utilities for safe conversion
|
||||
from modules.shared.jsonUtils import extractJsonString, tryParseJson, repairBrokenJson
|
||||
|
||||
# Operation Types
|
||||
class OperationTypeEnum(str, Enum):
|
||||
|
|
@ -258,3 +256,53 @@ class JsonAccumulationState(BaseModel):
|
|||
description="KPI definitions with current values: [{id, description, jsonPath, targetValue, currentValue}, ...]"
|
||||
)
|
||||
|
||||
|
||||
class ContinuationContext(BaseModel):
|
||||
"""Pydantic model for continuation context information."""
|
||||
section_count: int
|
||||
delivered_summary: str
|
||||
cut_off_element: Optional[str] = None
|
||||
element_before_cutoff: Optional[str] = None
|
||||
template_structure: Optional[str] = None
|
||||
last_complete_part: Optional[str] = None
|
||||
incomplete_part: Optional[str] = None
|
||||
structure_context: Optional[str] = None
|
||||
last_raw_json: Optional[str] = None
|
||||
|
||||
|
||||
class SectionPromptArgs(BaseModel):
|
||||
"""Type-safe arguments for section content prompt builder."""
|
||||
section: Dict[str, Any]
|
||||
contentParts: List[ContentPart]
|
||||
userPrompt: str
|
||||
generationHint: str
|
||||
allSections: List[Dict[str, Any]]
|
||||
sectionIndex: int
|
||||
isAggregation: bool
|
||||
language: str
|
||||
|
||||
|
||||
class ChapterStructurePromptArgs(BaseModel):
|
||||
"""Type-safe arguments for chapter structure prompt builder."""
|
||||
userPrompt: str
|
||||
contentParts: List[ContentPart] = Field(default_factory=list)
|
||||
outputFormat: str
|
||||
|
||||
|
||||
class CodeContentPromptArgs(BaseModel):
|
||||
"""Type-safe arguments for code content prompt builder."""
|
||||
filename: str
|
||||
fileType: str
|
||||
functions: List[Dict] = Field(default_factory=list)
|
||||
classes: List[Dict] = Field(default_factory=list)
|
||||
dependencies: List[str] = Field(default_factory=list)
|
||||
metadata: Dict[str, Any] = Field(default_factory=dict)
|
||||
userPrompt: str
|
||||
contentParts: List[ContentPart] = Field(default_factory=list)
|
||||
contextInfo: str = ""
|
||||
|
||||
|
||||
class CodeStructurePromptArgs(BaseModel):
|
||||
"""Type-safe arguments for code structure prompt builder."""
|
||||
userPrompt: str
|
||||
contentParts: List[ContentPart] = Field(default_factory=list)
|
||||
|
|
@ -12,7 +12,9 @@ import json
|
|||
import logging
|
||||
from typing import Dict, Any, List, Optional, Callable
|
||||
|
||||
from modules.datamodels.datamodelAi import AiCallRequest, AiCallOptions, OperationTypeEnum, PriorityEnum, ProcessingModeEnum, JsonAccumulationState
|
||||
from modules.datamodels.datamodelAi import (
|
||||
AiCallRequest, AiCallOptions
|
||||
)
|
||||
from modules.datamodels.datamodelExtraction import ContentPart
|
||||
from modules.shared.jsonUtils import buildContinuationContext, extractJsonString, tryParseJson
|
||||
from modules.services.serviceAi.subJsonResponseHandling import JsonResponseHandler
|
||||
|
|
@ -110,18 +112,38 @@ class AiCallLooper:
|
|||
# CRITICAL: Build continuation prompt if we have sections OR if we have a previous response (even if broken)
|
||||
# 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:
|
||||
# Extract templateStructure and basePrompt from promptArgs (REQUIRED)
|
||||
templateStructure = promptArgs.get("templateStructure")
|
||||
if not templateStructure:
|
||||
raise ValueError(
|
||||
f"templateStructure is REQUIRED in promptArgs for use case '{useCaseId}'. "
|
||||
"Prompt creation functions must return (prompt, templateStructure) tuple."
|
||||
)
|
||||
|
||||
basePrompt = promptArgs.get("basePrompt")
|
||||
if not basePrompt:
|
||||
# Fallback: use prompt parameter (should be the same)
|
||||
basePrompt = prompt
|
||||
logger.warning(
|
||||
f"basePrompt not found in promptArgs for use case '{useCaseId}', "
|
||||
"using prompt parameter instead. This may indicate a bug."
|
||||
)
|
||||
|
||||
# This is a continuation - build continuation context with raw JSON and rebuild prompt
|
||||
continuationContext = buildContinuationContext(allSections, lastRawResponse, useCaseId)
|
||||
continuationContext = buildContinuationContext(
|
||||
allSections, lastRawResponse, useCaseId, templateStructure
|
||||
)
|
||||
if not lastRawResponse:
|
||||
logger.warning(f"Iteration {iteration}: No previous response available for continuation!")
|
||||
|
||||
# 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)
|
||||
# Unified prompt builder call: Continuation builders only need continuationContext, templateStructure, and basePrompt
|
||||
# All initial context (section, userPrompt, etc.) is already in basePrompt, so promptArgs is not needed
|
||||
# Extract templateStructure and basePrompt from promptArgs (they're explicit parameters)
|
||||
iterationPrompt = await promptBuilder(
|
||||
continuationContext=continuationContext,
|
||||
templateStructure=templateStructure,
|
||||
basePrompt=basePrompt
|
||||
)
|
||||
else:
|
||||
# First iteration - use original prompt
|
||||
iterationPrompt = prompt
|
||||
|
|
@ -238,11 +260,10 @@ class AiCallLooper:
|
|||
pass
|
||||
|
||||
# Handle use cases that return JSON directly (no section extraction needed)
|
||||
directReturnUseCases = ["section_content", "chapter_structure", "code_structure", "code_content"]
|
||||
if useCaseId in directReturnUseCases:
|
||||
# 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:
|
||||
# Check if use case supports direct return (all registered use cases do)
|
||||
if useCase and not useCase.requiresExtraction:
|
||||
# For all direct return use cases, check completeness and support looping
|
||||
if True: # All registered use cases support looping
|
||||
# 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
|
||||
|
|
@ -310,7 +331,8 @@ class AiCallLooper:
|
|||
extracted = extractJsonString(mergedJsonString)
|
||||
parsed, parseErr, _ = tryParseJson(extracted)
|
||||
if parseErr is None and parsed:
|
||||
normalized = self._normalizeJsonStructure(parsed, useCaseId)
|
||||
# Use callback to normalize JSON structure
|
||||
normalized = self._normalizeJsonStructure(parsed, useCase)
|
||||
parsedJsonForUseCase = normalized
|
||||
result = json.dumps(normalized, indent=2, ensure_ascii=False)
|
||||
except Exception:
|
||||
|
|
@ -322,8 +344,8 @@ class AiCallLooper:
|
|||
parsed, parseErr, _ = tryParseJson(extracted)
|
||||
|
||||
if parseErr is None and parsed:
|
||||
# Parsing succeeded - normalize and use
|
||||
normalized = self._normalizeJsonStructure(parsed, useCaseId)
|
||||
# Parsing succeeded - normalize and use (via callback)
|
||||
normalized = self._normalizeJsonStructure(parsed, useCase)
|
||||
parsedJsonForUseCase = normalized
|
||||
result = json.dumps(normalized, indent=2, ensure_ascii=False)
|
||||
else:
|
||||
|
|
@ -334,7 +356,8 @@ class AiCallLooper:
|
|||
extracted = extractJsonString(jsonStr)
|
||||
parsed, parseErr, _ = tryParseJson(extracted)
|
||||
if parseErr is None and parsed:
|
||||
normalized = self._normalizeJsonStructure(parsed, useCaseId)
|
||||
# Use callback to normalize JSON structure
|
||||
normalized = self._normalizeJsonStructure(parsed, useCase)
|
||||
allParsed.append(normalized)
|
||||
|
||||
if allParsed:
|
||||
|
|
@ -399,18 +422,16 @@ class AiCallLooper:
|
|||
if iterationOperationId:
|
||||
self.services.chat.progressLogFinish(iterationOperationId, True)
|
||||
|
||||
# For section_content, return raw result to allow merging of multiple JSON blocks
|
||||
# The merging logic in subStructureFilling.py will handle extraction and merging
|
||||
if useCaseId == "section_content":
|
||||
final_json = result # Return raw response to preserve all JSON blocks
|
||||
# Write final merged result for section_content (overwrites iteration 1 response with complete merged result)
|
||||
self.services.utils.writeDebugFile(final_json, f"{debugPrefix}_response")
|
||||
else:
|
||||
final_json = json.dumps(parsedJsonForUseCase, indent=2, ensure_ascii=False) if parsedJsonForUseCase else (extractedJsonForUseCase or result)
|
||||
|
||||
# Write final result for chapter structure and code structure
|
||||
if useCaseId in ["chapter_structure", "code_structure"]:
|
||||
self.services.utils.writeDebugFile(final_json, f"{debugPrefix}_final_result")
|
||||
# Use callback to handle final result formatting and debug file writing (REQUIRED - no fallback)
|
||||
if not useCase.finalResultHandler:
|
||||
raise ValueError(
|
||||
f"Use case '{useCaseId}' is missing required 'finalResultHandler' callback. "
|
||||
"All use cases must provide a finalResultHandler function."
|
||||
)
|
||||
final_json = useCase.finalResultHandler(
|
||||
result, parsedJsonForUseCase, extractedJsonForUseCase,
|
||||
debugPrefix, self.services
|
||||
)
|
||||
|
||||
return final_json
|
||||
|
||||
|
|
@ -423,8 +444,8 @@ class AiCallLooper:
|
|||
if iteration >= maxIterations:
|
||||
logger.warning(f"AI call stopped after maximum iterations ({maxIterations})")
|
||||
|
||||
# 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
|
||||
# This code path should never be reached because all registered use cases
|
||||
# return early when JSON is complete. This 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 ""
|
||||
|
|
@ -539,51 +560,23 @@ class AiCallLooper:
|
|||
# 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:
|
||||
def _normalizeJsonStructure(self, parsed: Any, useCase) -> Any:
|
||||
"""
|
||||
Normalize JSON structure to ensure consistent format before merging.
|
||||
Handles different response formats and converts them to expected structure.
|
||||
|
||||
Args:
|
||||
parsed: Parsed JSON object (can be dict, list, or primitive)
|
||||
useCaseId: Use case ID to determine expected structure
|
||||
useCase: LoopingUseCase instance with jsonNormalizer callback
|
||||
|
||||
Returns:
|
||||
Normalized JSON structure
|
||||
"""
|
||||
# For section_content, expect {"elements": [...]} structure
|
||||
if useCaseId == "section_content":
|
||||
if isinstance(parsed, list):
|
||||
# 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
|
||||
# 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}
|
||||
elif isinstance(parsed, dict):
|
||||
# If it already has "elements", return as-is
|
||||
if "elements" in parsed:
|
||||
return parsed
|
||||
# If it has "type" and looks like an element, wrap in elements array
|
||||
elif parsed.get("type"):
|
||||
return {"elements": [parsed]}
|
||||
# Otherwise, assume it's already in correct format
|
||||
else:
|
||||
return parsed
|
||||
|
||||
# For other use cases, return as-is (they have their own structures)
|
||||
return parsed
|
||||
# Use callback to normalize JSON structure (REQUIRED - no fallback)
|
||||
if not useCase or not useCase.jsonNormalizer:
|
||||
raise ValueError(
|
||||
f"Use case '{useCase.useCaseId if useCase else 'unknown'}' is missing required 'jsonNormalizer' callback. "
|
||||
"All use cases must provide a jsonNormalizer function."
|
||||
)
|
||||
return useCase.jsonNormalizer(parsed, useCase.useCaseId)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,89 @@ from typing import Dict, Any, List, Optional, Callable
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Callback functions for use-case-specific logic
|
||||
|
||||
def _handleSectionContentFinalResult(result: str, parsedJsonForUseCase: Any, extractedJsonForUseCase: str,
|
||||
debugPrefix: str, services: Any) -> str:
|
||||
"""Handle final result for section_content: return raw result to preserve all JSON blocks."""
|
||||
final_json = result # Return raw response to preserve all JSON blocks
|
||||
# Write final merged result for section_content (overwrites iteration 1 response with complete merged result)
|
||||
if services and hasattr(services, 'utils') and hasattr(services.utils, 'writeDebugFile'):
|
||||
services.utils.writeDebugFile(final_json, f"{debugPrefix}_response")
|
||||
return final_json
|
||||
|
||||
|
||||
def _handleChapterStructureFinalResult(result: str, parsedJsonForUseCase: Any, extractedJsonForUseCase: str,
|
||||
debugPrefix: str, services: Any) -> str:
|
||||
"""Handle final result for chapter_structure: format JSON and write debug file."""
|
||||
import json
|
||||
final_json = json.dumps(parsedJsonForUseCase, indent=2, ensure_ascii=False) if parsedJsonForUseCase else (extractedJsonForUseCase or result)
|
||||
# Write final result for chapter structure
|
||||
if services and hasattr(services, 'utils') and hasattr(services.utils, 'writeDebugFile'):
|
||||
services.utils.writeDebugFile(final_json, f"{debugPrefix}_final_result")
|
||||
return final_json
|
||||
|
||||
|
||||
def _handleCodeStructureFinalResult(result: str, parsedJsonForUseCase: Any, extractedJsonForUseCase: str,
|
||||
debugPrefix: str, services: Any) -> str:
|
||||
"""Handle final result for code_structure: format JSON and write debug file."""
|
||||
import json
|
||||
final_json = json.dumps(parsedJsonForUseCase, indent=2, ensure_ascii=False) if parsedJsonForUseCase else (extractedJsonForUseCase or result)
|
||||
# Write final result for code structure
|
||||
if services and hasattr(services, 'utils') and hasattr(services.utils, 'writeDebugFile'):
|
||||
services.utils.writeDebugFile(final_json, f"{debugPrefix}_final_result")
|
||||
return final_json
|
||||
|
||||
|
||||
def _handleCodeContentFinalResult(result: str, parsedJsonForUseCase: Any, extractedJsonForUseCase: str,
|
||||
debugPrefix: str, services: Any) -> str:
|
||||
"""Handle final result for code_content: format JSON."""
|
||||
import json
|
||||
final_json = json.dumps(parsedJsonForUseCase, indent=2, ensure_ascii=False) if parsedJsonForUseCase else (extractedJsonForUseCase or result)
|
||||
return final_json
|
||||
|
||||
|
||||
def _normalizeSectionContentJson(parsed: Any, useCaseId: str) -> Any:
|
||||
"""Normalize JSON structure for section_content use case."""
|
||||
# For section_content, expect {"elements": [...]} structure
|
||||
if isinstance(parsed, list):
|
||||
# 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
|
||||
# Try to convert strings to paragraph elements as fallback
|
||||
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}
|
||||
elif isinstance(parsed, dict):
|
||||
# If it already has "elements", return as-is
|
||||
if "elements" in parsed:
|
||||
return parsed
|
||||
# If it has "type" and looks like an element, wrap in elements array
|
||||
elif parsed.get("type"):
|
||||
return {"elements": [parsed]}
|
||||
# Otherwise, assume it's already in correct format
|
||||
else:
|
||||
return parsed
|
||||
|
||||
# For other use cases, return as-is (they have their own structures)
|
||||
return parsed
|
||||
|
||||
|
||||
def _normalizeDefaultJson(parsed: Any, useCaseId: str) -> Any:
|
||||
"""Default normalizer: return as-is."""
|
||||
return parsed
|
||||
|
||||
|
||||
@dataclass
|
||||
class LoopingUseCase:
|
||||
|
|
@ -39,6 +122,10 @@ class LoopingUseCase:
|
|||
# Result Building
|
||||
resultBuilder: Optional[Callable] = None # Build final result from accumulated data
|
||||
|
||||
# Use-case-specific handlers (callbacks to avoid if/elif chains in generic code)
|
||||
finalResultHandler: Optional[Callable] = None # Handle final result formatting and debug file writing
|
||||
jsonNormalizer: Optional[Callable] = None # Normalize JSON structure for this use case
|
||||
|
||||
# Metadata
|
||||
supportsAccumulation: bool = True # Whether this use case supports accumulation
|
||||
requiresExtraction: bool = False # Whether this requires extraction (like sections)
|
||||
|
|
@ -124,6 +211,8 @@ class LoopingUseCaseRegistry:
|
|||
merger=None,
|
||||
continuationContextBuilder=None, # Will use default continuation context
|
||||
resultBuilder=None, # Return JSON directly
|
||||
finalResultHandler=_handleSectionContentFinalResult,
|
||||
jsonNormalizer=_normalizeSectionContentJson,
|
||||
supportsAccumulation=False,
|
||||
requiresExtraction=False
|
||||
))
|
||||
|
|
@ -141,6 +230,8 @@ class LoopingUseCaseRegistry:
|
|||
merger=None,
|
||||
continuationContextBuilder=None,
|
||||
resultBuilder=None, # Return JSON directly
|
||||
finalResultHandler=_handleChapterStructureFinalResult,
|
||||
jsonNormalizer=_normalizeDefaultJson,
|
||||
supportsAccumulation=False,
|
||||
requiresExtraction=False
|
||||
))
|
||||
|
|
@ -174,6 +265,8 @@ class LoopingUseCaseRegistry:
|
|||
merger=None,
|
||||
continuationContextBuilder=None,
|
||||
resultBuilder=None,
|
||||
finalResultHandler=_handleCodeStructureFinalResult,
|
||||
jsonNormalizer=_normalizeDefaultJson,
|
||||
supportsAccumulation=False,
|
||||
requiresExtraction=False
|
||||
))
|
||||
|
|
@ -190,6 +283,8 @@ class LoopingUseCaseRegistry:
|
|||
merger=None, # Will use default merger
|
||||
continuationContextBuilder=None,
|
||||
resultBuilder=None, # Will use default result builder
|
||||
finalResultHandler=_handleCodeContentFinalResult,
|
||||
jsonNormalizer=_normalizeDefaultJson,
|
||||
supportsAccumulation=True,
|
||||
requiresExtraction=False
|
||||
))
|
||||
|
|
|
|||
|
|
@ -753,7 +753,7 @@ class StructureFiller:
|
|||
if processedExtractedParts:
|
||||
logger.debug(f"Section {sectionId}: Aggregating {len(processedExtractedParts)} extracted parts with AI")
|
||||
isAggregation = True
|
||||
generationPrompt = self._buildSectionGenerationPrompt(
|
||||
generationPrompt, templateStructure = self._buildSectionGenerationPrompt(
|
||||
section=section,
|
||||
contentParts=processedExtractedParts,
|
||||
userPrompt=userPrompt,
|
||||
|
|
@ -811,106 +811,8 @@ class StructureFiller:
|
|||
f"{chapterId}_section_{sectionId}_response"
|
||||
)
|
||||
else:
|
||||
async def buildSectionPromptWithContinuation(
|
||||
continuationContext: Dict[str, 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,
|
||||
userPrompt=userPrompt,
|
||||
generationHint=generationHint,
|
||||
allSections=allSections,
|
||||
sectionIndex=sectionIndex,
|
||||
isAggregation=isAggregation,
|
||||
language=language
|
||||
)
|
||||
|
||||
# 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. Continue from where it stopped.
|
||||
|
||||
{structureTemplate}Context showing structure hierarchy with cut point:
|
||||
{unifiedContext}
|
||||
|
||||
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.
|
||||
|
||||
Last ~100 characters from previous response (repeat these at the start):
|
||||
{overlapContext if overlapContext else "No overlap context available"}
|
||||
|
||||
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
|
||||
# Use consolidated class method
|
||||
buildSectionPromptWithContinuation = self.buildSectionPromptWithContinuation
|
||||
|
||||
options = AiCallOptions(
|
||||
operationType=operationType,
|
||||
|
|
@ -932,7 +834,8 @@ CRITICAL:
|
|||
"allSections": all_sections_list,
|
||||
"sectionIndex": sectionIndex,
|
||||
"isAggregation": isAggregation,
|
||||
"services": self.services
|
||||
"templateStructure": templateStructure,
|
||||
"basePrompt": generationPrompt
|
||||
},
|
||||
operationId=sectionOperationId,
|
||||
userPrompt=userPrompt,
|
||||
|
|
@ -1038,7 +941,7 @@ CRITICAL:
|
|||
if len(contentPartIds) == 0 and useAiCall and generationHint:
|
||||
# Generate content from scratch using only generationHint
|
||||
logger.debug(f"Processing section {sectionId}: No content parts, generating from generationHint only")
|
||||
generationPrompt = self._buildSectionGenerationPrompt(
|
||||
generationPrompt, templateStructure = self._buildSectionGenerationPrompt(
|
||||
section=section,
|
||||
contentParts=[],
|
||||
userPrompt=userPrompt,
|
||||
|
|
@ -1097,106 +1000,8 @@ CRITICAL:
|
|||
else:
|
||||
isAggregation = False
|
||||
|
||||
async def buildSectionPromptWithContinuation(
|
||||
continuationContext: Dict[str, 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,
|
||||
userPrompt=userPrompt,
|
||||
generationHint=generationHint,
|
||||
allSections=allSections,
|
||||
sectionIndex=sectionIndex,
|
||||
isAggregation=isAggregation,
|
||||
language=language
|
||||
)
|
||||
|
||||
# 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. Continue from where it stopped.
|
||||
|
||||
{structureTemplate}Context showing structure hierarchy with cut point:
|
||||
{unifiedContext}
|
||||
|
||||
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.
|
||||
|
||||
Last ~100 characters from previous response (repeat these at the start):
|
||||
{overlapContext if overlapContext else "No overlap context available"}
|
||||
|
||||
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
|
||||
# Use consolidated class method
|
||||
buildSectionPromptWithContinuation = self.buildSectionPromptWithContinuation
|
||||
|
||||
options = AiCallOptions(
|
||||
operationType=operationType,
|
||||
|
|
@ -1208,7 +1013,7 @@ CRITICAL:
|
|||
prompt=generationPrompt,
|
||||
options=options,
|
||||
debugPrefix=f"{chapterId}_section_{sectionId}",
|
||||
promptBuilder=buildSectionPromptWithContinuation,
|
||||
promptBuilder=self.buildSectionPromptWithContinuation,
|
||||
promptArgs={
|
||||
"section": section,
|
||||
"contentParts": [],
|
||||
|
|
@ -1217,7 +1022,9 @@ CRITICAL:
|
|||
"allSections": all_sections_list,
|
||||
"sectionIndex": sectionIndex,
|
||||
"isAggregation": isAggregation,
|
||||
"services": self.services
|
||||
"templateStructure": templateStructure,
|
||||
"basePrompt": generationPrompt,
|
||||
"language": language
|
||||
},
|
||||
operationId=sectionOperationId,
|
||||
userPrompt=userPrompt,
|
||||
|
|
@ -1399,7 +1206,7 @@ CRITICAL:
|
|||
if useAiCall and generationHint:
|
||||
# AI-Call mit einzelnen ContentPart (now may be text part after Vision extraction)
|
||||
logger.debug(f"Processing section {sectionId}: Single extracted part with AI call")
|
||||
generationPrompt = self._buildSectionGenerationPrompt(
|
||||
generationPrompt, templateStructure = self._buildSectionGenerationPrompt(
|
||||
section=section,
|
||||
contentParts=[part],
|
||||
userPrompt=userPrompt,
|
||||
|
|
@ -1458,109 +1265,8 @@ CRITICAL:
|
|||
else:
|
||||
isAggregation = False
|
||||
|
||||
async def buildSectionPromptWithContinuation(
|
||||
continuationContext: Dict[str, 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,
|
||||
userPrompt=userPrompt,
|
||||
generationHint=generationHint,
|
||||
allSections=allSections,
|
||||
sectionIndex=sectionIndex,
|
||||
isAggregation=isAggregation,
|
||||
language=language
|
||||
)
|
||||
|
||||
# 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. Continue from where it stopped.
|
||||
|
||||
{structureTemplate}Context showing structure hierarchy with cut point:
|
||||
{unifiedContext}
|
||||
|
||||
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.
|
||||
|
||||
Last ~100 characters from previous response (repeat these at the start):
|
||||
{overlapContext if overlapContext else "No overlap context available"}
|
||||
|
||||
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
|
||||
# Use consolidated class method
|
||||
buildSectionPromptWithContinuation = self.buildSectionPromptWithContinuation
|
||||
|
||||
options = AiCallOptions(
|
||||
operationType=operationType,
|
||||
|
|
@ -1572,7 +1278,7 @@ CRITICAL:
|
|||
prompt=generationPrompt,
|
||||
options=options,
|
||||
debugPrefix=f"{chapterId}_section_{sectionId}",
|
||||
promptBuilder=buildSectionPromptWithContinuation,
|
||||
promptBuilder=self.buildSectionPromptWithContinuation,
|
||||
promptArgs={
|
||||
"section": section,
|
||||
"contentParts": [part],
|
||||
|
|
@ -1581,7 +1287,10 @@ CRITICAL:
|
|||
"allSections": all_sections_list,
|
||||
"sectionIndex": sectionIndex,
|
||||
"isAggregation": isAggregation,
|
||||
"services": self.services
|
||||
"services": self.services,
|
||||
"templateStructure": templateStructure,
|
||||
"basePrompt": generationPrompt,
|
||||
"language": language
|
||||
},
|
||||
operationId=sectionOperationId,
|
||||
userPrompt=userPrompt,
|
||||
|
|
@ -2203,7 +1912,7 @@ Return only valid JSON. Do not include any explanatory text outside the JSON.
|
|||
sectionIndex: Optional[int] = None,
|
||||
isAggregation: bool = False,
|
||||
language: str = "en"
|
||||
) -> str:
|
||||
) -> tuple[str, str]:
|
||||
"""Baue Prompt für Section-Generierung mit vollständigem Kontext."""
|
||||
# Filtere None-Werte
|
||||
validParts = [p for p in contentParts if p is not None]
|
||||
|
|
@ -2312,6 +2021,17 @@ Return only valid JSON. Do not include any explanatory text outside the JSON.
|
|||
|
||||
contentStructureExample = self._getContentStructureExample(contentType)
|
||||
|
||||
# Create template structure explicitly (not extracted from prompt)
|
||||
# This ensures exact identity between initial and continuation prompts
|
||||
templateStructure = f"""{{
|
||||
"elements": [
|
||||
{{
|
||||
"type": "{contentType}",
|
||||
"content": {contentStructureExample}
|
||||
}}
|
||||
]
|
||||
}}"""
|
||||
|
||||
if isAggregation:
|
||||
prompt = f"""# TASK: Generate Section Content (Aggregation)
|
||||
|
||||
|
|
@ -2459,7 +2179,78 @@ Output requirements:
|
|||
## CONTEXT
|
||||
{contextText if contextText else ""}
|
||||
"""
|
||||
return prompt
|
||||
return prompt, templateStructure
|
||||
|
||||
async def buildSectionPromptWithContinuation(
|
||||
self,
|
||||
continuationContext: Any,
|
||||
templateStructure: str,
|
||||
basePrompt: str
|
||||
) -> str:
|
||||
"""Build section prompt with continuation context. Uses unified signature.
|
||||
|
||||
Single unified implementation for all section content generation contexts.
|
||||
|
||||
Note: All initial context (section, contentParts, userPrompt, etc.) is already
|
||||
contained in basePrompt. This function only adds continuation-specific instructions.
|
||||
"""
|
||||
# Extract continuation context fields (only what's needed for continuation)
|
||||
incompletePart = continuationContext.incomplete_part
|
||||
lastRawJson = continuationContext.last_raw_json
|
||||
|
||||
# Build overlap context: extract last ~100 characters from the response for overlap
|
||||
overlapContext = ""
|
||||
if lastRawJson:
|
||||
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:
|
||||
breakPos = len(lastRawJson.rstrip())
|
||||
else:
|
||||
breakPos = len(lastRawJson.rstrip())
|
||||
|
||||
# Build intelligent context showing hierarchy
|
||||
from modules.shared.jsonUtils import _buildIncompleteContext
|
||||
unifiedContext = _buildIncompleteContext(lastRawJson, breakPos)
|
||||
elif incompletePart:
|
||||
unifiedContext = incompletePart
|
||||
else:
|
||||
unifiedContext = "Unable to extract context - response was completely broken"
|
||||
|
||||
# Build unified continuation prompt format
|
||||
continuationPrompt = f"""{basePrompt}
|
||||
|
||||
--- CONTINUATION REQUEST ---
|
||||
The previous JSON response was incomplete. Continue from where it stopped.
|
||||
|
||||
JSON Structure Template:
|
||||
{templateStructure}
|
||||
|
||||
Context showing structure hierarchy with cut point:
|
||||
{unifiedContext}
|
||||
|
||||
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.
|
||||
|
||||
Last ~100 characters from previous response (repeat these at the start):
|
||||
{overlapContext if overlapContext else "No overlap context available"}
|
||||
|
||||
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
|
||||
|
||||
def _extractAndMergeMultipleJsonBlocks(self, responseText: str, contentType: str, sectionId: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -107,52 +107,80 @@ class StructureGenerator:
|
|||
resultFormat="json"
|
||||
)
|
||||
|
||||
structurePrompt, templateStructure = self._buildChapterStructurePrompt(
|
||||
userPrompt=userPrompt,
|
||||
contentParts=contentParts,
|
||||
outputFormat=outputFormat
|
||||
)
|
||||
|
||||
# Create prompt builder for continuation support
|
||||
async def buildChapterStructurePromptWithContinuation(
|
||||
continuationContext: Optional[Dict[str, Any]] = None,
|
||||
**kwargs
|
||||
continuationContext: Any,
|
||||
templateStructure: str,
|
||||
basePrompt: str
|
||||
) -> str:
|
||||
"""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")
|
||||
"""Build chapter structure prompt with continuation context. Uses unified signature.
|
||||
|
||||
basePrompt = self._buildChapterStructurePrompt(
|
||||
userPrompt=userPrompt,
|
||||
contentParts=contentParts,
|
||||
outputFormat=outputFormat
|
||||
)
|
||||
Note: All initial context (userPrompt, contentParts, outputFormat, etc.) is already
|
||||
contained in basePrompt. This function only adds continuation-specific instructions.
|
||||
"""
|
||||
# Extract continuation context fields (only what's needed for continuation)
|
||||
incompletePart = continuationContext.incomplete_part
|
||||
lastRawJson = continuationContext.last_raw_json
|
||||
|
||||
if continuationContext:
|
||||
# Add continuation instructions
|
||||
deliveredSummary = continuationContext.get("delivered_summary", "")
|
||||
elementBeforeCutoff = continuationContext.get("element_before_cutoff", "")
|
||||
cutOffElement = continuationContext.get("cut_off_element", "")
|
||||
# Build overlap context: extract last ~100 characters from the response for overlap
|
||||
overlapContext = ""
|
||||
if lastRawJson:
|
||||
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:
|
||||
breakPos = len(lastRawJson.rstrip())
|
||||
else:
|
||||
breakPos = len(lastRawJson.rstrip())
|
||||
|
||||
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 += "Start directly with the next chapter that should follow.\n\n"
|
||||
|
||||
return f"""{basePrompt}
|
||||
|
||||
{continuationText}
|
||||
|
||||
Continue generating the remaining chapters now.
|
||||
"""
|
||||
# Build intelligent context showing hierarchy
|
||||
from modules.shared.jsonUtils import _buildIncompleteContext
|
||||
unifiedContext = _buildIncompleteContext(lastRawJson, breakPos)
|
||||
elif incompletePart:
|
||||
unifiedContext = incompletePart
|
||||
else:
|
||||
return basePrompt
|
||||
unifiedContext = "Unable to extract context - response was completely broken"
|
||||
|
||||
# Build unified continuation prompt format
|
||||
continuationPrompt = f"""{basePrompt}
|
||||
|
||||
--- CONTINUATION REQUEST ---
|
||||
The previous JSON response was incomplete. Continue from where it stopped.
|
||||
|
||||
JSON Structure Template:
|
||||
{templateStructure}
|
||||
|
||||
Context showing structure hierarchy with cut point:
|
||||
{unifiedContext}
|
||||
|
||||
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.
|
||||
|
||||
Last ~100 characters from previous response (repeat these at the start):
|
||||
{overlapContext if overlapContext else "No overlap context available"}
|
||||
|
||||
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
|
||||
|
||||
# Call AI with looping support
|
||||
# NOTE: Do NOT pass contentParts here - we only need metadata for structure generation
|
||||
|
|
@ -167,7 +195,8 @@ Continue generating the remaining chapters now.
|
|||
promptArgs={
|
||||
"userPrompt": userPrompt,
|
||||
"outputFormat": outputFormat,
|
||||
"services": self.services
|
||||
"templateStructure": templateStructure,
|
||||
"basePrompt": structurePrompt
|
||||
},
|
||||
useCaseId="chapter_structure", # REQUIRED: Explicit use case ID
|
||||
operationId=structureOperationId,
|
||||
|
|
@ -280,7 +309,7 @@ Continue generating the remaining chapters now.
|
|||
userPrompt: str,
|
||||
contentParts: List[ContentPart],
|
||||
outputFormat: str
|
||||
) -> str:
|
||||
) -> tuple[str, str]:
|
||||
"""Baue Prompt für Chapter-Struktur-Generierung."""
|
||||
# Baue ContentParts-Index - filtere leere Parts heraus
|
||||
contentPartsIndex = ""
|
||||
|
|
@ -336,6 +365,36 @@ Continue generating the remaining chapters now.
|
|||
language = self._getUserLanguage()
|
||||
logger.debug(f"Using language from services (user intention analysis) for structure generation: {language}")
|
||||
|
||||
# Create template structure explicitly (not extracted from prompt)
|
||||
# This ensures exact identity between initial and continuation prompts
|
||||
templateStructure = f"""{{
|
||||
"metadata": {{
|
||||
"title": "Document Title",
|
||||
"language": "{language}"
|
||||
}},
|
||||
"documents": [{{
|
||||
"id": "doc_1",
|
||||
"title": "Document Title",
|
||||
"filename": "document.{outputFormat}",
|
||||
"outputFormat": "{outputFormat}",
|
||||
"language": "{language}",
|
||||
"chapters": [
|
||||
{{
|
||||
"id": "chapter_1",
|
||||
"level": 1,
|
||||
"title": "Chapter Title",
|
||||
"contentParts": {{
|
||||
"extracted_part_id": {{
|
||||
"instruction": "Use extracted content with ALL relevant details from user request"
|
||||
}}
|
||||
}},
|
||||
"generationHint": "Detailed description including ALL relevant details from user request for this chapter",
|
||||
"sections": []
|
||||
}}
|
||||
]
|
||||
}}]
|
||||
}}"""
|
||||
|
||||
prompt = f"""# TASK: Generate Chapter Structure
|
||||
|
||||
This is a PLANNING task. Return EXACTLY ONE complete JSON object. Do not generate multiple JSON objects, alternatives, or variations. Do not use separators like "---" between JSON objects.
|
||||
|
|
@ -463,5 +522,5 @@ For each chapter, verify:
|
|||
|
||||
OUTPUT FORMAT: Start with {{ and end with }}. Do NOT use markdown code fences (```json). Do NOT add explanatory text before or after the JSON. Return ONLY the JSON object itself.
|
||||
"""
|
||||
return prompt
|
||||
return prompt, templateStructure
|
||||
|
||||
|
|
|
|||
|
|
@ -233,6 +233,26 @@ class CodeGenerationPath:
|
|||
if not contentPartsIndex:
|
||||
contentPartsIndex = "\n(No content parts available)"
|
||||
|
||||
# Create template structure explicitly (not extracted from prompt)
|
||||
templateStructure = f"""{{
|
||||
"metadata": {{
|
||||
"language": "{language}",
|
||||
"projectType": "single_file|multi_file",
|
||||
"projectName": ""
|
||||
}},
|
||||
"files": [
|
||||
{{
|
||||
"id": "",
|
||||
"filename": "",
|
||||
"fileType": "",
|
||||
"dependencies": [],
|
||||
"imports": [],
|
||||
"functions": [],
|
||||
"classes": []
|
||||
}}
|
||||
]
|
||||
}}"""
|
||||
|
||||
# Build structure generation prompt
|
||||
structurePrompt = f"""# TASK: Generate Code Project Structure
|
||||
|
||||
|
|
@ -302,6 +322,75 @@ For single-file projects, return one file. For multi-file projects, include ALL
|
|||
Return ONLY valid JSON matching the request above.
|
||||
"""
|
||||
|
||||
# Build continuation prompt builder
|
||||
async def buildCodeStructurePromptWithContinuation(
|
||||
continuationContext: Any,
|
||||
templateStructure: str,
|
||||
basePrompt: str
|
||||
) -> str:
|
||||
"""Build code structure prompt with continuation context. Uses unified signature.
|
||||
|
||||
Note: All initial context (userPrompt, contentParts, etc.) is already
|
||||
contained in basePrompt. This function only adds continuation-specific instructions.
|
||||
"""
|
||||
# Extract continuation context fields (only what's needed for continuation)
|
||||
incompletePart = continuationContext.incomplete_part
|
||||
lastRawJson = continuationContext.last_raw_json
|
||||
|
||||
# Build overlap context: extract last ~100 characters from the response for overlap
|
||||
overlapContext = ""
|
||||
if lastRawJson:
|
||||
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:
|
||||
breakPos = len(lastRawJson.rstrip())
|
||||
else:
|
||||
breakPos = len(lastRawJson.rstrip())
|
||||
|
||||
# Build intelligent context showing hierarchy
|
||||
from modules.shared.jsonUtils import _buildIncompleteContext
|
||||
unifiedContext = _buildIncompleteContext(lastRawJson, breakPos)
|
||||
elif incompletePart:
|
||||
unifiedContext = incompletePart
|
||||
else:
|
||||
unifiedContext = "Unable to extract context - response was completely broken"
|
||||
|
||||
# Build unified continuation prompt format
|
||||
continuationPrompt = f"""{basePrompt}
|
||||
|
||||
--- CONTINUATION REQUEST ---
|
||||
The previous JSON response was incomplete. Continue from where it stopped.
|
||||
|
||||
JSON Structure Template:
|
||||
{templateStructure}
|
||||
|
||||
Context showing structure hierarchy with cut point:
|
||||
{unifiedContext}
|
||||
|
||||
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.
|
||||
|
||||
Last ~100 characters from previous response (repeat these at the start):
|
||||
{overlapContext if overlapContext else "No overlap context available"}
|
||||
|
||||
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
|
||||
|
||||
# Use generic looping system with code_structure use case
|
||||
options = AiCallOptions(
|
||||
operationType=OperationTypeEnum.DATA_GENERATE,
|
||||
|
|
@ -311,6 +400,13 @@ Return ONLY valid JSON matching the request above.
|
|||
structureJson = await self.services.ai.callAiWithLooping(
|
||||
prompt=structurePrompt,
|
||||
options=options,
|
||||
promptBuilder=buildCodeStructurePromptWithContinuation,
|
||||
promptArgs={
|
||||
"userPrompt": userPrompt,
|
||||
"contentParts": contentParts,
|
||||
"templateStructure": templateStructure,
|
||||
"basePrompt": structurePrompt
|
||||
},
|
||||
useCaseId="code_structure",
|
||||
debugPrefix="code_structure_generation",
|
||||
contentParts=contentParts
|
||||
|
|
@ -640,6 +736,18 @@ Return ONLY valid JSON matching the request above.
|
|||
```
|
||||
"""
|
||||
|
||||
# Create template structure explicitly (not extracted from prompt)
|
||||
templateStructure = f"""{{
|
||||
"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 '[]'}
|
||||
}}
|
||||
]
|
||||
}}"""
|
||||
|
||||
# Build base prompt
|
||||
contentPrompt = f"""# TASK: Generate Code File Content
|
||||
|
||||
|
|
@ -667,141 +775,77 @@ Generate complete, production-ready code with:
|
|||
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 '[]'}
|
||||
}}
|
||||
]
|
||||
}}
|
||||
{templateStructure}
|
||||
"""
|
||||
|
||||
# Build continuation prompt builder
|
||||
async def buildCodeContentPromptWithContinuation(
|
||||
continuationContext: Optional[Dict[str, Any]] = None,
|
||||
**kwargs
|
||||
continuationContext: Any,
|
||||
templateStructure: str,
|
||||
basePrompt: str
|
||||
) -> 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", "")
|
||||
"""Build code content prompt with continuation context. Uses unified signature.
|
||||
|
||||
# Rebuild base prompt (same as initial prompt)
|
||||
userRequestSection = ""
|
||||
if userPrompt:
|
||||
userRequestSection = f"""
|
||||
## ORIGINAL USER REQUEST
|
||||
```
|
||||
{userPrompt}
|
||||
```
|
||||
"""
|
||||
Note: All initial context (filename, fileType, functions, etc.) is already
|
||||
contained in basePrompt. This function only adds continuation-specific instructions.
|
||||
"""
|
||||
# Extract continuation context fields (only what's needed for continuation)
|
||||
incompletePart = continuationContext.incomplete_part
|
||||
lastRawJson = continuationContext.last_raw_json
|
||||
|
||||
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"
|
||||
# Build overlap context: extract last ~100 characters from the response for overlap
|
||||
overlapContext = ""
|
||||
if lastRawJson:
|
||||
overlapContext = lastRawJson[-100:].strip()
|
||||
|
||||
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 '[]'}
|
||||
}}
|
||||
]
|
||||
}}
|
||||
"""
|
||||
# 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:
|
||||
breakPos = len(lastRawJson.rstrip())
|
||||
else:
|
||||
breakPos = len(lastRawJson.rstrip())
|
||||
|
||||
# Build intelligent context showing hierarchy
|
||||
from modules.shared.jsonUtils import _buildIncompleteContext
|
||||
unifiedContext = _buildIncompleteContext(lastRawJson, breakPos)
|
||||
elif incompletePart:
|
||||
unifiedContext = incompletePart
|
||||
else:
|
||||
unifiedContext = "Unable to extract context - response was completely broken"
|
||||
|
||||
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}
|
||||
# Build unified continuation prompt format
|
||||
continuationPrompt = f"""{basePrompt}
|
||||
|
||||
--- CONTINUATION REQUEST ---
|
||||
The previous JSON response was incomplete. Continue from where it stopped.
|
||||
|
||||
{continuationText}
|
||||
JSON Structure Template:
|
||||
{templateStructure}
|
||||
|
||||
Continue generating the remaining code content now.
|
||||
"""
|
||||
else:
|
||||
return basePrompt
|
||||
Context showing structure hierarchy with cut point:
|
||||
{unifiedContext}
|
||||
|
||||
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.
|
||||
|
||||
Last ~100 characters from previous response (repeat these at the start):
|
||||
{overlapContext if overlapContext else "No overlap context available"}
|
||||
|
||||
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
|
||||
|
||||
# Use generic looping system with code_content use case
|
||||
options = AiCallOptions(
|
||||
|
|
@ -823,7 +867,8 @@ Continue generating the remaining code content now.
|
|||
"userPrompt": userPrompt,
|
||||
"contentParts": contentParts,
|
||||
"contextInfo": contextInfo,
|
||||
"services": self.services
|
||||
"templateStructure": templateStructure,
|
||||
"basePrompt": contentPrompt
|
||||
},
|
||||
useCaseId="code_content",
|
||||
debugPrefix=f"code_content_{fileStructure.get('id', 'file')}",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import logging
|
|||
import re
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union, Type, TypeVar
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from modules.datamodels.datamodelAi import ContinuationContext
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -843,8 +844,9 @@ def _extractOverlapFromElement(elem: Dict[str, Any], elemType: str) -> Optional[
|
|||
def buildContinuationContext(
|
||||
allSections: List[Dict[str, Any]],
|
||||
lastRawResponse: Optional[str] = None,
|
||||
useCaseId: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
useCaseId: Optional[str] = None,
|
||||
templateStructure: Optional[str] = None
|
||||
) -> ContinuationContext:
|
||||
"""
|
||||
Build context information from accumulated sections for continuation prompt.
|
||||
|
||||
|
|
@ -854,14 +856,12 @@ def buildContinuationContext(
|
|||
allSections: List of ALL sections accumulated across ALL iterations
|
||||
lastRawResponse: Raw JSON response from last iteration (can be broken/incomplete)
|
||||
useCaseId: Optional use case ID to determine expected JSON structure
|
||||
templateStructure: JSON structure template from initial prompt (MUST be identical)
|
||||
|
||||
Returns:
|
||||
Dict with delivered_summary, cut_off_element, element_before_cutoff, template_structure,
|
||||
last_complete_part, incomplete_part, structure_context
|
||||
ContinuationContext: Pydantic model with all continuation context information
|
||||
"""
|
||||
context = {
|
||||
"section_count": len(allSections),
|
||||
}
|
||||
section_count = len(allSections)
|
||||
|
||||
# Build summary of delivered data (per-section counts)
|
||||
summary_lines = []
|
||||
|
|
@ -978,7 +978,7 @@ def buildContinuationContext(
|
|||
else:
|
||||
summary_lines.extend(summary_items)
|
||||
|
||||
context["delivered_summary"] = "\n".join(summary_lines)
|
||||
delivered_summary = "\n".join(summary_lines)
|
||||
|
||||
# Extract cut-off point using new algorithm
|
||||
# 1. Loop over all sections until finding incomplete section
|
||||
|
|
@ -1029,9 +1029,6 @@ def buildContinuationContext(
|
|||
except Exception as e:
|
||||
logger.debug(f"Error extracting cut-off point: {e}")
|
||||
|
||||
context["element_before_cutoff"] = element_before_cutoff
|
||||
context["cut_off_element"] = cut_off_element
|
||||
|
||||
# Extract overlap information for continuation prompt
|
||||
# GENERIC overlap extraction: handles elements of any size, including long strings
|
||||
# Strategy: Extract last N elements, but if an element is very large, extract only a portion
|
||||
|
|
@ -1067,38 +1064,36 @@ def buildContinuationContext(
|
|||
if overlapStrings:
|
||||
overlapString = ",\n".join(overlapStrings)
|
||||
|
||||
context["overlap_elements"] = overlapElements
|
||||
context["overlap_string"] = overlapString
|
||||
# Store raw JSON response and extract structure context
|
||||
last_raw_json = lastRawResponse or ""
|
||||
last_complete_part = ""
|
||||
incomplete_part = ""
|
||||
structure_context = ""
|
||||
|
||||
# Store raw JSON response for prompt builder to check
|
||||
if lastRawResponse:
|
||||
context["last_raw_json"] = lastRawResponse
|
||||
|
||||
# Extract JSON structure context for continuation prompt
|
||||
# This provides: template structure, last complete part, incomplete part, structure context
|
||||
# This provides: last complete part, incomplete part, structure context
|
||||
# NOTE: template_structure is now passed as parameter, not extracted
|
||||
try:
|
||||
structureContext = extractJsonStructureContext(lastRawResponse, useCaseId)
|
||||
context["template_structure"] = structureContext.get("template_structure", "")
|
||||
context["last_complete_part"] = structureContext.get("last_complete_part", "")
|
||||
context["incomplete_part"] = structureContext.get("incomplete_part", "")
|
||||
context["structure_context"] = structureContext.get("structure_context", "")
|
||||
# Log if extraction succeeded but returned empty values
|
||||
if not context["template_structure"] and not context["structure_context"]:
|
||||
logger.debug(f"JSON structure context extraction returned empty values for useCaseId={useCaseId}")
|
||||
last_complete_part = structureContext.get("last_complete_part", "")
|
||||
incomplete_part = structureContext.get("incomplete_part", "")
|
||||
structure_context = structureContext.get("structure_context", "")
|
||||
except Exception as e:
|
||||
logger.warning(f"Error extracting JSON structure context: {e}", exc_info=True)
|
||||
context["template_structure"] = ""
|
||||
context["last_complete_part"] = ""
|
||||
context["incomplete_part"] = ""
|
||||
context["structure_context"] = ""
|
||||
else:
|
||||
context["last_raw_json"] = ""
|
||||
context["template_structure"] = ""
|
||||
context["last_complete_part"] = ""
|
||||
context["incomplete_part"] = ""
|
||||
context["structure_context"] = ""
|
||||
|
||||
return context
|
||||
# Return ContinuationContext Pydantic model
|
||||
return ContinuationContext(
|
||||
section_count=section_count,
|
||||
delivered_summary=delivered_summary,
|
||||
cut_off_element=cut_off_element,
|
||||
element_before_cutoff=element_before_cutoff,
|
||||
template_structure=templateStructure, # Use passed parameter, not extracted
|
||||
last_complete_part=last_complete_part,
|
||||
incomplete_part=incomplete_part,
|
||||
structure_context=structure_context,
|
||||
last_raw_json=last_raw_json
|
||||
)
|
||||
|
||||
|
||||
def extractJsonStructureContext(
|
||||
|
|
|
|||
Loading…
Reference in a new issue