gateway/modules/datamodels/datamodelChat.py
2025-10-09 10:36:22 +02:00

956 lines
36 KiB
Python

"""Chat models: ChatWorkflow, ChatMessage, ChatLog, ChatStat, ChatDocument."""
from typing import List, Dict, Any, Optional
from pydantic import BaseModel, Field
from modules.shared.attributeUtils import register_model_labels, ModelMixin
from modules.shared.timezoneUtils import get_utc_timestamp
import uuid
class ChatStat(BaseModel, ModelMixin):
id: str = Field(
default_factory=lambda: str(uuid.uuid4()), description="Primary key"
)
workflowId: Optional[str] = Field(
None, description="Foreign key to workflow (for workflow stats)"
)
messageId: Optional[str] = Field(
None, description="Foreign key to message (for message stats)"
)
processingTime: Optional[float] = Field(
None, description="Processing time in seconds"
)
tokenCount: Optional[int] = Field(None, description="Number of tokens processed")
bytesSent: Optional[int] = Field(None, description="Number of bytes sent")
bytesReceived: Optional[int] = Field(None, description="Number of bytes received")
successRate: Optional[float] = Field(None, description="Success rate of operations")
errorCount: Optional[int] = Field(None, description="Number of errors encountered")
register_model_labels(
"ChatStat",
{"en": "Chat Statistics", "fr": "Statistiques de chat"},
{
"id": {"en": "ID", "fr": "ID"},
"workflowId": {"en": "Workflow ID", "fr": "ID du workflow"},
"messageId": {"en": "Message ID", "fr": "ID du message"},
"processingTime": {"en": "Processing Time", "fr": "Temps de traitement"},
"tokenCount": {"en": "Token Count", "fr": "Nombre de tokens"},
"bytesSent": {"en": "Bytes Sent", "fr": "Octets envoyés"},
"bytesReceived": {"en": "Bytes Received", "fr": "Octets reçus"},
"successRate": {"en": "Success Rate", "fr": "Taux de succès"},
"errorCount": {"en": "Error Count", "fr": "Nombre d'erreurs"},
},
)
class ChatLog(BaseModel, ModelMixin):
id: str = Field(
default_factory=lambda: str(uuid.uuid4()), description="Primary key"
)
workflowId: str = Field(description="Foreign key to workflow")
message: str = Field(description="Log message")
type: str = Field(description="Log type (info, warning, error, etc.)")
timestamp: float = Field(
default_factory=get_utc_timestamp,
description="When the log entry was created (UTC timestamp in seconds)",
)
status: Optional[str] = Field(None, description="Status of the log entry")
progress: Optional[float] = Field(
None, description="Progress indicator (0.0 to 1.0)"
)
performance: Optional[Dict[str, Any]] = Field(
None, description="Performance metrics"
)
register_model_labels(
"ChatLog",
{"en": "Chat Log", "fr": "Journal de chat"},
{
"id": {"en": "ID", "fr": "ID"},
"workflowId": {"en": "Workflow ID", "fr": "ID du flux de travail"},
"message": {"en": "Message", "fr": "Message"},
"type": {"en": "Type", "fr": "Type"},
"timestamp": {"en": "Timestamp", "fr": "Horodatage"},
"status": {"en": "Status", "fr": "Statut"},
"progress": {"en": "Progress", "fr": "Progression"},
"performance": {"en": "Performance", "fr": "Performance"},
},
)
class ChatDocument(BaseModel, ModelMixin):
id: str = Field(
default_factory=lambda: str(uuid.uuid4()), description="Primary key"
)
messageId: str = Field(description="Foreign key to message")
fileId: str = Field(description="Foreign key to file")
fileName: str = Field(description="Name of the file")
fileSize: int = Field(description="Size of the file")
mimeType: str = Field(description="MIME type of the file")
roundNumber: Optional[int] = Field(None, description="Round number in workflow")
taskNumber: Optional[int] = Field(None, description="Task number within round")
actionNumber: Optional[int] = Field(None, description="Action number within task")
actionId: Optional[str] = Field(
None, description="ID of the action that created this document"
)
register_model_labels(
"ChatDocument",
{"en": "Chat Document", "fr": "Document de chat"},
{
"id": {"en": "ID", "fr": "ID"},
"messageId": {"en": "Message ID", "fr": "ID du message"},
"fileId": {"en": "File ID", "fr": "ID du fichier"},
"fileName": {"en": "File Name", "fr": "Nom du fichier"},
"fileSize": {"en": "File Size", "fr": "Taille du fichier"},
"mimeType": {"en": "MIME Type", "fr": "Type MIME"},
"roundNumber": {"en": "Round Number", "fr": "Numéro de tour"},
"taskNumber": {"en": "Task Number", "fr": "Numéro de tâche"},
"actionNumber": {"en": "Action Number", "fr": "Numéro d'action"},
"actionId": {"en": "Action ID", "fr": "ID de l'action"},
},
)
class ContentMetadata(BaseModel, ModelMixin):
size: int = Field(description="Content size in bytes")
pages: Optional[int] = Field(
None, description="Number of pages for multi-page content"
)
error: Optional[str] = Field(None, description="Processing error if any")
width: Optional[int] = Field(None, description="Width in pixels for images/videos")
height: Optional[int] = Field(
None, description="Height in pixels for images/videos"
)
colorMode: Optional[str] = Field(None, description="Color mode")
fps: Optional[float] = Field(None, description="Frames per second for videos")
durationSec: Optional[float] = Field(
None, description="Duration in seconds for media"
)
mimeType: str = Field(description="MIME type of the content")
base64Encoded: bool = Field(description="Whether the data is base64 encoded")
register_model_labels(
"ContentMetadata",
{"en": "Content Metadata", "fr": "Métadonnées du contenu"},
{
"size": {"en": "Size", "fr": "Taille"},
"pages": {"en": "Pages", "fr": "Pages"},
"error": {"en": "Error", "fr": "Erreur"},
"width": {"en": "Width", "fr": "Largeur"},
"height": {"en": "Height", "fr": "Hauteur"},
"colorMode": {"en": "Color Mode", "fr": "Mode de couleur"},
"fps": {"en": "FPS", "fr": "IPS"},
"durationSec": {"en": "Duration", "fr": "Durée"},
"mimeType": {"en": "MIME Type", "fr": "Type MIME"},
"base64Encoded": {"en": "Base64 Encoded", "fr": "Encodé en Base64"},
},
)
class ContentItem(BaseModel, ModelMixin):
label: str = Field(description="Content label")
data: str = Field(description="Extracted text content")
metadata: ContentMetadata = Field(description="Content metadata")
register_model_labels(
"ContentItem",
{"en": "Content Item", "fr": "Élément de contenu"},
{
"label": {"en": "Label", "fr": "Étiquette"},
"data": {"en": "Data", "fr": "Données"},
"metadata": {"en": "Metadata", "fr": "Métadonnées"},
},
)
class ChatContentExtracted(BaseModel, ModelMixin):
id: str = Field(description="Reference to source ChatDocument")
contents: List[ContentItem] = Field(
default_factory=list, description="List of content items"
)
register_model_labels(
"ChatContentExtracted",
{"en": "Extracted Content", "fr": "Contenu extrait"},
{
"id": {"en": "Object ID", "fr": "ID de l'objet"},
"contents": {"en": "Contents", "fr": "Contenus"},
},
)
class ChatMessage(BaseModel, ModelMixin):
id: str = Field(
default_factory=lambda: str(uuid.uuid4()), description="Primary key"
)
workflowId: str = Field(description="Foreign key to workflow")
parentMessageId: Optional[str] = Field(
None, description="Parent message ID for threading"
)
documents: List[ChatDocument] = Field(
default_factory=list, description="Associated documents"
)
documentsLabel: Optional[str] = Field(
None, description="Label for the set of documents"
)
message: Optional[str] = Field(None, description="Message content")
summary: Optional[str] = Field(
None, description="Short summary of this message for planning/history"
)
role: str = Field(description="Role of the message sender")
status: str = Field(description="Status of the message (first, step, last)")
sequenceNr: int = Field(
description="Sequence number of the message (set automatically)"
)
publishedAt: float = Field(
default_factory=get_utc_timestamp,
description="When the message was published (UTC timestamp in seconds)",
)
stats: Optional[ChatStat] = Field(None, description="Statistics for this message")
success: Optional[bool] = Field(
None, description="Whether the message processing was successful"
)
actionId: Optional[str] = Field(
None, description="ID of the action that produced this message"
)
actionMethod: Optional[str] = Field(
None, description="Method of the action that produced this message"
)
actionName: Optional[str] = Field(
None, description="Name of the action that produced this message"
)
roundNumber: Optional[int] = Field(None, description="Round number in workflow")
taskNumber: Optional[int] = Field(None, description="Task number within round")
actionNumber: Optional[int] = Field(None, description="Action number within task")
taskProgress: Optional[str] = Field(
None, description="Task progress status: pending, running, success, fail, retry"
)
actionProgress: Optional[str] = Field(
None, description="Action progress status: pending, running, success, fail"
)
register_model_labels(
"ChatMessage",
{"en": "Chat Message", "fr": "Message de chat"},
{
"id": {"en": "ID", "fr": "ID"},
"workflowId": {"en": "Workflow ID", "fr": "ID du flux de travail"},
"parentMessageId": {"en": "Parent Message ID", "fr": "ID du message parent"},
"documents": {"en": "Documents", "fr": "Documents"},
"documentsLabel": {"en": "Documents Label", "fr": "Label des documents"},
"message": {"en": "Message", "fr": "Message"},
"summary": {"en": "Summary", "fr": "Résumé"},
"role": {"en": "Role", "fr": "Rôle"},
"status": {"en": "Status", "fr": "Statut"},
"sequenceNr": {"en": "Sequence Number", "fr": "Numéro de séquence"},
"publishedAt": {"en": "Published At", "fr": "Publié le"},
"stats": {"en": "Statistics", "fr": "Statistiques"},
"success": {"en": "Success", "fr": "Succès"},
"actionId": {"en": "Action ID", "fr": "ID de l'action"},
"actionMethod": {"en": "Action Method", "fr": "Méthode de l'action"},
"actionName": {"en": "Action Name", "fr": "Nom de l'action"},
"roundNumber": {"en": "Round Number", "fr": "Numéro de tour"},
"taskNumber": {"en": "Task Number", "fr": "Numéro de tâche"},
"actionNumber": {"en": "Action Number", "fr": "Numéro d'action"},
"taskProgress": {"en": "Task Progress", "fr": "Progression de la tâche"},
"actionProgress": {"en": "Action Progress", "fr": "Progression de l'action"},
},
)
class ChatWorkflow(BaseModel, ModelMixin):
id: str = Field(
default_factory=lambda: str(uuid.uuid4()),
description="Primary key",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
mandateId: str = Field(
description="ID of the mandate this workflow belongs to",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
status: str = Field(
description="Current status of the workflow",
frontend_type="select",
frontend_readonly=False,
frontend_required=False,
frontend_options=[
{"value": "running", "label": {"en": "Running", "fr": "En cours"}},
{"value": "completed", "label": {"en": "Completed", "fr": "Terminé"}},
{"value": "stopped", "label": {"en": "Stopped", "fr": "Arrêté"}},
{"value": "error", "label": {"en": "Error", "fr": "Erreur"}},
],
)
name: Optional[str] = Field(
None,
description="Name of the workflow",
frontend_type="text",
frontend_readonly=False,
frontend_required=True,
)
currentRound: int = Field(
description="Current round number",
frontend_type="integer",
frontend_readonly=True,
frontend_required=False,
)
currentTask: int = Field(
default=0,
description="Current task number",
frontend_type="integer",
frontend_readonly=True,
frontend_required=False,
)
currentAction: int = Field(
default=0,
description="Current action number",
frontend_type="integer",
frontend_readonly=True,
frontend_required=False,
)
totalTasks: int = Field(
default=0,
description="Total number of tasks in the workflow",
frontend_type="integer",
frontend_readonly=True,
frontend_required=False,
)
totalActions: int = Field(
default=0,
description="Total number of actions in the workflow",
frontend_type="integer",
frontend_readonly=True,
frontend_required=False,
)
lastActivity: float = Field(
default_factory=get_utc_timestamp,
description="Timestamp of last activity (UTC timestamp in seconds)",
frontend_type="timestamp",
frontend_readonly=True,
frontend_required=False,
)
startedAt: float = Field(
default_factory=get_utc_timestamp,
description="When the workflow started (UTC timestamp in seconds)",
frontend_type="timestamp",
frontend_readonly=True,
frontend_required=False,
)
logs: List[ChatLog] = Field(
default_factory=list,
description="Workflow logs",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
messages: List[ChatMessage] = Field(
default_factory=list,
description="Messages in the workflow",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
stats: Optional[ChatStat] = Field(
None,
description="Workflow statistics",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
tasks: list = Field(
default_factory=list,
description="List of tasks in the workflow",
frontend_type="text",
frontend_readonly=True,
frontend_required=False,
)
workflowMode: str = Field(
default="Actionplan",
description="Workflow mode selector",
frontend_type="select",
frontend_readonly=False,
frontend_required=False,
frontend_options=[
{
"value": "Actionplan",
"label": {"en": "Action Plan", "fr": "Plan d'actions"},
},
{"value": "React", "label": {"en": "React", "fr": "Réactif"}},
],
)
maxSteps: int = Field(
default=5,
description="Maximum number of iterations in react mode",
frontend_type="integer",
frontend_readonly=False,
frontend_required=False,
)
register_model_labels(
"ChatWorkflow",
{"en": "Chat Workflow", "fr": "Flux de travail de chat"},
{
"id": {"en": "ID", "fr": "ID"},
"mandateId": {"en": "Mandate ID", "fr": "ID du mandat"},
"status": {"en": "Status", "fr": "Statut"},
"name": {"en": "Name", "fr": "Nom"},
"currentRound": {"en": "Current Round", "fr": "Tour actuel"},
"currentTask": {"en": "Current Task", "fr": "Tâche actuelle"},
"currentAction": {"en": "Current Action", "fr": "Action actuelle"},
"totalTasks": {"en": "Total Tasks", "fr": "Total des tâches"},
"totalActions": {"en": "Total Actions", "fr": "Total des actions"},
"lastActivity": {"en": "Last Activity", "fr": "Dernière activité"},
"startedAt": {"en": "Started At", "fr": "Démarré le"},
"logs": {"en": "Logs", "fr": "Journaux"},
"messages": {"en": "Messages", "fr": "Messages"},
"stats": {"en": "Statistics", "fr": "Statistiques"},
"tasks": {"en": "Tasks", "fr": "Tâches"},
"workflowMode": {"en": "Workflow Mode", "fr": "Mode de workflow"},
"maxSteps": {"en": "Max Steps", "fr": "Étapes max"},
},
)
class UserInputRequest(BaseModel, ModelMixin):
prompt: str = Field(description="Prompt for the user")
listFileId: List[str] = Field(default_factory=list, description="List of file IDs")
userLanguage: str = Field(default="en", description="User's preferred language")
register_model_labels(
"UserInputRequest",
{"en": "User Input Request", "fr": "Demande de saisie utilisateur"},
{
"prompt": {"en": "Prompt", "fr": "Invite"},
"listFileId": {"en": "File IDs", "fr": "IDs des fichiers"},
"userLanguage": {"en": "User Language", "fr": "Langue de l'utilisateur"},
},
)
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"},
},
)