diff --git a/modules/workflows/methods/methodAi/actions/process.py b/modules/workflows/methods/methodAi/actions/process.py index 0dd37ce3..c76ce35e 100644 --- a/modules/workflows/methods/methodAi/actions/process.py +++ b/modules/workflows/methods/methodAi/actions/process.py @@ -54,8 +54,9 @@ async def process(self, parameters: Dict[str, Any]) -> ActionResult: logger.error(f"Invalid documentList type: {type(documentListParam)}") documentList = DocumentReferenceList(references=[]) - # Optional: if omitted, formats determined from prompt. Default "txt" is validation fallback only. - resultType = parameters.get("resultType") + resultType = parameters.get("resultType", "txt") + simpleMode = parameters.get("simpleMode", False) + if not aiPrompt: logger.error(f"aiPrompt is missing or empty. Parameters: {parameters}") @@ -63,18 +64,11 @@ async def process(self, parameters: Dict[str, Any]) -> ActionResult: error="AI prompt is required" ) - # Handle optional resultType: if None, formats determined from prompt by AI - if resultType: - normalized_result_type = (str(resultType).strip().lstrip('.').lower() or "txt") - output_extension = f".{normalized_result_type}" - output_format = output_extension.replace('.', '') or 'txt' - logger.info(f"Using result type: {resultType} -> {output_extension}") - else: - # No format specified - AI will determine formats from prompt - normalized_result_type = None - output_extension = None - output_format = None - logger.debug("resultType not provided - formats will be determined from prompt by AI") + # Determine output extension and default MIME type without duplicating service logic + normalized_result_type = (str(resultType).strip().lstrip('.').lower() or "txt") + output_extension = f".{normalized_result_type}" + output_mime_type = "application/octet-stream" # Prefer service-provided mimeType when available + logger.info(f"Using result type: {resultType} -> {output_extension}, simpleMode: {simpleMode}") output_mime_type = "application/octet-stream" # Prefer service-provided mimeType when available @@ -96,38 +90,99 @@ async def process(self, parameters: Dict[str, Any]) -> ActionResult: # Update progress - preparing AI call self.services.chat.progressLogUpdate(operationId, 0.4, "Preparing AI call") - # Detect image generation from resultType (if provided) - imageFormats = ["png", "jpg", "jpeg", "gif", "webp"] - isImageGeneration = normalized_result_type in imageFormats if normalized_result_type else False + # Build options + output_format = output_extension.replace('.', '') or 'txt' - # Build options with correct operationType - from modules.datamodels.datamodelAi import OperationTypeEnum - # resultFormat in options can be None - formats will be determined by AI if not provided - options = AiCallOptions( - resultFormat=output_format, # Can be None - formats determined by AI - operationType=OperationTypeEnum.IMAGE_GENERATE if isImageGeneration else OperationTypeEnum.DATA_GENERATE - ) - - # Get generationIntent from parameters (required for DATA_GENERATE) - # Default to "document" if not provided (most common use case) - # For code generation, use ai.generateCode action or explicitly pass generationIntent="code" - generationIntent = parameters.get("generationIntent", "document") + # Simple mode: fast path without document generation pipeline + if simpleMode: + # Update progress - calling AI (simple mode) + self.services.chat.progressLogUpdate(operationId, 0.6, "Calling AI (simple mode)") + + # Extract context from documents if provided + context_text = "" + if documentList and len(documentList.references) > 0: + try: + # Get documents from workflow + documents = self.services.chat.getChatDocumentsFromDocumentList(documentList) + context_parts = [] + for doc in documents: + if hasattr(doc, 'fileId') and doc.fileId: + # Get file data + fileData = self.services.interfaceDbComponent.getFileData(doc.fileId) + if fileData: + if isinstance(fileData, bytes): + doc_text = fileData.decode('utf-8', errors='ignore') + else: + doc_text = str(fileData) + context_parts.append(doc_text) + if context_parts: + context_text = "\n\n".join(context_parts) + except Exception as e: + logger.warning(f"Error extracting context from documents in simple mode: {e}") + + # Use direct AI call without document generation pipeline + from modules.datamodels.datamodelAi import AiCallRequest, OperationTypeEnum, ProcessingModeEnum + request = AiCallRequest( + prompt=aiPrompt, + context=context_text if context_text else None, + options=AiCallOptions( + resultFormat=output_format, + operationType=OperationTypeEnum.DATA_ANALYSE, + processingMode=ProcessingModeEnum.BASIC + ) + ) + + aiResponse_obj = await self.services.ai.callAi(request) + + # Convert AiCallResponse to AiResponse format + from modules.datamodels.datamodelWorkflow import AiResponse, AiResponseMetadata + aiResponse = AiResponse( + content=aiResponse_obj.content, + metadata=AiResponseMetadata( + additionalData={ + "modelName": aiResponse_obj.modelName, + "priceUsd": aiResponse_obj.priceUsd, + "processingTime": aiResponse_obj.processingTime, + "bytesSent": aiResponse_obj.bytesSent, + "bytesReceived": aiResponse_obj.bytesReceived, + "errorCount": aiResponse_obj.errorCount + } + ), + documents=[] # Simple mode doesn't generate documents + ) + else: + # Full mode: use unified callAiContent method + options = AiCallOptions( + resultFormat=output_format + ) - # Update progress - calling AI - self.services.chat.progressLogUpdate(operationId, 0.6, "Calling AI") + # Update progress - calling AI + self.services.chat.progressLogUpdate(operationId, 0.6, "Calling AI") - # Use unified callAiContent method with BOTH documentList and contentParts - # Extraction is handled by AI service - no extraction here - # outputFormat: Optional - if None, formats determined from prompt by AI - aiResponse = await self.services.ai.callAiContent( - prompt=aiPrompt, - options=options, - documentList=documentList, # Pass documentList - AI service handles extraction - contentParts=contentParts, # Pass contentParts if provided (or None) - outputFormat=output_format, # Can be None - AI determines from prompt - parentOperationId=operationId, - generationIntent=generationIntent # REQUIRED for DATA_GENERATE - ) + # Use unified callAiContent method + # If contentParts provided (pre-extracted), use them directly + # Otherwise, pass documentList and let callAiContent handle Phases 5A-5E internally + # Note: ContentExtracted documents (from context.extractContent) are now handled + # automatically in _extractAndPrepareContent() (Phase 5B) + if contentParts: + # Pre-extracted ContentParts - use them directly + aiResponse = await self.services.ai.callAiContent( + prompt=aiPrompt, + options=options, + contentParts=contentParts, # Pre-extracted ContentParts + outputFormat=output_format, + parentOperationId=operationId + ) + else: + # Pass documentList - callAiContent handles Phases 5A-5E internally + # This includes automatic detection of ContentExtracted documents + aiResponse = await self.services.ai.callAiContent( + prompt=aiPrompt, + options=options, + documentList=documentList, # callAiContent macht Phasen 5A-5E + outputFormat=output_format, + parentOperationId=operationId + ) # Update progress - processing result self.services.chat.progressLogUpdate(operationId, 0.8, "Processing result") diff --git a/modules/workflows/methods/methodAi/methodAi.py b/modules/workflows/methods/methodAi/methodAi.py index 234d573b..3afc3433 100644 --- a/modules/workflows/methods/methodAi/methodAi.py +++ b/modules/workflows/methods/methodAi/methodAi.py @@ -60,24 +60,16 @@ class MethodAi(MethodBase): frontendOptions=["txt", "json", "md", "csv", "xml", "html", "pdf", "docx", "xlsx", "pptx", "png", "jpg"], required=False, default="txt", - description="Output file extension. Optional: if omitted, formats are determined from prompt by AI. Default \"txt\" is validation fallback only. With per-document format determination, AI can determine different formats for different documents based on prompt." + description="Output file extension. All output documents will use this format" ), - "generationIntent": WorkflowActionParameter( - name="generationIntent", - type="str", - frontendType=FrontendType.SELECT, - frontendOptions=["document", "code", "image"], + "simpleMode": WorkflowActionParameter( + name="simpleMode", + type="bool", + frontendType=FrontendType.CHECKBOX, required=False, - default="document", - description="Explicit generation intent (\"document\" | \"code\" | \"image\"). Required for DATA_GENERATE operations. Defaults to \"document\" if not provided. For code generation, use ai.generateCode action or explicitly pass generationIntent=\"code\". For IMAGE_GENERATE operations, this parameter is ignored." - ), - "contentParts": WorkflowActionParameter( - name="contentParts", - type="List[ContentPart]", - frontendType=FrontendType.HIDDEN, - required=False, - description="Pre-extracted content parts (internal parameter, typically passed between actions). If provided, these will be used instead of extracting from documentList. Can be a list of ContentPart objects or an object with a 'parts' attribute." - ), + default=False, + description="If true, uses fast simple AI call without document generation pipeline. Use for chatbot responses and simple text generation." + ) }, execute=process.__get__(self, self.__class__) ),