structuring action logic
This commit is contained in:
parent
8e63357190
commit
eebd995d64
4 changed files with 545 additions and 269 deletions
|
|
@ -42,7 +42,6 @@ class TaskStatus(str, Enum):
|
||||||
COMPLETED = "completed"
|
COMPLETED = "completed"
|
||||||
FAILED = "failed"
|
FAILED = "failed"
|
||||||
CANCELLED = "cancelled"
|
CANCELLED = "cancelled"
|
||||||
ROLLED_BACK = "rolled_back"
|
|
||||||
|
|
||||||
# Register labels for TaskStatus
|
# Register labels for TaskStatus
|
||||||
register_model_labels(
|
register_model_labels(
|
||||||
|
|
@ -164,204 +163,131 @@ register_model_labels(
|
||||||
|
|
||||||
class TaskAction(BaseModel, ModelMixin):
|
class TaskAction(BaseModel, ModelMixin):
|
||||||
"""Model for task actions"""
|
"""Model for task actions"""
|
||||||
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique action identifier")
|
id: str = Field(..., description="Action ID")
|
||||||
method: str = Field(..., description="Method to execute")
|
execMethod: str = Field(..., description="Method to execute")
|
||||||
action: str = Field(..., description="Action to perform")
|
execAction: str = Field(..., description="Action to perform")
|
||||||
parameters: Dict[str, Any] = Field(default_factory=dict, description="Action parameters")
|
execParameters: Dict[str, Any] = Field(default_factory=dict, description="Action parameters")
|
||||||
status: TaskStatus = Field(default=TaskStatus.PENDING, description="Current action status")
|
execResultLabel: Optional[str] = Field(None, description="Label for the set of result documents")
|
||||||
retryCount: int = Field(default=0, description="Number of retry attempts")
|
status: TaskStatus = Field(default=TaskStatus.PENDING, description="Action status")
|
||||||
retryMax: int = Field(default=3, description="Maximum number of retry attempts")
|
|
||||||
error: Optional[str] = Field(None, description="Error message if action failed")
|
error: Optional[str] = Field(None, description="Error message if action failed")
|
||||||
startedAt: Optional[datetime] = Field(None, description="Action start timestamp")
|
retryCount: int = Field(default=0, description="Number of retries attempted")
|
||||||
finishedAt: Optional[datetime] = Field(None, description="Action completion timestamp")
|
retryMax: int = Field(default=3, description="Maximum number of retries")
|
||||||
|
processingTime: Optional[float] = Field(None, description="Processing time in seconds")
|
||||||
|
timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC), description="When the action was executed")
|
||||||
|
|
||||||
def start(self) -> None:
|
def isSuccessful(self) -> bool:
|
||||||
"""Start the action"""
|
"""Check if action was successful"""
|
||||||
self.status = TaskStatus.RUNNING
|
return self.status == TaskStatus.COMPLETED
|
||||||
self.startedAt = datetime.now(UTC)
|
|
||||||
|
|
||||||
def complete(self) -> None:
|
def hasError(self) -> bool:
|
||||||
"""Mark action as completed"""
|
"""Check if action has an error"""
|
||||||
self.status = TaskStatus.COMPLETED
|
return self.status == TaskStatus.FAILED
|
||||||
self.finishedAt = datetime.now(UTC)
|
|
||||||
|
|
||||||
def fail(self, error: str) -> None:
|
def getErrorMessage(self) -> Optional[str]:
|
||||||
"""Mark action as failed"""
|
"""Get error message if any"""
|
||||||
self.status = TaskStatus.FAILED
|
return self.error if self.hasError() else None
|
||||||
|
|
||||||
|
def setError(self, error: str) -> None:
|
||||||
|
"""Set action error"""
|
||||||
self.error = error
|
self.error = error
|
||||||
self.finishedAt = datetime.now(UTC)
|
self.status = TaskStatus.FAILED
|
||||||
|
|
||||||
def canRetry(self) -> bool:
|
def setSuccess(self) -> None:
|
||||||
"""Check if action can be retried"""
|
"""Set action as successful"""
|
||||||
return self.retryCount < self.retryMax
|
self.status = TaskStatus.COMPLETED
|
||||||
|
self.error = None
|
||||||
def incrementRetry(self) -> None:
|
|
||||||
"""Increment retry count"""
|
|
||||||
self.retryCount += 1
|
|
||||||
|
|
||||||
# Register labels for TaskAction
|
# Register labels for TaskAction
|
||||||
register_model_labels(
|
register_model_labels(
|
||||||
"TaskAction",
|
"TaskAction",
|
||||||
{"en": "Task Action", "fr": "Action de tâche"},
|
{"en": "Task Action", "fr": "Action de tâche"},
|
||||||
{
|
{
|
||||||
"id": {"en": "ID", "fr": "ID"},
|
"id": {"en": "Action ID", "fr": "ID de l'action"},
|
||||||
"method": {"en": "Method", "fr": "Méthode"},
|
"execMethod": {"en": "Method", "fr": "Méthode"},
|
||||||
"action": {"en": "Action", "fr": "Action"},
|
"execAction": {"en": "Action", "fr": "Action"},
|
||||||
"parameters": {"en": "Parameters", "fr": "Paramètres"},
|
"execParameters": {"en": "Parameters", "fr": "Paramètres"},
|
||||||
"status": {"en": "Status", "fr": "Statut"},
|
"status": {"en": "Status", "fr": "Statut"},
|
||||||
"retryCount": {"en": "Retry Count", "fr": "Nombre de tentatives"},
|
|
||||||
"retryMax": {"en": "Max Retries", "fr": "Tentatives maximales"},
|
|
||||||
"error": {"en": "Error", "fr": "Erreur"},
|
"error": {"en": "Error", "fr": "Erreur"},
|
||||||
"startedAt": {"en": "Started At", "fr": "Démarré le"},
|
"retryCount": {"en": "Retry Count", "fr": "Nombre de tentatives"},
|
||||||
"finishedAt": {"en": "Finished At", "fr": "Terminé le"}
|
"retryMax": {"en": "Max Retries", "fr": "Tentatives max"},
|
||||||
|
"resultDocuments": {"en": "Result Documents", "fr": "Documents du résultat"},
|
||||||
|
"execResultLabel": {"en": "Document Label", "fr": "Label du document"},
|
||||||
|
"processingTime": {"en": "Processing Time", "fr": "Temps de traitement"},
|
||||||
|
"timestamp": {"en": "Timestamp", "fr": "Horodatage"}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
class TaskItem(BaseModel, ModelMixin):
|
class TaskItem(BaseModel, ModelMixin):
|
||||||
"""Model for tasks"""
|
"""Model for workflow tasks"""
|
||||||
id: str = Field(..., description="Unique task identifier")
|
id: str = Field(..., description="Task ID")
|
||||||
workflowId: str = Field(..., description="Associated workflow ID")
|
workflowId: str = Field(..., description="Workflow ID")
|
||||||
status: TaskStatus = Field(default=TaskStatus.PENDING, description="Current task status")
|
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")
|
error: Optional[str] = Field(None, description="Error message if task failed")
|
||||||
startedAt: Optional[datetime] = Field(None, description="Task start timestamp")
|
startedAt: Optional[str] = Field(None, description="When the task started")
|
||||||
finishedAt: Optional[datetime] = Field(None, description="Task completion timestamp")
|
finishedAt: Optional[str] = Field(None, description="When the task finished")
|
||||||
actionList: List[TaskAction] = Field(default_factory=list, description="List of actions to execute")
|
actionList: List[TaskAction] = Field(default_factory=list, description="List of actions to execute")
|
||||||
documentsOutput: List[Dict[str, Any]] = Field(default_factory=list, description="Output documents")
|
retryCount: int = Field(default=0, description="Number of retries attempted")
|
||||||
retryCount: int = Field(default=0, description="Number of retry attempts")
|
retryMax: int = Field(default=3, description="Maximum number of retries")
|
||||||
retryMax: int = Field(default=3, description="Maximum number of retry attempts")
|
|
||||||
rollbackOnFailure: bool = Field(default=True, description="Whether to rollback on failure")
|
rollbackOnFailure: bool = Field(default=True, description="Whether to rollback on failure")
|
||||||
dependencies: List[str] = Field(default_factory=list, description="List of dependent task IDs")
|
dependencies: List[str] = Field(default_factory=list, description="List of task IDs this task depends on")
|
||||||
feedback: Optional[Dict[str, Any]] = Field(None, description="Task feedback data")
|
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")
|
||||||
|
|
||||||
def isCompleted(self) -> bool:
|
def isSuccessful(self) -> bool:
|
||||||
"""Check if task is completed"""
|
"""Check if task was successful"""
|
||||||
return self.status == TaskStatus.COMPLETED
|
return self.status == TaskStatus.COMPLETED
|
||||||
|
|
||||||
def isFailed(self) -> bool:
|
def hasError(self) -> bool:
|
||||||
"""Check if task has failed"""
|
"""Check if task has an error"""
|
||||||
return self.status == TaskStatus.FAILED
|
return self.status == TaskStatus.FAILED
|
||||||
|
|
||||||
def canRetry(self) -> bool:
|
def getErrorMessage(self) -> Optional[str]:
|
||||||
"""Check if task can be retried"""
|
"""Get error message if any"""
|
||||||
return self.retryCount < self.retryMax
|
return self.error if self.hasError() else None
|
||||||
|
|
||||||
def start(self) -> None:
|
def getResultDocuments(self) -> List[ChatDocument]:
|
||||||
"""Start the task"""
|
"""Get all documents from all successful actions"""
|
||||||
self.status = TaskStatus.RUNNING
|
documents = []
|
||||||
self.startedAt = datetime.now(UTC)
|
for action in self.actionList:
|
||||||
|
if action.isSuccessful() and action.resultDocuments:
|
||||||
|
documents.extend(action.resultDocuments)
|
||||||
|
return documents
|
||||||
|
|
||||||
def complete(self) -> None:
|
def getResultDocumentLabel(self) -> Optional[str]:
|
||||||
"""Mark task as completed"""
|
"""Get the label for the result documents"""
|
||||||
self.status = TaskStatus.COMPLETED
|
for action in self.actionList:
|
||||||
self.finishedAt = datetime.now(UTC)
|
if action.isSuccessful() and action.execResultLabel:
|
||||||
|
return action.execResultLabel
|
||||||
|
return None
|
||||||
|
|
||||||
def fail(self, error: str) -> None:
|
def getResultLabel(self, label: str) -> Optional[Any]:
|
||||||
"""Mark task as failed"""
|
"""Get value for a specific result label"""
|
||||||
self.status = TaskStatus.FAILED
|
return self.resultLabels.get(label) if self.resultLabels else None
|
||||||
self.error = error
|
|
||||||
self.finishedAt = datetime.now(UTC)
|
|
||||||
|
|
||||||
def cancel(self) -> None:
|
|
||||||
"""Cancel the task"""
|
|
||||||
self.status = TaskStatus.CANCELLED
|
|
||||||
self.finishedAt = datetime.now(UTC)
|
|
||||||
|
|
||||||
def rollback(self) -> None:
|
|
||||||
"""Mark task as rolled back"""
|
|
||||||
self.status = TaskStatus.ROLLED_BACK
|
|
||||||
self.finishedAt = datetime.now(UTC)
|
|
||||||
|
|
||||||
def incrementRetry(self) -> None:
|
|
||||||
"""Increment retry count"""
|
|
||||||
self.retryCount += 1
|
|
||||||
|
|
||||||
def addDependency(self, taskId: str) -> None:
|
|
||||||
"""Add a task dependency"""
|
|
||||||
if taskId not in self.dependencies:
|
|
||||||
self.dependencies.append(taskId)
|
|
||||||
|
|
||||||
def removeDependency(self, taskId: str) -> None:
|
|
||||||
"""Remove a task dependency"""
|
|
||||||
if taskId in self.dependencies:
|
|
||||||
self.dependencies.remove(taskId)
|
|
||||||
|
|
||||||
def addAction(self, action: TaskAction) -> None:
|
|
||||||
"""Add an action to the task"""
|
|
||||||
self.actionList.append(action)
|
|
||||||
|
|
||||||
def addDocumentOutput(self, document: Dict[str, Any]) -> None:
|
|
||||||
"""Add an output document"""
|
|
||||||
self.documentsOutput.append(document)
|
|
||||||
|
|
||||||
def setFeedback(self, feedback: Dict[str, Any]) -> None:
|
|
||||||
"""Set task feedback"""
|
|
||||||
self.feedback = feedback
|
|
||||||
|
|
||||||
# Register labels for TaskItem
|
# Register labels for TaskItem
|
||||||
register_model_labels(
|
register_model_labels(
|
||||||
"TaskItem",
|
"TaskItem",
|
||||||
{"en": "Task", "fr": "Tâche"},
|
{"en": "Task", "fr": "Tâche"},
|
||||||
{
|
{
|
||||||
"id": {"en": "ID", "fr": "ID"},
|
"id": {"en": "Task ID", "fr": "ID de la tâche"},
|
||||||
"workflowId": {"en": "Workflow ID", "fr": "ID du flux de travail"},
|
"workflowId": {"en": "Workflow ID", "fr": "ID du workflow"},
|
||||||
|
"userInput": {"en": "User Input", "fr": "Entrée utilisateur"},
|
||||||
"status": {"en": "Status", "fr": "Statut"},
|
"status": {"en": "Status", "fr": "Statut"},
|
||||||
"error": {"en": "Error", "fr": "Erreur"},
|
"error": {"en": "Error", "fr": "Erreur"},
|
||||||
"startedAt": {"en": "Started At", "fr": "Démarré le"},
|
"startedAt": {"en": "Started At", "fr": "Démarré à"},
|
||||||
"finishedAt": {"en": "Finished At", "fr": "Terminé le"},
|
"finishedAt": {"en": "Finished At", "fr": "Terminé à"},
|
||||||
"actionList": {"en": "Action List", "fr": "Liste d'actions"},
|
"actionList": {"en": "Actions", "fr": "Actions"},
|
||||||
"documentsOutput": {"en": "Output Documents", "fr": "Documents de sortie"},
|
|
||||||
"retryCount": {"en": "Retry Count", "fr": "Nombre de tentatives"},
|
"retryCount": {"en": "Retry Count", "fr": "Nombre de tentatives"},
|
||||||
"retryMax": {"en": "Max Retries", "fr": "Tentatives maximales"},
|
"retryMax": {"en": "Max Retries", "fr": "Tentatives max"},
|
||||||
"rollbackOnFailure": {"en": "Rollback on Failure", "fr": "Annulation en cas d'échec"},
|
"rollbackOnFailure": {"en": "Rollback On Failure", "fr": "Annuler en cas d'échec"},
|
||||||
"dependencies": {"en": "Dependencies", "fr": "Dépendances"},
|
"dependencies": {"en": "Dependencies", "fr": "Dépendances"},
|
||||||
"feedback": {"en": "Task Feedback", "fr": "Retour sur la tâche"}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
class TaskResult(BaseModel, ModelMixin):
|
|
||||||
"""Model for task execution results"""
|
|
||||||
taskId: str = Field(..., description="ID of the task this result belongs to")
|
|
||||||
status: TaskStatus = Field(..., description="Result status")
|
|
||||||
success: bool = Field(..., description="Whether the task was successful")
|
|
||||||
error: Optional[str] = Field(None, description="Error message if task failed")
|
|
||||||
data: Optional[Dict[str, Any]] = Field(None, description="Result data")
|
|
||||||
documents: List[ChatDocument] = Field(default_factory=list, description="Output documents")
|
|
||||||
documentsLabel: Optional[str] = Field(None, description="Label for the set of documents")
|
|
||||||
feedback: Optional[str] = Field(None, description="Task feedback message")
|
|
||||||
processingTime: Optional[float] = Field(None, description="Processing time in seconds")
|
|
||||||
timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC), description="When the result was created")
|
|
||||||
|
|
||||||
def isSuccessful(self) -> bool:
|
|
||||||
"""Check if result indicates success"""
|
|
||||||
return self.success and self.status == TaskStatus.COMPLETED
|
|
||||||
|
|
||||||
def hasError(self) -> bool:
|
|
||||||
"""Check if result has an error"""
|
|
||||||
return not self.success or self.status == TaskStatus.FAILED
|
|
||||||
|
|
||||||
def getErrorMessage(self) -> Optional[str]:
|
|
||||||
"""Get error message if any"""
|
|
||||||
return self.error if self.hasError() else None
|
|
||||||
|
|
||||||
# Register labels for TaskResult
|
|
||||||
register_model_labels(
|
|
||||||
"TaskResult",
|
|
||||||
{"en": "Task Result", "fr": "Résultat de la tâche"},
|
|
||||||
{
|
|
||||||
"taskId": {"en": "Task ID", "fr": "ID de la tâche"},
|
|
||||||
"status": {"en": "Status", "fr": "Statut"},
|
|
||||||
"success": {"en": "Success", "fr": "Succès"},
|
|
||||||
"error": {"en": "Error", "fr": "Erreur"},
|
|
||||||
"data": {"en": "Data", "fr": "Données"},
|
|
||||||
"documents": {"en": "Documents", "fr": "Documents"},
|
|
||||||
"feedback": {"en": "Feedback", "fr": "Retour"},
|
"feedback": {"en": "Feedback", "fr": "Retour"},
|
||||||
"processingTime": {"en": "Processing Time", "fr": "Temps de traitement"},
|
"processingTime": {"en": "Processing Time", "fr": "Temps de traitement"}
|
||||||
"timestamp": {"en": "Timestamp", "fr": "Horodatage"}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# ===== Message and Workflow Models =====
|
|
||||||
|
|
||||||
class ChatStat(BaseModel, ModelMixin):
|
class ChatStat(BaseModel, ModelMixin):
|
||||||
"""Data model for chat statistics"""
|
"""Data model for chat statistics"""
|
||||||
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key")
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key")
|
||||||
|
|
@ -420,6 +346,7 @@ class ChatMessage(BaseModel, ModelMixin):
|
||||||
workflowId: str = Field(description="Foreign key to workflow")
|
workflowId: str = Field(description="Foreign key to workflow")
|
||||||
parentMessageId: Optional[str] = Field(None, description="Parent message ID for threading")
|
parentMessageId: Optional[str] = Field(None, description="Parent message ID for threading")
|
||||||
documents: List[ChatDocument] = Field(default_factory=list, description="Associated documents")
|
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")
|
message: Optional[str] = Field(None, description="Message content")
|
||||||
role: str = Field(description="Role of the message sender")
|
role: str = Field(description="Role of the message sender")
|
||||||
status: str = Field(description="Status of the message (first, step, last)")
|
status: str = Field(description="Status of the message (first, step, last)")
|
||||||
|
|
@ -427,6 +354,9 @@ class ChatMessage(BaseModel, ModelMixin):
|
||||||
publishedAt: str = Field(description="When the message was published")
|
publishedAt: str = Field(description="When the message was published")
|
||||||
stats: Optional[ChatStat] = Field(None, description="Statistics for this message")
|
stats: Optional[ChatStat] = Field(None, description="Statistics for this message")
|
||||||
success: Optional[bool] = Field(None, description="Whether the message processing was successful")
|
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")
|
||||||
|
|
||||||
# Register labels for ChatMessage
|
# Register labels for ChatMessage
|
||||||
register_model_labels(
|
register_model_labels(
|
||||||
|
|
@ -437,13 +367,17 @@ register_model_labels(
|
||||||
"workflowId": {"en": "Workflow ID", "fr": "ID du flux de travail"},
|
"workflowId": {"en": "Workflow ID", "fr": "ID du flux de travail"},
|
||||||
"parentMessageId": {"en": "Parent Message ID", "fr": "ID du message parent"},
|
"parentMessageId": {"en": "Parent Message ID", "fr": "ID du message parent"},
|
||||||
"documents": {"en": "Documents", "fr": "Documents"},
|
"documents": {"en": "Documents", "fr": "Documents"},
|
||||||
|
"documentsLabel": {"en": "Documents Label", "fr": "Label des documents"},
|
||||||
"message": {"en": "Message", "fr": "Message"},
|
"message": {"en": "Message", "fr": "Message"},
|
||||||
"role": {"en": "Role", "fr": "Rôle"},
|
"role": {"en": "Role", "fr": "Rôle"},
|
||||||
"status": {"en": "Status", "fr": "Statut"},
|
"status": {"en": "Status", "fr": "Statut"},
|
||||||
"sequenceNr": {"en": "Sequence Number", "fr": "Numéro de séquence"},
|
"sequenceNr": {"en": "Sequence Number", "fr": "Numéro de séquence"},
|
||||||
"publishedAt": {"en": "Published At", "fr": "Publié le"},
|
"publishedAt": {"en": "Published At", "fr": "Publié le"},
|
||||||
"stats": {"en": "Statistics", "fr": "Statistiques"},
|
"stats": {"en": "Statistics", "fr": "Statistiques"},
|
||||||
"success": {"en": "Success", "fr": "Succès"}
|
"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"}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from typing import Dict, Any, Optional, List, Union
|
||||||
from datetime import datetime, UTC
|
from datetime import datetime, UTC
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
|
import time
|
||||||
|
|
||||||
from modules.interfaces.serviceAppModel import User
|
from modules.interfaces.serviceAppModel import User
|
||||||
from modules.interfaces.serviceChatModel import (
|
from modules.interfaces.serviceChatModel import (
|
||||||
|
|
@ -50,7 +51,10 @@ class ChatManager:
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create task using ChatInterface
|
# Create task using ChatInterface
|
||||||
return self.service.createTask(taskData)
|
task = self.service.createTask(taskData)
|
||||||
|
if task:
|
||||||
|
self.service.currentTask = task
|
||||||
|
return task
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating initial task: {str(e)}")
|
logger.error(f"Error creating initial task: {str(e)}")
|
||||||
|
|
@ -81,7 +85,10 @@ class ChatManager:
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create task using ChatInterface
|
# Create task using ChatInterface
|
||||||
return self.service.createTask(taskData)
|
task = self.service.createTask(taskData)
|
||||||
|
if task:
|
||||||
|
self.service.currentTask = task
|
||||||
|
return task
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating next task: {str(e)}")
|
logger.error(f"Error creating next task: {str(e)}")
|
||||||
|
|
@ -221,111 +228,215 @@ Document: {document.filename} ({document.mimeType})
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# ===== Task Execution and Processing =====
|
# ===== Task Execution and Processing =====
|
||||||
async def executeTask(self, task: TaskItem) -> TaskResult:
|
async def executeTask(self, task: TaskItem) -> TaskItem:
|
||||||
"""Execute a task and return its result"""
|
"""Execute a task with its list of actions"""
|
||||||
try:
|
try:
|
||||||
# Create result object
|
|
||||||
result = TaskResult(
|
|
||||||
taskId=task.id,
|
|
||||||
status=task.status,
|
|
||||||
success=True,
|
|
||||||
timestamp=datetime.now(UTC)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Start timing
|
# Start timing
|
||||||
startTime = datetime.now(UTC)
|
start_time = time.time()
|
||||||
|
task.startedAt = datetime.now(UTC).isoformat()
|
||||||
# Execute each action
|
task.status = TaskStatus.RUNNING
|
||||||
|
|
||||||
|
# Execute each action in sequence
|
||||||
for action in task.actionList:
|
for action in task.actionList:
|
||||||
try:
|
try:
|
||||||
# Execute action
|
# Execute action
|
||||||
actionResult = await action.execute()
|
action_start = time.time()
|
||||||
|
result = await self._executeAction(action)
|
||||||
# Update action status
|
action.processingTime = time.time() - action_start
|
||||||
action.status = actionResult.status
|
|
||||||
if actionResult.error:
|
|
||||||
action.error = actionResult.error
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Action execution error: {str(e)}")
|
|
||||||
action.status = TaskStatus.FAILED
|
|
||||||
action.error = str(e)
|
|
||||||
|
|
||||||
# Calculate processing time
|
|
||||||
endTime = datetime.now(UTC)
|
|
||||||
result.processingTime = (endTime - startTime).total_seconds()
|
|
||||||
|
|
||||||
# Update task status
|
|
||||||
if all(action.status == TaskStatus.COMPLETED for action in task.actionList):
|
|
||||||
result.status = TaskStatus.COMPLETED
|
|
||||||
result.success = True
|
|
||||||
else:
|
|
||||||
result.status = TaskStatus.FAILED
|
|
||||||
result.success = False
|
|
||||||
result.error = "One or more actions failed"
|
|
||||||
|
|
||||||
# Generate feedback and documents if task completed successfully
|
|
||||||
if result.status == TaskStatus.COMPLETED:
|
|
||||||
# Generate feedback using AI
|
|
||||||
result.feedback = await self._processTaskResults(task)
|
|
||||||
|
|
||||||
# Create output documents
|
|
||||||
result.documents = await self._createOutputDocuments(task)
|
|
||||||
# Set documents label based on task input
|
|
||||||
result.documentsLabel = "TaskResult"
|
|
||||||
else:
|
|
||||||
result.feedback = f"Task failed: {result.error}"
|
|
||||||
|
|
||||||
# Update task in database
|
|
||||||
self.service.updateTask(task.id, {
|
|
||||||
"status": result.status,
|
|
||||||
"error": result.error,
|
|
||||||
"finishedAt": datetime.now(UTC).isoformat(),
|
|
||||||
"actionList": [action.dict() for action in task.actionList],
|
|
||||||
"documentsOutput": result.documents,
|
|
||||||
"feedback": result.feedback,
|
|
||||||
"documentsLabel": result.documentsLabel
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Task execution error: {str(e)}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
async def parseTaskResult(self, workflow: ChatWorkflow, result: TaskResult) -> None:
|
# Validate result
|
||||||
|
if not result.success:
|
||||||
|
error_msg = result.error or "Unknown error"
|
||||||
|
action.setError(error_msg)
|
||||||
|
|
||||||
|
# Create error message
|
||||||
|
error_message = ChatMessage(
|
||||||
|
workflowId=task.workflowId,
|
||||||
|
message=f"{action.execMethod}.{action.execAction}.error: {error_msg}",
|
||||||
|
role="system",
|
||||||
|
status="step",
|
||||||
|
sequenceNr=0, # Will be set by workflow
|
||||||
|
publishedAt=datetime.now(UTC).isoformat(),
|
||||||
|
success=False,
|
||||||
|
actionId=action.id,
|
||||||
|
actionMethod=action.execMethod,
|
||||||
|
actionName=action.execAction
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add error message to workflow
|
||||||
|
await self._addMessageToWorkflow(task.workflowId, error_message)
|
||||||
|
|
||||||
|
# If action failed and we have retries left, retry
|
||||||
|
if action.retryCount < action.retryMax:
|
||||||
|
action.retryCount += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If we're out of retries, fail the task
|
||||||
|
task.error = f"Action {action.id} failed after {action.retryCount} retries: {error_msg}"
|
||||||
|
task.status = TaskStatus.FAILED
|
||||||
|
return task
|
||||||
|
|
||||||
|
# Process successful result
|
||||||
|
action.setSuccess()
|
||||||
|
|
||||||
|
# Set result label from AI response if provided
|
||||||
|
if result.data.get("resultLabel"):
|
||||||
|
action.execResultLabel = result.data["resultLabel"]
|
||||||
|
|
||||||
|
# Create result message with documents if any
|
||||||
|
if result.data.get("documents"):
|
||||||
|
# Store AI-generated documents in database and create ChatDocuments
|
||||||
|
documents = []
|
||||||
|
for doc in result.data["documents"]:
|
||||||
|
# Create document (which also creates the file)
|
||||||
|
document = await self.service.createDocument(
|
||||||
|
fileName=doc["filename"],
|
||||||
|
mimeType=doc["mimeType"],
|
||||||
|
content=doc["content"],
|
||||||
|
base64encoded=doc["base64Encoded"]
|
||||||
|
)
|
||||||
|
documents.append(document)
|
||||||
|
|
||||||
|
# Create success message with documents
|
||||||
|
success_message = ChatMessage(
|
||||||
|
workflowId=task.workflowId,
|
||||||
|
message=f"{action.execMethod}.{action.execAction}",
|
||||||
|
role="system",
|
||||||
|
status="step",
|
||||||
|
sequenceNr=0, # Will be set by workflow
|
||||||
|
publishedAt=datetime.now(UTC).isoformat(),
|
||||||
|
documents=documents,
|
||||||
|
documentsLabel=action.execResultLabel, # Use the label from action
|
||||||
|
success=True,
|
||||||
|
actionId=action.id,
|
||||||
|
actionMethod=action.execMethod,
|
||||||
|
actionName=action.execAction
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add success message to workflow
|
||||||
|
await self._addMessageToWorkflow(task.workflowId, success_message)
|
||||||
|
|
||||||
|
# Store result labels
|
||||||
|
if result.data.get("labels"):
|
||||||
|
task.resultLabels.update(result.data["labels"])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
action.setError(error_msg)
|
||||||
|
|
||||||
|
# Create error message
|
||||||
|
error_message = ChatMessage(
|
||||||
|
workflowId=task.workflowId,
|
||||||
|
message=f"{action.execMethod}.{action.execAction}.error: {error_msg}",
|
||||||
|
role="system",
|
||||||
|
status="step",
|
||||||
|
sequenceNr=0, # Will be set by workflow
|
||||||
|
publishedAt=datetime.now(UTC).isoformat(),
|
||||||
|
success=False,
|
||||||
|
actionId=action.id,
|
||||||
|
actionMethod=action.execMethod,
|
||||||
|
actionName=action.execAction
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add error message to workflow
|
||||||
|
await self._addMessageToWorkflow(task.workflowId, error_message)
|
||||||
|
|
||||||
|
# If action failed and we have retries left, retry
|
||||||
|
if action.retryCount < action.retryMax:
|
||||||
|
action.retryCount += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If we're out of retries, fail the task
|
||||||
|
task.error = f"Action {action.id} failed after {action.retryCount} retries: {error_msg}"
|
||||||
|
task.status = TaskStatus.FAILED
|
||||||
|
return task
|
||||||
|
|
||||||
|
# Check if all actions were successful
|
||||||
|
if all(action.isSuccessful() for action in task.actionList):
|
||||||
|
task.status = TaskStatus.COMPLETED
|
||||||
|
task.feedback = "Task completed successfully"
|
||||||
|
|
||||||
|
# Create chat message with results
|
||||||
|
message = ChatMessage(
|
||||||
|
workflowId=task.workflowId,
|
||||||
|
message=task.feedback,
|
||||||
|
role="system",
|
||||||
|
status="last",
|
||||||
|
sequenceNr=0, # Will be set by workflow
|
||||||
|
publishedAt=datetime.now(UTC).isoformat(),
|
||||||
|
success=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add message to workflow
|
||||||
|
await self._addMessageToWorkflow(task.workflowId, message)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# If any action failed, fail the task
|
||||||
|
task.status = TaskStatus.FAILED
|
||||||
|
task.error = "One or more actions failed"
|
||||||
|
|
||||||
|
# Create error message
|
||||||
|
error_message = ChatMessage(
|
||||||
|
workflowId=task.workflowId,
|
||||||
|
message=f"Task failed: {task.getErrorMessage()}",
|
||||||
|
role="system",
|
||||||
|
status="last",
|
||||||
|
sequenceNr=0, # Will be set by workflow
|
||||||
|
publishedAt=datetime.now(UTC).isoformat(),
|
||||||
|
success=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add error message to workflow
|
||||||
|
await self._addMessageToWorkflow(task.workflowId, error_message)
|
||||||
|
|
||||||
|
# Calculate processing time
|
||||||
|
task.processingTime = time.time() - start_time
|
||||||
|
task.finishedAt = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
|
return task
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Handle unexpected errors
|
||||||
|
task.status = TaskStatus.FAILED
|
||||||
|
task.error = str(e)
|
||||||
|
task.finishedAt = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
|
# Create error message
|
||||||
|
error_message = ChatMessage(
|
||||||
|
workflowId=task.workflowId,
|
||||||
|
message=f"Task failed with unexpected error: {task.getErrorMessage()}",
|
||||||
|
role="system",
|
||||||
|
status="last",
|
||||||
|
sequenceNr=0, # Will be set by workflow
|
||||||
|
publishedAt=datetime.now(UTC).isoformat(),
|
||||||
|
success=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add error message to workflow
|
||||||
|
await self._addMessageToWorkflow(task.workflowId, error_message)
|
||||||
|
|
||||||
|
return task
|
||||||
|
|
||||||
|
async def parseTaskResult(self, workflow: ChatWorkflow, task: TaskItem) -> None:
|
||||||
"""Process and store task result in workflow"""
|
"""Process and store task result in workflow"""
|
||||||
try:
|
try:
|
||||||
# Find task in workflow
|
|
||||||
task = self.service.getTask(result.taskId)
|
|
||||||
if not task:
|
|
||||||
logger.error(f"Task {result.taskId} not found in workflow")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Update task status
|
|
||||||
self.service.updateTask(task.id, {
|
|
||||||
"status": result.status,
|
|
||||||
"error": result.error,
|
|
||||||
"finishedAt": datetime.now(UTC).isoformat()
|
|
||||||
})
|
|
||||||
|
|
||||||
# Create feedback message if available
|
# Create feedback message if available
|
||||||
if result.feedback:
|
if task.feedback:
|
||||||
message = ChatMessage(
|
message = ChatMessage(
|
||||||
id=str(uuid.uuid4()),
|
id=str(uuid.uuid4()),
|
||||||
workflowId=workflow.id,
|
workflowId=workflow.id,
|
||||||
role="assistant",
|
role="assistant",
|
||||||
message=result.feedback,
|
message=task.feedback,
|
||||||
status="step",
|
status="step",
|
||||||
documents=result.documents
|
documents=task.getResultDocuments()
|
||||||
)
|
)
|
||||||
self.service.createWorkflowMessage(message.dict())
|
self.service.createWorkflowMessage(message.dict())
|
||||||
|
|
||||||
# Update workflow stats
|
# Update workflow stats
|
||||||
if result.processingTime:
|
if task.processingTime:
|
||||||
if not workflow.stats:
|
if not workflow.stats:
|
||||||
workflow.stats = ChatStat()
|
workflow.stats = ChatStat()
|
||||||
workflow.stats.processingTime = (workflow.stats.processingTime or 0) + result.processingTime
|
workflow.stats.processingTime = (workflow.stats.processingTime or 0) + task.processingTime
|
||||||
self.service.updateWorkflow(workflow.id, {"stats": workflow.stats.dict()})
|
self.service.updateWorkflow(workflow.id, {"stats": workflow.stats.dict()})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -593,4 +704,78 @@ Rules:
|
||||||
4. Include file extensions in filenames
|
4. Include file extensions in filenames
|
||||||
|
|
||||||
Return a JSON array of Document objects.
|
Return a JSON array of Document objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def _createTaskDefinitionPrompt(self, userInput: str, workflow: ChatWorkflow) -> str:
|
||||||
|
"""Create prompt for task definition"""
|
||||||
|
# Get available methods
|
||||||
|
methodList = self.service.getMethodsList()
|
||||||
|
|
||||||
|
# Get workflow history
|
||||||
|
messageSummary = self.service.getMessageSummary(workflow.messages[-1] if workflow.messages else None)
|
||||||
|
|
||||||
|
# Get available documents and connections
|
||||||
|
docRefs = self.service.getDocumentReferenceList()
|
||||||
|
connRefs = self.service.getConnectionReferenceList()
|
||||||
|
|
||||||
|
prompt = f"""
|
||||||
|
Task Definition for: {userInput}
|
||||||
|
|
||||||
|
Available Methods:
|
||||||
|
{chr(10).join(f"- {method}" for method in methodList)}
|
||||||
|
|
||||||
|
Workflow History:
|
||||||
|
{chr(10).join(f"- {msg['message']}" for msg in messageSummary.get('chat', []))}
|
||||||
|
|
||||||
|
Available Documents:
|
||||||
|
{chr(10).join(f"- {doc['documentReference']} ({doc['datetime']})" for doc in docRefs.get('chat', []))}
|
||||||
|
|
||||||
|
Available Connections:
|
||||||
|
{chr(10).join(f"- {conn}" for conn in connRefs)}
|
||||||
|
|
||||||
|
Instructions:
|
||||||
|
1. Result Format (JSON):
|
||||||
|
{{
|
||||||
|
"status": "pending|running|completed|failed",
|
||||||
|
"feedback": "string explaining what was done and what needs to be done next",
|
||||||
|
"actions": [
|
||||||
|
{{
|
||||||
|
"method": "string",
|
||||||
|
"action": "string",
|
||||||
|
"parameters": {{
|
||||||
|
"param1": "value1",
|
||||||
|
"param2": "value2"
|
||||||
|
}},
|
||||||
|
"resultLabel": "documentList_<uuid>_<label>"
|
||||||
|
}}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
|
||||||
|
2. Available Data:
|
||||||
|
- Use only provided document references (format: document_<id>_<filename> or documentList_<action.id>_<label>)
|
||||||
|
- Use only provided connection references
|
||||||
|
- Use result labels from previous actions in the same task
|
||||||
|
|
||||||
|
3. Method Usage Rules:
|
||||||
|
- Syntax: method.action([parameter:type])->resultLabel:type
|
||||||
|
- resultLabel format: documentList_<uuid>_<label>
|
||||||
|
- Actions must be in processing sequence
|
||||||
|
- Parameters must be from:
|
||||||
|
* Available document references
|
||||||
|
* Available connection references
|
||||||
|
* Result labels from previous actions
|
||||||
|
|
||||||
|
4. Result Labels:
|
||||||
|
- Use consistent naming for related documents
|
||||||
|
- Include descriptive labels for document sets
|
||||||
|
- Labels will be used to track document sets in messages
|
||||||
|
|
||||||
|
5. Error Handling:
|
||||||
|
- Include validation for each action
|
||||||
|
- Specify retry behavior if needed
|
||||||
|
- Provide clear error messages
|
||||||
|
- Errors will be recorded in messages with .error: suffix
|
||||||
|
|
||||||
|
Please provide the task definition in JSON format following these rules.
|
||||||
|
"""
|
||||||
|
return prompt
|
||||||
|
|
@ -13,6 +13,8 @@ from modules.interfaces.serviceChatClass import getInterface as getChatInterface
|
||||||
from modules.interfaces.serviceManagementClass import getInterface as getFileInterface
|
from modules.interfaces.serviceManagementClass import getInterface as getFileInterface
|
||||||
from modules.workflow.managerDocument import DocumentManager
|
from modules.workflow.managerDocument import DocumentManager
|
||||||
from modules.methods.methodBase import MethodBase
|
from modules.methods.methodBase import MethodBase
|
||||||
|
import uuid
|
||||||
|
import base64
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -25,6 +27,7 @@ class ServiceContainer:
|
||||||
self.workflow = workflow
|
self.workflow = workflow
|
||||||
self.tasks = workflow.tasks
|
self.tasks = workflow.tasks
|
||||||
self.statusEnums = TaskStatus
|
self.statusEnums = TaskStatus
|
||||||
|
self.currentTask = None # Initialize current task as None
|
||||||
|
|
||||||
# Initialize managers
|
# Initialize managers
|
||||||
self.interfaceChat = getChatInterface(currentUser)
|
self.interfaceChat = getChatInterface(currentUser)
|
||||||
|
|
@ -77,27 +80,129 @@ class ServiceContainer:
|
||||||
return self.methods
|
return self.methods
|
||||||
|
|
||||||
def getMethodsList(self) -> List[str]:
|
def getMethodsList(self) -> List[str]:
|
||||||
"""Get list of available methods"""
|
"""Get list of available methods with their signatures"""
|
||||||
return list(self.methods.keys())
|
methodList = []
|
||||||
|
for methodName, method in self.methods.items():
|
||||||
|
for actionName, action in method.actions.items():
|
||||||
|
# Get parameter types and return type from action signature
|
||||||
|
paramTypes = []
|
||||||
|
for paramName, param in action.parameters.items():
|
||||||
|
paramTypes.append(f"{paramName}:{param.type}")
|
||||||
|
|
||||||
|
# Format: method.action([param1:type, param2:type])->resultLabel:type # description
|
||||||
|
signature = f"{methodName}.{actionName}([{', '.join(paramTypes)}])->{action.resultLabel}:{action.resultType}"
|
||||||
|
if action.description:
|
||||||
|
signature += f" # {action.description}"
|
||||||
|
methodList.append(signature)
|
||||||
|
return methodList
|
||||||
|
|
||||||
def getDocumentReferenceList(self) -> Dict[str, List[Dict[str, str]]]:
|
def getDocumentReferenceList(self) -> Dict[str, List[Dict[str, str]]]:
|
||||||
"""Get list of document references sorted by datetime"""
|
"""Get list of document references sorted by datetime, categorized by chat round"""
|
||||||
|
chat_refs = []
|
||||||
|
history_refs = []
|
||||||
|
|
||||||
|
# Process messages in reverse order to find current chat round
|
||||||
|
for message in reversed(self.workflow.messages):
|
||||||
|
# Get document references from message
|
||||||
|
if message.documents:
|
||||||
|
# For messages with action context, use documentList reference
|
||||||
|
if message.actionId and message.documentsLabel:
|
||||||
|
doc_ref = self.getDocumentReferenceFromMessage(message)
|
||||||
|
doc_info = {
|
||||||
|
"documentReference": doc_ref,
|
||||||
|
"datetime": message.publishedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add to appropriate list based on message status
|
||||||
|
if message.status == "first":
|
||||||
|
chat_refs.append(doc_info)
|
||||||
|
break # Stop after finding first message
|
||||||
|
elif message.status == "step":
|
||||||
|
chat_refs.append(doc_info)
|
||||||
|
else:
|
||||||
|
history_refs.append(doc_info)
|
||||||
|
# For regular messages, use individual document references
|
||||||
|
else:
|
||||||
|
for doc in message.documents:
|
||||||
|
doc_ref = self.getDocumentReferenceFromChatDocument(doc)
|
||||||
|
doc_info = {
|
||||||
|
"documentReference": doc_ref,
|
||||||
|
"datetime": message.publishedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add to appropriate list based on message status
|
||||||
|
if message.status == "first":
|
||||||
|
chat_refs.append(doc_info)
|
||||||
|
break # Stop after finding first message
|
||||||
|
elif message.status == "step":
|
||||||
|
chat_refs.append(doc_info)
|
||||||
|
else:
|
||||||
|
history_refs.append(doc_info)
|
||||||
|
|
||||||
|
# Stop processing if we hit a first message
|
||||||
|
if message.status == "first":
|
||||||
|
break
|
||||||
|
|
||||||
|
# Sort both lists by datetime in descending order
|
||||||
|
chat_refs.sort(key=lambda x: x["datetime"], reverse=True)
|
||||||
|
history_refs.sort(key=lambda x: x["datetime"], reverse=True)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"chat": self._getChatDocumentReferences(),
|
"chat": chat_refs,
|
||||||
"history": self._getHistoryDocumentReferences()
|
"history": history_refs
|
||||||
}
|
}
|
||||||
|
|
||||||
def getDocumentReferenceFromChatDocument(self, document: ChatDocument) -> str:
|
def getDocumentReferenceFromChatDocument(self, document: ChatDocument) -> str:
|
||||||
"""Get document reference from ChatDocument"""
|
"""Get document reference from ChatDocument"""
|
||||||
return f"document_{document.id}_{document.filename}"
|
return f"document_{document.id}_{document.filename}"
|
||||||
|
|
||||||
def getDocumentReferenceFromTaskResult(self, result: TaskResult) -> str:
|
def getDocumentReferenceFromMessage(self, message: ChatMessage) -> str:
|
||||||
"""Get document reference from TaskResult"""
|
"""Get document reference from ChatMessage with action context"""
|
||||||
return f"documentList_{result.id}_{result.documentsLabel}"
|
if not message.actionId or not message.documentsLabel:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# If documentsLabel already contains the full reference format, return it
|
||||||
|
if message.documentsLabel.startswith("documentList_"):
|
||||||
|
return message.documentsLabel
|
||||||
|
|
||||||
|
# Otherwise construct the reference
|
||||||
|
return f"documentList_{message.actionId}_{message.documentsLabel}"
|
||||||
|
|
||||||
def getChatDocumentsFromDocumentReference(self, documentReference: str) -> List[ChatDocument]:
|
def getChatDocumentsFromDocumentReference(self, documentReference: str) -> List[ChatDocument]:
|
||||||
"""Get ChatDocuments from document reference"""
|
"""Get ChatDocuments from document reference"""
|
||||||
return self.documentManager.getDocumentsByReference(documentReference)
|
try:
|
||||||
|
# Parse reference format
|
||||||
|
parts = documentReference.split('_', 2) # Split into max 3 parts
|
||||||
|
if len(parts) < 3:
|
||||||
|
return []
|
||||||
|
|
||||||
|
ref_type = parts[0]
|
||||||
|
ref_id = parts[1]
|
||||||
|
ref_label = parts[2] # Keep the full label
|
||||||
|
|
||||||
|
if ref_type == "document":
|
||||||
|
# Handle ChatDocument reference: document_<id>_<filename>
|
||||||
|
# Find document in workflow messages
|
||||||
|
for message in self.workflow.messages:
|
||||||
|
if message.documents:
|
||||||
|
for doc in message.documents:
|
||||||
|
if doc.id == ref_id:
|
||||||
|
return [doc]
|
||||||
|
|
||||||
|
elif ref_type == "documentList":
|
||||||
|
# Handle document list reference: documentList_<action.id>_<label>
|
||||||
|
# Find message with matching action ID and documents label
|
||||||
|
for message in self.workflow.messages:
|
||||||
|
if (message.actionId == ref_id and
|
||||||
|
message.documentsLabel == documentReference and # Compare full reference
|
||||||
|
message.documents):
|
||||||
|
return message.documents
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting documents from reference {documentReference}: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
def getConnectionReferenceList(self) -> List[Dict[str, str]]:
|
def getConnectionReferenceList(self) -> List[Dict[str, str]]:
|
||||||
"""Get list of connection references sorted by authority"""
|
"""Get list of connection references sorted by authority"""
|
||||||
|
|
@ -130,9 +235,42 @@ class ServiceContainer:
|
||||||
"""Call AI image service"""
|
"""Call AI image service"""
|
||||||
return self.interfaceAi.callAiImage(imageData, mimeType, prompt)
|
return self.interfaceAi.callAiImage(imageData, mimeType, prompt)
|
||||||
|
|
||||||
def createFile(self, fileName: str, mimeType: str, content: bytes, base64encoded: bool = False) -> Dict[str, Any]:
|
def createFile(self, fileName: str, mimeType: str, content: str, base64encoded: bool = False) -> str:
|
||||||
"""Create new file"""
|
"""Create new file and return its ID"""
|
||||||
return self.interfaceFiles.createFile(fileName, mimeType, content, base64encoded)
|
# Convert content to bytes based on base64 flag
|
||||||
|
if base64encoded:
|
||||||
|
content_bytes = base64.b64decode(content)
|
||||||
|
else:
|
||||||
|
content_bytes = content.encode('utf-8')
|
||||||
|
|
||||||
|
# First create the file metadata
|
||||||
|
file_item = self.interfaceFiles.createFile(
|
||||||
|
name=fileName,
|
||||||
|
mimeType=mimeType,
|
||||||
|
size=len(content_bytes)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Then store the file data
|
||||||
|
self.interfaceFiles.createFileData(file_item.id, content_bytes)
|
||||||
|
|
||||||
|
return file_item.id
|
||||||
|
|
||||||
|
def createDocument(self, fileName: str, mimeType: str, content: str, base64encoded: bool = True) -> ChatDocument:
|
||||||
|
"""Create document from file data object created by AI call"""
|
||||||
|
# First create the file and get its ID
|
||||||
|
file_id = self.createFile(fileName, mimeType, content, base64encoded)
|
||||||
|
|
||||||
|
# Get file info for metadata
|
||||||
|
file_info = self.interfaceFiles.getFile(file_id)
|
||||||
|
|
||||||
|
# Create document with file reference
|
||||||
|
return ChatDocument(
|
||||||
|
id=str(uuid.uuid4()),
|
||||||
|
fileId=file_id,
|
||||||
|
filename=fileName,
|
||||||
|
fileSize=file_info.fileSize,
|
||||||
|
mimeType=mimeType
|
||||||
|
)
|
||||||
|
|
||||||
def getFileInfo(self, fileId: str) -> Dict[str, Any]:
|
def getFileInfo(self, fileId: str) -> Dict[str, Any]:
|
||||||
"""Get file information"""
|
"""Get file information"""
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,25 @@
|
||||||
Prompt for task definition:
|
TODO
|
||||||
|
- documenthandling to review with document-file-filedata --> redundant and too complex --> to have ChatDocument with label and reference to fileid with function to get metadate directly from file object
|
||||||
|
- prompt for task definition to fix
|
||||||
|
- implement all functions from service object correctly
|
||||||
|
- result parsing: execute task actions by using data references stepwise, all documents in TaskResults to save as ChatDocuments, to be available for next action.
|
||||||
|
- action execution: to use conversion functions, user DocumentReference and ConnectionReference
|
||||||
|
|
||||||
|
ADAPT:
|
||||||
|
|
||||||
|
can you analyse the following specification:
|
||||||
|
- clear what is required?
|
||||||
|
- clear what to do?
|
||||||
|
- questions?
|
||||||
|
please give a summarized feedback before implementing
|
||||||
|
|
||||||
|
|
||||||
|
How does the task process need to work:
|
||||||
|
- specification for task-prompt below ensures, that a json with actions is produced, where references are clear, always having uuid integrated, only valid references
|
||||||
|
- ai call result to be validated against json specification before proceeding, proper error handling for retry or abort depending on error (e.g. is ai down then to abort, if ai error to proceed with error to potentially fix it, etc.)
|
||||||
|
- then to process the action list.
|
||||||
|
|
||||||
|
Resulting specification Prompt for task definition:
|
||||||
- original user input (summary)
|
- original user input (summary)
|
||||||
- prompt for task to do
|
- prompt for task to do
|
||||||
- method list
|
- method list
|
||||||
|
|
@ -9,16 +30,14 @@ Prompt for task definition:
|
||||||
- rules for result: status (enum), feedback (to tell what is done and what needed next. Only to to the tasks, which are possible with the available methods, referencing and data, rest to do in next round)
|
- rules for result: status (enum), feedback (to tell what is done and what needed next. Only to to the tasks, which are possible with the available methods, referencing and data, rest to do in next round)
|
||||||
- available data to use: list of documentReference, list of connectionReference
|
- available data to use: list of documentReference, list of connectionReference
|
||||||
- methods usage:
|
- methods usage:
|
||||||
- syntax: method.action([parameter:type])->resultLabel:type
|
- syntax: method.action([parameter:type])->resultLabel:type (reference note: resultLabel to be stored in TaskAction.execResultLabel for later use)
|
||||||
|
- resultLabel to be in format documentList_<generated uuid>_<generated label>
|
||||||
- sequence of method.action to be the sequence ot processing
|
- sequence of method.action to be the sequence ot processing
|
||||||
- as parameter only to use available items of documentReference and connectionReference or resultLabel of a previous method.action
|
- as parameter only to use available items of documentReference or connectionReference or resultLabel from a previous method.action
|
||||||
- required result format: json for TaskResult, nothing else
|
- required result format: json, nothing else
|
||||||
|
|
||||||
|
|
||||||
TODO
|
FINALIZE:
|
||||||
- result parsing: execute task actions by using data references stepwise, all documents in TaskResults to save as ChatDocuments, to be available for next action.
|
|
||||||
- action execution: to use conversion functions, user DocumentReference and ConnectionReference
|
|
||||||
|
|
||||||
|
|
||||||
service (ServiceContainer)
|
service (ServiceContainer)
|
||||||
user <-- currentUser
|
user <-- currentUser
|
||||||
|
|
@ -37,11 +56,11 @@ service (ServiceContainer)
|
||||||
sharepoint.download([connectionReference:str, filepath:str])->documentReference:str
|
sharepoint.download([connectionReference:str, filepath:str])->documentReference:str
|
||||||
|
|
||||||
functions
|
functions
|
||||||
extractContent(prompt,documentReference):str <- managerDocument.extractContent(prompt,FilePreview)
|
ok extractContent(prompt,documentReference):str <- managerDocument.extractContent(prompt,FilePreview)
|
||||||
getMethodsCatalog():{...} <- to import dynamically all methods from the files "method*.py" in folder modules/methods
|
ok getMethodsCatalog():{...} <- to import dynamically all methods from the files "method*.py" in folder modules/methods
|
||||||
getMethodsList():[str] <- to transform result from getMethodsCatalog into a list with the method items in format "method.action([parameter:type])->resultLabel:type"
|
ok getMethodsList():[str] <- to transform result from getMethodsCatalog into a list with the method items in format "method.action([parameter:type])->resultLabel:type # description"
|
||||||
|
|
||||||
getDocumentReferenceList():{"chat":[{"documentReference":str,"datetime":str}],"history":[{"documentReference":str,"datetime":str}]} sorted by datetime desc
|
ok getDocumentReferenceList():{"chat":[{"documentReference":str,"datetime":str}],"history":[{"documentReference":str,"datetime":str}]} sorted by datetime desc
|
||||||
getDocumentReferenceFromChatDocument(ChatDocument):"document_"+ChatDocument.id+"_"+ChatDocument.filename
|
getDocumentReferenceFromChatDocument(ChatDocument):"document_"+ChatDocument.id+"_"+ChatDocument.filename
|
||||||
getDocumentReferenceFromTaskResult(TaskResult):"documentList_"+TaskResult.id+"_"+TaskResult.documentsLabel
|
getDocumentReferenceFromTaskResult(TaskResult):"documentList_"+TaskResult.id+"_"+TaskResult.documentsLabel
|
||||||
getChatDocumentsFromDocumentReference(documentReference):List[ChatDocuments]
|
getChatDocumentsFromDocumentReference(documentReference):List[ChatDocuments]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue