From d2b6820812b9d34119c7660a32ec0c2f7730b6e9 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Sun, 5 Oct 2025 16:28:44 +0200 Subject: [PATCH] cleaned pydantic model classes --- modules/datamodels/__init__.py | 1 - modules/datamodels/datamodelChat.py | 443 ++++++++++++++--- modules/datamodels/datamodelExtraction.py | 2 +- modules/datamodels/datamodelFiles.py | 10 - modules/datamodels/datamodelNeutralizer.py | 5 - modules/datamodels/datamodelSecurity.py | 6 - modules/datamodels/datamodelTickets.py | 1 - modules/datamodels/datamodelUam.py | 16 - modules/datamodels/datamodelUtils.py | 2 - modules/datamodels/datamodelVoice.py | 1 - modules/datamodels/datamodelWeb.py | 19 +- modules/datamodels/datamodelWorkflow.py | 446 ------------------ modules/interfaces/interfaceAiObjects.py | 2 +- modules/interfaces/interfaceDbChatObjects.py | 4 +- modules/services/serviceAi/mainServiceAi.py | 4 +- .../mainServiceExtraction.py | 22 +- .../services/serviceExtraction/subPipeline.py | 10 +- .../serviceWorkflow/mainServiceWorkflow.py | 2 +- modules/workflows/methods/methodAi.py | 4 +- modules/workflows/methods/methodDocument.py | 4 +- modules/workflows/methods/methodOutlook.py | 2 +- modules/workflows/methods/methodSharepoint.py | 2 +- .../processing/core/actionExecutor.py | 6 +- .../processing/core/messageCreator.py | 2 +- .../workflows/processing/core/taskPlanner.py | 2 +- .../processing/modes/modeActionplan.py | 44 +- .../workflows/processing/modes/modeBase.py | 6 +- .../workflows/processing/modes/modeReact.py | 38 +- .../processing/shared/executionState.py | 4 +- .../processing/shared/methodDiscovery.py | 2 +- .../promptGenerationActionsActionplan.py | 2 +- .../shared/promptGenerationActionsReact.py | 2 +- .../shared/promptGenerationTaskplan.py | 2 +- .../workflows/processing/workflowProcessor.py | 8 +- modules/workflows/workflowManager.py | 33 +- 35 files changed, 492 insertions(+), 667 deletions(-) delete mode 100644 modules/datamodels/datamodelWorkflow.py diff --git a/modules/datamodels/__init__.py b/modules/datamodels/__init__.py index bc18cabd..2ddc1189 100644 --- a/modules/datamodels/__init__.py +++ b/modules/datamodels/__init__.py @@ -10,7 +10,6 @@ from . import datamodelWeb as web from . import datamodelUam as uam from . import datamodelSecurity as security from . import datamodelNeutralizer as neutralizer -from . import datamodelWorkflow as workflow from . import datamodelChat as chat from . import datamodelFiles as files from . import datamodelVoice as voice diff --git a/modules/datamodels/datamodelChat.py b/modules/datamodels/datamodelChat.py index a1640b5d..b7b42aad 100644 --- a/modules/datamodels/datamodelChat.py +++ b/modules/datamodels/datamodelChat.py @@ -6,7 +6,6 @@ from modules.shared.attributeUtils import register_model_labels, ModelMixin from modules.shared.timezoneUtils import get_utc_timestamp import uuid - class ChatStat(BaseModel, ModelMixin): id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key") workflowId: Optional[str] = Field(None, description="Foreign key to workflow (for workflow stats)") @@ -17,8 +16,6 @@ class ChatStat(BaseModel, ModelMixin): bytesReceived: Optional[int] = Field(None, description="Number of bytes received") successRate: Optional[float] = Field(None, description="Success rate of operations") errorCount: Optional[int] = Field(None, description="Number of errors encountered") - - register_model_labels( "ChatStat", {"en": "Chat Statistics", "fr": "Statistiques de chat"}, @@ -35,7 +32,6 @@ register_model_labels( }, ) - class ChatLog(BaseModel, ModelMixin): id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key") workflowId: str = Field(description="Foreign key to workflow") @@ -45,8 +41,6 @@ class ChatLog(BaseModel, ModelMixin): status: Optional[str] = Field(None, description="Status of the log entry") progress: Optional[float] = Field(None, description="Progress indicator (0.0 to 1.0)") performance: Optional[Dict[str, Any]] = Field(None, description="Performance metrics") - - register_model_labels( "ChatLog", {"en": "Chat Log", "fr": "Journal de chat"}, @@ -62,7 +56,6 @@ register_model_labels( }, ) - class ChatDocument(BaseModel, ModelMixin): id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key") messageId: str = Field(description="Foreign key to message") @@ -74,8 +67,6 @@ class ChatDocument(BaseModel, ModelMixin): taskNumber: Optional[int] = Field(None, description="Task number within round") actionNumber: Optional[int] = Field(None, description="Action number within task") actionId: Optional[str] = Field(None, description="ID of the action that created this document") - - register_model_labels( "ChatDocument", {"en": "Chat Document", "fr": "Document de chat"}, @@ -93,7 +84,6 @@ register_model_labels( }, ) - class ContentMetadata(BaseModel, ModelMixin): size: int = Field(description="Content size in bytes") pages: Optional[int] = Field(None, description="Number of pages for multi-page content") @@ -105,8 +95,6 @@ class ContentMetadata(BaseModel, ModelMixin): durationSec: Optional[float] = Field(None, description="Duration in seconds for media") mimeType: str = Field(description="MIME type of the content") base64Encoded: bool = Field(description="Whether the data is base64 encoded") - - register_model_labels( "ContentMetadata", {"en": "Content Metadata", "fr": "Métadonnées du contenu"}, @@ -124,13 +112,10 @@ register_model_labels( }, ) - class ContentItem(BaseModel, ModelMixin): label: str = Field(description="Content label") data: str = Field(description="Extracted text content") metadata: ContentMetadata = Field(description="Content metadata") - - register_model_labels( "ContentItem", {"en": "Content Item", "fr": "Élément de contenu"}, @@ -141,14 +126,11 @@ register_model_labels( }, ) - -class ExtractedContent(BaseModel, ModelMixin): +class ChatContentExtracted(BaseModel, ModelMixin): id: str = Field(description="Reference to source ChatDocument") contents: List[ContentItem] = Field(default_factory=list, description="List of content items") - - register_model_labels( - "ExtractedContent", + "ChatContentExtracted", {"en": "Extracted Content", "fr": "Contenu extrait"}, { "id": {"en": "Object ID", "fr": "ID de l'objet"}, @@ -177,8 +159,6 @@ class ChatMessage(BaseModel, ModelMixin): actionNumber: Optional[int] = Field(None, description="Action number within task") taskProgress: Optional[str] = Field(None, description="Task progress status: pending, running, success, fail, retry") actionProgress: Optional[str] = Field(None, description="Action progress status: pending, running, success, fail") - - register_model_labels( "ChatMessage", {"en": "Chat Message", "fr": "Message de chat"}, @@ -206,7 +186,6 @@ register_model_labels( }, ) - class ChatWorkflow(BaseModel, ModelMixin): id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key", frontend_type="text", frontend_readonly=True, frontend_required=False) mandateId: str = Field(description="ID of the mandate this workflow belongs to", frontend_type="text", frontend_readonly=True, frontend_required=False) @@ -233,8 +212,6 @@ class ChatWorkflow(BaseModel, ModelMixin): {"value": "React", "label": {"en": "React", "fr": "Réactif"}}, ]) maxSteps: int = Field(default=5, description="Maximum number of iterations in react mode", frontend_type="integer", frontend_readonly=False, frontend_required=False) - - register_model_labels( "ChatWorkflow", {"en": "Chat Workflow", "fr": "Flux de travail de chat"}, @@ -259,41 +236,12 @@ register_model_labels( }, ) - -class WorkflowResult(BaseModel, ModelMixin): - status: str - completed_tasks: int - total_tasks: int - execution_time: float - final_results_count: int - error: Optional[str] = None - phase: Optional[str] = None - - -register_model_labels( - "WorkflowResult", - {"en": "Workflow Result", "fr": "Résultat du workflow"}, - { - "status": {"en": "Status", "fr": "Statut"}, - "completed_tasks": {"en": "Completed Tasks", "fr": "Tâches terminées"}, - "total_tasks": {"en": "Total Tasks", "fr": "Total des tâches"}, - "execution_time": {"en": "Execution Time", "fr": "Temps d'exécution"}, - "final_results_count": {"en": "Final Results Count", "fr": "Nombre de résultats finaux"}, - "error": {"en": "Error", "fr": "Erreur"}, - "phase": {"en": "Phase", "fr": "Phase"}, - }, -) - - class UserInputRequest(BaseModel, ModelMixin): prompt: str = Field(description="Prompt for the user") listFileId: List[str] = Field(default_factory=list, description="List of file IDs") userLanguage: str = Field(default="en", description="User's preferred language") - - register_model_labels( - "UserInputRequest", - {"en": "User Input Request", "fr": "Demande de saisie utilisateur"}, + "UserInputRequest", {"en": "User Input Request", "fr": "Demande de saisie utilisateur"}, { "prompt": {"en": "Prompt", "fr": "Invite"}, "listFileId": {"en": "File IDs", "fr": "IDs des fichiers"}, @@ -301,4 +249,389 @@ register_model_labels( }, ) +class ActionDocument(BaseModel, ModelMixin): + """Clear document structure for action results""" + documentName: str = Field(description="Name of the document") + documentData: Any = Field(description="Content/data of the document") + mimeType: str = Field(description="MIME type of the document") +register_model_labels( + "ActionDocument", + {"en": "Action Document", "fr": "Document d'action"}, + { + "documentName": {"en": "Document Name", "fr": "Nom du document"}, + "documentData": {"en": "Document Data", "fr": "Données du document"}, + "mimeType": {"en": "MIME Type", "fr": "Type MIME"}, + }, +) +class ActionResult(BaseModel, ModelMixin): + """Clean action result with documents as primary output + + IMPORTANT: Action methods should NOT set resultLabel in their return value. + The resultLabel is managed by the action handler using the action's execResultLabel + from the action plan. This ensures consistent document routing throughout the workflow. + """ + + success: bool = Field(description="Whether execution succeeded") + error: Optional[str] = Field(None, description="Error message if failed") + documents: List[ActionDocument] = Field(default_factory=list, description="Document outputs") + resultLabel: Optional[str] = Field(None, description="Label for document routing (set by action handler, not by action methods)") + + @classmethod + def isSuccess(cls, documents: List[ActionDocument] = None) -> "ActionResult": + return cls(success=True, documents=documents or []) + + @classmethod + def isFailure(cls, error: str, documents: List[ActionDocument] = None) -> "ActionResult": + return cls(success=False, documents=documents or [], error=error) +register_model_labels( + "ActionResult", + {"en": "Action Result", "fr": "Résultat de l'action"}, + { + "success": {"en": "Success", "fr": "Succès"}, + "error": {"en": "Error", "fr": "Erreur"}, + "documents": {"en": "Documents", "fr": "Documents"}, + "resultLabel": {"en": "Result Label", "fr": "Étiquette du résultat"}, + }, +) + +class ActionSelection(BaseModel, ModelMixin): + method: str = Field(description="Method to execute (e.g., web, document, ai)") + name: str = Field(description="Action name within the method (e.g., search, extract)") +register_model_labels( + "ActionSelection", + {"en": "Action Selection", "fr": "Sélection d'action"}, + { + "method": {"en": "Method", "fr": "Méthode"}, + "name": {"en": "Action Name", "fr": "Nom de l'action"}, + }, +) + +class ActionParameters(BaseModel, ModelMixin): + parameters: Dict[str, Any] = Field(default_factory=dict, description="Parameters to execute the selected action") +register_model_labels( + "ActionParameters", + {"en": "Action Parameters", "fr": "Paramètres d'action"}, + { + "parameters": {"en": "Parameters", "fr": "Paramètres"}, + }, +) + +class ObservationPreview(BaseModel, ModelMixin): + name: str = Field(description="Document name or URL label") + mime: str = Field(description="MIME type or kind") + snippet: str = Field(description="Short snippet or summary") +register_model_labels( + "ObservationPreview", + {"en": "Observation Preview", "fr": "Aperçu d'observation"}, + { + "name": {"en": "Name", "fr": "Nom"}, + "mime": {"en": "MIME", "fr": "MIME"}, + "snippet": {"en": "Snippet", "fr": "Extrait"}, + }, +) + +class Observation(BaseModel, ModelMixin): + success: bool = Field(description="Action execution success flag") + resultLabel: str = Field(description="Deterministic label for produced documents") + documentsCount: int = Field(description="Number of produced documents") + previews: List[ObservationPreview] = Field(default_factory=list, description="Compact previews of outputs") + notes: List[str] = Field(default_factory=list, description="Short notes or key facts") +register_model_labels( + "Observation", + {"en": "Observation", "fr": "Observation"}, + { + "success": {"en": "Success", "fr": "Succès"}, + "resultLabel": {"en": "Result Label", "fr": "Étiquette du résultat"}, + "documentsCount": {"en": "Documents Count", "fr": "Nombre de documents"}, + "previews": {"en": "Previews", "fr": "Aperçus"}, + "notes": {"en": "Notes", "fr": "Notes"}, + }, +) + +class TaskStatus(str): + PENDING = "pending" + RUNNING = "running" + COMPLETED = "completed" + FAILED = "failed" + CANCELLED = "cancelled" +register_model_labels( + "TaskStatus", + {"en": "Task Status", "fr": "Statut de la tâche"}, + { + "PENDING": {"en": "Pending", "fr": "En attente"}, + "RUNNING": {"en": "Running", "fr": "En cours"}, + "COMPLETED": {"en": "Completed", "fr": "Terminé"}, + "FAILED": {"en": "Failed", "fr": "Échec"}, + "CANCELLED": {"en": "Cancelled", "fr": "Annulé"}, + }, +) + +class DocumentExchange(BaseModel, ModelMixin): + documentsLabel: str = Field(description="Label for the set of documents") + documents: List[str] = Field(default_factory=list, description="List of document references") +register_model_labels( + "DocumentExchange", + {"en": "Document Exchange", "fr": "Échange de documents"}, + { + "documentsLabel": {"en": "Documents Label", "fr": "Label des documents"}, + "documents": {"en": "Documents", "fr": "Documents"}, + }, +) + +class ActionItem(BaseModel, ModelMixin): + id: str = Field(..., description="Action ID") + execMethod: str = Field(..., description="Method to execute") + execAction: str = Field(..., description="Action to perform") + execParameters: Dict[str, Any] = Field(default_factory=dict, description="Action parameters") + execResultLabel: Optional[str] = Field(None, description="Label for the set of result documents") + expectedDocumentFormats: Optional[List[Dict[str, str]]] = Field(None, description="Expected document formats (optional)") + userMessage: Optional[str] = Field(None, description="User-friendly message in user's language") + status: TaskStatus = Field(default=TaskStatus.PENDING, description="Action status") + error: Optional[str] = Field(None, description="Error message if action failed") + retryCount: int = Field(default=0, description="Number of retries attempted") + retryMax: int = Field(default=3, description="Maximum number of retries") + processingTime: Optional[float] = Field(None, description="Processing time in seconds") + timestamp: float = Field(..., description="When the action was executed (UTC timestamp in seconds)") + result: Optional[str] = Field(None, description="Result of the action") + + def setSuccess(self, result: str = None) -> None: + """Set the action as successful with optional result""" + self.status = TaskStatus.COMPLETED + self.error = None + if result is not None: + self.result = result + + def setError(self, error_message: str) -> None: + """Set the action as failed with error message""" + self.status = TaskStatus.FAILED + self.error = error_message +register_model_labels( + "ActionItem", + {"en": "Task Action", "fr": "Action de tâche"}, + { + "id": {"en": "Action ID", "fr": "ID de l'action"}, + "execMethod": {"en": "Method", "fr": "Méthode"}, + "execAction": {"en": "Action", "fr": "Action"}, + "execParameters": {"en": "Parameters", "fr": "Paramètres"}, + "execResultLabel": {"en": "Result Label", "fr": "Label du résultat"}, + "expectedDocumentFormats": {"en": "Expected Document Formats", "fr": "Formats de documents attendus"}, + "userMessage": {"en": "User Message", "fr": "Message utilisateur"}, + "status": {"en": "Status", "fr": "Statut"}, + "error": {"en": "Error", "fr": "Erreur"}, + "retryCount": {"en": "Retry Count", "fr": "Nombre de tentatives"}, + "retryMax": {"en": "Max Retries", "fr": "Tentatives max"}, + "processingTime": {"en": "Processing Time", "fr": "Temps de traitement"}, + "timestamp": {"en": "Timestamp", "fr": "Horodatage"}, + "result": {"en": "Result", "fr": "Résultat"}, + }, +) + +class TaskResult(BaseModel, ModelMixin): + taskId: str = Field(..., description="Task ID") + status: TaskStatus = Field(default=TaskStatus.PENDING, description="Task status") + success: bool = Field(..., description="Whether the task was successful") + feedback: Optional[str] = Field(None, description="Task feedback message") + error: Optional[str] = Field(None, description="Error message if task failed") +register_model_labels( + "TaskResult", + {"en": "Task Result", "fr": "Résultat de tâche"}, + { + "taskId": {"en": "Task ID", "fr": "ID de la tâche"}, + "status": {"en": "Status", "fr": "Statut"}, + "success": {"en": "Success", "fr": "Succès"}, + "feedback": {"en": "Feedback", "fr": "Retour"}, + "error": {"en": "Error", "fr": "Erreur"}, + }, +) + +class TaskItem(BaseModel, ModelMixin): + id: str = Field(..., description="Task ID") + workflowId: str = Field(..., description="Workflow ID") + userInput: str = Field(..., description="User input that triggered the task") + status: TaskStatus = Field(default=TaskStatus.PENDING, description="Task status") + error: Optional[str] = Field(None, description="Error message if task failed") + startedAt: Optional[float] = Field(None, description="When the task started (UTC timestamp in seconds)") + finishedAt: Optional[float] = Field(None, description="When the task finished (UTC timestamp in seconds)") + actionList: List[ActionItem] = Field(default_factory=list, description="List of actions to execute") + retryCount: int = Field(default=0, description="Number of retries attempted") + retryMax: int = Field(default=3, description="Maximum number of retries") + rollbackOnFailure: bool = Field(default=True, description="Whether to rollback on failure") + dependencies: List[str] = Field(default_factory=list, description="List of task IDs this task depends on") + feedback: Optional[str] = Field(None, description="Task feedback message") + processingTime: Optional[float] = Field(None, description="Total processing time in seconds") + resultLabels: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Map of result labels to their values") +register_model_labels( + "TaskItem", + {"en": "Task", "fr": "Tâche"}, + { + "id": {"en": "Task ID", "fr": "ID de la tâche"}, + "workflowId": {"en": "Workflow ID", "fr": "ID du workflow"}, + "userInput": {"en": "User Input", "fr": "Entrée utilisateur"}, + "status": {"en": "Status", "fr": "Statut"}, + "error": {"en": "Error", "fr": "Erreur"}, + "startedAt": {"en": "Started At", "fr": "Démarré à"}, + "finishedAt": {"en": "Finished At", "fr": "Terminé à"}, + "actionList": {"en": "Actions", "fr": "Actions"}, + "retryCount": {"en": "Retry Count", "fr": "Nombre de tentatives"}, + "retryMax": {"en": "Max Retries", "fr": "Tentatives max"}, + "processingTime": {"en": "Processing Time", "fr": "Temps de traitement"}, + }, +) + +class TaskStep(BaseModel, ModelMixin): + id: str + objective: str + dependencies: Optional[list[str]] = Field(default_factory=list) + success_criteria: Optional[list[str]] = Field(default_factory=list) + estimated_complexity: Optional[str] = None + userMessage: Optional[str] = Field(None, description="User-friendly message in user's language") +register_model_labels( + "TaskStep", + {"en": "Task Step", "fr": "Étape de tâche"}, + { + "id": {"en": "ID", "fr": "ID"}, + "objective": {"en": "Objective", "fr": "Objectif"}, + "dependencies": {"en": "Dependencies", "fr": "Dépendances"}, + "success_criteria": {"en": "Success Criteria", "fr": "Critères de succès"}, + "estimated_complexity": {"en": "Estimated Complexity", "fr": "Complexité estimée"}, + "userMessage": {"en": "User Message", "fr": "Message utilisateur"}, + }, +) + +class TaskHandover(BaseModel, ModelMixin): + taskId: str = Field(description="Target task ID") + sourceTask: Optional[str] = Field(None, description="Source task ID") + inputDocuments: List[DocumentExchange] = Field(default_factory=list, description="Available input documents") + outputDocuments: List[DocumentExchange] = Field(default_factory=list, description="Produced output documents") + context: Dict[str, Any] = Field(default_factory=dict, description="Task context") + previousResults: List[str] = Field(default_factory=list, description="Previous result summaries") + improvements: List[str] = Field(default_factory=list, description="Improvement suggestions") + workflowSummary: Optional[str] = Field(None, description="Summarized workflow context") + messageHistory: List[str] = Field(default_factory=list, description="Key message summaries") + timestamp: float = Field(..., description="When the handover was created (UTC timestamp in seconds)") + handoverType: str = Field(default="task", description="Type of handover: task, phase, or workflow") +register_model_labels( + "TaskHandover", + {"en": "Task Handover", "fr": "Transfert de tâche"}, + { + "taskId": {"en": "Task ID", "fr": "ID de la tâche"}, + "sourceTask": {"en": "Source Task", "fr": "Tâche source"}, + "inputDocuments": {"en": "Input Documents", "fr": "Documents d'entrée"}, + "outputDocuments": {"en": "Output Documents", "fr": "Documents de sortie"}, + "context": {"en": "Context", "fr": "Contexte"}, + "previousResults": {"en": "Previous Results", "fr": "Résultats précédents"}, + "improvements": {"en": "Improvements", "fr": "Améliorations"}, + "workflowSummary": {"en": "Workflow Summary", "fr": "Résumé du workflow"}, + "messageHistory": {"en": "Message History", "fr": "Historique des messages"}, + "timestamp": {"en": "Timestamp", "fr": "Horodatage"}, + "handoverType": {"en": "Handover Type", "fr": "Type de transfert"}, + }, +) + +class TaskContext(BaseModel, ModelMixin): + task_step: TaskStep + workflow: Optional['ChatWorkflow'] = None + workflow_id: Optional[str] = None + available_documents: Optional[str] = "No documents available" + available_connections: Optional[list[str]] = Field(default_factory=list) + previous_results: Optional[list[str]] = Field(default_factory=list) + previous_handover: Optional[TaskHandover] = None + improvements: Optional[list[str]] = Field(default_factory=list) + retry_count: Optional[int] = 0 + previous_action_results: Optional[list] = Field(default_factory=list) + previous_review_result: Optional[dict] = None + is_regeneration: Optional[bool] = False + failure_patterns: Optional[list[str]] = Field(default_factory=list) + failed_actions: Optional[list] = Field(default_factory=list) + successful_actions: Optional[list] = Field(default_factory=list) + criteria_progress: Optional[dict] = None + + def getDocumentReferences(self) -> List[str]: + docs = [] + if self.previous_handover: + for doc_exchange in self.previous_handover.inputDocuments: + docs.extend(doc_exchange.documents) + return list(set(docs)) + + def addImprovement(self, improvement: str) -> None: + if improvement not in (self.improvements or []): + if self.improvements is None: + self.improvements = [] + self.improvements.append(improvement) + +class ReviewContext(BaseModel, ModelMixin): + task_step: TaskStep + task_actions: Optional[list] = Field(default_factory=list) + action_results: Optional[list] = Field(default_factory=list) + step_result: Optional[dict] = Field(default_factory=dict) + workflow_id: Optional[str] = None + previous_results: Optional[list[str]] = Field(default_factory=list) + +class ReviewResult(BaseModel, ModelMixin): + status: str + reason: Optional[str] = None + improvements: Optional[list[str]] = Field(default_factory=list) + quality_score: Optional[int] = 5 + missing_outputs: Optional[list[str]] = Field(default_factory=list) + met_criteria: Optional[list[str]] = Field(default_factory=list) + unmet_criteria: Optional[list[str]] = Field(default_factory=list) + confidence: Optional[float] = 0.5 + userMessage: Optional[str] = Field(None, description="User-friendly message in user's language") +register_model_labels( + "ReviewResult", + {"en": "Review Result", "fr": "Résultat de l'évaluation"}, + { + "status": {"en": "Status", "fr": "Statut"}, + "reason": {"en": "Reason", "fr": "Raison"}, + "improvements": {"en": "Improvements", "fr": "Améliorations"}, + "quality_score": {"en": "Quality Score", "fr": "Score de qualité"}, + "missing_outputs": {"en": "Missing Outputs", "fr": "Sorties manquantes"}, + "met_criteria": {"en": "Met Criteria", "fr": "Critères respectés"}, + "unmet_criteria": {"en": "Unmet Criteria", "fr": "Critères non respectés"}, + "confidence": {"en": "Confidence", "fr": "Confiance"}, + "userMessage": {"en": "User Message", "fr": "Message utilisateur"}, + }, +) + +class TaskPlan(BaseModel, ModelMixin): + overview: str + tasks: list[TaskStep] + userMessage: Optional[str] = Field(None, description="Overall user-friendly message for the task plan") +register_model_labels( + "TaskPlan", + {"en": "Task Plan", "fr": "Plan de tâches"}, + { + "overview": {"en": "Overview", "fr": "Aperçu"}, + "tasks": {"en": "Tasks", "fr": "Tâches"}, + "userMessage": {"en": "User Message", "fr": "Message utilisateur"}, + }, +) + +# Resolve forward references +TaskContext.update_forward_refs() + +class PromptPlaceholder(BaseModel, ModelMixin): + label: str + content: str + summaryAllowed: bool = Field(default=False, description="Whether host may summarize content before sending to AI") +register_model_labels( + "PromptPlaceholder", + {"en": "Prompt Placeholder", "fr": "Espace réservé d'invite"}, + { + "label": {"en": "Label", "fr": "Libellé"}, + "content": {"en": "Content", "fr": "Contenu"}, + "summaryAllowed": {"en": "Summary Allowed", "fr": "Résumé autorisé"}, + }, +) + +class PromptBundle(BaseModel, ModelMixin): + prompt: str + placeholders: List[PromptPlaceholder] = Field(default_factory=list) +register_model_labels( + "PromptBundle", {"en": "Prompt Bundle", "fr": "Lot d'invite"}, + { + "prompt": {"en": "Prompt", "fr": "Invite"}, + "placeholders": {"en": "Placeholders", "fr": "Espaces réservés"}, + }, +) diff --git a/modules/datamodels/datamodelExtraction.py b/modules/datamodels/datamodelExtraction.py index 46977b78..ff44aa19 100644 --- a/modules/datamodels/datamodelExtraction.py +++ b/modules/datamodels/datamodelExtraction.py @@ -12,7 +12,7 @@ class ContentPart(BaseModel): metadata: Dict[str, Any] = Field(default_factory=dict, description="Arbitrary metadata for the part") -class ExtractedContent(BaseModel): +class ContentExtracted(BaseModel): id: str = Field(description="Extraction id or source document id") parts: List[ContentPart] = Field(default_factory=list, description="List of extracted parts") summary: Optional[Dict[str, Any]] = Field(default=None, description="Optional extraction summary") diff --git a/modules/datamodels/datamodelFiles.py b/modules/datamodels/datamodelFiles.py index d561c79e..41cb0cb6 100644 --- a/modules/datamodels/datamodelFiles.py +++ b/modules/datamodels/datamodelFiles.py @@ -19,8 +19,6 @@ class FileItem(BaseModel, ModelMixin): def to_dict(self) -> Dict[str, Any]: return super().to_dict() - - register_model_labels( "FileItem", {"en": "File Item", "fr": "Élément de fichier"}, @@ -35,7 +33,6 @@ register_model_labels( }, ) - class FilePreview(BaseModel, ModelMixin): content: Union[str, bytes] = Field(description="File content (text or binary)") mimeType: str = Field(description="MIME type of the file") @@ -49,8 +46,6 @@ class FilePreview(BaseModel, ModelMixin): if isinstance(data.get("content"), bytes): data["content"] = base64.b64encode(data["content"]).decode("utf-8") return data - - register_model_labels( "FilePreview", {"en": "File Preview", "fr": "Aperçu du fichier"}, @@ -64,13 +59,10 @@ register_model_labels( }, ) - class FileData(BaseModel, ModelMixin): id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key") data: str = Field(description="File data content") base64Encoded: bool = Field(description="Whether the data is base64 encoded") - - register_model_labels( "FileData", {"en": "File Data", "fr": "Données de fichier"}, @@ -80,5 +72,3 @@ register_model_labels( "base64Encoded": {"en": "Base64 Encoded", "fr": "Encodé en Base64"}, }, ) - - diff --git a/modules/datamodels/datamodelNeutralizer.py b/modules/datamodels/datamodelNeutralizer.py index 475b1146..998cb17f 100644 --- a/modules/datamodels/datamodelNeutralizer.py +++ b/modules/datamodels/datamodelNeutralizer.py @@ -14,8 +14,6 @@ class DataNeutraliserConfig(BaseModel, ModelMixin): namesToParse: str = Field(default="", description="Multiline list of names to parse for neutralization", frontend_type="textarea", frontend_readonly=False, frontend_required=False) sharepointSourcePath: str = Field(default="", description="SharePoint path to read files for neutralization", frontend_type="text", frontend_readonly=False, frontend_required=False) sharepointTargetPath: str = Field(default="", description="SharePoint path to store neutralized files", frontend_type="text", frontend_readonly=False, frontend_required=False) - - register_model_labels( "DataNeutraliserConfig", {"en": "Data Neutralization Config", "fr": "Configuration de neutralisation des données"}, @@ -30,7 +28,6 @@ register_model_labels( }, ) - class DataNeutralizerAttributes(BaseModel, ModelMixin): id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the attribute mapping (used as UID in neutralized files)", frontend_type="text", frontend_readonly=True, frontend_required=False) mandateId: str = Field(description="ID of the mandate this attribute belongs to", frontend_type="text", frontend_readonly=True, frontend_required=True) @@ -38,8 +35,6 @@ class DataNeutralizerAttributes(BaseModel, ModelMixin): originalText: str = Field(description="Original text that was neutralized", frontend_type="text", frontend_readonly=True, frontend_required=True) fileId: Optional[str] = Field(default=None, description="ID of the file this attribute belongs to", frontend_type="text", frontend_readonly=True, frontend_required=False) patternType: str = Field(description="Type of pattern that matched (email, phone, name, etc.)", frontend_type="text", frontend_readonly=True, frontend_required=True) - - register_model_labels( "DataNeutralizerAttributes", {"en": "Neutralized Data Attribute", "fr": "Attribut de données neutralisées"}, diff --git a/modules/datamodels/datamodelSecurity.py b/modules/datamodels/datamodelSecurity.py index ff6a3f6f..400d2c56 100644 --- a/modules/datamodels/datamodelSecurity.py +++ b/modules/datamodels/datamodelSecurity.py @@ -13,7 +13,6 @@ class TokenStatus(str, Enum): ACTIVE = "active" REVOKED = "revoked" - class Token(BaseModel, ModelMixin): id: Optional[str] = None userId: str @@ -33,8 +32,6 @@ class Token(BaseModel, ModelMixin): class Config: use_enum_values = True - - register_model_labels( "Token", {"en": "Token", "fr": "Jeton"}, @@ -57,7 +54,6 @@ register_model_labels( }, ) - class AuthEvent(BaseModel, ModelMixin): id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the auth event", frontend_type="text", frontend_readonly=True, frontend_required=False) userId: str = Field(description="ID of the user this event belongs to", frontend_type="text", frontend_readonly=True, frontend_required=True) @@ -67,8 +63,6 @@ class AuthEvent(BaseModel, ModelMixin): userAgent: Optional[str] = Field(default=None, description="User agent string from the request", frontend_type="text", frontend_readonly=True, frontend_required=False) success: bool = Field(default=True, description="Whether the authentication event was successful", frontend_type="boolean", frontend_readonly=True, frontend_required=True) details: Optional[str] = Field(default=None, description="Additional details about the event", frontend_type="text", frontend_readonly=True, frontend_required=False) - - register_model_labels( "AuthEvent", {"en": "Authentication Event", "fr": "Événement d'authentification"}, diff --git a/modules/datamodels/datamodelTickets.py b/modules/datamodels/datamodelTickets.py index d11606c6..40478bc6 100644 --- a/modules/datamodels/datamodelTickets.py +++ b/modules/datamodels/datamodelTickets.py @@ -9,7 +9,6 @@ class TicketFieldAttribute(BaseModel): fieldName: str = Field(description="Human-readable field name") field: str = Field(description="Ticket field ID/key") - class TicketBase(ABC): @abstractmethod async def read_attributes(self) -> list[TicketFieldAttribute]: ... diff --git a/modules/datamodels/datamodelUam.py b/modules/datamodels/datamodelUam.py index 283ff882..8bd24d8c 100644 --- a/modules/datamodels/datamodelUam.py +++ b/modules/datamodels/datamodelUam.py @@ -13,20 +13,17 @@ class AuthAuthority(str, Enum): GOOGLE = "google" MSFT = "msft" - class UserPrivilege(str, Enum): SYSADMIN = "sysadmin" ADMIN = "admin" USER = "user" - class ConnectionStatus(str, Enum): ACTIVE = "active" EXPIRED = "expired" REVOKED = "revoked" PENDING = "pending" - class Mandate(BaseModel, ModelMixin): id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the mandate", frontend_type="text", frontend_readonly=True, frontend_required=False) name: str = Field(description="Name of the mandate", frontend_type="text", frontend_readonly=False, frontend_required=True) @@ -37,8 +34,6 @@ class Mandate(BaseModel, ModelMixin): {"value": "it", "label": {"en": "Italiano", "fr": "Italien"}}, ]) enabled: bool = Field(default=True, description="Indicates whether the mandate is enabled", frontend_type="checkbox", frontend_readonly=False, frontend_required=False) - - register_model_labels( "Mandate", {"en": "Mandate", "fr": "Mandat"}, @@ -50,7 +45,6 @@ register_model_labels( }, ) - class UserConnection(BaseModel, ModelMixin): id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the connection", frontend_type="text", frontend_readonly=True, frontend_required=False) userId: str = Field(description="ID of the user this connection belongs to", frontend_type="text", frontend_readonly=True, frontend_required=False) @@ -77,8 +71,6 @@ class UserConnection(BaseModel, ModelMixin): {"value": "none", "label": {"en": "None", "fr": "Aucun"}}, ]) tokenExpiresAt: Optional[float] = Field(None, description="When the current token expires (UTC timestamp in seconds)", frontend_type="timestamp", frontend_readonly=True, frontend_required=False) - - register_model_labels( "UserConnection", {"en": "User Connection", "fr": "Connexion utilisateur"}, @@ -98,7 +90,6 @@ register_model_labels( }, ) - class User(BaseModel, ModelMixin): id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the user", frontend_type="text", frontend_readonly=True, frontend_required=False) username: str = Field(description="Username for login", frontend_type="text", frontend_readonly=False, frontend_required=True) @@ -122,8 +113,6 @@ class User(BaseModel, ModelMixin): {"value": "msft", "label": {"en": "Microsoft", "fr": "Microsoft"}}, ]) mandateId: Optional[str] = Field(None, description="ID of the mandate this user belongs to", frontend_type="text", frontend_readonly=True, frontend_required=False) - - register_model_labels( "User", {"en": "User", "fr": "Utilisateur"}, @@ -140,15 +129,10 @@ register_model_labels( }, ) - class UserInDB(User): hashedPassword: Optional[str] = Field(None, description="Hash of the user password") - - register_model_labels( "UserInDB", {"en": "User Access", "fr": "Accès de l'utilisateur"}, {"hashedPassword": {"en": "Password hash", "fr": "Hachage de mot de passe"}}, ) - - diff --git a/modules/datamodels/datamodelUtils.py b/modules/datamodels/datamodelUtils.py index 82a888e7..ccd01c04 100644 --- a/modules/datamodels/datamodelUtils.py +++ b/modules/datamodels/datamodelUtils.py @@ -10,8 +10,6 @@ class Prompt(BaseModel, ModelMixin): mandateId: str = Field(description="ID of the mandate this prompt belongs to", frontend_type="text", frontend_readonly=True, frontend_required=False) content: str = Field(description="Content of the prompt", frontend_type="textarea", frontend_readonly=False, frontend_required=True) name: str = Field(description="Name of the prompt", frontend_type="text", frontend_readonly=False, frontend_required=True) - - register_model_labels( "Prompt", {"en": "Prompt", "fr": "Invite"}, diff --git a/modules/datamodels/datamodelVoice.py b/modules/datamodels/datamodelVoice.py index 3fc69cd8..274a507b 100644 --- a/modules/datamodels/datamodelVoice.py +++ b/modules/datamodels/datamodelVoice.py @@ -22,7 +22,6 @@ class VoiceSettings(BaseModel, ModelMixin): def to_dict(self) -> Dict[str, Any]: return super().to_dict() - register_model_labels( "VoiceSettings", {"en": "Voice Settings", "fr": "Paramètres vocaux"}, diff --git a/modules/datamodels/datamodelWeb.py b/modules/datamodels/datamodelWeb.py index 6ef5a8a3..bc1e03e3 100644 --- a/modules/datamodels/datamodelWeb.py +++ b/modules/datamodels/datamodelWeb.py @@ -1,10 +1,8 @@ """Web-related modules""" - -from abc import ABC, abstractmethod from pydantic import BaseModel, Field, HttpUrl from typing import List, Optional, Literal, Dict, Any from modules.shared.configuration import APP_CONFIG -from modules.datamodels.datamodelWorkflow import ActionDocument, ActionResult +from modules.datamodels.datamodelChat import ActionDocument, ActionResult WEB_SEARCH_MAX_QUERY_LENGTH: int = int(APP_CONFIG.get("Web_Search_MAX_QUERY_LENGTH", "400")) @@ -27,7 +25,6 @@ class WebResearchOptions(BaseModel): include_answer: Optional[bool] = Field(default=None, description="Include AI answer") include_raw_content: Optional[bool] = Field(default=None, description="Include raw content") - class WebResearchRequest(BaseModel): """Main web research request""" user_prompt: str = Field(min_length=1, max_length=WEB_SEARCH_MAX_QUERY_LENGTH, description="User's research question or prompt") @@ -35,20 +32,17 @@ class WebResearchRequest(BaseModel): max_results: int = Field(default=5, ge=1, le=WEB_SEARCH_MAX_RESULTS, description="Max search results") options: WebResearchOptions = Field(default_factory=WebResearchOptions, description="Advanced options") - class WebSearchResultItem(BaseModel): """Individual search result""" title: str url: HttpUrl raw_content: Optional[str] = Field(default=None, description="Raw HTML content") - class WebCrawlResultItem(BaseModel): """Individual crawl result""" url: HttpUrl content: str - class WebResearchDocumentData(BaseModel): """Complete web research results""" user_prompt: str @@ -60,21 +54,14 @@ class WebResearchDocumentData(BaseModel): individual_content: Optional[Dict[str, str]] = None # URL -> content mapping debug_info: Optional[Dict[str, Any]] = None - class WebResearchActionDocument(ActionDocument): documentData: WebResearchDocumentData - class WebResearchActionResult(ActionResult): documents: List[WebResearchActionDocument] = Field(default_factory=list) - -class WebResearchBase(ABC): - @abstractmethod - async def web_research(self, request: WebResearchRequest) -> WebResearchActionResult: ... - - # Legacy models for connector compatibility + class WebSearchDocumentData(BaseModel): """Search results document data""" query: str @@ -153,5 +140,3 @@ class WebScrapeResultItem(BaseModel): """Individual scrape result""" url: HttpUrl content: str - - diff --git a/modules/datamodels/datamodelWorkflow.py b/modules/datamodels/datamodelWorkflow.py deleted file mode 100644 index c1da40c4..00000000 --- a/modules/datamodels/datamodelWorkflow.py +++ /dev/null @@ -1,446 +0,0 @@ -"""Workflow-related base datamodels and step/task structures.""" - -from typing import List, Dict, Any, Optional -from pydantic import BaseModel, Field -from modules.shared.attributeUtils import register_model_labels, ModelMixin - - -class ActionDocument(BaseModel, ModelMixin): - """Clear document structure for action results""" - documentName: str = Field(description="Name of the document") - documentData: Any = Field(description="Content/data of the document") - mimeType: str = Field(description="MIME type of the document") -register_model_labels( - "ActionDocument", - {"en": "Action Document", "fr": "Document d'action"}, - { - "documentName": {"en": "Document Name", "fr": "Nom du document"}, - "documentData": {"en": "Document Data", "fr": "Données du document"}, - "mimeType": {"en": "MIME Type", "fr": "Type MIME"}, - }, -) - - -class ActionResult(BaseModel, ModelMixin): - """Clean action result with documents as primary output - - IMPORTANT: Action methods should NOT set resultLabel in their return value. - The resultLabel is managed by the action handler using the action's execResultLabel - from the action plan. This ensures consistent document routing throughout the workflow. - """ - - success: bool = Field(description="Whether execution succeeded") - error: Optional[str] = Field(None, description="Error message if failed") - documents: List[ActionDocument] = Field(default_factory=list, description="Document outputs") - resultLabel: Optional[str] = Field(None, description="Label for document routing (set by action handler, not by action methods)") - - @classmethod - def isSuccess(cls, documents: List[ActionDocument] = None) -> "ActionResult": - return cls(success=True, documents=documents or []) - - @classmethod - def isFailure(cls, error: str, documents: List[ActionDocument] = None) -> "ActionResult": - return cls(success=False, documents=documents or [], error=error) -register_model_labels( - "ActionResult", - {"en": "Action Result", "fr": "Résultat de l'action"}, - { - "success": {"en": "Success", "fr": "Succès"}, - "error": {"en": "Error", "fr": "Erreur"}, - "documents": {"en": "Documents", "fr": "Documents"}, - "resultLabel": {"en": "Result Label", "fr": "Étiquette du résultat"}, - }, -) - - -class ActionSelection(BaseModel, ModelMixin): - method: str = Field(description="Method to execute (e.g., web, document, ai)") - name: str = Field(description="Action name within the method (e.g., search, extract)") - - -register_model_labels( - "ActionSelection", - {"en": "Action Selection", "fr": "Sélection d'action"}, - { - "method": {"en": "Method", "fr": "Méthode"}, - "name": {"en": "Action Name", "fr": "Nom de l'action"}, - }, -) - - -class ActionParameters(BaseModel, ModelMixin): - parameters: Dict[str, Any] = Field(default_factory=dict, description="Parameters to execute the selected action") - - -register_model_labels( - "ActionParameters", - {"en": "Action Parameters", "fr": "Paramètres d'action"}, - { - "parameters": {"en": "Parameters", "fr": "Paramètres"}, - }, -) - - -class ObservationPreview(BaseModel, ModelMixin): - name: str = Field(description="Document name or URL label") - mime: str = Field(description="MIME type or kind") - snippet: str = Field(description="Short snippet or summary") - - -register_model_labels( - "ObservationPreview", - {"en": "Observation Preview", "fr": "Aperçu d'observation"}, - { - "name": {"en": "Name", "fr": "Nom"}, - "mime": {"en": "MIME", "fr": "MIME"}, - "snippet": {"en": "Snippet", "fr": "Extrait"}, - }, -) - - -class Observation(BaseModel, ModelMixin): - success: bool = Field(description="Action execution success flag") - resultLabel: str = Field(description="Deterministic label for produced documents") - documentsCount: int = Field(description="Number of produced documents") - previews: List[ObservationPreview] = Field(default_factory=list, description="Compact previews of outputs") - notes: List[str] = Field(default_factory=list, description="Short notes or key facts") - - -register_model_labels( - "Observation", - {"en": "Observation", "fr": "Observation"}, - { - "success": {"en": "Success", "fr": "Succès"}, - "resultLabel": {"en": "Result Label", "fr": "Étiquette du résultat"}, - "documentsCount": {"en": "Documents Count", "fr": "Nombre de documents"}, - "previews": {"en": "Previews", "fr": "Aperçus"}, - "notes": {"en": "Notes", "fr": "Notes"}, - }, -) - - -class TaskStatus(str): - PENDING = "pending" - RUNNING = "running" - COMPLETED = "completed" - FAILED = "failed" - CANCELLED = "cancelled" - - -register_model_labels( - "TaskStatus", - {"en": "Task Status", "fr": "Statut de la tâche"}, - { - "PENDING": {"en": "Pending", "fr": "En attente"}, - "RUNNING": {"en": "Running", "fr": "En cours"}, - "COMPLETED": {"en": "Completed", "fr": "Terminé"}, - "FAILED": {"en": "Failed", "fr": "Échec"}, - "CANCELLED": {"en": "Cancelled", "fr": "Annulé"}, - }, -) - - -class DocumentExchange(BaseModel, ModelMixin): - documentsLabel: str = Field(description="Label for the set of documents") - documents: List[str] = Field(default_factory=list, description="List of document references") - - -register_model_labels( - "DocumentExchange", - {"en": "Document Exchange", "fr": "Échange de documents"}, - { - "documentsLabel": {"en": "Documents Label", "fr": "Label des documents"}, - "documents": {"en": "Documents", "fr": "Documents"}, - }, -) - - -class TaskAction(BaseModel, ModelMixin): - id: str = Field(..., description="Action ID") - execMethod: str = Field(..., description="Method to execute") - execAction: str = Field(..., description="Action to perform") - execParameters: Dict[str, Any] = Field(default_factory=dict, description="Action parameters") - execResultLabel: Optional[str] = Field(None, description="Label for the set of result documents") - expectedDocumentFormats: Optional[List[Dict[str, str]]] = Field(None, description="Expected document formats (optional)") - userMessage: Optional[str] = Field(None, description="User-friendly message in user's language") - status: TaskStatus = Field(default=TaskStatus.PENDING, description="Action status") - error: Optional[str] = Field(None, description="Error message if action failed") - retryCount: int = Field(default=0, description="Number of retries attempted") - retryMax: int = Field(default=3, description="Maximum number of retries") - processingTime: Optional[float] = Field(None, description="Processing time in seconds") - timestamp: float = Field(..., description="When the action was executed (UTC timestamp in seconds)") - result: Optional[str] = Field(None, description="Result of the action") - - def setSuccess(self, result: str = None) -> None: - """Set the action as successful with optional result""" - self.status = TaskStatus.COMPLETED - self.error = None - if result is not None: - self.result = result - - def setError(self, error_message: str) -> None: - """Set the action as failed with error message""" - self.status = TaskStatus.FAILED - self.error = error_message - - -register_model_labels( - "TaskAction", - {"en": "Task Action", "fr": "Action de tâche"}, - { - "id": {"en": "Action ID", "fr": "ID de l'action"}, - "execMethod": {"en": "Method", "fr": "Méthode"}, - "execAction": {"en": "Action", "fr": "Action"}, - "execParameters": {"en": "Parameters", "fr": "Paramètres"}, - "execResultLabel": {"en": "Result Label", "fr": "Label du résultat"}, - "expectedDocumentFormats": {"en": "Expected Document Formats", "fr": "Formats de documents attendus"}, - "userMessage": {"en": "User Message", "fr": "Message utilisateur"}, - "status": {"en": "Status", "fr": "Statut"}, - "error": {"en": "Error", "fr": "Erreur"}, - "retryCount": {"en": "Retry Count", "fr": "Nombre de tentatives"}, - "retryMax": {"en": "Max Retries", "fr": "Tentatives max"}, - "processingTime": {"en": "Processing Time", "fr": "Temps de traitement"}, - "timestamp": {"en": "Timestamp", "fr": "Horodatage"}, - "result": {"en": "Result", "fr": "Résultat"}, - }, -) - - -class TaskResult(BaseModel, ModelMixin): - taskId: str = Field(..., description="Task ID") - status: TaskStatus = Field(default=TaskStatus.PENDING, description="Task status") - success: bool = Field(..., description="Whether the task was successful") - feedback: Optional[str] = Field(None, description="Task feedback message") - error: Optional[str] = Field(None, description="Error message if task failed") - - -register_model_labels( - "TaskResult", - {"en": "Task Result", "fr": "Résultat de tâche"}, - { - "taskId": {"en": "Task ID", "fr": "ID de la tâche"}, - "status": {"en": "Status", "fr": "Statut"}, - "success": {"en": "Success", "fr": "Succès"}, - "feedback": {"en": "Feedback", "fr": "Retour"}, - "error": {"en": "Error", "fr": "Erreur"}, - }, -) - - -class TaskItem(BaseModel, ModelMixin): - id: str = Field(..., description="Task ID") - workflowId: str = Field(..., description="Workflow ID") - userInput: str = Field(..., description="User input that triggered the task") - status: TaskStatus = Field(default=TaskStatus.PENDING, description="Task status") - error: Optional[str] = Field(None, description="Error message if task failed") - startedAt: Optional[float] = Field(None, description="When the task started (UTC timestamp in seconds)") - finishedAt: Optional[float] = Field(None, description="When the task finished (UTC timestamp in seconds)") - actionList: List[TaskAction] = Field(default_factory=list, description="List of actions to execute") - retryCount: int = Field(default=0, description="Number of retries attempted") - retryMax: int = Field(default=3, description="Maximum number of retries") - rollbackOnFailure: bool = Field(default=True, description="Whether to rollback on failure") - dependencies: List[str] = Field(default_factory=list, description="List of task IDs this task depends on") - feedback: Optional[str] = Field(None, description="Task feedback message") - processingTime: Optional[float] = Field(None, description="Total processing time in seconds") - resultLabels: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Map of result labels to their values") - - -register_model_labels( - "TaskItem", - {"en": "Task", "fr": "Tâche"}, - { - "id": {"en": "Task ID", "fr": "ID de la tâche"}, - "workflowId": {"en": "Workflow ID", "fr": "ID du workflow"}, - "userInput": {"en": "User Input", "fr": "Entrée utilisateur"}, - "status": {"en": "Status", "fr": "Statut"}, - "error": {"en": "Error", "fr": "Erreur"}, - "startedAt": {"en": "Started At", "fr": "Démarré à"}, - "finishedAt": {"en": "Finished At", "fr": "Terminé à"}, - "actionList": {"en": "Actions", "fr": "Actions"}, - "retryCount": {"en": "Retry Count", "fr": "Nombre de tentatives"}, - "retryMax": {"en": "Max Retries", "fr": "Tentatives max"}, - "processingTime": {"en": "Processing Time", "fr": "Temps de traitement"}, - }, -) - - -class TaskStep(BaseModel, ModelMixin): - id: str - objective: str - dependencies: Optional[list[str]] = Field(default_factory=list) - success_criteria: Optional[list[str]] = Field(default_factory=list) - estimated_complexity: Optional[str] = None - userMessage: Optional[str] = Field(None, description="User-friendly message in user's language") - - -register_model_labels( - "TaskStep", - {"en": "Task Step", "fr": "Étape de tâche"}, - { - "id": {"en": "ID", "fr": "ID"}, - "objective": {"en": "Objective", "fr": "Objectif"}, - "dependencies": {"en": "Dependencies", "fr": "Dépendances"}, - "success_criteria": {"en": "Success Criteria", "fr": "Critères de succès"}, - "estimated_complexity": {"en": "Estimated Complexity", "fr": "Complexité estimée"}, - "userMessage": {"en": "User Message", "fr": "Message utilisateur"}, - }, -) - - -class TaskHandover(BaseModel, ModelMixin): - taskId: str = Field(description="Target task ID") - sourceTask: Optional[str] = Field(None, description="Source task ID") - inputDocuments: List[DocumentExchange] = Field(default_factory=list, description="Available input documents") - outputDocuments: List[DocumentExchange] = Field(default_factory=list, description="Produced output documents") - context: Dict[str, Any] = Field(default_factory=dict, description="Task context") - previousResults: List[str] = Field(default_factory=list, description="Previous result summaries") - improvements: List[str] = Field(default_factory=list, description="Improvement suggestions") - workflowSummary: Optional[str] = Field(None, description="Summarized workflow context") - messageHistory: List[str] = Field(default_factory=list, description="Key message summaries") - timestamp: float = Field(..., description="When the handover was created (UTC timestamp in seconds)") - handoverType: str = Field(default="task", description="Type of handover: task, phase, or workflow") - - -register_model_labels( - "TaskHandover", - {"en": "Task Handover", "fr": "Transfert de tâche"}, - { - "taskId": {"en": "Task ID", "fr": "ID de la tâche"}, - "sourceTask": {"en": "Source Task", "fr": "Tâche source"}, - "inputDocuments": {"en": "Input Documents", "fr": "Documents d'entrée"}, - "outputDocuments": {"en": "Output Documents", "fr": "Documents de sortie"}, - "context": {"en": "Context", "fr": "Contexte"}, - "previousResults": {"en": "Previous Results", "fr": "Résultats précédents"}, - "improvements": {"en": "Improvements", "fr": "Améliorations"}, - "workflowSummary": {"en": "Workflow Summary", "fr": "Résumé du workflow"}, - "messageHistory": {"en": "Message History", "fr": "Historique des messages"}, - "timestamp": {"en": "Timestamp", "fr": "Horodatage"}, - "handoverType": {"en": "Handover Type", "fr": "Type de transfert"}, - }, -) - - -class TaskContext(BaseModel, ModelMixin): - task_step: TaskStep - workflow: Optional['ChatWorkflow'] = None - workflow_id: Optional[str] = None - available_documents: Optional[str] = "No documents available" - available_connections: Optional[list[str]] = Field(default_factory=list) - previous_results: Optional[list[str]] = Field(default_factory=list) - previous_handover: Optional[TaskHandover] = None - improvements: Optional[list[str]] = Field(default_factory=list) - retry_count: Optional[int] = 0 - previous_action_results: Optional[list] = Field(default_factory=list) - previous_review_result: Optional[dict] = None - is_regeneration: Optional[bool] = False - failure_patterns: Optional[list[str]] = Field(default_factory=list) - failed_actions: Optional[list] = Field(default_factory=list) - successful_actions: Optional[list] = Field(default_factory=list) - criteria_progress: Optional[dict] = None - - def getDocumentReferences(self) -> List[str]: - docs = [] - if self.previous_handover: - for doc_exchange in self.previous_handover.inputDocuments: - docs.extend(doc_exchange.documents) - return list(set(docs)) - - def addImprovement(self, improvement: str) -> None: - if improvement not in (self.improvements or []): - if self.improvements is None: - self.improvements = [] - self.improvements.append(improvement) - - -class ReviewContext(BaseModel, ModelMixin): - task_step: TaskStep - task_actions: Optional[list] = Field(default_factory=list) - action_results: Optional[list] = Field(default_factory=list) - step_result: Optional[dict] = Field(default_factory=dict) - workflow_id: Optional[str] = None - previous_results: Optional[list[str]] = Field(default_factory=list) - - -class ReviewResult(BaseModel, ModelMixin): - status: str - reason: Optional[str] = None - improvements: Optional[list[str]] = Field(default_factory=list) - quality_score: Optional[int] = 5 - missing_outputs: Optional[list[str]] = Field(default_factory=list) - met_criteria: Optional[list[str]] = Field(default_factory=list) - unmet_criteria: Optional[list[str]] = Field(default_factory=list) - confidence: Optional[float] = 0.5 - userMessage: Optional[str] = Field(None, description="User-friendly message in user's language") - - -register_model_labels( - "ReviewResult", - {"en": "Review Result", "fr": "Résultat de l'évaluation"}, - { - "status": {"en": "Status", "fr": "Statut"}, - "reason": {"en": "Reason", "fr": "Raison"}, - "improvements": {"en": "Improvements", "fr": "Améliorations"}, - "quality_score": {"en": "Quality Score", "fr": "Score de qualité"}, - "missing_outputs": {"en": "Missing Outputs", "fr": "Sorties manquantes"}, - "met_criteria": {"en": "Met Criteria", "fr": "Critères respectés"}, - "unmet_criteria": {"en": "Unmet Criteria", "fr": "Critères non respectés"}, - "confidence": {"en": "Confidence", "fr": "Confiance"}, - "userMessage": {"en": "User Message", "fr": "Message utilisateur"}, - }, -) - - -class TaskPlan(BaseModel, ModelMixin): - overview: str - tasks: list[TaskStep] - userMessage: Optional[str] = Field(None, description="Overall user-friendly message for the task plan") - - -register_model_labels( - "TaskPlan", - {"en": "Task Plan", "fr": "Plan de tâches"}, - { - "overview": {"en": "Overview", "fr": "Aperçu"}, - "tasks": {"en": "Tasks", "fr": "Tâches"}, - "userMessage": {"en": "User Message", "fr": "Message utilisateur"}, - }, -) - -# Resolve forward references -from modules.datamodels.datamodelChat import ChatWorkflow -TaskContext.update_forward_refs() - -# ----------------------------- -# Prompt helper datamodels -# ----------------------------- - -class PromptPlaceholder(BaseModel, ModelMixin): - label: str - content: str - summaryAllowed: bool = Field(default=False, description="Whether host may summarize content before sending to AI") - - -register_model_labels( - "PromptPlaceholder", - {"en": "Prompt Placeholder", "fr": "Espace réservé d'invite"}, - { - "label": {"en": "Label", "fr": "Libellé"}, - "content": {"en": "Content", "fr": "Contenu"}, - "summaryAllowed": {"en": "Summary Allowed", "fr": "Résumé autorisé"}, - }, -) - - -class PromptBundle(BaseModel, ModelMixin): - prompt: str - placeholders: List[PromptPlaceholder] = Field(default_factory=list) - - -register_model_labels( - "PromptBundle", - {"en": "Prompt Bundle", "fr": "Lot d'invite"}, - { - "prompt": {"en": "Prompt", "fr": "Invite"}, - "placeholders": {"en": "Placeholders", "fr": "Espaces réservés"}, - }, -) diff --git a/modules/interfaces/interfaceAiObjects.py b/modules/interfaces/interfaceAiObjects.py index 4be38930..3e9c744d 100644 --- a/modules/interfaces/interfaceAiObjects.py +++ b/modules/interfaces/interfaceAiObjects.py @@ -27,7 +27,7 @@ from modules.datamodels.datamodelWeb import ( WebSearchRequest, WebCrawlRequest, ) -from modules.datamodels.datamodelWorkflow import ActionDocument +from modules.datamodels.datamodelChat import ActionDocument # Comprehensive model registry with capability tags and function mapping diff --git a/modules/interfaces/interfaceDbChatObjects.py b/modules/interfaces/interfaceDbChatObjects.py index 88af1d81..48ed60d6 100644 --- a/modules/interfaces/interfaceDbChatObjects.py +++ b/modules/interfaces/interfaceDbChatObjects.py @@ -12,8 +12,8 @@ from typing import Dict, Any, List, Optional, Union, get_origin, get_args import asyncio from modules.interfaces.interfaceDbChatAccess import ChatAccess -from modules.datamodels.datamodelWorkflow import ( - TaskAction, +from modules.datamodels.datamodelChat import ( + ActionItem, TaskResult, TaskItem, TaskStatus, diff --git a/modules/services/serviceAi/mainServiceAi.py b/modules/services/serviceAi/mainServiceAi.py index 19e2e1f0..168733c3 100644 --- a/modules/services/serviceAi/mainServiceAi.py +++ b/modules/services/serviceAi/mainServiceAi.py @@ -1,6 +1,6 @@ import logging from typing import Dict, Any, List, Optional, Tuple, Union -from modules.datamodels.datamodelWorkflow import PromptPlaceholder +from modules.datamodels.datamodelChat import PromptPlaceholder from modules.datamodels.datamodelChat import ChatDocument from modules.services.serviceExtraction.mainServiceExtraction import ExtractionService @@ -909,7 +909,7 @@ class AiService: logger.info(f"Extraction completed: {len(extracted_content)} documents") - # Build context from list of ExtractedContent + # Build context from list of extracted content if isinstance(extracted_content, list): context_parts = [] chunk_count = 0 diff --git a/modules/services/serviceExtraction/mainServiceExtraction.py b/modules/services/serviceExtraction/mainServiceExtraction.py index 156f21b7..6d313463 100644 --- a/modules/services/serviceExtraction/mainServiceExtraction.py +++ b/modules/services/serviceExtraction/mainServiceExtraction.py @@ -4,7 +4,7 @@ import logging from .subRegistry import ExtractorRegistry, ChunkerRegistry from .subPipeline import runExtraction, poolAndLimit, applyAiIfRequested -from modules.datamodels.datamodelExtraction import ExtractedContent, ContentPart, MergeStrategy +from modules.datamodels.datamodelExtraction import ContentExtracted, ContentPart, MergeStrategy from modules.datamodels.datamodelChat import ChatDocument @@ -17,7 +17,7 @@ class ExtractionService: self._extractorRegistry = ExtractorRegistry() self._chunkerRegistry = ChunkerRegistry() - def extractContent(self, documents: List[ChatDocument], options: Dict[str, Any]) -> List[ExtractedContent]: + def extractContent(self, documents: List[ChatDocument], options: Dict[str, Any]) -> List[ContentExtracted]: """ Extract content from a list of ChatDocument objects. @@ -26,9 +26,9 @@ class ExtractionService: options: Extraction options including maxSize, chunkAllowed, mergeStrategy, etc. Returns: - List of ExtractedContent objects, one per input document + List of ContentExtracted objects, one per input document """ - results: List[ExtractedContent] = [] + results: List[ContentExtracted] = [] # Lazy import to avoid circular deps and heavy init at module import from modules.interfaces.interfaceDbComponentObjects import getInterface @@ -90,20 +90,20 @@ class ExtractionService: def mergeAiResults( self, - extractedContent: List[ExtractedContent], + extractedContent: List[ContentExtracted], aiResults: List[str], strategy: MergeStrategy - ) -> ExtractedContent: + ) -> ContentExtracted: """ - Merge AI results from chunked content back into a single ExtractedContent. + Merge AI results from chunked content back into a single ContentExtracted. Args: - extractedContent: List of ExtractedContent objects that were processed + extractedContent: List of ContentExtracted objects that were processed aiResults: List of AI response strings, one per chunk strategy: Merge strategy configuration (dict or MergeStrategy object) Returns: - Single ExtractedContent with merged AI results + Single ContentExtracted with merged AI results """ logger.debug(f"=== MERGING AI RESULTS ===") logger.debug(f"Extracted content: {len(extractedContent)} documents") @@ -150,8 +150,8 @@ class ExtractionService: # Default to concatenate mergedParts = self._mergeConcatenate(allParts, aiResultParts, mergeStrategy) - # Create final ExtractedContent - mergedContent = ExtractedContent( + # Create final ContentExtracted + mergedContent = ContentExtracted( id=f"merged_{uuid.uuid4()}", parts=mergedParts ) diff --git a/modules/services/serviceExtraction/subPipeline.py b/modules/services/serviceExtraction/subPipeline.py index 1c1bfc85..1dda4c0e 100644 --- a/modules/services/serviceExtraction/subPipeline.py +++ b/modules/services/serviceExtraction/subPipeline.py @@ -2,7 +2,7 @@ from typing import Any, Dict, List import logging import os -from modules.datamodels.datamodelExtraction import ExtractedContent, ContentPart +from modules.datamodels.datamodelExtraction import ContentExtracted, ContentPart from .subUtils import makeId from .subRegistry import ExtractorRegistry, ChunkerRegistry from .merging.text_merger import TextMerger @@ -55,7 +55,7 @@ def _mergeParts(parts: List[ContentPart], mergeStrategy: Dict[str, Any]) -> List return merged_parts -def runExtraction(extractorRegistry: ExtractorRegistry, chunkerRegistry: ChunkerRegistry, documentBytes: bytes, fileName: str, mimeType: str, options: Dict[str, Any]) -> ExtractedContent: +def runExtraction(extractorRegistry: ExtractorRegistry, chunkerRegistry: ChunkerRegistry, documentBytes: bytes, fileName: str, mimeType: str, options: Dict[str, Any]) -> ContentExtracted: extractor = extractorRegistry.resolve(mimeType, fileName) if extractor is None: # fallback: single binary part @@ -68,7 +68,7 @@ def runExtraction(extractorRegistry: ExtractorRegistry, chunkerRegistry: Chunker data="", metadata={"warning": "No extractor registered"} ) - return ExtractedContent(id=makeId(), parts=[part]) + return ContentExtracted(id=makeId(), parts=[part]) parts = extractor.extract(documentBytes, {"fileName": fileName, "mimeType": mimeType, "options": options}) @@ -119,7 +119,7 @@ def runExtraction(extractorRegistry: ExtractorRegistry, chunkerRegistry: Chunker except Exception as _e: logger.debug(f"Debug dump skipped: {_e}") - return ExtractedContent(id=makeId(), parts=parts) + return ContentExtracted(id=makeId(), parts=parts) def poolAndLimit(parts: List[ContentPart], chunkerRegistry: ChunkerRegistry, options: Dict[str, Any]) -> List[ContentPart]: @@ -268,7 +268,7 @@ def _applySizeLimit(parts: List[ContentPart], maxSize: int) -> List[ContentPart] return kept -def applyAiIfRequested(extracted: ExtractedContent, options: Dict[str, Any]) -> ExtractedContent: +def applyAiIfRequested(extracted: ContentExtracted, options: Dict[str, Any]) -> ContentExtracted: """ Apply AI processing if requested in options. This is a placeholder for actual AI integration. diff --git a/modules/services/serviceWorkflow/mainServiceWorkflow.py b/modules/services/serviceWorkflow/mainServiceWorkflow.py index a3b402dd..180779b5 100644 --- a/modules/services/serviceWorkflow/mainServiceWorkflow.py +++ b/modules/services/serviceWorkflow/mainServiceWorkflow.py @@ -3,7 +3,7 @@ import uuid from typing import Dict, Any, List, Optional from modules.datamodels.datamodelUam import User, UserConnection from modules.datamodels.datamodelChat import ChatDocument, ChatMessage -from modules.datamodels.datamodelChat import ExtractedContent +from modules.datamodels.datamodelChat import ChatContentExtracted from modules.services.serviceExtraction.mainServiceExtraction import ExtractionService from modules.services.serviceGeneration.subDocumentUtility import getFileExtension, getMimeTypeFromExtension, detectContentTypeFromData from modules.shared.timezoneUtils import get_utc_timestamp diff --git a/modules/workflows/methods/methodAi.py b/modules/workflows/methods/methodAi.py index 691298dd..9e0d8954 100644 --- a/modules/workflows/methods/methodAi.py +++ b/modules/workflows/methods/methodAi.py @@ -8,7 +8,7 @@ from typing import Dict, Any, List, Optional from datetime import datetime, UTC from modules.workflows.methods.methodBase import MethodBase, action -from modules.datamodels.datamodelWorkflow import ActionResult +from modules.datamodels.datamodelChat import ActionResult from modules.datamodels.datamodelAi import AiCallOptions, OperationType, Priority from modules.datamodels.datamodelChat import ChatDocument from modules.datamodels.datamodelWeb import WebResearchRequest, WebResearchOptions @@ -204,7 +204,7 @@ For large datasets, set "continue": true to indicate more data is coming, and we # Parse JSON response from AI with streaming support import json import re - from modules.datamodels.datamodelWorkflow import ActionDocument + from modules.datamodels.datamodelChat import ActionDocument action_documents = [] all_data_chunks = [] # Store all data chunks for merging diff --git a/modules/workflows/methods/methodDocument.py b/modules/workflows/methods/methodDocument.py index 95531406..96441314 100644 --- a/modules/workflows/methods/methodDocument.py +++ b/modules/workflows/methods/methodDocument.py @@ -9,7 +9,7 @@ from typing import Dict, Any, List, Optional from datetime import datetime, UTC from modules.workflows.methods.methodBase import MethodBase, action -from modules.datamodels.datamodelWorkflow import ActionResult, ActionDocument +from modules.datamodels.datamodelChat import ActionResult, ActionDocument from modules.datamodels.datamodelChat import ChatDocument from modules.datamodels.datamodelAi import AiCallOptions, OperationType, Priority @@ -136,7 +136,7 @@ class MethodDocument(MethodBase): action_documents = [] for i, chatDocument in enumerate(chatDocuments): - # Extract text content from this document using new ExtractedContent structure + # Extract text content from this document using new extracted content structure text_content = "" try: ec = all_extracted_content[i] if i < len(all_extracted_content) else None diff --git a/modules/workflows/methods/methodOutlook.py b/modules/workflows/methods/methodOutlook.py index 2593b9cd..1997e75a 100644 --- a/modules/workflows/methods/methodOutlook.py +++ b/modules/workflows/methods/methodOutlook.py @@ -12,7 +12,7 @@ import uuid import requests from modules.workflows.methods.methodBase import MethodBase, action -from modules.datamodels.datamodelWorkflow import ActionResult, ActionDocument +from modules.datamodels.datamodelChat import ActionResult, ActionDocument from modules.datamodels.datamodelAi import AiCallOptions, OperationType, Priority, AiCallRequest from modules.datamodels.datamodelChat import ChatDocument from modules.datamodels.datamodelUam import ConnectionStatus diff --git a/modules/workflows/methods/methodSharepoint.py b/modules/workflows/methods/methodSharepoint.py index c58cf89d..f29a0ad3 100644 --- a/modules/workflows/methods/methodSharepoint.py +++ b/modules/workflows/methods/methodSharepoint.py @@ -14,7 +14,7 @@ import aiohttp import asyncio from modules.workflows.methods.methodBase import MethodBase, action -from modules.datamodels.datamodelWorkflow import ActionResult, ActionDocument +from modules.datamodels.datamodelChat import ActionResult, ActionDocument logger = logging.getLogger(__name__) diff --git a/modules/workflows/processing/core/actionExecutor.py b/modules/workflows/processing/core/actionExecutor.py index 7ac012f3..95a41606 100644 --- a/modules/workflows/processing/core/actionExecutor.py +++ b/modules/workflows/processing/core/actionExecutor.py @@ -3,7 +3,7 @@ import logging from typing import Dict, Any, List -from modules.datamodels.datamodelWorkflow import ActionResult, TaskAction, TaskStep +from modules.datamodels.datamodelChat import ActionResult, ActionItem, TaskStep from modules.datamodels.datamodelChat import ChatWorkflow from modules.workflows.processing.shared.methodDiscovery import methods @@ -65,7 +65,7 @@ class ActionExecutor: logger.error(f"Error executing compound action {compoundActionName}: {str(e)}") raise - async def executeSingleAction(self, action: TaskAction, workflow: ChatWorkflow, taskStep: TaskStep, + async def executeSingleAction(self, action: ActionItem, workflow: ChatWorkflow, taskStep: TaskStep, taskIndex: int = None, actionIndex: int = None, totalActions: int = None) -> ActionResult: """Execute a single action and return ActionResult with enhanced document processing""" try: @@ -199,7 +199,7 @@ class ActionExecutor: # Join all document results with separators return "\n\n---\n\n".join(resultParts) if resultParts else "" - async def _createActionCompletionMessage(self, action: TaskAction, result: ActionResult, workflow: ChatWorkflow, + async def _createActionCompletionMessage(self, action: ActionItem, result: ActionResult, workflow: ChatWorkflow, taskStep: TaskStep, taskIndex: int, actionIndex: int, totalActions: int): """Create action completion message with documents (generic)""" try: diff --git a/modules/workflows/processing/core/messageCreator.py b/modules/workflows/processing/core/messageCreator.py index ce4cabdf..ccd718d0 100644 --- a/modules/workflows/processing/core/messageCreator.py +++ b/modules/workflows/processing/core/messageCreator.py @@ -3,7 +3,7 @@ import logging from typing import Dict, Any, Optional, List -from modules.datamodels.datamodelWorkflow import TaskPlan, TaskStep, ActionResult, ReviewResult +from modules.datamodels.datamodelChat import TaskPlan, TaskStep, ActionResult, ReviewResult from modules.datamodels.datamodelChat import ChatWorkflow logger = logging.getLogger(__name__) diff --git a/modules/workflows/processing/core/taskPlanner.py b/modules/workflows/processing/core/taskPlanner.py index 237a3711..d8fd11a8 100644 --- a/modules/workflows/processing/core/taskPlanner.py +++ b/modules/workflows/processing/core/taskPlanner.py @@ -4,7 +4,7 @@ import json import logging from typing import Dict, Any -from modules.datamodels.datamodelWorkflow import TaskStep, TaskContext, TaskPlan +from modules.datamodels.datamodelChat import TaskStep, TaskContext, TaskPlan from modules.datamodels.datamodelAi import AiCallOptions, OperationType, ProcessingMode, Priority from modules.workflows.processing.shared.promptGenerationTaskplan import ( createTaskPlanningPromptTemplate diff --git a/modules/workflows/processing/modes/modeActionplan.py b/modules/workflows/processing/modes/modeActionplan.py index 547c266b..fdee8ff7 100644 --- a/modules/workflows/processing/modes/modeActionplan.py +++ b/modules/workflows/processing/modes/modeActionplan.py @@ -5,8 +5,8 @@ import json import logging import uuid from typing import List, Dict, Any -from modules.datamodels.datamodelWorkflow import ( - TaskStep, TaskContext, TaskResult, TaskAction, TaskStatus, +from modules.datamodels.datamodelChat import ( + TaskStep, TaskContext, TaskResult, ActionItem, TaskStatus, ActionResult, ReviewResult, ReviewContext ) from modules.datamodels.datamodelChat import ChatWorkflow @@ -26,8 +26,8 @@ class ActionplanMode(BaseMode): def __init__(self, services, workflow): super().__init__(services, workflow) - async def generateTaskActions(self, taskStep: TaskStep, workflow: ChatWorkflow, - previousResults: List = None, enhancedContext: TaskContext = None) -> List[TaskAction]: + 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 @@ -176,7 +176,7 @@ class ActionplanMode(BaseMode): logger.error("Generated actions failed validation") raise Exception("AI-generated actions failed validation - AI is required for action generation") - # Convert to TaskAction objects + # Convert to ActionItem objects taskActions = [] for i, a in enumerate(actions): if not isinstance(a, dict): @@ -193,7 +193,7 @@ class ActionplanMode(BaseMode): # Old separate format: method + action fields method_name = a.get('method', 'unknown') - taskAction = self._createTaskAction({ + taskAction = self._createActionItem({ "execMethod": method_name, "execAction": action_name, "execParameters": a.get('parameters', {}), @@ -207,7 +207,7 @@ class ActionplanMode(BaseMode): if taskAction: taskActions.append(taskAction) else: - logger.warning(f"Skipping invalid action {i+1}: failed to create TaskAction") + logger.warning(f"Skipping invalid action {i+1}: failed to create ActionItem") validActions = [ta for ta in taskActions if ta] @@ -216,7 +216,7 @@ class ActionplanMode(BaseMode): return validActions except Exception as e: - logger.error(f"Error in generateTaskActions: {str(e)}") + logger.error(f"Error in generateActionItems: {str(e)}") return [] @@ -250,7 +250,7 @@ class ActionplanMode(BaseMode): if retryContext: retryContext.retry_count = attempt + 1 - actions = await self.generateTaskActions(taskStep, workflow, + actions = await self.generateActionItems(taskStep, workflow, previousResults=retryContext.previous_results, enhancedContext=retryContext) @@ -415,7 +415,7 @@ class ActionplanMode(BaseMode): error="Task failed after all retries." ) - async def _reviewTaskCompletion(self, taskStep: TaskStep, taskActions: List[TaskAction], + async def _reviewTaskCompletion(self, taskStep: TaskStep, taskActions: List[ActionItem], actionResults: List[ActionResult], workflow: ChatWorkflow) -> ReviewResult: """Review task completion and determine success/failure/retry""" try: @@ -562,7 +562,7 @@ class ActionplanMode(BaseMode): quality_score=0 ) - def _createTaskAction(self, actionData: Dict[str, Any]) -> TaskAction: + def _createActionItem(self, actionData: Dict[str, Any]) -> ActionItem: """Creates a new task action""" try: # Ensure ID is present @@ -584,14 +584,14 @@ class ActionplanMode(BaseMode): if "execParameters" not in actionData: actionData["execParameters"] = {} - # Use generic field separation based on TaskAction model - simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(TaskAction, actionData) + # Use generic field separation based on ActionItem model + simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(ActionItem, actionData) # Create action in database - createdAction = self.services.interfaceDbChat.db.recordCreate(TaskAction, simpleFields) + createdAction = self.services.interfaceDbChat.db.recordCreate(ActionItem, simpleFields) - # Convert to TaskAction model - return TaskAction( + # Convert to ActionItem model + return ActionItem( id=createdAction["id"], execMethod=createdAction["execMethod"], execAction=createdAction["execAction"], @@ -704,7 +704,7 @@ class ActionplanMode(BaseMode): except Exception as e: logger.error(f"Error setting workflow totals: {str(e)}") - def _createTaskAction(self, actionData: Dict[str, Any]) -> TaskAction: + def _createActionItem(self, actionData: Dict[str, Any]) -> ActionItem: """Creates a new task action""" try: import uuid @@ -728,14 +728,14 @@ class ActionplanMode(BaseMode): if "execParameters" not in actionData: actionData["execParameters"] = {} - # Use generic field separation based on TaskAction model - simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(TaskAction, actionData) + # Use generic field separation based on ActionItem model + simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(ActionItem, actionData) # Create action in database - createdAction = self.services.interfaceDbChat.db.recordCreate(TaskAction, simpleFields) + createdAction = self.services.interfaceDbChat.db.recordCreate(ActionItem, simpleFields) - # Convert to TaskAction model - return TaskAction( + # Convert to ActionItem model + return ActionItem( id=createdAction["id"], execMethod=createdAction["execMethod"], execAction=createdAction["execAction"], diff --git a/modules/workflows/processing/modes/modeBase.py b/modules/workflows/processing/modes/modeBase.py index 611be32a..a318ca7d 100644 --- a/modules/workflows/processing/modes/modeBase.py +++ b/modules/workflows/processing/modes/modeBase.py @@ -4,7 +4,7 @@ from abc import ABC, abstractmethod import logging from typing import List, Dict, Any -from modules.datamodels.datamodelWorkflow import TaskStep, TaskContext, TaskResult, TaskAction +from modules.datamodels.datamodelChat import TaskStep, TaskContext, TaskResult, ActionItem from modules.datamodels.datamodelChat import ChatWorkflow from modules.workflows.processing.core.taskPlanner import TaskPlanner from modules.workflows.processing.core.actionExecutor import ActionExecutor @@ -46,8 +46,8 @@ class BaseMode(ABC): pass @abstractmethod - async def generateTaskActions(self, taskStep: TaskStep, workflow: ChatWorkflow, - previousResults: List = None, enhancedContext: TaskContext = None) -> List[TaskAction]: + async def generateActionItems(self, taskStep: TaskStep, workflow: ChatWorkflow, + previousResults: List = None, enhancedContext: TaskContext = None) -> List[ActionItem]: """Generate actions for a task step - must be implemented by concrete modes""" pass diff --git a/modules/workflows/processing/modes/modeReact.py b/modules/workflows/processing/modes/modeReact.py index 107022a5..40309e65 100644 --- a/modules/workflows/processing/modes/modeReact.py +++ b/modules/workflows/processing/modes/modeReact.py @@ -7,8 +7,8 @@ import re import time from datetime import datetime, timezone from typing import List, Dict, Any -from modules.datamodels.datamodelWorkflow import ( - TaskStep, TaskContext, TaskResult, TaskAction, TaskStatus, +from modules.datamodels.datamodelChat import ( + TaskStep, TaskContext, TaskResult, ActionItem, TaskStatus, ActionResult ) from modules.datamodels.datamodelChat import ChatWorkflow @@ -38,8 +38,8 @@ class ReactMode(BaseMode): self.currentIntent = None # Placeholder service no longer used; prompts are generated directly - async def generateTaskActions(self, taskStep: TaskStep, workflow: ChatWorkflow, - previousResults: List = None, enhancedContext: TaskContext = None) -> List[TaskAction]: + async def generateActionItems(self, taskStep: TaskStep, workflow: ChatWorkflow, + previousResults: List = None, enhancedContext: TaskContext = None) -> List[ActionItem]: """React mode doesn't use batch action generation - actions are generated iteratively""" # React mode generates actions one at a time in the execution loop return [] @@ -264,12 +264,12 @@ class ReactMode(BaseMode): if 'language' not in parameters and hasattr(self.services, 'user') and getattr(self.services.user, 'language', None): parameters['language'] = self.services.user.language - # Build a synthetic TaskAction for execution routing and labels + # Build a synthetic ActionItem for execution routing and labels currentRound = getattr(self.workflow, 'currentRound', 0) currentTask = getattr(self.workflow, 'currentTask', 0) resultLabel = f"round{currentRound}_task{currentTask}_action{stepIndex}_results" - taskAction = self._createTaskAction({ + taskAction = self._createActionItem({ "execMethod": methodName, "execAction": actionName, "execParameters": parameters, @@ -463,7 +463,7 @@ class ReactMode(BaseMode): async def _refineDecide(self, context: TaskContext, observation: Dict[str, Any]) -> Dict[str, Any]: """Refine: decide continue or stop, with reason""" # Create proper ReviewContext for extractReviewContent - from modules.datamodels.datamodelWorkflow import ReviewContext + from modules.datamodels.datamodelChat import ReviewContext reviewContext = ReviewContext( task_step=context.task_step, task_actions=[], @@ -662,7 +662,7 @@ Return only the user-friendly message, no technical details.""" logger.error(f"Error generating action result message: {str(e)}") return f"{method}.{actionName} action completed" - def _createTaskAction(self, actionData: Dict[str, Any]) -> TaskAction: + def _createActionItem(self, actionData: Dict[str, Any]) -> ActionItem: """Creates a new task action for React mode""" try: import uuid @@ -686,14 +686,14 @@ Return only the user-friendly message, no technical details.""" if "execParameters" not in actionData: actionData["execParameters"] = {} - # Use generic field separation based on TaskAction model - simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(TaskAction, actionData) + # Use generic field separation based on ActionItem model + simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(ActionItem, actionData) # Create action in database - createdAction = self.services.interfaceDbChat.db.recordCreate(TaskAction, simpleFields) + createdAction = self.services.interfaceDbChat.db.recordCreate(ActionItem, simpleFields) - # Convert to TaskAction model - return TaskAction( + # Convert to ActionItem model + return ActionItem( id=createdAction["id"], execMethod=createdAction["execMethod"], execAction=createdAction["execAction"], @@ -753,7 +753,7 @@ Return only the user-friendly message, no technical details.""" except Exception as e: logger.error(f"Error updating workflow before executing action: {str(e)}") - def _createTaskAction(self, actionData: Dict[str, Any]) -> TaskAction: + def _createActionItem(self, actionData: Dict[str, Any]) -> ActionItem: """Creates a new task action for React mode""" try: import uuid @@ -777,14 +777,14 @@ Return only the user-friendly message, no technical details.""" if "execParameters" not in actionData: actionData["execParameters"] = {} - # Use generic field separation based on TaskAction model - simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(TaskAction, actionData) + # Use generic field separation based on ActionItem model + simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(ActionItem, actionData) # Create action in database - createdAction = self.services.interfaceDbChat.db.recordCreate(TaskAction, simpleFields) + createdAction = self.services.interfaceDbChat.db.recordCreate(ActionItem, simpleFields) - # Convert to TaskAction model - return TaskAction( + # Convert to ActionItem model + return ActionItem( id=createdAction["id"], execMethod=createdAction["execMethod"], execAction=createdAction["execAction"], diff --git a/modules/workflows/processing/shared/executionState.py b/modules/workflows/processing/shared/executionState.py index d6368f5f..8504033a 100644 --- a/modules/workflows/processing/shared/executionState.py +++ b/modules/workflows/processing/shared/executionState.py @@ -4,8 +4,8 @@ import logging from typing import List from datetime import datetime, UTC -from modules.datamodels.datamodelWorkflow import TaskStep -from modules.datamodels.datamodelWorkflow import ActionResult +from modules.datamodels.datamodelChat import TaskStep +from modules.datamodels.datamodelChat import ActionResult logger = logging.getLogger(__name__) diff --git a/modules/workflows/processing/shared/methodDiscovery.py b/modules/workflows/processing/shared/methodDiscovery.py index 16b4b30c..4a371700 100644 --- a/modules/workflows/processing/shared/methodDiscovery.py +++ b/modules/workflows/processing/shared/methodDiscovery.py @@ -7,7 +7,7 @@ import importlib import pkgutil import inspect from typing import Any, Dict, List -from modules.datamodels.datamodelWorkflow import TaskContext, ReviewContext, DocumentExchange +from modules.datamodels.datamodelChat import TaskContext, ReviewContext, DocumentExchange from modules.workflows.methods.methodBase import MethodBase # Set up logger diff --git a/modules/workflows/processing/shared/promptGenerationActionsActionplan.py b/modules/workflows/processing/shared/promptGenerationActionsActionplan.py index e0d29ea4..c94179e6 100644 --- a/modules/workflows/processing/shared/promptGenerationActionsActionplan.py +++ b/modules/workflows/processing/shared/promptGenerationActionsActionplan.py @@ -6,7 +6,7 @@ Handles prompt templates and extraction functions for actionplan mode action han import json import logging from typing import Dict, Any, List -from modules.datamodels.datamodelWorkflow import PromptBundle, PromptPlaceholder +from modules.datamodels.datamodelChat import PromptBundle, PromptPlaceholder from modules.workflows.processing.shared.placeholderFactory import ( extractUserPrompt, extractAvailableDocumentsSummary, diff --git a/modules/workflows/processing/shared/promptGenerationActionsReact.py b/modules/workflows/processing/shared/promptGenerationActionsReact.py index c922ae71..3ff6c79d 100644 --- a/modules/workflows/processing/shared/promptGenerationActionsReact.py +++ b/modules/workflows/processing/shared/promptGenerationActionsReact.py @@ -4,7 +4,7 @@ Handles prompt templates for react mode action handling. """ from typing import Any, List -from modules.datamodels.datamodelWorkflow import PromptBundle, PromptPlaceholder +from modules.datamodels.datamodelChat import PromptBundle, PromptPlaceholder from modules.workflows.processing.shared.placeholderFactory import ( extractUserPrompt, extractUserLanguage, diff --git a/modules/workflows/processing/shared/promptGenerationTaskplan.py b/modules/workflows/processing/shared/promptGenerationTaskplan.py index c0b7bbb9..49085f80 100644 --- a/modules/workflows/processing/shared/promptGenerationTaskplan.py +++ b/modules/workflows/processing/shared/promptGenerationTaskplan.py @@ -6,7 +6,7 @@ Handles prompt templates and extraction functions for task planning phase. import json import logging from typing import Dict, Any, List -from modules.datamodels.datamodelWorkflow import PromptBundle, PromptPlaceholder +from modules.datamodels.datamodelChat import PromptBundle, PromptPlaceholder from modules.workflows.processing.shared.placeholderFactory import ( extractUserPrompt, extractAvailableDocumentsSummary, diff --git a/modules/workflows/processing/workflowProcessor.py b/modules/workflows/processing/workflowProcessor.py index 5041bb2f..7de1e114 100644 --- a/modules/workflows/processing/workflowProcessor.py +++ b/modules/workflows/processing/workflowProcessor.py @@ -3,7 +3,7 @@ import logging from typing import Dict, Any, Optional, List -from modules.datamodels.datamodelWorkflow import TaskStep, TaskContext, TaskPlan, TaskResult, ReviewResult +from modules.datamodels.datamodelChat import TaskStep, TaskContext, TaskPlan, TaskResult, ReviewResult from modules.datamodels.datamodelChat import ChatWorkflow from modules.workflows.processing.modes.modeBase import BaseMode from modules.workflows.processing.modes.modeActionplan import ActionplanMode @@ -84,7 +84,7 @@ class WorkflowProcessor: logger.error(f"Error in executeTask: {str(e)}") raise - async def generateTaskActions(self, taskStep: TaskStep, workflow: ChatWorkflow, + async def generateActionItems(self, taskStep: TaskStep, workflow: ChatWorkflow, previousResults: List = None, enhancedContext: TaskContext = None) -> List: """Generate actions for a task step using the appropriate mode""" try: @@ -96,9 +96,9 @@ class WorkflowProcessor: logger.info(f"Mode: {workflow.workflowMode}") # Delegate to the appropriate mode - return await self.mode.generateTaskActions(taskStep, workflow, previousResults, enhancedContext) + return await self.mode.generateActionItems(taskStep, workflow, previousResults, enhancedContext) except Exception as e: - logger.error(f"Error in generateTaskActions: {str(e)}") + logger.error(f"Error in generateActionItems: {str(e)}") raise def updateWorkflowAfterTaskPlanCreated(self, totalTasks: int): diff --git a/modules/workflows/workflowManager.py b/modules/workflows/workflowManager.py index db0d6652..4ac6b26c 100644 --- a/modules/workflows/workflowManager.py +++ b/modules/workflows/workflowManager.py @@ -8,10 +8,9 @@ from modules.datamodels.datamodelChat import ( UserInputRequest, ChatMessage, ChatWorkflow, - ChatDocument, - WorkflowResult + ChatDocument ) -from modules.datamodels.datamodelWorkflow import TaskItem, TaskStatus, TaskContext +from modules.datamodels.datamodelChat import TaskItem, TaskStatus, TaskContext from modules.workflows.processing.workflowProcessor import WorkflowProcessor, WorkflowStoppedException from modules.shared.timezoneUtils import get_utc_timestamp @@ -160,8 +159,8 @@ class WorkflowManager: self.workflowProcessor = WorkflowProcessor(self.services, workflow) message = await self._sendFirstMessage(userInput, workflow) task_plan = await self._planTasks(userInput, workflow) - workflow_result = await self._executeTasks(task_plan, workflow) - await self._processWorkflowResults(workflow, workflow_result, message) + await self._executeTasks(task_plan, workflow) + await self._processWorkflowResults(workflow, message) except WorkflowStoppedException: self._handleWorkflowStop(workflow) @@ -359,8 +358,8 @@ class WorkflowManager: logger.info(f"Executing workflow mode={workflow_mode} with {len(task_plan.tasks)} tasks") return task_plan - async def _executeTasks(self, task_plan, workflow: ChatWorkflow) -> WorkflowResult: - """Execute all tasks in the task plan""" + async def _executeTasks(self, task_plan, workflow: ChatWorkflow) -> None: + """Execute all tasks in the task plan and update workflow status.""" handling = self.workflowProcessor total_tasks = len(task_plan.tasks) all_task_results: List = [] @@ -404,16 +403,12 @@ class WorkflowManager: if task_result.success and task_result.feedback: previous_results.append(task_result.feedback) - return WorkflowResult( - status="completed", - completed_tasks=len(all_task_results), - total_tasks=total_tasks, - execution_time=0.0, - final_results_count=len(all_task_results) - ) + # Mark workflow as completed; error/stop cases update status elsewhere + workflow.status = "completed" + return None - async def _processWorkflowResults(self, workflow: ChatWorkflow, workflow_result: WorkflowResult, initial_message: ChatMessage) -> None: - """Process workflow results and create appropriate messages""" + async def _processWorkflowResults(self, workflow: ChatWorkflow, initial_message: ChatMessage) -> None: + """Process workflow results based on workflow status and create appropriate messages""" try: try: self.workflowProcessor._checkWorkflowStopped(workflow) @@ -451,7 +446,7 @@ class WorkflowManager: }) return - if workflow_result.status == 'stopped': + if workflow.status == 'stopped': # Create stopped message stopped_message = { "workflowId": workflow.id, @@ -493,12 +488,12 @@ class WorkflowManager: "progress": 100 }) return - elif workflow_result.status == 'failed': + elif workflow.status == 'failed': # Create error message error_message = { "workflowId": workflow.id, "role": "assistant", - "message": f"Workflow failed: {workflow_result.error or 'Unknown error'}", + "message": f"Workflow failed: {'Unknown error'}", "status": "last", "sequenceNr": len(workflow.messages) + 1, "publishedAt": self.services.utils.getUtcTimestamp(),