diff --git a/config.ini b/config.ini index 0ba1e0bf..9c529400 100644 --- a/config.ini +++ b/config.ini @@ -36,20 +36,6 @@ Security_LOCK_DURATION_MINUTES = 30 # Content Neutralization configuration Content_Neutralization_ENABLED = False -# Agent Webcrawler configuration -Agent_Webcrawler_SERPAPI_ENGINE = google -Agent_Webcrawler_SERPAPI_APIKEY = 7304bd34bca767aa52dd3233297e30a9edc0abc57871f702b3f8238b9d3ee7bc -Agent_Webcrawler_SERPAPI_MAX_URLS = 3 -Agent_Webcrawler_SERPAPI_MAX_SEARCH_KEYWORDS = 3 -Agent_Webcrawler_SERPAPI_MAX_SEARCH_RESULTS = 5 -Agent_Webcrawler_SERPAPI_TIMEOUT = 10 -Agent_Webcrawler_SERPAPI_USER_AGENT = Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 - -# Agent Coder configuration -Agent_Coder_INSTALL_TIMEOUT = 180 -Agent_Coder_EXECUTION_TIMEOUT = 60 -Agent_Coder_EXECUTION_RETRY = 5 - # Agent Mail configuration Service_MSFT_CLIENT_ID = c7e7112d-61dc-4f3a-8cd3-08cc4cd7504c Service_MSFT_CLIENT_SECRET = Kxf8Q~2lJIteZ~JaI32kMf1lfaWKATqxXiNiFbzV @@ -58,3 +44,16 @@ Service_MSFT_TENANT_ID = common # Google Service configuration Service_GOOGLE_CLIENT_ID = 354925410565-aqs2b2qaiqmm73qpjnel6al8eid78uvg.apps.googleusercontent.com Service_GOOGLE_CLIENT_SECRET = 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 + +# Tavily Web Search configuration +Connector_WebTavily_API_KEY = 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 + +# Web Search configuration +Web_Search_MAX_QUERY_LENGTH = 400 +Web_Search_MAX_RESULTS = 20 +Web_Search_MIN_RESULTS = 1 + +# Web Crawl configuration +Web_Crawl_TIMEOUT = 30 +Web_Crawl_MAX_RETRIES = 3 +Web_Crawl_RETRY_DELAY = 2 \ No newline at end of file diff --git a/modules/chat/documents/documentExtraction.py b/modules/chat/documents/documentExtraction.py index ea96289d..a304cbe3 100644 --- a/modules/chat/documents/documentExtraction.py +++ b/modules/chat/documents/documentExtraction.py @@ -341,7 +341,7 @@ class DocumentExtraction: # Use documentUtility for mime type - mime_type = getMimeTypeFromExtension(getFileExtension(fileName), self._serviceCenter) + mime_type = getMimeTypeFromExtension(getFileExtension(fileName)) return [ContentItem( label="main", data=content, @@ -360,7 +360,7 @@ class DocumentExtraction: """Process CSV document with robust encoding detection""" try: content = self._robustTextDecode(fileData, fileName) - mime_type = getMimeTypeFromExtension(getFileExtension(fileName), self._serviceCenter) + mime_type = getMimeTypeFromExtension(getFileExtension(fileName)) return [ContentItem( label="main", data=content, @@ -380,7 +380,7 @@ class DocumentExtraction: try: content = self._robustTextDecode(fileData, fileName) jsonData = json.loads(content) - mime_type = getMimeTypeFromExtension(getFileExtension(fileName), self._serviceCenter) + mime_type = getMimeTypeFromExtension(getFileExtension(fileName)) return [ContentItem( label="main", data=content, @@ -399,7 +399,7 @@ class DocumentExtraction: """Process XML document with robust encoding detection""" try: content = self._robustTextDecode(fileData, fileName) - mime_type = getMimeTypeFromExtension(getFileExtension(fileName), self._serviceCenter) + mime_type = getMimeTypeFromExtension(getFileExtension(fileName)) return [ContentItem( label="main", data=content, @@ -418,7 +418,7 @@ class DocumentExtraction: """Process HTML document with robust encoding detection""" try: content = self._robustTextDecode(fileData, fileName) - mime_type = getMimeTypeFromExtension(getFileExtension(fileName), self._serviceCenter) + mime_type = getMimeTypeFromExtension(getFileExtension(fileName)) return [ContentItem( label="main", data=content, @@ -512,7 +512,7 @@ class DocumentExtraction: # Combine all meaningful content final_content = "\n".join(meaningful_content) - mime_type = getMimeTypeFromExtension(getFileExtension(fileName), self._serviceCenter) + mime_type = getMimeTypeFromExtension(getFileExtension(fileName)) return [ContentItem( label="svg_content", data=final_content, diff --git a/modules/chat/documents/documentGeneration.py b/modules/chat/documents/documentGeneration.py index 5534462a..dfe10918 100644 --- a/modules/chat/documents/documentGeneration.py +++ b/modules/chat/documents/documentGeneration.py @@ -98,26 +98,12 @@ class DocumentGenerator: logger.info(f"Document {document_name} has content: {len(content)} characters") - # Create file in system - file_id = self.service.createFile( - fileName=document_name, - mimeType=mime_type, - content=content, - base64encoded=False - ) - if not file_id: - logger.error(f"Failed to create file for document {document_name}") - continue - - logger.info(f"Created file with ID: {file_id}") - - # Create document object using existing file ID + # Create document with file in one step document = self.service.createDocument( fileName=document_name, mimeType=mime_type, content=content, - base64encoded=False, - existing_file_id=file_id + base64encoded=False ) if document: # Set workflow context on the document if possible diff --git a/modules/chat/documents/documentUtility.py b/modules/chat/documents/documentUtility.py index 3d674720..5b0a612c 100644 --- a/modules/chat/documents/documentUtility.py +++ b/modules/chat/documents/documentUtility.py @@ -1,51 +1,160 @@ import json import logging +import os from typing import Any, Dict logger = logging.getLogger(__name__) def getFileExtension(fileName: str) -> str: - """Extract file extension from fileName""" + """Extract file extension from fileName (without dot, lowercased).""" if '.' in fileName: return fileName.rsplit('.', 1)[-1].lower() return '' -def getMimeTypeFromExtension(extension: str, service=None) -> str: - """Get MIME type based on file extension. Optionally use a service for mapping.""" - if service: - return service.getMimeTypeFromExtension(extension) - # Fallback mapping - mapping = { +def getMimeTypeFromExtension(extension: str) -> str: + """ + Get MIME type based on file extension. + This method consolidates MIME type detection from extension. + + Args: + extension: File extension (with or without dot) + + Returns: + str: MIME type for the extension + """ + # Normalize extension (remove dot if present) + if extension.startswith('.'): + extension = extension[1:] + + # Map extensions to MIME types + mime_types = { 'txt': 'text/plain', - 'md': 'text/markdown', - 'html': 'text/html', - 'css': 'text/css', - 'js': 'application/javascript', 'json': 'application/json', - 'csv': 'text/csv', 'xml': 'application/xml', + 'csv': 'text/csv', + 'html': 'text/html', + 'htm': 'text/html', + 'md': 'text/markdown', 'py': 'text/x-python', + 'js': 'application/javascript', + 'css': 'text/css', 'pdf': 'application/pdf', + 'doc': 'application/msword', 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'xls': 'application/vnd.ms-excel', 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'png': 'image/png', + 'ppt': 'application/vnd.ms-powerpoint', + 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'svg': 'image/svg+xml', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', + 'png': 'image/png', 'gif': 'image/gif', - 'svg': 'image/svg+xml', + 'bmp': 'image/bmp', + 'webp': 'image/webp', + 'zip': 'application/zip', + 'rar': 'application/x-rar-compressed', + '7z': 'application/x-7z-compressed', + 'tar': 'application/x-tar', + 'gz': 'application/gzip' } - return mapping.get(extension.lower(), 'application/octet-stream') + return mime_types.get(extension.lower(), 'application/octet-stream') + +def detectContentTypeFromData(fileData: bytes, fileName: str) -> str: + """ + Detect content type from file data and fileName. + This method makes the MIME type detection function accessible through the service center. + + Args: + fileData: Raw file data as bytes + fileName: Name of the file + + Returns: + str: Detected MIME type + """ + try: + # Check file extension first + ext = os.path.splitext(fileName)[1].lower() + if ext: + # Map common extensions to MIME types + extToMime = { + '.txt': 'text/plain', + '.md': 'text/markdown', + '.csv': 'text/csv', + '.json': 'application/json', + '.xml': 'application/xml', + '.js': 'application/javascript', + '.py': 'application/x-python', + '.svg': 'image/svg+xml', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif', + '.bmp': 'image/bmp', + '.webp': 'image/webp', + '.pdf': 'application/pdf', + '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + '.doc': 'application/msword', + '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + '.xls': 'application/vnd.ms-excel', + '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + '.ppt': 'application/vnd.ms-powerpoint', + '.html': 'text/html', + '.htm': 'text/html', + '.css': 'text/css', + '.zip': 'application/zip', + '.rar': 'application/x-rar-compressed', + '.7z': 'application/x-7z-compressed', + '.tar': 'application/x-tar', + '.gz': 'application/gzip' + } + if ext in extToMime: + return extToMime[ext] + + # Try to detect from content + if fileData.startswith(b'%PDF'): + return 'application/pdf' + elif fileData.startswith(b'PK\x03\x04'): + # ZIP-based formats (docx, xlsx, pptx) + return 'application/zip' + elif fileData.startswith(b'<'): + # XML-based formats + try: + text = fileData.decode('utf-8', errors='ignore') + if ' str: """Detect MIME type from file bytes and fileName using a service if provided.""" try: - if service: + if service and hasattr(service, 'detectContentTypeFromData'): detected = service.detectContentTypeFromData(file_bytes, fileName) if detected and detected != 'application/octet-stream': return detected - # Fallback: guess from extension - ext = getFileExtension(fileName) - return getMimeTypeFromExtension(ext, service) + # Fallback: use our consolidated function + return detectContentTypeFromData(file_bytes, fileName) except Exception as e: logger.warning(f"Error in MIME type detection for {fileName}: {str(e)}") return 'application/octet-stream' diff --git a/modules/chat/handling/handlingTasks.py b/modules/chat/handling/handlingTasks.py index 290fdf0a..88465b0e 100644 --- a/modules/chat/handling/handlingTasks.py +++ b/modules/chat/handling/handlingTasks.py @@ -108,7 +108,7 @@ class HandlingTasks: # Log the full task planning prompt being sent to AI for debugging logger.info("=== TASK PLANNING PROMPT SENT TO AI ===") logger.info(f"User Input: {userInput}") - logger.info(f"Available Documents: {len(available_docs) if available_docs else 0}") + logger.info(f"Available Documents: {available_docs}") logger.info("=== FULL TASK PLANNING PROMPT ===") logger.info(task_planning_prompt) logger.info("=== END TASK PLANNING PROMPT ===") @@ -192,7 +192,8 @@ class HandlingTasks: task_plan = TaskPlan( overview=task_plan_dict.get('overview', ''), - tasks=tasks + tasks=tasks, + userMessage=task_plan_dict.get('userMessage', '') ) # Set workflow totals for progress tracking @@ -217,24 +218,19 @@ class HandlingTasks: """Create a chat message containing the task plan with user-friendly messages""" try: # Build task plan summary - task_summary = f"📋 **Task Plan Generated**\n\n" - task_summary += f"**Overview:** {task_plan.overview}\n\n" - task_summary += f"**Total Tasks:** {len(task_plan.tasks)}\n\n" - - # Add each task with its user message - for i, task in enumerate(task_plan.tasks): - task_summary += f"**Task {i+1}:** {task.objective}\n" - if task.userMessage: - task_summary += f" 💬 {task.userMessage}\n" - if task.success_criteria: - criteria_str = ', '.join(task.success_criteria) - task_summary += f" ✅ Success Criteria: {criteria_str}\n" - task_summary += "\n" - + task_summary = f"📋 **Task Plan**\n\n" + # Get overall user message from task plan if available overall_message = task_plan.userMessage if overall_message: - task_summary += f"**Plan Summary:** {overall_message}\n\n" + task_summary += f"{overall_message}\n\n" + + # Add each task with its user message + for i, task in enumerate(task_plan.tasks): + if task.userMessage: + task_summary += f"💬 {task.userMessage}\n" + task_summary += "\n" + # Create workflow message message_data = { @@ -269,76 +265,6 @@ class HandlingTasks: except Exception as e: logger.error(f"Error creating task plan message: {str(e)}") - async def createDocumentContextMessage(self, documents: List, workflow): - """Create a chat message with document context and workflow labeling""" - try: - # Get current workflow context and stats - workflow_context = self.service.getWorkflowContext() - workflow_stats = self.service.getWorkflowStats() - - # Create a simple document context message without AI dependency - message_text = f"📄 **Document Context**\n\n" - message_text += f"**Total Documents:** {len(documents)}\n\n" - - # Add workflow context information - current_round = workflow_context.get('currentRound', 0) - current_task = workflow_context.get('currentTask', 0) - total_tasks = workflow_stats.get('totalTasks', 0) - current_action = workflow_context.get('currentAction', 0) - total_actions = workflow_stats.get('totalActions', 0) - - message_text += f"**Workflow Context:**\n" - message_text += f"- Round: {current_round}\n" - if total_tasks > 0: - message_text += f"- Task: {current_task}/{total_tasks}\n" - else: - message_text += f"- Task: {current_task}\n" - if total_actions > 0: - message_text += f"- Action: {current_action}/{total_actions}\n" - else: - message_text += f"- Action: {current_action}\n" - message_text += f"- Status: {workflow_stats.get('workflowStatus', 'unknown')}\n\n" - - # Add document list - if documents: - message_text += "**Available Documents:**\n" - for i, doc in enumerate(documents[:5]): # Show first 5 documents - message_text += f"- {doc.fileName if hasattr(doc, 'fileName') else f'Document {i+1}'}\n" - if len(documents) > 5: - message_text += f"- ... and {len(documents) - 5} more documents\n" - message_text += "\n" - - message_text += "Document context information is available for processing." - - # Create workflow message - message_data = { - "workflowId": workflow.id, - "role": "assistant", - "message": message_text, - "status": "step", - "sequenceNr": len(workflow.messages) + 1, - "publishedAt": get_utc_timestamp(), - "documentsLabel": "document_context", - "documents": [], # Empty documents for context message - # Add workflow context fields - "roundNumber": workflow_context.get('currentRound', 0), - "taskNumber": workflow_context.get('currentTask', 0), - "actionNumber": workflow_context.get('currentAction', 0), - # Add progress status - "taskProgress": "pending", - "actionProgress": "pending" - } - - message = self.chatInterface.createWorkflowMessage(message_data) - if message: - workflow.messages.append(message) - logger.info(f"Document context message created with {len(documents)} documents") - else: - logger.error("Failed to create document context message") - - except Exception as e: - logger.error(f"Error creating document context message: {str(e)}") - async def generateTaskActions(self, task_step, workflow, previous_results=None, enhanced_context=None) -> List[TaskAction]: """Generate actions for a given task step.""" try: @@ -386,12 +312,8 @@ class HandlingTasks: # Log available resources for debugging logger.info("=== AVAILABLE RESOURCES FOR ACTION GENERATION ===") - logger.info(f"Available Documents: {len(available_docs) if available_docs else 0}") - if available_docs: - for i, doc in enumerate(available_docs[:5]): # Show first 5 - logger.info(f" Doc {i+1}: {doc}") - if len(available_docs) > 5: - logger.info(f" ... and {len(available_docs) - 5} more documents") + logger.info(f"Available Documents: {available_docs}") + # Note: available_docs is now a string description, not a list logger.info(f"Available Connections: {len(available_connections) if available_connections else 0}") if available_connections: for i, conn in enumerate(available_connections[:5]): # Show first 5 @@ -450,7 +372,7 @@ class HandlingTasks: logger.info(f"Task Step ID: {action_context.task_step.id if action_context.task_step else 'None'}") logger.info(f"Task Step Objective: {action_context.task_step.objective if action_context.task_step else 'None'}") logger.info(f"Workflow ID: {action_context.workflow_id}") - logger.info(f"Available Documents Count: {len(action_context.available_documents) if action_context.available_documents else 0}") + logger.info(f"Available Documents: {action_context.available_documents or 'No documents available'}") logger.info(f"Available Connections Count: {len(action_context.available_connections) if action_context.available_connections else 0}") logger.info(f"Previous Results Count: {len(action_context.previous_results) if action_context.previous_results else 0}") logger.info(f"Retry Count: {action_context.retry_count}") @@ -546,25 +468,13 @@ class HandlingTasks: # Create database log entry for task start in format expected by frontend if task_index is not None: - if total_tasks is not None: - self.chatInterface.createWorkflowLog({ - "workflowId": workflow.id, - "message": f"Executing task {task_index}/{total_tasks}", - "type": "info" - }) - else: - self.chatInterface.createWorkflowLog({ - "workflowId": workflow.id, - "message": f"Executing task {task_index}/?", - "type": "info" - }) - + # Create a task start message for the user task_progress = f"{task_index}/{total_tasks}" if total_tasks is not None else str(task_index) task_start_message = { "workflowId": workflow.id, "role": "assistant", - "message": f"🚀 Starting Task {task_progress}\n\nObjective: {task_step.objective}", + "message": f"🚀 **Task {task_progress}**", "status": "step", "sequenceNr": len(workflow.messages) + 1, "publishedAt": get_utc_timestamp(), @@ -617,11 +527,6 @@ class HandlingTasks: logger.error("No actions defined for task step, aborting task execution") break - # Create document context message if documents are available - available_docs = self.service.getAvailableDocuments(workflow) - if available_docs: - await self.createDocumentContextMessage(available_docs, workflow) - action_results = [] for action_idx, action in enumerate(actions): # Check workflow status before each action execution @@ -639,18 +544,11 @@ class HandlingTasks: # Log action start in format expected by frontend logger.info(f"Task {task_index} - Starting action {action_number}/{total_actions}") - # Create database log entry for action start - self.chatInterface.createWorkflowLog({ - "workflowId": workflow.id, - "message": f"Task {task_index} - Starting action {action_number}/{total_actions}", - "type": "info" - }) - # Create an action start message for the user action_start_message = { "workflowId": workflow.id, "role": "assistant", - "message": f"⚡ Task {task_index} - Action {action_number}/{total_actions}\n\nMethod: {action.execMethod}.{action.execAction}", + "message": f"⚡ **Action {action_number}/{total_actions}** (Method {action.execMethod}.{action.execAction})", "status": "step", "sequenceNr": len(workflow.messages) + 1, "publishedAt": get_utc_timestamp(), @@ -694,34 +592,19 @@ class HandlingTasks: if success: logger.info(f"=== TASK {task_index or '?'} COMPLETED SUCCESSFULLY: {task_step.objective} ===") - # Create database log entry for task completion - if total_tasks is not None: - self.chatInterface.createWorkflowLog({ - "workflowId": workflow.id, - "message": f"🎯 Task {task_index}/{total_tasks} completed", - "type": "success" - }) - else: - self.chatInterface.createWorkflowLog({ - "workflowId": workflow.id, - "message": f"🎯 Task {task_index}/? completed", - "type": "success" - }) - # Create a task completion message for the user task_progress = f"{task_index}/{total_tasks}" if total_tasks is not None else str(task_index) # Enhanced completion message with criteria details - completion_message = f"🎯 Task {task_progress} Completed Successfully!\n\nObjective: {task_step.objective}\n\nFeedback: {feedback or 'Task completed successfully'}" + completion_message = f"🎯 **Task {task_progress}**\n\n✅ {feedback or 'Task completed successfully'}" # Add criteria status if available if hasattr(review_result, 'met_criteria') and review_result.met_criteria: - completion_message += f"\n\n✅ **Success Criteria Met:**\n" for criterion in review_result.met_criteria: - completion_message += f"• {criterion}\n" + completion_message += f"\n• {criterion}" if hasattr(review_result, 'quality_score'): - completion_message += f"\n📊 **Quality Score:** {review_result.quality_score}/10" + completion_message += f"\n📊 Score {review_result.quality_score}/10" task_completion_message = { "workflowId": workflow.id, @@ -740,10 +623,6 @@ class HandlingTasks: "taskProgress": "success" } - # Add user-friendly message if available - if task_step.userMessage: - task_completion_message["message"] += f"\n\n💬 {task_step.userMessage}" - message = self.chatInterface.createWorkflowMessage(task_completion_message) if message: workflow.messages.append(message) @@ -824,7 +703,7 @@ class HandlingTasks: retry_message = { "workflowId": workflow.id, "role": "assistant", - "message": f"🔄 Task {task_index} requires retry: {review_result.improvements}", + "message": f"🔄 **Task {task_index}** needs retry: {review_result.improvements}", "status": "step", "sequenceNr": len(workflow.messages) + 1, "publishedAt": get_utc_timestamp(), @@ -843,19 +722,19 @@ class HandlingTasks: continue else: logger.error(f"=== TASK {task_index or '?'} FAILED: {task_step.objective} after {attempt+1} attempts ===") - + task_progress = f"{task_index}/{total_tasks}" if total_tasks is not None else str(task_index) + # Create user-facing error message for task failure - error_message = f"❌ Task {task_index or '?'} - '{task_step.objective}' failed after {attempt+1} attempts\n\n" - error_message += f"Objective: {task_step.objective}\n\n" + error_message = f"**Task {task_progress}**\n\n❌ '{task_step.objective}' {attempt+1}x failed\n\n" # Add specific error details if available if review_result and hasattr(review_result, 'reason') and review_result.reason: - error_message += f"Reason: {review_result.reason}\n\n" + error_message += f"{review_result.reason}\n\n" # Add criteria progress information if available if retry_context and hasattr(retry_context, 'criteria_progress'): progress = retry_context.criteria_progress - error_message += f"📊 **Progress Summary:**\n" + error_message += f"📊 **Details**\n" if progress.get('met_criteria'): error_message += f"✅ Met criteria: {', '.join(progress['met_criteria'])}\n" if progress.get('unmet_criteria'): @@ -908,19 +787,18 @@ class HandlingTasks: logger.error(f"=== TASK {task_index or '?'} FAILED AFTER ALL RETRIES: {task_step.objective} ===") # Create user-facing error message for task failure - error_message = f"❌ Task {task_index or '?'} - '{task_step.objective}' failed after all retries\n\n" - error_message += f"Objective: {task_step.objective}\n\n" + error_message = f"**Task {task_index or '?'}**\n\n❌ '{task_step.objective}' failed after all retries\n\n" + error_message += f"{task_step.objective}\n\n" # Add specific error details if available if retry_context and hasattr(retry_context, 'previous_review_result') and retry_context.previous_review_result: reason = retry_context.previous_review_result.get('reason', '') if reason and reason != "Task failed after all retries.": - error_message += f"Reason: {reason}\n\n" + error_message += f"{reason}\n\n" # Add retry information error_message += f"Retries attempted: {retry_context.retry_count if retry_context else 'Unknown'}\n" - error_message += f"Status: Task failed permanently\n\n" - error_message += "Please check the connection and try again, or contact support if the issue persists." + error_message += f"Status: Task failed permanently" # Create workflow message for user message_data = { @@ -1170,7 +1048,8 @@ class HandlingTasks: processingTime=createdAction.get("processingTime"), timestamp=float(createdAction.get("timestamp", get_utc_timestamp())), result=createdAction.get("result"), - resultDocuments=createdAction.get("resultDocuments", []) + resultDocuments=createdAction.get("resultDocuments", []), + userMessage=createdAction.get("userMessage") ) except Exception as e: @@ -1241,20 +1120,6 @@ class HandlingTasks: # Log action results logger.info(f"Action completed successfully") - # Create database log entry for action completion - if total_actions is not None: - self.chatInterface.createWorkflowLog({ - "workflowId": workflow.id, - "message": f"✅ Task {task_num} - Action {action_num}/{total_actions} completed", - "type": "success" - }) - else: - self.chatInterface.createWorkflowLog({ - "workflowId": workflow.id, - "message": f"✅ Task {task_num} - Action {action_num}/? completed", - "type": "success" - }) - if created_documents: logger.info(f"Output documents ({len(created_documents)}):") for i, doc in enumerate(created_documents): @@ -1276,19 +1141,12 @@ class HandlingTasks: await self.createActionMessage(action, result, workflow, result_label, [], task_step, task_index) # Create database log entry for action failure - if total_actions is not None: - self.chatInterface.createWorkflowLog({ - "workflowId": workflow.id, - "message": f"❌ Task {task_num} - Action {action_num}/{total_actions} failed: {result.error}", - "type": "error" - }) - else: - self.chatInterface.createWorkflowLog({ - "workflowId": workflow.id, - "message": f"❌ Task {task_num} - Action {action_num}/? failed: {result.error}", - "type": "error" - }) - + self.chatInterface.createWorkflowLog({ + "workflowId": workflow.id, + "message": f"❌ **Task {task_num}**\n\n❌ **Action {action_num}/{total_actions}** failed: {result.error}", + "type": "error" + }) + # Log action summary logger.info(f"=== TASK {task_num} ACTION {action_num} COMPLETED ===") @@ -1336,89 +1194,25 @@ class HandlingTasks: # Create a more meaningful message that includes task context task_objective = task_step.objective if task_step else 'Unknown task' - + + # Add comprehensive workflow context + current_round = workflow_context.get('currentRound', 0) + current_task = workflow_context.get('currentTask', 0) + total_tasks = workflow_stats.get('totalTasks', 0) + current_action = workflow_context.get('currentAction', 0) + total_actions = workflow_stats.get('totalActions', 0) + # Build a user-friendly message based on success/failure if result.success: - if created_documents and len(created_documents) > 0: - doc_names = [doc.fileName for doc in created_documents[:3]] - if len(created_documents) > 3: - doc_names.append(f"... and {len(created_documents) - 3} more") - - # Enhanced message with workflow context - message_text = f"✅ **Task {task_index or '?'} - Action {action.execMethod}.{action.execAction} Completed**\n\n" - message_text += f"**Objective:** {task_objective}\n\n" - message_text += f"**Generated {len(created_documents)} document(s):** {', '.join(doc_names)}\n\n" - message_text += f"**Result Label:** {result_label}\n" - - # Add comprehensive workflow context - current_round = workflow_context.get('currentRound', 0) - current_task = workflow_context.get('currentTask', 0) - total_tasks = workflow_stats.get('totalTasks', 0) - current_action = workflow_context.get('currentAction', 0) - total_actions = workflow_stats.get('totalActions', 0) - - message_text += f"**Workflow Context:**\n" - message_text += f"- Round: {current_round}\n" - if total_tasks > 0: - message_text += f"- Task: {current_task}/{total_tasks}\n" - else: - message_text += f"- Task: {current_task}\n" - if total_actions > 0: - message_text += f"- Action: {current_action}/{total_actions}\n" - else: - message_text += f"- Action: {current_action}\n" - message_text += f"- Status: {workflow_stats.get('workflowStatus', 'unknown')}" - else: - message_text = f"✅ **Task {task_index or '?'} - Action {action.execMethod}.{action.execAction} Completed**\n\n" - message_text += f"**Objective:** {task_objective}\n\n" - message_text += "**Action executed successfully**\n\n" - message_text += f"**Result Label:** {result_label}\n" - - # Add comprehensive workflow context - current_round = workflow_context.get('currentRound', 0) - current_task = workflow_context.get('currentTask', 0) - total_tasks = workflow_stats.get('totalTasks', 0) - current_action = workflow_context.get('currentAction', 0) - total_actions = workflow_stats.get('totalActions', 0) - - message_text += f"**Workflow Context:**\n" - message_text += f"- Round: {current_round}\n" - if total_tasks > 0: - message_text += f"- Task: {current_task}/{total_tasks}\n" - else: - message_text += f"- Task: {current_task}\n" - if total_actions > 0: - message_text += f"- Action: {current_action}/{total_actions}\n" - else: - message_text += f"- Action: {current_action}\n" - message_text += f"- Status: {workflow_stats.get('workflowStats', 'unknown')}" + message_text = f"**Action {current_action}/{total_actions} ({action.execMethod}.{action.execAction})**\n\n" + message_text += f"✅ {task_objective}\n\n" else: # ⚠️ FAILURE MESSAGE - Show error details to user error_details = result.error if result.error else "Unknown error occurred" - message_text = f"❌ **Task {task_index or '?'} - Action {action.execMethod}.{action.execAction} Failed**\n\n" - message_text += f"**Objective:** {task_objective}\n\n" - message_text += f"**Error:** {error_details}\n\n" - message_text += f"**Result Label:** {result_label}\n" - - # Add comprehensive workflow context - current_round = workflow_context.get('currentRound', 0) - current_task = workflow_context.get('currentTask', 0) - total_tasks = workflow_stats.get('totalTasks', 0) - current_action = workflow_context.get('currentAction', 0) - total_actions = workflow_stats.get('totalActions', 0) - - message_text += f"**Workflow Context:**\n" - message_text += f"- Round: {current_round}\n" - if total_tasks > 0: - message_text += f"- Task: {current_task}/{total_tasks}\n" - else: - message_text += f"- Task: {current_task}\n" - if total_actions > 0: - message_text += f"- Action: {current_action}/{total_actions}\n" - message_text += f"- Action: {current_action}\n" - message_text += f"- Status: {workflow_stats.get('workflowStatus', 'unknown')}\n\n" - message_text += "Please check the connection and try again." - + message_text = f"**Action {current_action}/{total_actions} ({action.execMethod}.{action.execAction})**\n\n" + message_text += f"❌ {task_objective}\n\n" + message_text += f"{error_details}\n\n" + message_data = { "workflowId": workflow.id, "role": "assistant", @@ -1432,19 +1226,12 @@ class HandlingTasks: "documentsLabel": result_label, "documents": created_documents, # Add workflow context fields - extract from result_label to match document reference - "roundNumber": workflow_context.get('currentRound', 0), - "taskNumber": task_index, - "actionNumber": self._extractActionNumberFromLabel(result_label) if result_label else workflow_context.get('currentAction', 0), + "roundNumber": current_round, + "taskNumber": current_task, + "actionNumber": current_action, "actionProgress": "success" if result.success else "fail" } - # Add user-friendly message if available - if action.userMessage: - if result.success: - message_data["message"] += f"\n\n💬 {action.userMessage}" - else: - message_data["message"] += f"\n\n💬 Action was intended to: {action.userMessage}" - # Add debugging for error messages if not result.success: logger.info(f"Creating ERROR message: {message_text}") diff --git a/modules/chat/handling/methodOutlook.py b/modules/chat/handling/methodOutlook.py deleted file mode 100644 index b28b04f6..00000000 --- a/modules/chat/handling/methodOutlook.py +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/modules/chat/handling/promptFactory.py b/modules/chat/handling/promptFactory.py index 884606e4..9faa06b3 100644 --- a/modules/chat/handling/promptFactory.py +++ b/modules/chat/handling/promptFactory.py @@ -20,13 +20,13 @@ def createTaskPlanningPrompt(context: TaskContext, service) -> str: user_request = context.task_step.objective if context.task_step else 'No request specified' # Extract available documents from context - use Pydantic model directly - available_documents = context.available_documents or [] + available_documents = context.available_documents or "No documents available" return f"""You are a task planning AI that analyzes user requests and creates structured task plans with user-friendly feedback messages. USER REQUEST: {user_request} -AVAILABLE DOCUMENTS: {', '.join(available_documents)} +AVAILABLE DOCUMENTS: {available_documents} INSTRUCTIONS: 1. Analyze the user request and available documents @@ -34,8 +34,8 @@ INSTRUCTIONS: 3. Focus on business outcomes, not technical operations 4. Each task should produce meaningful, usable outputs 5. Ensure proper handover between tasks using result labels -6. Generate user-friendly messages for each task in the user's language ({user_language}) -7. Detect the language of the user request and include it in languageUserDetected +6. Detect the language of the user request and include it in languageUserDetected +7. Generate user-friendly messages for each task in the user's request language 8. Return a JSON object with the exact structure shown below TASK GROUPING PRINCIPLES: @@ -63,15 +63,15 @@ TASK PLANNING PRINCIPLES: - Keep tasks at a meaningful level of abstraction - Each task should produce results that can be used by subsequent tasks - Ensure clear dependencies and handovers between tasks -- Provide clear, actionable user messages in the user's language ({user_language}) +- Provide clear, actionable user messages in the user's request language - Group related activities to minimize task fragmentation - Only create multiple tasks when dealing with truly different, independent objectives REQUIRED JSON STRUCTURE: {{ "overview": "Brief description of the overall plan", - "userMessage": "User-friendly message explaining the task plan in {user_language}", "languageUserDetected": "en", // Language code detected from user request (en, de, fr, it, es, etc.) + "userMessage": "User-friendly message explaining the task plan in user's request language", "tasks": [ {{ "id": "task_1", @@ -79,7 +79,7 @@ REQUIRED JSON STRUCTURE: "dependencies": ["task_0"], // IDs of tasks that must complete first "success_criteria": ["criteria1", "criteria2"], "estimated_complexity": "low|medium|high", - "userMessage": "User-friendly message explaining what this task will accomplish in {user_language}" + "userMessage": "User-friendly message explaining what this task will accomplish in user's request language" }} ] }} diff --git a/modules/chat/serviceCenter.py b/modules/chat/serviceCenter.py index 9a37030c..cef1555b 100644 --- a/modules/chat/serviceCenter.py +++ b/modules/chat/serviceCenter.py @@ -14,6 +14,7 @@ from modules.interfaces.interfaceChatModel import ActionResult from modules.interfaces.interfaceComponentObjects import getInterface as getComponentObjects from modules.interfaces.interfaceAppObjects import getInterface as getAppObjects from modules.chat.documents.documentExtraction import DocumentExtraction +from modules.chat.documents.documentUtility import getFileExtension, getMimeTypeFromExtension, detectContentTypeFromData from modules.chat.methodBase import MethodBase from modules.shared.timezoneUtils import get_utc_timestamp import uuid @@ -111,165 +112,9 @@ class ServiceCenter: except Exception as e: logger.error(f"Error discovering methods: {str(e)}") - def detectContentTypeFromData(self, fileData: bytes, fileName: str) -> str: - """ - Detect content type from file data and fileName. - This method makes the MIME type detection function accessible through the service center. - - Args: - fileData: Raw file data as bytes - fileName: Name of the file - - Returns: - str: Detected MIME type - """ - try: - # Check file extension first - ext = os.path.splitext(fileName)[1].lower() - if ext: - # Map common extensions to MIME types - extToMime = { - '.txt': 'text/plain', - '.md': 'text/markdown', - '.csv': 'text/csv', - '.json': 'application/json', - '.xml': 'application/xml', - '.js': 'application/javascript', - '.py': 'application/x-python', - '.svg': 'image/svg+xml', - '.jpg': 'image/jpeg', - '.jpeg': 'image/jpeg', - '.png': 'image/png', - '.gif': 'image/gif', - '.bmp': 'image/bmp', - '.webp': 'image/webp', - '.pdf': 'application/pdf', - '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - '.doc': 'application/msword', - '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - '.xls': 'application/vnd.ms-excel', - '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - '.ppt': 'application/vnd.ms-powerpoint', - '.html': 'text/html', - '.htm': 'text/html', - '.css': 'text/css', - '.zip': 'application/zip', - '.rar': 'application/x-rar-compressed', - '.7z': 'application/x-7z-compressed', - '.tar': 'application/x-tar', - '.gz': 'application/gzip' - } - if ext in extToMime: - return extToMime[ext] - - # Try to detect from content - if fileData.startswith(b'%PDF'): - return 'application/pdf' - elif fileData.startswith(b'PK\x03\x04'): - # ZIP-based formats (docx, xlsx, pptx) - return 'application/zip' - elif fileData.startswith(b'<'): - # XML-based formats - try: - text = fileData.decode('utf-8', errors='ignore') - if ' str: - """ - Get MIME type based on file extension. - This method consolidates MIME type detection from extension. - - Args: - extension: File extension (with or without dot) - - Returns: - str: MIME type for the extension - """ - # Normalize extension (remove dot if present) - if extension.startswith('.'): - extension = extension[1:] - - # Map extensions to MIME types - mime_types = { - 'txt': 'text/plain', - 'json': 'application/json', - 'xml': 'application/xml', - 'csv': 'text/csv', - 'html': 'text/html', - 'htm': 'text/html', - 'md': 'text/markdown', - 'py': 'text/x-python', - 'js': 'application/javascript', - 'css': 'text/css', - 'pdf': 'application/pdf', - 'doc': 'application/msword', - 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'xls': 'application/vnd.ms-excel', - 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'ppt': 'application/vnd.ms-powerpoint', - 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'svg': 'image/svg+xml', - 'jpg': 'image/jpeg', - 'jpeg': 'image/jpeg', - 'png': 'image/png', - 'gif': 'image/gif', - 'bmp': 'image/bmp', - 'webp': 'image/webp', - 'zip': 'application/zip', - 'rar': 'application/x-rar-compressed', - '7z': 'application/x-7z-compressed', - 'tar': 'application/x-tar', - 'gz': 'application/gzip' - } - return mime_types.get(extension.lower(), 'application/octet-stream') - def getFileExtension(self, fileName: str) -> str: - """ - Extract file extension from fileName. - - Args: - fileName: Name of the file - - Returns: - str: File extension (without dot) - """ - if '.' in fileName: - return fileName.split('.')[-1].lower() - return "txt" # Default to text - - def getFileExtension(self, fileName): - """ - Extract file extension from fileName (without dot, lowercased). - Returns empty string if no extension is found. - """ - if '.' in fileName: - return fileName.rsplit('.', 1)[-1].lower() - return '' - - # ===== Functions ===== + # ===== Functions for Prompts: Context ===== def getMethodsList(self) -> List[str]: """Get list of available methods with their signatures in the required format""" @@ -283,48 +128,122 @@ class ServiceCenter: methodList.append(signature) return methodList + async def summarizeChat(self, messages: List[ChatMessage]) -> str: + """ + Summarize chat messages from last to first message with status="first" - def generateDocumentLabel(self, document: ChatDocument, message: ChatMessage) -> str: - """Generate new document label: round+task+action+filename.extension""" + Args: + messages: List of chat messages to summarize + + Returns: + str: Summary of the chat in user's language + """ try: - # Get workflow context from message - round_num = message.roundNumber if hasattr(message, 'roundNumber') else 1 - task_num = message.taskNumber if hasattr(message, 'taskNumber') else 0 - action_num = message.actionNumber if hasattr(message, 'actionNumber') else 0 + # Get messages from last to first, stopping at first message with status="first" + relevantMessages = [] + for msg in reversed(messages): + relevantMessages.append(msg) + if msg.status == "first": + break - # Get file extension from document's fileName property - try: - file_extension = self.getFileExtension(document.fileName) - filename = document.fileName - except Exception as e: - # Try to diagnose and recover the issue - diagnosis = self.diagnoseDocumentAccess(document) - logger.error(f"Critical error: Cannot access document fileName for document {document.id}. Diagnosis: {diagnosis}") - - # Attempt recovery - if self.recoverDocumentAccess(document): - try: - file_extension = self.getFileExtension(document.fileName) - filename = document.fileName - logger.info(f"Document access recovered for {document.id}") - except Exception as recovery_error: - logger.error(f"Recovery failed for document {document.id}: {str(recovery_error)}") - raise RuntimeError(f"Document {document.id} is permanently inaccessible after recovery attempt: {str(recovery_error)}") - else: - # Recovery failed - don't continue with invalid data - raise RuntimeError(f"Document {document.id} is inaccessible and recovery failed. Diagnosis: {diagnosis}") + # Create prompt for AI + prompt = f"""You are an AI assistant providing a summary of a chat conversation. +Please respond in '{self.user.language}' language. + +Chat History: +{chr(10).join(f"- {msg.message}" for msg in reversed(relevantMessages))} + +Instructions: +1. Summarize the conversation's key points and outcomes +2. Be concise but informative +3. Use a professional but friendly tone +4. Focus on important decisions and next steps if any + +Please provide a comprehensive summary of this conversation.""" - # Construct label: round1_task2_action3_filename.ext - if file_extension: - label = f"round{round_num}_task{task_num}_action{action_num}_{filename}" - else: - label = f"round{round_num}_task{task_num}_action{action_num}_{filename}" + # Get summary using AI + return await self.callAiTextBasic(prompt) - return label except Exception as e: - logger.error(f"Critical error generating document label for document {document.id}: {str(e)}") - # Re-raise the error to prevent workflow from continuing with invalid data - raise + logger.error(f"Error summarizing chat: {str(e)}") + return f"Error summarizing chat: {str(e)}" + + # ===== Functions for Prompts + Actions: Document References generation and resolution ===== + + def getEnhancedDocumentContext(self) -> str: + """Get enhanced document context formatted for action planning prompts with proper docList and docItem references""" + try: + document_list = self.getDocumentReferenceList() + + # Build technical context string for AI action planning + context = "AVAILABLE DOCUMENTS:\n\n" + + # Process chat exchanges (current round) + if document_list["chat"]: + context += "CURRENT ROUND DOCUMENTS:\n" + for exchange in document_list["chat"]: + # Generate docList reference for the exchange (using message ID and label) + # Find the message that corresponds to this exchange + message_id = None + for message in self.workflow.messages: + if hasattr(message, 'documentsLabel') and message.documentsLabel == exchange.documentsLabel: + message_id = message.id + break + + if message_id: + doc_list_ref = f"docList:{message_id}:{exchange.documentsLabel}" + else: + # Fallback to label-only format if message ID not found + doc_list_ref = f"docList:{exchange.documentsLabel}" + + logger.debug(f"Using document label for action planning: {exchange.documentsLabel} (message_id: {message_id})") + context += f"- {doc_list_ref} contains:\n" + # Generate docItem references for each document in the list + for doc_ref in exchange.documents: + if doc_ref.startswith("docItem:"): + context += f" - {doc_ref}\n" + else: + # Convert to proper docItem format if needed + context += f" - docItem:{doc_ref}\n" + context += "\n" + + # Process history exchanges (previous rounds) + if document_list["history"]: + context += "WORKFLOW HISTORY DOCUMENTS:\n" + for exchange in document_list["history"]: + # Generate docList reference for the exchange (using message ID and label) + # Find the message that corresponds to this exchange + message_id = None + for message in self.workflow.messages: + if hasattr(message, 'documentsLabel') and message.documentsLabel == exchange.documentsLabel: + message_id = message.id + break + + if message_id: + doc_list_ref = f"docList:{message_id}:{exchange.documentsLabel}" + else: + # Fallback to label-only format if message ID not found + doc_list_ref = f"docList:{exchange.documentsLabel}" + + logger.debug(f"Using history document label for action planning: {exchange.documentsLabel} (message_id: {message_id})") + context += f"- {doc_list_ref} contains:\n" + # Generate docItem references for each document in the list + for doc_ref in exchange.documents: + if doc_ref.startswith("docItem:"): + context += f" - {doc_ref}\n" + else: + # Convert to proper docItem format if needed + context += f" - docItem:{doc_ref}\n" + context += "\n" + + if not document_list["chat"] and not document_list["history"]: + context += "NO DOCUMENTS AVAILABLE - This workflow has no documents to process.\n" + + return context + + except Exception as e: + logger.error(f"Error generating enhanced document context: {str(e)}") + return "NO DOCUMENTS AVAILABLE - Error generating document context." def getDocumentReferenceList(self) -> Dict[str, List[DocumentExchange]]: """Get list of document exchanges with new labeling format, sorted by recency""" @@ -336,7 +255,7 @@ class ServiceCenter: # Refresh file attributes for all documents if all_documents: - self.refreshDocumentFileAttributes(all_documents) + self._refreshDocumentFileAttributes(all_documents) chat_exchanges = [] history_exchanges = [] @@ -350,29 +269,30 @@ class ServiceCenter: doc_exchange = None if message.documents: if message.actionId and message.documentsLabel: - # Use new document label format + # Validate that we use the same label as in the message + validated_label = self._validateDocumentLabelConsistency(message) + + # Use the message's actual documentsLabel doc_refs = [] for doc in message.documents: - doc_ref = self.getDocumentReferenceFromChatDocument(doc, message) + doc_ref = self._getDocumentReferenceFromChatDocument(doc, message) doc_refs.append(doc_ref) - doc_exchange = DocumentExchange( - documentsLabel=message.documentsLabel, + doc_exchange = DocumentExchange( + documentsLabel=validated_label, documents=doc_refs - ) + ) else: # Generate new labels for documents without explicit labels doc_refs = [] for doc in message.documents: - doc_ref = self.getDocumentReferenceFromChatDocument(doc, message) + doc_ref = self._getDocumentReferenceFromChatDocument(doc, message) doc_refs.append(doc_ref) if doc_refs: # Create a label based on message context - round_num = message.roundNumber if hasattr(message, 'roundNumber') else 1 - task_num = message.taskNumber if hasattr(message, 'taskNumber') else 0 - action_num = message.actionNumber if hasattr(message, 'actionNumber') else 0 - context_label = f"round{round_num}_task{task_num}_action{action_num}_context" + context_prefix = self._generateWorkflowContextPrefix(message) + context_label = f"{context_prefix}_context" doc_exchange = DocumentExchange( documentsLabel=context_label, @@ -400,7 +320,38 @@ class ServiceCenter: "chat": chat_exchanges, "history": history_exchanges } - + + def _refreshDocumentFileAttributes(self, documents: List[ChatDocument]) -> None: + """Update file attributes (fileName, fileSize, mimeType) for documents""" + for doc in documents: + try: + file_item = self.interfaceComponent.getFile(doc.fileId) + if file_item: + doc.fileName = file_item.fileName + doc.fileSize = file_item.fileSize + doc.mimeType = file_item.mimeType + else: + logger.warning(f"File not found for document {doc.id}, fileId: {doc.fileId}") + except Exception as e: + logger.error(f"Error refreshing file attributes for document {doc.id}: {e}") + + def _generateWorkflowContextPrefix(self, message: ChatMessage) -> str: + """Generate workflow context prefix: round{num}_task{num}_action{num}""" + round_num = message.roundNumber if hasattr(message, 'roundNumber') else 1 + task_num = message.taskNumber if hasattr(message, 'taskNumber') else 0 + action_num = message.actionNumber if hasattr(message, 'actionNumber') else 0 + return f"round{round_num}_task{task_num}_action{action_num}" + + def _getDocumentReferenceFromChatDocument(self, document: ChatDocument, message: ChatMessage) -> str: + """Get document reference using document ID and filename.""" + try: + # Use document ID and filename for simple reference + return f"docItem:{document.id}:{document.fileName}" + except Exception as e: + logger.error(f"Critical error creating document reference for document {document.id}: {str(e)}") + # Re-raise the error to prevent workflow from continuing with invalid data + raise + def _getMessageSequenceForExchange(self, exchange: DocumentExchange) -> int: """Get message sequence number for sorting exchanges by recency""" try: @@ -432,54 +383,15 @@ class ServiceCenter: logger.error(f"Error getting message sequence for exchange: {str(e)}") return 0 - def getEnhancedDocumentContext(self) -> str: - """Get enhanced document context formatted for action planning prompts with proper docList and docItem references""" - try: - document_list = self.getDocumentReferenceList() + def _validateDocumentLabelConsistency(self, message) -> str: + """Validate that the document label used for references matches the message's actual label""" + if not hasattr(message, 'documentsLabel') or not message.documentsLabel: + logger.debug(f"Message {message.id} has no documentsLabel, returning None") + return None - # Build technical context string for AI action planning - context = "AVAILABLE DOCUMENTS:\n\n" - - # Process chat exchanges (current round) - if document_list["chat"]: - context += "CURRENT ROUND DOCUMENTS:\n" - for exchange in document_list["chat"]: - # Generate docList reference for the exchange (using message ID) - doc_list_ref = f"docList:{exchange.documentsLabel}" - context += f"- {doc_list_ref} contains:\n" - # Generate docItem references for each document in the list - for doc_ref in exchange.documents: - if doc_ref.startswith("docItem:"): - context += f" - {doc_ref}\n" - else: - # Convert to proper docItem format if needed - context += f" - docItem:{doc_ref}\n" - context += "\n" - - # Process history exchanges (previous rounds) - if document_list["history"]: - context += "WORKFLOW HISTORY DOCUMENTS:\n" - for exchange in document_list["history"]: - # Generate docList reference for the exchange (using message ID) - doc_list_ref = f"docList:{exchange.documentsLabel}" - context += f"- {doc_list_ref} contains:\n" - # Generate docItem references for each document in the list - for doc_ref in exchange.documents: - if doc_ref.startswith("docItem:"): - context += f" - {doc_ref}\n" - else: - # Convert to proper docItem format if needed - context += f" - docItem:{doc_ref}\n" - context += "\n" - - if not document_list["chat"] and not document_list["history"]: - context += "NO DOCUMENTS AVAILABLE - This workflow has no documents to process.\n" - - return context - - except Exception as e: - logger.error(f"Error generating enhanced document context: {str(e)}") - return "NO DOCUMENTS AVAILABLE - Error generating document context." + # Simply return the message's actual documentsLabel - no correction, just validation + logger.debug(f"Using message's documentsLabel for references: '{message.documentsLabel}'") + return message.documentsLabel def _extractDocumentInfoFromReference(self, doc_ref: str) -> Dict[str, str]: """Extract document information from reference string""" @@ -533,27 +445,6 @@ class ServiceCenter: logger.error(f"Error extracting document info from reference: {str(e)}") return None - def getDocumentReferenceFromChatDocument(self, document: ChatDocument, message: ChatMessage) -> str: - """Get document reference using document ID and filename.""" - try: - # Use document ID and filename for simple reference - return f"docItem:{document.id}:{document.fileName}" - except Exception as e: - logger.error(f"Critical error creating document reference for document {document.id}: {str(e)}") - # Re-raise the error to prevent workflow from continuing with invalid data - raise - - def getDocumentListReferenceFromChatMessage(self, message: ChatMessage) -> str: - """Get document list reference using message ID and label.""" - try: - # Use message ID and documentsLabel for document list reference - label = getattr(message, 'documentsLabel', f"message_{message.id}") - return f"docList:{message.id}:{label}" - except Exception as e: - logger.error(f"Critical error creating document list reference for message {message.id}: {str(e)}") - # Re-raise the error to prevent workflow from continuing with invalid data - raise - def getChatDocumentsFromDocumentList(self, documentList: List[str]) -> List[ChatDocument]: """Get ChatDocuments from a list of document references using all three formats.""" try: @@ -569,19 +460,56 @@ class ServiceCenter: if message.documents: for doc in message.documents: if doc.id == doc_id: + doc_name = getattr(doc, 'fileName', 'unknown') + logger.debug(f"Found docItem reference {doc_ref}: {doc_name}") all_documents.append(doc) break elif doc_ref.startswith("docList:"): - # docList::