106 lines
No EOL
4.3 KiB
Python
106 lines
No EOL
4.3 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
# executionState.py
|
|
# Contains all execution state management logic
|
|
|
|
import logging
|
|
from typing import List, Optional
|
|
from modules.datamodels.datamodelChatbot import TaskStep, ActionResult, Observation
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class TaskExecutionState:
|
|
"""Manages execution state for a task with retry logic"""
|
|
|
|
def __init__(self, task_step: TaskStep):
|
|
self.task_step = task_step
|
|
self.successful_actions: List[ActionResult] = [] # Preserved across retries
|
|
self.failed_actions: List[ActionResult] = [] # For analysis
|
|
self.current_action_index = 0
|
|
self.retry_count = 0
|
|
self.max_retries = 3
|
|
# Iterative loop (dynamic mode)
|
|
self.current_step = 0
|
|
self.max_steps = 0 # Will be overridden by workflow.maxSteps from workflowManager.py
|
|
|
|
def addSuccessfulAction(self, action_result: ActionResult):
|
|
"""Add a successful action to the state"""
|
|
self.successful_actions.append(action_result)
|
|
self.current_action_index += 1
|
|
|
|
def addFailedAction(self, action_result: ActionResult):
|
|
"""Add a failed action to the state for analysis"""
|
|
self.failed_actions.append(action_result)
|
|
self.current_action_index += 1
|
|
|
|
def canRetry(self) -> bool:
|
|
"""Check if task can be retried"""
|
|
return self.retry_count < self.max_retries
|
|
|
|
def incrementRetryCount(self):
|
|
"""Increment retry count"""
|
|
self.retry_count += 1
|
|
|
|
def getFailurePatterns(self) -> list:
|
|
"""Analyze failure patterns from failed actions"""
|
|
patterns = []
|
|
for action in self.failed_actions:
|
|
error = action.error.lower() if action.error else ''
|
|
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))
|
|
|
|
def shouldContinue(observation: Optional[Observation], review=None, current_step: int = 0, max_steps: int = 1) -> bool:
|
|
"""Helper to decide if the iterative loop should continue
|
|
|
|
Args:
|
|
observation: Observation Pydantic model with action execution results
|
|
review: ReviewResult or dict with review decision (optional)
|
|
current_step: Current step number in the iteration
|
|
max_steps: Maximum allowed steps
|
|
|
|
Returns:
|
|
bool: True if loop should continue, False if should stop
|
|
|
|
Logic:
|
|
- Stop if max steps reached
|
|
- Stop if review indicates 'stop' or success criteria are met
|
|
- Continue if observation indicates failure but allow one more step (caller caps by max_steps)
|
|
"""
|
|
try:
|
|
# Stop if max steps reached
|
|
if current_step >= max_steps:
|
|
logger.info(f"Stopping workflow: reached max_steps limit ({current_step} >= {max_steps})")
|
|
return False
|
|
|
|
# Check review decision (can be ReviewResult model or dict)
|
|
if review:
|
|
if hasattr(review, 'status'):
|
|
# ReviewResult Pydantic model
|
|
if review.status in ('stop', 'success'):
|
|
return False
|
|
elif isinstance(review, dict):
|
|
# Legacy dict format
|
|
decision = review.get('decision') or review.get('status')
|
|
if decision in ('stop', 'success'):
|
|
return False
|
|
|
|
# Check observation: if hard failure with no documents, allow one more step
|
|
# The caller will enforce max_steps limit
|
|
if observation:
|
|
if observation.success is False and observation.documentsCount == 0:
|
|
# Allow next step once; the caller caps by max_steps
|
|
return True
|
|
|
|
return True
|
|
except Exception as e:
|
|
logger.warning(f"Error in shouldContinue: {e}")
|
|
return False |