# Task Intentions & Generic Looping System - Refactoring Architecture ## Executive Summary This document outlines a comprehensive refactoring to enhance the generation system with: 1. **AI Service-Level Intent Detection**: Detect intent (document vs code) when `DATA_GENERATE` operation is called - workflow level remains unchanged 2. **Generic Looping System**: Parametrized looping infrastructure supporting different JSON formats and use cases 3. **Multiple Generation Paths**: Document, code, and image generation paths within the generation service, all unified as action result documents 4. **Smart Code Generation**: Multi-file projects with dependency handling, requirements.txt/package.json generation, and proper cross-file references --- ## Part 1: AI Service-Level Intent Detection ### 1.1 Current State **Problem**: - `DATA_GENERATE` operation type is used for both document and code generation - No distinction at AI service level - always routes to document generation pipeline - Code generation requests treated as document generation - `IMAGE_GENERATE` already works correctly (no changes needed) **Current Flow**: ``` User Request ↓ Task Planning (unchanged) ↓ Action Planning (selects ai.process) ↓ ai.process → callAiContent(operationType=DATA_GENERATE) ↓ Document Generation Pipeline (always) ❌ Wrong for code! ``` **Key Insight**: - **Workflow level (task/action planning)**: Remains unchanged ✅ - **AI Service level**: Need to detect intent when `DATA_GENERATE` is called - **Operation Types**: - `IMAGE_GENERATE` → Already handles images correctly ✅ - `DATA_GENERATE` → Needs to split: document vs code **Current Issue with `ai.process`**: - `ai.process` creates `AiCallOptions(resultFormat=output_format)` - **no operationType set** - `callAiContent()` defaults to `DATA_GENERATE` if operationType not set (line 623) - If `resultType="png"` or `"jpg"` → still uses `DATA_GENERATE`, NOT `IMAGE_GENERATE` ❌ - Image generation requests go through document pipeline instead of image pipeline **Solution**: Detect image generation intent and set `operationType=IMAGE_GENERATE` when appropriate ### 1.2 Proposed Architecture #### Intent Detection at AI Service Level **Location**: `gateway/modules/services/serviceAi/mainServiceAi.py` and `callAiContent()` **Principle**: When `DATA_GENERATE` operation is called, detect from prompt/content whether it's: - **Document generation**: Reports, articles, formatted documents (existing behavior) - **Code generation**: Executable code files (new behavior) **No changes needed**: - Task planning (remains unchanged) - Action planning (remains unchanged) - `IMAGE_GENERATE` operation (already works) #### Intent Detection Logic **NO AUTO-DETECTION**: Intent detection is NOT used in the new architecture. **Architecture Principle**: - **NO auto-detection**: Actions must explicitly provide `generationIntent` - **Clear use cases**: Each action defines its intent explicitly - **No fallback**: No fallback to old processing or detection logic - **Fail fast**: If `generationIntent` is missing, raise error immediately - **Explicit over implicit**: All intent must be explicitly specified - no guessing or inference - **Format detection vs Intent detection**: - ✅ **Format detection is acceptable**: Detecting image formats from explicit `resultType` parameter (e.g., "png", "jpg") is acceptable because it's based on an explicit parameter, not prompt analysis - ❌ **Intent detection is NOT acceptable**: Detecting intent from prompt content or other inferred sources is not allowed - intent must be explicit **Implementation**: - All actions must pass explicit `generationIntent` parameter - `callAiContent()` requires `generationIntent` for `DATA_GENERATE` operations - No IntentDetector class needed - intent comes from action definition - Image generation detection: `ai.process` detects image formats from `resultType` and sets `operationType=IMAGE_GENERATE` automatically (this is format detection based on explicit parameter, not intent detection from prompt) #### AI Service Integration **Modify**: `mainServiceAi.py` - `callAiContent()` method ```python async def callAiContent( self, prompt: str, options: Optional[AiCallOptions] = None, documentList: Optional[DocumentReferenceList] = None, contentParts: Optional[List[ContentPart]] = None, outputFormat: str = None, title: str = None, parentOperationId: Optional[str] = None, generationIntent: Optional[str] = None # NEW: Explicit intent from action (skips detection) ) -> AiResponse: """ Unified AI content generation with explicit intent requirement. Args: generationIntent: REQUIRED explicit intent ("document" | "code" | "image") from action. NO auto-detection - actions must explicitly specify intent. """ options = options or AiCallOptions() operationType = options.operationType or OperationTypeEnum.DATA_GENERATE # Route based on operation type if operationType == OperationTypeEnum.IMAGE_GENERATE: # Image generation - already works correctly, no changes needed return await self._handleImageGeneration(prompt, options, outputFormat) elif operationType == OperationTypeEnum.DATA_GENERATE: # Data generation - REQUIRES explicit generationIntent if not generationIntent: raise ValueError( "generationIntent is required for DATA_GENERATE operation. " "Actions must explicitly specify 'document' or 'code' intent. " "No auto-detection - use qualified actions (ai.generateDocument, ai.generateCode)." ) # Route based on explicit intent (no auto-detection, no fallback) if generationIntent == "code": # Route to code generation path return await self._handleCodeGeneration( prompt=prompt, options=options, contentParts=contentParts, outputFormat=outputFormat, title=title, parentOperationId=parentOperationId ) else: # Route to document generation path (existing behavior) return await self._handleDocumentGeneration( prompt=prompt, options=options, documentList=documentList, contentParts=contentParts, outputFormat=outputFormat, title=title, parentOperationId=parentOperationId ) # Other operation types (DATA_ANALYSE, DATA_EXTRACT, etc.) - existing logic # ... ``` #### Generation Path Handlers **New Methods in `mainServiceAi.py`**: ```python async def _handleCodeGeneration( self, prompt: str, options: AiCallOptions, contentParts: Optional[List[ContentPart]], outputFormat: str, title: str, parentOperationId: Optional[str] ) -> AiResponse: """Handle code generation using code generation path.""" from modules.services.serviceGeneration.paths.codePath import CodeGenerationPath codePath = CodeGenerationPath(self.services) return await codePath.generateCode( userPrompt=prompt, outputFormat=outputFormat, contentParts=contentParts ) async def _handleDocumentGeneration( self, prompt: str, options: AiCallOptions, documentList: Optional[DocumentReferenceList], contentParts: Optional[List[ContentPart]], outputFormat: str, title: str, parentOperationId: Optional[str] ) -> AiResponse: """Handle document generation using existing document path.""" # Existing document generation logic (unchanged) # ... ``` #### Action Integration **Enhancement**: Actions can pass explicit `generationIntent` to skip detection **1. Enhance `ai.generateDocument` Action** **Modify**: `generateDocument.py` ```python async def generateDocument(self, parameters: Dict[str, Any]) -> ActionResult: """Generate documents - explicitly sets intent to 'document'.""" # ... existing code ... aiResponse: AiResponse = await self.services.ai.callAiContent( prompt=prompt, options=options, documentList=docRefList, outputFormat=resultType, title=title, parentOperationId=parentOperationId, generationIntent="document" # NEW: Explicit intent, skips detection ) # ... rest of method ... ``` **2. Create New `ai.generateCode` Action** **New File**: `generateCode.py` ```python @action async def generateCode(self, parameters: Dict[str, Any]) -> ActionResult: """ Generate code files - explicitly sets intent to 'code'. Parameters: - prompt (str, required): Description of code to generate - documentList (list, optional): Reference documents - resultType (str, optional): Output format (html, js, py, etc.). Default: based on prompt """ prompt = parameters.get("prompt") if not prompt: return ActionResult.isFailure(error="prompt is required") documentList = parameters.get("documentList", []) resultType = parameters.get("resultType") # Auto-detect format from prompt if not provided if not resultType: promptLower = prompt.lower() if ".html" in promptLower or "html file" in promptLower: resultType = "html" elif ".js" in promptLower or "javascript" in promptLower: resultType = "js" elif ".py" in promptLower or "python" in promptLower: resultType = "py" else: resultType = "txt" # Default # Prepare title title = "Generated Code" # Call AI service with explicit code intent options = AiCallOptions( operationType=OperationTypeEnum.DATA_GENERATE, priority=PriorityEnum.BALANCED, processingMode=ProcessingModeEnum.DETAILED ) aiResponse: AiResponse = await self.services.ai.callAiContent( prompt=prompt, options=options, documentList=docRefList, outputFormat=resultType, title=title, parentOperationId=parentOperationId, generationIntent="code" # Explicit intent, skips detection ) # Convert to ActionResult (same as generateDocument) # ... ``` **3. Enhance `ai.process` Action** **Modify**: `process.py` - Detect image generation from resultType, require generationIntent for DATA_GENERATE **Important**: Image format detection (png, jpg, etc.) is **format detection**, not intent detection. This is acceptable because it's based on explicit `resultType` parameter, not prompt analysis. ```python async def process(self, parameters: Dict[str, Any]) -> ActionResult: """Universal AI document processing action.""" # ... existing code ... # Detect image generation from resultType (format detection, not intent detection) # This is acceptable because resultType is an explicit parameter, not inferred from prompt resultType = parameters.get("resultType", "txt") normalized_result_type = (str(resultType).strip().lstrip('.').lower() or "txt") imageFormats = ["png", "jpg", "jpeg", "gif", "webp"] isImageGeneration = normalized_result_type in imageFormats # Build options with correct operationType output_format = normalized_result_type.replace('.', '') or 'txt' options = AiCallOptions( resultFormat=output_format, operationType=OperationTypeEnum.IMAGE_GENERATE if isImageGeneration else OperationTypeEnum.DATA_GENERATE ) # Get generationIntent from parameters (REQUIRED for DATA_GENERATE) generationIntent = parameters.get("generationIntent") # For DATA_GENERATE, generationIntent is REQUIRED (no auto-detection, no fallback) if options.operationType == OperationTypeEnum.DATA_GENERATE and not generationIntent: raise ValueError( "ai.process called with DATA_GENERATE but no generationIntent. " "Use qualified actions (ai.generateDocument, ai.generateCode) instead, " "or explicitly pass generationIntent parameter." ) # ... existing code ... # Pass generationIntent to callAiContent (REQUIRED for DATA_GENERATE) if contentParts: aiResponse = await self.services.ai.callAiContent( prompt=aiPrompt, options=options, contentParts=contentParts, outputFormat=output_format, parentOperationId=operationId, generationIntent=generationIntent # REQUIRED for DATA_GENERATE ) else: aiResponse = await self.services.ai.callAiContent( prompt=aiPrompt, options=options, documentList=documentList, outputFormat=output_format, parentOperationId=operationId, generationIntent=generationIntent # REQUIRED for DATA_GENERATE ) # ... rest of method ... ``` **Behavior**: - If `resultType` is image format (png, jpg, etc.) → Sets `operationType=IMAGE_GENERATE` ✅ - For `DATA_GENERATE`: `generationIntent` is REQUIRED (no auto-detection, no fallback) - If `generationIntent` not provided → Raises ValueError (fail fast) - **Best Practice**: Use qualified actions (`ai.generateDocument`, `ai.generateCode`) instead of `ai.process` **Rationale**: - `ai.process` detects image generation from `resultType` and sets correct operationType - For DATA_GENERATE, explicit intent is required - no auto-detection, no fallback - Wrapper actions (`translateDocument`, `summarizeDocument`) must pass explicit `generationIntent` - Clear use cases - no ambiguity **4. `ai.translateDocument` and `ai.summarizeDocument` Actions** **Current**: Both wrap `ai.process()` with specific prompts **Enhancement**: Pass `generationIntent="document"` when calling `process()` internally **Modify**: `translateDocument.py` and `summarizeDocument.py` ```python # In translateDocument.py processParams = { "aiPrompt": aiPrompt, "documentList": documentList, "generationIntent": "document" # NEW: Explicit intent } if resultType: processParams["resultType"] = resultType return await self.process(processParams) # In summarizeDocument.py return await self.process({ "aiPrompt": aiPrompt, "documentList": documentList, "resultType": resultType, "generationIntent": "document" # NEW: Explicit intent }) ``` **Summary**: | Action | generationIntent | Behavior | |--------|------------------|----------| | `ai.generateDocument` | `"document"` | Explicit intent, skips detection ✅ | | `ai.generateCode` | `"code"` | Explicit intent, skips detection ✅ | | `ai.translateDocument` | `"document"` | Explicit intent (via process) ✅ | | `ai.summarizeDocument` | `"document"` | Explicit intent (via process) ✅ | | `ai.process` | REQUIRED | Must provide `generationIntent` for DATA_GENERATE, raises error if missing ❌ | **Benefits**: - **Efficiency**: Qualified actions skip detection (saves AI call) - **Clarity**: Intent is explicit in action name - **No Ambiguity**: Always clear use case - no auto-detection, no fallback - **Consistency**: All actions must explicitly define intent **Critical Requirements**: - **NO auto-detection**: `callAiContent()` requires explicit `generationIntent` for DATA_GENERATE - **NO fallback**: No fallback to old processing logic - raises error if intent missing - **Clear use cases**: Always explicit - no ambiguity - **Use qualified actions**: Prefer `ai.generateDocument`, `ai.generateCode` over generic `ai.process` - **Fail fast**: Missing `generationIntent` raises ValueError immediately --- ## Part 2: Generic Looping System ### 2.1 Current State **Current System**: `subAiCallLooping.py` - Handles different JSON formats through early detection - Format-specific routing (elements, chapters, sections) - Continuation context built for sections (not generic) - No parametrized configuration **Issues**: - Hard-coded format detection - Continuation context mismatch for different formats - No accumulation support for all formats - Not easily extensible for new formats ### 2.2 Proposed Generic Looping System #### Looping Use Case Configuration **New Class**: `LoopingUseCase` ```python @dataclass class LoopingUseCase: """Configuration for a specific looping use case.""" # Identification useCaseId: str # "section_content", "chapter_structure", "code_structure", "code_content" # JSON Format Detection jsonTemplate: Dict[str, Any] # Expected JSON structure template detectionKeys: List[str] # Keys to check for format detection (e.g., ["elements"], ["chapters"], ["files"]) detectionPath: str # JSONPath to check (e.g., "documents[0].chapters", "files[0].content") # Prompt Building initialPromptBuilder: Callable # Function to build initial prompt continuationPromptBuilder: Callable # Function to build continuation prompt # Accumulation & Merging accumulator: Optional[Callable] = None # Function to accumulate fragments merger: Optional[Callable] = None # Function to merge accumulated data # Continuation Context continuationContextBuilder: Optional[Callable] = None # Build continuation context for this format # Result Building resultBuilder: Optional[Callable] = None # Build final result from accumulated data # Metadata supportsAccumulation: bool = True # Whether this use case supports accumulation requiresExtraction: bool = False # Whether this requires extraction (like sections) ``` #### Use Case Registry **New Module**: `gateway/modules/services/serviceAi/subLoopingUseCases.py` ```python class LoopingUseCaseRegistry: """Registry of all looping use cases.""" def __init__(self): self.useCases: Dict[str, LoopingUseCase] = {} self._registerDefaultUseCases() def register(self, useCase: LoopingUseCase): """Register a new use case.""" self.useCases[useCase.useCaseId] = useCase def get(self, useCaseId: str) -> Optional[LoopingUseCase]: """Get use case by ID.""" return self.useCases.get(useCaseId) def detectUseCase(self, parsedJson: Dict[str, Any]) -> Optional[str]: """Detect which use case matches the JSON structure.""" for useCaseId, useCase in self.useCases.items(): if self._matchesFormat(parsedJson, useCase): return useCaseId return None def _matchesFormat(self, json: Dict[str, Any], useCase: LoopingUseCase) -> bool: """Check if JSON matches use case format.""" for key in useCase.detectionKeys: if key in json: return True # Check nested path if useCase.detectionPath: try: from jsonpath_ng import parse jsonpath_expr = parse(useCase.detectionPath) matches = [match.value for match in jsonpath_expr.find(json)] if matches: return True except: pass return False def _registerDefaultUseCases(self): """Register default use cases.""" # Use Case 1: Section Content Generation self.register(LoopingUseCase( useCaseId="section_content", jsonTemplate={"elements": []}, detectionKeys=["elements"], detectionPath="", initialPromptBuilder=buildSectionContentPrompt, continuationPromptBuilder=buildSectionContentContinuationPrompt, accumulator=None, # Direct return, no accumulation merger=None, continuationContextBuilder=buildSectionContinuationContext, resultBuilder=None, # Return JSON directly supportsAccumulation=False, requiresExtraction=False )) # Use Case 2: Chapter Structure Generation self.register(LoopingUseCase( useCaseId="chapter_structure", jsonTemplate={"documents": [{"chapters": []}]}, detectionKeys=["chapters"], detectionPath="documents[0].chapters", initialPromptBuilder=buildChapterStructurePrompt, continuationPromptBuilder=buildChapterStructureContinuationPrompt, accumulator=None, # Direct return, no accumulation merger=None, continuationContextBuilder=buildChapterContinuationContext, resultBuilder=None, # Return JSON directly supportsAccumulation=False, requiresExtraction=False )) merger=mergeDocumentSections, continuationContextBuilder=buildDocumentContinuationContext, resultBuilder=buildDocumentResultFromSections, supportsAccumulation=True, requiresExtraction=True )) # Use Case 4: Code Structure Generation (NEW) self.register(LoopingUseCase( useCaseId="code_structure", jsonTemplate={ "metadata": { "language": "", "projectType": "single_file|multi_file", "projectName": "" }, "files": [ { "id": "", "filename": "", "fileType": "", "dependencies": [], # List of file IDs this file depends on "imports": [], # List of import statements (for dependency extraction) "functions": [], # Function signatures for cross-file references "classes": [] # Class definitions for cross-file references } ] }, detectionKeys=["files"], detectionPath="files", initialPromptBuilder=buildCodeStructurePrompt, continuationPromptBuilder=buildCodeStructureContinuationPrompt, accumulator=None, # Direct return merger=None, continuationContextBuilder=buildCodeContinuationContext, resultBuilder=None, supportsAccumulation=False, requiresExtraction=False )) # Use Case 5: Code Content Generation (NEW) self.register(LoopingUseCase( useCaseId="code_content", jsonTemplate={"files": [{"content": "", "functions": []}]}, detectionKeys=["content", "functions"], detectionPath="files[0].content", initialPromptBuilder=buildCodeContentPrompt, continuationPromptBuilder=buildCodeContentContinuationPrompt, accumulator=accumulateCodeContent, merger=mergeCodeContent, continuationContextBuilder=buildCodeContentContinuationContext, resultBuilder=buildCodeResultFromContent, supportsAccumulation=True, requiresExtraction=False )) resultBuilder=None, supportsAccumulation=False, requiresExtraction=False )) ``` #### Refactored Looping System **Refactor**: `subAiCallLooping.py` ```python class AiCallLooper: """Generic looping system with parametrized use cases.""" def __init__(self, services, aiService, responseParser): self.services = services self.aiService = aiService self.responseParser = responseParser self.useCaseRegistry = LoopingUseCaseRegistry() async def callAiWithLooping( self, prompt: str, options: AiCallOptions, useCaseId: str, # REQUIRED: Explicit use case ID debugPrefix: str = "ai_call", promptArgs: Optional[Dict[str, Any]] = None, operationId: Optional[str] = None, userPrompt: Optional[str] = None, contentParts: Optional[List[ContentPart]] = None ) -> str: """ Generic looping system with parametrized use case. Args: useCaseId: REQUIRED explicit use case ID (e.g., "code_structure", "section_content", "chapter_structure") promptArgs: Optional arguments for prompt builders ... (other args) """ maxIterations = 50 iteration = 0 accumulatedData = {} # Generic accumulation (replaces allSections) lastRawResponse = None # Get use case (REQUIRED - no auto-detection) useCase = self.useCaseRegistry.get(useCaseId) if not useCase: raise ValueError(f"Use case '{useCaseId}' not found in registry. Available use cases: {list(self.useCaseRegistry.useCases.keys())}") while iteration < maxIterations: iteration += 1 # Build prompt using use case if iteration == 1: # Initial prompt currentPrompt = useCase.initialPromptBuilder( prompt=prompt, **promptArgs or {} ) else: # Continuation prompt continuationContext = None if useCase.continuationContextBuilder: continuationContext = useCase.continuationContextBuilder( accumulatedData, lastRawResponse ) currentPrompt = useCase.continuationPromptBuilder( prompt=prompt, continuationContext=continuationContext, **promptArgs or {} ) # Make AI call result = await self._makeAiCall(currentPrompt, options, iteration, operationId, debugPrefix) lastRawResponse = result # Process response based on use case processedResult, isComplete, shouldContinue = await self._processUseCaseResponse( result, useCase, accumulatedData, iteration, debugPrefix ) if not shouldContinue: return processedResult # Max iterations reached logger.warning(f"Max iterations ({maxIterations}) reached") return accumulatedData.get("finalResult", lastRawResponse) async def _processUseCaseResponse( self, result: str, useCase: LoopingUseCase, accumulatedData: Dict[str, Any], iteration: int, debugPrefix: str ) -> Tuple[str, bool, bool]: """Process response according to use case configuration.""" # Parse JSON extractedJson = extractJsonString(result) parsedJson, parseError, _ = tryParseJson(extractedJson) if parseError: # JSON parsing failed - continue return result, False, True # Check if use case requires extraction if useCase.requiresExtraction: # Extract data (e.g., sections from document structure) extracted = self._extractData(parsedJson, useCase) accumulatedData.setdefault("extracted", []).extend(extracted) # Check completeness isComplete = self._isJsonComplete(parsedJson, useCase) # Accumulate if supported if useCase.supportsAccumulation and useCase.accumulator: accumulatedData = useCase.accumulator(accumulatedData, parsedJson, iteration) # Merge if supported if useCase.merger and accumulatedData.get("extracted"): accumulatedData["merged"] = useCase.merger(accumulatedData["extracted"], iteration) # Build result if complete if isComplete: if useCase.resultBuilder: finalResult = useCase.resultBuilder(accumulatedData, useCase) else: # Direct return finalResult = json.dumps(parsedJson, indent=2, ensure_ascii=False) accumulatedData["finalResult"] = finalResult return finalResult, True, False # Not complete - continue return result, False, True ``` --- ## Part 3: Multiple Generation Paths ### 3.1 Current State **Current**: Single document generation path in `serviceGeneration` **Structure**: ``` serviceGeneration/ ├── mainServiceGeneration.py ├── subStructureGeneration.py (chapter structure) ├── subStructureFilling.py (section structure + content) └── renderers/ (document rendering) ``` ### 3.2 Proposed Multi-Path Architecture #### Enhanced Generation Service Structure ``` serviceGeneration/ ├── mainServiceGeneration.py # Main entry point, routes by intent ├── paths/ │ ├── documentPath.py # Document generation path │ ├── codePath.py # Code generation path (NEW) │ ├── imagePath.py # Image generation path (NEW) │ ├── videoPath.py # Video generation path (FUTURE) │ └── audioPath.py # Audio generation path (FUTURE) ├── shared/ │ ├── subStructureGeneration.py # Shared structure generation (if applicable) │ ├── subContentGeneration.py # Shared content generation (if applicable) │ └── subPromptBuilder.py # Shared prompt builders └── renderers/ # Format-specific renderers ├── document/ # Document renderers (existing) ├── code/ # Code renderers (NEW) └── image/ # Image renderers (NEW) ``` #### Main Service Entry Point **Refactor**: `mainServiceGeneration.py` ```python class GenerationService: """Main generation service with multiple paths.""" def __init__(self, services): self.services = services self.documentPath = DocumentGenerationPath(services) self.codePath = CodeGenerationPath(services) self.imagePath = ImageGenerationPath(services) # Future: videoPath, audioPath async def generate( self, userPrompt: str, generationIntent: str, # "document" | "code" | "image" (detected at AI service level) documentList: Optional[DocumentReferenceList] = None, contentParts: Optional[List[ContentPart]] = None, outputFormat: str = None, **kwargs ) -> AiResponse: """ Main entry point - routes to appropriate generation path. Args: generationIntent: Intent detected at AI service level ("document" | "code" | "image") Returns: AiResponse with documents list (unified format) """ # Route to appropriate path based on generationIntent if generationIntent == "code": return await self.codePath.generateCode( userPrompt=userPrompt, contentParts=contentParts, outputFormat=outputFormat, **kwargs ) elif generationIntent == "image": return await self.imagePath.generateImages( userPrompt=userPrompt, outputFormat=outputFormat, **kwargs ) elif generationIntent == "document": return await self.documentPath.generateDocument( userPrompt=userPrompt, documentList=documentList, contentParts=contentParts, outputFormat=outputFormat, **kwargs ) # Future paths... else: raise ValueError(f"Unsupported generationIntent: {generationIntent}") ``` #### Document Generation Path (Existing, Refactored) **File**: `paths/documentPath.py` ```python class DocumentGenerationPath: """Document generation path (existing functionality, refactored).""" async def generateDocument( self, userPrompt: str, documentList: Optional[DocumentReferenceList] = None, outputFormat: str = "txt", **kwargs ) -> AiResponse: """ Generate document using existing chapter/section model. Returns: AiResponse with documents list """ # Phase 1: Chapter structure generation (with looping) chapterStructure = await self._generateChapterStructure( userPrompt=userPrompt, contentParts=contentParts, outputFormat=outputFormat ) # Phase 2: Section structure generation (parallel) sectionStructure = await self._generateSectionStructures(chapterStructure) # Phase 3: Content generation (with looping, parallel) filledStructure = await self._generateContent(sectionStructure) # Phase 4: Rendering renderedDocuments = await self._renderDocuments(filledStructure, outputFormat) # Return unified format return AiResponse( documents=renderedDocuments, content=None, metadata=AiResponseMetadata(title=title, filename=filename) ) ``` #### Code Generation Path (NEW) **File**: `paths/codePath.py` ```python class CodeGenerationPath: """Code generation path.""" async def generateCode( self, userPrompt: str, language: str = None, fileTypes: List[str] = None, projectType: str = "single_file", outputFormat: str = None, **kwargs ) -> AiResponse: """ Generate code files. Returns: AiResponse with code files as documents """ # Phase 1: Code structure generation (with looping) codeStructure = await self._generateCodeStructure( userPrompt=userPrompt, language=language, fileTypes=fileTypes, projectType=projectType ) # Phase 2: Code content generation (with looping, parallel per file) codeFiles = await self._generateCodeContent(codeStructure) # Phase 3: Code formatting & validation formattedFiles = await self._formatAndValidateCode(codeFiles) # Convert to unified document format documents = [] for file in formattedFiles: documents.append(DocumentData( documentName=file["filename"], documentData=file["content"].encode('utf-8'), mimeType=self._getMimeType(file["fileType"]), sourceJson=file )) return AiResponse( documents=documents, content=None, metadata=AiResponseMetadata(title="Generated Code", filename=None) ) async def _generateCodeStructure( self, userPrompt: str, language: str, fileTypes: List[str], projectType: str ) -> Dict[str, Any]: """Generate code structure using looping system.""" prompt = buildCodeStructurePrompt( userPrompt=userPrompt, language=language, fileTypes=fileTypes, projectType=projectType ) # Use generic looping system with code_structure use case structureJson = await self.services.ai._callAiWithLooping( prompt=prompt, options=AiCallOptions(operationType=OperationTypeEnum.DATA_GENERATE), useCaseId="code_structure", # Use parametrized use case debugPrefix="code_structure_generation", promptArgs={ "userPrompt": userPrompt, "language": language, "fileTypes": fileTypes } ) return json.loads(structureJson) async def _generateCodeContent( self, codeStructure: Dict[str, Any] ) -> List[Dict[str, Any]]: """Generate code content for each file with dependency handling.""" files = codeStructure.get("files", []) metadata = codeStructure.get("metadata", {}) # Step 1: Resolve dependency order orderedFiles = self._resolveDependencyOrder(files) # Step 2: Generate dependency files first (requirements.txt, package.json, etc.) dependencyFiles = await self._generateDependencyFiles(metadata, orderedFiles) # Step 3: Generate code files in dependency order (not fully parallel) codeFiles = [] generatedFileContext = {} # Track what's been generated for cross-file references for fileStructure in orderedFiles: # Provide context about already-generated files for proper imports fileContext = self._buildFileContext(generatedFileContext, fileStructure) # Generate this file with context fileContent = await self._generateSingleFileContent( fileStructure, fileContext=fileContext, allFilesStructure=orderedFiles ) codeFiles.append(fileContent) # Update context with generated file info (for next files) generatedFileContext[fileStructure["id"]] = { "filename": fileContent.get("filename"), "functions": fileContent.get("functions", []), "classes": fileContent.get("classes", []), "exports": fileContent.get("exports", []) } # Combine dependency files and code files return dependencyFiles + codeFiles def _resolveDependencyOrder(self, files: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """Resolve file generation order based on dependencies.""" # Build dependency graph fileMap = {f["id"]: f for f in files} dependencies = {} for file in files: fileId = file["id"] deps = file.get("dependencies", []) # List of file IDs this file depends on dependencies[fileId] = deps # Topological sort ordered = [] visited = set() tempMark = set() def visit(fileId: str): if fileId in tempMark: # Circular dependency detected - break it logger.warning(f"Circular dependency detected involving {fileId}") return if fileId in visited: return tempMark.add(fileId) for depId in dependencies.get(fileId, []): if depId in fileMap: visit(depId) tempMark.remove(fileId) visited.add(fileId) ordered.append(fileMap[fileId]) for file in files: if file["id"] not in visited: visit(file["id"]) return ordered async def _generateDependencyFiles( self, metadata: Dict[str, Any], files: List[Dict[str, Any]] ) -> List[Dict[str, Any]]: """Generate dependency files (requirements.txt, package.json, etc.).""" language = metadata.get("language", "").lower() dependencyFiles = [] # Extract all dependencies from files allDependencies = set() for file in files: fileDeps = file.get("dependencies", []) if isinstance(fileDeps, list): allDependencies.update(fileDeps) # Generate requirements.txt for Python if language in ["python", "py"]: requirementsContent = await self._generateRequirementsTxt(files, allDependencies) if requirementsContent: dependencyFiles.append({ "filename": "requirements.txt", "content": requirementsContent, "fileType": "txt", "id": "requirements_txt" }) # Generate package.json for JavaScript/TypeScript elif language in ["javascript", "typescript", "js", "ts"]: packageJson = await self._generatePackageJson(files, allDependencies, metadata) if packageJson: dependencyFiles.append({ "filename": "package.json", "content": json.dumps(packageJson, indent=2), "fileType": "json", "id": "package_json" }) return dependencyFiles async def _generateRequirementsTxt( self, files: List[Dict[str, Any]], dependencies: set ) -> str: """Generate requirements.txt content.""" # Extract Python imports from file structures pythonPackages = set() for file in files: imports = file.get("imports", []) if isinstance(imports, list): for imp in imports: # Extract package name from import (e.g., "from flask import" -> "flask") if isinstance(imp, str): # Simple extraction - can be enhanced if "import" in imp: parts = imp.split("import") if len(parts) > 0: package = parts[0].strip().split("from")[-1].strip() if package and not package.startswith("."): pythonPackages.add(package) # Generate requirements.txt if pythonPackages: return "\n".join(sorted(pythonPackages)) return None async def _generatePackageJson( self, files: List[Dict[str, Any]], dependencies: set, metadata: Dict[str, Any] ) -> Optional[Dict[str, Any]]: """Generate package.json content.""" # Extract npm packages from file structures npmPackages = {} for file in files: imports = file.get("imports", []) if isinstance(imports, list): for imp in imports: # Extract npm package (e.g., "import express from 'express'" -> "express") if isinstance(imp, str) and ("from" in imp or "require" in imp): # Simple extraction - can be enhanced if "from" in imp: parts = imp.split("from") if len(parts) > 1: package = parts[1].strip().strip("'\"") if package and not package.startswith("."): npmPackages[package] = "*" # Default version if npmPackages: return { "name": metadata.get("projectName", "generated-project"), "version": "1.0.0", "dependencies": npmPackages } return None def _buildFileContext( self, generatedFileContext: Dict[str, Dict[str, Any]], currentFile: Dict[str, Any] ) -> Dict[str, Any]: """Build context about other files for proper imports/references.""" context = { "availableFiles": [], "availableFunctions": {}, "availableClasses": {} } # Add info about already-generated files for fileId, fileInfo in generatedFileContext.items(): context["availableFiles"].append({ "id": fileId, "filename": fileInfo["filename"], "functions": fileInfo.get("functions", []), "classes": fileInfo.get("classes", []), "exports": fileInfo.get("exports", []) }) # Build function/class maps for easy lookup for func in fileInfo.get("functions", []): funcName = func.get("name", "") if funcName: context["availableFunctions"][funcName] = { "file": fileInfo["filename"], "signature": func.get("signature", "") } for cls in fileInfo.get("classes", []): className = cls.get("name", "") if className: context["availableClasses"][className] = { "file": fileInfo["filename"] } return context async def _generateSingleFileContent( self, fileStructure: Dict[str, Any], fileContext: Dict[str, Any] = None, allFilesStructure: List[Dict[str, Any]] = None ) -> Dict[str, Any]: """Generate code content for a single file with context about other files.""" # Build prompt with context about other files for proper imports prompt = buildCodeContentPrompt( fileStructure, fileContext=fileContext, allFilesStructure=allFilesStructure ) # Use generic looping system with code_content use case contentJson = await self.services.ai._callAiWithLooping( prompt=prompt, options=AiCallOptions(operationType=OperationTypeEnum.DATA_GENERATE), useCaseId="code_content", # Use parametrized use case debugPrefix=f"code_content_{fileStructure['id']}", promptArgs={ "fileStructure": fileStructure, "fileContext": fileContext, "allFilesStructure": allFilesStructure } ) parsed = json.loads(contentJson) # Extract function/class info for context building parsed["functions"] = parsed.get("files", [{}])[0].get("functions", []) parsed["classes"] = parsed.get("files", [{}])[0].get("classes", []) return parsed ``` #### Image Generation Path (NEW) **File**: `paths/imagePath.py` ```python class ImageGenerationPath: """Image generation path.""" async def generateImages( self, userPrompt: str, count: int = 1, style: str = None, format: str = "png", **kwargs ) -> AiResponse: """ Generate image files. Returns: AiResponse with image files as documents """ # Phase 1: Image prompt generation (if multiple images) if count > 1: imagePrompts = await self._generateImagePrompts(userPrompt, count, style) else: imagePrompts = [userPrompt] # Phase 2: Generate images (parallel) images = await self._generateImagesParallel(imagePrompts, format) # Convert to unified document format documents = [] for i, imageData in enumerate(images): documents.append(DocumentData( documentName=f"image_{i+1}.{format}", documentData=imageData, # Already bytes mimeType=f"image/{format}", sourceJson={"prompt": imagePrompts[i], "index": i} )) return AiResponse( documents=documents, content=None, metadata=AiResponseMetadata(title="Generated Images", filename=None) ) async def _generateImagesParallel( self, imagePrompts: List[str], format: str ) -> List[bytes]: """Generate multiple images in parallel.""" tasks = [] for prompt in imagePrompts: task = self._generateSingleImage(prompt, format) tasks.append(task) images = await asyncio.gather(*tasks) return images async def _generateSingleImage( self, prompt: str, format: str ) -> bytes: """Generate a single image.""" # Use IMAGE_GENERATE operation request = AiCallRequest( prompt=prompt, options=AiCallOptions( operationType=OperationTypeEnum.IMAGE_GENERATE, resultFormat="base64" ) ) response = await self.services.ai.callAi(request) # Decode base64 to bytes import base64 imageBytes = base64.b64decode(response.content) return imageBytes ``` --- ## Part 4: Unified Document Output ### 4.1 Current State **Current State**: ✅ All actions already return unified `ActionResult` format with `ActionDocument` objects **Note**: The unification needed is at the **AI Service level** (`AiResponse`), not at the action level. Actions already convert `AiResponse` to `ActionResult` consistently. ### 4.2 AI Service Level Format **Current**: ✅ All AI service paths already return unified `AiResponse` format **Format** (already exists): ```python @dataclass class DocumentData: """Unified document data structure (already exists).""" documentName: str # Filename documentData: bytes # File content (bytes) mimeType: str # MIME type (e.g., "text/html", "image/png", "application/pdf") sourceJson: Optional[Dict[str, Any]] = None # Source JSON structure (if applicable) @dataclass class AiResponse: """Unified AI response format (already exists).""" documents: List[DocumentData] # List of generated documents content: Optional[str] = None # Optional text content metadata: Optional[AiResponseMetadata] = None ``` **Requirement**: Ensure all new generation paths (code, image) return `AiResponse` in this format (same as document path) ### 4.3 Action Result Integration **Current**: ✅ All actions already convert `AiResponse` to `ActionResult` consistently **Pattern** (already implemented in all actions): ```python # All actions follow this pattern (existing code): async def execute(self, parameters: Dict[str, Any]) -> ActionResult: # Call AI service - returns AiResponse aiResponse = await self.services.ai.callAiContent(...) # Convert AiResponse to ActionDocument (unified format) documents = [] for docData in aiResponse.documents: documents.append(ActionDocument( documentName=docData.documentName, documentData=docData.documentData, mimeType=docData.mimeType, sourceJson=docData.sourceJson )) return ActionResult.isSuccess(documents=documents) # ✅ Already unified ``` **Note**: - ✅ Actions already return unified `ActionResult` format - ✅ No changes needed at action level - ✅ Focus: Ensure new AI service paths (code, image) return `AiResponse` consistently --- ## Part 5: Implementation Plan ### Phase 1: Foundation (Weeks 1-2) 1. **Explicit Intent Requirement at AI Service Level** - **Note**: No `IntentDetector` class needed - intent comes explicitly from actions - Integrate `generationIntent` parameter into `callAiContent()` method - Add `_handleCodeGeneration()` and `_handleDocumentGeneration()` methods - Update `ai.process` to detect image formats from `resultType` (format detection, not intent detection) - Require explicit `generationIntent` for all `DATA_GENERATE` operations - Test with various actions (generateDocument, generateCode, process) - Verify `IMAGE_GENERATE` still works correctly (no changes) 2. **Generic Looping System** - Create `LoopingUseCase` dataclass - Create `LoopingUseCaseRegistry` - Register existing use cases (section_content, chapter_structure, code_structure) - Refactor `subAiCallLooping.py` to use registry ### Phase 2: Code Generation (Weeks 3-4) 1. **Code Generation Path** - Create `paths/codePath.py` - Implement code structure generation - Implement code content generation - Register code use cases in looping registry - Create `ai.generateCode` action 2. **Integration** - Integrate code path into `mainServiceGeneration.py` - Test code generation end-to-end - Validate code output quality ### Phase 3: Image Generation (Weeks 5-6) 1. **Image Generation Path** - Create `paths/imagePath.py` - Implement standalone image generation - Support batch image generation - Register image use cases in looping registry - Create `ai.generateImages` action 2. **Integration** - Integrate image path into `mainServiceGeneration.py` - Test image generation end-to-end - Validate image output quality ### Phase 4: Refinement (Weeks 7-8) 1. **Unified Output** - Ensure all paths return unified `AiResponse` format - Standardize action result handling - Test cross-path compatibility 2. **Documentation & Testing** - Document all use cases - Add unit tests for looping system - Add integration tests for each path - Performance testing --- ## Part 6: Migration Strategy ### Clean Implementation 1. **No Legacy Code**: Remove old prompt builder parameters completely 2. **Clear Use Cases**: All calls must specify explicit `useCaseId` 3. **No Fallback**: Fail fast if use case not found or intent missing ### Testing Strategy 1. **Unit Tests**: Test each use case independently 2. **Integration Tests**: Test full generation flows 3. **Use Case Tests**: Test all registered use cases 4. **Performance Tests**: Compare performance before/after --- ## Part 7: Future Extensions ### Video Generation Path (Future) - Similar structure to image path - Video structure planning (scenes, transitions) - Frame-by-frame generation - Video encoding ### Audio Generation Path (Future) - Similar structure to image path - Text-to-speech generation - Music generation - Audio file output ### Additional Use Cases - Easy to add new use cases to registry - Just register new `LoopingUseCase` configuration - No changes to core looping system needed --- ## Part 8: Critical Cross-Check ### 8.1 Codebase Verification **✅ Multiple Files Support**: - Current system already supports multiple documents via `renderReport()` → returns `List[RenderedDocument]` - HTML renderer creates multiple files (HTML + images) as separate documents - Code generation path enhanced to generate multiple code files + dependency files **✅ Code Generation Intelligence**: 1. **Dependency Handling**: - ✅ Code structure includes `dependencies` field (list of file IDs) - ✅ `_resolveDependencyOrder()` implements topological sort for proper generation order - ✅ Handles circular dependencies gracefully - ✅ Files generated sequentially based on dependencies (not fully parallel) 2. **Requirements/Dependencies Files**: - ✅ `_generateDependencyFiles()` generates: - `requirements.txt` for Python projects (extracts packages from imports) - `package.json` for JavaScript/TypeScript projects (extracts npm packages) - ✅ Dependency files generated BEFORE code files - ✅ Extracts dependencies from file structures' `imports` field 3. **Cross-File References**: - ✅ `_buildFileContext()` provides context about already-generated files - ✅ Tracks functions, classes, and exports from each file - ✅ Context passed to each file generation for proper imports - ✅ `fileContext` includes: - Available files and their exports - Function signatures for proper imports - Class definitions for proper imports 4. **File Structure Template**: ```json { "metadata": { "language": "python|javascript|typescript", "projectType": "single_file|multi_file", "projectName": "..." }, "files": [ { "id": "file_1", "filename": "main.py", "fileType": "py", "dependencies": ["file_2"], // File IDs this depends on "imports": ["from utils import helper"], // For dependency extraction "functions": [{"name": "main", "signature": "..."}], "classes": [{"name": "MyClass", "signature": "..."}] } ] } ``` ### 8.2 Architecture Validation **✅ Smart Enough for Multi-File Projects**: - ✅ Dependency resolution ensures proper order - ✅ Requirements.txt/package.json automatically generated - ✅ Cross-file context enables proper imports/references - ✅ Function/class tracking enables accurate references - ✅ Sequential generation with context accumulation **✅ Current Codebase Compatibility**: - ✅ Uses existing `List[RenderedDocument]` pattern - ✅ Follows existing `AiResponse` → `ActionResult` conversion - ✅ Compatible with existing document processing pipeline - ✅ No breaking changes to existing document generation **✅ Potential Enhancements** (Future): - More sophisticated import parsing (AST-based) - Support for more dependency file types (Cargo.toml, go.mod, etc.) - Parallel generation of independent files (files without dependencies) - Validation of imports against generated files --- ## Conclusion This refactoring provides: 1. ✅ **AI Service-Level Intent Detection**: Detect document vs code when `DATA_GENERATE` is called - workflow unchanged 2. ✅ **Generic Looping System**: Parametrized, extensible, supports all JSON formats 3. ✅ **Multiple Generation Paths**: Document, code, image paths (extensible to video/audio) 4. ✅ **Unified Output**: All paths return same format, unified as action result documents 5. ✅ **Smart Code Generation**: Multi-file projects with dependencies, requirements.txt, and proper references **Benefits**: - **Minimal Changes**: Workflow level (task/action planning) remains unchanged - **Correct Level**: Intent detection at AI service level where generation happens - **Clean Architecture**: Separation of concerns - workflow handles planning, AI service handles generation - **Easy to Extend**: New intents can be added by registering new use cases - **Clear Code**: No legacy code, no deprecated parameters, no fallback logic - **Well-tested Foundation**: Changes isolated to AI service layer - **Smart Code Generation**: Handles complex multi-file projects with dependencies **Next Steps**: 1. Review and approve architecture 2. Start Phase 1 implementation 3. Iterate based on feedback