371 lines
16 KiB
Python
371 lines
16 KiB
Python
# promptFactory.py
|
|
# Enhanced prompt factory with reusable functions
|
|
|
|
import json
|
|
import logging
|
|
import importlib
|
|
import pkgutil
|
|
import inspect
|
|
from typing import Any, Dict, List
|
|
from modules.datamodels.datamodelWorkflow import TaskContext, ReviewContext, DocumentExchange
|
|
from modules.datamodels.datamodelChat import ChatDocument
|
|
from modules.services.serviceGeneration.subDocumentUtility import getFileExtension
|
|
from modules.workflows.methods.methodBase import MethodBase
|
|
|
|
# Set up logger
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Global methods catalog - moved from serviceCenter
|
|
methods = {}
|
|
|
|
def discoverMethods(serviceCenter):
|
|
"""Dynamically discover all method classes and their actions in modules methods package"""
|
|
try:
|
|
# Import the methods package
|
|
methodsPackage = importlib.import_module('modules.workflows.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.workflows.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
|
|
methodInstance = item(serviceCenter)
|
|
|
|
# Use the actions property from MethodBase which handles @action decorator
|
|
actions = methodInstance.actions
|
|
|
|
# Create method info
|
|
methodInfo = {
|
|
'instance': methodInstance,
|
|
'actions': actions,
|
|
'description': item.__doc__ or f"Method {itemName}"
|
|
}
|
|
|
|
# Store the method with full class name
|
|
methods[itemName] = methodInfo
|
|
|
|
# Also store with short name for action executor access
|
|
shortName = itemName.replace('Method', '').lower()
|
|
methods[shortName] = methodInfo
|
|
|
|
logger.info(f"Discovered method {itemName} (short: {shortName}) with {len(actions)} actions")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error discovering method {name}: {str(e)}")
|
|
continue
|
|
|
|
logger.info(f"Discovered {len(methods)} method entries total")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error discovering methods: {str(e)}")
|
|
|
|
def getMethodsList(serviceCenter):
|
|
"""Get a list of available methods with their signatures"""
|
|
if not methods:
|
|
discoverMethods(serviceCenter)
|
|
|
|
methodsList = []
|
|
for methodName, methodInfo in methods.items():
|
|
methodDescription = methodInfo['description']
|
|
actionsList = []
|
|
|
|
for actionName, actionInfo in methodInfo['actions'].items():
|
|
actionDescription = actionInfo['description']
|
|
parameters = actionInfo['parameters']
|
|
|
|
# Build parameter signature
|
|
paramSig = []
|
|
for paramName, paramInfo in parameters.items():
|
|
paramType = paramInfo['type']
|
|
paramRequired = paramInfo['required']
|
|
paramDefault = paramInfo['default']
|
|
|
|
if paramRequired:
|
|
paramSig.append(f"{paramName}: {paramType}")
|
|
else:
|
|
defaultStr = f" = {paramDefault}" if paramDefault is not None else " = None"
|
|
paramSig.append(f"{paramName}: {paramType}{defaultStr}")
|
|
|
|
paramSignature = f"({', '.join(paramSig)})" if paramSig else "()"
|
|
actionsList.append(f"- {actionName}{paramSignature}: {actionDescription}")
|
|
|
|
actionsStr = "\n".join(actionsList)
|
|
methodsList.append(f"**{methodName}**: {methodDescription}\n{actionsStr}")
|
|
|
|
return "\n\n".join(methodsList)
|
|
|
|
# Reusable prompt element functions
|
|
def getAvailableDocuments(context: Any) -> str:
|
|
"""Get available documents for prompt context"""
|
|
try:
|
|
if not context or not hasattr(context, 'available_documents') or not context.available_documents:
|
|
return "No documents available"
|
|
|
|
documents = context.available_documents
|
|
if not isinstance(documents, list):
|
|
return "No documents available"
|
|
|
|
docList = []
|
|
for i, doc in enumerate(documents, 1):
|
|
if isinstance(doc, ChatDocument):
|
|
docInfo = f"{i}. **{doc.fileName}**"
|
|
if hasattr(doc, 'mimeType') and doc.mimeType:
|
|
docInfo += f" ({doc.mimeType})"
|
|
if hasattr(doc, 'size') and doc.size:
|
|
docInfo += f" - {doc.size} bytes"
|
|
docList.append(docInfo)
|
|
elif isinstance(doc, dict):
|
|
docInfo = f"{i}. **{doc.get('fileName', 'Unknown')}**"
|
|
if doc.get('mimeType'):
|
|
docInfo += f" ({doc['mimeType']})"
|
|
if doc.get('size'):
|
|
docInfo += f" - {doc['size']} bytes"
|
|
docList.append(docInfo)
|
|
else:
|
|
docList.append(f"{i}. {str(doc)}")
|
|
|
|
return "\n".join(docList) if docList else "No documents available"
|
|
except Exception as e:
|
|
logger.error(f"Error getting available documents: {str(e)}")
|
|
return "Error retrieving documents"
|
|
|
|
def getWorkflowHistory(services, context: Any) -> str:
|
|
"""Get workflow history for prompt context"""
|
|
try:
|
|
if not context or not hasattr(context, 'workflow_id'):
|
|
return "No workflow history available"
|
|
|
|
workflowId = context.workflow_id
|
|
if not workflowId:
|
|
return "No workflow history available"
|
|
|
|
# Get workflow messages
|
|
messages = services.interfaceDbChat.getWorkflowMessages(workflowId)
|
|
if not messages:
|
|
return "No workflow history available"
|
|
|
|
# Filter for relevant messages (last 10)
|
|
recentMessages = messages[-10:] if len(messages) > 10 else messages
|
|
|
|
historyList = []
|
|
for msg in recentMessages:
|
|
if hasattr(msg, 'role') and hasattr(msg, 'message'):
|
|
role = "User" if msg.role == "user" else "Assistant"
|
|
message = msg.message[:200] + "..." if len(msg.message) > 200 else msg.message
|
|
historyList.append(f"**{role}**: {message}")
|
|
|
|
return "\n".join(historyList) if historyList else "No workflow history available"
|
|
except Exception as e:
|
|
logger.error(f"Error getting workflow history: {str(e)}")
|
|
return "Error retrieving workflow history"
|
|
|
|
def getAvailableMethods(services) -> str:
|
|
"""Get available methods for prompt context"""
|
|
try:
|
|
if not methods:
|
|
discoverMethods(services)
|
|
|
|
return getMethodsList(services)
|
|
except Exception as e:
|
|
logger.error(f"Error getting available methods: {str(e)}")
|
|
return "Error retrieving available methods"
|
|
|
|
def getEnhancedDocumentContext(services) -> str:
|
|
"""Get enhanced document context with full metadata"""
|
|
try:
|
|
# Get all documents from the current workflow
|
|
workflow = getattr(services, 'currentWorkflow', None)
|
|
if not workflow or not hasattr(workflow, 'id'):
|
|
return "No workflow context available"
|
|
|
|
# Get workflow documents from messages
|
|
if not hasattr(workflow, 'messages') or not workflow.messages:
|
|
return "No documents available"
|
|
|
|
# Collect all documents from all messages
|
|
all_documents = []
|
|
for message in workflow.messages:
|
|
if hasattr(message, 'documents') and message.documents:
|
|
all_documents.extend(message.documents)
|
|
|
|
if not all_documents:
|
|
return "No documents available"
|
|
|
|
# Group documents by round/task/action for better organization
|
|
docGroups = {}
|
|
for message in workflow.messages:
|
|
if hasattr(message, 'documents') and message.documents:
|
|
round_num = getattr(message, 'roundNumber', 0)
|
|
task_num = getattr(message, 'taskNumber', 0)
|
|
action_num = getattr(message, 'actionNumber', 0)
|
|
label = getattr(message, 'documentsLabel', 'results')
|
|
|
|
group_key = f"round{round_num}_task{task_num}_action{action_num}_{label}"
|
|
if group_key not in docGroups:
|
|
docGroups[group_key] = []
|
|
docGroups[group_key].extend(message.documents)
|
|
|
|
# Format documents by groups with proper docList references
|
|
docList = []
|
|
for group_key, group_docs in docGroups.items():
|
|
# Find the message that contains these documents to get the message ID
|
|
message_id = None
|
|
for message in workflow.messages:
|
|
if hasattr(message, 'documents') and message.documents:
|
|
round_num = getattr(message, 'roundNumber', 0)
|
|
task_num = getattr(message, 'taskNumber', 0)
|
|
action_num = getattr(message, 'actionNumber', 0)
|
|
label = getattr(message, 'documentsLabel', 'results')
|
|
msg_group_key = f"round{round_num}_task{task_num}_action{action_num}_{label}"
|
|
|
|
if msg_group_key == group_key:
|
|
message_id = str(message.id)
|
|
break
|
|
|
|
# Generate proper docList reference
|
|
if message_id:
|
|
docListRef = f"docList:{message_id}:{group_key}"
|
|
else:
|
|
# Fallback to direct label reference
|
|
docListRef = group_key
|
|
|
|
docList.append(f"\n**{group_key}:**")
|
|
docList.append(f"Reference: {docListRef}")
|
|
for i, doc in enumerate(group_docs, 1):
|
|
if isinstance(doc, ChatDocument):
|
|
docInfo = f" {i}. **{doc.fileName}**"
|
|
if hasattr(doc, 'mimeType') and doc.mimeType:
|
|
docInfo += f" ({doc.mimeType})"
|
|
if hasattr(doc, 'size') and doc.size:
|
|
docInfo += f" - {doc.size} bytes"
|
|
if hasattr(doc, 'created') and doc.created:
|
|
docInfo += f" - Created: {doc.created}"
|
|
docList.append(docInfo)
|
|
elif isinstance(doc, dict):
|
|
docInfo = f" {i}. **{doc.get('fileName', 'Unknown')}**"
|
|
if doc.get('mimeType'):
|
|
docInfo += f" ({doc['mimeType']})"
|
|
if doc.get('size'):
|
|
docInfo += f" - {doc['size']} bytes"
|
|
if doc.get('created'):
|
|
docInfo += f" - Created: {doc['created']}"
|
|
docList.append(docInfo)
|
|
else:
|
|
docList.append(f" {i}. {str(doc)}")
|
|
|
|
return "\n".join(docList) if docList else "No documents available"
|
|
except Exception as e:
|
|
logger.error(f"Error getting enhanced document context: {str(e)}")
|
|
return "Error retrieving document context"
|
|
|
|
def getConnectionReferenceList(services) -> List[str]:
|
|
"""Get list of available connections"""
|
|
try:
|
|
# Get connections from the database
|
|
if hasattr(services, 'interfaceDbApp') and hasattr(services, 'user'):
|
|
userId = services.user.id
|
|
connections = services.interfaceDbApp.getUserConnections(userId)
|
|
if connections:
|
|
# Format connections as reference strings
|
|
connectionRefs = []
|
|
for conn in connections:
|
|
# Create reference string in format: conn_{authority}_{id}
|
|
ref = f"conn_{conn.authority.value}_{conn.id}"
|
|
connectionRefs.append(ref)
|
|
return connectionRefs
|
|
|
|
return []
|
|
except Exception as e:
|
|
logger.error(f"Error getting connection reference list: {str(e)}")
|
|
return []
|
|
|
|
def getUserLanguage(services) -> str:
|
|
"""Get user language from services"""
|
|
try:
|
|
if hasattr(services, 'user') and hasattr(services.user, 'language'):
|
|
return services.user.language or 'en'
|
|
return 'en'
|
|
except Exception as e:
|
|
logger.error(f"Error getting user language: {str(e)}")
|
|
return 'en'
|
|
|
|
def getReviewContent(context: Any) -> str:
|
|
"""Get review content for prompt context"""
|
|
try:
|
|
if not context or not hasattr(context, 'observation'):
|
|
return "No review content available"
|
|
|
|
observation = context.observation
|
|
if not isinstance(observation, dict):
|
|
return "No review content available"
|
|
|
|
reviewParts = []
|
|
|
|
# Add success status
|
|
if 'success' in observation:
|
|
reviewParts.append(f"Success: {observation['success']}")
|
|
|
|
# Add documents count
|
|
if 'documentsCount' in observation:
|
|
reviewParts.append(f"Documents generated: {observation['documentsCount']}")
|
|
|
|
# Add previews
|
|
if 'previews' in observation and observation['previews']:
|
|
reviewParts.append("Document previews:")
|
|
for preview in observation['previews']:
|
|
if isinstance(preview, dict):
|
|
name = preview.get('name', 'Unknown')
|
|
mimeType = preview.get('mimeType', 'Unknown')
|
|
size = preview.get('contentSize', 'Unknown size')
|
|
reviewParts.append(f" - {name} ({mimeType}) - {size}")
|
|
|
|
# Add notes
|
|
if 'notes' in observation and observation['notes']:
|
|
reviewParts.append("Notes:")
|
|
for note in observation['notes']:
|
|
reviewParts.append(f" - {note}")
|
|
|
|
return "\n".join(reviewParts) if reviewParts else "No review content available"
|
|
except Exception as e:
|
|
logger.error(f"Error getting review content: {str(e)}")
|
|
return "Error retrieving review content"
|
|
|
|
def getPreviousRoundContext(services, context: Any) -> str:
|
|
"""Get previous round context for prompt"""
|
|
try:
|
|
if not context or not hasattr(context, 'workflow_id'):
|
|
return "No previous round context available"
|
|
|
|
workflowId = context.workflow_id
|
|
if not workflowId:
|
|
return "No previous round context available"
|
|
|
|
# Get previous round results
|
|
previousResults = getattr(context, 'previous_results', [])
|
|
if not previousResults:
|
|
return "No previous round context available"
|
|
|
|
contextList = []
|
|
for i, result in enumerate(previousResults, 1):
|
|
if hasattr(result, 'success') and hasattr(result, 'resultLabel'):
|
|
status = "Success" if result.success else "Failed"
|
|
contextList.append(f"{i}. {result.resultLabel} - {status}")
|
|
elif isinstance(result, dict):
|
|
status = "Success" if result.get('success', False) else "Failed"
|
|
label = result.get('resultLabel', 'Unknown')
|
|
contextList.append(f"{i}. {label} - {status}")
|
|
else:
|
|
contextList.append(f"{i}. {str(result)}")
|
|
|
|
return "\n".join(contextList) if contextList else "No previous round context available"
|
|
except Exception as e:
|
|
logger.error(f"Error getting previous round context: {str(e)}")
|
|
return "Error retrieving previous round context"
|