Refactored workflow modes to the standard Dynamic (default), Actionplan (option), Template (future)

This commit is contained in:
ValueOn AG 2025-11-02 19:22:01 +01:00
parent 53bfe06dbe
commit 46fcd089c4
17 changed files with 156 additions and 82 deletions

View file

@ -263,6 +263,23 @@ registerModelLabels(
) )
class WorkflowModeEnum(str, Enum):
WORKFLOW_ACTIONPLAN = "Actionplan"
WORKFLOW_DYNAMIC = "Dynamic"
WORKFLOW_TEMPLATE = "Template"
registerModelLabels(
"WorkflowModeEnum",
{"en": "Workflow Mode", "fr": "Mode de workflow"},
{
"WORKFLOW_ACTIONPLAN": {"en": "Actionplan", "fr": "Actionplan"},
"WORKFLOW_DYNAMIC": {"en": "Dynamic", "fr": "Dynamique"},
"WORKFLOW_TEMPLATE": {"en": "Template", "fr": "Modèle"},
},
)
class ChatWorkflow(BaseModel): class ChatWorkflow(BaseModel):
id: str = Field( id: str = Field(
default_factory=lambda: str(uuid.uuid4()), default_factory=lambda: str(uuid.uuid4()),
@ -372,18 +389,25 @@ class ChatWorkflow(BaseModel):
frontend_readonly=True, frontend_readonly=True,
frontend_required=False, frontend_required=False,
) )
workflowMode: str = Field( workflowMode: WorkflowModeEnum = Field(
default="Actionplan", default=WorkflowModeEnum.WORKFLOW_DYNAMIC,
description="Workflow mode selector", description="Workflow mode selector",
frontend_type="select", frontend_type="select",
frontend_readonly=False, frontend_readonly=False,
frontend_required=False, frontend_required=False,
frontend_options=[ frontend_options=[
{ {
"value": "Actionplan", "value": WorkflowModeEnum.WORKFLOW_ACTIONPLAN.value,
"label": {"en": "Action Plan", "fr": "Plan d'actions"}, "label": {"en": "Actionplan", "fr": "Actionplan"},
},
{
"value": WorkflowModeEnum.WORKFLOW_DYNAMIC.value,
"label": {"en": "Dynamic", "fr": "Dynamique"},
},
{
"value": WorkflowModeEnum.WORKFLOW_TEMPLATE.value,
"label": {"en": "Template", "fr": "Modèle"},
}, },
{"value": "React", "label": {"en": "React", "fr": "Réactif"}},
], ],
) )
maxSteps: int = Field( maxSteps: int = Field(

View file

@ -2,13 +2,13 @@ import logging
from typing import Optional from typing import Optional
from modules.datamodels.datamodelUam import User from modules.datamodels.datamodelUam import User
from modules.datamodels.datamodelChat import ChatWorkflow, UserInputRequest from modules.datamodels.datamodelChat import ChatWorkflow, UserInputRequest, WorkflowModeEnum
from modules.workflows.workflowManager import WorkflowManager from modules.workflows.workflowManager import WorkflowManager
from modules.services import getInterface as getServices from modules.services import getInterface as getServices
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def chatStart(interfaceDbChat, currentUser: User, userInput: UserInputRequest, workflowId: Optional[str] = None, workflowMode: str = "Actionplan") -> ChatWorkflow: async def chatStart(interfaceDbChat, currentUser: User, userInput: UserInputRequest, workflowId: Optional[str] = None, workflowMode: WorkflowModeEnum = WorkflowModeEnum.WORKFLOW_ACTIONPLAN) -> ChatWorkflow:
""" """
Starts a new chat or continues an existing one, then launches processing asynchronously. Starts a new chat or continues an existing one, then launches processing asynchronously.
@ -17,10 +17,10 @@ async def chatStart(interfaceDbChat, currentUser: User, userInput: UserInputRequ
currentUser: Current user currentUser: Current user
userInput: User input request userInput: User input request
workflowId: Optional workflow ID to continue existing workflow workflowId: Optional workflow ID to continue existing workflow
workflowMode: "Actionplan" for traditional task planning, "React" for iterative react-style processing workflowMode: "Actionplan" for traditional task planning, "Dynamic" for iterative dynamic-style processing, "Template" for template-based processing
Example usage for React mode: Example usage for Dynamic mode:
workflow = await chatStart(interfaceDbChat, currentUser, userInput, workflowMode="React") workflow = await chatStart(interfaceDbChat, currentUser, userInput, workflowMode=WorkflowModeEnum.WORKFLOW_DYNAMIC)
""" """
try: try:
services = getServices(currentUser, None) services = getServices(currentUser, None)

View file

@ -17,7 +17,8 @@ from modules.datamodels.datamodelChat import (
ChatStat, ChatStat,
ChatLog, ChatLog,
ChatMessage, ChatMessage,
ChatWorkflow ChatWorkflow,
WorkflowModeEnum
) )
from modules.datamodels.datamodelUam import User from modules.datamodels.datamodelUam import User
@ -367,7 +368,7 @@ class ChatObjects:
messages=[], messages=[],
stats=[], stats=[],
mandateId=created.get("mandateId", self.currentUser.mandateId), mandateId=created.get("mandateId", self.currentUser.mandateId),
workflowMode=created.get("workflowMode", "Actionplan"), workflowMode=created.get("workflowMode", WorkflowModeEnum.WORKFLOW_DYNAMIC),
maxSteps=created.get("maxSteps", 1) maxSteps=created.get("maxSteps", 1)
) )

