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 datamodelSecurity as security
from . import datamodelNeutralizer as neutralizer
from . import datamodelWorkflow as workflow
from . import datamodelChat as chat
from . import datamodelFiles as files
from . import datamodelVoice as voice

View file

@ -6,7 +6,6 @@ from modules.shared.attributeUtils import register_model_labels, ModelMixin
from modules.shared.timezoneUtils import get_utc_timestamp
import uuid
class ChatStat(BaseModel, ModelMixin):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key")
workflowId: Optional[str] = Field(None, description="Foreign key to workflow (for workflow stats)")
@ -17,8 +16,6 @@ class ChatStat(BaseModel, ModelMixin):
bytesReceived: Optional[int] = Field(None, description="Number of bytes received")
successRate: Optional[float] = Field(None, description="Success rate of operations")
errorCount: Optional[int] = Field(None, description="Number of errors encountered")
register_model_labels(
"ChatStat",
{"en": "Chat Statistics", "fr": "Statistiques de chat"},
@ -35,7 +32,6 @@ register_model_labels(
},
)
class ChatLog(BaseModel, ModelMixin):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key")
workflowId: str = Field(description="Foreign key to workflow")
@ -45,8 +41,6 @@ class ChatLog(BaseModel, ModelMixin):
status: Optional[str] = Field(None, description="Status of the log entry")
progress: Optional[float] = Field(None, description="Progress indicator (0.0 to 1.0)")
performance: Optional[Dict[str, Any]] = Field(None, description="Performance metrics")
register_model_labels(
"ChatLog",
{"en": "Chat Log", "fr": "Journal de chat"},
@ -62,7 +56,6 @@ register_model_labels(
},
)
class ChatDocument(BaseModel, ModelMixin):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key")
messageId: str = Field(description="Foreign key to message")
@ -74,8 +67,6 @@ class ChatDocument(BaseModel, ModelMixin):
taskNumber: Optional[int] = Field(None, description="Task number within round")
actionNumber: Optional[int] = Field(None, description="Action number within task")
actionId: Optional[str] = Field(None, description="ID of the action that created this document")
register_model_labels(
"ChatDocument",
{"en": "Chat Document", "fr": "Document de chat"},
@ -93,7 +84,6 @@ register_model_labels(
},
)
class ContentMetadata(BaseModel, ModelMixin):
size: int = Field(description="Content size in bytes")
pages: Optional[int] = Field(None, description="Number of pages for multi-page content")
@ -105,8 +95,6 @@ class ContentMetadata(BaseModel, ModelMixin):
durationSec: Optional[float] = Field(None, description="Duration in seconds for media")
mimeType: str = Field(description="MIME type of the content")
base64Encoded: bool = Field(description="Whether the data is base64 encoded")
register_model_labels(
"ContentMetadata",
{"en": "Content Metadata", "fr": "Métadonnées du contenu"},
@ -124,13 +112,10 @@ register_model_labels(
},
)
class ContentItem(BaseModel, ModelMixin):
label: str = Field(description="Content label")
data: str = Field(description="Extracted text content")
metadata: ContentMetadata = Field(description="Content metadata")
register_model_labels(
"ContentItem",
{"en": "Content Item", "fr": "Élément de contenu"},
@ -141,14 +126,11 @@ register_model_labels(
},
)
class ExtractedContent(BaseModel, ModelMixin):
class ChatContentExtracted(BaseModel, ModelMixin):
id: str = Field(description="Reference to source ChatDocument")
contents: List[ContentItem] = Field(default_factory=list, description="List of content items")
register_model_labels(
"ExtractedContent",
"ChatContentExtracted",
{"en": "Extracted Content", "fr": "Contenu extrait"},
{
"id": {"en": "Object ID", "fr": "ID de l'objet"},
@ -177,8 +159,6 @@ class ChatMessage(BaseModel, ModelMixin):
actionNumber: Optional[int] = Field(None, description="Action number within task")
taskProgress: Optional[str] = Field(None, description="Task progress status: pending, running, success, fail, retry")
actionProgress: Optional[str] = Field(None, description="Action progress status: pending, running, success, fail")
register_model_labels(
"ChatMessage",
{"en": "Chat Message", "fr": "Message de chat"},
@ -206,7 +186,6 @@ register_model_labels(
},
)
class ChatWorkflow(BaseModel, ModelMixin):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key", frontend_type="text", frontend_readonly=True, frontend_required=False)
mandateId: str = Field(description="ID of the mandate this workflow belongs to", frontend_type="text", frontend_readonly=True, frontend_required=False)
@ -233,8 +212,6 @@ class ChatWorkflow(BaseModel, ModelMixin):
{"value": "React", "label": {"en": "React", "fr": "Réactif"}},
])
maxSteps: int = Field(default=5, description="Maximum number of iterations in react mode", frontend_type="integer", frontend_readonly=False, frontend_required=False)
register_model_labels(
"ChatWorkflow",
{"en": "Chat Workflow", "fr": "Flux de travail de chat"},
@ -259,41 +236,12 @@ register_model_labels(
},
)
class WorkflowResult(BaseModel, ModelMixin):
status: str
completed_tasks: int
total_tasks: int
execution_time: float
final_results_count: int
error: Optional[str] = None
phase: Optional[str] = None
register_model_labels(
"WorkflowResult",
{"en": "Workflow Result", "fr": "Résultat du workflow"},
{
"status": {"en": "Status", "fr": "Statut"},
"completed_tasks": {"en": "Completed Tasks", "fr": "Tâches terminées"},
"total_tasks": {"en": "Total Tasks", "fr": "Total des tâches"},
"execution_time": {"en": "Execution Time", "fr": "Temps d'exécution"},
"final_results_count": {"en": "Final Results Count", "fr": "Nombre de résultats finaux"},
"error": {"en": "Error", "fr": "Erreur"},
"phase": {"en": "Phase", "fr": "Phase"},
},
)
class UserInputRequest(BaseModel, ModelMixin):
prompt: str = Field(description="Prompt for the user")
listFileId: List[str] = Field(default_factory=list, description="List of file IDs")
userLanguage: str = Field(default="en", description="User's preferred language")
register_model_labels(
"UserInputRequest",
{"en": "User Input Request", "fr": "Demande de saisie utilisateur"},
"UserInputRequest", {"en": "User Input Request", "fr": "Demande de saisie utilisateur"},
{
"prompt": {"en": "Prompt", "fr": "Invite"},
"listFileId": {"en": "File IDs", "fr": "IDs des fichiers"},
@ -301,4 +249,389 @@ register_model_labels(
},
)
class ActionDocument(BaseModel, ModelMixin):
"""Clear document structure for action results"""
documentName: str = Field(description="Name of the document")
documentData: Any = Field(description="Content/data of the document")
mimeType: str = Field(description="MIME type of the document")
register_model_labels(
"ActionDocument",
{"en": "Action Document", "fr": "Document d'action"},
{
"documentName": {"en": "Document Name", "fr": "Nom du document"},
"documentData": {"en": "Document Data", "fr": "Données du document"},
"mimeType": {"en": "MIME Type", "fr": "Type MIME"},
},
)
class ActionResult(BaseModel, ModelMixin):
"""Clean action result with documents as primary output
IMPORTANT: Action methods should NOT set resultLabel in their return value.
The resultLabel is managed by the action handler using the action's execResultLabel
from the action plan. This ensures consistent document routing throughout the workflow.
"""
success: bool = Field(description="Whether execution succeeded")
error: Optional[str] = Field(None, description="Error message if failed")
documents: List[ActionDocument] = Field(default_factory=list, description="Document outputs")
resultLabel: Optional[str] = Field(None, description="Label for document routing (set by action handler, not by action methods)")
@classmethod
def isSuccess(cls, documents: List[ActionDocument] = None) -> "ActionResult":
return cls(success=True, documents=documents or [])
@classmethod
def isFailure(cls, error: str, documents: List[ActionDocument] = None) -> "ActionResult":
return cls(success=False, documents=documents or [], error=error)
register_model_labels(
"ActionResult",
{"en": "Action Result", "fr": "Résultat de l'action"},
{
"success": {"en": "Success", "fr": "Succès"},
"error": {"en": "Error", "fr": "Erreur"},
"documents": {"en": "Documents", "fr": "Documents"},
"resultLabel": {"en": "Result Label", "fr": "Étiquette du résultat"},
},
)
class ActionSelection(BaseModel, ModelMixin):
method: str = Field(description="Method to execute (e.g., web, document, ai)")
name: str = Field(description="Action name within the method (e.g., search, extract)")
register_model_labels(
"ActionSelection",
{"en": "Action Selection", "fr": "Sélection d'action"},
{
"method": {"en": "Method", "fr": "Méthode"},
"name": {"en": "Action Name", "fr": "Nom de l'action"},
},
)
class ActionParameters(BaseModel, ModelMixin):
parameters: Dict[str, Any] = Field(default_factory=dict, description="Parameters to execute the selected action")
register_model_labels(
"ActionParameters",
{"en": "Action Parameters", "fr": "Paramètres d'action"},
{
"parameters": {"en": "Parameters", "fr": "Paramètres"},
},
)
class ObservationPreview(BaseModel, ModelMixin):
name: str = Field(description="Document name or URL label")
mime: str = Field(description="MIME type or kind")
snippet: str = Field(description="Short snippet or summary")
register_model_labels(
"ObservationPreview",
{"en": "Observation Preview", "fr": "Aperçu d'observation"},
{
"name": {"en": "Name", "fr": "Nom"},
"mime": {"en": "MIME", "fr": "MIME"},
"snippet": {"en": "Snippet", "fr": "Extrait"},
},
)
class Observation(BaseModel, ModelMixin):
success: bool = Field(description="Action execution success flag")
resultLabel: str = Field(description="Deterministic label for produced documents")
documentsCount: int = Field(description="Number of produced documents")
previews: List[ObservationPreview] = Field(default_factory=list, description="Compact previews of outputs")
notes: List[str] = Field(default_factory=list, description="Short notes or key facts")
register_model_labels(
"Observation",
{"en": "Observation", "fr": "Observation"},
{
"success": {"en": "Success", "fr": "Succès"},
"resultLabel": {"en": "Result Label", "fr": "Étiquette du résultat"},
"documentsCount": {"en": "Documents Count", "fr": "Nombre de documents"},
"previews": {"en": "Previews", "fr": "Aperçus"},
"notes": {"en": "Notes", "fr": "Notes"},
},
)
class TaskStatus(str):
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
register_model_labels(
"TaskStatus",
{"en": "Task Status", "fr": "Statut de la tâche"},
{
"PENDING": {"en": "Pending", "fr": "En attente"},
"RUNNING": {"en": "Running", "fr": "En cours"},
"COMPLETED": {"en": "Completed", "fr": "Terminé"},
"FAILED": {"en": "Failed", "fr": "Échec"},
"CANCELLED": {"en": "Cancelled", "fr": "Annulé"},
},
)
class DocumentExchange(BaseModel, ModelMixin):
documentsLabel: str = Field(description="Label for the set of documents")
documents: List[str] = Field(default_factory=list, description="List of document references")
register_model_labels(
"DocumentExchange",
{"en": "Document Exchange", "fr": "Échange de documents"},
{
"documentsLabel": {"en": "Documents Label", "fr": "Label des documents"},
"documents": {"en": "Documents", "fr": "Documents"},
},
)
class ActionItem(BaseModel, ModelMixin):
id: str = Field(..., description="Action ID")
execMethod: str = Field(..., description="Method to execute")
execAction: str = Field(..., description="Action to perform")
execParameters: Dict[str, Any] = Field(default_factory=dict, description="Action parameters")
execResultLabel: Optional[str] = Field(None, description="Label for the set of result documents")
expectedDocumentFormats: Optional[List[Dict[str, str]]] = Field(None, description="Expected document formats (optional)")
userMessage: Optional[str] = Field(None, description="User-friendly message in user's language")
status: TaskStatus = Field(default=TaskStatus.PENDING, description="Action status")
error: Optional[str] = Field(None, description="Error message if action failed")
retryCount: int = Field(default=0, description="Number of retries attempted")
retryMax: int = Field(default=3, description="Maximum number of retries")
processingTime: Optional[float] = Field(None, description="Processing time in seconds")
timestamp: float = Field(..., description="When the action was executed (UTC timestamp in seconds)")
result: Optional[str] = Field(None, description="Result of the action")
def setSuccess(self, result: str = None) -> None:
"""Set the action as successful with optional result"""
self.status = TaskStatus.COMPLETED
self.error = None
if result is not None:
self.result = result
def setError(self, error_message: str) -> None:
"""Set the action as failed with error message"""
self.status = TaskStatus.FAILED
self.error = error_message
register_model_labels(
"ActionItem",
{"en": "Task Action", "fr": "Action de tâche"},
{
"id": {"en": "Action ID", "fr": "ID de l'action"},
"execMethod": {"en": "Method", "fr": "Méthode"},
"execAction": {"en": "Action", "fr": "Action"},
"execParameters": {"en": "Parameters", "fr": "Paramètres"},
"execResultLabel": {"en": "Result Label", "fr": "Label du résultat"},
"expectedDocumentFormats": {"en": "Expected Document Formats", "fr": "Formats de documents attendus"},
"userMessage": {"en": "User Message", "fr": "Message utilisateur"},
"status": {"en": "Status", "fr": "Statut"},
"error": {"en": "Error", "fr": "Erreur"},
"retryCount": {"en": "Retry Count", "fr": "Nombre de tentatives"},
"retryMax": {"en": "Max Retries", "fr": "Tentatives max"},
"processingTime": {"en": "Processing Time", "fr": "Temps de traitement"},
"timestamp": {"en": "Timestamp", "fr": "Horodatage"},
"result": {"en": "Result", "fr": "Résultat"},
},
)
class TaskResult(BaseModel, ModelMixin):
taskId: str = Field(..., description="Task ID")
status: TaskStatus = Field(default=TaskStatus.PENDING, description="Task status")
success: bool = Field(..., description="Whether the task was successful")
feedback: Optional[str] = Field(None, description="Task feedback message")
error: Optional[str] = Field(None, description="Error message if task failed")
register_model_labels(
"TaskResult",
{"en": "Task Result", "fr": "Résultat de tâche"},
{
"taskId": {"en": "Task ID", "fr": "ID de la tâche"},
"status": {"en": "Status", "fr": "Statut"},
"success": {"en": "Success", "fr": "Succès"},
"feedback": {"en": "Feedback", "fr": "Retour"},
"error": {"en": "Error", "fr": "Erreur"},
},
)
class TaskItem(BaseModel, ModelMixin):
id: str = Field(..., description="Task ID")
workflowId: str = Field(..., description="Workflow ID")
userInput: str = Field(..., description="User input that triggered the task")
status: TaskStatus = Field(default=TaskStatus.PENDING, description="Task status")
error: Optional[str] = Field(None, description="Error message if task failed")
startedAt: Optional[float] = Field(None, description="When the task started (UTC timestamp in seconds)")
finishedAt: Optional[float] = Field(None, description="When the task finished (UTC timestamp in seconds)")
actionList: List[ActionItem] = Field(default_factory=list, description="List of actions to execute")
retryCount: int = Field(default=0, description="Number of retries attempted")
retryMax: int = Field(default=3, description="Maximum number of retries")
rollbackOnFailure: bool = Field(default=True, description="Whether to rollback on failure")
dependencies: List[str] = Field(default_factory=list, description="List of task IDs this task depends on")
feedback: Optional[str] = Field(None, description="Task feedback message")
processingTime: Optional[float] = Field(None, description="Total processing time in seconds")
resultLabels: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Map of result labels to their values")
register_model_labels(
"TaskItem",
{"en": "Task", "fr": "Tâche"},
{
"id": {"en": "Task ID", "fr": "ID de la tâche"},
"workflowId": {"en": "Workflow ID", "fr": "ID du workflow"},
"userInput": {"en": "User Input", "fr": "Entrée utilisateur"},
"status": {"en": "Status", "fr": "Statut"},
"error": {"en": "Error", "fr": "Erreur"},
"startedAt": {"en": "Started At", "fr": "Démarré à"},
"finishedAt": {"en": "Finished At", "fr": "Terminé à"},
"actionList": {"en": "Actions", "fr": "Actions"},
"retryCount": {"en": "Retry Count", "fr": "Nombre de tentatives"},
"retryMax": {"en": "Max Retries", "fr": "Tentatives max"},
"processingTime": {"en": "Processing Time", "fr": "Temps de traitement"},
},
)
class TaskStep(BaseModel, ModelMixin):
id: str
objective: str
dependencies: Optional[list[str]] = Field(default_factory=list)
success_criteria: Optional[list[str]] = Field(default_factory=list)
estimated_complexity: Optional[str] = None
userMessage: Optional[str] = Field(None, description="User-friendly message in user's language")
register_model_labels(
"TaskStep",
{"en": "Task Step", "fr": "Étape de tâche"},
{
"id": {"en": "ID", "fr": "ID"},
"objective": {"en": "Objective", "fr": "Objectif"},
"dependencies": {"en": "Dependencies", "fr": "Dépendances"},
"success_criteria": {"en": "Success Criteria", "fr": "Critères de succès"},
"estimated_complexity": {"en": "Estimated Complexity", "fr": "Complexité estimée"},
"userMessage": {"en": "User Message", "fr": "Message utilisateur"},
},
)
class TaskHandover(BaseModel, ModelMixin):
taskId: str = Field(description="Target task ID")
sourceTask: Optional[str] = Field(None, description="Source task ID")
inputDocuments: List[DocumentExchange] = Field(default_factory=list, description="Available input documents")
outputDocuments: List[DocumentExchange] = Field(default_factory=list, description="Produced output documents")
context: Dict[str, Any] = Field(default_factory=dict, description="Task context")
previousResults: List[str] = Field(default_factory=list, description="Previous result summaries")
improvements: List[str] = Field(default_factory=list, description="Improvement suggestions")
workflowSummary: Optional[str] = Field(None, description="Summarized workflow context")
messageHistory: List[str] = Field(default_factory=list, description="Key message summaries")
timestamp: float = Field(..., description="When the handover was created (UTC timestamp in seconds)")
handoverType: str = Field(default="task", description="Type of handover: task, phase, or workflow")
register_model_labels(
"TaskHandover",
{"en": "Task Handover", "fr": "Transfert de tâche"},
{
"taskId": {"en": "Task ID", "fr": "ID de la tâche"},
"sourceTask": {"en": "Source Task", "fr": "Tâche source"},
"inputDocuments": {"en": "Input Documents", "fr": "Documents d'entrée"},
"outputDocuments": {"en": "Output Documents", "fr": "Documents de sortie"},
"context": {"en": "Context", "fr": "Contexte"},
"previousResults": {"en": "Previous Results", "fr": "Résultats précédents"},
"improvements": {"en": "Improvements", "fr": "Améliorations"},
"workflowSummary": {"en": "Workflow Summary", "fr": "Résumé du workflow"},
"messageHistory": {"en": "Message History", "fr": "Historique des messages"},
"timestamp": {"en": "Timestamp", "fr": "Horodatage"},
"handoverType": {"en": "Handover Type", "fr": "Type de transfert"},
},
)
class TaskContext(BaseModel, ModelMixin):
task_step: TaskStep
workflow: Optional['ChatWorkflow'] = None
workflow_id: Optional[str] = None
available_documents: Optional[str] = "No documents available"
available_connections: Optional[list[str]] = Field(default_factory=list)
previous_results: Optional[list[str]] = Field(default_factory=list)
previous_handover: Optional[TaskHandover] = None
improvements: Optional[list[str]] = Field(default_factory=list)
retry_count: Optional[int] = 0
previous_action_results: Optional[list] = Field(default_factory=list)
previous_review_result: Optional[dict] = None
is_regeneration: Optional[bool] = False
failure_patterns: Optional[list[str]] = Field(default_factory=list)
failed_actions: Optional[list] = Field(default_factory=list)
successful_actions: Optional[list] = Field(default_factory=list)
criteria_progress: Optional[dict] = None
def getDocumentReferences(self) -> List[str]:
docs = []
if self.previous_handover:
for doc_exchange in self.previous_handover.inputDocuments:
docs.extend(doc_exchange.documents)
return list(set(docs))
def addImprovement(self, improvement: str) -> None:
if improvement not in (self.improvements or []):
if self.improvements is None:
self.improvements = []
self.improvements.append(improvement)
class ReviewContext(BaseModel, ModelMixin):
task_step: TaskStep
task_actions: Optional[list] = Field(default_factory=list)
action_results: Optional[list] = Field(default_factory=list)
step_result: Optional[dict] = Field(default_factory=dict)
workflow_id: Optional[str] = None
previous_results: Optional[list[str]] = Field(default_factory=list)
class ReviewResult(BaseModel, ModelMixin):
status: str
reason: Optional[str] = None
improvements: Optional[list[str]] = Field(default_factory=list)
quality_score: Optional[int] = 5
missing_outputs: Optional[list[str]] = Field(default_factory=list)
met_criteria: Optional[list[str]] = Field(default_factory=list)
unmet_criteria: Optional[list[str]] = Field(default_factory=list)
confidence: Optional[float] = 0.5
userMessage: Optional[str] = Field(None, description="User-friendly message in user's language")
register_model_labels(
"ReviewResult",
{"en": "Review Result", "fr": "Résultat de l'évaluation"},
{
"status": {"en": "Status", "fr": "Statut"},
"reason": {"en": "Reason", "fr": "Raison"},
"improvements": {"en": "Improvements", "fr": "Améliorations"},
"quality_score": {"en": "Quality Score", "fr": "Score de qualité"},
"missing_outputs": {"en": "Missing Outputs", "fr": "Sorties manquantes"},
"met_criteria": {"en": "Met Criteria", "fr": "Critères respectés"},
"unmet_criteria": {"en": "Unmet Criteria", "fr": "Critères non respectés"},
"confidence": {"en": "Confidence", "fr": "Confiance"},
"userMessage": {"en": "User Message", "fr": "Message utilisateur"},
},
)
class TaskPlan(BaseModel, ModelMixin):
overview: str
tasks: list[TaskStep]
userMessage: Optional[str] = Field(None, description="Overall user-friendly message for the task plan")
register_model_labels(
"TaskPlan",
{"en": "Task Plan", "fr": "Plan de tâches"},
{
"overview": {"en": "Overview", "fr": "Aperçu"},
"tasks": {"en": "Tasks", "fr": "Tâches"},
"userMessage": {"en": "User Message", "fr": "Message utilisateur"},
},
)
# Resolve forward references
TaskContext.update_forward_refs()
class PromptPlaceholder(BaseModel, ModelMixin):
label: str
content: str
summaryAllowed: bool = Field(default=False, description="Whether host may summarize content before sending to AI")
register_model_labels(
"PromptPlaceholder",
{"en": "Prompt Placeholder", "fr": "Espace réservé d'invite"},
{
"label": {"en": "Label", "fr": "Libellé"},
"content": {"en": "Content", "fr": "Contenu"},
"summaryAllowed": {"en": "Summary Allowed", "fr": "Résumé autorisé"},
},
)
class PromptBundle(BaseModel, ModelMixin):
prompt: str
placeholders: List[PromptPlaceholder] = Field(default_factory=list)
register_model_labels(
"PromptBundle", {"en": "Prompt Bundle", "fr": "Lot d'invite"},
{
"prompt": {"en": "Prompt", "fr": "Invite"},
"placeholders": {"en": "Placeholders", "fr": "Espaces réservés"},
},
)

View file

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

View file

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

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)
sharepointSourcePath: str = Field(default="", description="SharePoint path to read files for neutralization", frontend_type="text", frontend_readonly=False, frontend_required=False)
sharepointTargetPath: str = Field(default="", description="SharePoint path to store neutralized files", frontend_type="text", frontend_readonly=False, frontend_required=False)
register_model_labels(
"DataNeutraliserConfig",
{"en": "Data Neutralization Config", "fr": "Configuration de neutralisation des données"},
@ -30,7 +28,6 @@ register_model_labels(
},
)
class DataNeutralizerAttributes(BaseModel, ModelMixin):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the attribute mapping (used as UID in neutralized files)", frontend_type="text", frontend_readonly=True, frontend_required=False)
mandateId: str = Field(description="ID of the mandate this attribute belongs to", frontend_type="text", frontend_readonly=True, frontend_required=True)
@ -38,8 +35,6 @@ class DataNeutralizerAttributes(BaseModel, ModelMixin):
originalText: str = Field(description="Original text that was neutralized", frontend_type="text", frontend_readonly=True, frontend_required=True)
fileId: Optional[str] = Field(default=None, description="ID of the file this attribute belongs to", frontend_type="text", frontend_readonly=True, frontend_required=False)
patternType: str = Field(description="Type of pattern that matched (email, phone, name, etc.)", frontend_type="text", frontend_readonly=True, frontend_required=True)
register_model_labels(
"DataNeutralizerAttributes",
{"en": "Neutralized Data Attribute", "fr": "Attribut de données neutralisées"},

View file

@ -13,7 +13,6 @@ class TokenStatus(str, Enum):
ACTIVE = "active"
REVOKED = "revoked"
class Token(BaseModel, ModelMixin):
id: Optional[str] = None
userId: str
@ -33,8 +32,6 @@ class Token(BaseModel, ModelMixin):
class Config:
use_enum_values = True
register_model_labels(
"Token",
{"en": "Token", "fr": "Jeton"},
@ -57,7 +54,6 @@ register_model_labels(
},
)
class AuthEvent(BaseModel, ModelMixin):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the auth event", frontend_type="text", frontend_readonly=True, frontend_required=False)
userId: str = Field(description="ID of the user this event belongs to", frontend_type="text", frontend_readonly=True, frontend_required=True)
@ -67,8 +63,6 @@ class AuthEvent(BaseModel, ModelMixin):
userAgent: Optional[str] = Field(default=None, description="User agent string from the request", frontend_type="text", frontend_readonly=True, frontend_required=False)
success: bool = Field(default=True, description="Whether the authentication event was successful", frontend_type="boolean", frontend_readonly=True, frontend_required=True)
details: Optional[str] = Field(default=None, description="Additional details about the event", frontend_type="text", frontend_readonly=True, frontend_required=False)
register_model_labels(
"AuthEvent",
{"en": "Authentication Event", "fr": "Événement d'authentification"},

View file

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

View file

@ -13,20 +13,17 @@ class AuthAuthority(str, Enum):
GOOGLE = "google"
MSFT = "msft"
class UserPrivilege(str, Enum):
SYSADMIN = "sysadmin"
ADMIN = "admin"
USER = "user"
class ConnectionStatus(str, Enum):
ACTIVE = "active"
EXPIRED = "expired"
REVOKED = "revoked"
PENDING = "pending"
class Mandate(BaseModel, ModelMixin):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the mandate", frontend_type="text", frontend_readonly=True, frontend_required=False)
name: str = Field(description="Name of the mandate", frontend_type="text", frontend_readonly=False, frontend_required=True)
@ -37,8 +34,6 @@ class Mandate(BaseModel, ModelMixin):
{"value": "it", "label": {"en": "Italiano", "fr": "Italien"}},
])
enabled: bool = Field(default=True, description="Indicates whether the mandate is enabled", frontend_type="checkbox", frontend_readonly=False, frontend_required=False)
register_model_labels(
"Mandate",
{"en": "Mandate", "fr": "Mandat"},
@ -50,7 +45,6 @@ register_model_labels(
},
)
class UserConnection(BaseModel, ModelMixin):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the connection", frontend_type="text", frontend_readonly=True, frontend_required=False)
userId: str = Field(description="ID of the user this connection belongs to", frontend_type="text", frontend_readonly=True, frontend_required=False)
@ -77,8 +71,6 @@ class UserConnection(BaseModel, ModelMixin):
{"value": "none", "label": {"en": "None", "fr": "Aucun"}},
])
tokenExpiresAt: Optional[float] = Field(None, description="When the current token expires (UTC timestamp in seconds)", frontend_type="timestamp", frontend_readonly=True, frontend_required=False)
register_model_labels(
"UserConnection",
{"en": "User Connection", "fr": "Connexion utilisateur"},
@ -98,7 +90,6 @@ register_model_labels(
},
)
class User(BaseModel, ModelMixin):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the user", frontend_type="text", frontend_readonly=True, frontend_required=False)
username: str = Field(description="Username for login", frontend_type="text", frontend_readonly=False, frontend_required=True)
@ -122,8 +113,6 @@ class User(BaseModel, ModelMixin):
{"value": "msft", "label": {"en": "Microsoft", "fr": "Microsoft"}},
])
mandateId: Optional[str] = Field(None, description="ID of the mandate this user belongs to", frontend_type="text", frontend_readonly=True, frontend_required=False)
register_model_labels(
"User",
{"en": "User", "fr": "Utilisateur"},
@ -140,15 +129,10 @@ register_model_labels(
},
)
class UserInDB(User):
hashedPassword: Optional[str] = Field(None, description="Hash of the user password")
register_model_labels(
"UserInDB",
{"en": "User Access", "fr": "Accès de l'utilisateur"},
{"hashedPassword": {"en": "Password hash", "fr": "Hachage de mot de passe"}},
)

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

