cleanup chat part

This commit is contained in:
ValueOn AG 2025-06-11 00:38:26 +02:00
parent 2f7d73f2ce
commit e25903daca
11 changed files with 860 additions and 842 deletions

View file

@ -145,7 +145,7 @@ class AppAccess:
return filtered_records
def canModify(self, table: str, recordId: Optional[int] = None) -> bool:
def canModify(self, table: str, recordId: Optional[str] = None) -> bool:
"""
Checks if the current user can modify (create/update/delete) records in a table.

View file

@ -45,29 +45,16 @@ class ChatAccess:
# Admins see records in their mandate
filtered_records = [r for r in recordset if r.get("mandateId","-") == self.mandateId]
else: # Regular users
# For prompts, users can see all prompts from their mandate
if table == "prompts":
filtered_records = [r for r in recordset if r.get("mandateId") == self.mandateId]
else:
# Users see only their records for other tables
filtered_records = [r for r in recordset
if r.get("mandateId","-") == self.mandateId and r.get("_createdBy") == self.userId]
# Users see only their records for other tables
filtered_records = [r for r in recordset
if r.get("mandateId","-") == self.mandateId and r.get("_createdBy") == self.userId]
# Add access control attributes to each record
for record in filtered_records:
record_id = record.get("id")
# Set access control flags based on user permissions
if table == "prompts":
record["_hideView"] = False # Everyone can view
record["_hideEdit"] = not self.canModify("prompts", record_id)
record["_hideDelete"] = not self.canModify("prompts", record_id)
elif table == "files":
record["_hideView"] = False # Everyone can view
record["_hideEdit"] = not self.canModify("files", record_id)
record["_hideDelete"] = not self.canModify("files", record_id)
record["_hideDownload"] = not self.canModify("files", record_id)
elif table == "workflows":
if table == "workflows":
record["_hideView"] = False # Everyone can view
record["_hideEdit"] = not self.canModify("workflows", record_id)
record["_hideDelete"] = not self.canModify("workflows", record_id)

View file

@ -14,22 +14,20 @@ import asyncio
from modules.interfaces.serviceChatAccess import ChatAccess
from modules.interfaces.serviceChatModel import (
ChatContent, ChatDocument, ChatStat, ChatMessage,
ChatLog, ChatWorkflow, Agent, AgentResponse,
Task, TaskPlan, UserInputRequest
TaskStatus, UserInputRequest, ContentMetadata, ContentItem,
ChatDocument, TaskDocument, ExtractedContent, TaskItem,
TaskResult, ChatStat, ChatLog, ChatMessage, ChatWorkflow
)
from modules.interfaces.serviceAppModel import User
from modules.workflow.managerDocument import DocumentManager
# DYNAMIC PART: Connectors to the Interface
from modules.connectors.connectorDbJson import DatabaseConnector
from modules.connectors.connectorAiOpenai import ChatService
# Basic Configurations
from modules.shared.configuration import APP_CONFIG
logger = logging.getLogger(__name__)
# Singleton factory for Chat instances with AI service per context
# Singleton factory for Chat instances
_chatInterfaces = {}
class ChatInterface:
@ -57,15 +55,10 @@ class ChatInterface:
self.setUserContext(currentUser)
def _initializeServices(self):
"""Initialize service dependencies"""
self.documentManager = DocumentManager(self.service)
pass
def setUserContext(self, currentUser: User):
"""Sets the user context for the interface."""
if not currentUser:
logger.info("Initializing interface without user context")
return
self.currentUser = currentUser # Store User object directly
self.userId = currentUser.id
self.mandateId = currentUser.mandateId
@ -110,59 +103,7 @@ class ChatInterface:
def _initRecords(self):
"""Initializes standard records in the database if they don't exist."""
try:
# Initialize standard prompts
self._initializeStandardPrompts()
# Add other record initializations here
logger.info("Standard records initialized successfully")
except Exception as e:
logger.error(f"Failed to initialize standard records: {str(e)}")
raise
def _initializeStandardPrompts(self):
"""Creates standard prompts if they don't exist."""
prompts = self.db.getRecordset("prompts")
logger.debug(f"Found {len(prompts)} existing prompts")
if not prompts:
logger.debug("Creating standard prompts")
# Define standard prompts
standardPrompts = [
{
"content": "Research the current market trends and developments in [TOPIC]. Collect information about leading companies, innovative products or services, and current challenges. Present the results in a structured overview with relevant data and sources.",
"name": "Web Research: Market Research"
},
{
"content": "Analyze the attached dataset on [TOPIC] and identify the most important trends, patterns, and anomalies. Perform statistical calculations to support your findings. Present the results in a clearly structured analysis and draw relevant conclusions.",
"name": "Analysis: Data Analysis"
},
{
"content": "Create a detailed protocol of our meeting on [TOPIC]. Capture all discussed points, decisions made, and agreed measures. Structure the protocol clearly with agenda items, participant list, and clear responsibilities for follow-up actions.",
"name": "Protocol: Meeting Minutes"
},
{
"content": "Develop a UI/UX design concept for [APPLICATION/WEBSITE]. Consider the target audience, main functions, and brand identity. Describe the visual design, navigation, interaction patterns, and information architecture. Explain how the design optimizes user-friendliness and user experience.",
"name": "Design: UI/UX Design"
},
{
"content": "Gib mir die ersten 1000 Primzahlen",
"name": "Code: Primzahlen"
},
{
"content": "Bereite mir eine formelle E-Mail an peter.muster@domain.com vor, um meinen Termin von 10 Uhr auf Freitag zu scheiben.",
"name": "Mail: Vorbereitung"
},
]
# Create prompts
for promptData in standardPrompts:
createdPrompt = self.db.recordCreate("prompts", promptData)
logger.debug(f"Prompt '{promptData.get('name', 'Standard')}' was created with ID {createdPrompt['id']} and context mandate={createdPrompt.get('mandateId')}, user={createdPrompt.get('_createdBy')}")
else:
logger.debug("Prompts already exist, skipping creation")
pass
def _uam(self, table: str, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Delegate to access control module."""
@ -182,47 +123,6 @@ class ChatInterface:
"""Delegate to access control module."""
return self.access.canModify(table, recordId)
# Language support method
def setUserLanguage(self, languageCode: str):
"""Set the user's preferred language"""
self.userLanguage = languageCode
logger.debug(f"User language set to: {languageCode}")
# AI Call Root Function
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.aiService:
logger.error("AI service not set in LucydomInterface")
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.aiService.callApi(messages, temperature=temperature)
else:
return await self.aiService.callApi(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.aiService:
logger.error("AI service not set in LucydomInterface")
return "Error: AI service not available"
return await self.aiService.analyzeImage(imageData, mimeType, prompt)
# Utilities
def getInitialId(self, table: str) -> Optional[str]:
@ -837,7 +737,7 @@ class ChatInterface:
# Workflow Actions
async def workflowStart(self, userInput: UserInputRequest, workflowId: Optional[str] = None) -> ChatWorkflow:
async def workflowStart(self, currentUser: User, userInput: UserInputRequest, workflowId: Optional[str] = None) -> ChatWorkflow:
"""
Starts a new workflow or continues an existing one.
@ -852,35 +752,12 @@ class ChatInterface:
# Get current timestamp
currentTime = self._getCurrentTimestamp()
# Process files if any
documents = []
if userInput.listFileId:
documents = await self._processFileIds(userInput.listFileId)
# Create initial message
initialMessage = ChatMessage(
id=str(uuid.uuid4()),
role="user",
content=userInput.prompt,
timestamp=currentTime,
documents=documents
)
if workflowId:
# Continue existing workflow
workflow = self.getWorkflow(workflowId)
if not workflow:
raise ValueError(f"Workflow {workflowId} not found")
# Add message to workflow
self.createWorkflowMessage({
"workflowId": workflowId,
"messageId": initialMessage.id,
"role": initialMessage.role,
"content": initialMessage.content,
"timestamp": initialMessage.timestamp,
"documents": [doc.dict() for doc in initialMessage.documents]
})
# Update workflow
self.updateWorkflow(workflowId, {
@ -897,10 +774,10 @@ class ChatInterface:
"lastActivity": currentTime,
"currentRound": 1,
"mandateId": self.mandateId,
"messageIds": [initialMessage.id],
"messageIds": [],
"dataStats": {
"totalMessages": 1,
"totalDocuments": len(documents),
"totalMessages": 0,
"totalDocuments": 0,
"totalTokens": 0
}
}
@ -908,16 +785,6 @@ class ChatInterface:
# Create workflow
workflow = self.createWorkflow(workflowData)
# Add initial message
self.createWorkflowMessage({
"workflowId": workflow.id,
"messageId": initialMessage.id,
"role": initialMessage.role,
"content": initialMessage.content,
"timestamp": initialMessage.timestamp,
"documents": [doc.dict() for doc in initialMessage.documents]
})
# Add log entry
self.createWorkflowLog({
"workflowId": workflow.id,
@ -929,7 +796,7 @@ class ChatInterface:
# Start workflow processing
from modules.workflow.managerWorkflow import WorkflowManager
workflowManager = WorkflowManager(self)
workflowManager = WorkflowManager(self, currentUser)
asyncio.create_task(workflowManager.workflowProcess(userInput, workflow))
return workflow
@ -979,36 +846,6 @@ class ChatInterface:
logger.error(f"Error stopping workflow: {str(e)}")
raise
async def processFileIds(self, fileIds: List[str]) -> List[ChatDocument]:
"""
Process multiple files and extract their contents.
Args:
fileIds: List of file IDs to process
Returns:
List of ChatDocument objects
"""
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
def getInterface(currentUser: Optional[User] = None) -> 'ChatInterface':
"""

View file

@ -10,8 +10,7 @@ from enum import Enum
from modules.shared.attributeUtils import register_model_labels, ModelMixin
# ENUMS
# ===== Base Enums and Simple Models =====
class TaskStatus(str, Enum):
"""Task status enumeration"""
@ -36,13 +35,12 @@ register_model_labels(
}
)
# USER MODELS
class UserInputRequest(BaseModel, ModelMixin):
"""Data model for a user input request"""
prompt: str = Field(description="Prompt for the user")
listFileId: List[str] = Field(default_factory=list, description="List of file IDs")
userLanguage: str = Field(default="en", description="User's preferred language")
# Register labels for UserInputRequest
register_model_labels(
"UserInputRequest",
@ -54,14 +52,13 @@ register_model_labels(
}
)
# DOCUMENT MODELS
# ===== Content Models =====
class ContentMetadata(BaseModel, ModelMixin):
"""Metadata for content items"""
size: int = Field(description="Content size in bytes")
pages: Optional[int] = Field(None, description="Number of pages for multi-page content")
error: Optional[str] = Field(None, description="Processing error if any")
# Media-specific attributes
width: Optional[int] = Field(None, description="Width in pixels for images/videos")
height: Optional[int] = Field(None, description="Height in pixels for images/videos")
colorMode: Optional[str] = Field(None, description="Color mode (e.g., RGB, CMYK, grayscale)")
@ -162,96 +159,10 @@ register_model_labels(
}
)
# WORKFLOW MODELS
# ===== Task Models =====
class ChatStat(BaseModel, ModelMixin):
"""Data model for chat statistics"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key")
processingTime: Optional[float] = Field(None, description="Processing time in seconds")
tokenCount: Optional[int] = Field(None, description="Number of tokens processed")
bytesSent: Optional[int] = Field(None, description="Number of bytes sent")
bytesReceived: Optional[int] = Field(None, description="Number of bytes received")
successRate: Optional[float] = Field(None, description="Success rate of operations")
errorCount: Optional[int] = Field(None, description="Number of errors encountered")
# Register labels for ChatStat
register_model_labels(
"ChatStat",
{"en": "Chat Statistics", "fr": "Statistiques de chat"},
{
"id": {"en": "ID", "fr": "ID"},
"processingTime": {"en": "Processing Time", "fr": "Temps de traitement"},
"tokenCount": {"en": "Token Count", "fr": "Nombre de tokens"},
"bytesSent": {"en": "Bytes Sent", "fr": "Octets envoyés"},
"bytesReceived": {"en": "Bytes Received", "fr": "Octets reçus"},
"successRate": {"en": "Success Rate", "fr": "Taux de succès"},
"errorCount": {"en": "Error Count", "fr": "Nombre d'erreurs"}
}
)
class ChatLog(BaseModel, ModelMixin):
"""Data model for a chat log"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key")
workflowId: str = Field(description="Foreign key to workflow")
message: str = Field(description="Log message")
type: str = Field(description="Type of log entry")
timestamp: str = Field(description="Timestamp of the log entry")
agentName: str = Field(description="Name of the agent")
status: str = Field(description="Status of the log entry")
progress: Optional[int] = Field(None, description="Progress percentage")
performance: Optional[Dict[str, Any]] = Field(None, description="Performance metrics")
# Register labels for ChatLog
register_model_labels(
"ChatLog",
{"en": "Chat Log", "fr": "Journal de chat"},
{
"id": {"en": "ID", "fr": "ID"},
"workflowId": {"en": "Workflow ID", "fr": "ID du flux de travail"},
"message": {"en": "Message", "fr": "Message"},
"type": {"en": "Type", "fr": "Type"},
"timestamp": {"en": "Timestamp", "fr": "Horodatage"},
"agentName": {"en": "Agent Name", "fr": "Nom de l'agent"},
"status": {"en": "Status", "fr": "Statut"},
"progress": {"en": "Progress", "fr": "Progression"},
"performance": {"en": "Performance", "fr": "Performance"}
}
)
class ChatMessage(BaseModel, ModelMixin):
"""Data model for a chat message"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key")
workflowId: str = Field(description="Foreign key to workflow")
parentMessageId: Optional[str] = Field(None, description="Parent message ID for threading")
agentName: Optional[str] = Field(None, description="Name of the agent")
documents: List[ChatDocument] = Field(default_factory=list, description="Associated documents")
message: Optional[str] = Field(None, description="Message content")
role: str = Field(description="Role of the message sender")
status: str = Field(description="Status of the message (first, step, last)")
sequenceNr: int = Field(description="Sequence number of the message (set automatically)")
publishedAt: str = Field(description="When the message was published")
stats: Optional[ChatStat] = Field(None, description="Statistics for this message")
success: Optional[bool] = Field(None, description="Whether the message processing was successful")
# Register labels for ChatMessage
register_model_labels(
"ChatMessage",
{"en": "Chat Message", "fr": "Message de chat"},
{
"id": {"en": "ID", "fr": "ID"},
"workflowId": {"en": "Workflow ID", "fr": "ID du flux de travail"},
"parentMessageId": {"en": "Parent Message ID", "fr": "ID du message parent"},
"agentName": {"en": "Agent Name", "fr": "Nom de l'agent"},
"documents": {"en": "Documents", "fr": "Documents"},
"message": {"en": "Message", "fr": "Message"},
"role": {"en": "Role", "fr": "Rôle"},
"status": {"en": "Status", "fr": "Statut"},
"sequenceNr": {"en": "Sequence Number", "fr": "Numéro de séquence"},
"publishedAt": {"en": "Published At", "fr": "Publié le"},
"stats": {"en": "Statistics", "fr": "Statistiques"},
"success": {"en": "Success", "fr": "Succès"}
}
)
class AgentTask(BaseModel, ModelMixin):
"""Model for agent tasks"""
class TaskItem(BaseModel, ModelMixin):
"""Model for tasks"""
id: str = Field(..., description="Unique task identifier")
workflowId: str = Field(..., description="Associated workflow ID")
status: TaskStatus = Field(default=TaskStatus.PENDING, description="Current task status")
@ -264,7 +175,7 @@ class AgentTask(BaseModel, ModelMixin):
retryMax: int = Field(default=3, description="Maximum number of retry attempts")
rollbackOnFailure: bool = Field(default=True, description="Whether to rollback on failure")
dependencies: List[str] = Field(default_factory=list, description="List of dependent task IDs")
thisTaskFeedback: Optional[Dict[str, Any]] = Field(None, description="Task feedback data")
feedback: Optional[Dict[str, Any]] = Field(None, description="Task feedback data")
def isCompleted(self) -> bool:
"""Check if task is completed"""
@ -328,11 +239,11 @@ class AgentTask(BaseModel, ModelMixin):
def setFeedback(self, feedback: Dict[str, Any]) -> None:
"""Set task feedback"""
self.thisTaskFeedback = feedback
self.feedback = feedback
# Register labels for AgentTask
# Register labels for TaskItem
register_model_labels(
"AgentTask",
"TaskItem",
{"en": "Task", "fr": "Tâche"},
{
"id": {"en": "ID", "fr": "ID"},
@ -347,11 +258,137 @@ register_model_labels(
"retryMax": {"en": "Max Retries", "fr": "Tentatives maximales"},
"rollbackOnFailure": {"en": "Rollback on Failure", "fr": "Annulation en cas d'échec"},
"dependencies": {"en": "Dependencies", "fr": "Dépendances"},
"thisTaskFeedback": {"en": "Task Feedback", "fr": "Retour sur la tâche"}
"feedback": {"en": "Task Feedback", "fr": "Retour sur la tâche"}
}
)
# WORKFLOW MODEL
class TaskResult(BaseModel, ModelMixin):
"""Model for task execution results"""
taskId: str = Field(..., description="ID of the task this result belongs to")
status: TaskStatus = Field(..., description="Result status")
success: bool = Field(..., description="Whether the task was successful")
error: Optional[str] = Field(None, description="Error message if task failed")
data: Optional[Dict[str, Any]] = Field(None, description="Result data")
documents: List[ChatDocument] = Field(default_factory=list, description="Output documents")
feedback: Optional[str] = Field(None, description="Task feedback message")
processingTime: Optional[float] = Field(None, description="Processing time in seconds")
timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC), description="When the result was created")
def isSuccessful(self) -> bool:
"""Check if result indicates success"""
return self.success and self.status == TaskStatus.COMPLETED
def hasError(self) -> bool:
"""Check if result has an error"""
return not self.success or self.status == TaskStatus.FAILED
def getErrorMessage(self) -> Optional[str]:
"""Get error message if any"""
return self.error if self.hasError() else None
# Register labels for TaskResult
register_model_labels(
"TaskResult",
{"en": "Task Result", "fr": "Résultat de la tâche"},
{
"taskId": {"en": "Task ID", "fr": "ID de la tâche"},
"status": {"en": "Status", "fr": "Statut"},
"success": {"en": "Success", "fr": "Succès"},
"error": {"en": "Error", "fr": "Erreur"},
"data": {"en": "Data", "fr": "Données"},
"documents": {"en": "Documents", "fr": "Documents"},
"feedback": {"en": "Feedback", "fr": "Retour"},
"processingTime": {"en": "Processing Time", "fr": "Temps de traitement"},
"timestamp": {"en": "Timestamp", "fr": "Horodatage"}
}
)
# ===== Message and Workflow Models =====
class ChatStat(BaseModel, ModelMixin):
"""Data model for chat statistics"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key")
processingTime: Optional[float] = Field(None, description="Processing time in seconds")
tokenCount: Optional[int] = Field(None, description="Number of tokens processed")
bytesSent: Optional[int] = Field(None, description="Number of bytes sent")
bytesReceived: Optional[int] = Field(None, description="Number of bytes received")
successRate: Optional[float] = Field(None, description="Success rate of operations")
errorCount: Optional[int] = Field(None, description="Number of errors encountered")
# Register labels for ChatStat
register_model_labels(
"ChatStat",
{"en": "Chat Statistics", "fr": "Statistiques de chat"},
{
"id": {"en": "ID", "fr": "ID"},
"processingTime": {"en": "Processing Time", "fr": "Temps de traitement"},
"tokenCount": {"en": "Token Count", "fr": "Nombre de tokens"},
"bytesSent": {"en": "Bytes Sent", "fr": "Octets envoyés"},
"bytesReceived": {"en": "Bytes Received", "fr": "Octets reçus"},
"successRate": {"en": "Success Rate", "fr": "Taux de succès"},
"errorCount": {"en": "Error Count", "fr": "Nombre d'erreurs"}
}
)
class ChatLog(BaseModel, ModelMixin):
"""Data model for a chat log"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key")
workflowId: str = Field(description="Foreign key to workflow")
message: str = Field(description="Log message")
type: str = Field(description="Type of log entry")
timestamp: str = Field(description="Timestamp of the log entry")
status: str = Field(description="Status of the log entry")
progress: Optional[int] = Field(None, description="Progress percentage")
performance: Optional[Dict[str, Any]] = Field(None, description="Performance metrics")
# Register labels for ChatLog
register_model_labels(
"ChatLog",
{"en": "Chat Log", "fr": "Journal de chat"},
{
"id": {"en": "ID", "fr": "ID"},
"workflowId": {"en": "Workflow ID", "fr": "ID du flux de travail"},
"message": {"en": "Message", "fr": "Message"},
"type": {"en": "Type", "fr": "Type"},
"timestamp": {"en": "Timestamp", "fr": "Horodatage"},
"status": {"en": "Status", "fr": "Statut"},
"progress": {"en": "Progress", "fr": "Progression"},
"performance": {"en": "Performance", "fr": "Performance"}
}
)
class ChatMessage(BaseModel, ModelMixin):
"""Data model for a chat message"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key")
workflowId: str = Field(description="Foreign key to workflow")
parentMessageId: Optional[str] = Field(None, description="Parent message ID for threading")
documents: List[ChatDocument] = Field(default_factory=list, description="Associated documents")
message: Optional[str] = Field(None, description="Message content")
role: str = Field(description="Role of the message sender")
status: str = Field(description="Status of the message (first, step, last)")
sequenceNr: int = Field(description="Sequence number of the message (set automatically)")
publishedAt: str = Field(description="When the message was published")
stats: Optional[ChatStat] = Field(None, description="Statistics for this message")
success: Optional[bool] = Field(None, description="Whether the message processing was successful")
# Register labels for ChatMessage
register_model_labels(
"ChatMessage",
{"en": "Chat Message", "fr": "Message de chat"},
{
"id": {"en": "ID", "fr": "ID"},
"workflowId": {"en": "Workflow ID", "fr": "ID du flux de travail"},
"parentMessageId": {"en": "Parent Message ID", "fr": "ID du message parent"},
"documents": {"en": "Documents", "fr": "Documents"},
"message": {"en": "Message", "fr": "Message"},
"role": {"en": "Role", "fr": "Rôle"},
"status": {"en": "Status", "fr": "Statut"},
"sequenceNr": {"en": "Sequence Number", "fr": "Numéro de séquence"},
"publishedAt": {"en": "Published At", "fr": "Publié le"},
"stats": {"en": "Statistics", "fr": "Statistiques"},
"success": {"en": "Success", "fr": "Succès"}
}
)
class ChatWorkflow(BaseModel, ModelMixin):
"""Data model for a chat workflow"""
@ -365,7 +402,8 @@ class ChatWorkflow(BaseModel, ModelMixin):
logs: List[ChatLog] = Field(default_factory=list, description="Workflow logs")
messages: List[ChatMessage] = Field(default_factory=list, description="Messages in the workflow")
stats: Optional[ChatStat] = Field(None, description="Workflow statistics")
tasks: List[AgentTask] = Field(default_factory=list, description="List of tasks in the workflow")
tasks: List[TaskItem] = Field(default_factory=list, description="List of tasks in the workflow")
# Register labels for ChatWorkflow
register_model_labels(
"ChatWorkflow",

View file

@ -241,47 +241,6 @@ class ServiceManagement:
"""Delegate to access control module."""
return self.access.canModify(table, recordId)
# Language support method
def setUserLanguage(self, languageCode: str):
"""Set the user's preferred language"""
self.userLanguage = languageCode
logger.debug(f"User language set to: {languageCode}")
# AI Call Root Function
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.aiService:
logger.error("AI service not set in ServiceManagement")
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.aiService.callApi(messages, temperature=temperature)
else:
return await self.aiService.callApi(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.aiService:
logger.error("AI service not set in ServiceManagement")
return "Error: AI service not available"
return await self.aiService.analyzeImage(imageData, mimeType, prompt)
# Utilities
def getInitialId(self, table: str) -> Optional[str]:

View file

@ -18,9 +18,6 @@ from modules.security.auth import limiter, getCurrentUser
import modules.interfaces.serviceChatClass as serviceChatClass
from modules.interfaces.serviceChatClass import getInterface
# Import workflow manager
from modules.workflow.workflowManager import getWorkflowManager
# Import models
from modules.interfaces.serviceChatModel import (
ChatWorkflow,
@ -46,18 +43,8 @@ router = APIRouter(
responses={404: {"description": "Not found"}}
)
def createServiceContainer(currentUser: Dict[str, Any]):
"""Create a service container with all required interfaces."""
# Get all interfaces
chatInterface = serviceChatClass.getInterface(currentUser)
# Create service container
service = type('ServiceContainer', (), {
'user': currentUser,
'functions': chatInterface
})
return service
def getServiceChat(currentUser: User):
return serviceChatClass.getInterface(currentUser)
# Consolidated endpoint for getting all workflows
@router.get("/", response_model=List[ChatWorkflow])
@ -117,10 +104,10 @@ async def get_workflow_status(
"""Get the current status of a workflow."""
try:
# Get service container
service = createServiceContainer(currentUser)
serviceChat = getServiceChat(currentUser)
# Retrieve workflow
workflow = service.base.getWorkflow(workflowId)
workflow = serviceChat.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@ -149,10 +136,10 @@ async def get_workflow_logs(
"""Get logs for a workflow with support for selective data transfer."""
try:
# Get service container
service = createServiceContainer(currentUser)
serviceChat = getServiceChat(currentUser)
# Verify workflow exists
workflow = service.base.getWorkflow(workflowId)
workflow = serviceChat.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@ -160,7 +147,7 @@ async def get_workflow_logs(
)
# Get all logs
allLogs = service.base.getWorkflowLogs(workflowId)
allLogs = serviceChat.getWorkflowLogs(workflowId)
# Apply selective data transfer if logId is provided
if logId:
@ -192,10 +179,10 @@ async def get_workflow_messages(
"""Get messages for a workflow with support for selective data transfer."""
try:
# Get service container
service = createServiceContainer(currentUser)
serviceChat = getServiceChat(currentUser)
# Verify workflow exists
workflow = service.base.getWorkflow(workflowId)
workflow = serviceChat.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@ -203,7 +190,7 @@ async def get_workflow_messages(
)
# Get all messages
allMessages = service.base.getWorkflowMessages(workflowId)
allMessages = serviceChat.getWorkflowMessages(workflowId)
# Apply selective data transfer if messageId is provided
if messageId:
@ -238,10 +225,10 @@ async def start_workflow(
"""
try:
# Get service container
service = createServiceContainer(currentUser)
serviceChat = getServiceChat(currentUser)
# Start or continue workflow using ChatInterface
workflow = await service.functions.workflowStart(userInput, workflowId)
workflow = await serviceChat.workflowStart(currentUser, userInput, workflowId)
return ChatWorkflow(**workflow)
@ -263,10 +250,10 @@ async def stop_workflow(
"""Stops a running workflow."""
try:
# Get service container
service = createServiceContainer(currentUser)
serviceChat = getServiceChat(currentUser)
# Stop workflow using ChatInterface
workflow = await service.functions.workflowStop(workflowId)
workflow = await serviceChat.workflowStop(workflowId)
return ChatWorkflow(**workflow)
@ -288,10 +275,10 @@ async def delete_workflow(
"""Deletes a workflow and its associated data."""
try:
# Get service container
service = createServiceContainer(currentUser)
serviceChat = getServiceChat(currentUser)
# Verify workflow exists
workflow = service.base.getWorkflow(workflowId)
workflow = serviceChat.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@ -306,7 +293,7 @@ async def delete_workflow(
)
# Delete workflow
success = service.base.deleteWorkflow(workflowId)
success = serviceChat.deleteWorkflow(workflowId)
if not success:
raise HTTPException(
@ -341,10 +328,10 @@ async def delete_workflow_message(
"""Delete a message from a workflow."""
try:
# Get service container
service = createServiceContainer(currentUser)
serviceChat = getServiceChat(currentUser)
# Verify workflow exists
workflow = service.base.getWorkflow(workflowId)
workflow = serviceChat.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@ -352,7 +339,7 @@ async def delete_workflow_message(
)
# Delete the message
success = service.base.deleteWorkflowMessage(workflowId, messageId)
success = serviceChat.deleteWorkflowMessage(workflowId, messageId)
if not success:
raise HTTPException(
@ -364,7 +351,7 @@ async def delete_workflow_message(
messageIds = workflow.get("messageIds", [])
if messageId in messageIds:
messageIds.remove(messageId)
service.base.updateWorkflow(workflowId, {"messageIds": messageIds})
serviceChat.updateWorkflow(workflowId, {"messageIds": messageIds})
return {
"workflowId": workflowId,
@ -392,10 +379,10 @@ async def delete_file_from_message(
"""Delete a file reference from a message in a workflow."""
try:
# Get service container
service = createServiceContainer(currentUser)
serviceChat = getServiceChat(currentUser)
# Verify workflow exists
workflow = service.base.getWorkflow(workflowId)
workflow = serviceChat.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@ -403,7 +390,7 @@ async def delete_file_from_message(
)
# Delete file reference from message
success = service.base.deleteFileFromMessage(workflowId, messageId, fileId)
success = serviceChat.deleteFileFromMessage(workflowId, messageId, fileId)
if not success:
raise HTTPException(

File diff suppressed because it is too large Load diff

View file

@ -4,8 +4,6 @@ Document Manager Module for handling document operations and content extraction.
import base64
import logging
from typing import List, Optional, Dict, Any, Union
from pathlib import Path
import uuid
from modules.interfaces.serviceChatModel import (

View file

@ -3,9 +3,12 @@ import logging
from datetime import datetime, UTC
import uuid
from modules.interfaces.serviceAppClass import User
from modules.interfaces.serviceChatModel import (
AgentTask, AgentResult, TaskStatus, ChatMessage,
UserInputRequest, ChatWorkflow, ChatDocument
TaskStatus, UserInputRequest, ContentMetadata, ContentItem,
ChatDocument, TaskDocument, ExtractedContent, TaskItem,
TaskResult, ChatStat, ChatLog, ChatMessage, ChatWorkflow
)
from modules.interfaces.serviceChatClass import ChatInterface
from modules.workflow.managerChat import ChatManager
@ -17,131 +20,111 @@ class WorkflowStoppedException(Exception):
pass
class WorkflowManager:
"""Manages workflow execution lifecycle"""
"""Manager for workflow processing and coordination"""
def __init__(self, chatInterface: ChatInterface):
self.workflow = None
self.isRunning = False
def __init__(self, chatInterface: ChatInterface, currentUser: User):
self.chatInterface = chatInterface
self.chatManager = ChatManager()
self.chatManager = ChatManager(currentUser)
self.currentUser = currentUser
def _checkWorkflowStopped(self, workflow: ChatWorkflow) -> None:
"""Check if workflow has been stopped"""
if workflow.status == "stopped":
logger.info(f"Workflow {workflow.id} stopped by user")
raise WorkflowStoppedException("User stopped workflow")
raise WorkflowStoppedException("Workflow was stopped by user")
async def workflowProcess(self, userInput: UserInputRequest, workflow: ChatWorkflow) -> None:
"""Main workflow execution process"""
"""Process a workflow with user input"""
try:
self.workflow = workflow
self.isRunning = True
# Initialize chat manager
await self.chatManager.initialize(workflow)
# Process documents from userInput using ChatInterface's method
documents = []
if userInput.listFileId:
documents = await self.chatInterface.processFileIds(userInput.listFileId)
# Set user language
self.chatManager.setUserLanguage(userInput.userLanguage)
# Create initial ChatMessage from userInput
initialMessage = ChatMessage(
# Send first message
message = await self._sendFirstMessage(userInput, workflow)
# Create initial task
task = await self.chatManager.createInitialTask(workflow, message)
# Process workflow
while True:
# Check if workflow is stopped
self._checkWorkflowStopped(workflow)
# Execute task
result = await self.chatManager.executeTask(task)
# Process result
await self.chatManager.parseTaskResult(workflow, result)
# Check if workflow should continue
if not await self.chatManager.shouldContinue(workflow):
break
# Identify next task
nextTaskResult = await self.chatManager.identifyNextTask(workflow)
# Create next task
task = await self.chatManager.createNextTask(workflow, nextTaskResult)
if not task:
break
# Send last message
await self._sendLastMessage(workflow)
except WorkflowStoppedException:
logger.info("Workflow stopped by user")
except Exception as e:
logger.error(f"Workflow processing error: {str(e)}")
raise
async def _sendFirstMessage(self, userInput: UserInputRequest, workflow: ChatWorkflow) -> ChatMessage:
"""Send first message to start workflow"""
try:
# Create initial message
message = ChatMessage(
id=str(uuid.uuid4()),
workflowId=workflow.id,
role="user",
message=userInput.prompt,
status="first", # First message in workflow
documents=documents
status="first",
sequenceNr=1,
publishedAt=datetime.now(UTC).isoformat()
)
# Add documents if any
if userInput.listFileId:
message.documents = await self.chatManager.processFileIds(userInput.listFileId)
# Add message to workflow
await self.chatInterface.createWorkflowMessage(initialMessage.dict())
workflow.messages.append(message)
return message
# Create initial task
task = await self.chatInterface.createInitialTask(workflow, initialMessage)
if not task:
logger.error("Failed to create initial task")
workflow.status = "error"
workflow.error = "Failed to create initial task"
return
except Exception as e:
logger.error(f"Error sending first message: {str(e)}")
raise
# Main workflow loop
while self.isRunning and workflow.status == "running":
self._checkWorkflowStopped(workflow)
# Execute task
result = AgentResult(
id=task.id,
status=TaskStatus.PENDING,
createdAt=datetime.now(UTC),
updatedAt=datetime.now(UTC)
)
# Execute each action
for action in task.actionList:
self._checkWorkflowStopped(workflow)
try:
# Execute action
actionResult = await action.execute()
# Update action status
action.status = TaskStatus.COMPLETED if actionResult.success else TaskStatus.FAILED
action.result = actionResult
# Check for failure
if not actionResult.success:
result.status = TaskStatus.FAILED
result.error = actionResult.error
break
except Exception as e:
logger.error(f"Action error: {str(e)}")
action.status = TaskStatus.FAILED
result.status = TaskStatus.FAILED
result.error = str(e)
break
# Update result status
if result.status != TaskStatus.FAILED:
result.status = TaskStatus.COMPLETED
result.updatedAt = datetime.now(UTC)
self._checkWorkflowStopped(workflow)
# Update workflow with result
await self.chatInterface.addTaskResult(workflow, result)
# Get next task
task = await self.chatInterface.getNextTask(workflow)
if not task:
break
# Check if should continue
if not await self.chatInterface.shouldContinue(workflow):
break
async def _sendLastMessage(self, workflow: ChatWorkflow) -> None:
"""Send last message to complete workflow"""
try:
# Generate feedback
feedback = await self.chatManager.generateWorkflowFeedback(workflow)
# Generate final feedback message using ChatManager
finalFeedback = await self.chatManager.generateWorkflowFeedback(workflow)
# Create final message with "last" status
self._checkWorkflowStopped(workflow)
finalMessage = ChatMessage(
# Create last message
message = ChatMessage(
id=str(uuid.uuid4()),
workflowId=workflow.id,
role="assistant",
message=finalFeedback,
status="last" # Last message in workflow
message=feedback,
status="last",
sequenceNr=len(workflow.messages) + 1,
publishedAt=datetime.now(UTC).isoformat()
)
await self.chatInterface.createWorkflowMessage(finalMessage.dict())
# Complete workflow
if workflow.status != "failed":
workflow.status = "completed"
workflow.lastActivity = datetime.now(UTC).isoformat()
# Add message to workflow
workflow.messages.append(message)
except Exception as e:
logger.error(f"Workflow error: {str(e)}")
if self.workflow:
self.workflow.status = "error"
self.workflow.error = str(e)
logger.error(f"Error sending last message: {str(e)}")
raise

View file

@ -6,15 +6,20 @@ import asyncio
from modules.shared.configuration import APP_CONFIG
from modules.methods import MethodBase, MethodResult
from modules.interfaces.serviceChatModel import AgentTask, AgentAction, AgentResult, Action, TaskStatus, ActionStatus
from modules.interfaces.serviceChatModel import (
TaskStatus, UserInputRequest, ContentMetadata, ContentItem,
ChatDocument, TaskDocument, ExtractedContent, TaskItem,
TaskResult, ChatStat, ChatLog, ChatMessage, ChatWorkflow
)
from modules.interfaces.serviceManagementClass import ServiceManagement
from modules.interfaces.serviceChatClass import ChatInterface
logger = logging.getLogger(__name__)
class ServiceContainer:
"""Service container for dependency injection and service management."""
def __init__(self):
def __init__(self, chatInterface: ChatInterface):
self.methods = {}
self.context = {}
self.workflow = None
@ -29,7 +34,7 @@ class ServiceContainer:
'lastError': None,
'lastErrorTime': None
}
self.tasks: Dict[str, Any] = {} # Will be populated with AgentTask instances
self.tasks: Dict[str, TaskItem] = {} # Will be populated with TaskItem instances
# Initialize service management
self.serviceManagement = ServiceManagement()

View file

@ -1,8 +1,5 @@
Clean
- ServiceContainer to clean for used functions, no spare!
- all the definitions used in serviceChatModel?
- all AI calls to route over AI-Module (AI basic, ai special, ai...)
- all AI calls to route over AI-Module (AI basic, ai special, ai...) and to streamline
- service object to define correctly