View file

@ -14,7 +14,7 @@ from modules.security.auth import limiter, getCurrentUser
import modules.interfaces.interfaceDbChatObjects as interfaceDbChatObjects import modules.interfaces.interfaceDbChatObjects as interfaceDbChatObjects
# Import models # Import models
from modules.datamodels.datamodelChat import ChatWorkflow, UserInputRequest from modules.datamodels.datamodelChat import ChatWorkflow, UserInputRequest, WorkflowModeEnum
from modules.datamodels.datamodelUam import User from modules.datamodels.datamodelUam import User
# Import workflow control functions # Import workflow control functions
@ -39,7 +39,7 @@ def getServiceChat(currentUser: User):
async def start_workflow( async def start_workflow(
request: Request, request: Request,
workflowId: Optional[str] = Query(None, description="Optional ID of the workflow to continue"), workflowId: Optional[str] = Query(None, description="Optional ID of the workflow to continue"),
workflowMode: str = Query("Actionplan", description="Workflow mode: 'Actionplan' or 'React'"), workflowMode: WorkflowModeEnum = Query(WorkflowModeEnum.WORKFLOW_DYNAMIC, description="Workflow mode: 'Actionplan', 'Dynamic', or 'Template'"),
userInput: UserInputRequest = Body(...), userInput: UserInputRequest = Body(...),
currentUser: User = Depends(getCurrentUser) currentUser: User = Depends(getCurrentUser)
) -> ChatWorkflow: ) -> ChatWorkflow:
@ -48,7 +48,7 @@ async def start_workflow(
Corresponds to State 1 in the state machine documentation. Corresponds to State 1 in the state machine documentation.
Args: Args:
workflowMode: "Actionplan" for traditional task planning, "React" for iterative react-style processing workflowMode: "Actionplan" for traditional task planning, "Dynamic" for iterative dynamic-style processing, "Template" for template-based processing
""" """
try: try:
# Get service center # Get service center

View file

@ -1,4 +1,4 @@
# adaptive module for React mode # adaptive module for Dynamic mode
# Provides adaptive learning capabilities # Provides adaptive learning capabilities
from .intentAnalyzer import IntentAnalyzer from .intentAnalyzer import IntentAnalyzer

View file

@ -1,5 +1,5 @@
# contentValidator.py # contentValidator.py
# Content validation for adaptive React mode # Content validation for adaptive Dynamic mode
# Generic, document-aware validation system # Generic, document-aware validation system
import logging import logging

View file

@ -1,5 +1,5 @@
# intentAnalyzer.py # intentAnalyzer.py
# Intent analysis for adaptive React mode - AI-based, language-agnostic # Intent analysis for adaptive Dynamic mode - AI-based, language-agnostic
import json import json
import logging import logging

View file

@ -1,5 +1,5 @@
# learningEngine.py # learningEngine.py
# Learning engine for adaptive React mode # Learning engine for adaptive Dynamic mode
import logging import logging
from typing import Dict, Any, List from typing import Dict, Any, List

View file

@ -1,5 +1,5 @@
# progressTracker.py # progressTracker.py
# Progress tracking for adaptive React mode # Progress tracking for adaptive Dynamic mode
import logging import logging
from typing import Dict, Any, List from typing import Dict, Any, List

View file

@ -334,7 +334,7 @@ class ActionplanMode(BaseMode):
taskIndex, actionNumber, totalActions) taskIndex, actionNumber, totalActions)
actionResults.append(result) actionResults.append(result)
# Enhanced validation: Content validation after each action (like React mode) # Enhanced validation: Content validation after each action (like Dynamic mode)
if getattr(self, 'workflowIntent', None) and result.documents: if getattr(self, 'workflowIntent', None) and result.documents:
# Pass ALL documents to validator - validator decides what to validate (generic approach) # Pass ALL documents to validator - validator decides what to validate (generic approach)
# Pass taskStep so validator can use task.objective and format fields # Pass taskStep so validator can use task.objective and format fields

