prod azure 1.1.1 fix agents

This commit is contained in:
ValueOn AG 2025-05-11 03:04:02 +02:00
parent 1ceb361274
commit 8ea05780d1
12 changed files with 582 additions and 99 deletions

View file

@ -38,11 +38,10 @@ class AgentAnalyst(AgentBase):
def setDependencies(self, mydom=None): def setDependencies(self, mydom=None):
"""Set external dependencies for the agent.""" """Set external dependencies for the agent."""
self.mydom = mydom
async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]: async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]:
""" """
Process a task by focusing on required outputs and using AI to generate them. Process a task by focusing on required outputs and using AI to guide the analysis process.
Args: Args:
task: Task dictionary with prompt, inputDocuments, outputSpecifications task: Task dictionary with prompt, inputDocuments, outputSpecifications
@ -53,68 +52,49 @@ class AgentAnalyst(AgentBase):
try: try:
# Extract task information # Extract task information
prompt = task.get("prompt", "") prompt = task.get("prompt", "")
inputDocuments = task.get("inputDocuments", [])
outputSpecs = task.get("outputSpecifications", []) outputSpecs = task.get("outputSpecifications", [])
workflow = task.get("context", {}).get("workflow", {})
# Check AI service # Check AI service
if not self.mydom: if not self.mydom:
return { return {
"feedback": "The Analyst agent requires an AI service to function.", "feedback": "The Analyst agent requires an AI service to function effectively.",
"documents": [] "documents": []
} }
# Extract data from documents - focusing only on dataExtracted # Create analysis plan
self.mydom.logAdd(task["workflowId"], "Extracting data from documents...", level="info", progress=35) if workflow:
datasets, documentContext = self._extractData(inputDocuments) self.workflowManager.logAdd(workflow, "Extracting data from documents...", level="info", progress=35)
analysisPlan = await self._createAnalysisPlan(prompt)
# Generate task analysis to understand what's needed # Check if this is truly an analysis task
self.mydom.logAdd(task["workflowId"], "Analyzing task requirements...", level="info", progress=45) if not analysisPlan.get("requiresAnalysis", True):
analysisPlan = await self._analyzeTask(prompt, documentContext, datasets, outputSpecs) return {
"feedback": "This task doesn't appear to require analysis. Please try a different agent.",
"documents": []
}
# Generate all required output documents # Analyze data
documents = [] if workflow:
self.workflowManager.logAdd(workflow, "Analyzing task requirements...", level="info", progress=45)
analysisResults = await self._analyzeData(task, analysisPlan)
# If no output specs provided, create default analysis outputs # Format results into requested output documents
if not outputSpecs:
outputSpecs = []
# Process each output specification
totalSpecs = len(outputSpecs) totalSpecs = len(outputSpecs)
for i, spec in enumerate(outputSpecs): for i, spec in enumerate(outputSpecs):
progress = 45 + int((i / totalSpecs) * 45) # Progress from 45% to 90% progress = 50 + int((i / totalSpecs) * 40) # Progress from 50% to 90%
self.mydom.logAdd(task["workflowId"], f"Creating output {i+1}/{totalSpecs}...", level="info", progress=progress) if self.workflowManager:
self.workflowManager.logAdd(workflow, f"Creating output {i+1}/{totalSpecs}...", level="info", progress=progress)
outputLabel = spec.get("label", "")
outputDescription = spec.get("description", "") documents = await self._createOutputDocuments(
prompt,
# Determine type based on file extension analysisResults,
outputType = outputLabel.split('.')[-1].lower() if '.' in outputLabel else "txt" outputSpecs,
analysisPlan
# Generate appropriate content based on output type )
if outputType in ['png', 'jpg', 'jpeg', 'svg']:
# Create visualization
document = await self._createVisualization(
datasets, prompt, outputLabel, analysisPlan, outputDescription
)
documents.append(document)
elif outputType in ['csv', 'json', 'xlsx']:
# Create data document
document = await self._createDataDocument(
datasets, prompt, outputLabel, analysisPlan, outputDescription
)
documents.append(document)
else:
# Create text document (report, analysis, etc.)
document = await self._createTextDocument(
datasets, documentContext, prompt, outputLabel,
outputType, analysisPlan, outputDescription
)
documents.append(document)
# Generate feedback # Generate feedback
feedback = f"{analysisPlan.get('feedback')}" feedback = analysisPlan.get("feedback", f"I analyzed '{prompt[:50]}...' and generated {len(documents)} output documents.")
if analysisPlan.get("insights"):
feedback += f"\n\n{analysisPlan.get('insights')}"
return { return {
"feedback": feedback, "feedback": feedback,
@ -122,7 +102,7 @@ class AgentAnalyst(AgentBase):
} }
except Exception as e: except Exception as e:
logger.error(f"Error in analysis: {str(e)}", exc_info=True) logger.error(f"Error during analysis: {str(e)}", exc_info=True)
return { return {
"feedback": f"Error during analysis: {str(e)}", "feedback": f"Error during analysis: {str(e)}",
"documents": [] "documents": []
@ -337,6 +317,124 @@ class AgentAnalyst(AgentBase):
], ],
"feedback": f"I'll analyze the data and provide insights about {prompt}" "feedback": f"I'll analyze the data and provide insights about {prompt}"
} }
async def _createAnalysisPlan(self, prompt: str) -> Dict[str, Any]:
"""
Create an analysis plan based on the task prompt.
Args:
prompt: The task prompt
Returns:
Analysis plan dictionary
"""
try:
# Create analysis prompt
analysisPrompt = f"""
Analyze this data analysis task and create a detailed plan:
TASK: {prompt}
Create a detailed analysis plan in JSON format with:
{{
"requiresAnalysis": true/false,
"analysisSteps": [
{{
"step": "step description",
"purpose": "why this step is needed",
"techniques": ["technique1", "technique2"],
"outputs": ["output1", "output2"]
}}
],
"visualizations": [
{{
"type": "visualization type",
"purpose": "what it shows",
"settings": {{"key": "value"}}
}}
],
"insights": [
{{
"type": "insight type",
"description": "what to look for"
}}
],
"feedback": "explanation of the analysis approach"
}}
Respond with ONLY the JSON object, no additional text or explanations.
"""
# Get analysis plan from AI
response = await self.mydom.callAi([
{"role": "system", "content": "You are a data analysis expert. Create detailed analysis plans. Respond with valid JSON only."},
{"role": "user", "content": analysisPrompt}
], produceUserAnswer=True)
# Extract JSON
jsonStart = response.find('{')
jsonEnd = response.rfind('}') + 1
if jsonStart >= 0 and jsonEnd > jsonStart:
plan = json.loads(response[jsonStart:jsonEnd])
return plan
else:
# Fallback plan
logger.warning(f"Not able creating analysis plan, generating fallback plan")
return {
"requiresAnalysis": True,
"analysisSteps": [
{
"step": "Basic data analysis",
"purpose": "Understand the data structure and content",
"techniques": ["summary statistics", "data visualization"],
"outputs": ["summary report", "basic visualizations"]
}
],
"visualizations": [
{
"type": "basic charts",
"purpose": "Show data distribution and relationships",
"settings": {}
}
],
"insights": [
{
"type": "basic insights",
"description": "Key findings from the data"
}
],
"feedback": f"I'll analyze the data and provide insights about {prompt}"
}
except Exception as e:
logger.warning(f"Error creating analysis plan: {str(e)}")
# Simple fallback plan
return {
"requiresAnalysis": True,
"analysisSteps": [
{
"step": "Basic data analysis",
"purpose": "Understand the data structure and content",
"techniques": ["summary statistics", "data visualization"],
"outputs": ["summary report", "basic visualizations"]
}
],
"visualizations": [
{
"type": "basic charts",
"purpose": "Show data distribution and relationships",
"settings": {}
}
],
"insights": [
{
"type": "basic insights",
"description": "Key findings from the data"
}
],
"feedback": f"I'll analyze the data and provide insights about {prompt}"
}
async def _createVisualization(self, datasets: Dict, prompt: str, outputLabel: str, async def _createVisualization(self, datasets: Dict, prompt: str, outputLabel: str,
analysisPlan: Dict, description: str) -> Dict: analysisPlan: Dict, description: str) -> Dict:
@ -770,6 +868,102 @@ class AgentAnalyst(AgentBase):
# Convert to base64 # Convert to base64
return base64.b64encode(imageData).decode('utf-8') return base64.b64encode(imageData).decode('utf-8')
async def _analyzeData(self, task: Dict[str, Any], analysisPlan: Dict[str, Any]) -> Dict[str, Any]:
"""
Analyze data based on the analysis plan.
Args:
task: Task dictionary with input documents and specifications
analysisPlan: Analysis plan from _createAnalysisPlan
Returns:
Analysis results dictionary
"""
try:
# Extract data from input documents
inputDocuments = task.get("inputDocuments", [])
datasets, documentContext = self._extractData(inputDocuments)
# Get task information
prompt = task.get("prompt", "")
outputSpecs = task.get("outputSpecifications", [])
# Analyze task requirements
analysisResults = await self._analyzeTask(prompt, documentContext, datasets, outputSpecs)
# Add datasets and context to results
analysisResults["datasets"] = datasets
analysisResults["documentContext"] = documentContext
return analysisResults
except Exception as e:
logger.error(f"Error analyzing data: {str(e)}", exc_info=True)
return {
"error": str(e),
"datasets": {},
"documentContext": ""
}
async def _createOutputDocuments(self, prompt: str, analysisResults: Dict[str, Any],
outputSpecs: List[Dict[str, Any]], analysisPlan: Dict[str, Any]) -> List[Dict[str, Any]]:
"""
Create output documents based on analysis results.
Args:
prompt: Original task prompt
analysisResults: Results from data analysis
outputSpecs: List of output specifications
analysisPlan: Analysis plan from _createAnalysisPlan
Returns:
List of document objects
"""
documents = []
datasets = analysisResults.get("datasets", {})
documentContext = analysisResults.get("documentContext", "")
# Process each output specification
for spec in outputSpecs:
outputLabel = spec.get("label", "")
outputDescription = spec.get("description", "")
# Determine format from filename
formatType = outputLabel.split('.')[-1].lower() if '.' in outputLabel else "txt"
try:
# Create appropriate document based on format
if formatType in ["png", "jpg", "jpeg", "svg"]:
# Visualization output
document = await self._createVisualization(
datasets, prompt, outputLabel, analysisPlan, outputDescription
)
elif formatType in ["csv", "json", "xlsx"]:
# Data document output
document = await self._createDataDocument(
datasets, prompt, outputLabel, analysisPlan, outputDescription
)
else:
# Text document output (markdown, html, text)
document = await self._createTextDocument(
datasets, documentContext, prompt, outputLabel, formatType,
analysisPlan, outputDescription
)
documents.append(document)
except Exception as e:
logger.error(f"Error creating output document {outputLabel}: {str(e)}", exc_info=True)
# Create error document
errorDoc = self.formatAgentDocumentOutput(
outputLabel,
f"Error creating document: {str(e)}",
"text/plain"
)
documents.append(errorDoc)
return documents
# Factory function for the Analyst agent # Factory function for the Analyst agent
def getAgentAnalyst(): def getAgentAnalyst():

