messageing aligned

This commit is contained in:
ValueOn AG 2025-09-02 11:47:39 +02:00
parent 0e034e253d
commit 91aff56e1c
7 changed files with 177 additions and 296 deletions

View file

@ -192,7 +192,8 @@ class HandlingTasks:
task_plan = TaskPlan( task_plan = TaskPlan(
overview=task_plan_dict.get('overview', ''), overview=task_plan_dict.get('overview', ''),
tasks=tasks tasks=tasks,
userMessage=task_plan_dict.get('userMessage', '')
) )
# Set workflow totals for progress tracking # Set workflow totals for progress tracking
@ -217,24 +218,19 @@ class HandlingTasks:
"""Create a chat message containing the task plan with user-friendly messages""" """Create a chat message containing the task plan with user-friendly messages"""
try: try:
# Build task plan summary # Build task plan summary
task_summary = f"📋 **Task Plan Generated**\n\n" task_summary = f"📋 **Task Plan**\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"
# Get overall user message from task plan if available # Get overall user message from task plan if available
overall_message = task_plan.userMessage overall_message = task_plan.userMessage
if overall_message: 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 # Create workflow message
message_data = { message_data = {
@ -269,76 +265,6 @@ class HandlingTasks:
except Exception as e: except Exception as e:
logger.error(f"Error creating task plan message: {str(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]: async def generateTaskActions(self, task_step, workflow, previous_results=None, enhanced_context=None) -> List[TaskAction]:
"""Generate actions for a given task step.""" """Generate actions for a given task step."""
try: try:
@ -546,25 +472,13 @@ class HandlingTasks:
# Create database log entry for task start in format expected by frontend # Create database log entry for task start in format expected by frontend
if task_index is not None: 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 # 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_progress = f"{task_index}/{total_tasks}" if total_tasks is not None else str(task_index)
task_start_message = { task_start_message = {
"workflowId": workflow.id, "workflowId": workflow.id,
"role": "assistant", "role": "assistant",
"message": f"🚀 Starting Task {task_progress}\n\nObjective: {task_step.objective}", "message": f"🚀 **Task {task_progress}**",
"status": "step", "status": "step",
"sequenceNr": len(workflow.messages) + 1, "sequenceNr": len(workflow.messages) + 1,
"publishedAt": get_utc_timestamp(), "publishedAt": get_utc_timestamp(),
@ -617,11 +531,6 @@ class HandlingTasks:
logger.error("No actions defined for task step, aborting task execution") logger.error("No actions defined for task step, aborting task execution")
break 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 = [] action_results = []
for action_idx, action in enumerate(actions): for action_idx, action in enumerate(actions):
# Check workflow status before each action execution # Check workflow status before each action execution
@ -639,18 +548,11 @@ class HandlingTasks:
# Log action start in format expected by frontend # Log action start in format expected by frontend
logger.info(f"Task {task_index} - Starting action {action_number}/{total_actions}") 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 # Create an action start message for the user
action_start_message = { action_start_message = {
"workflowId": workflow.id, "workflowId": workflow.id,
"role": "assistant", "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", "status": "step",
"sequenceNr": len(workflow.messages) + 1, "sequenceNr": len(workflow.messages) + 1,
"publishedAt": get_utc_timestamp(), "publishedAt": get_utc_timestamp(),
@ -694,34 +596,19 @@ class HandlingTasks:
if success: if success:
logger.info(f"=== TASK {task_index or '?'} COMPLETED SUCCESSFULLY: {task_step.objective} ===") 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 # 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) task_progress = f"{task_index}/{total_tasks}" if total_tasks is not None else str(task_index)
# Enhanced completion message with criteria details # 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 # Add criteria status if available
if hasattr(review_result, 'met_criteria') and review_result.met_criteria: 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: for criterion in review_result.met_criteria:
completion_message += f"{criterion}\n" completion_message += f"\n{criterion}\n"
if hasattr(review_result, 'quality_score'): 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 = { task_completion_message = {
"workflowId": workflow.id, "workflowId": workflow.id,
@ -740,10 +627,6 @@ class HandlingTasks:
"taskProgress": "success" "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) message = self.chatInterface.createWorkflowMessage(task_completion_message)
if message: if message:
workflow.messages.append(message) workflow.messages.append(message)
@ -824,7 +707,7 @@ class HandlingTasks:
retry_message = { retry_message = {
"workflowId": workflow.id, "workflowId": workflow.id,
"role": "assistant", "role": "assistant",
"message": f"🔄 Task {task_index} requires retry: {review_result.improvements}", "message": f"🔄 **Task {task_index}** needs retry: {review_result.improvements}",
"status": "step", "status": "step",
"sequenceNr": len(workflow.messages) + 1, "sequenceNr": len(workflow.messages) + 1,
"publishedAt": get_utc_timestamp(), "publishedAt": get_utc_timestamp(),
@ -843,19 +726,19 @@ class HandlingTasks:
continue continue
else: else:
logger.error(f"=== TASK {task_index or '?'} FAILED: {task_step.objective} after {attempt+1} attempts ===") 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 # 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"**Task {task_progress}**\n\n'{task_step.objective}' {attempt+1}x failed\n\n"
error_message += f"Objective: {task_step.objective}\n\n"
# Add specific error details if available # Add specific error details if available
if review_result and hasattr(review_result, 'reason') and review_result.reason: 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 # Add criteria progress information if available
if retry_context and hasattr(retry_context, 'criteria_progress'): if retry_context and hasattr(retry_context, 'criteria_progress'):
progress = 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'): if progress.get('met_criteria'):
error_message += f"✅ Met criteria: {', '.join(progress['met_criteria'])}\n" error_message += f"✅ Met criteria: {', '.join(progress['met_criteria'])}\n"
if progress.get('unmet_criteria'): 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} ===") logger.error(f"=== TASK {task_index or '?'} FAILED AFTER ALL RETRIES: {task_step.objective} ===")
# Create user-facing error message for task failure # 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"**Task {task_index or '?'}**\n\n '{task_step.objective}' failed after all retries\n\n"
error_message += f"Objective: {task_step.objective}\n\n" error_message += f"{task_step.objective}\n\n"
# Add specific error details if available # Add specific error details if available
if retry_context and hasattr(retry_context, 'previous_review_result') and retry_context.previous_review_result: if retry_context and hasattr(retry_context, 'previous_review_result') and retry_context.previous_review_result:
reason = retry_context.previous_review_result.get('reason', '') reason = retry_context.previous_review_result.get('reason', '')
if reason and reason != "Task failed after all retries.": 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 # Add retry information
error_message += f"Retries attempted: {retry_context.retry_count if retry_context else 'Unknown'}\n" 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 += f"Status: Task failed permanently"
error_message += "Please check the connection and try again, or contact support if the issue persists."
# Create workflow message for user # Create workflow message for user
message_data = { message_data = {
@ -1170,7 +1052,8 @@ class HandlingTasks:
processingTime=createdAction.get("processingTime"), processingTime=createdAction.get("processingTime"),
timestamp=float(createdAction.get("timestamp", get_utc_timestamp())), timestamp=float(createdAction.get("timestamp", get_utc_timestamp())),
result=createdAction.get("result"), result=createdAction.get("result"),
resultDocuments=createdAction.get("resultDocuments", []) resultDocuments=createdAction.get("resultDocuments", []),
userMessage=createdAction.get("userMessage")
) )
except Exception as e: except Exception as e:
@ -1241,20 +1124,6 @@ class HandlingTasks:
# Log action results # Log action results
logger.info(f"Action completed successfully") 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: if created_documents:
logger.info(f"Output documents ({len(created_documents)}):") logger.info(f"Output documents ({len(created_documents)}):")
for i, doc in enumerate(created_documents): for i, doc in enumerate(created_documents):
@ -1276,18 +1145,11 @@ class HandlingTasks:
await self.createActionMessage(action, result, workflow, result_label, [], task_step, task_index) await self.createActionMessage(action, result, workflow, result_label, [], task_step, task_index)
# Create database log entry for action failure # Create database log entry for action failure
if total_actions is not None: self.chatInterface.createWorkflowLog({
self.chatInterface.createWorkflowLog({ "workflowId": workflow.id,
"workflowId": workflow.id, "message": f"❌ **Task {task_num}**\n\n❌ **Action {action_num}/{total_actions}** failed: {result.error}",
"message": f"❌ Task {task_num} - Action {action_num}/{total_actions} failed: {result.error}", "type": "error"
"type": "error" })
})
else:
self.chatInterface.createWorkflowLog({
"workflowId": workflow.id,
"message": f"❌ Task {task_num} - Action {action_num}/? failed: {result.error}",
"type": "error"
})
# Log action summary # Log action summary
logger.info(f"=== TASK {task_num} ACTION {action_num} COMPLETED ===") logger.info(f"=== TASK {task_num} ACTION {action_num} COMPLETED ===")
@ -1337,87 +1199,23 @@ class HandlingTasks:
# Create a more meaningful message that includes task context # Create a more meaningful message that includes task context
task_objective = task_step.objective if task_step else 'Unknown task' 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 # Build a user-friendly message based on success/failure
if result.success: if result.success:
if created_documents and len(created_documents) > 0: message_text = f"**Action {current_action}/{total_actions} ({action.execMethod}.{action.execAction})**\n\n"
doc_names = [doc.fileName for doc in created_documents[:3]] message_text += f"{task_objective}\n\n"
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')}"
else: else:
# ⚠️ FAILURE MESSAGE - Show error details to user # ⚠️ FAILURE MESSAGE - Show error details to user
error_details = result.error if result.error else "Unknown error occurred" 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"**Action {current_action}/{total_actions} ({action.execMethod}.{action.execAction})**\n\n"
message_text += f"**Objective:** {task_objective}\n\n" message_text += f"{task_objective}\n\n"
message_text += f"**Error:** {error_details}\n\n" message_text += f"{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_data = { message_data = {
"workflowId": workflow.id, "workflowId": workflow.id,
@ -1432,19 +1230,12 @@ class HandlingTasks:
"documentsLabel": result_label, "documentsLabel": result_label,
"documents": created_documents, "documents": created_documents,
# Add workflow context fields - extract from result_label to match document reference # Add workflow context fields - extract from result_label to match document reference
"roundNumber": workflow_context.get('currentRound', 0), "roundNumber": current_round,
"taskNumber": task_index, "taskNumber": current_task,
"actionNumber": self._extractActionNumberFromLabel(result_label) if result_label else workflow_context.get('currentAction', 0), "actionNumber": current_action,
"actionProgress": "success" if result.success else "fail" "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 # Add debugging for error messages
if not result.success: if not result.success:
logger.info(f"Creating ERROR message: {message_text}") logger.info(f"Creating ERROR message: {message_text}")

View file

@ -34,8 +34,8 @@ INSTRUCTIONS:
3. Focus on business outcomes, not technical operations 3. Focus on business outcomes, not technical operations
4. Each task should produce meaningful, usable outputs 4. Each task should produce meaningful, usable outputs
5. Ensure proper handover between tasks using result labels 5. Ensure proper handover between tasks using result labels
6. Generate user-friendly messages for each task in the user's language ({user_language}) 6. Detect the language of the user request and include it in languageUserDetected
7. 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 8. Return a JSON object with the exact structure shown below
TASK GROUPING PRINCIPLES: TASK GROUPING PRINCIPLES:
@ -63,15 +63,15 @@ TASK PLANNING PRINCIPLES:
- Keep tasks at a meaningful level of abstraction - Keep tasks at a meaningful level of abstraction
- Each task should produce results that can be used by subsequent tasks - Each task should produce results that can be used by subsequent tasks
- Ensure clear dependencies and handovers between 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 - Group related activities to minimize task fragmentation
- Only create multiple tasks when dealing with truly different, independent objectives - Only create multiple tasks when dealing with truly different, independent objectives
REQUIRED JSON STRUCTURE: REQUIRED JSON STRUCTURE:
{{ {{
"overview": "Brief description of the overall plan", "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.) "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": [ "tasks": [
{{ {{
"id": "task_1", "id": "task_1",
@ -79,7 +79,7 @@ REQUIRED JSON STRUCTURE:
"dependencies": ["task_0"], // IDs of tasks that must complete first "dependencies": ["task_0"], // IDs of tasks that must complete first
"success_criteria": ["criteria1", "criteria2"], "success_criteria": ["criteria1", "criteria2"],
"estimated_complexity": "low|medium|high", "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"
}} }}
] ]
}} }}

View file

@ -350,16 +350,19 @@ class ServiceCenter:
doc_exchange = None doc_exchange = None
if message.documents: if message.documents:
if message.actionId and message.documentsLabel: 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 = [] doc_refs = []
for doc in message.documents: for doc in message.documents:
doc_ref = self.getDocumentReferenceFromChatDocument(doc, message) doc_ref = self.getDocumentReferenceFromChatDocument(doc, message)
doc_refs.append(doc_ref) doc_refs.append(doc_ref)
doc_exchange = DocumentExchange( doc_exchange = DocumentExchange(
documentsLabel=message.documentsLabel, documentsLabel=validated_label,
documents=doc_refs documents=doc_refs
) )
else: else:
# Generate new labels for documents without explicit labels # Generate new labels for documents without explicit labels
doc_refs = [] doc_refs = []
@ -444,8 +447,21 @@ class ServiceCenter:
if document_list["chat"]: if document_list["chat"]:
context += "CURRENT ROUND DOCUMENTS:\n" context += "CURRENT ROUND DOCUMENTS:\n"
for exchange in document_list["chat"]: for exchange in document_list["chat"]:
# Generate docList reference for the exchange (using message ID) # Generate docList reference for the exchange (using message ID and label)
doc_list_ref = f"docList:{exchange.documentsLabel}" # 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" context += f"- {doc_list_ref} contains:\n"
# Generate docItem references for each document in the list # Generate docItem references for each document in the list
for doc_ref in exchange.documents: for doc_ref in exchange.documents:
@ -460,8 +476,21 @@ class ServiceCenter:
if document_list["history"]: if document_list["history"]:
context += "WORKFLOW HISTORY DOCUMENTS:\n" context += "WORKFLOW HISTORY DOCUMENTS:\n"
for exchange in document_list["history"]: for exchange in document_list["history"]:
# Generate docList reference for the exchange (using message ID) # Generate docList reference for the exchange (using message ID and label)
doc_list_ref = f"docList:{exchange.documentsLabel}" # 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" context += f"- {doc_list_ref} contains:\n"
# Generate docItem references for each document in the list # Generate docItem references for each document in the list
for doc_ref in exchange.documents: for doc_ref in exchange.documents:
@ -481,6 +510,16 @@ class ServiceCenter:
logger.error(f"Error generating enhanced document context: {str(e)}") logger.error(f"Error generating enhanced document context: {str(e)}")
return "NO DOCUMENTS AVAILABLE - Error generating document context." 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]: def _extractDocumentInfoFromReference(self, doc_ref: str) -> Dict[str, str]:
"""Extract document information from reference string""" """Extract document information from reference string"""
try: try:
@ -569,19 +608,56 @@ class ServiceCenter:
if message.documents: if message.documents:
for doc in message.documents: for doc in message.documents:
if doc.id == doc_id: 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) all_documents.append(doc)
break break
elif doc_ref.startswith("docList:"): elif doc_ref.startswith("docList:"):
# docList:<messageId>:<label> - extract message ID and find document list # docList:<messageId>:<label> or docList:<label> - extract message ID and find document list
parts = doc_ref.split(':') parts = doc_ref.split(':')
if len(parts) >= 2: if len(parts) >= 3:
# Format: docList:<messageId>:<label>
message_id = parts[1] message_id = parts[1]
label = parts[2]
# Find the message by ID and get all its documents # Find the message by ID and get all its documents
for message in self.workflow.messages: for message in self.workflow.messages:
if str(message.id) == message_id: if str(message.id) == message_id:
if message.documents: if message.documents:
doc_names = [doc.fileName for doc in message.documents if hasattr(doc, 'fileName')]
logger.debug(f"Found docList reference {doc_ref}: {len(message.documents)} documents - {doc_names}")
all_documents.extend(message.documents) all_documents.extend(message.documents)
else:
logger.debug(f"Found docList reference {doc_ref} but message has no documents")
break break
elif len(parts) >= 2:
# Format: docList:<label> - find message by documentsLabel
label = parts[1]
logger.debug(f"Looking for message with documentsLabel: {label}")
# Find messages with matching documentsLabel
matching_messages = []
for message in self.workflow.messages:
# Check both attribute and raw data for documentsLabel
msg_label = getattr(message, 'documentsLabel', None)
if msg_label == label:
matching_messages.append(message)
logger.debug(f"Found message {message.id} with matching documentsLabel: {msg_label}")
else:
# Debug: show what labels we're comparing
logger.debug(f"Message {message.id} has documentsLabel: '{msg_label}' (looking for: '{label}')")
if matching_messages:
# Use the newest message (highest publishedAt)
matching_messages.sort(key=lambda msg: getattr(msg, 'publishedAt', 0), reverse=True)
newest_message = matching_messages[0]
if newest_message.documents:
doc_names = [doc.fileName for doc in newest_message.documents if hasattr(doc, 'fileName')]
logger.debug(f"Found docList reference {doc_ref}: {len(newest_message.documents)} documents - {doc_names}")
all_documents.extend(newest_message.documents)
else:
logger.debug(f"Found docList reference {doc_ref} but message has no documents")
else:
logger.debug(f"No messages found with documentsLabel: {label}")
else: else:
# Direct label reference (round1_task2_action3_contextinfo) # Direct label reference (round1_task2_action3_contextinfo)
# Search for messages with matching documentsLabel to find the actual documents # Search for messages with matching documentsLabel to find the actual documents
@ -619,8 +695,9 @@ class ServiceCenter:
logger.debug(f"Newest message has {len(newest_message.documents) if newest_message.documents else 0} documents") logger.debug(f"Newest message has {len(newest_message.documents) if newest_message.documents else 0} documents")
if newest_message.documents: if newest_message.documents:
doc_names = [doc.fileName for doc in newest_message.documents if hasattr(doc, 'fileName')]
logger.debug(f"Added {len(newest_message.documents)} documents from newest message {newest_message.id}: {doc_names}")
all_documents.extend(newest_message.documents) all_documents.extend(newest_message.documents)
logger.debug(f"Added {len(newest_message.documents)} documents from newest message {newest_message.id}")
else: else:
logger.debug(f"No documents found in newest message {newest_message.id}") logger.debug(f"No documents found in newest message {newest_message.id}")
else: else:
@ -641,8 +718,9 @@ class ServiceCenter:
logger.debug(f"Using fallback message {newest_fallback.id} with documentsLabel: {getattr(newest_fallback, 'documentsLabel', 'unknown')}") logger.debug(f"Using fallback message {newest_fallback.id} with documentsLabel: {getattr(newest_fallback, 'documentsLabel', 'unknown')}")
if newest_fallback.documents: if newest_fallback.documents:
doc_names = [doc.fileName for doc in newest_fallback.documents if hasattr(doc, 'fileName')]
logger.debug(f"Added {len(newest_fallback.documents)} documents from fallback message {newest_fallback.id}: {doc_names}")
all_documents.extend(newest_fallback.documents) all_documents.extend(newest_fallback.documents)
logger.debug(f"Added {len(newest_fallback.documents)} documents from fallback message {newest_fallback.id}")
else: else:
logger.debug(f"No documents found in fallback message {newest_fallback.id}") logger.debug(f"No documents found in fallback message {newest_fallback.id}")
else: else:

View file

@ -441,6 +441,10 @@ class MethodDocument(MethodBase):
if len(lines) > 2: if len(lines) > 2:
formatted_content = '\n'.join(lines[1:-1]) formatted_content = '\n'.join(lines[1:-1])
# For HTML format, check if AI returned complete HTML document
if extension == ".html" and (formatted_content.startswith('<!DOCTYPE') or formatted_content.startswith('<html')):
return formatted_content
return formatted_content return formatted_content
except Exception as e: except Exception as e:
@ -643,7 +647,22 @@ class MethodDocument(MethodBase):
raise Exception("AI report generation failed - AI is required for report generation") raise Exception("AI report generation failed - AI is required for report generation")
# Clean up the AI response and ensure it's valid HTML # Clean up the AI response and ensure it's valid HTML
if not aiReport.strip().startswith('<html'): aiReport = aiReport.strip()
# Strip fenced code blocks like ```html ... ``` if present
if aiReport.startswith("```") and aiReport.endswith("```"):
lines = aiReport.split('\n')
if len(lines) >= 2:
# remove first and last fence lines (language tag allowed on first)
aiReport = '\n'.join(lines[1:-1]).strip()
# Check if AI response starts with DOCTYPE or html tag (complete HTML document)
if aiReport.startswith('<!DOCTYPE') or aiReport.startswith('<html'):
# AI returned complete HTML document, use it directly
return aiReport
else:
# AI returned HTML content without document structure, wrap it
# Check if AI response already contains a title/header # Check if AI response already contains a title/header
has_title = any(title.lower() in aiReport.lower() for title in [title, "outlook", "report", "status"]) has_title = any(title.lower() in aiReport.lower() for title in [title, "outlook", "report", "status"])
@ -660,9 +679,6 @@ class MethodDocument(MethodBase):
html.append(aiReport) html.append(aiReport)
html.append("</body></html>") html.append("</body></html>")
return '\n'.join(html) return '\n'.join(html)
else:
# AI returned complete HTML, use it directly
return aiReport
except Exception as e: except Exception as e:
logger.error(f"Error generating AI report: {str(e)}") logger.error(f"Error generating AI report: {str(e)}")

View file

@ -270,15 +270,6 @@ async def get_workflow_messages(
# Get all messages # Get all messages
allMessages = interfaceChat.getWorkflowMessages(workflowId) allMessages = interfaceChat.getWorkflowMessages(workflowId)
# Debug logging: Log attributes for each message
logger.debug(f"Retrieved {len(allMessages)} messages for workflow {workflowId}")
for i, message in enumerate(allMessages):
logger.debug(f"Message {i+1} (ID: {message.id}): {message}")
logger.debug(f" - Type: {getattr(message, 'type', 'N/A')}")
logger.debug(f" - Content: {getattr(message, 'content', 'N/A')[:100]}...")
logger.debug(f" - PublishedAt: {getattr(message, 'publishedAt', 'N/A')}")
logger.debug(f" - All attributes: {message.__dict__}")
# Apply selective data transfer if messageId is provided # Apply selective data transfer if messageId is provided
if messageId: if messageId:
# Find the index of the message with the given ID # Find the index of the message with the given ID

View file

@ -141,6 +141,12 @@ class WorkflowManager:
self.chatManager.handlingTasks._checkWorkflowStopped() self.chatManager.handlingTasks._checkWorkflowStopped()
# Create initial message using interface # Create initial message using interface
# Generate the correct documentsLabel that matches what getDocumentReferenceList() will create
round_num = workflow.currentRound
task_num = 0
action_num = 0
context_label = f"round{round_num}_task{task_num}_action{action_num}_context"
messageData = { messageData = {
"workflowId": workflow.id, "workflowId": workflow.id,
"role": "user", "role": "user",
@ -148,7 +154,7 @@ class WorkflowManager:
"status": "first", "status": "first",
"sequenceNr": 1, "sequenceNr": 1,
"publishedAt": get_utc_timestamp(), "publishedAt": get_utc_timestamp(),
"documentsLabel": "workflow_start", "documentsLabel": context_label,
"documents": [], "documents": [],
# Add workflow context fields # Add workflow context fields
"roundNumber": workflow.currentRound, "roundNumber": workflow.currentRound,
@ -390,7 +396,7 @@ class WorkflowManager:
summary_message = { summary_message = {
"workflowId": workflow.id, "workflowId": workflow.id,
"role": "assistant", "role": "assistant",
"message": f"Workflow completed successfully. Completed {workflow_result.completed_tasks}/{workflow_result.total_tasks} tasks in {workflow_result.execution_time:.2f} seconds.", "message": f"Workflow completed successfully.",
"status": "last", "status": "last",
"sequenceNr": len(workflow.messages) + 1, "sequenceNr": len(workflow.messages) + 1,
"publishedAt": get_utc_timestamp(), "publishedAt": get_utc_timestamp(),

View file

@ -2,8 +2,7 @@
TODO TODO
# System # System
- Backend/UI fix Table Connections mit korrekten Token Infos, View jedesmal neu laden im formGeneric - chat workflow: messages to user in user language
- model reference diagram for all models. who uses who? --> to see the basic building blocks
- neutralizer to activate AND put back placeholders to the returned data - neutralizer to activate AND put back placeholders to the returned data
# Tests # Tests