View file

@ -1,5 +1,5 @@
# modeReact.py # modeDynamic.py
# React mode implementation for workflows # Dynamic mode implementation for workflows
import json import json
import logging import logging
@ -14,10 +14,10 @@ from modules.datamodels.datamodelChat import (
from modules.datamodels.datamodelChat import ChatWorkflow from modules.datamodels.datamodelChat import ChatWorkflow
from modules.workflows.processing.modes.modeBase import BaseMode from modules.workflows.processing.modes.modeBase import BaseMode
from modules.workflows.processing.shared.executionState import TaskExecutionState, shouldContinue from modules.workflows.processing.shared.executionState import TaskExecutionState, shouldContinue
from modules.workflows.processing.shared.promptGenerationActionsReact import ( from modules.workflows.processing.shared.promptGenerationActionsDynamic import (
generateReactPlanSelectionPrompt, generateDynamicPlanSelectionPrompt,
generateReactParametersPrompt, generateDynamicParametersPrompt,
generateReactRefinementPrompt generateDynamicRefinementPrompt
) )
from modules.workflows.processing.shared.placeholderFactory import extractReviewContent from modules.workflows.processing.shared.placeholderFactory import extractReviewContent
from modules.workflows.processing.adaptive import IntentAnalyzer, ContentValidator, LearningEngine, ProgressTracker from modules.workflows.processing.adaptive import IntentAnalyzer, ContentValidator, LearningEngine, ProgressTracker
@ -25,8 +25,8 @@ from modules.workflows.processing.adaptive.adaptiveLearningEngine import Adaptiv
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class ReactMode(BaseMode): class DynamicMode(BaseMode):
"""React mode implementation - iterative plan-act-observe-refine loop""" """Dynamic mode implementation - iterative plan-act-observe-refine loop"""
def __init__(self, services, workflow): def __init__(self, services, workflow):
super().__init__(services, workflow) super().__init__(services, workflow)
@ -41,13 +41,13 @@ class ReactMode(BaseMode):
async def generateActionItems(self, taskStep: TaskStep, workflow: ChatWorkflow, async def generateActionItems(self, taskStep: TaskStep, workflow: ChatWorkflow,
previousResults: List = None, enhancedContext: TaskContext = None) -> List[ActionItem]: previousResults: List = None, enhancedContext: TaskContext = None) -> List[ActionItem]:
"""React mode doesn't use batch action generation - actions are generated iteratively""" """Dynamic mode doesn't use batch action generation - actions are generated iteratively"""
# React mode generates actions one at a time in the execution loop # Dynamic mode generates actions one at a time in the execution loop
return [] return []
async def executeTask(self, taskStep: TaskStep, workflow: ChatWorkflow, context: TaskContext, async def executeTask(self, taskStep: TaskStep, workflow: ChatWorkflow, context: TaskContext,
taskIndex: int = None, totalTasks: int = None) -> TaskResult: taskIndex: int = None, totalTasks: int = None) -> TaskResult:
"""Execute task using React mode - iterative plan-act-observe-refine loop""" """Execute task using Dynamic mode - iterative plan-act-observe-refine loop"""
logger.info(f"=== STARTING TASK {taskIndex or '?'}: {taskStep.objective} ===") logger.info(f"=== STARTING TASK {taskIndex or '?'}: {taskStep.objective} ===")
# Use workflow-level intent from planning phase (stored in workflow object) # Use workflow-level intent from planning phase (stored in workflow object)
@ -79,9 +79,9 @@ class ReactMode(BaseMode):
await self.messageCreator.createTaskStartMessage(taskStep, workflow, taskIndex, totalTasks) await self.messageCreator.createTaskStartMessage(taskStep, workflow, taskIndex, totalTasks)
state = TaskExecutionState(taskStep) state = TaskExecutionState(taskStep)
# React mode uses max_steps instead of max_retries # Dynamic mode uses max_steps instead of max_retries
state.max_steps = max(1, int(getattr(workflow, 'maxSteps', 5))) state.max_steps = max(1, int(getattr(workflow, 'maxSteps', 5)))
logger.info(f"Using React mode execution with max_steps: {state.max_steps}") logger.info(f"Using Dynamic mode execution with max_steps: {state.max_steps}")
step = 1 step = 1
decision = None decision = None
@ -95,7 +95,7 @@ class ReactMode(BaseMode):
try: try:
t0 = time.time() t0 = time.time()
selection = await self._planSelect(context) selection = await self._planSelect(context)
logger.info(f"React step {step}: Selected action: {selection}") logger.info(f"Dynamic step {step}: Selected action: {selection}")
# Create user-friendly message BEFORE action execution # Create user-friendly message BEFORE action execution
# Action intention message is now handled by the standard message creator in _actExecute # Action intention message is now handled by the standard message creator in _actExecute
@ -154,7 +154,7 @@ class ReactMode(BaseMode):
# Action completion message is now handled by the standard message creator in _actExecute # Action completion message is now handled by the standard message creator in _actExecute
except Exception as e: except Exception as e:
logger.error(f"React step {step} error: {e}") logger.error(f"Dynamic step {step} error: {e}")
break break
# NEW: Use adaptive stopping logic # NEW: Use adaptive stopping logic
@ -168,7 +168,7 @@ class ReactMode(BaseMode):
break break
step += 1 step += 1
# Summarize task result for react mode # Summarize task result for dynamic mode
status = TaskStatus.COMPLETED status = TaskStatus.COMPLETED
success = True success = True
# Get feedback from last decision if available # Get feedback from last decision if available
@ -199,7 +199,7 @@ class ReactMode(BaseMode):
async def _planSelect(self, context: TaskContext) -> Dict[str, Any]: async def _planSelect(self, context: TaskContext) -> Dict[str, Any]:
"""Plan: select exactly one action. Returns {"action": {method, name}}""" """Plan: select exactly one action. Returns {"action": {method, name}}"""
bundle = generateReactPlanSelectionPrompt(self.services, context, self.adaptiveLearningEngine) bundle = generateDynamicPlanSelectionPrompt(self.services, context, self.adaptiveLearningEngine)
promptTemplate = bundle.prompt promptTemplate = bundle.prompt
placeholders = bundle.placeholders placeholders = bundle.placeholders
@ -297,7 +297,7 @@ class ReactMode(BaseMode):
stage2Context.taskStep = getattr(context, 'taskStep', None) stage2Context.taskStep = getattr(context, 'taskStep', None)
stage2Context.workflowId = getattr(context, 'workflowId', None) stage2Context.workflowId = getattr(context, 'workflowId', None)
# Set Stage 1 data directly on the permissive context (snake_case for promptGenerationActionsReact compatibility) # Set Stage 1 data directly on the permissive context (snake_case for promptGenerationActionsDynamic compatibility)
if isinstance(selection, dict): if isinstance(selection, dict):
stage2Context.action_objective = selection.get('actionObjective', '') stage2Context.action_objective = selection.get('actionObjective', '')
stage2Context.parameters_context = selection.get('parametersContext', '') stage2Context.parameters_context = selection.get('parametersContext', '')
@ -308,7 +308,7 @@ class ReactMode(BaseMode):
stage2Context.learnings = [] stage2Context.learnings = []
# Build and send the Stage 2 parameters prompt (always) # Build and send the Stage 2 parameters prompt (always)
bundle = generateReactParametersPrompt(self.services, stage2Context, compoundActionName, self.adaptiveLearningEngine) bundle = generateDynamicParametersPrompt(self.services, stage2Context, compoundActionName, self.adaptiveLearningEngine)
promptTemplate = bundle.prompt promptTemplate = bundle.prompt
placeholders = bundle.placeholders placeholders = bundle.placeholders
@ -596,7 +596,7 @@ class ReactMode(BaseMode):
reviewContext = ReviewContext( reviewContext = ReviewContext(
taskStep=context.taskStep, taskStep=context.taskStep,
taskActions=[], taskActions=[],
actionResults=[], # React mode doesn't have action results in this context actionResults=[], # Dynamic mode doesn't have action results in this context
stepResult={'observation': observationDict}, stepResult={'observation': observationDict},
workflowId=context.workflowId, workflowId=context.workflowId,
previousResults=[] previousResults=[]
@ -640,7 +640,7 @@ class ReactMode(BaseMode):
# Update placeholders with enhanced review content # Update placeholders with enhanced review content
placeholders["REVIEW_CONTENT"] = enhancedReviewContent placeholders["REVIEW_CONTENT"] = enhancedReviewContent
bundle = generateReactRefinementPrompt(self.services, context, enhancedReviewContent) bundle = generateDynamicRefinementPrompt(self.services, context, enhancedReviewContent)
promptTemplate = bundle.prompt promptTemplate = bundle.prompt
placeholders = bundle.placeholders placeholders = bundle.placeholders
@ -707,10 +707,10 @@ class ReactMode(BaseMode):
qualityScore=5.0 qualityScore=5.0
) )
async def _createReactActionMessage(self, workflow: ChatWorkflow, selection: Dict[str, Any], async def _createDynamicActionMessage(self, workflow: ChatWorkflow, selection: Dict[str, Any],
step: int, maxSteps: int, taskIndex: int, messageType: str, step: int, maxSteps: int, taskIndex: int, messageType: str,
result: ActionResult = None, observation: Observation = None): result: ActionResult = None, observation: Observation = None):
"""Create user-friendly messages for React workflow actions""" """Create user-friendly messages for Dynamic workflow actions"""
try: try:
action = selection.get('action', {}) action = selection.get('action', {})
method = action.get('method', '') method = action.get('method', '')
@ -757,7 +757,7 @@ class ReactMode(BaseMode):
self.services.workflow.storeMessageWithDocuments(workflow, messageData, []) self.services.workflow.storeMessageWithDocuments(workflow, messageData, [])
except Exception as e: except Exception as e:
logger.error(f"Error creating React action message: {str(e)}") logger.error(f"Error creating Dynamic action message: {str(e)}")
async def _generateActionIntentionMessage(self, method: str, actionName: str, userLanguage: str): async def _generateActionIntentionMessage(self, method: str, actionName: str, userLanguage: str):
"""Generate user-friendly message explaining what action will do""" """Generate user-friendly message explaining what action will do"""
@ -817,7 +817,7 @@ Return only the user-friendly message, no technical details."""
return f"{method}.{actionName} action completed" return f"{method}.{actionName} action completed"
def _createActionItem(self, actionData: Dict[str, Any]) -> ActionItem: def _createActionItem(self, actionData: Dict[str, Any]) -> ActionItem:
"""Creates a new task action for React mode""" """Creates a new task action for Dynamic mode"""
try: try:
import uuid import uuid
@ -908,7 +908,7 @@ Return only the user-friendly message, no technical details."""
logger.error(f"Error updating workflow before executing action: {str(e)}") logger.error(f"Error updating workflow before executing action: {str(e)}")
def _createActionItem(self, actionData: Dict[str, Any]) -> ActionItem: def _createActionItem(self, actionData: Dict[str, Any]) -> ActionItem:
"""Creates a new task action for React mode""" """Creates a new task action for Dynamic mode"""
try: try:
import uuid import uuid

View file

@ -0,0 +1,41 @@
# modeTemplate.py
# Template mode implementation for workflows (placeholder for future use)
import logging
from typing import List, Dict, Any
from modules.datamodels.datamodelChat import (
TaskStep, TaskContext, TaskResult, ActionItem, TaskStatus
)
from modules.datamodels.datamodelChat import ChatWorkflow
from modules.workflows.processing.modes.modeBase import BaseMode
logger = logging.getLogger(__name__)
class TemplateMode(BaseMode):
"""Template mode implementation - placeholder for future template-based workflow execution"""
def __init__(self, services, workflow):
super().__init__(services, workflow)
logger.warning("TemplateMode is not yet implemented - using placeholder implementation")
async def generateActionItems(self, taskStep: TaskStep, workflow: ChatWorkflow,
previousResults: List = None, enhancedContext: TaskContext = None) -> List[ActionItem]:
"""Generate actions for a given task step using template-based approach"""
logger.warning("TemplateMode.generateActionItems not yet implemented")
# TODO: Implement template-based action generation
return []
async def executeTask(self, taskStep: TaskStep, workflow: ChatWorkflow, context: TaskContext,
taskIndex: int = None, totalTasks: int = None) -> TaskResult:
"""Execute task using Template mode - placeholder implementation"""
logger.warning("TemplateMode.executeTask not yet implemented - returning placeholder result")
# Return placeholder task result
return TaskResult(
taskId=taskStep.id,
status=TaskStatus.COMPLETED,
success=False,
feedback="Template mode is not yet implemented",
error="Template mode is not yet implemented"
)

View file

@ -17,7 +17,7 @@ class TaskExecutionState:
self.current_action_index = 0 self.current_action_index = 0
self.retry_count = 0 self.retry_count = 0
self.max_retries = 3 self.max_retries = 3
# Iterative loop (react mode) # Iterative loop (dynamic mode)
self.current_step = 0 self.current_step = 0
self.max_steps = 5 self.max_steps = 5

View file

@ -8,22 +8,22 @@ NAMING CONVENTION:
- Placeholder names are in UPPER_CASE with underscores - Placeholder names are in UPPER_CASE with underscores
- Function names are in camelCase - Function names are in camelCase
MAPPING TABLE (keys function) with usage [taskplan | actionplan | react]: MAPPING TABLE (keys function) with usage [taskplan | actionplan | dynamic]:
{{KEY:USER_PROMPT}} -> extractUserPrompt() [taskplan, actionplan, react] {{KEY:USER_PROMPT}} -> extractUserPrompt() [taskplan, actionplan, dynamic]
{{KEY:USER_LANGUAGE}} -> extractUserLanguage() [actionplan, react] {{KEY:USER_LANGUAGE}} -> extractUserLanguage() [actionplan, dynamic]
{{KEY:LANGUAGE_USER_DETECTED}} -> extractLanguageUserDetected() [taskplan] {{KEY:LANGUAGE_USER_DETECTED}} -> extractLanguageUserDetected() [taskplan]
{{KEY:WORKFLOW_HISTORY}} -> extractWorkflowHistory() [taskplan, actionplan, react] {{KEY:WORKFLOW_HISTORY}} -> extractWorkflowHistory() [taskplan, actionplan, dynamic]
{{KEY:AVAILABLE_CONNECTIONS_INDEX}} -> extractAvailableConnectionsIndex() [actionplan, react] {{KEY:AVAILABLE_CONNECTIONS_INDEX}} -> extractAvailableConnectionsIndex() [actionplan, dynamic]
{{KEY:AVAILABLE_CONNECTIONS_SUMMARY}} -> extractAvailableConnectionsSummary() [] {{KEY:AVAILABLE_CONNECTIONS_SUMMARY}} -> extractAvailableConnectionsSummary() []
{{KEY:AVAILABLE_DOCUMENTS_SUMMARY}} -> extractAvailableDocumentsSummary() [taskplan, actionplan, react] {{KEY:AVAILABLE_DOCUMENTS_SUMMARY}} -> extractAvailableDocumentsSummary() [taskplan, actionplan, dynamic]
{{KEY:AVAILABLE_DOCUMENTS_INDEX}} -> extractAvailableDocumentsIndex() [react] {{KEY:AVAILABLE_DOCUMENTS_INDEX}} -> extractAvailableDocumentsIndex() [dynamic]
{{KEY:AVAILABLE_METHODS}} -> extractAvailableMethods() [actionplan, react] {{KEY:AVAILABLE_METHODS}} -> extractAvailableMethods() [actionplan, dynamic]
{{KEY:REVIEW_CONTENT}} -> extractReviewContent() [actionplan, react] {{KEY:REVIEW_CONTENT}} -> extractReviewContent() [actionplan, dynamic]
{{KEY:PREVIOUS_ACTION_RESULTS}} -> extractPreviousActionResults() [react] {{KEY:PREVIOUS_ACTION_RESULTS}} -> extractPreviousActionResults() [dynamic]
{{KEY:LEARNINGS_AND_IMPROVEMENTS}} -> extractLearningsAndImprovements() [react] {{KEY:LEARNINGS_AND_IMPROVEMENTS}} -> extractLearningsAndImprovements() [dynamic]
{{KEY:LATEST_REFINEMENT_FEEDBACK}} -> extractLatestRefinementFeedback() [react] {{KEY:LATEST_REFINEMENT_FEEDBACK}} -> extractLatestRefinementFeedback() [dynamic]
Following placeholders are populated directly by prompt builders with according context in promptGenerationActionsReact module: Following placeholders are populated directly by prompt builders with according context in promptGenerationActionsDynamic module:
- ACTION_OBJECTIVE, - ACTION_OBJECTIVE,
- SELECTED_ACTION, - SELECTED_ACTION,
- ACTION_SIGNATURE - ACTION_SIGNATURE

View file

@ -1,6 +1,6 @@
""" """
React Mode Prompt Generation Dynamic Mode Prompt Generation
Handles prompt templates for react mode action handling. Handles prompt templates for dynamic mode action handling.
""" """
import json import json
@ -20,7 +20,7 @@ from modules.workflows.processing.shared.placeholderFactory import (
) )
from modules.workflows.processing.shared.methodDiscovery import methods, getActionParameterList from modules.workflows.processing.shared.methodDiscovery import methods, getActionParameterList
def generateReactPlanSelectionPrompt(services, context: Any, learningEngine=None) -> PromptBundle: def generateDynamicPlanSelectionPrompt(services, context: Any, learningEngine=None) -> PromptBundle:
"""Define placeholders first, then the template; return PromptBundle.""" """Define placeholders first, then the template; return PromptBundle."""
placeholders: List[PromptPlaceholder] = [ placeholders: List[PromptPlaceholder] = [
PromptPlaceholder(label="USER_PROMPT", content=extractUserPrompt(context), summaryAllowed=False), PromptPlaceholder(label="USER_PROMPT", content=extractUserPrompt(context), summaryAllowed=False),
@ -113,7 +113,7 @@ RULES:
return PromptBundle(prompt=template, placeholders=placeholders) return PromptBundle(prompt=template, placeholders=placeholders)
def generateReactParametersPrompt(services, context: Any, compoundActionName: str, learningEngine=None) -> PromptBundle: def generateDynamicParametersPrompt(services, context: Any, compoundActionName: str, learningEngine=None) -> PromptBundle:
"""Define placeholders first, then the template; return PromptBundle. """Define placeholders first, then the template; return PromptBundle.
Minimal Stage 2 (no fallback): consumes actionObjective, selectedAction, parametersContext only. Minimal Stage 2 (no fallback): consumes actionObjective, selectedAction, parametersContext only.
@ -265,7 +265,7 @@ RULES:
return PromptBundle(prompt=template, placeholders=placeholders) return PromptBundle(prompt=template, placeholders=placeholders)
def generateReactRefinementPrompt(services, context: Any, reviewContent: str) -> PromptBundle: def generateDynamicRefinementPrompt(services, context: Any, reviewContent: str) -> PromptBundle:
"""Define placeholders first, then the template; return PromptBundle.""" """Define placeholders first, then the template; return PromptBundle."""
placeholders: List[PromptPlaceholder] = [ placeholders: List[PromptPlaceholder] = [
PromptPlaceholder(label="USER_PROMPT", content=extractUserPrompt(context), summaryAllowed=False), PromptPlaceholder(label="USER_PROMPT", content=extractUserPrompt(context), summaryAllowed=False),

View file

@ -4,10 +4,11 @@
import logging import logging
from typing import Dict, Any, Optional, List from typing import Dict, Any, Optional, List
from modules.datamodels.datamodelChat import TaskStep, TaskContext, TaskPlan, TaskResult from modules.datamodels.datamodelChat import TaskStep, TaskContext, TaskPlan, TaskResult
from modules.datamodels.datamodelChat import ChatWorkflow from modules.datamodels.datamodelChat import ChatWorkflow, WorkflowModeEnum
from modules.workflows.processing.modes.modeBase import BaseMode from modules.workflows.processing.modes.modeBase import BaseMode
from modules.workflows.processing.modes.modeActionplan import ActionplanMode from modules.workflows.processing.modes.modeActionplan import ActionplanMode
from modules.workflows.processing.modes.modeReact import ReactMode from modules.workflows.processing.modes.modeDynamic import DynamicMode
from modules.workflows.processing.modes.modeTemplate import TemplateMode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -21,15 +22,18 @@ class WorkflowProcessor:
def __init__(self, services, workflow=None): def __init__(self, services, workflow=None):
self.services = services self.services = services
self.workflow = workflow self.workflow = workflow
self.mode = self._createMode(workflow.workflowMode if workflow else "Actionplan") self.mode = self._createMode(workflow.workflowMode if workflow else WorkflowModeEnum.WORKFLOW_DYNAMIC)
def _createMode(self, workflowMode: str) -> BaseMode: def _createMode(self, workflowMode: WorkflowModeEnum) -> BaseMode:
"""Create the appropriate mode implementation based on workflow mode""" """Create the appropriate mode implementation based on workflow mode"""
if workflowMode == "React": if workflowMode == WorkflowModeEnum.WORKFLOW_DYNAMIC:
return ReactMode(self.services, self.workflow) return DynamicMode(self.services, self.workflow)
else: elif workflowMode == WorkflowModeEnum.WORKFLOW_ACTIONPLAN:
return ActionplanMode(self.services, self.workflow) return ActionplanMode(self.services, self.workflow)
elif workflowMode == WorkflowModeEnum.WORKFLOW_TEMPLATE:
return TemplateMode(self.services, self.workflow)
else:
raise ValueError(f"Invalid workflow mode: {workflowMode}")
def _checkWorkflowStopped(self, workflow): def _checkWorkflowStopped(self, workflow):
"""Check if workflow has been stopped by user and raise exception if so""" """Check if workflow has been stopped by user and raise exception if so"""
try: try:
@ -61,7 +65,7 @@ class WorkflowProcessor:
operationId, operationId,
"Workflow Planning", "Workflow Planning",
"Task Plan Generation", "Task Plan Generation",
f"Mode: {workflow.workflowMode}" f"Mode: {workflow.workflowMode.value if hasattr(workflow.workflowMode, 'value') else workflow.workflowMode}"
) )
# Initialize currentUserLanguage to empty at workflow start # Initialize currentUserLanguage to empty at workflow start
@ -70,7 +74,8 @@ class WorkflowProcessor:
logger.info(f"=== STARTING TASK PLAN GENERATION ===") logger.info(f"=== STARTING TASK PLAN GENERATION ===")
logger.info(f"Workflow ID: {workflow.id}") logger.info(f"Workflow ID: {workflow.id}")
logger.info(f"User Input: {userInput}") logger.info(f"User Input: {userInput}")
logger.info(f"Workflow Mode: {workflow.workflowMode}") modeValue = workflow.workflowMode.value if hasattr(workflow.workflowMode, 'value') else workflow.workflowMode
logger.info(f"Workflow Mode: {modeValue}")
# Update progress - generating task plan # Update progress - generating task plan
self.services.workflow.progressLogUpdate(operationId, 0.3, "Analyzing input") self.services.workflow.progressLogUpdate(operationId, 0.3, "Analyzing input")
@ -116,7 +121,8 @@ class WorkflowProcessor:
logger.info(f"=== STARTING TASK EXECUTION ===") logger.info(f"=== STARTING TASK EXECUTION ===")
logger.info(f"Task: {taskStep.objective}") logger.info(f"Task: {taskStep.objective}")
logger.info(f"Mode: {workflow.workflowMode}") modeValue = workflow.workflowMode.value if hasattr(workflow.workflowMode, 'value') else workflow.workflowMode
logger.info(f"Mode: {modeValue}")
# Update progress - executing task # Update progress - executing task
self.services.workflow.progressLogUpdate(operationId, 0.2, "Executing") self.services.workflow.progressLogUpdate(operationId, 0.2, "Executing")
@ -143,7 +149,8 @@ class WorkflowProcessor:
logger.info(f"=== STARTING ACTION GENERATION ===") logger.info(f"=== STARTING ACTION GENERATION ===")
logger.info(f"Task: {taskStep.objective}") logger.info(f"Task: {taskStep.objective}")
logger.info(f"Mode: {workflow.workflowMode}") modeValue = workflow.workflowMode.value if hasattr(workflow.workflowMode, 'value') else workflow.workflowMode
logger.info(f"Mode: {modeValue}")
# Delegate to the appropriate mode # Delegate to the appropriate mode
return await self.mode.generateActionItems(taskStep, workflow, previousResults, enhancedContext) return await self.mode.generateActionItems(taskStep, workflow, previousResults, enhancedContext)

View file

@ -8,7 +8,8 @@ from modules.datamodels.datamodelChat import (
UserInputRequest, UserInputRequest,
ChatMessage, ChatMessage,
ChatWorkflow, ChatWorkflow,
ChatDocument ChatDocument,
WorkflowModeEnum
) )
from modules.datamodels.datamodelChat import TaskContext from modules.datamodels.datamodelChat import TaskContext
from modules.workflows.processing.workflowProcessor import WorkflowProcessor, WorkflowStoppedException from modules.workflows.processing.workflowProcessor import WorkflowProcessor, WorkflowStoppedException
@ -25,7 +26,7 @@ class WorkflowManager:
# Exported functions # Exported functions
async def workflowStart(self, userInput: UserInputRequest, workflowId: Optional[str] = None, workflowMode: str = "React") -> ChatWorkflow: async def workflowStart(self, userInput: UserInputRequest, workflowId: Optional[str] = None, workflowMode: WorkflowModeEnum = WorkflowModeEnum.WORKFLOW_DYNAMIC) -> ChatWorkflow:
"""Starts a new workflow or continues an existing one, then launches processing.""" """Starts a new workflow or continues an existing one, then launches processing."""
try: try:
# Debug log to check workflowMode parameter # Debug log to check workflowMode parameter
@ -90,7 +91,7 @@ class WorkflowManager:
"mandateId": self.services.user.mandateId, "mandateId": self.services.user.mandateId,
"messageIds": [], "messageIds": [],
"workflowMode": workflowMode, "workflowMode": workflowMode,
"maxSteps": 5 if workflowMode == "React" else 1, # Set maxSteps for React mode "maxSteps": 5 if workflowMode == WorkflowModeEnum.WORKFLOW_DYNAMIC else 1, # Set maxSteps for Dynamic mode
} }
workflow = self.services.workflow.createWorkflow(workflowData) workflow = self.services.workflow.createWorkflow(workflowData)
@ -320,7 +321,7 @@ class WorkflowManager:
task_plan = await handling.generateTaskPlan(userInput.prompt, workflow) task_plan = await handling.generateTaskPlan(userInput.prompt, workflow)
if not task_plan or not task_plan.tasks: if not task_plan or not task_plan.tasks:
raise Exception("No tasks generated in task plan.") raise Exception("No tasks generated in task plan.")
workflow_mode = getattr(workflow, 'workflowMode', 'Actionplan') workflow_mode = getattr(workflow, 'workflowMode', WorkflowModeEnum.WORKFLOW_DYNAMIC)
logger.info(f"Workflow object attributes: {workflow.__dict__ if hasattr(workflow, '__dict__') else 'No __dict__'}") logger.info(f"Workflow object attributes: {workflow.__dict__ if hasattr(workflow, '__dict__') else 'No __dict__'}")
logger.info(f"Executing workflow mode={workflow_mode} with {len(task_plan.tasks)} tasks") logger.info(f"Executing workflow mode={workflow_mode} with {len(task_plan.tasks)} tasks")
return task_plan return task_plan