gateway/modules/workflow/managerChat.py
2025-07-10 23:58:27 +02:00

2328 lines
108 KiB
Python

import asyncio
import logging
import uuid
import json
import time
from typing import Dict, Any, Optional, List, Union
from datetime import datetime, UTC
from modules.interfaces.interfaceAppModel import User
from modules.interfaces.interfaceChatModel import (
TaskStatus, ChatDocument, TaskItem, TaskAction, TaskResult, ChatStat, ChatLog, ChatMessage, ChatWorkflow, UserInputRequest, ActionResult,
ExtractedContent, ContentItem, ContentMetadata, DocumentExchange
)
from modules.workflow.serviceCenter import ServiceCenter
from modules.interfaces.interfaceChatObjects import ChatObjects
logger = logging.getLogger(__name__)
# ===== STATE MANAGEMENT AND VALIDATION CLASSES =====
class TaskExecutionState:
"""Manages state during task execution with retry logic"""
def __init__(self, task_step: dict):
self.task_step = task_step
self.successful_actions = [] # Preserved across retries
self.failed_actions = [] # For analysis
self.current_action_index = 0
self.retry_count = 0
self.improvements = []
self.partial_results = {} # Store intermediate results
self.max_retries = 3
def addSuccessfulAction(self, action_result: dict):
self.successful_actions.append(action_result)
if action_result.get('resultLabel'):
self.partial_results[action_result['resultLabel']] = action_result
def addFailedAction(self, action_result: dict):
self.failed_actions.append(action_result)
def getAvailableResults(self) -> list:
return [result.get('resultLabel', '') for result in self.successful_actions if result.get('resultLabel')]
def shouldRetryTask(self) -> bool:
return len(self.successful_actions) > 0 and len(self.failed_actions) > 0
def canRetry(self) -> bool:
return self.retry_count < self.max_retries
def incrementRetryCount(self):
self.retry_count += 1
def getFailurePatterns(self) -> list:
patterns = []
for action in self.failed_actions:
error = action.get('error', '').lower()
if "timeout" in error:
patterns.append("timeout_issues")
elif "document_not_found" in error or "file not found" in error:
patterns.append("document_reference_issues")
elif "empty_result" in error or "no content" in error:
patterns.append("content_extraction_issues")
elif "invalid_format" in error or "wrong format" in error:
patterns.append("format_issues")
elif "permission" in error or "access denied" in error:
patterns.append("permission_issues")
return list(set(patterns))
class ActionValidator:
"""Generic AI-based action result validation"""
def __init__(self, chat_manager):
self.chat_manager = chat_manager
async def validateActionResult(self, action_result: ActionResult, action: TaskAction, context: dict) -> dict:
"""Generic action validation using AI"""
try:
# Create generic validation prompt
prompt = self._createGenericValidationPrompt(action_result, action, context)
response = await self.chat_manager._callAIWithCircuitBreaker(prompt, "action_validation")
validation = self._parseValidationResponse(response)
# Add action metadata
validation['action_id'] = action.id
validation['action_method'] = action.execMethod
validation['action_name'] = action.execAction
validation['result_label'] = action.execResultLabel
return validation
except Exception as e:
logger.error(f"Error validating action result: {str(e)}")
return {
'status': 'success',
'reason': f'Validation failed: {str(e)}',
'confidence': 0.5,
'improvements': [],
'action_id': action.id,
'action_method': action.execMethod,
'action_name': action.execAction,
'result_label': action.execResultLabel
}
def _createGenericValidationPrompt(self, action_result: ActionResult, action: TaskAction, context: dict) -> str:
"""Create a validation prompt focused on result file delivery"""
# Extract data from ActionResult model
success = action_result.success
result_data = action_result.data
error = action_result.error
validation_messages = action_result.validation
# Extract result text from data
result_text = result_data.get("result", "") if isinstance(result_data, dict) else str(result_data)
# Get documents from ActionResult data
documents = result_data.get("documents", []) if isinstance(result_data, dict) else []
doc_count = len(documents)
# Extract expected result format from action parameters
expected_result_label = action.execResultLabel
expected_format = action.execParameters.get('outputFormat', 'unknown')
# Analyze delivered documents and content
delivered_files = []
content_items = []
# Check for ChatDocument objects
for doc in documents:
if hasattr(doc, 'filename'):
delivered_files.append(doc.filename)
elif isinstance(doc, dict) and 'filename' in doc:
delivered_files.append(doc['filename'])
else:
delivered_files.append(f"document_{len(delivered_files)}")
# Check for ExtractedContent in result data
if isinstance(result_data, dict):
if 'extractedContent' in result_data:
extracted_content = result_data['extractedContent']
if hasattr(extracted_content, 'contents'):
content_items = extracted_content.contents
elif 'contents' in result_data:
content_items = result_data['contents']
# Analyze content items
content_summary = []
for item in content_items:
if hasattr(item, 'label') and hasattr(item, 'metadata'):
content_summary.append(f"{item.label}: {item.metadata.mimeType if hasattr(item.metadata, 'mimeType') else 'unknown'}")
return f"""You are an action result validator. Your primary focus is to validate that the action delivered the promised result files in the promised format.
ACTION DETAILS:
- Method: {action.execMethod}
- Action: {action.execAction}
- Expected Result Label: {expected_result_label}
- Expected Format: {expected_format}
- Parameters: {json.dumps(action.execParameters, indent=2)}
RESULT TO VALIDATE:
- Success: {success}
- Result Data: {result_text[:500]}{'...' if len(result_text) > 500 else ''}
- Error: {error}
- Validation Messages: {', '.join(validation_messages) if validation_messages else 'None'}
- Documents Produced: {doc_count}
- Delivered Files: {', '.join(delivered_files) if delivered_files else 'None'}
- Content Items: {', '.join(content_summary) if content_summary else 'None'}
CRITICAL VALIDATION CRITERIA:
1. **File Delivery**: Did the action deliver the promised result file(s)?
2. **Format Compliance**: Are the delivered files in the promised format?
3. **Result Label Match**: Does the result match the expected result label?
4. **Content Quality**: Is the content of the delivered files usable and complete?
5. **Content Processing**: If content extraction was expected, was it performed correctly?
CONTEXT:
- Task Description: {context.get('task_description', 'Unknown')}
- Previous Results: {', '.join(context.get('previous_results', []))}
VALIDATION INSTRUCTIONS:
1. Check if the expected result label "{expected_result_label}" is present in the result
2. Verify that files were delivered when expected
3. Validate that the delivered files match the expected format "{expected_format}"
4. Assess if the content is complete and usable
5. Check if content extraction was performed when expected
6. Determine if retry would improve file delivery or format compliance
REQUIRED JSON RESPONSE:
{{
"status": "success|retry|fail",
"reason": "Detailed explanation focusing on file delivery and format compliance",
"confidence": 0.0-1.0,
"improvements": ["specific file delivery improvements", "format compliance fixes"],
"quality_score": 1-10,
"missing_elements": ["missing files", "format issues"],
"suggested_retry_approach": "Specific approach for retry if status is retry"
}}
NOTE: Respond with ONLY the JSON object. Do not include any explanatory text."""
def _parseValidationResponse(self, response: str) -> dict:
"""Parse the AI validation response"""
try:
json_start = response.find('{')
json_end = response.rfind('}') + 1
if json_start == -1 or json_end == 0:
raise ValueError("No JSON found in validation response")
json_str = response[json_start:json_end]
validation = json.loads(json_str)
if 'status' not in validation:
raise ValueError("Validation response missing 'status' field")
# Set defaults for optional fields
validation.setdefault('confidence', 0.5)
validation.setdefault('improvements', [])
validation.setdefault('quality_score', 5)
validation.setdefault('missing_elements', [])
validation.setdefault('suggested_retry_approach', '')
return validation
except Exception as e:
logger.error(f"Error parsing validation response: {str(e)}")
return {
'status': 'success',
'reason': f'Parse error: {str(e)}',
'confidence': 0.5,
'improvements': [],
'quality_score': 5,
'missing_elements': [],
'suggested_retry_approach': ''
}
class ChatManager:
"""Chat manager with improved AI integration and method handling"""
def __init__(self, currentUser: User, chatInterface: ChatObjects):
self.currentUser = currentUser
self.chatInterface = chatInterface
self.service: ServiceCenter = None
self.workflow: ChatWorkflow = None
# Circuit breaker for AI calls
self.ai_failure_count = 0
self.ai_last_failure_time = None
self.ai_circuit_breaker_threshold = 5
self.ai_circuit_breaker_timeout = 300 # 5 minutes
# Timeout settings
self.ai_call_timeout = 120 # 2 minutes
self.task_execution_timeout = 600 # 10 minutes
# ===== Initialization and Setup =====
async def initialize(self, workflow: ChatWorkflow) -> None:
"""Initialize chat manager with workflow"""
self.workflow = workflow
self.service = ServiceCenter(self.currentUser, self.workflow)
# ===== WORKFLOW PHASES =====
# Phase 1: High-Level Task Planning
async def planHighLevelTasks(self, userInput: str, workflow: ChatWorkflow) -> Dict[str, Any]:
"""Phase 1: Plan high-level tasks from user input"""
try:
logger.info(f"Planning high-level tasks for workflow {workflow.id}")
# Create planning prompt
prompt = self._createTaskPlanningPrompt({
'user_request': userInput,
'available_documents': self._getAvailableDocuments(workflow),
'workflow_id': workflow.id
})
# Get AI response
response = await self.service.callAiTextAdvanced(prompt)
# Parse and validate task plan
task_plan = self._parseTaskPlanResponse(response)
if not self._validateTaskPlan(task_plan):
logger.error("Generated task plan failed validation")
raise Exception("AI-generated task plan failed validation - AI is required for task planning")
logger.info(f"High-level task planning completed: {len(task_plan.get('tasks', []))} tasks")
return task_plan
except Exception as e:
logger.error(f"Error in high-level task planning: {str(e)}")
raise Exception(f"AI is required for task planning but failed: {str(e)}")
# Phase 2: Task Definition and Action Generation
async def defineTaskActions(self, task_step: Dict[str, Any], workflow: ChatWorkflow, previous_results: List[str] = None,
enhanced_context: Dict[str, Any] = None) -> List[TaskAction]:
"""Phase 2: Define specific actions for a task step with enhanced retry context"""
try:
logger.info(f"Defining actions for task: {task_step.get('description', 'Unknown')}")
# Use enhanced context if provided (for retries), otherwise create basic context
if enhanced_context:
context = enhanced_context
else:
context = {
'task_step': task_step,
'workflow': workflow,
'workflow_id': workflow.id,
'available_documents': self._getAvailableDocuments(workflow),
'previous_results': previous_results or [],
'improvements': None,
'retry_count': 0,
'previous_action_results': [],
'previous_review_result': None
}
# Generate actions using AI
actions = await self._generateActionsForTaskStep(context)
# Convert to TaskAction objects
task_actions = []
for action_dict in actions:
action_data = {
"execMethod": action_dict.get('method', 'unknown'),
"execAction": action_dict.get('action', 'unknown'),
"execParameters": action_dict.get('parameters', {}),
"execResultLabel": action_dict.get('resultLabel', ''),
"status": TaskStatus.PENDING
}
task_action = self.chatInterface.createTaskAction(action_data)
if task_action:
task_actions.append(task_action)
logger.info(f"Created task action: {task_action.execMethod}.{task_action.execAction}")
# Update stats for task validation (estimate bytes for action validation)
if task_actions:
# Calculate actual action size for stats
action_size = self.service.calculateObjectSize(task_actions)
self.service.updateWorkflowStats(eventLabel="action", bytesSent=action_size)
logger.info(f"Task action definition completed: {len(task_actions)} actions")
return task_actions
except Exception as e:
logger.error(f"Error defining task actions: {str(e)}")
return []
# Phase 3: Action Execution
async def executeTaskActions(self, task_actions: List[TaskAction], workflow: ChatWorkflow) -> List[Dict[str, Any]]:
"""Phase 3: Execute all actions for a task with retry mechanism"""
try:
logger.info(f"Executing {len(task_actions)} task actions")
results = []
for i, action in enumerate(task_actions):
logger.info(f"Executing action {i+1}/{len(task_actions)}: {action.execMethod}.{action.execAction}")
# Execute single action with retry mechanism
result = await self._executeSingleAction(action, workflow)
results.append(result)
# If action failed after all retries, continue with next action instead of stopping
if result.get('status') == 'failed':
logger.error(f"Action {i+1} failed after retries, continuing with next action")
# Don't break - continue with remaining actions
continue
logger.info(f"Task action execution completed: {len(results)} results")
return results
except Exception as e:
logger.error(f"Error executing task actions: {str(e)}")
return []
# Phase 4: Task Review and Quality Assessment
async def reviewTaskCompletion(self, task_step: Dict[str, Any], task_actions: List[TaskAction],
action_results: List[Dict[str, Any]], workflow: ChatWorkflow) -> Dict[str, Any]:
"""Phase 4: Review task completion and decide next steps"""
try:
logger.info(f"Reviewing task completion: {task_step.get('description', 'Unknown')}")
# Create step result summary from action results
step_result = {
'task_step': task_step,
'action_results': action_results,
'successful_actions': sum(1 for result in action_results if result.get('status') == 'completed'),
'total_actions': len(action_results),
'results': [result.get('result', '') for result in action_results if result.get('status') == 'completed'],
'errors': [result.get('error', '') for result in action_results if result.get('status') == 'failed']
}
# Prepare review context
review_context = {
'task_step': task_step,
'task_actions': task_actions,
'action_results': action_results,
'step_result': step_result, # Add the missing step_result
'workflow_id': workflow.id,
'previous_results': self._getPreviousResultsFromActions(task_actions)
}
# Use AI to review the results
review = await self._performTaskReview(review_context)
# Add quality metrics
review['quality_metrics'] = self._calculateTaskQualityMetrics(task_step, action_results)
logger.info(f"Task review completed: {review.get('status', 'unknown')}")
return review
except Exception as e:
logger.error(f"Error reviewing task completion: {str(e)}")
return {
'status': 'failed',
'reason': f'Review failed: {str(e)}',
'quality_metrics': {'score': 0, 'confidence': 0}
}
# Phase 5: Task Handover and State Management
async def prepareTaskHandover(self, task_step: Dict[str, Any], task_actions: List[TaskAction],
review_result: Dict[str, Any], workflow: ChatWorkflow) -> Dict[str, Any]:
"""Phase 5: Prepare results for next task or workflow completion"""
try:
logger.info(f"Preparing task handover: {task_step.get('description', 'Unknown')}")
# Update task actions with results
for action in task_actions:
if action.status == TaskStatus.PENDING:
action.status = TaskStatus.COMPLETED if review_result.get('status') == 'success' else TaskStatus.FAILED
# Create serializable task actions
task_actions_serializable = []
for action in task_actions:
action_dict = {
'id': action.id,
'execMethod': action.execMethod,
'execAction': action.execAction,
'execParameters': action.execParameters,
'execResultLabel': action.execResultLabel,
'status': action.status.value if hasattr(action.status, 'value') else str(action.status)
}
task_actions_serializable.append(action_dict)
# Create handover data
handover_data = {
'task_step': task_step,
'task_actions': task_actions_serializable,
'review_result': review_result,
'next_task_ready': review_result.get('status') == 'success',
'available_results': self._getPreviousResultsFromActions(task_actions)
}
logger.info(f"Task handover prepared: next_task_ready={handover_data['next_task_ready']}")
return handover_data
except Exception as e:
logger.error(f"Error preparing task handover: {str(e)}")
# Create serializable task actions for exception case
task_actions_serializable = []
for action in task_actions:
action_dict = {
'id': action.id,
'execMethod': action.execMethod,
'execAction': action.execAction,
'execParameters': action.execParameters,
'execResultLabel': action.execResultLabel,
'status': action.status.value if hasattr(action.status, 'value') else str(action.status)
}
task_actions_serializable.append(action_dict)
return {
'task_step': task_step,
'task_actions': task_actions_serializable,
'review_result': review_result,
'next_task_ready': False,
'available_results': []
}
# ===== Utility Methods =====
async def processFileIds(self, fileIds: List[str]) -> List[ChatDocument]:
"""Process file IDs and return ChatDocument objects"""
documents = []
for fileId in fileIds:
try:
# Ensure service is initialized
if not hasattr(self, 'service') or not self.service:
logger.error(f"Service not initialized for file ID {fileId}")
continue
# Get file info from service
fileInfo = self.service.getFileInfo(fileId)
if fileInfo:
# Create document using interface
documentData = {
"fileId": fileId,
"filename": fileInfo.get("filename", "unknown"),
"fileSize": fileInfo.get("size", 0),
"mimeType": fileInfo.get("mimeType", "application/octet-stream")
}
document = self.chatInterface.createChatDocument(documentData)
if document:
documents.append(document)
logger.info(f"Processed file ID {fileId} -> {document.filename}")
else:
logger.warning(f"No file info found for file ID {fileId}")
except Exception as e:
logger.error(f"Error processing file ID {fileId}: {str(e)}")
return documents
def setUserLanguage(self, language: str) -> None:
"""Set user language for the chat manager"""
if hasattr(self, 'service') and self.service:
self.service.user.language = language
# ===== Enhanced Task Planning Methods =====
async def _callAIWithCircuitBreaker(self, prompt: str, context: str) -> str:
"""Call AI with circuit breaker pattern for fault tolerance"""
try:
# Check circuit breaker
if self._isCircuitBreakerOpen():
raise Exception("AI circuit breaker is open - too many recent failures")
# Call AI with timeout
logger.debug(f"ACTION GENERATION PROMPT: {prompt}")
response = await asyncio.wait_for(
self._callAI(prompt, context),
timeout=self.ai_call_timeout
)
# Reset failure count on success
self.ai_failure_count = 0
return response
except asyncio.TimeoutError:
self._recordAIFailure("Timeout")
raise Exception(f"AI call timed out after {self.ai_call_timeout} seconds")
except Exception as e:
self._recordAIFailure(str(e))
raise
def _isCircuitBreakerOpen(self) -> bool:
"""Check if circuit breaker is open"""
if self.ai_failure_count >= self.ai_circuit_breaker_threshold:
if self.ai_last_failure_time:
time_since_failure = (datetime.now(UTC) - self.ai_last_failure_time).total_seconds()
if time_since_failure < self.ai_circuit_breaker_timeout:
return True
else:
# Reset circuit breaker after timeout
self.ai_failure_count = 0
self.ai_last_failure_time = None
return False
def _recordAIFailure(self, error: str):
"""Record AI failure for circuit breaker"""
self.ai_failure_count += 1
self.ai_last_failure_time = datetime.now(UTC)
logger.warning(f"AI failure recorded ({self.ai_failure_count}/{self.ai_circuit_breaker_threshold}): {error}")
def _validateTaskPlan(self, task_plan: Dict[str, Any]) -> bool:
"""Validate task plan structure and dependencies"""
try:
if not isinstance(task_plan, dict):
return False
if 'tasks' not in task_plan or not isinstance(task_plan['tasks'], list):
return False
# Check each task
task_ids = set()
for task in task_plan['tasks']:
if not isinstance(task, dict):
return False
required_fields = ['id', 'description', 'expected_outputs', 'success_criteria']
if not all(field in task for field in required_fields):
return False
# Check for duplicate task IDs
if task['id'] in task_ids:
return False
task_ids.add(task['id'])
# Validate dependencies
dependencies = task.get('dependencies', [])
if not isinstance(dependencies, list):
return False
# Check that dependencies reference existing tasks
for dep in dependencies:
if dep not in task_ids and dep != 'task_0': # Allow task_0 as special case
return False
# Validate ai_prompt if present (optional field)
if 'ai_prompt' in task and not isinstance(task['ai_prompt'], str):
return False
return True
except Exception as e:
logger.error(f"Error validating task plan: {str(e)}")
return False
def _validateActions(self, actions: List[Dict[str, Any]], context: Dict[str, Any]) -> bool:
"""Validate generated actions"""
try:
if not isinstance(actions, list):
logger.error("Actions must be a list")
return False
if len(actions) == 0:
logger.warning("No actions generated")
return False
for i, action in enumerate(actions):
if not isinstance(action, dict):
logger.error(f"Action {i} must be a dictionary")
return False
# Check required fields
required_fields = ['method', 'action', 'parameters', 'resultLabel']
missing_fields = []
for field in required_fields:
if field not in action or not action[field]:
missing_fields.append(field)
if missing_fields:
logger.error(f"Action {i} missing required fields: {missing_fields}")
return False
# Validate result label format
result_label = action.get('resultLabel', '')
if not result_label.startswith('task'):
logger.error(f"Action {i} result label must start with 'task': {result_label}")
return False
# Validate parameters
parameters = action.get('parameters', {})
if not isinstance(parameters, dict):
logger.error(f"Action {i} parameters must be a dictionary")
return False
logger.info(f"Successfully validated {len(actions)} actions")
return True
except Exception as e:
logger.error(f"Error validating actions: {str(e)}")
return False
# ===== Prompt Creation Methods =====
def _createTaskPlanningPrompt(self, context: Dict[str, Any]) -> str:
"""Create prompt for task planning"""
return f"""You are a task planning AI that analyzes user requests and creates structured task plans.
USER REQUEST: {context['user_request']}
AVAILABLE DOCUMENTS: {', '.join(context['available_documents'])}
INSTRUCTIONS:
1. Analyze the user request and available documents
2. Break down the request into 2-4 meaningful high-level task steps
3. Focus on business outcomes, not technical operations
4. For document processing, create ONE task with a comprehensive AI prompt rather than multiple granular tasks
5. Each task should produce meaningful, usable outputs
6. Ensure proper handover between tasks using result labels
7. Return a JSON object with the exact structure shown below
TASK PLANNING PRINCIPLES:
- Combine related operations into single tasks (e.g., "Extract and analyze all candidate profiles" instead of separate "read file" and "analyze content" tasks)
- Use comprehensive AI prompts for document processing rather than multiple small tasks
- Focus on business value and outcomes
- Keep tasks at a meaningful level of abstraction
- Each task should produce results that can be used by subsequent tasks
REQUIRED JSON STRUCTURE:
{{
"overview": "Brief description of the overall plan",
"tasks": [
{{
"id": "task_1",
"description": "Clear description of what this task accomplishes (business outcome)",
"dependencies": ["task_0"], // IDs of tasks that must complete first
"expected_outputs": ["output1", "output2"],
"success_criteria": ["criteria1", "criteria2"],
"required_documents": ["doc1", "doc2"],
"estimated_complexity": "low|medium|high",
"ai_prompt": "Comprehensive AI prompt for document processing tasks (if applicable)"
}}
]
}}
EXAMPLES OF GOOD TASK DESCRIPTIONS:
- "Extract and analyze all candidate profiles to identify key qualifications and experience"
- "Create evaluation matrix and rate candidates against product designer criteria"
- "Generate comprehensive PowerPoint presentation for management decision"
- "Store final presentation in SharePoint for specified account"
EXAMPLES OF BAD TASK DESCRIPTIONS:
- "Open and read the PDF file" (too granular)
- "Identify table structure" (technical detail)
- "Convert data to CSV format" (implementation detail)
NOTE: Respond with ONLY the JSON object. Do not include any explanatory text."""
async def _createActionDefinitionPrompt(self, context: Dict[str, Any]) -> str:
"""Create prompt for action generation with enhanced document extraction guidance and retry context"""
task_step = context['task_step']
workflow = context.get('workflow')
available_docs = context['available_documents']
previous_results = context['previous_results']
improvements = context.get('improvements', '')
retry_count = context.get('retry_count', 0)
previous_action_results = context.get('previous_action_results', [])
previous_review_result = context.get('previous_review_result')
# Get available methods and actions with signatures
methodList = self.service.getMethodsList()
method_actions = {}
for sig in methodList:
if '.' in sig:
method, rest = sig.split('.', 1)
action = rest.split('(')[0]
method_actions.setdefault(method, []).append((action, sig))
# Get workflow history
messageSummary = await self.service.summarizeChat(workflow.messages)
# Get available documents and connections
docRefs = self.service.getDocumentReferenceList()
connRefs = self.service.getConnectionReferenceList()
all_doc_refs = docRefs.get('chat', []) + docRefs.get('history', [])
# Build AVAILABLE METHODS section
available_methods_str = ''
for method, actions in method_actions.items():
available_methods_str += f"- {method}:\n"
for action, sig in actions:
available_methods_str += f" - {action}: {sig}\n"
# Get AI prompt from task step if available
task_ai_prompt = task_step.get('ai_prompt', '')
# Build retry context section
retry_context = ""
if retry_count > 0:
retry_context = f"""
RETRY CONTEXT (Attempt {retry_count}):
Previous action results that failed or were incomplete:
"""
for i, result in enumerate(previous_action_results):
retry_context += f"- Action {i+1}: {result.get('actionMethod', 'unknown')}.{result.get('actionName', 'unknown')}\n"
retry_context += f" Status: {result.get('status', 'unknown')}\n"
retry_context += f" Error: {result.get('error', 'None')}\n"
retry_context += f" Result: {result.get('result', '')[:100]}...\n"
if previous_review_result:
retry_context += f"""
Previous review feedback:
- Status: {previous_review_result.get('status', 'unknown')}
- Reason: {previous_review_result.get('reason', 'No reason provided')}
- Quality Score: {previous_review_result.get('quality_score', 0)}/10
- Missing Outputs: {', '.join(previous_review_result.get('missing_outputs', []))}
- Unmet Criteria: {', '.join(previous_review_result.get('unmet_criteria', []))}
"""
return f"""
You are an action generation AI that creates specific actions to accomplish a task step.
DOCUMENT REFERENCE TYPES:
- docItem: Reference to a single document. Format: "docItem:<id>:<filename>"
- docList: Reference to a group of documents under a label. Format: <label> (e.g., "task1_action2_results" or "docList:msg123:user_uploads").
- Each docList label maps to a list of docItem references (see AVAILABLE DOCUMENTS).
- A label like "task1_action2_results" refers to the output of action 2 in task 1.
TASK STEP: {task_step.get('description', 'Unknown')} (ID: {task_step.get('id', 'Unknown')})
EXPECTED OUTPUTS: {', '.join(task_step.get('expected_outputs', []))}
SUCCESS CRITERIA: {', '.join(task_step.get('success_criteria', []))}
TASK AI PROMPT: {task_ai_prompt if task_ai_prompt else 'None provided'}
CONTEXT - Chat History:
{messageSummary}
AVAILABLE METHODS AND ACTIONS (with signatures):
{available_methods_str}
AVAILABLE CONNECTIONS:
{chr(10).join(f"- {conn}" for conn in connRefs)}
AVAILABLE DOCUMENTS:
{chr(10).join(f"- {doc.documentsLabel} contains {', '.join(doc.documents)}" for doc in all_doc_refs)}
(Use the label as a value in documentList to refer to the group)
PREVIOUS RESULTS: {', '.join(previous_results) if previous_results else 'None'}
IMPROVEMENTS NEEDED: {improvements if improvements else 'None'}{retry_context}
ACTION GENERATION PRINCIPLES:
- Create meaningful actions per task step
- Use comprehensive AI prompts for document processing
- Focus on business outcomes, not technical operations
- Combine related operations into single actions when possible
- Use the task's AI prompt if provided, or create a comprehensive one
- Each action should produce meaningful, usable outputs
- For document extraction, ensure prompts are specific and detailed
- Include validation steps in extraction prompts
- If this is a retry, learn from previous failures and improve the approach
- Address specific issues mentioned in previous review feedback
INSTRUCTIONS:
- Generate actions to accomplish this task step using available documents, connections, and previous results
- Use docItem for single documents and docList labels for groups of documents as shown in AVAILABLE DOCUMENTS
- Always pass documentList as a LIST of references (docItem and/or docList)
- For resultLabel, use the format: "task{{task_id}}_action{{action_number}}_{{short_label}}" where:
- {{task_id}} = the current task's id (e.g., 1)
- {{action_number}} = the sequence number of the action within the task (e.g., 2)
- {{short_label}} = a short, descriptive label for the output (e.g., "analysis_results")
Example: "task1_action2_analysis_results"
- If this is a retry, ensure the new actions address the specific issues from previous attempts
- Follow the JSON structure below. All fields are required.
REQUIRED JSON STRUCTURE:
{{
"actions": [
{{
"method": "method_name", // Use only the method name (e.g., "document")
"action": "action_name", // Use only the action name (e.g., "extract")
"parameters": {{
"documentList": ["docItem:doc_abc:file1.txt", "task1_action2_results"],
"aiPrompt": "Comprehensive AI prompt describing what to accomplish"
}},
"resultLabel": "task1_action3_analysis_results",
"description": "What this action accomplishes (business outcome)"
}}
]
}}
FIELD REQUIREMENTS:
- "method": Must be from AVAILABLE METHODS
- "action": Must be valid for the method
- "parameters": Method-specific, must include documentList as a list if required by the signature
- "resultLabel": Must follow the format above (e.g., "task1_action3_analysis_results")
- "description": Clear summary of the business outcome
EXAMPLES OF GOOD ACTIONS:
1. Comprehensive document analysis:
{{
"method": "document",
"action": "extract",
"parameters": {{
"documentList": ["docItem:doc_57520394-6b6d-41c2-b641-bab3fc6d7f4b:candidate_1_profile.txt"],
"aiPrompt": "Extract and analyze the candidate's qualifications, experience, skills, and suitability for the product designer position. Identify key strengths, relevant experience, technical skills, and any areas of concern. Provide a comprehensive assessment that can be used for evaluation."
}},
"resultLabel": "task1_action1_candidate_analysis",
"description": "Comprehensive analysis of candidate profile for evaluation"
}}
2. Multi-document processing:
{{
"method": "document",
"action": "extract",
"parameters": {{
"documentList": ["task1_action1_candidate_analysis", "task1_action2_candidate_analysis", "task1_action3_candidate_analysis"],
"aiPrompt": "Compare all three candidate profiles and create an evaluation matrix. Rate each candidate on technical skills, experience level, cultural fit, portfolio quality, and communication skills. Provide clear rankings and recommendations for the product designer position."
}},
"resultLabel": "task1_action4_evaluation_matrix",
"description": "Create comprehensive evaluation matrix comparing all candidates"
}}
NOTE: Respond with ONLY the JSON object. Do not include any explanatory text."""
def _createResultReviewPrompt(self, review_context: Dict[str, Any]) -> str:
"""Create prompt for result review"""
task_step = review_context['task_step']
step_result = review_context['step_result']
# Create serializable version of step_result with only metadata (no document content)
step_result_serializable = {
'task_step': {
'id': task_step.get('id', ''),
'description': task_step.get('description', ''),
'expected_outputs': task_step.get('expected_outputs', []),
'success_criteria': task_step.get('success_criteria', [])
},
'action_results': [],
'successful_actions': step_result.get('successful_actions', 0),
'total_actions': step_result.get('total_actions', 0),
'results_count': len(step_result.get('results', [])),
'errors_count': len(step_result.get('errors', []))
}
# Convert action_results to serializable format with only metadata (no document content)
for action_result in step_result.get('action_results', []):
# Extract only document metadata, not content
documents_metadata = []
for doc in action_result.get('documents', []):
if hasattr(doc, 'filename'):
documents_metadata.append({
'filename': doc.filename,
'fileSize': getattr(doc, 'fileSize', 0),
'mimeType': getattr(doc, 'mimeType', 'unknown')
})
elif isinstance(doc, dict):
documents_metadata.append({
'filename': doc.get('filename', 'unknown'),
'fileSize': doc.get('fileSize', 0),
'mimeType': doc.get('mimeType', 'unknown')
})
serializable_action_result = {
'status': action_result.get('status', ''),
'result_summary': action_result.get('result', '')[:200] + '...' if len(action_result.get('result', '')) > 200 else action_result.get('result', ''),
'error': action_result.get('error', ''),
'resultLabel': action_result.get('resultLabel', ''),
'documents_count': len(documents_metadata),
'documents_metadata': documents_metadata,
'actionId': action_result.get('actionId', ''),
'actionMethod': action_result.get('actionMethod', ''),
'actionName': action_result.get('actionName', ''),
'success_indicator': 'documents' if len(documents_metadata) > 0 else 'text_result' if action_result.get('result', '').strip() else 'none'
}
step_result_serializable['action_results'].append(serializable_action_result)
return f"""You are a result review AI that evaluates task step completion and decides on next actions.
TASK STEP: {task_step.get('description', 'Unknown')}
EXPECTED OUTPUTS: {', '.join(task_step.get('expected_outputs', []))}
SUCCESS CRITERIA: {', '.join(task_step.get('success_criteria', []))}
STEP RESULT: {json.dumps(step_result_serializable, indent=2, ensure_ascii=False)}
INSTRUCTIONS:
1. Evaluate if the task step was completed successfully
2. Check if all expected outputs were produced
3. Verify if success criteria were met
4. Decide on next action: continue, retry, or fail
5. If retry, provide specific improvements needed
IMPORTANT NOTES:
- Actions can produce either text results OR documents (or both)
- Empty result_summary is acceptable if documents were produced (documents_count > 0)
- Focus on whether the action achieved its intended purpose, not just text output
- Document-based actions (like file extractions) often have empty text results but successful document outputs
- Check the 'success_indicator' field: 'documents' means success via document output, 'text_result' means success via text, 'none' means no output
REQUIRED JSON STRUCTURE:
{{
"status": "success|retry|failed",
"reason": "Explanation of the decision",
"improvements": "Specific improvements for retry (if status is retry)",
"quality_score": 1-10,
"missing_outputs": ["output1", "output2"],
"met_criteria": ["criteria1", "criteria2"],
"unmet_criteria": ["criteria3", "criteria4"]
}}
NOTE: Respond with ONLY the JSON object. Do not include any explanatory text."""
# ===== HELPER METHODS FOR WORKFLOW PHASES =====
async def _generateActionsForTaskStep(self, context: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate actions for a specific task step with enhanced retry context"""
try:
# Prepare prompt for action generation
prompt = await self._createActionDefinitionPrompt(context)
# Call AI with circuit breaker
response = await self._callAIWithCircuitBreaker(prompt, "action_generation")
# Parse and validate actions
actions = self._parseActionResponse(response)
# Validate actions
if not self._validateActions(actions, context):
logger.error("Generated actions failed validation")
raise Exception("AI-generated actions failed validation - AI is required for action generation")
logger.info(f"Generated {len(actions)} actions for task step")
return actions
except Exception as e:
logger.error(f"Error generating actions for task step: {str(e)}")
raise Exception(f"AI is required for action generation but failed: {str(e)}")
async def _executeSingleAction(self, action: TaskAction, workflow: ChatWorkflow) -> ActionResult:
"""Execute a single action and return ActionResult with enhanced document processing"""
try:
# Execute the actual method action using the service center
result = await self.service.executeAction(
methodName=action.execMethod,
actionName=action.execAction,
parameters=action.execParameters
)
# Always use the execResultLabel from the action definition
result_label = action.execResultLabel
# Update action based on result
if result.success:
action.setSuccess()
action.result = result.data.get("result", "")
action.execResultLabel = result_label
# Create and store message in workflow for successful action
await self._createActionMessage(action, result, workflow, result_label)
else:
action.setError(result.error or "Action execution failed")
# Enhanced result processing with better document handling
documents = result.data.get("documents", [])
processed_documents = []
# Process documents with better metadata extraction
for doc in documents:
if hasattr(doc, 'filename') and doc.filename:
# Document object with proper metadata
mime_type = getattr(doc, 'mimeType', 'application/octet-stream')
# Enhanced MIME type detection for document objects
if mime_type == "application/octet-stream":
mime_type = self._detectMimeTypeFromDocument(doc, doc.filename)
processed_documents.append({
'filename': doc.filename,
'fileSize': getattr(doc, 'fileSize', 0),
'mimeType': mime_type,
'content': getattr(doc, 'content', ''),
'document': doc
})
elif isinstance(doc, dict):
# Dictionary document with metadata
filename = doc.get('documentName', doc.get('filename', f"{action.execMethod}_{action.execAction}_{datetime.now(UTC).strftime('%Y%m%d_%H%M%S')}"))
fileSize = doc.get('fileSize', len(str(doc.get('documentData', ''))))
mimeType = doc.get('mimeType', 'application/octet-stream')
# Enhanced MIME type detection for dictionary documents
if mimeType == "application/octet-stream":
document_data = doc.get('documentData', '')
mimeType = self._detectMimeTypeFromContent(document_data, filename)
processed_documents.append({
'filename': filename,
'fileSize': fileSize,
'mimeType': mimeType,
'content': doc.get('documentData', ''),
'document': doc
})
else:
# Fallback for unknown document types
logger.warning(f"Unknown document type for action {action.execMethod}.{action.execAction}: {type(doc)}")
filename = f"{action.execMethod}_{action.execAction}_{datetime.now(UTC).strftime('%Y%m%d_%H%M%S')}"
mimeType = 'application/octet-stream'
# Try to detect MIME type for unknown document types
mimeType = self._detectMimeTypeFromContent(doc, filename)
processed_documents.append({
'filename': filename,
'fileSize': 0,
'mimeType': mimeType,
'content': str(doc),
'document': doc
})
# Create ActionResult with processed data
return ActionResult(
success=result.success,
data={
"result": result.data.get("result", ""),
"documents": processed_documents,
"actionId": action.id,
"actionMethod": action.execMethod,
"actionName": action.execAction,
"resultLabel": result_label
},
metadata={
"actionId": action.id,
"actionMethod": action.execMethod,
"actionName": action.execAction,
"resultLabel": result_label
},
validation=[],
error=result.error or ""
)
except Exception as e:
logger.error(f"Error executing single action: {str(e)}")
action.setError(str(e))
return ActionResult(
success=False,
data={
"actionId": action.id,
"actionMethod": action.execMethod,
"actionName": action.execAction,
"documents": []
},
metadata={
"actionId": action.id,
"actionMethod": action.execMethod,
"actionName": action.execAction
},
validation=[],
error=str(e)
)
async def _createActionMessage(self, action: TaskAction, result: Any, workflow: ChatWorkflow, result_label: str = None) -> None:
"""Create and store a message for the action result in the workflow with enhanced document processing"""
try:
# Get result data
result_data = result.data if hasattr(result, 'data') else {}
documents_data = result_data.get("documents", [])
if result_label is None:
result_label = action.execResultLabel
# Create message data
message_data = {
"workflowId": workflow.id,
"role": "assistant",
"message": f"Executed action {action.execMethod}.{action.execAction}",
"status": "step",
"sequenceNr": len(workflow.messages) + 1,
"publishedAt": datetime.now(UTC).isoformat(),
"actionId": action.id,
"actionMethod": action.execMethod,
"actionName": action.execAction,
"documentsLabel": result_label, # Use intent label from action definition
"documents": []
}
# Process documents if any
if documents_data:
processed_documents = []
for doc_data in documents_data:
try:
# Handle different document data formats
if isinstance(doc_data, dict):
# Enhanced document processing for dictionary format
document_name = doc_data.get("documentName", doc_data.get("filename", f"{action.execMethod}_{action.execAction}_{datetime.now(UTC).strftime('%Y%m%d_%H%M%S')}"))
document_data = doc_data.get("documentData", {})
file_size = doc_data.get("fileSize", 0)
mime_type = doc_data.get("mimeType", "application/octet-stream")
elif hasattr(doc_data, 'filename'):
# Document object format
document_name = doc_data.filename
document_data = getattr(doc_data, 'content', {})
file_size = getattr(doc_data, 'fileSize', 0)
mime_type = getattr(doc_data, 'mimeType', "application/octet-stream")
else:
# Fallback for unknown formats
document_name = f"{action.execMethod}_{action.execAction}_{datetime.now(UTC).strftime('%Y%m%d_%H%M%S')}"
document_data = doc_data
file_size = len(str(doc_data))
mime_type = "application/octet-stream"
# Enhanced MIME type detection using service center
if mime_type == "application/octet-stream":
mime_type = self._detectMimeTypeFromContent(document_data, document_name)
# Convert document data to string content
content = self._convertDocumentDataToString(document_data, self._getFileExtension(document_name))
# Validate content before creating file
if not content or content.strip() == "":
logger.warning(f"Empty content for document {document_name}, skipping")
continue
# Create file in database
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
# Create ChatDocument object
document = self.service.createDocument(
fileName=document_name,
mimeType=mime_type,
content=content,
base64encoded=False
)
if document:
processed_documents.append(document)
logger.info(f"Created document: {document_name} with file ID: {file_id} and MIME type: {mime_type}")
else:
logger.error(f"Failed to create ChatDocument object for {document_name}")
except Exception as e:
logger.error(f"Error processing document {doc_data.get('documentName', 'unknown')}: {str(e)}")
continue
# Update message with processed documents
message_data["documents"] = processed_documents
# Create message using interface
message = self.chatInterface.createWorkflowMessage(message_data)
if message:
workflow.messages.append(message)
logger.info(f"Created action message for {action.execMethod}.{action.execAction} with {len(message_data.get('documents', []))} documents")
else:
logger.error(f"Failed to create workflow message for action {action.execMethod}.{action.execAction}")
except Exception as e:
logger.error(f"Error creating action message: {str(e)}")
def _getFileExtension(self, filename: str) -> str:
"""Extract file extension from filename"""
return self.service.getFileExtension(filename)
def _getMimeType(self, extension: str) -> str:
"""Get MIME type based on file extension"""
return self.service.getMimeTypeFromExtension(extension)
def _detectMimeTypeFromContent(self, content: Any, filename: str) -> str:
"""
Detect MIME type from content and filename using service center.
Only returns a detected MIME type if it's better than application/octet-stream.
Args:
content: Content data (string, dict, or other)
filename: Name of the file
Returns:
str: Detected MIME type or original if detection failed
"""
try:
# Convert content to bytes for MIME type detection
if isinstance(content, str):
file_bytes = content.encode('utf-8')
elif isinstance(content, dict):
import json
file_bytes = json.dumps(content, ensure_ascii=False).encode('utf-8')
else:
file_bytes = str(content).encode('utf-8')
# Use service center's MIME type detection
detected_mime_type = self.service.detectContentTypeFromData(file_bytes, filename)
if detected_mime_type != "application/octet-stream":
return detected_mime_type
return "application/octet-stream"
except Exception as e:
logger.warning(f"Error in MIME type detection for {filename}: {str(e)}")
return 'application/octet-stream'
def _detectMimeTypeFromDocument(self, document: Any, filename: str) -> str:
"""
Detect MIME type from document object using service center.
Only returns a detected MIME type if it's better than application/octet-stream.
Args:
document: Document object with content attribute
filename: Name of the file
Returns:
str: Detected MIME type or original if detection failed
"""
try:
# Get document content as bytes for MIME type detection
content = getattr(document, 'content', '')
if isinstance(content, str):
file_bytes = content.encode('utf-8')
else:
file_bytes = str(content).encode('utf-8')
# Use service center's MIME type detection
detected_mime_type = self.service.detectContentTypeFromData(file_bytes, filename)
if detected_mime_type != "application/octet-stream":
return detected_mime_type
return "application/octet-stream"
except Exception as e:
logger.warning(f"Error in MIME type detection for document {filename}: {str(e)}")
return 'application/octet-stream'
def _convertDocumentDataToString(self, document_data: Dict[str, Any], file_extension: str) -> str:
"""Convert document data to string content based on file type with enhanced processing"""
try:
# Handle None or empty data
if document_data is None:
return ""
# Handle string data directly
if isinstance(document_data, str):
return document_data
# Handle dictionary data
if isinstance(document_data, dict):
# For JSON files, return formatted JSON
if file_extension == 'json':
return json.dumps(document_data, indent=2, ensure_ascii=False)
# For text files, try to extract text content
elif file_extension in ['txt', 'md', 'html', 'css', 'js', 'py']:
# Look for common text content fields
text_fields = ['content', 'text', 'data', 'result', 'summary', 'extracted_content', 'table_data']
for field in text_fields:
if field in document_data:
content = document_data[field]
if isinstance(content, str):
return content
elif isinstance(content, (dict, list)):
return json.dumps(content, indent=2, ensure_ascii=False)
# If no text field found, convert entire dict to JSON
return json.dumps(document_data, indent=2, ensure_ascii=False)
# For CSV files, try to extract table data
elif file_extension == 'csv':
# Look for CSV-specific fields
csv_fields = ['table_data', 'csv_data', 'rows', 'data']
for field in csv_fields:
if field in document_data:
content = document_data[field]
if isinstance(content, str):
return content
elif isinstance(content, list):
# Convert list of rows to CSV format
if content and isinstance(content[0], (list, dict)):
import csv
import io
output = io.StringIO()
if isinstance(content[0], dict):
# List of dictionaries
if content:
fieldnames = content[0].keys()
writer = csv.DictWriter(output, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(content)
else:
# List of lists
writer = csv.writer(output)
writer.writerows(content)
return output.getvalue()
# Fallback to JSON if no CSV data found
return json.dumps(document_data, indent=2, ensure_ascii=False)
# For other file types, convert to JSON
else:
return json.dumps(document_data, indent=2, ensure_ascii=False)
# Handle list data
elif isinstance(document_data, list):
if file_extension == 'csv':
# Convert list to CSV format
import csv
import io
output = io.StringIO()
if document_data and isinstance(document_data[0], dict):
# List of dictionaries
fieldnames = document_data[0].keys()
writer = csv.DictWriter(output, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(document_data)
else:
# List of lists
writer = csv.writer(output)
writer.writerows(document_data)
return output.getvalue()
else:
return json.dumps(document_data, indent=2, ensure_ascii=False)
# Handle other data types
else:
return str(document_data)
except Exception as e:
logger.error(f"Error converting document data to string: {str(e)}")
return str(document_data)
async def _performTaskReview(self, review_context: Dict[str, Any]) -> Dict[str, Any]:
"""Perform AI-based task review with enhanced retry logic"""
try:
# Prepare prompt for result review
prompt = self._createResultReviewPrompt(review_context)
# Call AI with circuit breaker
response = await self._callAIWithCircuitBreaker(prompt, "result_review")
# Parse review result
review = self._parseReviewResponse(response)
# Add default values for missing fields
review.setdefault('status', 'unknown')
review.setdefault('reason', 'No reason provided')
review.setdefault('quality_score', 5)
# Enhanced retry logic based on result quality
if review.get('status') == 'retry':
# Analyze the specific issues for better retry guidance
action_results = review_context.get('action_results', [])
if action_results:
# Check for common issues that warrant retry
# Only consider empty results a problem if there are no documents produced
has_empty_results = any(
not result.get('result', '').strip() and
not result.get('documents', []) and
not result.get('documents_metadata', [])
for result in action_results
if result.get('status') == 'completed'
)
has_incomplete_metadata = any(
any(doc.get('filename') == 'unknown' for doc in result.get('documents_metadata', []))
for result in action_results
if result.get('status') == 'completed'
)
if has_empty_results:
review['improvements'] = (review.get('improvements', '') +
" Ensure the document extraction returns actual content, not empty results. " +
"Check if the AI prompt is specific enough to extract meaningful data.")
if has_incomplete_metadata:
review['improvements'] = (review.get('improvements', '') +
" Ensure proper document metadata is extracted including filename, size, and mime type. " +
"The document processing should provide complete file information.")
# If we have specific issues, adjust quality score
if has_empty_results or has_incomplete_metadata:
review['quality_score'] = max(1, review.get('quality_score', 5) - 2)
return review
except Exception as e:
logger.error(f"Error performing task review: {str(e)}")
return {
'status': 'success', # Default to success to avoid blocking workflow
'reason': f'Review failed: {str(e)}',
'quality_score': 5,
'confidence': 0.5
}
def _getPreviousResultsFromActions(self, task_actions: List[TaskAction]) -> List[str]:
"""Get list of previous results from completed actions and workflow messages"""
results = []
# Get results from action objects
for action in task_actions:
if action.execResultLabel and action.isSuccessful():
results.append(action.execResultLabel)
# Get results from workflow messages (for actions that have been executed)
if hasattr(self, 'workflow') and self.workflow and self.workflow.messages:
for message in self.workflow.messages:
if (message.role == 'assistant' and
message.status == 'step' and
message.documentsLabel and
message.documentsLabel not in results):
results.append(message.documentsLabel)
return results
def _calculateTaskQualityMetrics(self, task_step: Dict[str, Any], action_results: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Calculate quality metrics for task step results"""
try:
quality_score = 0
confidence = 0
# Count successful actions
successful_actions = sum(1 for result in action_results if result.get('status') == 'completed')
total_actions = len(action_results)
if total_actions > 0:
success_rate = successful_actions / total_actions
quality_score = int(success_rate * 10) # Scale to 0-10
confidence = min(success_rate, 1.0)
return {
'score': quality_score,
'confidence': confidence,
'successful_actions': successful_actions,
'total_actions': total_actions
}
except Exception as e:
logger.error(f"Error calculating task quality metrics: {str(e)}")
return {'score': 0, 'confidence': 0, 'successful_actions': 0, 'total_actions': 0}
# ===== BASIC HELPER METHODS =====
def _getAvailableDocuments(self, workflow: ChatWorkflow) -> List[str]:
"""Get list of available documents in the workflow"""
documents = []
for message in workflow.messages:
for doc in message.documents:
documents.append(doc.filename)
return documents
def _getPreviousResults(self, task: TaskItem) -> List[str]:
"""Get list of previous results from completed actions"""
results = []
for action in task.actionList:
if action.execResultLabel:
results.append(action.execResultLabel)
return results
def _parseTaskPlanResponse(self, response: str) -> Dict[str, Any]:
"""Parse AI response into task plan structure"""
try:
# Extract JSON from response
json_start = response.find('{')
json_end = response.rfind('}') + 1
if json_start == -1 or json_end == 0:
raise ValueError("No JSON found in response")
json_str = response[json_start:json_end]
task_plan = json.loads(json_str)
# Validate structure
if 'tasks' not in task_plan:
raise ValueError("Task plan missing 'tasks' field")
return task_plan
except Exception as e:
logger.error(f"Error parsing task plan response: {str(e)}")
return {'tasks': []}
def _parseActionResponse(self, response: str) -> List[Dict[str, Any]]:
"""Parse AI response into action list"""
try:
# Extract JSON from response
json_start = response.find('{')
json_end = response.rfind('}') + 1
if json_start == -1 or json_end == 0:
raise ValueError("No JSON found in response")
json_str = response[json_start:json_end]
action_data = json.loads(json_str)
# Validate structure
if 'actions' not in action_data:
raise ValueError("Action response missing 'actions' field")
return action_data['actions']
except Exception as e:
logger.error(f"Error parsing action response: {str(e)}")
return []
def _parseReviewResponse(self, response: str) -> Dict[str, Any]:
"""Parse AI response into review result"""
try:
# Extract JSON from response
json_start = response.find('{')
json_end = response.rfind('}') + 1
if json_start == -1 or json_end == 0:
raise ValueError("No JSON found in response")
json_str = response[json_start:json_end]
review = json.loads(json_str)
# Validate structure
if 'status' not in review:
raise ValueError("Review response missing 'status' field")
return review
except Exception as e:
logger.error(f"Error parsing review response: {str(e)}")
return {'status': 'failed', 'reason': f'Parse error: {str(e)}'}
async def _callAI(self, prompt: str, context: str) -> str:
"""Call AI service with prompt"""
try:
# Use the existing AI call mechanism through service
if hasattr(self, 'service') and self.service:
# Ensure service is properly initialized
if hasattr(self.service, 'callAiTextBasic'):
response = await self.service.callAiTextBasic(prompt)
return response
else:
raise Exception("Service does not have callAiTextBasic method")
else:
raise Exception("No service available for AI calls")
except Exception as e:
logger.error(f"Error calling AI for {context}: {str(e)}")
raise
# ===== WORKFLOW FEEDBACK GENERATION =====
async def generateWorkflowFeedback(self, workflow: ChatWorkflow) -> str:
"""Generate feedback message for workflow completion"""
try:
# Count messages by role
user_messages = [msg for msg in workflow.messages if msg.role == 'user']
assistant_messages = [msg for msg in workflow.messages if msg.role == 'assistant']
# Generate summary feedback
feedback = f"Workflow completed.\n\n"
feedback += f"Processed {len(user_messages)} user inputs and generated {len(assistant_messages)} responses.\n"
# Add final status
if workflow.status == "completed":
feedback += "All tasks completed successfully."
elif workflow.status == "partial":
feedback += "Some tasks completed with partial success."
else:
feedback += f"Workflow status: {workflow.status}"
return feedback
except Exception as e:
logger.error(f"Error generating workflow feedback: {str(e)}")
return "Workflow processing completed."
# ===== UNIFIED WORKFLOW EXECUTION =====
async def executeUnifiedWorkflow(self, userInput: UserInputRequest, workflow: ChatWorkflow) -> Dict[str, Any]:
"""Execute a unified workflow with state management and action-level validation"""
try:
logger.info(f"Starting unified workflow execution for workflow {workflow.id}")
start_time = time.time()
# Initialize chat manager with workflow
await self.initialize(workflow)
# Process file IDs if provided
documents = []
if hasattr(userInput, 'listFileId') and userInput.listFileId:
documents = await self.processFileIds(userInput.listFileId)
logger.info(f"Processed {len(documents)} documents")
# Calculate and update user input stats
user_input_size = self.service.calculateUserInputSize(userInput.prompt)
self.service.updateWorkflowStats(eventLabel="userinput", bytesReceived=user_input_size)
# Phase 1: High-Level Task Planning
logger.info("--- PHASE 1: HIGH-LEVEL TASK PLANNING ---")
task_plan = await self.planHighLevelTasks(userInput.prompt, workflow)
# Update stats for task planning
task_plan_size = self.service.calculateObjectSize(task_plan)
self.service.updateWorkflowStats(eventLabel="taskplan", bytesSent=task_plan_size)
# Create user-friendly task plan log
tasks_count = len(task_plan.get('tasks', []))
task_descriptions = "\n".join([f"- {task.get('description', 'No description')}" for task in task_plan.get('tasks', [])])
self.chatInterface.createWorkflowLog({
"workflowId": workflow.id,
"message": f"Planning completed: {tasks_count} tasks identified\n{task_descriptions}",
"type": "info",
"status": "running",
"progress": 15,
"agentName": "System"
})
# Log task plan details (without document content)
task_plan_log = {
'overview': task_plan.get('overview', ''),
'tasks_count': len(task_plan.get('tasks', [])),
'tasks': []
}
for task in task_plan.get('tasks', []):
task_log = {
'id': task.get('id', ''),
'description': task.get('description', ''),
'dependencies': task.get('dependencies', []),
'expected_outputs': task.get('expected_outputs', []),
'success_criteria': task.get('success_criteria', []),
'required_documents_count': len(task.get('required_documents', [])),
'estimated_complexity': task.get('estimated_complexity', '')
}
task_plan_log['tasks'].append(task_log)
logger.debug(f"TASK PLAN CREATED: {json.dumps(task_plan_log, indent=2, ensure_ascii=False)}")
# Execute each task step with state management
workflow_results = []
previous_results = []
for i, task_step in enumerate(task_plan['tasks']):
task_description = task_step.get('description', 'Unknown')
logger.info(f"=== PROCESSING TASK {i+1}/{len(task_plan['tasks'])}: {task_description} ===")
# Create user-friendly task start log
progress = 20 + (i * 60 // len(task_plan['tasks']))
self.chatInterface.createWorkflowLog({
"workflowId": workflow.id,
"message": f"Executing task {i+1}/{len(task_plan['tasks'])}: {task_description}",
"type": "info",
"status": "running",
"progress": progress,
"agentName": "System"
})
# Create context for task execution
task_context = {
'workflow': workflow,
'workflow_id': workflow.id,
'available_documents': self._getAvailableDocuments(workflow),
'previous_results': previous_results,
'task_description': task_description
}
# Execute task with state management
task_result = await self.executeTaskWithStateManagement(task_step, workflow, task_context)
# Log task result
if task_result['status'] == 'success':
successful_actions = len(task_result.get('successful_actions', []))
failed_actions = len(task_result.get('failed_actions', []))
quality_metrics = task_result.get('quality_metrics', {})
quality_score = quality_metrics.get('score', 0)
self.chatInterface.createWorkflowLog({
"workflowId": workflow.id,
"message": f"🎯 Task {i+1} completed successfully ({successful_actions} actions, quality score: {quality_score})",
"type": "success",
"status": "running",
"progress": progress + 20
})
# Update previous results for next task
previous_results = task_result.get('successful_actions', [])
else:
# Task failed - provide detailed feedback and stop workflow
failed_actions = len(task_result.get('failed_actions', []))
retry_count = task_result.get('retry_count', 0)
reason = task_result.get('reason', 'Unknown error')
self.chatInterface.createWorkflowLog({
"workflowId": workflow.id,
"message": f"❌ Task {i+1} failed after {retry_count} retries: {reason}",
"type": "error",
"status": "failed",
"progress": progress + 15
})
# Generate detailed failure feedback
failure_feedback = self._generateTaskFailureFeedback(task_step, task_result, i+1, len(task_plan['tasks']))
# Stop workflow and return failure
return {
'status': 'failed',
'completed_tasks': i,
'total_tasks': len(task_plan['tasks']),
'failed_task': task_step,
'failure_reason': reason,
'feedback': failure_feedback,
'execution_time': time.time() - start_time
}
# Add task result to workflow results
workflow_results.append(task_result)
# Calculate total processing time
total_processing_time = time.time() - start_time
# Create final success log
self.chatInterface.createWorkflowLog({
"workflowId": workflow.id,
"message": f"🎉 Workflow completed successfully ({len(workflow_results)}/{len(task_plan['tasks'])} tasks)",
"type": "success",
"status": "completed",
"progress": 100
})
# Create workflow summary
workflow_summary = {
'status': 'completed',
'completed_tasks': len(workflow_results),
'total_tasks': len(task_plan['tasks']),
'execution_time': total_processing_time,
'final_results_count': len(previous_results)
}
logger.info(f"=== UNIFIED WORKFLOW COMPLETED: {len(workflow_results)}/{len(task_plan['tasks'])} tasks successful ===")
logger.debug(f"FINAL WORKFLOW SUMMARY: {json.dumps(workflow_summary, indent=2, ensure_ascii=False)}")
return workflow_summary
except Exception as e:
logger.error(f"Error in unified workflow execution: {str(e)}")
# Create error log for user
self.chatInterface.createWorkflowLog({
"workflowId": workflow.id,
"message": f"Workflow execution failed: {str(e)}",
"type": "error",
"status": "failed",
"progress": 100,
"agentName": "System"
})
return {
'status': 'failed',
'error': str(e),
'phase': 'execution'
}
def _generateTaskFailureFeedback(self, task_step: Dict[str, Any], task_result: Dict[str, Any], task_number: int, total_tasks: int) -> str:
"""Generate detailed feedback for task failure"""
successful_actions = len(task_result.get('successful_actions', []))
failed_actions = len(task_result.get('failed_actions', []))
retry_count = task_result.get('retry_count', 0)
reason = task_result.get('reason', 'Unknown error')
feedback = f"""
Workflow execution stopped due to task failure.
PROGRESS: {task_number}/{total_tasks} tasks completed successfully
FAILED TASK: {task_step.get('description', 'Unknown')}
FAILURE DETAILS:
- Retry attempts: {retry_count}
- Successful actions: {successful_actions}
- Failed actions: {failed_actions}
- Failure reason: {reason}
The workflow cannot continue because this task is essential for subsequent steps.
Please review the task requirements and try again with different input or approach.
"""
return feedback
# ===== NEW STATE MANAGEMENT AND VALIDATION CLASSES =====
async def executeTaskWithStateManagement(self, task_step: Dict[str, Any], workflow: ChatWorkflow, context: Dict[str, Any]) -> Dict[str, Any]:
"""Execute task with state management and action-level retry logic"""
try:
logger.info(f"Executing task with state management: {task_step.get('description', 'Unknown')}")
# Initialize task state
state = TaskExecutionState(task_step)
while state.canRetry():
logger.info(f"Task execution attempt {state.retry_count + 1}/{state.max_retries + 1}")
# Generate actions (first time or after regeneration)
if state.retry_count == 0:
actions = await self.defineTaskActions(task_step, workflow, context.get('previous_results', []))
else:
# Regenerate actions with failure context
actions = await self._regenerateTaskActionsWithFailureContext(task_step, state, context)
if not actions:
logger.warning(f"No actions defined for task, marking as failed")
return {
'status': 'failed',
'reason': 'No actions could be generated for task',
'successful_actions': state.successful_actions,
'failed_actions': state.failed_actions,
'retry_count': state.retry_count
}
# Execute actions with individual validation
for i, action in enumerate(actions):
logger.info(f"Executing action {i+1}/{len(actions)}: {action.execMethod}.{action.execAction}")
# Execute action with validation
result = await self.executeActionWithValidation(action, workflow, context)
if result['validation']['status'] == 'success':
state.addSuccessfulAction(result)
logger.info(f"Action {i+1} completed successfully")
elif result['validation']['status'] == 'retry':
# Retry individual action
improvements = result['validation'].get('improvements', [])
retry_result = await self.retryActionWithImprovements(action, result, improvements)
if retry_result['validation']['status'] == 'success':
state.addSuccessfulAction(retry_result)
logger.info(f"Action {i+1} retry successful")
else:
state.addFailedAction(retry_result)
logger.error(f"Action {i+1} retry failed")
# Action failed after retry - stop task execution and regenerate
break
else: # fail
state.addFailedAction(result)
logger.error(f"Action {i+1} failed validation - stopping task execution")
# Action failed - stop task execution and regenerate
break
# Validate task completion
task_validation = await self._validateTaskCompletion(state.successful_actions, task_step, workflow)
if task_validation['status'] == 'success':
logger.info(f"Task completed successfully with {len(state.successful_actions)} successful actions")
return {
'status': 'success',
'successful_actions': state.successful_actions,
'failed_actions': state.failed_actions,
'retry_count': state.retry_count,
'quality_metrics': task_validation.get('quality_metrics', {})
}
elif task_validation['status'] == 'retry':
state.improvements = task_validation.get('improvements', [])
state.incrementRetryCount()
logger.info(f"Task needs retry. Improvements: {state.improvements}")
# Preserve successful actions for next iteration
continue
else: # fail
logger.error(f"Task failed permanently")
return {
'status': 'failed',
'reason': task_validation.get('reason', 'Task validation failed'),
'successful_actions': state.successful_actions,
'failed_actions': state.failed_actions,
'retry_count': state.retry_count
}
# Max retries reached
logger.error(f"Task failed after {state.max_retries} retries")
return {
'status': 'failed',
'reason': f'Task failed after {state.max_retries} retries',
'successful_actions': state.successful_actions,
'failed_actions': state.failed_actions,
'retry_count': state.retry_count
}
except Exception as e:
logger.error(f"Error in task execution with state management: {str(e)}")
return {
'status': 'failed',
'reason': f'Task execution error: {str(e)}',
'successful_actions': [],
'failed_actions': [],
'retry_count': 0
}
async def _regenerateTaskActionsWithFailureContext(self, task_step: Dict[str, Any], state: TaskExecutionState, context: Dict[str, Any]) -> List[TaskAction]:
"""Regenerate task actions with failure context and improvements"""
try:
logger.info(f"Regenerating actions for task with failure context")
# Analyze failure patterns
failure_patterns = state.getFailurePatterns()
# Create enhanced context with failure information
enhanced_context = {
'task_step': task_step,
'workflow': context.get('workflow'),
'workflow_id': context.get('workflow_id'),
'available_documents': context.get('available_documents', []),
'previous_results': state.getAvailableResults(),
'improvements': state.improvements,
'retry_count': state.retry_count,
'failure_patterns': failure_patterns,
'failed_actions': state.failed_actions,
'successful_actions': state.successful_actions,
'is_regeneration': True
}
# Generate new actions with failure avoidance
actions = await self.defineTaskActions(task_step, context.get('workflow'), state.getAvailableResults(), enhanced_context)
logger.info(f"Regenerated {len(actions)} actions with failure context")
return actions
except Exception as e:
logger.error(f"Error regenerating task actions: {str(e)}")
return []
async def _validateTaskCompletion(self, successful_actions: List[Dict[str, Any]], task_step: Dict[str, Any], workflow: ChatWorkflow) -> Dict[str, Any]:
"""Validate if task is completed successfully"""
try:
logger.info(f"Validating task completion: {task_step.get('description', 'Unknown')}")
# Create task result summary
task_result = {
'task_step': task_step,
'successful_actions': successful_actions,
'successful_count': len(successful_actions),
'expected_outputs': task_step.get('expected_outputs', []),
'success_criteria': task_step.get('success_criteria', [])
}
# Use AI to validate task completion
prompt = self._createTaskCompletionValidationPrompt(task_result, task_step)
response = await self._callAIWithCircuitBreaker(prompt, "task_completion_validation")
# Parse validation result
validation = self._parseTaskValidationResponse(response)
# Add quality metrics
validation['quality_metrics'] = self._calculateTaskQualityMetrics(task_step, successful_actions)
logger.info(f"Task completion validation: {validation.get('status', 'unknown')}")
return validation
except Exception as e:
logger.error(f"Error validating task completion: {str(e)}")
return {
'status': 'success', # Default to success to avoid blocking
'reason': f'Validation failed: {str(e)}',
'quality_metrics': {'score': 5, 'confidence': 0.5}
}
def _createTaskCompletionValidationPrompt(self, task_result: Dict[str, Any], task_step: Dict[str, Any]) -> str:
"""Create prompt for task completion validation"""
successful_actions = task_result['successful_actions']
expected_outputs = task_result['expected_outputs']
success_criteria = task_result['success_criteria']
# Summarize successful actions
action_summary = []
for action in successful_actions:
action_summary.append({
'method': action.get('actionMethod', ''),
'action': action.get('actionName', ''),
'result_label': action.get('resultLabel', ''),
'documents_count': len(action.get('documents', [])),
'has_text_result': bool(action.get('result', '').strip())
})
return f"""You are a task completion validator that evaluates if a task was successfully completed.
TASK DETAILS:
- Description: {task_step.get('description', 'Unknown')}
- Expected Outputs: {', '.join(expected_outputs)}
- Success Criteria: {', '.join(success_criteria)}
SUCCESSFUL ACTIONS ({len(successful_actions)}):
{json.dumps(action_summary, indent=2)}
VALIDATION QUESTIONS:
1. Were all expected outputs produced?
2. Are the success criteria met?
3. Do the action results collectively accomplish the task goal?
4. Is the task ready for handover to the next task?
REQUIRED JSON RESPONSE:
{{
"status": "success|retry|fail",
"reason": "Detailed explanation of the validation decision",
"improvements": ["specific improvement 1", "specific improvement 2"],
"missing_outputs": ["output1", "output2"],
"met_criteria": ["criteria1", "criteria2"],
"unmet_criteria": ["criteria3", "criteria4"],
"quality_score": 1-10,
"confidence": 0.0-1.0
}}
NOTE: Respond with ONLY the JSON object. Do not include any explanatory text."""
def _parseTaskValidationResponse(self, response: str) -> Dict[str, Any]:
"""Parse task validation response"""
try:
# Extract JSON from response
json_start = response.find('{')
json_end = response.rfind('}') + 1
if json_start == -1 or json_end == 0:
raise ValueError("No JSON found in task validation response")
json_str = response[json_start:json_end]
validation = json.loads(json_str)
# Validate structure
if 'status' not in validation:
raise ValueError("Task validation response missing 'status' field")
# Set default values
validation.setdefault('reason', 'No reason provided')
validation.setdefault('improvements', [])
validation.setdefault('missing_outputs', [])
validation.setdefault('met_criteria', [])
validation.setdefault('unmet_criteria', [])
validation.setdefault('quality_score', 5)
validation.setdefault('confidence', 0.5)
return validation
except Exception as e:
logger.error(f"Error parsing task validation response: {str(e)}")
return {
'status': 'success',
'reason': f'Parse error: {str(e)}',
'improvements': [],
'missing_outputs': [],
'met_criteria': [],
'unmet_criteria': [],
'quality_score': 5,
'confidence': 0.5
}
# ===== FIX MISSING METHOD CALL =====
async def _executeSingleActionWithRetry(self, action: TaskAction, workflow: ChatWorkflow) -> Dict[str, Any]:
"""Execute single action with retry mechanism - FIXED"""
try:
logger.info(f"Executing action with retry: {action.execMethod}.{action.execAction}")
# Create context for validation
context = {
'task_description': 'Action execution',
'previous_results': [],
'action_index': 0,
'total_actions': 1
}
# Execute action with validation
result = await self.executeActionWithValidation(action, workflow, context)
# If validation indicates retry, attempt retry
if result['validation']['status'] == 'retry':
improvements = result['validation'].get('improvements', [])
retry_result = await self.retryActionWithImprovements(action, result, improvements)
return retry_result
return result
except Exception as e:
logger.error(f"Error executing action with retry: {str(e)}")
action.setError(str(e))
return {
"status": "failed",
"error": str(e),
"actionId": action.id,
"actionMethod": action.execMethod,
"actionName": action.execAction,
"documents": [],
"validation": {
'status': 'fail',
'reason': f'Execution error: {str(e)}',
'confidence': 0.0,
'improvements': [],
'quality_score': 0,
'missing_elements': [],
'suggested_retry_approach': ''
}
}
# ===== REPLACE OLD executeTaskActions METHOD =====
async def executeTaskActions(self, task_actions: List[TaskAction], workflow: ChatWorkflow) -> List[Dict[str, Any]]:
"""Execute task actions with individual validation and retry logic"""
try:
logger.info(f"Executing {len(task_actions)} task actions with validation")
results = []
for i, action in enumerate(task_actions):
logger.info(f"Executing action {i+1}/{len(task_actions)}: {action.execMethod}.{action.execAction}")
# Create context for validation
context = {
'task_description': 'Action execution',
'previous_results': [r.get('resultLabel', '') for r in results if r.get('resultLabel')],
'action_index': i,
'total_actions': len(task_actions)
}
# Execute action with validation
result = await self.executeActionWithValidation(action, workflow, context)
results.append(result)
# If action failed after validation, continue with next action
if result['validation']['status'] == 'fail':
logger.error(f"Action {i+1} failed validation, continuing with next action")
continue
logger.info(f"Task action execution completed: {len(results)} results")
return results
except Exception as e:
logger.error(f"Error executing task actions: {str(e)}")
return []
# ===== RESTRUCTURED ACTION EXECUTION WITH VALIDATION =====
async def executeActionWithValidation(self, action: TaskAction, workflow: ChatWorkflow, context: Dict[str, Any]) -> Dict[str, Any]:
"""Execute single action with immediate validation"""
try:
logger.info(f"Executing action: {action.execMethod}.{action.execAction}")
# Execute the action
result = await self._executeSingleAction(action, workflow)
# Validate the result immediately
validator = ActionValidator(self)
validation = await validator.validateActionResult(result, action, context)
# Add validation result to action result
result['validation'] = validation
# Update action status based on validation
if validation['status'] == 'success':
action.setSuccess()
logger.info(f"Action {action.execMethod}.{action.execAction} validated successfully")
elif validation['status'] == 'retry':
action.status = TaskStatus.PENDING # Keep pending for retry
logger.warning(f"Action {action.execMethod}.{action.execAction} needs retry: {validation.get('reason', 'No reason')}")
else: # fail
action.setError(validation.get('reason', 'Action failed validation'))
logger.error(f"Action {action.execMethod}.{action.execAction} failed validation: {validation.get('reason', 'No reason')}")
return result
except Exception as e:
logger.error(f"Error executing action with validation: {str(e)}")
action.setError(str(e))
return {
"status": "failed",
"error": str(e),
"actionId": action.id,
"actionMethod": action.execMethod,
"actionName": action.execAction,
"documents": [],
"validation": {
'status': 'fail',
'reason': f'Execution error: {str(e)}',
'confidence': 0.0,
'improvements': [],
'quality_score': 0,
'missing_elements': [],
'suggested_retry_approach': ''
}
}
async def retryActionWithImprovements(self, action: TaskAction, previous_result: Dict[str, Any], improvements: List[str]) -> Dict[str, Any]:
"""Retry action with improvements based on previous failure"""
try:
logger.info(f"Retrying action {action.execMethod}.{action.execAction} with improvements")
# Apply improvements to action parameters
enhanced_action = self._enhanceActionWithImprovements(action, improvements, previous_result)
# Execute enhanced action
result = await self._executeSingleAction(enhanced_action, self.workflow)
# Validate the retry result
validator = ActionValidator(self)
context = {
'task_description': 'Action retry',
'previous_results': [],
'retry_count': 1
}
validation = await validator.validateActionResult(result, enhanced_action, context)
# Add validation and retry metadata
result['validation'] = validation
result['is_retry'] = True
result['previous_error'] = previous_result.get('error', '')
result['applied_improvements'] = improvements
# Update action status
if validation['status'] == 'success':
enhanced_action.setSuccess()
logger.info(f"Action retry successful: {enhanced_action.execMethod}.{enhanced_action.execAction}")
else:
enhanced_action.setError(validation.get('reason', 'Retry failed'))
logger.error(f"Action retry failed: {enhanced_action.execMethod}.{enhanced_action.execAction}")
return result
except Exception as e:
logger.error(f"Error retrying action: {str(e)}")
action.setError(str(e))
return {
"status": "failed",
"error": str(e),
"actionId": action.id,
"actionMethod": action.execMethod,
"actionName": action.execAction,
"documents": [],
"is_retry": True,
"validation": {
'status': 'fail',
'reason': f'Retry execution error: {str(e)}',
'confidence': 0.0,
'improvements': [],
'quality_score': 0,
'missing_elements': [],
'suggested_retry_approach': ''
}
}
def _enhanceActionWithImprovements(self, action: TaskAction, improvements: List[str], previous_result: Dict[str, Any]) -> TaskAction:
"""Enhance action parameters based on improvements and previous failure"""
enhanced_action = TaskAction(
id=action.id,
execMethod=action.execMethod,
execAction=action.execAction,
execParameters=action.execParameters.copy(), # Copy to avoid modifying original
execResultLabel=action.execResultLabel,
status=action.status
)
# Apply improvements based on failure patterns
for improvement in improvements:
if "incomplete_analysis" in improvement.lower():
# Enhance AI prompt for more comprehensive analysis
current_prompt = enhanced_action.execParameters.get('aiPrompt', '')
enhanced_prompt = current_prompt + "\n\nIMPORTANT: Provide comprehensive, detailed analysis covering all aspects. Be thorough and include all relevant information."
enhanced_action.execParameters['aiPrompt'] = enhanced_prompt
elif "missing_content" in improvement.lower():
# Add content validation to prompt
current_prompt = enhanced_action.execParameters.get('aiPrompt', '')
enhanced_prompt = current_prompt + "\n\nVALIDATION: Ensure all content is extracted and no information is missed. Provide complete output."
enhanced_action.execParameters['aiPrompt'] = enhanced_prompt
elif "wrong_format" in improvement.lower():
# Add format specification
current_prompt = enhanced_action.execParameters.get('aiPrompt', '')
enhanced_prompt = current_prompt + "\n\nFORMAT: Provide output in structured, well-organized format with clear sections and proper formatting."
enhanced_action.execParameters['aiPrompt'] = enhanced_prompt
elif "timeout" in improvement.lower():
# Add timeout handling
current_prompt = enhanced_action.execParameters.get('aiPrompt', '')
enhanced_prompt = current_prompt + "\n\nEFFICIENCY: Provide concise but complete analysis. Focus on key information and avoid unnecessary details."
enhanced_action.execParameters['aiPrompt'] = enhanced_prompt
# Apply specific improvements from validation
validation = previous_result.get('validation', {})
suggested_approach = validation.get('suggested_retry_approach', '')
if suggested_approach:
current_prompt = enhanced_action.execParameters.get('aiPrompt', '')
enhanced_prompt = current_prompt + f"\n\nRETRY APPROACH: {suggested_approach}"
enhanced_action.execParameters['aiPrompt'] = enhanced_prompt
return enhanced_action