cleaned pydantic model classes

This commit is contained in:
ValueOn AG 2025-10-05 16:28:44 +02:00
parent 6b976fc1ca
commit d2b6820812
35 changed files with 492 additions and 667 deletions

View file

@ -10,7 +10,6 @@ from . import datamodelWeb as web
from . import datamodelUam as uam from . import datamodelUam as uam
from . import datamodelSecurity as security from . import datamodelSecurity as security
from . import datamodelNeutralizer as neutralizer from . import datamodelNeutralizer as neutralizer
from . import datamodelWorkflow as workflow
from . import datamodelChat as chat from . import datamodelChat as chat
from . import datamodelFiles as files from . import datamodelFiles as files
from . import datamodelVoice as voice from . import datamodelVoice as voice

View file

@ -6,7 +6,6 @@ from modules.shared.attributeUtils import register_model_labels, ModelMixin
from modules.shared.timezoneUtils import get_utc_timestamp from modules.shared.timezoneUtils import get_utc_timestamp
import uuid import uuid
class ChatStat(BaseModel, ModelMixin): class ChatStat(BaseModel, ModelMixin):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key") 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)") 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") bytesReceived: Optional[int] = Field(None, description="Number of bytes received")
successRate: Optional[float] = Field(None, description="Success rate of operations") successRate: Optional[float] = Field(None, description="Success rate of operations")
errorCount: Optional[int] = Field(None, description="Number of errors encountered") errorCount: Optional[int] = Field(None, description="Number of errors encountered")
register_model_labels( register_model_labels(
"ChatStat", "ChatStat",
{"en": "Chat Statistics", "fr": "Statistiques de chat"}, {"en": "Chat Statistics", "fr": "Statistiques de chat"},
@ -35,7 +32,6 @@ register_model_labels(
}, },
) )
class ChatLog(BaseModel, ModelMixin): class ChatLog(BaseModel, ModelMixin):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key") id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key")
workflowId: str = Field(description="Foreign key to workflow") 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") status: Optional[str] = Field(None, description="Status of the log entry")
progress: Optional[float] = Field(None, description="Progress indicator (0.0 to 1.0)") progress: Optional[float] = Field(None, description="Progress indicator (0.0 to 1.0)")
performance: Optional[Dict[str, Any]] = Field(None, description="Performance metrics") performance: Optional[Dict[str, Any]] = Field(None, description="Performance metrics")
register_model_labels( register_model_labels(
"ChatLog", "ChatLog",
{"en": "Chat Log", "fr": "Journal de chat"}, {"en": "Chat Log", "fr": "Journal de chat"},
@ -62,7 +56,6 @@ register_model_labels(
}, },
) )
class ChatDocument(BaseModel, ModelMixin): class ChatDocument(BaseModel, ModelMixin):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key") id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key")
messageId: str = Field(description="Foreign key to message") 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") taskNumber: Optional[int] = Field(None, description="Task number within round")
actionNumber: Optional[int] = Field(None, description="Action number within task") actionNumber: Optional[int] = Field(None, description="Action number within task")
actionId: Optional[str] = Field(None, description="ID of the action that created this document") actionId: Optional[str] = Field(None, description="ID of the action that created this document")
register_model_labels( register_model_labels(
"ChatDocument", "ChatDocument",
{"en": "Chat Document", "fr": "Document de chat"}, {"en": "Chat Document", "fr": "Document de chat"},
@ -93,7 +84,6 @@ register_model_labels(
}, },
) )
class ContentMetadata(BaseModel, ModelMixin): class ContentMetadata(BaseModel, ModelMixin):
size: int = Field(description="Content size in bytes") size: int = Field(description="Content size in bytes")
pages: Optional[int] = Field(None, description="Number of pages for multi-page content") 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") durationSec: Optional[float] = Field(None, description="Duration in seconds for media")
mimeType: str = Field(description="MIME type of the content") mimeType: str = Field(description="MIME type of the content")
base64Encoded: bool = Field(description="Whether the data is base64 encoded") base64Encoded: bool = Field(description="Whether the data is base64 encoded")
register_model_labels( register_model_labels(
"ContentMetadata", "ContentMetadata",
{"en": "Content Metadata", "fr": "Métadonnées du contenu"}, {"en": "Content Metadata", "fr": "Métadonnées du contenu"},
@ -124,13 +112,10 @@ register_model_labels(
}, },
) )
class ContentItem(BaseModel, ModelMixin): class ContentItem(BaseModel, ModelMixin):
label: str = Field(description="Content label") label: str = Field(description="Content label")
data: str = Field(description="Extracted text content") data: str = Field(description="Extracted text content")
metadata: ContentMetadata = Field(description="Content metadata") metadata: ContentMetadata = Field(description="Content metadata")
register_model_labels( register_model_labels(
"ContentItem", "ContentItem",
{"en": "Content Item", "fr": "Élément de contenu"}, {"en": "Content Item", "fr": "Élément de contenu"},
@ -141,14 +126,11 @@ register_model_labels(
}, },
) )
class ChatContentExtracted(BaseModel, ModelMixin):
class ExtractedContent(BaseModel, ModelMixin):
id: str = Field(description="Reference to source ChatDocument") id: str = Field(description="Reference to source ChatDocument")
contents: List[ContentItem] = Field(default_factory=list, description="List of content items") contents: List[ContentItem] = Field(default_factory=list, description="List of content items")
register_model_labels( register_model_labels(
"ExtractedContent", "ChatContentExtracted",
{"en": "Extracted Content", "fr": "Contenu extrait"}, {"en": "Extracted Content", "fr": "Contenu extrait"},
{ {
"id": {"en": "Object ID", "fr": "ID de l'objet"}, "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") actionNumber: Optional[int] = Field(None, description="Action number within task")
taskProgress: Optional[str] = Field(None, description="Task progress status: pending, running, success, fail, retry") 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") actionProgress: Optional[str] = Field(None, description="Action progress status: pending, running, success, fail")
register_model_labels( register_model_labels(
"ChatMessage", "ChatMessage",
{"en": "Chat Message", "fr": "Message de chat"}, {"en": "Chat Message", "fr": "Message de chat"},
@ -206,7 +186,6 @@ register_model_labels(
}, },
) )
class ChatWorkflow(BaseModel, ModelMixin): 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) 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) 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"}}, {"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) 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( register_model_labels(
"ChatWorkflow", "ChatWorkflow",
{"en": "Chat Workflow", "fr": "Flux de travail de chat"}, {"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): class UserInputRequest(BaseModel, ModelMixin):
prompt: str = Field(description="Prompt for the user") prompt: str = Field(description="Prompt for the user")
listFileId: List[str] = Field(default_factory=list, description="List of file IDs") listFileId: List[str] = Field(default_factory=list, description="List of file IDs")
userLanguage: str = Field(default="en", description="User's preferred language") userLanguage: str = Field(default="en", description="User's preferred language")
register_model_labels( register_model_labels(
"UserInputRequest", "UserInputRequest", {"en": "User Input Request", "fr": "Demande de saisie utilisateur"},
{"en": "User Input Request", "fr": "Demande de saisie utilisateur"},
{ {
"prompt": {"en": "Prompt", "fr": "Invite"}, "prompt": {"en": "Prompt", "fr": "Invite"},
"listFileId": {"en": "File IDs", "fr": "IDs des fichiers"}, "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"},
},
)

View file

@ -12,7 +12,7 @@ class ContentPart(BaseModel):
metadata: Dict[str, Any] = Field(default_factory=dict, description="Arbitrary metadata for the part") 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") id: str = Field(description="Extraction id or source document id")
parts: List[ContentPart] = Field(default_factory=list, description="List of extracted parts") parts: List[ContentPart] = Field(default_factory=list, description="List of extracted parts")
summary: Optional[Dict[str, Any]] = Field(default=None, description="Optional extraction summary") summary: Optional[Dict[str, Any]] = Field(default=None, description="Optional extraction summary")

View file

@ -19,8 +19,6 @@ class FileItem(BaseModel, ModelMixin):
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
return super().to_dict() return super().to_dict()
register_model_labels( register_model_labels(
"FileItem", "FileItem",
{"en": "File Item", "fr": "Élément de fichier"}, {"en": "File Item", "fr": "Élément de fichier"},
@ -35,7 +33,6 @@ register_model_labels(
}, },
) )
class FilePreview(BaseModel, ModelMixin): class FilePreview(BaseModel, ModelMixin):
content: Union[str, bytes] = Field(description="File content (text or binary)") content: Union[str, bytes] = Field(description="File content (text or binary)")
mimeType: str = Field(description="MIME type of the file") mimeType: str = Field(description="MIME type of the file")
@ -49,8 +46,6 @@ class FilePreview(BaseModel, ModelMixin):
if isinstance(data.get("content"), bytes): if isinstance(data.get("content"), bytes):
data["content"] = base64.b64encode(data["content"]).decode("utf-8") data["content"] = base64.b64encode(data["content"]).decode("utf-8")
return data return data
register_model_labels( register_model_labels(
"FilePreview", "FilePreview",
{"en": "File Preview", "fr": "Aperçu du fichier"}, {"en": "File Preview", "fr": "Aperçu du fichier"},
@ -64,13 +59,10 @@ register_model_labels(
}, },
) )
class FileData(BaseModel, ModelMixin): class FileData(BaseModel, ModelMixin):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key") id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key")
data: str = Field(description="File data content") data: str = Field(description="File data content")
base64Encoded: bool = Field(description="Whether the data is base64 encoded") base64Encoded: bool = Field(description="Whether the data is base64 encoded")
register_model_labels( register_model_labels(
"FileData", "FileData",
{"en": "File Data", "fr": "Données de fichier"}, {"en": "File Data", "fr": "Données de fichier"},
@ -80,5 +72,3 @@ register_model_labels(
"base64Encoded": {"en": "Base64 Encoded", "fr": "Encodé en Base64"}, "base64Encoded": {"en": "Base64 Encoded", "fr": "Encodé en Base64"},
}, },
) )

