clean flow
This commit is contained in:
parent
d03b82a49f
commit
12fd5e0a41
12 changed files with 383 additions and 352 deletions
|
|
@ -88,7 +88,7 @@ class DocumentExtraction:
|
||||||
import PyPDF2
|
import PyPDF2
|
||||||
import fitz # PyMuPDF for more extensive PDF processing
|
import fitz # PyMuPDF for more extensive PDF processing
|
||||||
pdfExtractorLoaded = True
|
pdfExtractorLoaded = True
|
||||||
logger.info("PDF extraction libraries successfully loaded")
|
logger.debug("📄 PDF extraction libraries successfully loaded")
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
logger.warning(f"PDF extraction libraries could not be loaded: {e}")
|
logger.warning(f"PDF extraction libraries could not be loaded: {e}")
|
||||||
|
|
||||||
|
|
@ -101,7 +101,7 @@ class DocumentExtraction:
|
||||||
import docx # python-docx for Word documents
|
import docx # python-docx for Word documents
|
||||||
import openpyxl # for Excel files
|
import openpyxl # for Excel files
|
||||||
officeExtractorLoaded = True
|
officeExtractorLoaded = True
|
||||||
logger.info("Office extraction libraries successfully loaded")
|
logger.debug("📄 Office extraction libraries successfully loaded")
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
logger.warning(f"Office extraction libraries could not be loaded: {e}")
|
logger.warning(f"Office extraction libraries could not be loaded: {e}")
|
||||||
|
|
||||||
|
|
@ -113,7 +113,7 @@ class DocumentExtraction:
|
||||||
global PIL, Image
|
global PIL, Image
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
imageProcessorLoaded = True
|
imageProcessorLoaded = True
|
||||||
logger.info("Image processing libraries successfully loaded")
|
logger.debug("📄 Image processing libraries successfully loaded")
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
logger.warning(f"Image processing libraries could not be loaded: {e}")
|
logger.warning(f"Image processing libraries could not be loaded: {e}")
|
||||||
|
|
||||||
|
|
@ -157,7 +157,7 @@ class DocumentExtraction:
|
||||||
processedItems = await self._aiDataExtraction(contentItems, prompt)
|
processedItems = await self._aiDataExtraction(contentItems, prompt)
|
||||||
contentItems = processedItems
|
contentItems = processedItems
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing content with AI: {str(e)}")
|
logger.error(f"❌ Error processing content with AI: {str(e)}")
|
||||||
|
|
||||||
return ExtractedContent(
|
return ExtractedContent(
|
||||||
id=documentId if documentId else str(uuid.uuid4()),
|
id=documentId if documentId else str(uuid.uuid4()),
|
||||||
|
|
@ -165,7 +165,7 @@ class DocumentExtraction:
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing file data: {str(e)}")
|
logger.error(f"❌ Error processing file data: {str(e)}")
|
||||||
raise FileProcessingError(f"Failed to process file data: {str(e)}")
|
raise FileProcessingError(f"Failed to process file data: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -187,7 +187,7 @@ class DocumentExtraction:
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing text document: {str(e)}")
|
logger.error(f"❌ Error processing text document: {str(e)}")
|
||||||
raise FileProcessingError(f"Failed to process text document: {str(e)}")
|
raise FileProcessingError(f"Failed to process text document: {str(e)}")
|
||||||
|
|
||||||
async def _processCsv(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
async def _processCsv(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
||||||
|
|
@ -206,7 +206,7 @@ class DocumentExtraction:
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing CSV document: {str(e)}")
|
logger.error(f"❌ Error processing CSV document: {str(e)}")
|
||||||
raise FileProcessingError(f"Failed to process CSV document: {str(e)}")
|
raise FileProcessingError(f"Failed to process CSV document: {str(e)}")
|
||||||
|
|
||||||
async def _processJson(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
async def _processJson(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
||||||
|
|
@ -226,7 +226,7 @@ class DocumentExtraction:
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing JSON document: {str(e)}")
|
logger.error(f"❌ Error processing JSON document: {str(e)}")
|
||||||
raise FileProcessingError(f"Failed to process JSON document: {str(e)}")
|
raise FileProcessingError(f"Failed to process JSON document: {str(e)}")
|
||||||
|
|
||||||
async def _processXml(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
async def _processXml(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
||||||
|
|
@ -245,7 +245,7 @@ class DocumentExtraction:
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing XML document: {str(e)}")
|
logger.error(f"❌ Error processing XML document: {str(e)}")
|
||||||
raise FileProcessingError(f"Failed to process XML document: {str(e)}")
|
raise FileProcessingError(f"Failed to process XML document: {str(e)}")
|
||||||
|
|
||||||
async def _processHtml(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
async def _processHtml(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
||||||
|
|
@ -264,7 +264,7 @@ class DocumentExtraction:
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing HTML document: {str(e)}")
|
logger.error(f"❌ Error processing HTML document: {str(e)}")
|
||||||
raise FileProcessingError(f"Failed to process HTML document: {str(e)}")
|
raise FileProcessingError(f"Failed to process HTML document: {str(e)}")
|
||||||
|
|
||||||
async def _processSvg(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
async def _processSvg(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
||||||
|
|
@ -284,7 +284,7 @@ class DocumentExtraction:
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing SVG document: {str(e)}")
|
logger.error(f"❌ Error processing SVG document: {str(e)}")
|
||||||
raise FileProcessingError(f"Failed to process SVG document: {str(e)}")
|
raise FileProcessingError(f"Failed to process SVG document: {str(e)}")
|
||||||
|
|
||||||
async def _processImage(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
async def _processImage(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
||||||
|
|
@ -315,7 +315,7 @@ class DocumentExtraction:
|
||||||
metadata=metadata
|
metadata=metadata
|
||||||
)]
|
)]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing image document: {str(e)}")
|
logger.error(f"❌ Error processing image document: {str(e)}")
|
||||||
raise FileProcessingError(f"Failed to process image document: {str(e)}")
|
raise FileProcessingError(f"Failed to process image document: {str(e)}")
|
||||||
|
|
||||||
async def _processPdf(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
async def _processPdf(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
||||||
|
|
@ -378,13 +378,13 @@ class DocumentExtraction:
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
except Exception as imgE:
|
except Exception as imgE:
|
||||||
logger.warning(f"Error extracting image {imgIndex} on page {pageNum + 1}: {str(imgE)}")
|
logger.warning(f"⚠️ Error extracting image {imgIndex} on page {pageNum + 1}: {str(imgE)}")
|
||||||
|
|
||||||
doc.close()
|
doc.close()
|
||||||
|
|
||||||
return contentItems
|
return contentItems
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing PDF document: {str(e)}")
|
logger.error(f"❌ Error processing PDF document: {str(e)}")
|
||||||
raise FileProcessingError(f"Failed to process PDF document: {str(e)}")
|
raise FileProcessingError(f"Failed to process PDF document: {str(e)}")
|
||||||
|
|
||||||
async def _processDocx(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
async def _processDocx(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
||||||
|
|
@ -423,7 +423,7 @@ class DocumentExtraction:
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing Word document: {str(e)}")
|
logger.error(f"❌ Error processing Word document: {str(e)}")
|
||||||
raise FileProcessingError(f"Failed to process Word document: {str(e)}")
|
raise FileProcessingError(f"Failed to process Word document: {str(e)}")
|
||||||
|
|
||||||
async def _processXlsx(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
async def _processXlsx(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
||||||
|
|
@ -465,7 +465,7 @@ class DocumentExtraction:
|
||||||
|
|
||||||
return contentItems
|
return contentItems
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing Excel document: {str(e)}")
|
logger.error(f"❌ Error processing Excel document: {str(e)}")
|
||||||
raise FileProcessingError(f"Failed to process Excel document: {str(e)}")
|
raise FileProcessingError(f"Failed to process Excel document: {str(e)}")
|
||||||
|
|
||||||
async def _processBinary(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
async def _processBinary(self, fileData: bytes, filename: str, mimeType: str) -> List[ContentItem]:
|
||||||
|
|
@ -482,7 +482,7 @@ class DocumentExtraction:
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing binary document: {str(e)}")
|
logger.error(f"❌ Error processing binary document: {str(e)}")
|
||||||
raise FileProcessingError(f"Failed to process binary document: {str(e)}")
|
raise FileProcessingError(f"Failed to process binary document: {str(e)}")
|
||||||
|
|
||||||
async def _aiDataExtraction(self, contentItems: List[ContentItem], prompt: str) -> List[ContentItem]:
|
async def _aiDataExtraction(self, contentItems: List[ContentItem], prompt: str) -> List[ContentItem]:
|
||||||
|
|
@ -502,7 +502,7 @@ class DocumentExtraction:
|
||||||
try:
|
try:
|
||||||
# Get content type from metadata
|
# Get content type from metadata
|
||||||
mimeType = item.metadata.mimeType if hasattr(item.metadata, 'mimeType') else "text/plain"
|
mimeType = item.metadata.mimeType if hasattr(item.metadata, 'mimeType') else "text/plain"
|
||||||
logger.debug(f"Processing content item with MIME type: {mimeType}, label: {item.label}")
|
logger.debug(f"📄 Processing content item with MIME type: {mimeType}, label: {item.label}")
|
||||||
|
|
||||||
# Chunk content based on type
|
# Chunk content based on type
|
||||||
if mimeType.startswith('text/'):
|
if mimeType.startswith('text/'):
|
||||||
|
|
@ -527,12 +527,12 @@ class DocumentExtraction:
|
||||||
for chunk in chunks:
|
for chunk in chunks:
|
||||||
# Process with AI based on content type
|
# Process with AI based on content type
|
||||||
try:
|
try:
|
||||||
logger.debug(f"AI processing chunk with MIME type: {mimeType}")
|
logger.debug(f"🤖 AI processing chunk with MIME type: {mimeType}")
|
||||||
if mimeType.startswith('image/'):
|
if mimeType.startswith('image/'):
|
||||||
# For images, use image AI service with base64 data
|
# For images, use image AI service with base64 data
|
||||||
# chunk is already base64 encoded string from _processImage
|
# chunk is already base64 encoded string from _processImage
|
||||||
# Use the original prompt directly for images (no content embedding)
|
# Use the original prompt directly for images (no content embedding)
|
||||||
logger.debug(f"Calling image AI service for MIME type: {mimeType}")
|
logger.debug(f"🤖 Calling image AI service for MIME type: {mimeType}")
|
||||||
processedContent = await self._serviceCenter.callAiImageBasic(prompt, chunk, mimeType)
|
processedContent = await self._serviceCenter.callAiImageBasic(prompt, chunk, mimeType)
|
||||||
else:
|
else:
|
||||||
# For text content, use text AI service
|
# For text content, use text AI service
|
||||||
|
|
@ -553,14 +553,14 @@ class DocumentExtraction:
|
||||||
Return ONLY the extracted information in a clear, concise format.
|
Return ONLY the extracted information in a clear, concise format.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger.debug(f"Calling text AI service for MIME type: {mimeType}")
|
logger.debug(f"🤖 Calling text AI service for MIME type: {mimeType}")
|
||||||
processedContent = await self._serviceCenter.callAiTextBasic(aiPrompt, contentToProcess)
|
processedContent = await self._serviceCenter.callAiTextBasic(aiPrompt, contentToProcess)
|
||||||
|
|
||||||
chunkResults.append(processedContent)
|
chunkResults.append(processedContent)
|
||||||
except Exception as aiError:
|
except Exception as aiError:
|
||||||
logger.error(f"AI processing failed for chunk: {str(aiError)}")
|
logger.error(f"❌ AI processing failed for chunk: {str(aiError)}")
|
||||||
# Fallback to original content
|
# Fallback to original content
|
||||||
chunkResults.append(chunk)
|
chunkResults.append(chunk)
|
||||||
|
|
||||||
# Combine chunk results
|
# Combine chunk results
|
||||||
combinedResult = "\n".join(chunkResults)
|
combinedResult = "\n".join(chunkResults)
|
||||||
|
|
@ -578,7 +578,7 @@ class DocumentExtraction:
|
||||||
))
|
))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing content chunk: {str(e)}")
|
logger.error(f"❌ Error processing content chunk: {str(e)}")
|
||||||
# Add original content if processing fails
|
# Add original content if processing fails
|
||||||
processedItems.append(item)
|
processedItems.append(item)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,13 @@ class DocumentGenerator:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
documents = action_result.data.get("documents", [])
|
documents = action_result.data.get("documents", [])
|
||||||
|
logger.debug(f"Processing {len(documents)} documents from action result")
|
||||||
processed_documents = []
|
processed_documents = []
|
||||||
for doc in documents:
|
for doc in documents:
|
||||||
processed_doc = self.processSingleDocument(doc, action)
|
processed_doc = self.processSingleDocument(doc, action)
|
||||||
if processed_doc:
|
if processed_doc:
|
||||||
processed_documents.append(processed_doc)
|
processed_documents.append(processed_doc)
|
||||||
|
logger.debug(f"Successfully processed {len(processed_documents)} documents")
|
||||||
return processed_documents
|
return processed_documents
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing action result documents: {str(e)}")
|
logger.error(f"Error processing action result documents: {str(e)}")
|
||||||
|
|
@ -119,45 +121,14 @@ class DocumentGenerator:
|
||||||
)
|
)
|
||||||
if document:
|
if document:
|
||||||
created_documents.append(document)
|
created_documents.append(document)
|
||||||
logger.info(f"Created document: {document_name} with file ID: {file_id} and MIME type: {mime_type}")
|
logger.debug(f"Created document: {document_name} ({len(content)} bytes, {mime_type})")
|
||||||
else:
|
else:
|
||||||
logger.error(f"Failed to create ChatDocument object for {document_name}")
|
logger.error(f"Failed to create ChatDocument object for {document_name}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating document {doc_data.get('filename', 'unknown')}: {str(e)}")
|
logger.error(f"Error creating document {doc_data.get('filename', 'unknown')}: {str(e)}")
|
||||||
continue
|
continue
|
||||||
|
logger.info(f"Created {len(created_documents)} documents from action result")
|
||||||
return created_documents
|
return created_documents
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating documents from action result: {str(e)}")
|
logger.error(f"Error creating documents from action result: {str(e)}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_delivered_files_and_formats(documents):
|
|
||||||
delivered_files = []
|
|
||||||
delivered_formats = []
|
|
||||||
for doc in documents:
|
|
||||||
if hasattr(doc, 'filename'):
|
|
||||||
delivered_files.append(doc.filename)
|
|
||||||
file_extension = getFileExtension(doc.filename)
|
|
||||||
mime_type = getattr(doc, 'mimeType', 'application/octet-stream')
|
|
||||||
delivered_formats.append({
|
|
||||||
'filename': doc.filename,
|
|
||||||
'extension': file_extension,
|
|
||||||
'mimeType': mime_type
|
|
||||||
})
|
|
||||||
elif isinstance(doc, dict) and 'filename' in doc:
|
|
||||||
delivered_files.append(doc['filename'])
|
|
||||||
file_extension = getFileExtension(doc['filename'])
|
|
||||||
mime_type = doc.get('mimeType', 'application/octet-stream')
|
|
||||||
delivered_formats.append({
|
|
||||||
'filename': doc['filename'],
|
|
||||||
'extension': file_extension,
|
|
||||||
'mimeType': mime_type
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
delivered_files.append(f"document_{len(delivered_files)}")
|
|
||||||
delivered_formats.append({
|
|
||||||
'filename': f"document_{len(delivered_files)}",
|
|
||||||
'extension': 'unknown',
|
|
||||||
'mimeType': 'application/octet-stream'
|
|
||||||
})
|
|
||||||
return delivered_files, delivered_formats
|
|
||||||
|
|
@ -1,42 +1,56 @@
|
||||||
# executionState.py
|
# executionState.py
|
||||||
# Contains all execution state management logic extracted from managerChat.py
|
# Contains all execution state management logic extracted from managerChat.py
|
||||||
|
|
||||||
|
import logging
|
||||||
from typing import List
|
from typing import List
|
||||||
from modules.interfaces.interfaceChatModel import TaskStep, ActionExecutionResult
|
from datetime import datetime, UTC
|
||||||
|
from modules.interfaces.interfaceChatModel import TaskStep, ActionResult
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class TaskExecutionState:
|
class TaskExecutionState:
|
||||||
"""Manages state during task execution with retry logic"""
|
"""Manages execution state for a task with retry logic"""
|
||||||
|
|
||||||
def __init__(self, task_step: TaskStep):
|
def __init__(self, task_step: TaskStep):
|
||||||
self.task_step = task_step
|
self.task_step = task_step
|
||||||
self.successful_actions: List[ActionExecutionResult] = [] # Preserved across retries
|
self.successful_actions: List[ActionResult] = [] # Preserved across retries
|
||||||
self.failed_actions: List[ActionExecutionResult] = [] # For analysis
|
self.failed_actions: List[ActionResult] = [] # For analysis
|
||||||
self.current_action_index = 0
|
self.current_action_index = 0
|
||||||
self.retry_count = 0
|
self.retry_count = 0
|
||||||
self.improvements = []
|
|
||||||
self.partial_results = {} # Store intermediate results
|
|
||||||
self.max_retries = 3
|
self.max_retries = 3
|
||||||
|
|
||||||
def addSuccessfulAction(self, action_result: ActionExecutionResult):
|
def addSuccessfulAction(self, action_result: ActionResult):
|
||||||
|
"""Add a successful action to the state"""
|
||||||
self.successful_actions.append(action_result)
|
self.successful_actions.append(action_result)
|
||||||
if action_result.data.get('resultLabel'):
|
self.current_action_index += 1
|
||||||
self.partial_results[action_result.data['resultLabel']] = action_result
|
|
||||||
|
|
||||||
def addFailedAction(self, action_result: ActionExecutionResult):
|
def addFailedAction(self, action_result: ActionResult):
|
||||||
|
"""Add a failed action to the state for analysis"""
|
||||||
self.failed_actions.append(action_result)
|
self.failed_actions.append(action_result)
|
||||||
|
self.current_action_index += 1
|
||||||
|
|
||||||
def getAvailableResults(self) -> list:
|
def getAvailableResults(self) -> list:
|
||||||
return [result.data.get('resultLabel', '') for result in self.successful_actions if result.data.get('resultLabel')]
|
"""Get available results from successful actions"""
|
||||||
|
results = []
|
||||||
|
for action in self.successful_actions:
|
||||||
|
if action.data and action.data.get('result'):
|
||||||
|
results.append(action.data['result'])
|
||||||
|
return results
|
||||||
|
|
||||||
def shouldRetryTask(self) -> bool:
|
def shouldRetryTask(self) -> bool:
|
||||||
return len(self.successful_actions) > 0 and len(self.failed_actions) > 0
|
"""Determine if task should be retried based on failure patterns"""
|
||||||
|
return len(self.failed_actions) > 0 and self.canRetry()
|
||||||
|
|
||||||
def canRetry(self) -> bool:
|
def canRetry(self) -> bool:
|
||||||
|
"""Check if task can be retried"""
|
||||||
return self.retry_count < self.max_retries
|
return self.retry_count < self.max_retries
|
||||||
|
|
||||||
def incrementRetryCount(self):
|
def incrementRetryCount(self):
|
||||||
|
"""Increment retry count"""
|
||||||
self.retry_count += 1
|
self.retry_count += 1
|
||||||
|
|
||||||
def getFailurePatterns(self) -> list:
|
def getFailurePatterns(self) -> list:
|
||||||
|
"""Analyze failure patterns from failed actions"""
|
||||||
patterns = []
|
patterns = []
|
||||||
for action in self.failed_actions:
|
for action in self.failed_actions:
|
||||||
error = action.error.lower() if action.error else ''
|
error = action.error.lower() if action.error else ''
|
||||||
|
|
|
||||||
|
|
@ -1,202 +0,0 @@
|
||||||
# handlingActions.py
|
|
||||||
# Contains all action handling functions extracted from managerChat.py
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
from typing import Dict, Any, Optional, List, Union
|
|
||||||
from datetime import datetime, UTC
|
|
||||||
from modules.interfaces.interfaceChatModel import ReviewResult, ActionResult
|
|
||||||
from .promptFactory import createResultReviewPrompt
|
|
||||||
from modules.chat.documents.documentGeneration import DocumentGenerator
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class HandlingActions:
|
|
||||||
def __init__(self, service, chatInterface):
|
|
||||||
self.service = service
|
|
||||||
self.chatInterface = chatInterface
|
|
||||||
self.documentGenerator = DocumentGenerator(service)
|
|
||||||
|
|
||||||
async def executeSingleAction(self, action, workflow):
|
|
||||||
"""Execute a single action and return ActionResult with enhanced document processing"""
|
|
||||||
try:
|
|
||||||
enhanced_parameters = action.execParameters.copy()
|
|
||||||
if action.expectedDocumentFormats:
|
|
||||||
enhanced_parameters['expectedDocumentFormats'] = action.expectedDocumentFormats
|
|
||||||
logger.info(f"Action {action.execMethod}.{action.execAction} expects formats: {action.expectedDocumentFormats}")
|
|
||||||
result = await self.service.executeAction(
|
|
||||||
methodName=action.execMethod,
|
|
||||||
actionName=action.execAction,
|
|
||||||
parameters=enhanced_parameters
|
|
||||||
)
|
|
||||||
result_label = action.execResultLabel
|
|
||||||
if result.success:
|
|
||||||
action.setSuccess()
|
|
||||||
action.result = result.data.get("result", "")
|
|
||||||
action.execResultLabel = result_label
|
|
||||||
await self.createActionMessage(action, result, workflow, result_label)
|
|
||||||
else:
|
|
||||||
action.setError(result.error or "Action execution failed")
|
|
||||||
processed_documents = self.documentGenerator.processActionResultDocuments(result, action, workflow)
|
|
||||||
return ActionResult(
|
|
||||||
success=result.success,
|
|
||||||
data={
|
|
||||||
"result": result.data.get("result", ""),
|
|
||||||
"documents": processed_documents,
|
|
||||||
"actionId": action.id,
|
|
||||||
"actionMethod": action.execMethod,
|
|
||||||
"actionName": action.execAction,
|
|
||||||
"resultLabel": result_label
|
|
||||||
},
|
|
||||||
metadata={
|
|
||||||
"actionId": action.id,
|
|
||||||
"actionMethod": action.execMethod,
|
|
||||||
"actionName": action.execAction,
|
|
||||||
"resultLabel": result_label
|
|
||||||
},
|
|
||||||
validation=[],
|
|
||||||
error=result.error or ""
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error executing single action: {str(e)}")
|
|
||||||
action.setError(str(e))
|
|
||||||
return ActionResult(
|
|
||||||
success=False,
|
|
||||||
data={
|
|
||||||
"actionId": action.id,
|
|
||||||
"actionMethod": action.execMethod,
|
|
||||||
"actionName": action.execAction,
|
|
||||||
"documents": []
|
|
||||||
},
|
|
||||||
metadata={
|
|
||||||
"actionId": action.id,
|
|
||||||
"actionMethod": action.execMethod,
|
|
||||||
"actionName": action.execAction
|
|
||||||
},
|
|
||||||
validation=[],
|
|
||||||
error=str(e)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def validateActionResult(self, action_result, action, context) -> dict:
|
|
||||||
try:
|
|
||||||
prompt = self._createGenericValidationPrompt(action_result, action, context)
|
|
||||||
response = await self.service.callAiTextAdvanced(prompt, "action_validation")
|
|
||||||
validation = self._parseValidationResponse(response)
|
|
||||||
validation['action_id'] = action.id
|
|
||||||
validation['action_method'] = action.execMethod
|
|
||||||
validation['action_name'] = action.execAction
|
|
||||||
validation['result_label'] = action.execResultLabel
|
|
||||||
return validation
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error validating action result: {str(e)}")
|
|
||||||
return {
|
|
||||||
'status': 'success',
|
|
||||||
'reason': f'Validation failed: {str(e)}',
|
|
||||||
'confidence': 0.5,
|
|
||||||
'improvements': [],
|
|
||||||
'action_id': action.id,
|
|
||||||
'action_method': action.execMethod,
|
|
||||||
'action_name': action.execAction,
|
|
||||||
'result_label': action.execResultLabel
|
|
||||||
}
|
|
||||||
|
|
||||||
async def createActionMessage(self, action, result, workflow, result_label=None):
|
|
||||||
"""Create and store a message for the action result in the workflow with enhanced document processing"""
|
|
||||||
try:
|
|
||||||
if result_label is None:
|
|
||||||
result_label = action.execResultLabel
|
|
||||||
message_data = {
|
|
||||||
"workflowId": workflow.id,
|
|
||||||
"role": "assistant",
|
|
||||||
"message": f"Executed action {action.execMethod}.{action.execAction}",
|
|
||||||
"status": "step",
|
|
||||||
"sequenceNr": len(workflow.messages) + 1,
|
|
||||||
"publishedAt": datetime.now(UTC).isoformat(),
|
|
||||||
"actionId": action.id,
|
|
||||||
"actionMethod": action.execMethod,
|
|
||||||
"actionName": action.execAction,
|
|
||||||
"documentsLabel": result_label,
|
|
||||||
"documents": []
|
|
||||||
}
|
|
||||||
# Use the local createDocumentsFromActionResult method
|
|
||||||
created_documents = self.documentGenerator.createDocumentsFromActionResult(result, action, workflow)
|
|
||||||
message_data["documents"] = created_documents
|
|
||||||
message = self.chatInterface.createWorkflowMessage(message_data)
|
|
||||||
if message:
|
|
||||||
workflow.messages.append(message)
|
|
||||||
logger.info(f"Created action message for {action.execMethod}.{action.execAction} with {len(created_documents)} documents")
|
|
||||||
logger.debug(f"WORKFLOW STATE after createActionMessage: id={id(workflow)}, message_count={len(workflow.messages)}")
|
|
||||||
for idx, msg in enumerate(workflow.messages):
|
|
||||||
label = getattr(msg, 'documentsLabel', None)
|
|
||||||
docs = getattr(msg, 'documents', None)
|
|
||||||
logger.debug(f" Message {idx}: label='{label}', documents_count={len(docs) if docs else 0}")
|
|
||||||
else:
|
|
||||||
logger.error(f"Failed to create workflow message for action {action.execMethod}.{action.execAction}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error creating action message: {str(e)}")
|
|
||||||
|
|
||||||
# Internal helper methods
|
|
||||||
|
|
||||||
def _createGenericValidationPrompt(self, action_result, action, context) -> str:
|
|
||||||
success = action_result.success
|
|
||||||
result_data = action_result.data
|
|
||||||
error = action_result.error
|
|
||||||
validation_messages = action_result.validation
|
|
||||||
result_text = result_data.get("result", "") if isinstance(result_data, dict) else str(result_data)
|
|
||||||
documents = result_data.get("documents", []) if isinstance(result_data, dict) else []
|
|
||||||
doc_count = len(documents)
|
|
||||||
expected_result_label = action.execResultLabel
|
|
||||||
expected_format = action.execParameters.get('outputFormat', 'unknown')
|
|
||||||
expected_document_formats = action.expectedDocumentFormats or []
|
|
||||||
actual_result_label = result_data.get("resultLabel", "") if isinstance(result_data, dict) else ""
|
|
||||||
result_label_match = actual_result_label == expected_result_label
|
|
||||||
# Use DocumentGenerator for file/format extraction
|
|
||||||
delivered_files, delivered_formats = DocumentGenerator.get_delivered_files_and_formats(documents)
|
|
||||||
content_items = []
|
|
||||||
if isinstance(result_data, dict):
|
|
||||||
if 'extractedContent' in result_data:
|
|
||||||
extracted_content = result_data['extractedContent']
|
|
||||||
if hasattr(extracted_content, 'contents'):
|
|
||||||
content_items = extracted_content.contents
|
|
||||||
elif 'contents' in result_data:
|
|
||||||
content_items = result_data['contents']
|
|
||||||
if delivered_files and not content_items:
|
|
||||||
content_items = [f"File content available in: {', '.join(delivered_files)}"]
|
|
||||||
content_summary = []
|
|
||||||
for item in content_items:
|
|
||||||
if hasattr(item, 'label') and hasattr(item, 'metadata'):
|
|
||||||
content_summary.append(f"{item.label}: {item.metadata.mimeType if hasattr(item.metadata, 'mimeType') else 'unknown'}")
|
|
||||||
elif isinstance(item, str):
|
|
||||||
content_summary.append(item)
|
|
||||||
else:
|
|
||||||
content_summary.append(str(item))
|
|
||||||
return f"""You are an action result validator. Your primary focus is to validate that the action delivered the promised result files in the promised format.\n\nACTION DETAILS:\n- Method: {action.execMethod}\n- Action: {action.execAction}\n- Expected Result Label: {expected_result_label}\n- Actual Result Label: {actual_result_label}\n- Result Label Match: {result_label_match}\n- Expected Format: {expected_format}\n- Expected Document Formats: {json.dumps(expected_document_formats, indent=2) if expected_document_formats else 'None specified'}\n- Parameters: {json.dumps(action.execParameters, indent=2)}\n\nRESULT TO VALIDATE:\n- Success: {success}\n- Result Data: {result_text[:500]}{'...' if len(result_text) > 500 else ''}\n- Error: {error}\n- Validation Messages: {', '.join(validation_messages) if validation_messages else 'None'}\n- Documents Produced: {doc_count}\n- Delivered Files: {', '.join(delivered_files) if delivered_files else 'None'}\n- Delivered Formats: {json.dumps(delivered_formats, indent=2) if delivered_formats else 'None'}\n- Content Items: {', '.join(content_summary) if content_summary else 'None'}\n\nCRITICAL VALIDATION CRITERIA:\n1. **Result Label Match**: Does the action result contain the expected result label?\n2. **File Delivery**: Did the action deliver the promised result file(s)?\n3. **Format Compliance**: If expected document formats were specified, do the delivered files match the expected formats?\n4. **Content Quality**: Is the content of the delivered files usable and complete?\n5. **Content Processing**: If content extraction was expected, was it performed correctly?\n\nCONTEXT:\n- Task Description: {context.task_step.description if context.task_step else 'Unknown'}\n- Previous Results: {', '.join(context.previous_results) if context.previous_results else 'None'}\n\nVALIDATION INSTRUCTIONS:\n1. **Result Label Check**: Verify that the expected result label \"{expected_result_label}\" is present in the action result data. This is the primary success criterion.\n2. **File Delivery**: Check if files were delivered when expected. The individual filenames don't need to match the result label - focus on whether content was actually produced.\n3. **Format Compliance**: If expected document formats were specified, check if delivered files match the expected extensions and MIME types. If no formats were specified, this criterion is satisfied.\n4. **Content Quality**: If files were delivered, consider the action successful. The presence of delivered files indicates content was processed and stored.\n5. **Content Processing**: If files were delivered, assume content extraction was performed correctly. The file delivery is evidence of successful processing.\n6. **Success Criteria**: The action is successful if the result label matches AND files were delivered. If expected formats were specified, they should also match.\n\nIMPORTANT NOTES:\n- The result label must be present in the action result data for success\n- Individual filenames can be different from the result label\n- If files were delivered, consider the action successful even if content details are not provided\n- Focus on whether the action accomplished its intended purpose (file delivery)\n- Empty files should be considered failures, but delivered files indicate success\n\nREQUIRED JSON RESPONSE:\n{{\n \"status\": \"success|retry|fail\",\n \"reason\": \"Detailed explanation focusing on result label match and content quality\",\n \"confidence\": 0.0-1.0,\n \"improvements\": [\"specific improvements if needed\"],\n \"quality_score\": 1-10,\n \"missing_elements\": [\"missing result label\", \"missing files\", \"content issues\"],\n \"suggested_retry_approach\": \"Specific approach for retry if status is retry\"\n}}\n\nNOTE: Respond with ONLY the JSON object. Do not include any explanatory text."""
|
|
||||||
|
|
||||||
def _parseValidationResponse(self, response: str) -> dict:
|
|
||||||
try:
|
|
||||||
json_start = response.find('{')
|
|
||||||
json_end = response.rfind('}') + 1
|
|
||||||
if json_start == -1 or json_end == 0:
|
|
||||||
raise ValueError("No JSON found in validation response")
|
|
||||||
json_str = response[json_start:json_end]
|
|
||||||
validation = json.loads(json_str)
|
|
||||||
if 'status' not in validation:
|
|
||||||
raise ValueError("Validation response missing 'status' field")
|
|
||||||
validation.setdefault('confidence', 0.5)
|
|
||||||
validation.setdefault('improvements', [])
|
|
||||||
validation.setdefault('quality_score', 5)
|
|
||||||
validation.setdefault('missing_elements', [])
|
|
||||||
validation.setdefault('suggested_retry_approach', '')
|
|
||||||
return validation
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error parsing validation response: {str(e)}")
|
|
||||||
return {
|
|
||||||
'status': 'success',
|
|
||||||
'reason': f'Parse error: {str(e)}',
|
|
||||||
'confidence': 0.5,
|
|
||||||
'improvements': [],
|
|
||||||
'quality_score': 5,
|
|
||||||
'missing_elements': [],
|
|
||||||
'suggested_retry_approach': ''
|
|
||||||
}
|
|
||||||
|
|
@ -8,11 +8,11 @@ import time
|
||||||
from typing import Dict, Any, Optional, List, Union
|
from typing import Dict, Any, Optional, List, Union
|
||||||
from datetime import datetime, UTC
|
from datetime import datetime, UTC
|
||||||
from modules.interfaces.interfaceChatModel import (
|
from modules.interfaces.interfaceChatModel import (
|
||||||
TaskStatus, TaskStep, TaskContext, TaskAction, ActionExecutionResult, ReviewResult, TaskPlan, WorkflowResult, TaskResult, ReviewContext
|
TaskStatus, TaskStep, TaskContext, TaskAction, ReviewResult, TaskPlan, WorkflowResult, TaskResult, ReviewContext, ActionResult
|
||||||
)
|
)
|
||||||
from .executionState import TaskExecutionState
|
from .executionState import TaskExecutionState
|
||||||
from .handlingActions import HandlingActions
|
|
||||||
from .promptFactory import createTaskPlanningPrompt, createActionDefinitionPrompt, createResultReviewPrompt
|
from .promptFactory import createTaskPlanningPrompt, createActionDefinitionPrompt, createResultReviewPrompt
|
||||||
|
from modules.chat.documents.documentGeneration import DocumentGenerator
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -21,28 +21,50 @@ class HandlingTasks:
|
||||||
self.chatInterface = chatInterface
|
self.chatInterface = chatInterface
|
||||||
self.service = service
|
self.service = service
|
||||||
self.workflow = workflow
|
self.workflow = workflow
|
||||||
self.handlingActions = HandlingActions(service, chatInterface)
|
self.documentGenerator = DocumentGenerator(service)
|
||||||
|
|
||||||
async def generateTaskPlan(self, userInput: str, workflow) -> TaskPlan:
|
async def generateTaskPlan(self, userInput: str, workflow) -> TaskPlan:
|
||||||
"""Generate a high-level task plan for the workflow."""
|
"""Generate a high-level task plan for the workflow."""
|
||||||
try:
|
try:
|
||||||
logger.info(f"Generating task plan for workflow {workflow.id}")
|
logger.info(f"Generating task plan for workflow {workflow.id}")
|
||||||
|
available_docs = self.service.getAvailableDocuments(workflow)
|
||||||
|
logger.debug(f"Available documents: {available_docs}")
|
||||||
|
|
||||||
prompt = await self.service.callAiTextAdvanced(
|
prompt = await self.service.callAiTextAdvanced(
|
||||||
createTaskPlanningPrompt(self, {
|
createTaskPlanningPrompt(self, {
|
||||||
'user_request': userInput,
|
'user_request': userInput,
|
||||||
'available_documents': self.service.getAvailableDocuments(workflow),
|
'available_documents': available_docs,
|
||||||
'workflow_id': workflow.id
|
'workflow_id': workflow.id
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
task_plan_dict = self._parseTaskPlanResponse(prompt)
|
# Inline _parseTaskPlanResponse logic
|
||||||
|
try:
|
||||||
|
json_start = prompt.find('{')
|
||||||
|
json_end = prompt.rfind('}') + 1
|
||||||
|
if json_start == -1 or json_end == 0:
|
||||||
|
raise ValueError("No JSON found in response")
|
||||||
|
json_str = prompt[json_start:json_end]
|
||||||
|
task_plan_dict = json.loads(json_str)
|
||||||
|
if 'tasks' not in task_plan_dict:
|
||||||
|
raise ValueError("Task plan missing 'tasks' field")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error parsing task plan response: {str(e)}")
|
||||||
|
task_plan_dict = {'tasks': []}
|
||||||
|
|
||||||
if not self._validateTaskPlan(task_plan_dict):
|
if not self._validateTaskPlan(task_plan_dict):
|
||||||
logger.error("Generated task plan failed validation")
|
logger.error("Generated task plan failed validation")
|
||||||
raise Exception("AI-generated task plan failed validation - AI is required for task planning")
|
raise Exception("AI-generated task plan failed validation - AI is required for task planning")
|
||||||
|
|
||||||
tasks = [TaskStep(**task_dict) for task_dict in task_plan_dict.get('tasks', [])]
|
tasks = [TaskStep(**task_dict) for task_dict in task_plan_dict.get('tasks', [])]
|
||||||
return TaskPlan(
|
task_plan = TaskPlan(
|
||||||
overview=task_plan_dict.get('overview', ''),
|
overview=task_plan_dict.get('overview', ''),
|
||||||
tasks=tasks
|
tasks=tasks
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.info(f"Task plan generated successfully with {len(tasks)} tasks")
|
||||||
|
logger.debug(f"Task plan: {json.dumps(task_plan_dict, indent=2)}")
|
||||||
|
|
||||||
|
return task_plan
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in generateTaskPlan: {str(e)}")
|
logger.error(f"Error in generateTaskPlan: {str(e)}")
|
||||||
raise
|
raise
|
||||||
|
|
@ -51,11 +73,17 @@ class HandlingTasks:
|
||||||
"""Generate actions for a given task step."""
|
"""Generate actions for a given task step."""
|
||||||
try:
|
try:
|
||||||
logger.info(f"Generating actions for task: {task_step.description}")
|
logger.info(f"Generating actions for task: {task_step.description}")
|
||||||
|
|
||||||
|
available_docs = self.service.getAvailableDocuments(workflow)
|
||||||
|
available_connections = self.service.getConnectionReferenceList()
|
||||||
|
logger.debug(f"Available documents: {available_docs}")
|
||||||
|
logger.debug(f"Available connections: {available_connections}")
|
||||||
|
|
||||||
context = enhanced_context or TaskContext(
|
context = enhanced_context or TaskContext(
|
||||||
task_step=task_step,
|
task_step=task_step,
|
||||||
workflow=workflow,
|
workflow=workflow,
|
||||||
workflow_id=workflow.id,
|
workflow_id=workflow.id,
|
||||||
available_documents=self.service.getAvailableDocuments(workflow),
|
available_documents=available_docs,
|
||||||
previous_results=previous_results or [],
|
previous_results=previous_results or [],
|
||||||
improvements=[],
|
improvements=[],
|
||||||
retry_count=0,
|
retry_count=0,
|
||||||
|
|
@ -67,7 +95,7 @@ class HandlingTasks:
|
||||||
successful_actions=[]
|
successful_actions=[]
|
||||||
)
|
)
|
||||||
prompt = await self.service.callAiTextAdvanced(
|
prompt = await self.service.callAiTextAdvanced(
|
||||||
createActionDefinitionPrompt(self, context)
|
await createActionDefinitionPrompt(self, context)
|
||||||
)
|
)
|
||||||
# Inline parseActionResponse logic here
|
# Inline parseActionResponse logic here
|
||||||
json_start = prompt.find('{')
|
json_start = prompt.find('{')
|
||||||
|
|
@ -86,6 +114,7 @@ class HandlingTasks:
|
||||||
if not self._validateActions(actions, context):
|
if not self._validateActions(actions, context):
|
||||||
logger.error("Generated actions failed validation")
|
logger.error("Generated actions failed validation")
|
||||||
raise Exception("AI-generated actions failed validation - AI is required for action generation")
|
raise Exception("AI-generated actions failed validation - AI is required for action generation")
|
||||||
|
|
||||||
# Convert to TaskAction objects
|
# Convert to TaskAction objects
|
||||||
task_actions = [self.chatInterface.createTaskAction({
|
task_actions = [self.chatInterface.createTaskAction({
|
||||||
"execMethod": a.get('method', 'unknown'),
|
"execMethod": a.get('method', 'unknown'),
|
||||||
|
|
@ -95,7 +124,12 @@ class HandlingTasks:
|
||||||
"expectedDocumentFormats": a.get('expectedDocumentFormats', None),
|
"expectedDocumentFormats": a.get('expectedDocumentFormats', None),
|
||||||
"status": TaskStatus.PENDING
|
"status": TaskStatus.PENDING
|
||||||
}) for a in actions]
|
}) for a in actions]
|
||||||
return [ta for ta in task_actions if ta]
|
|
||||||
|
valid_actions = [ta for ta in task_actions if ta]
|
||||||
|
logger.info(f"Generated {len(valid_actions)} actions for task: {task_step.description}")
|
||||||
|
logger.debug(f"Task actions plan: {json.dumps(action_data, indent=2)}")
|
||||||
|
|
||||||
|
return valid_actions
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in generateTaskActions: {str(e)}")
|
logger.error(f"Error in generateTaskActions: {str(e)}")
|
||||||
return []
|
return []
|
||||||
|
|
@ -114,7 +148,7 @@ class HandlingTasks:
|
||||||
break
|
break
|
||||||
action_results = []
|
action_results = []
|
||||||
for action in actions:
|
for action in actions:
|
||||||
result = await self.handlingActions.executeSingleAction(action, workflow)
|
result = await self.executeSingleAction(action, workflow)
|
||||||
action_results.append(result)
|
action_results.append(result)
|
||||||
if result.success:
|
if result.success:
|
||||||
state.addSuccessfulAction(result)
|
state.addSuccessfulAction(result)
|
||||||
|
|
@ -195,14 +229,36 @@ class HandlingTasks:
|
||||||
review.setdefault('status', 'unknown')
|
review.setdefault('status', 'unknown')
|
||||||
review.setdefault('reason', 'No reason provided')
|
review.setdefault('reason', 'No reason provided')
|
||||||
review.setdefault('quality_score', 5)
|
review.setdefault('quality_score', 5)
|
||||||
|
|
||||||
|
# Ensure improvements is a list
|
||||||
|
improvements = review.get('improvements', [])
|
||||||
|
if isinstance(improvements, str):
|
||||||
|
# Split string into list if it's a single improvement
|
||||||
|
improvements = [improvements.strip()] if improvements.strip() else []
|
||||||
|
elif not isinstance(improvements, list):
|
||||||
|
improvements = []
|
||||||
|
|
||||||
|
# Ensure all list fields are properly typed
|
||||||
|
missing_outputs = review.get('missing_outputs', [])
|
||||||
|
if not isinstance(missing_outputs, list):
|
||||||
|
missing_outputs = []
|
||||||
|
|
||||||
|
met_criteria = review.get('met_criteria', [])
|
||||||
|
if not isinstance(met_criteria, list):
|
||||||
|
met_criteria = []
|
||||||
|
|
||||||
|
unmet_criteria = review.get('unmet_criteria', [])
|
||||||
|
if not isinstance(unmet_criteria, list):
|
||||||
|
unmet_criteria = []
|
||||||
|
|
||||||
return ReviewResult(
|
return ReviewResult(
|
||||||
status=review.get('status', 'unknown'),
|
status=review.get('status', 'unknown'),
|
||||||
reason=review.get('reason', 'No reason provided'),
|
reason=review.get('reason', 'No reason provided'),
|
||||||
improvements=review.get('improvements', []),
|
improvements=improvements,
|
||||||
quality_score=review.get('quality_score', 5),
|
quality_score=review.get('quality_score', 5),
|
||||||
missing_outputs=review.get('missing_outputs', []),
|
missing_outputs=missing_outputs,
|
||||||
met_criteria=review.get('met_criteria', []),
|
met_criteria=met_criteria,
|
||||||
unmet_criteria=review.get('unmet_criteria', []),
|
unmet_criteria=unmet_criteria,
|
||||||
confidence=review.get('confidence', 0.5)
|
confidence=review.get('confidence', 0.5)
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -215,6 +271,23 @@ class HandlingTasks:
|
||||||
|
|
||||||
async def prepareTaskHandover(self, task_step, task_actions, review_result, workflow):
|
async def prepareTaskHandover(self, task_step, task_actions, review_result, workflow):
|
||||||
try:
|
try:
|
||||||
|
# Log handover status summary
|
||||||
|
if hasattr(review_result, 'status'):
|
||||||
|
status = review_result.status
|
||||||
|
if hasattr(review_result, 'missing_outputs'):
|
||||||
|
missing = review_result.missing_outputs
|
||||||
|
else:
|
||||||
|
missing = []
|
||||||
|
if hasattr(review_result, 'met_criteria'):
|
||||||
|
met = review_result.met_criteria
|
||||||
|
else:
|
||||||
|
met = []
|
||||||
|
|
||||||
|
logger.debug(f"Task handover status: {status}")
|
||||||
|
logger.debug(f"Promised documents: {task_step.expected_outputs}")
|
||||||
|
logger.debug(f"Delivered documents: {met}")
|
||||||
|
logger.debug(f"Missing documents: {missing}")
|
||||||
|
|
||||||
handover_data = {
|
handover_data = {
|
||||||
'task_id': task_step.id,
|
'task_id': task_step.id,
|
||||||
'task_description': task_step.description,
|
'task_description': task_step.description,
|
||||||
|
|
@ -229,22 +302,127 @@ class HandlingTasks:
|
||||||
logger.error(f"Error in prepareTaskHandover: {str(e)}")
|
logger.error(f"Error in prepareTaskHandover: {str(e)}")
|
||||||
return {'error': str(e)}
|
return {'error': str(e)}
|
||||||
|
|
||||||
# --- Helper and validation methods (unchanged, but can be inlined or made private) ---
|
# --- Helper action handling methods ---
|
||||||
|
|
||||||
def _parseTaskPlanResponse(self, response: str) -> dict:
|
async def executeSingleAction(self, action, workflow):
|
||||||
|
"""Execute a single action and return ActionResult with enhanced document processing"""
|
||||||
try:
|
try:
|
||||||
json_start = response.find('{')
|
logger.info(f"Executing action: {action.execMethod}.{action.execAction}")
|
||||||
json_end = response.rfind('}') + 1
|
|
||||||
if json_start == -1 or json_end == 0:
|
# Log input documents and connections
|
||||||
raise ValueError("No JSON found in response")
|
input_docs = action.execParameters.get('documentList', [])
|
||||||
json_str = response[json_start:json_end]
|
logger.debug(f"Input documents: {input_docs}")
|
||||||
task_plan = json.loads(json_str)
|
logger.debug(f"Input connections: {action.execParameters.get('connections', [])}")
|
||||||
if 'tasks' not in task_plan:
|
|
||||||
raise ValueError("Task plan missing 'tasks' field")
|
enhanced_parameters = action.execParameters.copy()
|
||||||
return task_plan
|
if action.expectedDocumentFormats:
|
||||||
|
enhanced_parameters['expectedDocumentFormats'] = action.expectedDocumentFormats
|
||||||
|
logger.debug(f"Expected document formats: {action.expectedDocumentFormats}")
|
||||||
|
|
||||||
|
result = await self.service.executeAction(
|
||||||
|
methodName=action.execMethod,
|
||||||
|
actionName=action.execAction,
|
||||||
|
parameters=enhanced_parameters
|
||||||
|
)
|
||||||
|
result_label = action.execResultLabel
|
||||||
|
|
||||||
|
if result.success:
|
||||||
|
action.setSuccess()
|
||||||
|
action.result = result.data.get("result", "")
|
||||||
|
action.execResultLabel = result_label
|
||||||
|
await self.createActionMessage(action, result, workflow, result_label)
|
||||||
|
logger.info(f"Action {action.execMethod}.{action.execAction} executed successfully")
|
||||||
|
else:
|
||||||
|
action.setError(result.error or "Action execution failed")
|
||||||
|
logger.error(f"Action {action.execMethod}.{action.execAction} failed: {result.error}")
|
||||||
|
|
||||||
|
return ActionResult(
|
||||||
|
success=result.success,
|
||||||
|
data={
|
||||||
|
"result": result.data.get("result", ""),
|
||||||
|
"documents": [], # Documents will be processed in createActionMessage
|
||||||
|
"actionId": action.id,
|
||||||
|
"actionMethod": action.execMethod,
|
||||||
|
"actionName": action.execAction,
|
||||||
|
"resultLabel": result_label
|
||||||
|
},
|
||||||
|
metadata={
|
||||||
|
"actionId": action.id,
|
||||||
|
"actionMethod": action.execMethod,
|
||||||
|
"actionName": action.execAction,
|
||||||
|
"resultLabel": result_label
|
||||||
|
},
|
||||||
|
validation={},
|
||||||
|
error=result.error or ""
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error parsing task plan response: {str(e)}")
|
logger.error(f"Error executing single action: {str(e)}")
|
||||||
return {'tasks': []}
|
action.setError(str(e))
|
||||||
|
return ActionResult(
|
||||||
|
success=False,
|
||||||
|
data={
|
||||||
|
"actionId": action.id,
|
||||||
|
"actionMethod": action.execMethod,
|
||||||
|
"actionName": action.execAction,
|
||||||
|
"documents": []
|
||||||
|
},
|
||||||
|
metadata={
|
||||||
|
"actionId": action.id,
|
||||||
|
"actionMethod": action.execMethod,
|
||||||
|
"actionName": action.execAction
|
||||||
|
},
|
||||||
|
validation={},
|
||||||
|
error=str(e)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def createActionMessage(self, action, result, workflow, result_label=None):
|
||||||
|
"""Create and store a message for the action result in the workflow with enhanced document processing"""
|
||||||
|
try:
|
||||||
|
if result_label is None:
|
||||||
|
result_label = action.execResultLabel
|
||||||
|
|
||||||
|
# Use the local createDocumentsFromActionResult method
|
||||||
|
created_documents = self.documentGenerator.createDocumentsFromActionResult(result, action, workflow)
|
||||||
|
|
||||||
|
# Log delivered documents with sizes
|
||||||
|
if created_documents:
|
||||||
|
doc_info = []
|
||||||
|
for doc in created_documents:
|
||||||
|
if hasattr(doc, 'filename') and hasattr(doc, 'fileSize'):
|
||||||
|
doc_info.append(f"{doc.filename} ({doc.fileSize} bytes)")
|
||||||
|
elif hasattr(doc, 'filename'):
|
||||||
|
doc_info.append(f"{doc.filename}")
|
||||||
|
else:
|
||||||
|
doc_info.append("unknown document")
|
||||||
|
logger.debug(f"Produced result label: {result_label}")
|
||||||
|
logger.debug(f"Delivered documents: {doc_info}")
|
||||||
|
else:
|
||||||
|
logger.debug(f"Produced result label: {result_label} (no documents)")
|
||||||
|
|
||||||
|
message_data = {
|
||||||
|
"workflowId": workflow.id,
|
||||||
|
"role": "assistant",
|
||||||
|
"message": f"Executed action {action.execMethod}.{action.execAction}",
|
||||||
|
"status": "step",
|
||||||
|
"sequenceNr": len(workflow.messages) + 1,
|
||||||
|
"publishedAt": datetime.now(UTC).isoformat(),
|
||||||
|
"actionId": action.id,
|
||||||
|
"actionMethod": action.execMethod,
|
||||||
|
"actionName": action.execAction,
|
||||||
|
"documentsLabel": result_label,
|
||||||
|
"documents": created_documents
|
||||||
|
}
|
||||||
|
|
||||||
|
message = self.chatInterface.createWorkflowMessage(message_data)
|
||||||
|
if message:
|
||||||
|
workflow.messages.append(message)
|
||||||
|
logger.info(f"Created action message for {action.execMethod}.{action.execAction} with {len(created_documents)} documents")
|
||||||
|
else:
|
||||||
|
logger.error(f"Failed to create workflow message for action {action.execMethod}.{action.execAction}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating action message: {str(e)}")
|
||||||
|
|
||||||
|
# --- Helper validation methods ---
|
||||||
|
|
||||||
def _validateTaskPlan(self, task_plan: Dict[str, Any]) -> bool:
|
def _validateTaskPlan(self, task_plan: Dict[str, Any]) -> bool:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any, List
|
from typing import Dict, Any, List
|
||||||
from modules.interfaces.interfaceAppModel import User
|
from modules.interfaces.interfaceAppModel import User
|
||||||
from modules.interfaces.interfaceChatModel import ChatWorkflow, UserInputRequest, TaskStep, TaskAction, ActionExecutionResult, ReviewResult, TaskPlan, WorkflowResult, TaskContext
|
from modules.interfaces.interfaceChatModel import ChatWorkflow, UserInputRequest, TaskStep, TaskAction, ActionResult, ReviewResult, TaskPlan, WorkflowResult, TaskContext
|
||||||
from modules.chat.serviceCenter import ServiceCenter
|
from modules.chat.serviceCenter import ServiceCenter
|
||||||
from modules.interfaces.interfaceChatObjects import ChatObjects
|
from modules.interfaces.interfaceChatObjects import ChatObjects
|
||||||
from .handling.handlingTasks import HandlingTasks
|
from .handling.handlingTasks import HandlingTasks
|
||||||
|
|
@ -30,45 +30,58 @@ class ChatManager:
|
||||||
"""Unified Workflow Execution"""
|
"""Unified Workflow Execution"""
|
||||||
try:
|
try:
|
||||||
logger.info(f"Starting unified workflow execution for workflow {workflow.id}")
|
logger.info(f"Starting unified workflow execution for workflow {workflow.id}")
|
||||||
|
logger.debug(f"User request: {userInput.prompt}")
|
||||||
|
|
||||||
# Phase 1: High-Level Task Planning
|
# Phase 1: High-Level Task Planning
|
||||||
task_plan = await self.handlingTasks.planHighLevelTasks(userInput.userRequest, workflow)
|
logger.info("Phase 1: Generating task plan")
|
||||||
|
task_plan = await self.handlingTasks.generateTaskPlan(userInput.prompt, workflow)
|
||||||
if not task_plan or not task_plan.tasks:
|
if not task_plan or not task_plan.tasks:
|
||||||
raise Exception("No tasks generated in task plan.")
|
raise Exception("No tasks generated in task plan.")
|
||||||
workflow.taskPlan = task_plan
|
|
||||||
# Phase 2-5: For each task, define actions, execute, review, and handover
|
# Phase 2-5: For each task, execute and get results
|
||||||
|
logger.info(f"Phase 2: Executing {len(task_plan.tasks)} tasks")
|
||||||
all_task_results = []
|
all_task_results = []
|
||||||
|
previous_results = []
|
||||||
for idx, task_step in enumerate(task_plan.tasks):
|
for idx, task_step in enumerate(task_plan.tasks):
|
||||||
logger.info(f"Processing task {idx+1}/{len(task_plan.tasks)}: {task_step.description}")
|
logger.info(f"Task {idx+1}/{len(task_plan.tasks)}: {task_step.description}")
|
||||||
# Define actions
|
# Create task context for this task
|
||||||
previous_results = self.handlingTasks.getPreviousResults(task_step) if hasattr(self.handlingTasks, 'getPreviousResults') else []
|
task_context = TaskContext(
|
||||||
actions = await self.handlingTasks.generateTaskActions(task_step, workflow, previous_results=previous_results)
|
task_step=task_step,
|
||||||
if not actions:
|
workflow=workflow,
|
||||||
logger.warning(f"No actions defined for task {task_step.id}, skipping.")
|
workflow_id=workflow.id,
|
||||||
continue
|
available_documents=self.service.getAvailableDocuments(workflow),
|
||||||
# Execute actions and get results (including review_result)
|
previous_results=previous_results
|
||||||
task_result = await self.handlingTasks.executeTaskActions(actions, workflow)
|
)
|
||||||
# task_result should include action_results and review_result
|
# Execute task (this handles action generation, execution, and review internally)
|
||||||
action_results = getattr(task_result, 'action_results', None)
|
task_result = await self.handlingTasks.executeTask(task_step, workflow, task_context)
|
||||||
review_result = getattr(task_result, 'review_result', None)
|
|
||||||
# Handover
|
# Handover
|
||||||
handover_data = await self.handlingTasks.prepareTaskHandover(task_step, actions, review_result, workflow)
|
handover_data = await self.handlingTasks.prepareTaskHandover(task_step, [], task_result, workflow)
|
||||||
# Collect results
|
# Collect results
|
||||||
all_task_results.append({
|
all_task_results.append({
|
||||||
'task_step': task_step,
|
'task_step': task_step,
|
||||||
'actions': actions,
|
'task_result': task_result,
|
||||||
'action_results': action_results,
|
|
||||||
'review_result': review_result,
|
|
||||||
'handover_data': handover_data
|
'handover_data': handover_data
|
||||||
})
|
})
|
||||||
|
# Update previous results for next task
|
||||||
|
if task_result.success and task_result.feedback:
|
||||||
|
previous_results.append(task_result.feedback)
|
||||||
|
|
||||||
# Final workflow result
|
# Final workflow result
|
||||||
workflow_result = WorkflowResult(
|
workflow_result = WorkflowResult(
|
||||||
status="completed",
|
status="completed",
|
||||||
task_results=all_task_results,
|
completed_tasks=len(all_task_results),
|
||||||
workflow=workflow
|
total_tasks=len(task_plan.tasks),
|
||||||
|
execution_time=0.0, # TODO: Calculate actual execution time
|
||||||
|
final_results_count=len(all_task_results)
|
||||||
)
|
)
|
||||||
logger.info(f"Unified workflow execution completed for workflow {workflow.id}")
|
logger.info(f"Unified workflow execution completed successfully for workflow {workflow.id}")
|
||||||
return workflow_result
|
return workflow_result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in executeUnifiedWorkflow: {str(e)}")
|
logger.error(f"Error in executeUnifiedWorkflow: {str(e)}")
|
||||||
from modules.interfaces.interfaceChatModel import WorkflowResult
|
return WorkflowResult(
|
||||||
return WorkflowResult(status="failed", task_results=[], workflow=workflow)
|
status="failed",
|
||||||
|
completed_tasks=0,
|
||||||
|
total_tasks=0,
|
||||||
|
execution_time=0.0,
|
||||||
|
final_results_count=0
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -264,10 +264,12 @@ class MethodBase:
|
||||||
success=success,
|
success=success,
|
||||||
data=data,
|
data=data,
|
||||||
metadata=metadata or {},
|
metadata=metadata or {},
|
||||||
validation=[],
|
validation={},
|
||||||
error=error
|
error=error
|
||||||
)
|
)
|
||||||
|
|
||||||
def _addValidationMessage(self, result: ActionResult, message: str) -> None:
|
def _addValidationMessage(self, result: ActionResult, message: str) -> None:
|
||||||
"""Add a validation message to the result"""
|
"""Add a validation message to the result"""
|
||||||
result.validation.append(message)
|
if 'messages' not in result.validation:
|
||||||
|
result.validation['messages'] = []
|
||||||
|
result.validation['messages'].append(message)
|
||||||
|
|
@ -13,13 +13,74 @@ from modules.shared.attributeUtils import register_model_labels, ModelMixin
|
||||||
# ===== Method Models =====
|
# ===== Method Models =====
|
||||||
|
|
||||||
class ActionResult(BaseModel, ModelMixin):
|
class ActionResult(BaseModel, ModelMixin):
|
||||||
"""Model for action results from a methods action"""
|
"""Unified model for action results with workflow state management"""
|
||||||
|
# Core result fields
|
||||||
success: bool = Field(description="Whether the method execution was successful")
|
success: bool = Field(description="Whether the method execution was successful")
|
||||||
data: Dict[str, Any] = Field(description="Result data")
|
data: Dict[str, Any] = Field(description="Result data")
|
||||||
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
||||||
validation: List[str] = Field(default_factory=list, description="Validation messages")
|
|
||||||
error: Optional[str] = Field(None, description="Error message if any")
|
error: Optional[str] = Field(None, description="Error message if any")
|
||||||
|
|
||||||
|
# Action identification
|
||||||
|
actionId: Optional[str] = Field(None, description="ID of the action that produced this result")
|
||||||
|
actionMethod: Optional[str] = Field(None, description="Method of the action that produced this result")
|
||||||
|
actionName: Optional[str] = Field(None, description="Name of the action that produced this result")
|
||||||
|
|
||||||
|
# Document handling
|
||||||
|
documents: List[str] = Field(default_factory=list, description="List of document references")
|
||||||
|
resultLabel: Optional[str] = Field(None, description="Label for the result")
|
||||||
|
|
||||||
|
# Validation and workflow state
|
||||||
|
validation: Dict[str, Any] = Field(default_factory=dict, description="Validation information")
|
||||||
|
is_retry: bool = Field(default=False, description="Whether this is a retry attempt")
|
||||||
|
previous_error: Optional[str] = Field(None, description="Previous error message for retries")
|
||||||
|
applied_improvements: List[str] = Field(default_factory=list, description="Improvements applied for retry")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def success(cls, documents: List[str] = None, resultLabel: str = None, data: Dict[str, Any] = None,
|
||||||
|
actionId: str = None, actionMethod: str = None, actionName: str = None) -> 'ActionResult':
|
||||||
|
"""Create a successful action result"""
|
||||||
|
return cls(
|
||||||
|
success=True,
|
||||||
|
data=data or {},
|
||||||
|
documents=documents or [],
|
||||||
|
resultLabel=resultLabel,
|
||||||
|
actionId=actionId,
|
||||||
|
actionMethod=actionMethod,
|
||||||
|
actionName=actionName
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def failure(cls, error: str, data: Dict[str, Any] = None,
|
||||||
|
actionId: str = None, actionMethod: str = None, actionName: str = None) -> 'ActionResult':
|
||||||
|
"""Create a failed action result"""
|
||||||
|
return cls(
|
||||||
|
success=False,
|
||||||
|
data=data or {},
|
||||||
|
error=error,
|
||||||
|
actionId=actionId,
|
||||||
|
actionMethod=actionMethod,
|
||||||
|
actionName=actionName
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def retry(cls, previous_result: 'ActionResult', improvements: List[str] = None) -> 'ActionResult':
|
||||||
|
"""Create a retry action result based on a previous result"""
|
||||||
|
return cls(
|
||||||
|
success=previous_result.success,
|
||||||
|
data=previous_result.data,
|
||||||
|
metadata=previous_result.metadata,
|
||||||
|
validation=previous_result.validation,
|
||||||
|
error=previous_result.error,
|
||||||
|
documents=previous_result.documents,
|
||||||
|
resultLabel=previous_result.resultLabel,
|
||||||
|
actionId=previous_result.actionId,
|
||||||
|
actionMethod=previous_result.actionMethod,
|
||||||
|
actionName=previous_result.actionName,
|
||||||
|
is_retry=True,
|
||||||
|
previous_error=previous_result.error,
|
||||||
|
applied_improvements=improvements or []
|
||||||
|
)
|
||||||
|
|
||||||
# Register labels for ActionResult
|
# Register labels for ActionResult
|
||||||
register_model_labels(
|
register_model_labels(
|
||||||
"ActionResult",
|
"ActionResult",
|
||||||
|
|
@ -29,7 +90,15 @@ register_model_labels(
|
||||||
"data": {"en": "Data", "fr": "Données"},
|
"data": {"en": "Data", "fr": "Données"},
|
||||||
"metadata": {"en": "Metadata", "fr": "Métadonnées"},
|
"metadata": {"en": "Metadata", "fr": "Métadonnées"},
|
||||||
"validation": {"en": "Validation", "fr": "Validation"},
|
"validation": {"en": "Validation", "fr": "Validation"},
|
||||||
"error": {"en": "Error", "fr": "Erreur"}
|
"error": {"en": "Error", "fr": "Erreur"},
|
||||||
|
"documents": {"en": "Documents", "fr": "Documents"},
|
||||||
|
"resultLabel": {"en": "Result Label", "fr": "Étiquette du résultat"},
|
||||||
|
"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"},
|
||||||
|
"is_retry": {"en": "Is Retry", "fr": "Est une nouvelle tentative"},
|
||||||
|
"previous_error": {"en": "Previous Error", "fr": "Erreur précédente"},
|
||||||
|
"applied_improvements": {"en": "Applied Improvements", "fr": "Améliorations appliquées"}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -484,20 +553,6 @@ class TaskContext(BaseModel, ModelMixin):
|
||||||
failed_actions: Optional[list] = []
|
failed_actions: Optional[list] = []
|
||||||
successful_actions: Optional[list] = []
|
successful_actions: Optional[list] = []
|
||||||
|
|
||||||
class ActionExecutionResult(BaseModel, ModelMixin):
|
|
||||||
success: bool
|
|
||||||
data: dict
|
|
||||||
metadata: dict = {}
|
|
||||||
error: Optional[str] = None
|
|
||||||
actionId: Optional[str] = None
|
|
||||||
actionMethod: Optional[str] = None
|
|
||||||
actionName: Optional[str] = None
|
|
||||||
documents: Optional[list] = []
|
|
||||||
validation: Optional[dict] = {}
|
|
||||||
is_retry: Optional[bool] = False
|
|
||||||
previous_error: Optional[str] = None
|
|
||||||
applied_improvements: Optional[list[str]] = []
|
|
||||||
|
|
||||||
class ReviewContext(BaseModel, ModelMixin):
|
class ReviewContext(BaseModel, ModelMixin):
|
||||||
task_step: TaskStep
|
task_step: TaskStep
|
||||||
task_actions: Optional[list] = []
|
task_actions: Optional[list] = []
|
||||||
|
|
|
||||||
|
|
@ -1225,7 +1225,7 @@ class ChatObjects:
|
||||||
success=createdResult.get("success", False),
|
success=createdResult.get("success", False),
|
||||||
data=createdResult.get("data", {}),
|
data=createdResult.get("data", {}),
|
||||||
metadata=createdResult.get("metadata", {}),
|
metadata=createdResult.get("metadata", {}),
|
||||||
validation=createdResult.get("validation", []),
|
validation=createdResult.get("validation", {}),
|
||||||
error=createdResult.get("error")
|
error=createdResult.get("error")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ class MethodDocument(MethodBase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate HTML report
|
# Generate HTML report
|
||||||
html_content = self._generateHtmlReport(chatDocuments, title, includeMetadata)
|
html_content = await self._generateHtmlReport(chatDocuments, title, includeMetadata)
|
||||||
|
|
||||||
# Create output filename
|
# Create output filename
|
||||||
timestamp = datetime.now(UTC).strftime('%Y%m%d_%H%M%S')
|
timestamp = datetime.now(UTC).strftime('%Y%m%d_%H%M%S')
|
||||||
|
|
@ -250,7 +250,7 @@ class MethodDocument(MethodBase):
|
||||||
error=str(e)
|
error=str(e)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _generateHtmlReport(self, chatDocuments: List[Any], title: str, includeMetadata: bool) -> str:
|
async def _generateHtmlReport(self, chatDocuments: List[Any], title: str, includeMetadata: bool) -> str:
|
||||||
"""
|
"""
|
||||||
Generate a comprehensive HTML report using AI from all input documents.
|
Generate a comprehensive HTML report using AI from all input documents.
|
||||||
"""
|
"""
|
||||||
|
|
@ -304,7 +304,7 @@ class MethodDocument(MethodBase):
|
||||||
|
|
||||||
# Call AI to generate the report
|
# Call AI to generate the report
|
||||||
logger.info(f"Generating AI report for {len(validDocuments)} documents")
|
logger.info(f"Generating AI report for {len(validDocuments)} documents")
|
||||||
aiReport = self.service.callAiTextBasic(aiPrompt, combinedContent)
|
aiReport = await self.service.callAiTextBasic(aiPrompt, combinedContent)
|
||||||
|
|
||||||
# If AI call fails, fall back to basic HTML
|
# If AI call fails, fall back to basic HTML
|
||||||
if not aiReport or aiReport.strip() == "":
|
if not aiReport or aiReport.strip() == "":
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class WorkflowManager:
|
||||||
await self.chatManager.initialize(workflow)
|
await self.chatManager.initialize(workflow)
|
||||||
|
|
||||||
# Set user language
|
# Set user language
|
||||||
self.chatManager.setUserLanguage(userInput.userLanguage)
|
self.chatManager.service.setUserLanguage(userInput.userLanguage)
|
||||||
|
|
||||||
# Send first message
|
# Send first message
|
||||||
message = await self._sendFirstMessage(userInput, workflow)
|
message = await self._sendFirstMessage(userInput, workflow)
|
||||||
|
|
@ -121,7 +121,7 @@ class WorkflowManager:
|
||||||
# Add documents if any
|
# Add documents if any
|
||||||
if userInput.listFileId:
|
if userInput.listFileId:
|
||||||
# Process file IDs and add to message data
|
# Process file IDs and add to message data
|
||||||
documents = await self.chatManager.processFileIds(userInput.listFileId)
|
documents = await self.chatManager.service.processFileIds(userInput.listFileId)
|
||||||
messageData["documents"] = documents
|
messageData["documents"] = documents
|
||||||
|
|
||||||
# Create message using interface
|
# Create message using interface
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue