726 lines
No EOL
28 KiB
Python
726 lines
No EOL
28 KiB
Python
import logging
|
|
import importlib
|
|
import pkgutil
|
|
import inspect
|
|
from typing import Dict, Any, Optional, List, Type, Callable, Awaitable, Union
|
|
from datetime import datetime, UTC
|
|
import json
|
|
import base64
|
|
import uuid
|
|
|
|
from modules.interfaces.serviceAppClass import User
|
|
from modules.methods.methodBase import MethodBase, AuthSource, MethodResult
|
|
from modules.workflow.serviceContainer import ServiceContainer
|
|
from modules.interfaces.serviceChatModel import (
|
|
TaskStatus, UserInputRequest, ContentMetadata, ContentItem,
|
|
ChatDocument, TaskDocument, ExtractedContent, TaskItem,
|
|
TaskResult, ChatStat, ChatLog, ChatMessage, ChatWorkflow
|
|
)
|
|
from modules.workflow.processorDocument import DocumentProcessor
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class ChatManager:
|
|
"""Chat manager with improved AI integration and method handling"""
|
|
|
|
def __init__(self, currentUser: User):
|
|
self._discoverMethods()
|
|
self.workflow: Optional[ChatWorkflow] = None
|
|
self.currentTask: Optional[TaskItem] = None
|
|
self.workflowHistory: List[ChatMessage] = []
|
|
self.documentProcessor = DocumentProcessor()
|
|
self.userLanguage = None
|
|
self.currentUser = currentUser
|
|
|
|
# ===== Initialization and Setup =====
|
|
async def initialize(self, workflow: ChatWorkflow) -> None:
|
|
"""Initialize chat manager with workflow"""
|
|
self.service.workflow = workflow
|
|
|
|
# Initialize AI model
|
|
self.service.model = {
|
|
'callAiBasic': self._callAiBasic,
|
|
'callAiAdvanced': self._callAiAdvanced
|
|
}
|
|
|
|
# Initialize document processor
|
|
self.service.documentProcessor.initialize()
|
|
|
|
def setUserLanguage(self, languageCode: str):
|
|
"""Set the user's preferred language"""
|
|
self.userLanguage = languageCode
|
|
logger.debug(f"User language set to: {languageCode}")
|
|
|
|
def _discoverMethods(self):
|
|
"""Dynamically discover all method classes in modules.methods package"""
|
|
try:
|
|
# Import the methods package
|
|
methodsPackage = importlib.import_module('modules.methods')
|
|
|
|
# Discover all modules in the package
|
|
for _, name, isPkg in pkgutil.iter_modules(methodsPackage.__path__):
|
|
if not isPkg and name.startswith('method'):
|
|
try:
|
|
# Import the module
|
|
module = importlib.import_module(f'modules.methods.{name}')
|
|
|
|
# Find all classes in the module that inherit from MethodBase
|
|
for itemName, item in inspect.getmembers(module):
|
|
if (inspect.isclass(item) and
|
|
issubclass(item, MethodBase) and
|
|
item != MethodBase):
|
|
# Instantiate the method and add to service
|
|
methodInstance = item()
|
|
self.service.methods[methodInstance.name] = methodInstance
|
|
logger.info(f"Discovered method: {methodInstance.name}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error loading method module {name}: {str(e)}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error discovering methods: {str(e)}")
|
|
|
|
# ===== Task Creation and Management =====
|
|
async def createInitialTask(self, workflow: ChatWorkflow, initialMessage: ChatMessage) -> Optional[TaskItem]:
|
|
"""Create the initial task from the first message"""
|
|
try:
|
|
# Get available methods and their actions
|
|
methodCatalog = self.service.getAvailableMethods()
|
|
|
|
# Process user input with AI
|
|
processedInput = await self._processUserInput(initialMessage.message, methodCatalog)
|
|
|
|
# Create actions from processed input
|
|
actions = await self._createActions(processedInput['actions'])
|
|
|
|
# Create task
|
|
task = TaskItem(
|
|
id=f"task_{datetime.now(UTC).timestamp()}",
|
|
workflowId=workflow.id,
|
|
userInput=processedInput['objective'],
|
|
dataList=initialMessage.documents,
|
|
actionList=actions,
|
|
status=TaskStatus.PENDING,
|
|
createdAt=datetime.now(UTC),
|
|
updatedAt=datetime.now(UTC)
|
|
)
|
|
|
|
# Add task to workflow
|
|
workflow.tasks.append(task)
|
|
return task
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error creating initial task: {str(e)}")
|
|
return None
|
|
|
|
async def createNextTask(self, workflow: ChatWorkflow, previousResult: TaskResult) -> Optional[TaskItem]:
|
|
"""Create next task based on previous result"""
|
|
try:
|
|
# Check if previous result was successful
|
|
if not previousResult.success:
|
|
logger.error(f"Previous task failed: {previousResult.error}")
|
|
return None
|
|
|
|
# Extract task data from previous result
|
|
taskData = previousResult.data
|
|
if not taskData:
|
|
logger.error("No task data in previous result")
|
|
return None
|
|
|
|
# Create next task
|
|
nextTask = TaskItem(
|
|
id=f"task_{datetime.now(UTC).timestamp()}",
|
|
workflowId=workflow.id,
|
|
userInput=taskData.get('objective', ''),
|
|
actionList=await self._createActions(taskData.get('actions', [])),
|
|
status=TaskStatus.PENDING,
|
|
createdAt=datetime.now(UTC),
|
|
updatedAt=datetime.now(UTC)
|
|
)
|
|
|
|
# Add task to workflow
|
|
workflow.tasks.append(nextTask)
|
|
return nextTask
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error creating next task: {str(e)}")
|
|
return None
|
|
|
|
async def identifyNextTask(self, workflow: ChatWorkflow) -> TaskResult:
|
|
"""Identify the next task based on workflow state"""
|
|
try:
|
|
# Get workflow summary
|
|
summary = await self._summarizeWorkflow()
|
|
|
|
# Generate prompt for next task
|
|
prompt = f"""Based on the workflow summary:
|
|
{summary}
|
|
|
|
Determine what the next task should be.
|
|
Return a JSON object with:
|
|
- objective: The main goal or task to accomplish
|
|
- actions: List of required actions with method and parameters
|
|
"""
|
|
|
|
# Get AI response
|
|
response = await self._callAiBasic(prompt)
|
|
|
|
# Parse response
|
|
try:
|
|
result = json.loads(response)
|
|
return TaskResult(
|
|
taskId=f"analysis_{datetime.now(UTC).timestamp()}",
|
|
status=TaskStatus.COMPLETED,
|
|
success=True,
|
|
timestamp=datetime.now(UTC),
|
|
data=result
|
|
)
|
|
except json.JSONDecodeError as e:
|
|
logger.error(f"Error parsing AI response: {str(e)}")
|
|
return TaskResult(
|
|
taskId=f"analysis_{datetime.now(UTC).timestamp()}",
|
|
status=TaskStatus.FAILED,
|
|
success=False,
|
|
timestamp=datetime.now(UTC),
|
|
error=f"Error parsing AI response: {str(e)}"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error identifying next task: {str(e)}")
|
|
return TaskResult(
|
|
taskId=f"analysis_{datetime.now(UTC).timestamp()}",
|
|
status=TaskStatus.FAILED,
|
|
success=False,
|
|
timestamp=datetime.now(UTC),
|
|
error=f"Error identifying next task: {str(e)}"
|
|
)
|
|
|
|
async def callAi(self, messages: List[Dict[str, str]], produceUserAnswer: bool = False, temperature: float = None) -> str:
|
|
"""Enhanced AI service call with language support."""
|
|
if not self.service or not self.service.base:
|
|
logger.error("AI service not set in ChatManager")
|
|
return "Error: AI service not available"
|
|
|
|
# Add language instruction for user-facing responses
|
|
if produceUserAnswer and self.userLanguage:
|
|
ltext = f"Please respond in '{self.userLanguage}' language."
|
|
if messages and messages[0]["role"] == "system":
|
|
if "language" not in messages[0]["content"].lower():
|
|
messages[0]["content"] = f"{ltext} {messages[0]['content']}"
|
|
else:
|
|
# Insert a system message with language instruction
|
|
messages.insert(0, {
|
|
"role": "system",
|
|
"content": ltext
|
|
})
|
|
|
|
# Call the AI service
|
|
if temperature is not None:
|
|
return await self.service.base.callAi(messages, temperature=temperature)
|
|
else:
|
|
return await self.service.base.callAi(messages)
|
|
|
|
async def callAi4Image(self, imageData: Union[str, bytes], mimeType: str = None, prompt: str = "Describe this image") -> str:
|
|
"""Enhanced AI service call with language support."""
|
|
if not self.service or not self.service.base:
|
|
logger.error("AI service not set in ChatManager")
|
|
return "Error: AI service not available"
|
|
return await self.service.base.analyzeImage(imageData, mimeType, prompt)
|
|
|
|
async def _callAiBasic(self, prompt: str) -> str:
|
|
"""Call basic AI service"""
|
|
try:
|
|
if not self.service or not self.service.base:
|
|
raise ValueError("Service or base interface not initialized")
|
|
return await self.callAi([
|
|
{"role": "system", "content": "You are an AI assistant that helps process user requests."},
|
|
{"role": "user", "content": prompt}
|
|
])
|
|
except Exception as e:
|
|
logger.error(f"Error calling AI service: {str(e)}")
|
|
raise
|
|
|
|
async def _callAiAdvanced(self, prompt: str, context: Dict[str, Any]) -> str:
|
|
"""Call advanced AI model with context"""
|
|
# TODO: Implement actual AI call
|
|
return "AI response placeholder"
|
|
|
|
async def generateWorkflowFeedback(self, workflow: ChatWorkflow) -> str:
|
|
"""
|
|
Generates a final feedback message for the workflow in the user's language.
|
|
|
|
Args:
|
|
workflow: The completed workflow to generate feedback for
|
|
|
|
Returns:
|
|
str: The generated feedback message
|
|
"""
|
|
try:
|
|
# Get workflow summary
|
|
workflowSummary = {
|
|
"status": workflow.status,
|
|
"totalMessages": len(workflow.messages),
|
|
"totalDocuments": sum(len(msg.documents) for msg in workflow.messages),
|
|
"duration": (datetime.now(UTC) - datetime.fromisoformat(workflow.startedAt)).total_seconds()
|
|
}
|
|
|
|
# Get user language from workflow mandate
|
|
userLanguage = workflow.mandateId.split('_')[0] if workflow.mandateId else 'en'
|
|
self.setUserLanguage(userLanguage)
|
|
|
|
# Prepare messages for AI context
|
|
messages = [
|
|
{
|
|
"role": "system",
|
|
"content": f"You are an AI assistant providing a summary of a completed workflow. "
|
|
f"Please respond in '{userLanguage}' language. "
|
|
f"Summarize the workflow's activities, outcomes, and any important points. "
|
|
f"Be concise but informative. Use a professional but friendly tone."
|
|
},
|
|
{
|
|
"role": "user",
|
|
"content": f"Please provide a summary of this workflow:\n"
|
|
f"Status: {workflowSummary['status']}\n"
|
|
f"Total Messages: {workflowSummary['totalMessages']}\n"
|
|
f"Total Documents: {workflowSummary['totalDocuments']}\n"
|
|
f"Duration: {workflowSummary['duration']:.1f} seconds"
|
|
}
|
|
]
|
|
|
|
# Add relevant workflow messages for context
|
|
for msg in workflow.messages:
|
|
if msg.role == "user" or msg.status in ["first", "last"]:
|
|
messages.append({
|
|
"role": msg.role,
|
|
"content": msg.message
|
|
})
|
|
|
|
# Generate feedback using AI
|
|
feedback = await self.callAi(messages, produceUserAnswer=True, temperature=0.7)
|
|
|
|
return feedback
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error generating workflow feedback: {str(e)}")
|
|
return "Workflow completed successfully."
|
|
|
|
def _generatePrompt(self, task: str, document: ChatDocument, examples: List[Dict[str, str]] = None) -> str:
|
|
"""Generate a prompt based on task and document"""
|
|
try:
|
|
# Create base prompt
|
|
prompt = f"""Task: {task}
|
|
Document: {document.filename} ({document.mimeType})
|
|
|
|
"""
|
|
|
|
# Add examples if provided
|
|
if examples:
|
|
prompt += "\nExamples:\n"
|
|
for example in examples:
|
|
prompt += f"Input: {example.get('input', '')}\n"
|
|
prompt += f"Output: {example.get('output', '')}\n\n"
|
|
|
|
return prompt
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error generating prompt: {str(e)}")
|
|
return ""
|
|
|
|
# ===== Task Execution and Processing =====
|
|
async def executeTask(self, task: TaskItem) -> TaskResult:
|
|
"""Execute a task and return its result"""
|
|
try:
|
|
# Create result object
|
|
result = TaskResult(
|
|
taskId=task.id,
|
|
status=task.status,
|
|
success=True,
|
|
timestamp=datetime.now(UTC)
|
|
)
|
|
|
|
# Start timing
|
|
startTime = datetime.now(UTC)
|
|
|
|
# Execute each action
|
|
for action in task.actionList:
|
|
try:
|
|
# Execute action
|
|
actionResult = await action.execute()
|
|
|
|
# Update action status
|
|
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)
|
|
else:
|
|
result.feedback = f"Task failed: {result.error}"
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.error(f"Task execution error: {str(e)}")
|
|
raise
|
|
|
|
async def parseTaskResult(self, workflow: ChatWorkflow, result: TaskResult) -> None:
|
|
"""Process and store task result in workflow"""
|
|
try:
|
|
# Find task in workflow
|
|
task = next((t for t in workflow.tasks if t.id == result.taskId), None)
|
|
if not task:
|
|
logger.error(f"Task {result.taskId} not found in workflow")
|
|
return
|
|
|
|
# Update task status
|
|
task.status = result.status
|
|
if result.error:
|
|
task.error = result.error
|
|
|
|
# Create feedback message if available
|
|
if result.feedback:
|
|
message = ChatMessage(
|
|
id=str(uuid.uuid4()),
|
|
workflowId=workflow.id,
|
|
role="assistant",
|
|
message=result.feedback,
|
|
status="step",
|
|
documents=result.documents
|
|
)
|
|
workflow.messages.append(message)
|
|
|
|
# Update workflow stats
|
|
if result.processingTime:
|
|
if not workflow.stats:
|
|
workflow.stats = ChatStat()
|
|
workflow.stats.processingTime = (workflow.stats.processingTime or 0) + result.processingTime
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error parsing task result: {str(e)}")
|
|
raise
|
|
|
|
async def shouldContinue(self, workflow: ChatWorkflow) -> bool:
|
|
"""Determine if workflow should continue"""
|
|
try:
|
|
# Check if workflow is in a terminal state
|
|
if workflow.status in ["completed", "failed", "stopped"]:
|
|
return False
|
|
|
|
# Check if there are any pending tasks
|
|
hasPendingTasks = any(t.status == TaskStatus.PENDING for t in workflow.tasks)
|
|
if not hasPendingTasks:
|
|
return False
|
|
|
|
# Check if any task is currently running
|
|
hasRunningTasks = any(t.status == TaskStatus.RUNNING for t in workflow.tasks)
|
|
if hasRunningTasks:
|
|
return True
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error checking workflow continuation: {str(e)}")
|
|
return False
|
|
|
|
async def _summarizeWorkflow(self) -> str:
|
|
"""Summarize workflow history"""
|
|
if not self.workflow.messages:
|
|
return ""
|
|
|
|
prompt = f"""Summarize the following chat history:
|
|
{json.dumps([m.dict() for m in self.workflow.messages], indent=2)}
|
|
|
|
Please provide a concise summary focusing on:
|
|
1. Main objectives
|
|
2. Key actions taken
|
|
3. Current status
|
|
4. Any issues or blockers
|
|
"""
|
|
|
|
return await self._callAiBasic(prompt)
|
|
|
|
async def _analyzeTaskResults(self, task: TaskItem) -> Dict[str, Any]:
|
|
"""Analyze task results to determine next steps"""
|
|
# Get workflow summary
|
|
summary = await self._summarizeWorkflow()
|
|
|
|
# Generate prompt for analysis
|
|
prompt = f"""Based on the workflow summary and task results:
|
|
{summary}
|
|
|
|
Task: {task.userInput}
|
|
Status: {task.status}
|
|
Error: {task.error if task.error else 'None'}
|
|
|
|
Determine if the workflow is complete and what the next steps should be.
|
|
Return a JSON object with:
|
|
- isComplete: boolean
|
|
- objective: string
|
|
- nextActions: array of action objects
|
|
"""
|
|
|
|
# Get AI response
|
|
response = await self._callAiBasic(prompt)
|
|
|
|
# Parse response
|
|
return json.loads(response)
|
|
|
|
def _promptInstructions(self, methodCatalog: Dict[str, Any], isInitialTask: bool = False) -> str:
|
|
"""Generate common prompt instructions for task analysis"""
|
|
instructions = f"""Available Methods and Actions:
|
|
{json.dumps(methodCatalog, indent=2)}
|
|
|
|
"""
|
|
if isInitialTask:
|
|
instructions += """Please provide a JSON response with:
|
|
1. objective: The main goal or task to accomplish
|
|
2. actions: List of required actions with method and parameters
|
|
|
|
Example format:
|
|
{
|
|
"objective": "Search for documents about project X",
|
|
"actions": [
|
|
{
|
|
"method": "sharepoint",
|
|
"action": "search",
|
|
"parameters": {
|
|
"query": "project X",
|
|
"site": "projects"
|
|
}
|
|
}
|
|
]
|
|
}"""
|
|
else:
|
|
instructions += """Please provide a JSON response with:
|
|
1. isComplete: Whether the workflow is complete
|
|
2. nextActions: List of next actions needed (if any)
|
|
3. issues: Any issues or blockers identified
|
|
|
|
Example format:
|
|
{
|
|
"isComplete": false,
|
|
"nextActions": [
|
|
{
|
|
"method": "sharepoint",
|
|
"action": "read",
|
|
"parameters": {
|
|
"documentId": "doc123"
|
|
}
|
|
}
|
|
],
|
|
"issues": ["Need authentication for SharePoint"]
|
|
}
|
|
|
|
Note: Only use methods and actions that are available in the method catalog above."""
|
|
|
|
return instructions
|
|
|
|
async def _processUserInput(self, userInput: Dict[str, Any], methodCatalog: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Process user input with AI to extract objectives and actions"""
|
|
# Create prompt with available methods and actions
|
|
prompt = f"""Given the following user input and available methods/actions, extract the objective and required actions:
|
|
|
|
User Input: {userInput.get('message', '')}
|
|
|
|
{self._promptInstructions(methodCatalog, isInitialTask=True)}"""
|
|
|
|
# Call AI service
|
|
response = await self._callAiBasic(prompt)
|
|
return json.loads(response)
|
|
|
|
async def _createActions(self, actionsData: List[Dict[str, Any]]) -> List[TaskItem]:
|
|
"""Create action objects from processed input"""
|
|
actions = []
|
|
for actionData in actionsData:
|
|
try:
|
|
# Validate required fields
|
|
if not all(k in actionData for k in ['method', 'action']):
|
|
logger.warning(f"Skipping invalid action data: {actionData}")
|
|
continue
|
|
|
|
action = TaskItem(
|
|
id=f"action_{datetime.now(UTC).timestamp()}",
|
|
method=actionData['method'],
|
|
action=actionData['action'],
|
|
parameters=actionData.get('parameters', {}),
|
|
status=TaskStatus.PENDING,
|
|
retryCount=0,
|
|
retryMax=actionData.get('retryMax', 3)
|
|
)
|
|
actions.append(action)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error creating action: {str(e)}")
|
|
continue
|
|
|
|
return actions
|
|
|
|
async def _processTaskResults(self, task: TaskItem) -> str:
|
|
"""Process task results and generate feedback"""
|
|
try:
|
|
# Generate document prompt
|
|
docPrompt = self._generateDocumentPrompt(task.userInput)
|
|
|
|
# Get AI response for document generation
|
|
docResponse = await self._callAiBasic(docPrompt)
|
|
|
|
# Parse response into TaskDocument objects
|
|
try:
|
|
taskDocs = json.loads(docResponse)
|
|
task.documentsOutput = taskDocs
|
|
except json.JSONDecodeError as e:
|
|
logger.error(f"Error parsing document response: {str(e)}")
|
|
return f"Error processing results: {str(e)}"
|
|
|
|
# Generate feedback
|
|
feedback = await self._callAiBasic(
|
|
f"""Generate feedback for the completed task:
|
|
Task: {task.userInput}
|
|
Generated Documents: {len(task.documentsOutput)} files
|
|
|
|
Provide a concise summary of what was accomplished.
|
|
"""
|
|
)
|
|
|
|
return feedback
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing task results: {str(e)}")
|
|
return f"Error processing results: {str(e)}"
|
|
|
|
async def _createOutputDocuments(self, task: TaskItem) -> List[ChatDocument]:
|
|
"""Create output documents from task results"""
|
|
try:
|
|
fileIds = []
|
|
|
|
# Process each TaskDocument from AI output
|
|
for taskDoc in task.documentsOutput:
|
|
# Store file in database
|
|
fileItem = self.service.functions.createFile(
|
|
name=taskDoc.filename,
|
|
mimeType=taskDoc.mimeType
|
|
)
|
|
|
|
# Store file content
|
|
if taskDoc.base64Encoded:
|
|
# Decode base64 content
|
|
content = base64.b64decode(taskDoc.data)
|
|
else:
|
|
# Use text content directly
|
|
content = taskDoc.data.encode('utf-8')
|
|
|
|
# Store file data
|
|
self.service.functions.createFileData(fileItem.id, content)
|
|
fileIds.append(fileItem.id)
|
|
|
|
# Convert all files to ChatDocuments in one call
|
|
if fileIds:
|
|
return await self.processFileIds(fileIds)
|
|
return []
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error creating output documents: {str(e)}")
|
|
return []
|
|
|
|
async def processFileIds(self, fileIds: List[str]) -> List[ChatDocument]:
|
|
"""Process multiple files and extract their contents."""
|
|
documents = []
|
|
for fileId in fileIds:
|
|
# Get file metadata
|
|
fileMetadata = self.service.functions.getFile(fileId)
|
|
if not fileMetadata:
|
|
logger.warning(f"File metadata not found for {fileId}")
|
|
continue
|
|
|
|
# Create ChatDocument
|
|
document = ChatDocument(
|
|
id=str(uuid.uuid4()),
|
|
fileId=fileId,
|
|
filename=fileMetadata.get("name", "Unknown"),
|
|
fileSize=fileMetadata.get("size", 0),
|
|
mimeType=fileMetadata.get("mimeType", "text/plain")
|
|
)
|
|
|
|
documents.append(document)
|
|
return documents
|
|
|
|
async def addTaskResult(self, workflow: ChatWorkflow, result: TaskResult) -> None:
|
|
"""Add task result to workflow and update status"""
|
|
try:
|
|
# Find task in workflow
|
|
task = next((t for t in workflow.tasks if t.id == result.taskId), None)
|
|
if not task:
|
|
logger.error(f"Task {result.taskId} not found in workflow")
|
|
return
|
|
|
|
# Update task status
|
|
task.status = result.status
|
|
if result.error:
|
|
task.error = result.error
|
|
|
|
# Create feedback message if available
|
|
if result.feedback:
|
|
message = ChatMessage(
|
|
id=str(uuid.uuid4()),
|
|
workflowId=workflow.id,
|
|
role="assistant",
|
|
message=result.feedback,
|
|
status="step",
|
|
documents=result.documents
|
|
)
|
|
workflow.messages.append(message)
|
|
|
|
# Update workflow stats
|
|
if result.processingTime:
|
|
if not workflow.stats:
|
|
workflow.stats = ChatStat()
|
|
workflow.stats.processingTime = (workflow.stats.processingTime or 0) + result.processingTime
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error adding task result: {str(e)}")
|
|
|
|
def _generateDocumentPrompt(self, task: str) -> str:
|
|
"""Generate a prompt for document generation"""
|
|
return f"""Generate output documents for the following task:
|
|
|
|
Task: {task}
|
|
|
|
For each document you need to generate, provide a TaskDocument object with the following structure:
|
|
{{
|
|
"filename": "string", # Filename with extension
|
|
"mimeType": "string", # MIME type of the file
|
|
"data": "string", # File content as text or base64
|
|
"base64Encoded": boolean # True if data is base64 encoded
|
|
}}
|
|
|
|
Rules:
|
|
1. For text files (txt, json, xml, etc.), provide content directly in the data field
|
|
2. For binary files (images, videos, etc.), encode content in base64 and set base64Encoded to true
|
|
3. Use appropriate MIME types (e.g., text/plain, image/jpeg, application/pdf)
|
|
4. Include file extensions in filenames
|
|
|
|
Return a JSON array of TaskDocument objects.
|
|
""" |