View file

@ -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) 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) 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) sharepointTargetPath: str = Field(default="", description="SharePoint path to store neutralized files", frontend_type="text", frontend_readonly=False, frontend_required=False)
register_model_labels( register_model_labels(
"DataNeutraliserConfig", "DataNeutraliserConfig",
{"en": "Data Neutralization Config", "fr": "Configuration de neutralisation des données"}, {"en": "Data Neutralization Config", "fr": "Configuration de neutralisation des données"},
@ -30,7 +28,6 @@ register_model_labels(
}, },
) )
class DataNeutralizerAttributes(BaseModel, ModelMixin): 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) 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) 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) 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) 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) 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( register_model_labels(
"DataNeutralizerAttributes", "DataNeutralizerAttributes",
{"en": "Neutralized Data Attribute", "fr": "Attribut de données neutralisées"}, {"en": "Neutralized Data Attribute", "fr": "Attribut de données neutralisées"},

View file

@ -13,7 +13,6 @@ class TokenStatus(str, Enum):
ACTIVE = "active" ACTIVE = "active"
REVOKED = "revoked" REVOKED = "revoked"
class Token(BaseModel, ModelMixin): class Token(BaseModel, ModelMixin):
id: Optional[str] = None id: Optional[str] = None
userId: str userId: str
@ -33,8 +32,6 @@ class Token(BaseModel, ModelMixin):
class Config: class Config:
use_enum_values = True use_enum_values = True
register_model_labels( register_model_labels(
"Token", "Token",
{"en": "Token", "fr": "Jeton"}, {"en": "Token", "fr": "Jeton"},
@ -57,7 +54,6 @@ register_model_labels(
}, },
) )
class AuthEvent(BaseModel, ModelMixin): 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) 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) 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) 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) 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) details: Optional[str] = Field(default=None, description="Additional details about the event", frontend_type="text", frontend_readonly=True, frontend_required=False)
register_model_labels( register_model_labels(
"AuthEvent", "AuthEvent",
{"en": "Authentication Event", "fr": "Événement d'authentification"}, {"en": "Authentication Event", "fr": "Événement d'authentification"},

View file

@ -9,7 +9,6 @@ class TicketFieldAttribute(BaseModel):
fieldName: str = Field(description="Human-readable field name") fieldName: str = Field(description="Human-readable field name")
field: str = Field(description="Ticket field ID/key") field: str = Field(description="Ticket field ID/key")
class TicketBase(ABC): class TicketBase(ABC):
@abstractmethod @abstractmethod
async def read_attributes(self) -> list[TicketFieldAttribute]: ... async def read_attributes(self) -> list[TicketFieldAttribute]: ...

View file

@ -13,20 +13,17 @@ class AuthAuthority(str, Enum):
GOOGLE = "google" GOOGLE = "google"
MSFT = "msft" MSFT = "msft"
class UserPrivilege(str, Enum): class UserPrivilege(str, Enum):
SYSADMIN = "sysadmin" SYSADMIN = "sysadmin"
ADMIN = "admin" ADMIN = "admin"
USER = "user" USER = "user"
class ConnectionStatus(str, Enum): class ConnectionStatus(str, Enum):
ACTIVE = "active" ACTIVE = "active"
EXPIRED = "expired" EXPIRED = "expired"
REVOKED = "revoked" REVOKED = "revoked"
PENDING = "pending" PENDING = "pending"
class Mandate(BaseModel, ModelMixin): 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) 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) 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"}}, {"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) enabled: bool = Field(default=True, description="Indicates whether the mandate is enabled", frontend_type="checkbox", frontend_readonly=False, frontend_required=False)
register_model_labels( register_model_labels(
"Mandate", "Mandate",
{"en": "Mandate", "fr": "Mandat"}, {"en": "Mandate", "fr": "Mandat"},
@ -50,7 +45,6 @@ register_model_labels(
}, },
) )
class UserConnection(BaseModel, ModelMixin): 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) 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) 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"}}, {"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) 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( register_model_labels(
"UserConnection", "UserConnection",
{"en": "User Connection", "fr": "Connexion utilisateur"}, {"en": "User Connection", "fr": "Connexion utilisateur"},
@ -98,7 +90,6 @@ register_model_labels(
}, },
) )
class User(BaseModel, ModelMixin): 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) 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) 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"}}, {"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) 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( register_model_labels(
"User", "User",
{"en": "User", "fr": "Utilisateur"}, {"en": "User", "fr": "Utilisateur"},
@ -140,15 +129,10 @@ register_model_labels(
}, },
) )
class UserInDB(User): class UserInDB(User):
hashedPassword: Optional[str] = Field(None, description="Hash of the user password") hashedPassword: Optional[str] = Field(None, description="Hash of the user password")
register_model_labels( register_model_labels(
"UserInDB", "UserInDB",
{"en": "User Access", "fr": "Accès de l'utilisateur"}, {"en": "User Access", "fr": "Accès de l'utilisateur"},
{"hashedPassword": {"en": "Password hash", "fr": "Hachage de mot de passe"}}, {"hashedPassword": {"en": "Password hash", "fr": "Hachage de mot de passe"}},
) )

