# modeActionplan.py # Actionplan mode implementation for workflows import json import logging import uuid from datetime import datetime, timezone from typing import List, Dict, Any from modules.datamodels.datamodelChat import ( TaskStep, TaskContext, TaskResult, ActionItem, TaskStatus, ActionResult, ReviewResult, ReviewContext ) from modules.datamodels.datamodelChat import ChatWorkflow from modules.datamodels.datamodelAi import AiCallOptions, OperationTypeEnum, ProcessingModeEnum, PriorityEnum from modules.workflows.processing.modes.modeBase import BaseMode from modules.workflows.processing.shared.stateTools import checkWorkflowStopped from modules.workflows.processing.shared.executionState import TaskExecutionState from modules.workflows.processing.shared.promptGenerationActionsActionplan import ( generateActionDefinitionPrompt, generateResultReviewPrompt ) from modules.workflows.processing.adaptive import IntentAnalyzer, ContentValidator, LearningEngine, ProgressTracker from modules.workflows.processing.adaptive.adaptiveLearningEngine import AdaptiveLearningEngine logger = logging.getLogger(__name__) class ActionplanMode(BaseMode): """Actionplan mode implementation - batch planning and sequential execution""" def __init__(self, services): super().__init__(services) # Initialize adaptive components for enhanced validation and learning self.intentAnalyzer = IntentAnalyzer(services) self.learningEngine = LearningEngine() self.adaptiveLearningEngine = AdaptiveLearningEngine() self.contentValidator = ContentValidator(services, self.adaptiveLearningEngine) self.progressTracker = ProgressTracker() self.workflowIntent = None self.taskIntent = None async def generateActionItems(self, taskStep: TaskStep, workflow: ChatWorkflow, previousResults: List = None, enhancedContext: TaskContext = None) -> List[ActionItem]: """Generate actions for a given task step using batch planning approach""" try: # Check workflow status before generating actions checkWorkflowStopped(self.services) retryInfo = f" (Retry #{enhancedContext.retryCount})" if enhancedContext and enhancedContext.retryCount > 0 else "" logger.info(f"Generating actions for task: {taskStep.objective}{retryInfo}") # Log criteria progress if this is a retry if enhancedContext and hasattr(enhancedContext, 'criteriaProgress') and enhancedContext.criteriaProgress is not None: progress = enhancedContext.criteriaProgress logger.info(f"Retry attempt {enhancedContext.retryCount} - Criteria progress:") if progress.get('met_criteria'): logger.info(f" Met criteria: {', '.join(progress['met_criteria'])}") if progress.get('unmet_criteria'): logger.warning(f" Unmet criteria: {', '.join(progress['unmet_criteria'])}") # Show improvement trends if progress.get('attempt_history'): recentAttempts = progress['attempt_history'][-2:] # Last 2 attempts if len(recentAttempts) >= 2: prevScore = recentAttempts[0].get('quality_score', 0) currScore = recentAttempts[1].get('quality_score', 0) if currScore > prevScore: logger.info(f" Quality improving: {prevScore} -> {currScore}") elif currScore < prevScore: logger.warning(f" Quality declining: {prevScore} -> {currScore}") else: logger.info(f" Quality stable: {currScore}") # Enhanced retry context logging if enhancedContext and enhancedContext.retryCount > 0: logger.info("=== RETRY CONTEXT FOR ACTION GENERATION ===") logger.info(f"Retry Count: {enhancedContext.retryCount}") logger.debug(f"Previous Improvements: {enhancedContext.improvements}") logger.debug(f"Previous Review Result: {enhancedContext.previousReviewResult}") logger.debug(f"Failure Patterns: {enhancedContext.failurePatterns}") logger.debug(f"Failed Actions: {enhancedContext.failedActions}") logger.debug(f"Successful Actions: {enhancedContext.successfulActions}") logger.info("=== END RETRY CONTEXT ===") # Log that we're starting action generation logger.info("=== STARTING ACTION GENERATION ===") # Create proper context object for action definition if enhancedContext and isinstance(enhancedContext, TaskContext): # Use existing TaskContext if provided actionContext = TaskContext( taskStep=enhancedContext.taskStep, workflow=enhancedContext.workflow, workflowId=enhancedContext.workflowId, availableDocuments=enhancedContext.availableDocuments, availableConnections=enhancedContext.availableConnections, previousResults=enhancedContext.previousResults or previousResults or [], previousHandover=enhancedContext.previousHandover, improvements=enhancedContext.improvements or [], retryCount=enhancedContext.retryCount or 0, previousActionResults=enhancedContext.previousActionResults or [], previousReviewResult=enhancedContext.previousReviewResult, isRegeneration=enhancedContext.isRegeneration or False, failurePatterns=enhancedContext.failurePatterns or [], failedActions=enhancedContext.failedActions or [], successfulActions=enhancedContext.successfulActions or [], criteriaProgress=enhancedContext.criteriaProgress ) else: # Create new context from scratch actionContext = TaskContext( taskStep=taskStep, workflow=workflow, workflowId=workflow.id, availableDocuments=None, availableConnections=None, previousResults=previousResults or [], previousHandover=None, improvements=[], retryCount=0, previousActionResults=[], previousReviewResult=None, isRegeneration=False, failurePatterns=[], failedActions=[], successfulActions=[], criteriaProgress=None ) # Check workflow status before calling AI service checkWorkflowStopped(self.services) # Build prompt bundle (template + placeholders) bundle = generateActionDefinitionPrompt(self.services, actionContext) actionPromptTemplate = bundle.prompt placeholders = bundle.placeholders # Centralized AI call: Action planning (quality, detailed) with placeholders options = AiCallOptions( operationType=OperationTypeEnum.PLAN, priority=PriorityEnum.QUALITY, compressPrompt=False, compressContext=False, processingMode=ProcessingModeEnum.DETAILED, maxCost=0.10, maxProcessingTime=30 ) prompt = await self.services.ai.callAiPlanning( prompt=actionPromptTemplate, placeholders=placeholders, debugType="actionplan" ) # Check if AI response is valid if not prompt: raise ValueError("AI service returned no response") # Log action response received logger.info("=== ACTION PLAN AI RESPONSE RECEIVED ===") logger.info(f"Response length: {len(prompt) if prompt else 0}") # Parse action response jsonStart = prompt.find('{') jsonEnd = prompt.rfind('}') + 1 if jsonStart == -1 or jsonEnd == 0: raise ValueError("No JSON found in response") jsonStr = prompt[jsonStart:jsonEnd] try: actionData = json.loads(jsonStr) except Exception as e: logger.error(f"Error parsing action response JSON: {str(e)}") actionData = {} if 'actions' not in actionData: raise ValueError("Action response missing 'actions' field") actions = actionData['actions'] if not actions: raise ValueError("Action response contains empty actions list") if not isinstance(actions, list): raise ValueError(f"Action response 'actions' field is not a list: {type(actions)}") if not self.validator.validateAction(actions, actionContext): logger.error("Generated actions failed validation") raise Exception("AI-generated actions failed validation - AI is required for action generation") # Convert to ActionItem objects taskActions = [] for i, a in enumerate(actions): if not isinstance(a, dict): logger.warning(f"Skipping invalid action {i+1}: not a dictionary") continue # Handle compound action format (new) or separate method/action format (old) action_name = a.get('action', 'unknown') if '.' in action_name: # New compound action format: "method.action" method_name, action_name = action_name.split('.', 1) else: # Old separate format: method + action fields method_name = a.get('method', 'unknown') taskAction = self._createActionItem({ "execMethod": method_name, "execAction": action_name, "execParameters": a.get('parameters', {}), "execResultLabel": a.get('resultLabel', ''), "expectedDocumentFormats": a.get('expectedDocumentFormats', None), "status": TaskStatus.PENDING, # Extract user-friendly message if available "userMessage": a.get('userMessage', None) }) if taskAction: taskActions.append(taskAction) else: logger.warning(f"Skipping invalid action {i+1}: failed to create ActionItem") validActions = [ta for ta in taskActions if ta] if not validActions: raise ValueError("No valid actions could be created from AI response") return validActions except Exception as e: logger.error(f"Error in generateActionItems: {str(e)}") return [] async def executeTask(self, taskStep: TaskStep, workflow: ChatWorkflow, context: TaskContext, taskIndex: int = None, totalTasks: int = None) -> TaskResult: """Execute all actions for a task step using Actionplan mode""" logger.info(f"=== STARTING TASK {taskIndex or '?'}: {taskStep.objective} ===") # Use workflow-level intent from planning phase (stored in workflow object) # This avoids redundant intent analysis - intent was already analyzed during task planning if hasattr(workflow, '_workflowIntent') and workflow._workflowIntent: self.workflowIntent = workflow._workflowIntent logger.info(f"Using workflow intent from planning phase") else: # Fallback: analyze if not available (shouldn't happen in normal flow) originalPrompt = self.services.currentUserPrompt if self.services and hasattr(self.services, 'currentUserPrompt') else taskStep.objective self.workflowIntent = await self.intentAnalyzer.analyzeUserIntent(originalPrompt, context) logger.warning(f"Workflow intent not found in workflow object, analyzed fresh") # Task-level intent is NOT needed - use task.objective + task format fields (dataType, expectedFormats, qualityRequirements) # These format fields are populated from workflow intent during task planning self.taskIntent = None # Removed redundant task-level intent analysis logger.info(f"Workflow intent: {self.workflowIntent}") if taskStep.dataType or taskStep.expectedFormats or taskStep.qualityRequirements: logger.info(f"Task format info: dataType={taskStep.dataType}, expectedFormats={taskStep.expectedFormats}") # Reset progress tracking for new task self.progressTracker.reset() # Update workflow object before executing task if taskIndex is not None: self._updateWorkflowBeforeExecutingTask(taskIndex) # Update workflow context for this task if taskIndex is not None: self.services.chat.setWorkflowContext(taskNumber=taskIndex) # Create task start message await self.messageCreator.createTaskStartMessage(taskStep, workflow, taskIndex, totalTasks) state = TaskExecutionState(taskStep) retryContext = context maxRetries = state.max_retries for attempt in range(maxRetries): logger.info(f"Task execution attempt {attempt+1}/{maxRetries}") # Check workflow status before starting task execution checkWorkflowStopped(self.services) # Update retry context with current attempt information if retryContext: retryContext.retryCount = attempt + 1 actions = await self.generateActionItems(taskStep, workflow, previousResults=retryContext.previousResults, enhancedContext=retryContext) # Log total actions count for this task totalActions = len(actions) if actions else 0 logger.info(f"Task {taskIndex or '?'} has {totalActions} actions") # Update workflow object after action planning self._updateWorkflowAfterActionPlanning(totalActions) self._setWorkflowTotals(totalActions=totalActions) if not actions: logger.error("No actions defined for task step, aborting task execution") break actionResults = [] for actionIdx, action in enumerate(actions): # Check workflow status before each action execution checkWorkflowStopped(self.services) # Update workflow object before executing action actionNumber = actionIdx + 1 self._updateWorkflowBeforeExecutingAction(actionNumber) # Log action start logger.info(f"Task {taskIndex} - Starting action {actionNumber}/{totalActions}") # Create action start message actionStartMessage = { "workflowId": workflow.id, "role": "assistant", "message": f"⚡ **Action {actionNumber}** (Method {action.execMethod}.{action.execAction})", "status": "step", "sequenceNr": len(workflow.messages) + 1, "publishedAt": self.services.utils.timestampGetUtc(), "documentsLabel": f"action_{actionNumber}_start", "documents": [], "actionProgress": "running", "roundNumber": workflow.currentRound, "taskNumber": taskIndex, "actionNumber": actionNumber } # Add user-friendly message if available if action.userMessage: actionStartMessage["message"] += f"\n\n💬 {action.userMessage}" self.services.chat.storeMessageWithDocuments(workflow, actionStartMessage, []) logger.info(f"Action start message created for action {actionNumber}") # Execute single action result = await self.actionExecutor.executeSingleAction(action, workflow, taskStep, taskIndex, actionNumber, totalActions) actionResults.append(result) # Enhanced validation: Content validation after each action (like Dynamic mode) if getattr(self, 'workflowIntent', None) and result.documents: # Pass ALL documents to validator - validator decides what to validate (generic approach) # Pass taskStep so validator can use task.objective and format fields # Pass action name so validator knows which action created the documents actionName = f"{action.execMethod}.{action.execAction}" validationResult = await self.contentValidator.validateContent(result.documents, self.workflowIntent, taskStep, actionName) qualityScore = validationResult.get('qualityScore', 0.0) if qualityScore is None: qualityScore = 0.0 logger.info(f"Content validation for action {actionNumber}: {validationResult['overallSuccess']} (quality: {qualityScore:.2f})") # Record validation result for adaptive learning actionContext = { 'actionName': f"{action.execMethod}.{action.execAction}", 'workflowId': context.workflowId } self.adaptiveLearningEngine.recordValidationResult( validationResult, actionContext, context.workflowId, actionNumber ) # Learn from feedback feedback = self._collectFeedback(result, validationResult, self.workflowIntent) self.learningEngine.learnFromFeedback(feedback, context, self.workflowIntent) # Update progress self.progressTracker.updateOperation(result, validationResult, self.workflowIntent) if result.success: state.addSuccessfulAction(result) else: state.addFailedAction(result) # Check workflow status before review checkWorkflowStopped(self.services) reviewResult = await self._reviewTaskCompletion(taskStep, actions, actionResults, workflow) success = reviewResult.status == 'success' feedback = reviewResult.reason error = None if success else reviewResult.reason if success: logger.info(f"=== TASK {taskIndex or '?'} COMPLETED SUCCESSFULLY: {taskStep.objective} ===") # Create task completion message await self.messageCreator.createTaskCompletionMessage(taskStep, workflow, taskIndex, totalTasks, reviewResult) return TaskResult( taskId=taskStep.id, status=TaskStatus.COMPLETED, success=True, feedback=feedback, error=None ) elif reviewResult.status == 'retry' and state.canRetry(): logger.warning(f"Task step '{taskStep.objective}' requires retry: {reviewResult.improvements}") # Enhanced logging of criteria status if reviewResult.metCriteria: logger.info(f"Met criteria: {', '.join(reviewResult.metCriteria)}") if reviewResult.unmetCriteria: logger.warning(f"Unmet criteria: {', '.join(reviewResult.unmetCriteria)}") state.incrementRetryCount() # Update retry context with retry information and criteria tracking if retryContext: retryContext.retryCount = state.retry_count retryContext.improvements = reviewResult.improvements retryContext.previousActionResults = actionResults retryContext.previousReviewResult = reviewResult retryContext.isRegeneration = True retryContext.failurePatterns = state.getFailurePatterns() retryContext.failedActions = state.failed_actions retryContext.successfulActions = state.successful_actions # Track criteria progress across retries if not hasattr(retryContext, 'criteriaProgress'): retryContext.criteriaProgress = { 'met_criteria': set(), 'unmet_criteria': set(), 'attempt_history': [] } # Update criteria progress if reviewResult.metCriteria: retryContext.criteriaProgress['met_criteria'].update(reviewResult.metCriteria) if reviewResult.unmetCriteria: retryContext.criteriaProgress['unmet_criteria'].update(reviewResult.unmetCriteria) # Record this attempt's criteria status attemptRecord = { 'attempt': state.retry_count, 'met_criteria': reviewResult.metCriteria or [], 'unmet_criteria': reviewResult.unmetCriteria or [], 'quality_score': reviewResult.qualityScore, 'improvements': reviewResult.improvements or [] } retryContext.criteriaProgress['attempt_history'].append(attemptRecord) # Create retry message await self.messageCreator.createRetryMessage(taskStep, workflow, taskIndex, reviewResult) continue else: logger.error(f"=== TASK {taskIndex or '?'} FAILED: {taskStep.objective} after {attempt+1} attempts ===") # Create error message await self.messageCreator.createErrorMessage(taskStep, workflow, taskIndex, reviewResult.reason) return TaskResult( taskId=taskStep.id, status=TaskStatus.FAILED, success=False, feedback=feedback, error=reviewResult.reason if reviewResult and hasattr(reviewResult, 'reason') else "Task failed after retry attempts" ) logger.error(f"=== TASK {taskIndex or '?'} FAILED AFTER ALL RETRIES: {taskStep.objective} ===") # Create final error message await self.messageCreator.createErrorMessage(taskStep, workflow, taskIndex, "Task failed after all retries") return TaskResult( taskId=taskStep.id, status=TaskStatus.FAILED, success=False, feedback="Task failed after all retries.", error="Task failed after all retries." ) async def _reviewTaskCompletion(self, taskStep: TaskStep, taskActions: List[ActionItem], actionResults: List[ActionResult], workflow: ChatWorkflow) -> ReviewResult: """Review task completion and determine success/failure/retry""" try: # Check workflow status before reviewing task completion checkWorkflowStopped(self.services) logger.info(f"=== STARTING TASK COMPLETION REVIEW ===") logger.info(f"Task: {taskStep.objective}") logger.info(f"Actions executed: {len(taskActions) if taskActions else 0}") logger.info(f"Action results: {len(actionResults) if actionResults else 0}") # Create proper context object for result review reviewContext = ReviewContext( taskStep=taskStep, taskActions=taskActions, actionResults=actionResults, stepResult={ 'successful_actions': sum(1 for result in actionResults if result.success), 'total_actions': len(actionResults), 'results': [self._extractResultText(result) for result in actionResults if result.success], 'errors': [result.error for result in actionResults if not result.success], 'documents': [ { 'action_index': i, 'documents_count': len(result.documents) if result.documents else 0, 'documents': result.documents if result.documents else [] } for i, result in enumerate(actionResults) ] }, workflowId=workflow.id, previousResults=[] ) # Check workflow status before calling AI service checkWorkflowStopped(self.services) # Build prompt bundle for result review bundle = generateResultReviewPrompt(reviewContext) promptTemplate = bundle.prompt placeholders = bundle.placeholders # Log result review prompt sent to AI logger.info("=== RESULT REVIEW PROMPT SENT TO AI ===") logger.info(f"Task: {taskStep.objective}") logger.info(f"Action Results Count: {len(reviewContext.actionResults) if reviewContext.actionResults else 0}") logger.info(f"Task Actions Count: {len(reviewContext.taskActions) if reviewContext.taskActions else 0}") # Centralized AI call: Result validation (balanced analysis) with placeholders options = AiCallOptions( operationType=OperationTypeEnum.DATA_ANALYSE, priority=PriorityEnum.BALANCED, compressPrompt=True, compressContext=False, processingMode=ProcessingModeEnum.ADVANCED, maxCost=0.05, maxProcessingTime=30 ) response = await self.services.ai.callAiPlanning( prompt=promptTemplate, placeholders=placeholders, debugType="resultreview" ) # Log result review response received logger.info("=== RESULT REVIEW AI RESPONSE RECEIVED ===") logger.info(f"Response length: {len(response) if response else 0}") # Parse review response jsonStart = response.find('{') jsonEnd = response.rfind('}') + 1 if jsonStart == -1 or jsonEnd == 0: raise ValueError("No JSON found in review response") jsonStr = response[jsonStart:jsonEnd] try: review = json.loads(jsonStr) except Exception as e: logger.error(f"Error parsing review response JSON: {str(e)}") review = {} if 'status' not in review: raise ValueError("Review response missing 'status' field") review.setdefault('status', 'unknown') review.setdefault('reason', 'No reason provided') review.setdefault('quality_score', 5.0) # Ensure improvements is a list improvements = review.get('improvements', []) if isinstance(improvements, str): # Split string into list if it's a single improvement improvements = [improvements.strip()] if improvements.strip() else [] elif not isinstance(improvements, list): improvements = [] # Ensure all list fields are properly typed metCriteria = review.get('met_criteria', []) if not isinstance(metCriteria, list): metCriteria = [] unmetCriteria = review.get('unmet_criteria', []) if not isinstance(unmetCriteria, list): unmetCriteria = [] reviewResult = ReviewResult( status=review.get('status', 'unknown'), reason=review.get('reason', 'No reason provided'), improvements=improvements, qualityScore=float(review.get('quality_score', review.get('qualityScore', 5.0))), missingOutputs=[], metCriteria=metCriteria, unmetCriteria=unmetCriteria, confidence=review.get('confidence', 0.5), # Extract user-friendly message if available userMessage=review.get('userMessage', None) ) # Enhanced validation logging logger.info(f"VALIDATION RESULT - Task: '{taskStep.objective}' - Status: {reviewResult.status.upper()}, Quality: {reviewResult.qualityScore}/10") if reviewResult.status == 'success': logger.info(f"VALIDATION SUCCESS - Task completed successfully") if reviewResult.metCriteria: logger.info(f"Met criteria: {', '.join(reviewResult.metCriteria)}") elif reviewResult.status == 'retry': logger.warning(f"VALIDATION RETRY - Task requires retry: {reviewResult.improvements}") if reviewResult.unmetCriteria: logger.warning(f"Unmet criteria: {', '.join(reviewResult.unmetCriteria)}") else: logger.error(f"VALIDATION FAILED - Task failed: {reviewResult.reason}") logger.info(f"=== TASK COMPLETION REVIEW FINISHED ===") logger.info(f"Final Status: {reviewResult.status}") logger.info(f"Quality Score: {reviewResult.qualityScore}/10") logger.info(f"Improvements: {reviewResult.improvements}") logger.info("=== END REVIEW ===") return reviewResult except Exception as e: logger.error(f"Error in reviewTaskCompletion: {str(e)}") return ReviewResult( status='failed', reason=str(e), qualityScore=0.0 ) def _createActionItem(self, actionData: Dict[str, Any]) -> ActionItem: """Creates a new task action""" try: # Ensure ID is present if "id" not in actionData or not actionData["id"]: actionData["id"] = f"action_{uuid.uuid4()}" # Ensure required fields if "status" not in actionData: actionData["status"] = TaskStatus.PENDING if "execMethod" not in actionData: logger.error("execMethod is required for task action") return None if "execAction" not in actionData: logger.error("execAction is required for task action") return None if "execParameters" not in actionData: actionData["execParameters"] = {} # Use generic field separation based on ActionItem model simpleFields, objectFields = self.services.interfaceDbChat._separateObjectFields(ActionItem, actionData) # Create action in database createdAction = self.services.interfaceDbChat.db.recordCreate(ActionItem, simpleFields) # Convert to ActionItem model return ActionItem( id=createdAction["id"], execMethod=createdAction["execMethod"], execAction=createdAction["execAction"], execParameters=createdAction.get("execParameters", {}), execResultLabel=createdAction.get("execResultLabel"), expectedDocumentFormats=createdAction.get("expectedDocumentFormats"), status=createdAction.get("status", TaskStatus.PENDING), error=createdAction.get("error"), retryCount=createdAction.get("retryCount", 0), retryMax=createdAction.get("retryMax", 3), processingTime=createdAction.get("processingTime"), timestamp=float(createdAction.get("timestamp", self.services.utils.timestampGetUtc())), result=createdAction.get("result"), resultDocuments=createdAction.get("resultDocuments", []), userMessage=createdAction.get("userMessage") ) except Exception as e: logger.error(f"Error creating task action: {str(e)}") return None def _extractResultText(self, result: ActionResult) -> str: """Extract result text from ActionResult documents""" if not result.success or not result.documents: return "" # Extract text directly from ActionDocument objects resultParts = [] for doc in result.documents: if hasattr(doc, 'documentData') and doc.documentData: resultParts.append(str(doc.documentData)) # Join all document results with separators return "\n\n---\n\n".join(resultParts) if resultParts else "" def _collectFeedback(self, result: Any, validation: Dict[str, Any], intent: Dict[str, Any]) -> Dict[str, Any]: """Collects comprehensive feedback from action execution""" try: # Extract content summary contentDelivered = "" if result.documents: firstDoc = result.documents[0] if hasattr(firstDoc, 'documentData'): data = firstDoc.documentData if isinstance(data, dict) and 'content' in data: content = str(data['content']) contentDelivered = content[:100] + "..." if len(content) > 100 else content else: contentDelivered = str(data)[:100] + "..." if len(str(data)) > 100 else str(data) return { "actionAttempted": result.resultLabel or "unknown", "parametersUsed": {}, # Would be extracted from action context "contentDelivered": contentDelivered, "intentMatchScore": validation.get('qualityScore', 0), "qualityScore": validation.get('qualityScore', 0), "issuesFound": validation.get('improvementSuggestions', []), "learningOpportunities": validation.get('improvementSuggestions', []), "userSatisfaction": None, # Would be collected from user feedback "timestamp": datetime.now(timezone.utc).timestamp() } except Exception as e: logger.error(f"Error collecting feedback: {str(e)}") return { "actionAttempted": "unknown", "parametersUsed": {}, "contentDelivered": "", "intentMatchScore": 0, "qualityScore": 0, "issuesFound": [], "learningOpportunities": [], "userSatisfaction": None, "timestamp": datetime.now(timezone.utc).timestamp() } def _updateWorkflowBeforeExecutingTask(self, taskNumber: int): """Update workflow object before executing a task""" try: updateData = { "currentTask": taskNumber, "currentAction": 0, "totalActions": 0 } # Update workflow object self.workflow.currentTask = taskNumber self.workflow.currentAction = 0 self.workflow.totalActions = 0 # Update in database self.services.interfaceDbChat.updateWorkflow(self.workflow.id, updateData) logger.info(f"Updated workflow {self.workflow.id} before executing task {taskNumber}: {updateData}") except Exception as e: logger.error(f"Error updating workflow before executing task: {str(e)}") def _updateWorkflowAfterActionPlanning(self, totalActions: int): """Update workflow object after action planning for current task""" try: updateData = { "totalActions": totalActions } # Update workflow object self.workflow.totalActions = totalActions # Update in database self.services.interfaceDbChat.updateWorkflow(self.workflow.id, updateData) logger.info(f"Updated workflow {self.workflow.id} after action planning: {updateData}") except Exception as e: logger.error(f"Error updating workflow after action planning: {str(e)}") def _updateWorkflowBeforeExecutingAction(self, actionNumber: int): """Update workflow object before executing an action""" try: updateData = { "currentAction": actionNumber } # Update workflow object self.workflow.currentAction = actionNumber # Update in database self.services.interfaceDbChat.updateWorkflow(self.workflow.id, updateData) logger.info(f"Updated workflow {self.workflow.id} before executing action {actionNumber}: {updateData}") except Exception as e: logger.error(f"Error updating workflow before executing action: {str(e)}") def _setWorkflowTotals(self, totalTasks: int = None, totalActions: int = None): """Set total counts for workflow progress tracking and update database""" try: updateData = {} if totalTasks is not None: self.workflow.totalTasks = totalTasks updateData["totalTasks"] = totalTasks if totalActions is not None: self.workflow.totalActions = totalActions updateData["totalActions"] = totalActions # Update workflow object in database if we have changes if updateData: self.services.interfaceDbChat.updateWorkflow(self.workflow.id, updateData) logger.info(f"Updated workflow {self.workflow.id} totals in database: {updateData}") logger.debug(f"Updated workflow totals: Tasks {self.workflow.totalTasks if hasattr(self.workflow, 'totalTasks') else 'N/A'}, Actions {self.workflow.totalActions if hasattr(self.workflow, 'totalActions') else 'N/A'}") except Exception as e: logger.error(f"Error setting workflow totals: {str(e)}")