View file

@ -33,8 +33,7 @@ class AgentCoach(AgentBase):
def setDependencies(self, mydom=None): def setDependencies(self, mydom=None):
"""Set external dependencies for the agent.""" """Set external dependencies for the agent."""
self.mydom = mydom
async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]: async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]:
""" """
Process a task by directly using AI to provide answers or content based on extracted data. Process a task by directly using AI to provide answers or content based on extracted data.

View file

@ -41,8 +41,7 @@ class AgentCoder(AgentBase):
def setDependencies(self, mydom=None): def setDependencies(self, mydom=None):
"""Set external dependencies for the agent.""" """Set external dependencies for the agent."""
self.mydom = mydom
async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]: async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]:
""" """
Process a task and perform code development/execution. Process a task and perform code development/execution.

View file

@ -30,8 +30,7 @@ class AgentDocumentation(AgentBase):
def setDependencies(self, mydom=None): def setDependencies(self, mydom=None):
"""Set external dependencies for the agent.""" """Set external dependencies for the agent."""
self.mydom = mydom
async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]: async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]:
""" """
Process a task by focusing on required outputs and using AI to generate them. Process a task by focusing on required outputs and using AI to generate them.

View file

@ -46,7 +46,6 @@ class AgentEmail(AgentBase):
def setDependencies(self, mydom=None): def setDependencies(self, mydom=None):
"""Set external dependencies for the agent.""" """Set external dependencies for the agent."""
self.mydom = mydom
self._loadConfiguration() self._loadConfiguration()
def _loadConfiguration(self): def _loadConfiguration(self):