View file

@ -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) 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) 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) name: str = Field(description="Name of the prompt", frontend_type="text", frontend_readonly=False, frontend_required=True)
register_model_labels( register_model_labels(
"Prompt", "Prompt",
{"en": "Prompt", "fr": "Invite"}, {"en": "Prompt", "fr": "Invite"},

View file

@ -22,7 +22,6 @@ class VoiceSettings(BaseModel, ModelMixin):
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
return super().to_dict() return super().to_dict()
register_model_labels( register_model_labels(
"VoiceSettings", "VoiceSettings",
{"en": "Voice Settings", "fr": "Paramètres vocaux"}, {"en": "Voice Settings", "fr": "Paramètres vocaux"},

View file

@ -1,10 +1,8 @@
"""Web-related modules""" """Web-related modules"""
from abc import ABC, abstractmethod
from pydantic import BaseModel, Field, HttpUrl from pydantic import BaseModel, Field, HttpUrl
from typing import List, Optional, Literal, Dict, Any from typing import List, Optional, Literal, Dict, Any
from modules.shared.configuration import APP_CONFIG 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")) 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_answer: Optional[bool] = Field(default=None, description="Include AI answer")
include_raw_content: Optional[bool] = Field(default=None, description="Include raw content") include_raw_content: Optional[bool] = Field(default=None, description="Include raw content")
class WebResearchRequest(BaseModel): class WebResearchRequest(BaseModel):
"""Main web research request""" """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") 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") 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") options: WebResearchOptions = Field(default_factory=WebResearchOptions, description="Advanced options")
class WebSearchResultItem(BaseModel): class WebSearchResultItem(BaseModel):
"""Individual search result""" """Individual search result"""
title: str title: str
url: HttpUrl url: HttpUrl
raw_content: Optional[str] = Field(default=None, description="Raw HTML content") raw_content: Optional[str] = Field(default=None, description="Raw HTML content")
class WebCrawlResultItem(BaseModel): class WebCrawlResultItem(BaseModel):
"""Individual crawl result""" """Individual crawl result"""
url: HttpUrl url: HttpUrl
content: str content: str
class WebResearchDocumentData(BaseModel): class WebResearchDocumentData(BaseModel):
"""Complete web research results""" """Complete web research results"""
user_prompt: str user_prompt: str
@ -60,21 +54,14 @@ class WebResearchDocumentData(BaseModel):
individual_content: Optional[Dict[str, str]] = None # URL -> content mapping individual_content: Optional[Dict[str, str]] = None # URL -> content mapping
debug_info: Optional[Dict[str, Any]] = None debug_info: Optional[Dict[str, Any]] = None
class WebResearchActionDocument(ActionDocument): class WebResearchActionDocument(ActionDocument):
documentData: WebResearchDocumentData documentData: WebResearchDocumentData
class WebResearchActionResult(ActionResult): class WebResearchActionResult(ActionResult):
documents: List[WebResearchActionDocument] = Field(default_factory=list) documents: List[WebResearchActionDocument] = Field(default_factory=list)
class WebResearchBase(ABC):
@abstractmethod
async def web_research(self, request: WebResearchRequest) -> WebResearchActionResult: ...
# Legacy models for connector compatibility # Legacy models for connector compatibility
class WebSearchDocumentData(BaseModel): class WebSearchDocumentData(BaseModel):
"""Search results document data""" """Search results document data"""
query: str query: str
@ -153,5 +140,3 @@ class WebScrapeResultItem(BaseModel):
"""Individual scrape result""" """Individual scrape result"""
url: HttpUrl url: HttpUrl
content: str content: str

View file

@ -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"},
},
)

View file

@ -27,7 +27,7 @@ from modules.datamodels.datamodelWeb import (
WebSearchRequest, WebSearchRequest,
WebCrawlRequest, WebCrawlRequest,
) )
from modules.datamodels.datamodelWorkflow import ActionDocument from modules.datamodels.datamodelChat import ActionDocument
# Comprehensive model registry with capability tags and function mapping # Comprehensive model registry with capability tags and function mapping

View file

@ -12,8 +12,8 @@ from typing import Dict, Any, List, Optional, Union, get_origin, get_args
import asyncio import asyncio
from modules.interfaces.interfaceDbChatAccess import ChatAccess from modules.interfaces.interfaceDbChatAccess import ChatAccess
from modules.datamodels.datamodelWorkflow import ( from modules.datamodels.datamodelChat import (
TaskAction, ActionItem,
TaskResult, TaskResult,
TaskItem, TaskItem,
TaskStatus, TaskStatus,

View file

@ -1,6 +1,6 @@
import logging import logging
from typing import Dict, Any, List, Optional, Tuple, Union 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.datamodels.datamodelChat import ChatDocument
from modules.services.serviceExtraction.mainServiceExtraction import ExtractionService from modules.services.serviceExtraction.mainServiceExtraction import ExtractionService
@ -909,7 +909,7 @@ class AiService:
logger.info(f"Extraction completed: {len(extracted_content)} documents") 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): if isinstance(extracted_content, list):
context_parts = [] context_parts = []
chunk_count = 0 chunk_count = 0

View file

@ -4,7 +4,7 @@ import logging
from .subRegistry import ExtractorRegistry, ChunkerRegistry from .subRegistry import ExtractorRegistry, ChunkerRegistry
from .subPipeline import runExtraction, poolAndLimit, applyAiIfRequested 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 from modules.datamodels.datamodelChat import ChatDocument
@ -17,7 +17,7 @@ class ExtractionService:
self._extractorRegistry = ExtractorRegistry() self._extractorRegistry = ExtractorRegistry()
self._chunkerRegistry = ChunkerRegistry() 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. Extract content from a list of ChatDocument objects.
@ -26,9 +26,9 @@ class ExtractionService:
options: Extraction options including maxSize, chunkAllowed, mergeStrategy, etc. options: Extraction options including maxSize, chunkAllowed, mergeStrategy, etc.
Returns: 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 # Lazy import to avoid circular deps and heavy init at module import
from modules.interfaces.interfaceDbComponentObjects import getInterface from modules.interfaces.interfaceDbComponentObjects import getInterface
@ -90,20 +90,20 @@ class ExtractionService:
def mergeAiResults( def mergeAiResults(
self, self,
extractedContent: List[ExtractedContent], extractedContent: List[ContentExtracted],
aiResults: List[str], aiResults: List[str],
strategy: MergeStrategy 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: 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 aiResults: List of AI response strings, one per chunk
strategy: Merge strategy configuration (dict or MergeStrategy object) strategy: Merge strategy configuration (dict or MergeStrategy object)
Returns: Returns:
Single ExtractedContent with merged AI results Single ContentExtracted with merged AI results
""" """
logger.debug(f"=== MERGING AI RESULTS ===") logger.debug(f"=== MERGING AI RESULTS ===")
logger.debug(f"Extracted content: {len(extractedContent)} documents") logger.debug(f"Extracted content: {len(extractedContent)} documents")
@ -150,8 +150,8 @@ class ExtractionService:
# Default to concatenate # Default to concatenate
mergedParts = self._mergeConcatenate(allParts, aiResultParts, mergeStrategy) mergedParts = self._mergeConcatenate(allParts, aiResultParts, mergeStrategy)
# Create final ExtractedContent # Create final ContentExtracted
mergedContent = ExtractedContent( mergedContent = ContentExtracted(
id=f"merged_{uuid.uuid4()}", id=f"merged_{uuid.uuid4()}",
parts=mergedParts parts=mergedParts
) )

View file

@ -2,7 +2,7 @@ from typing import Any, Dict, List
import logging import logging
import os import os
from modules.datamodels.datamodelExtraction import ExtractedContent, ContentPart from modules.datamodels.datamodelExtraction import ContentExtracted, ContentPart
from .subUtils import makeId from .subUtils import makeId
from .subRegistry import ExtractorRegistry, ChunkerRegistry from .subRegistry import ExtractorRegistry, ChunkerRegistry
from .merging.text_merger import TextMerger from .merging.text_merger import TextMerger
@ -55,7 +55,7 @@ def _mergeParts(parts: List[ContentPart], mergeStrategy: Dict[str, Any]) -> List
return merged_parts 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) extractor = extractorRegistry.resolve(mimeType, fileName)
if extractor is None: if extractor is None:
# fallback: single binary part # fallback: single binary part
@ -68,7 +68,7 @@ def runExtraction(extractorRegistry: ExtractorRegistry, chunkerRegistry: Chunker
data="", data="",
metadata={"warning": "No extractor registered"} 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}) parts = extractor.extract(documentBytes, {"fileName": fileName, "mimeType": mimeType, "options": options})
@ -119,7 +119,7 @@ def runExtraction(extractorRegistry: ExtractorRegistry, chunkerRegistry: Chunker
except Exception as _e: except Exception as _e:
logger.debug(f"Debug dump skipped: {_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]: 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 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. Apply AI processing if requested in options.
This is a placeholder for actual AI integration. This is a placeholder for actual AI integration.

View file

@ -3,7 +3,7 @@ import uuid
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional
from modules.datamodels.datamodelUam import User, UserConnection from modules.datamodels.datamodelUam import User, UserConnection
from modules.datamodels.datamodelChat import ChatDocument, ChatMessage 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.serviceExtraction.mainServiceExtraction import ExtractionService
from modules.services.serviceGeneration.subDocumentUtility import getFileExtension, getMimeTypeFromExtension, detectContentTypeFromData from modules.services.serviceGeneration.subDocumentUtility import getFileExtension, getMimeTypeFromExtension, detectContentTypeFromData
from modules.shared.timezoneUtils import get_utc_timestamp from modules.shared.timezoneUtils import get_utc_timestamp

View file

@ -8,7 +8,7 @@ from typing import Dict, Any, List, Optional
from datetime import datetime, UTC from datetime import datetime, UTC
from modules.workflows.methods.methodBase import MethodBase, action 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.datamodelAi import AiCallOptions, OperationType, Priority
from modules.datamodels.datamodelChat import ChatDocument from modules.datamodels.datamodelChat import ChatDocument
from modules.datamodels.datamodelWeb import WebResearchRequest, WebResearchOptions 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 # Parse JSON response from AI with streaming support
import json import json
import re import re
from modules.datamodels.datamodelWorkflow import ActionDocument from modules.datamodels.datamodelChat import ActionDocument
action_documents = [] action_documents = []
all_data_chunks = [] # Store all data chunks for merging all_data_chunks = [] # Store all data chunks for merging

View file

@ -9,7 +9,7 @@ from typing import Dict, Any, List, Optional
from datetime import datetime, UTC from datetime import datetime, UTC
from modules.workflows.methods.methodBase import MethodBase, action 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.datamodelChat import ChatDocument
from modules.datamodels.datamodelAi import AiCallOptions, OperationType, Priority from modules.datamodels.datamodelAi import AiCallOptions, OperationType, Priority
@ -136,7 +136,7 @@ class MethodDocument(MethodBase):
action_documents = [] action_documents = []
for i, chatDocument in enumerate(chatDocuments): 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 = "" text_content = ""
try: try:
ec = all_extracted_content[i] if i < len(all_extracted_content) else None ec = all_extracted_content[i] if i < len(all_extracted_content) else None

View file

@ -12,7 +12,7 @@ import uuid
import requests import requests
from modules.workflows.methods.methodBase import MethodBase, action 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.datamodelAi import AiCallOptions, OperationType, Priority, AiCallRequest
from modules.datamodels.datamodelChat import ChatDocument from modules.datamodels.datamodelChat import ChatDocument
from modules.datamodels.datamodelUam import ConnectionStatus from modules.datamodels.datamodelUam import ConnectionStatus

View file

@ -14,7 +14,7 @@ import aiohttp
import asyncio import asyncio
from modules.workflows.methods.methodBase import MethodBase, action 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__) logger = logging.getLogger(__name__)

View file

@ -3,7 +3,7 @@
import logging import logging
from typing import Dict, Any, List 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.datamodels.datamodelChat import ChatWorkflow
from modules.workflows.processing.shared.methodDiscovery import methods from modules.workflows.processing.shared.methodDiscovery import methods
@ -65,7 +65,7 @@ class ActionExecutor:
logger.error(f"Error executing compound action {compoundActionName}: {str(e)}") logger.error(f"Error executing compound action {compoundActionName}: {str(e)}")
raise 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: taskIndex: int = None, actionIndex: int = None, totalActions: int = None) -> ActionResult:
"""Execute a single action and return ActionResult with enhanced document processing""" """Execute a single action and return ActionResult with enhanced document processing"""
try: try:
@ -199,7 +199,7 @@ class ActionExecutor:
# Join all document results with separators # Join all document results with separators
return "\n\n---\n\n".join(resultParts) if resultParts else "" 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): taskStep: TaskStep, taskIndex: int, actionIndex: int, totalActions: int):
"""Create action completion message with documents (generic)""" """Create action completion message with documents (generic)"""
try: try:

View file

@ -3,7 +3,7 @@
import logging import logging
from typing import Dict, Any, Optional, List 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 from modules.datamodels.datamodelChat import ChatWorkflow
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View file

@ -4,7 +4,7 @@
import json import json
import logging import logging
from typing import Dict, Any 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.datamodels.datamodelAi import AiCallOptions, OperationType, ProcessingMode, Priority
from modules.workflows.processing.shared.promptGenerationTaskplan import ( from modules.workflows.processing.shared.promptGenerationTaskplan import (
createTaskPlanningPromptTemplate createTaskPlanningPromptTemplate

View file

@ -5,8 +5,8 @@ import json
import logging import logging
import uuid import uuid
from typing import List, Dict, Any from typing import List, Dict, Any
from modules.datamodels.datamodelWorkflow import ( from modules.datamodels.datamodelChat import (
TaskStep, TaskContext, TaskResult, TaskAction, TaskStatus, TaskStep, TaskContext, TaskResult, ActionItem, TaskStatus,
ActionResult, ReviewResult, ReviewContext ActionResult, ReviewResult, ReviewContext
) )
from modules.datamodels.datamodelChat import ChatWorkflow from modules.datamodels.datamodelChat import ChatWorkflow
@ -26,8 +26,8 @@ class ActionplanMode(BaseMode):
def __init__(self, services, workflow): def __init__(self, services, workflow):
super().__init__(services, workflow) super().__init__(services, workflow)
async def generateTaskActions(self, taskStep: TaskStep, workflow: ChatWorkflow, async def generateActionItems(self, taskStep: TaskStep, workflow: ChatWorkflow,
previousResults: List = None, enhancedContext: TaskContext = None) -> List[TaskAction]: previousResults: List = None, enhancedContext: TaskContext = None) -> List[ActionItem]:
"""Generate actions for a given task step using batch planning approach""" """Generate actions for a given task step using batch planning approach"""
try: try:
# Check workflow status before generating actions # Check workflow status before generating actions
@ -176,7 +176,7 @@ class ActionplanMode(BaseMode):
logger.error("Generated actions failed validation") logger.error("Generated actions failed validation")
raise Exception("AI-generated actions failed validation - AI is required for action generation") raise Exception("AI-generated actions failed validation - AI is required for action generation")
# Convert to TaskAction objects # Convert to ActionItem objects
taskActions = [] taskActions = []
for i, a in enumerate(actions): for i, a in enumerate(actions):
if not isinstance(a, dict): if not isinstance(a, dict):
@ -193,7 +193,7 @@ class ActionplanMode(BaseMode):
# Old separate format: method + action fields # Old separate format: method + action fields
method_name = a.get('method', 'unknown') method_name = a.get('method', 'unknown')
taskAction = self._createTaskAction({ taskAction = self._createActionItem({
"execMethod": method_name, "execMethod": method_name,
"execAction": action_name, "execAction": action_name,
"execParameters": a.get('parameters', {}), "execParameters": a.get('parameters', {}),
@ -207,7 +207,7 @@ class ActionplanMode(BaseMode):
if taskAction: if taskAction:
taskActions.append(taskAction) taskActions.append(taskAction)
else: 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] validActions = [ta for ta in taskActions if ta]
@ -216,7 +216,7 @@ class ActionplanMode(BaseMode):
return validActions return validActions
except Exception as e: except Exception as e:
logger.error(f"Error in generateTaskActions: {str(e)}") logger.error(f"Error in generateActionItems: {str(e)}")
return [] return []
@ -250,7 +250,7 @@ class ActionplanMode(BaseMode):
if retryContext: if retryContext:
retryContext.retry_count = attempt + 1 retryContext.retry_count = attempt + 1
actions = await self.generateTaskActions(taskStep, workflow, actions = await self.generateActionItems(taskStep, workflow,
previousResults=retryContext.previous_results, previousResults=retryContext.previous_results,
enhancedContext=retryContext) enhancedContext=retryContext)
@ -415,7 +415,7 @@ class ActionplanMode(BaseMode):
error="Task failed after all retries." 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: actionResults: List[ActionResult], workflow: ChatWorkflow) -> ReviewResult:
"""Review task completion and determine success/failure/retry""" """Review task completion and determine success/failure/retry"""
try: try:
@ -562,7 +562,7 @@ class ActionplanMode(BaseMode):
quality_score=0 quality_score=0
) )
def _createTaskAction(self, actionData: Dict[str, Any]) -> TaskAction: def _createActionItem(self, actionData: Dict[str, Any]) -> ActionItem:
"""Creates a new task action""" """Creates a new task action"""
try: try:
# Ensure ID is present # Ensure ID is present
@ -584,14 +584,14 @@ class ActionplanMode(BaseMode):
if "execParameters" not in actionData: if "execParameters" not in actionData:
actionData["execParameters"] = {} actionData["execParameters"] = {}
# Use generic field separation based on TaskAction model # Use generic field separation based on ActionItem model
simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(TaskAction, actionData) simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(ActionItem, actionData)
# Create action in database # Create action in database
createdAction = self.services.interfaceDbChat.db.recordCreate(TaskAction, simpleFields) createdAction = self.services.interfaceDbChat.db.recordCreate(ActionItem, simpleFields)
# Convert to TaskAction model # Convert to ActionItem model
return TaskAction( return ActionItem(
id=createdAction["id"], id=createdAction["id"],
execMethod=createdAction["execMethod"], execMethod=createdAction["execMethod"],
execAction=createdAction["execAction"], execAction=createdAction["execAction"],
@ -704,7 +704,7 @@ class ActionplanMode(BaseMode):
except Exception as e: except Exception as e:
logger.error(f"Error setting workflow totals: {str(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""" """Creates a new task action"""
try: try:
import uuid import uuid
@ -728,14 +728,14 @@ class ActionplanMode(BaseMode):
if "execParameters" not in actionData: if "execParameters" not in actionData:
actionData["execParameters"] = {} actionData["execParameters"] = {}
# Use generic field separation based on TaskAction model # Use generic field separation based on ActionItem model
simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(TaskAction, actionData) simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(ActionItem, actionData)
# Create action in database # Create action in database
createdAction = self.services.interfaceDbChat.db.recordCreate(TaskAction, simpleFields) createdAction = self.services.interfaceDbChat.db.recordCreate(ActionItem, simpleFields)
# Convert to TaskAction model # Convert to ActionItem model
return TaskAction( return ActionItem(
id=createdAction["id"], id=createdAction["id"],
execMethod=createdAction["execMethod"], execMethod=createdAction["execMethod"],
execAction=createdAction["execAction"], execAction=createdAction["execAction"],

View file

@ -4,7 +4,7 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import logging import logging
from typing import List, Dict, Any 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.datamodels.datamodelChat import ChatWorkflow
from modules.workflows.processing.core.taskPlanner import TaskPlanner from modules.workflows.processing.core.taskPlanner import TaskPlanner
from modules.workflows.processing.core.actionExecutor import ActionExecutor from modules.workflows.processing.core.actionExecutor import ActionExecutor
@ -46,8 +46,8 @@ class BaseMode(ABC):
pass pass
@abstractmethod @abstractmethod
async def generateTaskActions(self, taskStep: TaskStep, workflow: ChatWorkflow, async def generateActionItems(self, taskStep: TaskStep, workflow: ChatWorkflow,
previousResults: List = None, enhancedContext: TaskContext = None) -> List[TaskAction]: previousResults: List = None, enhancedContext: TaskContext = None) -> List[ActionItem]:
"""Generate actions for a task step - must be implemented by concrete modes""" """Generate actions for a task step - must be implemented by concrete modes"""
pass pass

View file

@ -7,8 +7,8 @@ import re
import time import time
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import List, Dict, Any from typing import List, Dict, Any
from modules.datamodels.datamodelWorkflow import ( from modules.datamodels.datamodelChat import (
TaskStep, TaskContext, TaskResult, TaskAction, TaskStatus, TaskStep, TaskContext, TaskResult, ActionItem, TaskStatus,
ActionResult ActionResult
) )
from modules.datamodels.datamodelChat import ChatWorkflow from modules.datamodels.datamodelChat import ChatWorkflow
@ -38,8 +38,8 @@ class ReactMode(BaseMode):
self.currentIntent = None self.currentIntent = None
# Placeholder service no longer used; prompts are generated directly # Placeholder service no longer used; prompts are generated directly
async def generateTaskActions(self, taskStep: TaskStep, workflow: ChatWorkflow, async def generateActionItems(self, taskStep: TaskStep, workflow: ChatWorkflow,
previousResults: List = None, enhancedContext: TaskContext = None) -> List[TaskAction]: previousResults: List = None, enhancedContext: TaskContext = None) -> List[ActionItem]:
"""React mode doesn't use batch action generation - actions are generated iteratively""" """React mode doesn't use batch action generation - actions are generated iteratively"""
# React mode generates actions one at a time in the execution loop # React mode generates actions one at a time in the execution loop
return [] 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): if 'language' not in parameters and hasattr(self.services, 'user') and getattr(self.services.user, 'language', None):
parameters['language'] = self.services.user.language 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) currentRound = getattr(self.workflow, 'currentRound', 0)
currentTask = getattr(self.workflow, 'currentTask', 0) currentTask = getattr(self.workflow, 'currentTask', 0)
resultLabel = f"round{currentRound}_task{currentTask}_action{stepIndex}_results" resultLabel = f"round{currentRound}_task{currentTask}_action{stepIndex}_results"
taskAction = self._createTaskAction({ taskAction = self._createActionItem({
"execMethod": methodName, "execMethod": methodName,
"execAction": actionName, "execAction": actionName,
"execParameters": parameters, "execParameters": parameters,
@ -463,7 +463,7 @@ class ReactMode(BaseMode):
async def _refineDecide(self, context: TaskContext, observation: Dict[str, Any]) -> Dict[str, Any]: async def _refineDecide(self, context: TaskContext, observation: Dict[str, Any]) -> Dict[str, Any]:
"""Refine: decide continue or stop, with reason""" """Refine: decide continue or stop, with reason"""
# Create proper ReviewContext for extractReviewContent # Create proper ReviewContext for extractReviewContent
from modules.datamodels.datamodelWorkflow import ReviewContext from modules.datamodels.datamodelChat import ReviewContext
reviewContext = ReviewContext( reviewContext = ReviewContext(
task_step=context.task_step, task_step=context.task_step,
task_actions=[], 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)}") logger.error(f"Error generating action result message: {str(e)}")
return f"{method}.{actionName} action completed" 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""" """Creates a new task action for React mode"""
try: try:
import uuid import uuid
@ -686,14 +686,14 @@ Return only the user-friendly message, no technical details."""
if "execParameters" not in actionData: if "execParameters" not in actionData:
actionData["execParameters"] = {} actionData["execParameters"] = {}
# Use generic field separation based on TaskAction model # Use generic field separation based on ActionItem model
simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(TaskAction, actionData) simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(ActionItem, actionData)
# Create action in database # Create action in database
createdAction = self.services.interfaceDbChat.db.recordCreate(TaskAction, simpleFields) createdAction = self.services.interfaceDbChat.db.recordCreate(ActionItem, simpleFields)
# Convert to TaskAction model # Convert to ActionItem model
return TaskAction( return ActionItem(
id=createdAction["id"], id=createdAction["id"],
execMethod=createdAction["execMethod"], execMethod=createdAction["execMethod"],
execAction=createdAction["execAction"], execAction=createdAction["execAction"],
@ -753,7 +753,7 @@ Return only the user-friendly message, no technical details."""
except Exception as e: except Exception as e:
logger.error(f"Error updating workflow before executing action: {str(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""" """Creates a new task action for React mode"""
try: try:
import uuid import uuid
@ -777,14 +777,14 @@ Return only the user-friendly message, no technical details."""
if "execParameters" not in actionData: if "execParameters" not in actionData:
actionData["execParameters"] = {} actionData["execParameters"] = {}
# Use generic field separation based on TaskAction model # Use generic field separation based on ActionItem model
simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(TaskAction, actionData) simpleFields, objectFields = self.services.interfaceDbChat._separate_object_fields(ActionItem, actionData)
# Create action in database # Create action in database
createdAction = self.services.interfaceDbChat.db.recordCreate(TaskAction, simpleFields) createdAction = self.services.interfaceDbChat.db.recordCreate(ActionItem, simpleFields)
# Convert to TaskAction model # Convert to ActionItem model
return TaskAction( return ActionItem(
id=createdAction["id"], id=createdAction["id"],
execMethod=createdAction["execMethod"], execMethod=createdAction["execMethod"],
execAction=createdAction["execAction"], execAction=createdAction["execAction"],

View file

@ -4,8 +4,8 @@
import logging import logging
from typing import List from typing import List
from datetime import datetime, UTC from datetime import datetime, UTC
from modules.datamodels.datamodelWorkflow import TaskStep from modules.datamodels.datamodelChat import TaskStep
from modules.datamodels.datamodelWorkflow import ActionResult from modules.datamodels.datamodelChat import ActionResult
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View file

@ -7,7 +7,7 @@ import importlib
import pkgutil import pkgutil
import inspect import inspect
from typing import Any, Dict, List 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 from modules.workflows.methods.methodBase import MethodBase
# Set up logger # Set up logger

View file

@ -6,7 +6,7 @@ Handles prompt templates and extraction functions for actionplan mode action han
import json import json
import logging import logging
from typing import Dict, Any, List 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 ( from modules.workflows.processing.shared.placeholderFactory import (
extractUserPrompt, extractUserPrompt,
extractAvailableDocumentsSummary, extractAvailableDocumentsSummary,

View file

@ -4,7 +4,7 @@ Handles prompt templates for react mode action handling.
""" """
from typing import Any, List 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 ( from modules.workflows.processing.shared.placeholderFactory import (
extractUserPrompt, extractUserPrompt,
extractUserLanguage, extractUserLanguage,

View file

@ -6,7 +6,7 @@ Handles prompt templates and extraction functions for task planning phase.
import json import json
import logging import logging
from typing import Dict, Any, List 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 ( from modules.workflows.processing.shared.placeholderFactory import (
extractUserPrompt, extractUserPrompt,
extractAvailableDocumentsSummary, extractAvailableDocumentsSummary,

View file

@ -3,7 +3,7 @@
import logging import logging
from typing import Dict, Any, Optional, List 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.datamodels.datamodelChat import ChatWorkflow
from modules.workflows.processing.modes.modeBase import BaseMode from modules.workflows.processing.modes.modeBase import BaseMode
from modules.workflows.processing.modes.modeActionplan import ActionplanMode from modules.workflows.processing.modes.modeActionplan import ActionplanMode
@ -84,7 +84,7 @@ class WorkflowProcessor:
logger.error(f"Error in executeTask: {str(e)}") logger.error(f"Error in executeTask: {str(e)}")
raise raise
async def generateTaskActions(self, taskStep: TaskStep, workflow: ChatWorkflow, async def generateActionItems(self, taskStep: TaskStep, workflow: ChatWorkflow,
previousResults: List = None, enhancedContext: TaskContext = None) -> List: previousResults: List = None, enhancedContext: TaskContext = None) -> List:
"""Generate actions for a task step using the appropriate mode""" """Generate actions for a task step using the appropriate mode"""
try: try:
@ -96,9 +96,9 @@ class WorkflowProcessor:
logger.info(f"Mode: {workflow.workflowMode}") logger.info(f"Mode: {workflow.workflowMode}")
# Delegate to the appropriate mode # 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: except Exception as e:
logger.error(f"Error in generateTaskActions: {str(e)}") logger.error(f"Error in generateActionItems: {str(e)}")
raise raise
def updateWorkflowAfterTaskPlanCreated(self, totalTasks: int): def updateWorkflowAfterTaskPlanCreated(self, totalTasks: int):

View file

@ -8,10 +8,9 @@ from modules.datamodels.datamodelChat import (
UserInputRequest, UserInputRequest,
ChatMessage, ChatMessage,
ChatWorkflow, ChatWorkflow,
ChatDocument, ChatDocument
WorkflowResult
) )
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.workflows.processing.workflowProcessor import WorkflowProcessor, WorkflowStoppedException
from modules.shared.timezoneUtils import get_utc_timestamp from modules.shared.timezoneUtils import get_utc_timestamp
@ -160,8 +159,8 @@ class WorkflowManager:
self.workflowProcessor = WorkflowProcessor(self.services, workflow) self.workflowProcessor = WorkflowProcessor(self.services, workflow)
message = await self._sendFirstMessage(userInput, workflow) message = await self._sendFirstMessage(userInput, workflow)
task_plan = await self._planTasks(userInput, workflow) task_plan = await self._planTasks(userInput, workflow)
workflow_result = await self._executeTasks(task_plan, workflow) await self._executeTasks(task_plan, workflow)
await self._processWorkflowResults(workflow, workflow_result, message) await self._processWorkflowResults(workflow, message)
except WorkflowStoppedException: except WorkflowStoppedException:
self._handleWorkflowStop(workflow) self._handleWorkflowStop(workflow)
@ -359,8 +358,8 @@ class WorkflowManager:
logger.info(f"Executing workflow mode={workflow_mode} with {len(task_plan.tasks)} tasks") logger.info(f"Executing workflow mode={workflow_mode} with {len(task_plan.tasks)} tasks")
return task_plan return task_plan
async def _executeTasks(self, task_plan, workflow: ChatWorkflow) -> WorkflowResult: async def _executeTasks(self, task_plan, workflow: ChatWorkflow) -> None:
"""Execute all tasks in the task plan""" """Execute all tasks in the task plan and update workflow status."""
handling = self.workflowProcessor handling = self.workflowProcessor
total_tasks = len(task_plan.tasks) total_tasks = len(task_plan.tasks)
all_task_results: List = [] all_task_results: List = []
@ -404,16 +403,12 @@ class WorkflowManager:
if task_result.success and task_result.feedback: if task_result.success and task_result.feedback:
previous_results.append(task_result.feedback) previous_results.append(task_result.feedback)
return WorkflowResult( # Mark workflow as completed; error/stop cases update status elsewhere
status="completed", workflow.status = "completed"
completed_tasks=len(all_task_results), return None
total_tasks=total_tasks,
execution_time=0.0,
final_results_count=len(all_task_results)
)
async def _processWorkflowResults(self, workflow: ChatWorkflow, workflow_result: WorkflowResult, initial_message: ChatMessage) -> None: async def _processWorkflowResults(self, workflow: ChatWorkflow, initial_message: ChatMessage) -> None:
"""Process workflow results and create appropriate messages""" """Process workflow results based on workflow status and create appropriate messages"""
try: try:
try: try:
self.workflowProcessor._checkWorkflowStopped(workflow) self.workflowProcessor._checkWorkflowStopped(workflow)
@ -451,7 +446,7 @@ class WorkflowManager:
}) })
return return
if workflow_result.status == 'stopped': if workflow.status == 'stopped':
# Create stopped message # Create stopped message
stopped_message = { stopped_message = {
"workflowId": workflow.id, "workflowId": workflow.id,
@ -493,12 +488,12 @@ class WorkflowManager:
"progress": 100 "progress": 100
}) })
return return
elif workflow_result.status == 'failed': elif workflow.status == 'failed':
# Create error message # Create error message
error_message = { error_message = {
"workflowId": workflow.id, "workflowId": workflow.id,
"role": "assistant", "role": "assistant",
"message": f"Workflow failed: {workflow_result.error or 'Unknown error'}", "message": f"Workflow failed: {'Unknown error'}",
"status": "last", "status": "last",
"sequenceNr": len(workflow.messages) + 1, "sequenceNr": len(workflow.messages) + 1,
"publishedAt": self.services.utils.getUtcTimestamp(), "publishedAt": self.services.utils.getUtcTimestamp(),