diff --git a/modules/chat/handling/handlingTasks.py b/modules/chat/handling/handlingTasks.py index 290fdf0a..6138932b 100644 --- a/modules/chat/handling/handlingTasks.py +++ b/modules/chat/handling/handlingTasks.py @@ -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: @@ -546,25 +472,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 +531,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 +548,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 +596,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}\n" 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 +627,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 +707,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 +726,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 +791,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 +1052,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 +1124,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 +1145,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 +1198,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 +1230,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/promptFactory.py b/modules/chat/handling/promptFactory.py index 884606e4..4587cce4 100644 --- a/modules/chat/handling/promptFactory.py +++ b/modules/chat/handling/promptFactory.py @@ -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..13545001 100644 --- a/modules/chat/serviceCenter.py +++ b/modules/chat/serviceCenter.py @@ -350,16 +350,19 @@ 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_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 = [] @@ -444,8 +447,21 @@ class ServiceCenter: 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}" + # 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: @@ -460,8 +476,21 @@ class ServiceCenter: 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}" + # 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: @@ -481,6 +510,16 @@ class ServiceCenter: logger.error(f"Error generating enhanced document context: {str(e)}") return "NO DOCUMENTS AVAILABLE - Error generating document context." + 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 + + # 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""" try: @@ -569,19 +608,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::