View file

@ -52,7 +52,6 @@ class AgentWebcrawler(AgentBase):
def setDependencies(self, mydom=None): def setDependencies(self, mydom=None):
"""Set external dependencies for the agent.""" """Set external dependencies for the agent."""
self.mydom = mydom
async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]: async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]:
""" """
@ -68,6 +67,7 @@ class AgentWebcrawler(AgentBase):
# Extract task information # Extract task information
prompt = task.get("prompt", "") prompt = task.get("prompt", "")
outputSpecs = task.get("outputSpecifications", []) outputSpecs = task.get("outputSpecifications", [])
workflow = task.get("context", {}).get("workflow", {})
# Check AI service # Check AI service
if not self.mydom: if not self.mydom:
@ -77,7 +77,8 @@ class AgentWebcrawler(AgentBase):
} }
# Create research plan # Create research plan
self.mydom.logAdd(task["workflowId"], "Creating research plan...", level="info", progress=35) if workflow:
self.workflowManager.logAdd(workflow, "Creating research plan...", level="info", progress=35)
researchPlan = await self._createResearchPlan(prompt) researchPlan = await self._createResearchPlan(prompt)
# Check if this is truly a web research task # Check if this is truly a web research task
@ -88,11 +89,13 @@ class AgentWebcrawler(AgentBase):
} }
# Gather raw material through web research # Gather raw material through web research
self.mydom.logAdd(task["workflowId"], "Gathering research material...", level="info", progress=45) if workflow:
rawResults = await self._gatherResearchMaterial(researchPlan) self.workflowManager.logAdd(workflow, "Gathering research material...", level="info", progress=45)
rawResults = await self._gatherResearchMaterial(researchPlan, workflow)
# Format results into requested output documents # Format results into requested output documents
self.mydom.logAdd(task["workflowId"], "Creating output documents...", level="info", progress=55) if workflow:
self.workflowManager.logAdd(workflow, "Creating output documents...", level="info", progress=55)
documents = await self._createOutputDocuments( documents = await self._createOutputDocuments(
prompt, prompt,
rawResults, rawResults,
@ -191,12 +194,13 @@ class AgentWebcrawler(AgentBase):
"feedback": f"I'll conduct web research on '{prompt}' and gather relevant information." "feedback": f"I'll conduct web research on '{prompt}' and gather relevant information."
} }
async def _gatherResearchMaterial(self, researchPlan: Dict[str, Any]) -> List[Dict[str, Any]]: async def _gatherResearchMaterial(self, researchPlan: Dict[str, Any], workflow: Dict[str, Any]) -> List[Dict[str, Any]]:
""" """
Gather research material based on the research plan. Gather research material based on the research plan.
Args: Args:
researchPlan: Research plan dictionary researchPlan: Research plan dictionary
workflow: Current workflow object
Returns: Returns:
List of research results List of research results
@ -207,7 +211,8 @@ class AgentWebcrawler(AgentBase):
directUrls = researchPlan.get("directUrls", [])[:self.maxUrl] directUrls = researchPlan.get("directUrls", [])[:self.maxUrl]
for i, url in enumerate(directUrls): for i, url in enumerate(directUrls):
progress = 45 + int((i / len(directUrls)) * 5) # Progress from 45% to 50% progress = 45 + int((i / len(directUrls)) * 5) # Progress from 45% to 50%
self.mydom.logAdd(researchPlan.get("workflowId"), f"Processing direct URL {i+1}/{len(directUrls)}...", level="info", progress=progress) if hasattr(self, 'workflowManager') and self.workflowManager:
self.workflowManager.logAdd(workflow, f"Processing direct URL {i+1}/{len(directUrls)}...", level="info", progress=progress)
logger.info(f"Processing direct URL: {url}") logger.info(f"Processing direct URL: {url}")
try: try:
# Fetch and extract content # Fetch and extract content
@ -233,7 +238,8 @@ class AgentWebcrawler(AgentBase):
searchTerms = researchPlan.get("searchTerms", [])[:self.maxSearchTerms] searchTerms = researchPlan.get("searchTerms", [])[:self.maxSearchTerms]
for i, term in enumerate(searchTerms): for i, term in enumerate(searchTerms):
progress = 50 + int((i / len(searchTerms)) * 5) # Progress from 50% to 55% progress = 50 + int((i / len(searchTerms)) * 5) # Progress from 50% to 55%
self.mydom.logAdd(researchPlan.get("workflowId"), f"Searching term {i+1}/{len(searchTerms)}...", level="info", progress=progress) if hasattr(self, 'workflowManager') and self.workflowManager:
self.workflowManager.logAdd(workflow, f"Searching term {i+1}/{len(searchTerms)}...", level="info", progress=progress)
logger.info(f"Searching for: {term}") logger.info(f"Searching for: {term}")
try: try:
# Perform search # Perform search
@ -262,7 +268,7 @@ class AgentWebcrawler(AgentBase):
if len(allResults) >= self.maxResults: if len(allResults) >= self.maxResults:
break break
# Create summaries in parallel for all results # Create summaries for all results
allResults = await self._summarizeAllResults(allResults, researchPlan) allResults = await self._summarizeAllResults(allResults, researchPlan)
return allResults return allResults