View file

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

View file

@ -1,10 +1,8 @@
"""Web-related modules"""
from abc import ABC, abstractmethod
from pydantic import BaseModel, Field, HttpUrl
from typing import List, Optional, Literal, Dict, Any
from modules.shared.configuration import APP_CONFIG
from modules.datamodels.datamodelWorkflow import ActionDocument, ActionResult
from modules.datamodels.datamodelChat import ActionDocument, ActionResult
WEB_SEARCH_MAX_QUERY_LENGTH: int = int(APP_CONFIG.get("Web_Search_MAX_QUERY_LENGTH", "400"))
@ -27,7 +25,6 @@ class WebResearchOptions(BaseModel):
include_answer: Optional[bool] = Field(default=None, description="Include AI answer")
include_raw_content: Optional[bool] = Field(default=None, description="Include raw content")
class WebResearchRequest(BaseModel):
"""Main web research request"""
user_prompt: str = Field(min_length=1, max_length=WEB_SEARCH_MAX_QUERY_LENGTH, description="User's research question or prompt")
@ -35,20 +32,17 @@ class WebResearchRequest(BaseModel):
max_results: int = Field(default=5, ge=1, le=WEB_SEARCH_MAX_RESULTS, description="Max search results")
options: WebResearchOptions = Field(default_factory=WebResearchOptions, description="Advanced options")
class WebSearchResultItem(BaseModel):
"""Individual search result"""
title: str
url: HttpUrl
raw_content: Optional[str] = Field(default=None, description="Raw HTML content")
class WebCrawlResultItem(BaseModel):
"""Individual crawl result"""
url: HttpUrl
content: str
class WebResearchDocumentData(BaseModel):
"""Complete web research results"""
user_prompt: str
@ -60,21 +54,14 @@ class WebResearchDocumentData(BaseModel):
individual_content: Optional[Dict[str, str]] = None # URL -> content mapping
debug_info: Optional[Dict[str, Any]] = None
class WebResearchActionDocument(ActionDocument):
documentData: WebResearchDocumentData
class WebResearchActionResult(ActionResult):
documents: List[WebResearchActionDocument] = Field(default_factory=list)
class WebResearchBase(ABC):
@abstractmethod
async def web_research(self, request: WebResearchRequest) -> WebResearchActionResult: ...
# Legacy models for connector compatibility
class WebSearchDocumentData(BaseModel):
"""Search results document data"""
query: str
@ -153,5 +140,3 @@ class WebScrapeResultItem(BaseModel):
"""Individual scrape result"""
url: HttpUrl
content: str

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,
WebCrawlRequest,
)
from modules.datamodels.datamodelWorkflow import ActionDocument
from modules.datamodels.datamodelChat import ActionDocument
# 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
from modules.interfaces.interfaceDbChatAccess import ChatAccess
from modules.datamodels.datamodelWorkflow import (
TaskAction,
from modules.datamodels.datamodelChat import (
ActionItem,
TaskResult,
TaskItem,
TaskStatus,

View file

@ -1,6 +1,6 @@
import logging
from typing import Dict, Any, List, Optional, Tuple, Union
from modules.datamodels.datamodelWorkflow import PromptPlaceholder
from modules.datamodels.datamodelChat import PromptPlaceholder
from modules.datamodels.datamodelChat import ChatDocument
from modules.services.serviceExtraction.mainServiceExtraction import ExtractionService
@ -909,7 +909,7 @@ class AiService:
logger.info(f"Extraction completed: {len(extracted_content)} documents")
# Build context from list of ExtractedContent
# Build context from list of extracted content
if isinstance(extracted_content, list):
context_parts = []
chunk_count = 0

View file

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

View file

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

View file

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

View file

@ -8,7 +8,7 @@ from typing import Dict, Any, List, Optional
from datetime import datetime, UTC
from modules.workflows.methods.methodBase import MethodBase, action
from modules.datamodels.datamodelWorkflow import ActionResult
from modules.datamodels.datamodelChat import ActionResult
from modules.datamodels.datamodelAi import AiCallOptions, OperationType, Priority
from modules.datamodels.datamodelChat import ChatDocument
from modules.datamodels.datamodelWeb import WebResearchRequest, WebResearchOptions
@ -204,7 +204,7 @@ For large datasets, set "continue": true to indicate more data is coming, and we
# Parse JSON response from AI with streaming support
import json
import re
from modules.datamodels.datamodelWorkflow import ActionDocument
from modules.datamodels.datamodelChat import ActionDocument
action_documents = []
all_data_chunks = [] # Store all data chunks for merging

View file

@ -9,7 +9,7 @@ from typing import Dict, Any, List, Optional
from datetime import datetime, UTC
from modules.workflows.methods.methodBase import MethodBase, action
from modules.datamodels.datamodelWorkflow import ActionResult, ActionDocument
from modules.datamodels.datamodelChat import ActionResult, ActionDocument
from modules.datamodels.datamodelChat import ChatDocument
from modules.datamodels.datamodelAi import AiCallOptions, OperationType, Priority
@ -136,7 +136,7 @@ class MethodDocument(MethodBase):
action_documents = []
for i, chatDocument in enumerate(chatDocuments):
# Extract text content from this document using new ExtractedContent structure
# Extract text content from this document using new extracted content structure
text_content = ""
try:
ec = all_extracted_content[i] if i < len(all_extracted_content) else None

View file

@ -12,7 +12,7 @@ import uuid
import requests
from modules.workflows.methods.methodBase import MethodBase, action
from modules.datamodels.datamodelWorkflow import ActionResult, ActionDocument
from modules.datamodels.datamodelChat import ActionResult, ActionDocument
from modules.datamodels.datamodelAi import AiCallOptions, OperationType, Priority, AiCallRequest
from modules.datamodels.datamodelChat import ChatDocument
from modules.datamodels.datamodelUam import ConnectionStatus

View file

@ -14,7 +14,7 @@ import aiohttp
import asyncio
from modules.workflows.methods.methodBase import MethodBase, action
from modules.datamodels.datamodelWorkflow import ActionResult, ActionDocument
from modules.datamodels.datamodelChat import ActionResult, ActionDocument
logger = logging.getLogger(__name__)

View file

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

View file

@ -3,7 +3,7 @@
import logging
from typing import Dict, Any, Optional, List
from modules.datamodels.datamodelWorkflow import TaskPlan, TaskStep, ActionResult, ReviewResult
from modules.datamodels.datamodelChat import TaskPlan, TaskStep, ActionResult, ReviewResult
from modules.datamodels.datamodelChat import ChatWorkflow
logger = logging.getLogger(__name__)

View file

@ -4,7 +4,7 @@
import json
import logging
from typing import Dict, Any
from modules.datamodels.datamodelWorkflow import TaskStep, TaskContext, TaskPlan
from modules.datamodels.datamodelChat import TaskStep, TaskContext, TaskPlan
from modules.datamodels.datamodelAi import AiCallOptions, OperationType, ProcessingMode, Priority
from modules.workflows.processing.shared.promptGenerationTaskplan import (
createTaskPlanningPromptTemplate

View file

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

View file

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

View file

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

View file

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

View file

@ -7,7 +7,7 @@ import importlib
import pkgutil
import inspect
from typing import Any, Dict, List
from modules.datamodels.datamodelWorkflow import TaskContext, ReviewContext, DocumentExchange
from modules.datamodels.datamodelChat import TaskContext, ReviewContext, DocumentExchange
from modules.workflows.methods.methodBase import MethodBase
# Set up logger

View file

@ -6,7 +6,7 @@ Handles prompt templates and extraction functions for actionplan mode action han
import json
import logging
from typing import Dict, Any, List
from modules.datamodels.datamodelWorkflow import PromptBundle, PromptPlaceholder
from modules.datamodels.datamodelChat import PromptBundle, PromptPlaceholder
from modules.workflows.processing.shared.placeholderFactory import (
extractUserPrompt,
extractAvailableDocumentsSummary,

View file

@ -4,7 +4,7 @@ Handles prompt templates for react mode action handling.
"""
from typing import Any, List
from modules.datamodels.datamodelWorkflow import PromptBundle, PromptPlaceholder
from modules.datamodels.datamodelChat import PromptBundle, PromptPlaceholder
from modules.workflows.processing.shared.placeholderFactory import (
extractUserPrompt,
extractUserLanguage,

View file

@ -6,7 +6,7 @@ Handles prompt templates and extraction functions for task planning phase.
import json
import logging
from typing import Dict, Any, List
from modules.datamodels.datamodelWorkflow import PromptBundle, PromptPlaceholder
from modules.datamodels.datamodelChat import PromptBundle, PromptPlaceholder
from modules.workflows.processing.shared.placeholderFactory import (
extractUserPrompt,
extractAvailableDocumentsSummary,

View file

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

View file

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