View file

@ -17,6 +17,10 @@ pdfExtractorLoaded = False
officeExtractorLoaded = False officeExtractorLoaded = False
imageProcessorLoaded = False imageProcessorLoaded = False
class FileProcessingError(Exception):
"""Custom exception for file processing errors."""
pass
def getDocumentContents(fileMetadata: Dict[str, Any], fileContent: bytes) -> List[Dict[str, Any]]: def getDocumentContents(fileMetadata: Dict[str, Any], fileContent: bytes) -> List[Dict[str, Any]]:
""" """
Main function for extracting content from a file based on its MIME type. Main function for extracting content from a file based on its MIME type.

View file

@ -123,27 +123,50 @@ class GatewayInterface:
def _uam(self, table: str, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]: def _uam(self, table: str, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
""" """
Unified user access management function that filters data based on user privileges. Unified user access management function that filters data based on user privileges
and adds access control attributes.
Args: Args:
table: Name of the table table: Name of the table
recordset: Recordset to filter based on access rules recordset: Recordset to filter based on access rules
Returns: Returns:
Filtered recordset based on user privilege level Filtered recordset with access control attributes
""" """
userPrivilege = self.currentUser.get("privilege", "user") userPrivilege = self.currentUser.get("privilege", "user")
filtered_records = []
# Apply filtering based on privilege # Apply filtering based on privilege
if userPrivilege == "sysadmin": if userPrivilege == "sysadmin":
return recordset # System admins see all records filtered_records = recordset # System admins see all records
elif userPrivilege == "admin": elif userPrivilege == "admin":
# Admins see records in their mandate # Admins see records in their mandate
return [r for r in recordset if r.get("mandateId") == self.mandateId] filtered_records = [r for r in recordset if r.get("mandateId") == self.mandateId]
else: # Regular users else: # Regular users
# Users only see records they own within their mandate # Users only see records they own within their mandate
return [r for r in recordset filtered_records = [r for r in recordset
if r.get("mandateId") == self.mandateId and r.get("userId") == self.userId] if r.get("mandateId") == self.mandateId and r.get("userId") == 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 == "mandates":
record["_hideView"] = False # Everyone can view
record["_hideEdit"] = not self._canModify("mandates", record_id)
record["_hideDelete"] = not self._canModify("mandates", record_id)
elif table == "users":
record["_hideView"] = False # Everyone can view
record["_hideEdit"] = not self._canModify("users", record_id)
record["_hideDelete"] = not self._canModify("users", record_id)
else:
# Default access control for other tables
record["_hideView"] = False
record["_hideEdit"] = not self._canModify(table, record_id)
record["_hideDelete"] = not self._canModify(table, record_id)
return filtered_records
def _canModify(self, table: str, recordId: Optional[int] = None) -> bool: def _canModify(self, table: str, recordId: Optional[int] = None) -> bool:
""" """

View file

@ -162,35 +162,72 @@ class LucyDOMInterface:
def _uam(self, table: str, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]: def _uam(self, table: str, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
""" """
Unified user access management function that filters data based on user privileges. Unified user access management function that filters data based on user privileges
and adds access control attributes.
Args: Args:
table: Name of the table table: Name of the table
recordset: Recordset to filter based on access rules recordset: Recordset to filter based on access rules
Returns: Returns:
Filtered recordset based on user privilege level Filtered recordset with access control attributes
""" """
userPrivilege = self.currentUser.get("privilege", "user") userPrivilege = self.currentUser.get("privilege", "user")
filtered_records = []
# Apply filtering based on privilege # Apply filtering based on privilege
if userPrivilege == "sysadmin": if userPrivilege == "sysadmin":
return recordset # System admins see all records filtered_records = recordset # System admins see all records
elif userPrivilege == "admin": elif userPrivilege == "admin":
# Admins see records in their mandate # Admins see records in their mandate
return [r for r in recordset if r.get("mandateId") == self.mandateId] filtered_records = [r for r in recordset if r.get("mandateId") == self.mandateId]
else: # Regular users else: # Regular users
# To see all prompts from mandate 0 and own # To see all prompts from mandate 0 and own
if table == "prompts": if table == "prompts":
return [r for r in recordset if filtered_records = [r for r in recordset if
(r.get("mandateId") == self.mandateId and r.get("userId") == self.userId) (r.get("mandateId") == self.mandateId and r.get("userId") == self.userId)
or or
(r.get("mandateId") == 0) (r.get("mandateId") == 0)
] ]
# Users see only their records else:
return [r for r in recordset # Users see only their records
filtered_records = [r for r in recordset
if r.get("mandateId") == self.mandateId and r.get("userId") == self.userId] if r.get("mandateId") == self.mandateId and r.get("userId") == 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":
record["_hideView"] = False # Everyone can view
record["_hideEdit"] = not self._canModify("workflows", record_id)
record["_hideDelete"] = not self._canModify("workflows", record_id)
elif table == "workflowMessages":
record["_hideView"] = False # Everyone can view
record["_hideEdit"] = not self._canModify("workflows", record.get("workflowId"))
record["_hideDelete"] = not self._canModify("workflows", record.get("workflowId"))
elif table == "workflowLogs":
record["_hideView"] = False # Everyone can view
record["_hideEdit"] = not self._canModify("workflows", record.get("workflowId"))
record["_hideDelete"] = not self._canModify("workflows", record.get("workflowId"))
else:
# Default access control for other tables
record["_hideView"] = False
record["_hideEdit"] = not self._canModify(table, record_id)
record["_hideDelete"] = not self._canModify(table, record_id)
return filtered_records
def _canModify(self, table: str, recordId: Optional[int] = None) -> bool: def _canModify(self, table: str, recordId: Optional[int] = None) -> bool:
""" """
Checks if the current user can modify (create/update/delete) records in a table. Checks if the current user can modify (create/update/delete) records in a table.

View file

@ -32,6 +32,7 @@ class AgentBase:
self.description = "Basic agent functionality" self.description = "Basic agent functionality"
self.capabilities = [] self.capabilities = []
self.mydom = None self.mydom = None
self.workflowManager = None # Will be set by workflow manager
def setDependencies(self, mydom=None): def setDependencies(self, mydom=None):
"""Set external dependencies for the agent.""" """Set external dependencies for the agent."""
@ -58,11 +59,16 @@ class AgentBase:
Args: Args:
task: A dictionary containing: task: A dictionary containing:
- taskId: Unique ID for this task - taskId: Unique ID for this task
- workflowId: ID of the parent workflow (optional) - workflowId: ID of the parent workflow
- prompt: The main instruction for the agent - prompt: The main instruction for the agent
- inputDocuments: List of document objects to process - inputDocuments: List of document objects to process
- outputSpecifications: List of required output documents - outputSpecifications: List of required output documents
- context: Additional contextual information - context: Additional contextual information including:
- workflow: The complete workflow object
- workflowRound: Current workflow round
- agentType: Type of agent
- timestamp: Task timestamp
- language: User language
Returns: Returns:
A dictionary containing: A dictionary containing:
@ -208,6 +214,11 @@ class AgentRegistry:
self.mydom = mydom self.mydom = mydom
self.updateAgentDependencies() self.updateAgentDependencies()
def setWorkflowManager(self, workflowManager):
"""Set the workflow manager reference for all agents."""
for agent in self.agents.values():
agent.workflowManager = workflowManager
def updateAgentDependencies(self): def updateAgentDependencies(self):
"""Update dependencies for all registered agents.""" """Update dependencies for all registered agents."""
for agentId, agent in self.agents.items(): for agentId, agent in self.agents.items():
@ -239,8 +250,8 @@ class AgentRegistry:
if agentIdentifier in self.agents: if agentIdentifier in self.agents:
agent = self.agents[agentIdentifier] agent = self.agents[agentIdentifier]
# Ensure the agent has the AI service # Ensure the agent has the AI service
if hasattr(agent, 'setDependencies') and self.mydom: if self.mydom:
agent.setDependencies(mydom=self.mydom) agent.mydom = self.mydom
return agent return agent
logger.error(f"Agent with identifier '{agentIdentifier}' not found") logger.error(f"Agent with identifier '{agentIdentifier}' not found")
return None return None

View file

@ -12,6 +12,7 @@ import uuid
import base64 import base64
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Dict, Any, List, Optional, Union, Tuple from typing import Dict, Any, List, Optional, Union, Tuple
import time
from modules.mimeUtils import isTextMimeType, determineContentEncoding from modules.mimeUtils import isTextMimeType, determineContentEncoding
@ -58,6 +59,7 @@ class WorkflowManager:
self.mydom = domInterface(mandateId, userId) self.mydom = domInterface(mandateId, userId)
self.agentRegistry = getAgentRegistry() self.agentRegistry = getAgentRegistry()
self.agentRegistry.setMydom(self.mydom) self.agentRegistry.setMydom(self.mydom)
self.agentRegistry.setWorkflowManager(self) # Set self as workflow manager for all agents
### Workflow State Machine Implementation ### Workflow State Machine Implementation
@ -132,6 +134,7 @@ class WorkflowManager:
Returns: Returns:
Updated workflow with processing results Updated workflow with processing results
""" """
startTime = time.time()
try: try:
# State 3: User Message Processing # State 3: User Message Processing
self.checkExitCriteria(workflow) self.checkExitCriteria(workflow)
@ -161,8 +164,42 @@ class WorkflowManager:
} }
self.messageAdd(workflow, responseMessage) self.messageAdd(workflow, responseMessage)
self.logAdd(workflow, f"Planned outputs: {len(objFinalDocuments)} documents", level="info", progress=20) # Add detailed log entry about the task plan
self.logAdd(workflow, f"Work plan created with {len(objWorkplan)} steps", level="info", progress=25) taskPlanLog = "Input: "
if objFinalDocuments:
taskPlanLog += ", ".join(objFinalDocuments) + "<br>"
else:
taskPlanLog += "No input files<br>"
# Work Plan Steps
for i, task in enumerate(objWorkplan, 1):
agentName = task.get("agent", "unknown")
taskPlanLog += f"{i}. Agent {agentName}<br>"
# Input Documents
inputDocs = task.get("inputDocuments", [])
if inputDocs:
inputLabels = [doc.get("label", "unknown") for doc in inputDocs]
taskPlanLog += f"- Input: {', '.join(inputLabels)}<br>"
# Task Prompt
prompt = task.get('prompt', 'No prompt')
taskPlanLog += f"- Task: {prompt}<br>"
# Output Documents
outputDocs = task.get("outputDocuments", [])
if outputDocs:
outputLabels = [doc.get("label", "unknown") for doc in outputDocs]
taskPlanLog += f"- Output: {', '.join(outputLabels)}<br>"
# Final Results
taskPlanLog += "Result: "
if objFinalDocuments:
taskPlanLog += ", ".join(objFinalDocuments)
else:
taskPlanLog += "No result files"
self.logAdd(workflow, taskPlanLog, level="info", progress=25)
# State 5: Agent Execution # State 5: Agent Execution
objResults = [] objResults = []
@ -199,6 +236,10 @@ class WorkflowManager:
self.checkExitCriteria(workflow) self.checkExitCriteria(workflow)
self.workflowFinish(workflow) self.workflowFinish(workflow)
# Update processing time
endTime = time.time()
workflow["dataStats"]["processingTime"] = endTime - startTime
return workflow return workflow
except Exception as e: except Exception as e:
@ -207,10 +248,15 @@ class WorkflowManager:
workflow["status"] = "failed" workflow["status"] = "failed"
workflow["lastActivity"] = datetime.now().isoformat() workflow["lastActivity"] = datetime.now().isoformat()
# Update processing time even on error
endTime = time.time()
workflow["dataStats"]["processingTime"] = endTime - startTime
# Update in database # Update in database
self.mydom.updateWorkflow(workflow["id"], { self.mydom.updateWorkflow(workflow["id"], {
"status": "failed", "status": "failed",
"lastActivity": workflow["lastActivity"] "lastActivity": workflow["lastActivity"],
"dataStats": workflow["dataStats"]
}) })
self.logAdd(workflow, f"Workflow failed: {str(e)}", level="error", progress=100) self.logAdd(workflow, f"Workflow failed: {str(e)}", level="error", progress=100)
@ -241,7 +287,12 @@ class WorkflowManager:
"messages": [], # Empty list - will be filled with references "messages": [], # Empty list - will be filled with references
"messageIds": [], # Initialize empty messageIds list "messageIds": [], # Initialize empty messageIds list
"logs": [], "logs": [],
"dataStats": {}, "dataStats": {
"bytesSent": 0,
"bytesReceived": 0,
"tokensUsed": 0,
"processingTime": 0.0
},
"currentRound": 1, "currentRound": 1,
"status": "running", "status": "running",
"lastActivity": currentTime, "lastActivity": currentTime,
@ -287,11 +338,24 @@ class WorkflowManager:
else: else:
workflow["currentRound"] = 1 workflow["currentRound"] = 1
# Ensure dataStats exists with correct field names
if "dataStats" not in workflow:
workflow["dataStats"] = {
"bytesSent": 0,
"bytesReceived": 0,
"tokensUsed": 0,
"processingTime": 0.0
}
elif "tokenCount" in workflow["dataStats"]:
# Convert old tokenCount to tokensUsed if needed
workflow["dataStats"]["tokensUsed"] = workflow["dataStats"].pop("tokenCount", 0)
# Update in database - only the relevant workflow fields # Update in database - only the relevant workflow fields
workflowUpdate = { workflowUpdate = {
"status": workflow["status"], "status": workflow["status"],
"lastActivity": workflow["lastActivity"], "lastActivity": workflow["lastActivity"],
"currentRound": workflow["currentRound"] "currentRound": workflow["currentRound"],
"dataStats": workflow["dataStats"] # Include updated dataStats
} }
self.mydom.updateWorkflow(workflowId, workflowUpdate) self.mydom.updateWorkflow(workflowId, workflowUpdate)
@ -474,6 +538,9 @@ JSON_OUTPUT = {{
return [] return []
agentLabel = agent.label agentLabel = agent.label
# Set workflow manager reference on the agent
agent.workflowManager = self
# Log the current step # Log the current step
outputLabels = [] outputLabels = []
for doc in task.get("outputDocuments", []): for doc in task.get("outputDocuments", []):
@ -498,7 +565,7 @@ JSON_OUTPUT = {{
# Prepare input documents for the agent # Prepare input documents for the agent
inputDocuments = await self.prepareAgentInputDocuments(task.get('inputDocuments', []), workflow) inputDocuments = await self.prepareAgentInputDocuments(task.get('inputDocuments', []), workflow)
# Create a standardized task object for the agent as per state machine spec # Create a standardized task object for the agent as per state machine spec
agentTask = { agentTask = {
"taskId": str(uuid.uuid4()), "taskId": str(uuid.uuid4()),
@ -507,20 +574,61 @@ JSON_OUTPUT = {{
"inputDocuments": inputDocuments, "inputDocuments": inputDocuments,
"outputSpecifications": outputSpecs, "outputSpecifications": outputSpecs,
"context": { "context": {
"workflow": workflow, # Add the complete workflow object
"workflowRound": workflow.get("currentRound", 1), "workflowRound": workflow.get("currentRound", 1),
"agentType": agentName, "agentType": agentName,
"timestamp": datetime.now().isoformat(), "timestamp": datetime.now().isoformat(),
"language": self.mydom.userLanguage # Pass language to agent "language": self.mydom.userLanguage # Pass language to agent
} }
} }
# Execute the agent with the standardized task # Execute the agent with the standardized task
try: try:
# Process the task using the agent's standardized interface # Process the task using the agent's standardized interface
logger.debug("TASK: "+self.parseJson2text(agentTask)) logger.debug("TASK: "+self.parseJson2text(agentTask))
logger.debug(f"Agent '{agentName}' AI service available: {agent.mydom is not None}") logger.debug(f"Agent '{agentName}' AI service available: {agent.mydom is not None}")
# Calculate bytes sent before processing
bytesSent = len(json.dumps(agentTask).encode('utf-8'))
for doc in inputDocuments:
if doc.get('data'):
bytesSent += len(doc['data'].encode('utf-8'))
for content in doc.get('contents', []):
if content.get('data'):
bytesSent += len(content['data'].encode('utf-8'))
# Process the task
startTime = time.time()
agentResults = await agent.processTask(agentTask) agentResults = await agent.processTask(agentTask)
endTime = time.time()
# Calculate bytes received
bytesReceived = len(json.dumps(agentResults).encode('utf-8'))
for doc in agentResults.get('documents', []):
if doc.get('content'):
bytesReceived += len(doc['content'].encode('utf-8'))
# Calculate tokens used (now using bytes)
tokensUsed = bytesSent + bytesReceived
# Update workflow statistics
if 'dataStats' not in workflow:
workflow['dataStats'] = {
'bytesSent': 0,
'bytesReceived': 0,
'tokensUsed': 0,
'processingTime': 0
}
workflow['dataStats']['bytesSent'] += bytesSent
workflow['dataStats']['bytesReceived'] += bytesReceived
workflow['dataStats']['tokensUsed'] += tokensUsed
workflow['dataStats']['processingTime'] += (endTime - startTime)
# Update in database
self.mydom.updateWorkflow(workflow["id"], {
"dataStats": workflow['dataStats']
})
logger.debug(f"Agent '{agentName}' completed task. RESULT: {self.parseJson2text(agentResults)}") logger.debug(f"Agent '{agentName}' completed task. RESULT: {self.parseJson2text(agentResults)}")
@ -712,6 +820,38 @@ filesDelivered = {self.parseJson2text(matchingDocuments)}
messageObject = self.messageAdd(workflow, messageObject) messageObject = self.messageAdd(workflow, messageObject)
logger.debug(f"message_user = {self.parseJson2text(messageObject)}.") logger.debug(f"message_user = {self.parseJson2text(messageObject)}.")
# Update statistics for user input
if role == "user":
# Calculate bytes sent
bytesSent = len(messageContent.encode('utf-8'))
for doc in additionalFiles:
if doc.get('data'):
bytesSent += len(doc['data'].encode('utf-8'))
for content in doc.get('contents', []):
if content.get('data'):
bytesSent += len(content['data'].encode('utf-8'))
# Calculate tokens used (now using bytes)
tokensUsed = bytesSent
# Update workflow statistics
if 'dataStats' not in workflow:
workflow['dataStats'] = {
'bytesSent': 0,
'bytesReceived': 0,
'tokensUsed': 0,
'processingTime': 0
}
workflow['dataStats']['bytesSent'] += bytesSent
workflow['dataStats']['tokensUsed'] += tokensUsed
# Update in database
self.mydom.updateWorkflow(workflow["id"], {
"dataStats": workflow['dataStats']
})
return messageObject return messageObject
async def processFileIds(self, fileIds: List[int]) -> List[Dict[str, Any]]: async def processFileIds(self, fileIds: List[int]) -> List[Dict[str, Any]]:
@ -954,6 +1094,7 @@ filesDelivered = {self.parseJson2text(matchingDocuments)}
""" """
Adds a message to the workflow and updates lastActivity. Adds a message to the workflow and updates lastActivity.
Saves the message in the database and updates the workflow with references. Saves the message in the database and updates the workflow with references.
Also updates statistics for the message.
Args: Args:
workflow: Workflow object workflow: Workflow object
@ -991,6 +1132,35 @@ filesDelivered = {self.parseJson2text(matchingDocuments)}
# Set status if not present # Set status if not present
if "status" not in message: if "status" not in message:
message["status"] = "step" message["status"] = "step"
# Calculate statistics for the message
bytesSent = len(message.get("content", "").encode('utf-8'))
for doc in message.get("documents", []):
if doc.get("data"):
bytesSent += len(doc["data"].encode('utf-8'))
for content in doc.get("contents", []):
if content.get("data"):
bytesSent += len(content["data"].encode('utf-8'))
# Calculate tokens used (now using bytes)
tokensUsed = bytesSent
# Update workflow statistics
if "dataStats" not in workflow:
workflow["dataStats"] = {
"bytesSent": 0,
"bytesReceived": 0,
"tokensUsed": 0,
"processingTime": 0
}
# Update statistics based on message role
if message["role"] == "user":
workflow["dataStats"]["bytesSent"] += bytesSent
workflow["dataStats"]["tokensUsed"] += tokensUsed
else: # assistant messages
workflow["dataStats"]["bytesReceived"] += bytesSent
workflow["dataStats"]["tokensUsed"] += tokensUsed
# Add message to workflow # Add message to workflow
workflow["messages"].append(message) workflow["messages"].append(message)
@ -1008,15 +1178,39 @@ filesDelivered = {self.parseJson2text(matchingDocuments)}
# Save to database - first the message itself # Save to database - first the message itself
self.mydom.createWorkflowMessage(message) self.mydom.createWorkflowMessage(message)
# Then save the workflow with updated references # Then save the workflow with updated references and statistics
workflowUpdate = { workflowUpdate = {
"lastActivity": currentTime, "lastActivity": currentTime,
"messageIds": workflow["messageIds"] # Update the messageIds field "messageIds": workflow["messageIds"],
"dataStats": workflow["dataStats"] # Include updated statistics
} }
self.mydom.updateWorkflow(workflow["id"], workflowUpdate) self.mydom.updateWorkflow(workflow["id"], workflowUpdate)
return message return message
def _trimDataInJson(self, jsonObj: Any) -> Any:
"""
Trims the data attribute in JSON objects while preserving other content.
Args:
jsonObj: JSON object to process
Returns:
Processed JSON object with trimmed data attribute
"""
if isinstance(jsonObj, dict):
# Create a copy to avoid modifying the original
result = jsonObj.copy()
if 'data' in result:
# Trim data attribute if it's a string
if isinstance(result['data'], str):
result['data'] = result['data'][:100] + '...'
# If it's a dict or list, convert to string and trim
else:
result['data'] = str(result['data'])[:100] + '...'
return result
return jsonObj
def logAdd(self, workflow: Dict[str, Any], message: str, level: str = "info", def logAdd(self, workflow: Dict[str, Any], message: str, level: str = "info",
progress: Optional[int] = None) -> str: progress: Optional[int] = None) -> str:
""" """
@ -1045,11 +1239,24 @@ filesDelivered = {self.parseJson2text(matchingDocuments)}
# Set agentName from global settings # Set agentName from global settings
agentName = GLOBAL_WORKFLOW_LABELS.get("systemName", "unknown") agentName = GLOBAL_WORKFLOW_LABELS.get("systemName", "unknown")
# Process message if it contains JSON
processedMessage = message
try:
if isinstance(message, str) and ("{" in message or "[" in message):
# Try to parse as JSON
jsonObj = json.loads(message)
# Trim data attribute if present
processedJson = self._trimDataInJson(jsonObj)
processedMessage = json.dumps(processedJson)
except json.JSONDecodeError:
# If parsing fails, use original message
pass
# Create log entry # Create log entry
logEntry = { logEntry = {
"id": logId, "id": logId,
"workflowId": workflow["id"], "workflowId": workflow["id"],
"message": message, "message": processedMessage,
"type": level, "type": level,
"timestamp": datetime.now().isoformat(), "timestamp": datetime.now().isoformat(),
"agentName": agentName, "agentName": agentName,
@ -1068,11 +1275,11 @@ filesDelivered = {self.parseJson2text(matchingDocuments)}
# Also log in logger # Also log in logger
if level == "info": if level == "info":
logger.info(f"Workflow {workflow['id']}: {message}") logger.info(f"Workflow {workflow['id']}: {processedMessage}")
elif level == "warning": elif level == "warning":
logger.warning(f"Workflow {workflow['id']}: {message}") logger.warning(f"Workflow {workflow['id']}: {processedMessage}")
elif level == "error": elif level == "error":
logger.error(f"Workflow {workflow['id']}: {message}") logger.error(f"Workflow {workflow['id']}: {processedMessage}")
return logId return logId

View file

@ -1,9 +1,14 @@
....................... TASKS ....................... TASKS
agentDocumentation delivers a ".docx" file, but the content is a ".md" text markup file
access management to extract into separate modules "lucydomAccess.py" and "gatewayAccess.py". Here to move the functions from "*Interface.py", which define what access which role has.
check data extraction tabelle im pdf
Check data extraction of types! Check data extraction of types!
final message with 100% to give
@ -30,7 +35,7 @@ PRIO3:
Tools to transfer incl funds: Tools to transfer incl funds:
- Google SERPAPI (shelly) - Google SERPAPI (shelly)
- Anthropic Claude (valueon + shelly) - Anthropic Claude (valueon + shelly)
- - Cursor Pro
----------------------- DONE ----------------------- DONE