basic workflow running

This commit is contained in:
ValueOn AG 2025-04-23 09:01:52 +02:00
parent 1711abf100
commit 811ed72615
19 changed files with 372 additions and 271 deletions

3
app.py
View file

@ -28,7 +28,8 @@ from modules.auth import (
# Import models - import generically for INITIALIZATION, even if dummy!
import modules.gateway_model as gateway_model
import modules.lucydom_interface as lucydom_model
#from modules.lucydom_interface import get_lucydom_interface as dom_interface
def init_logging():

View file

@ -52,3 +52,4 @@ Agent_Webcrawler_MAX_SEARCH_RESULTS = 5
# Agent Coder configuration
Agent_Coder_INSTALL_TIMEOUT = 180
Agent_Coder_EXECUTION_TIMEOUT = 60
Agent_Coder_EXECUTION_RETRY = 5

View file

@ -37,7 +37,6 @@ class ChatService:
"Content-Type": "application/json"
}
)
logger.info(f"OpenAI Connector initialized with model: {self.model_name}")
async def call_api(self, messages: List[Dict[str, Any]], temperature: float = None, max_tokens: int = None) -> str:

View file

@ -14,14 +14,25 @@ from datetime import datetime
from typing import Dict, Any, List, Optional, Union
# Required imports
from connectors.connector_aichat_openai import ChatService
from modules.chat_registry import get_agent_registry
from modules.lucydom_interface import get_lucydom_interface, GLOBAL_SETTINGS
from modules.lucydom_interface import get_lucydom_interface as dom_interface
from modules.chat_content_extraction import get_document_contents
# Configure logger
logger = logging.getLogger(__name__)
# Global settings for the workflow management
GLOBAL_WorkflowLabels = {
"system_name": "AI Assistant", # Default system name for logs
"workflow_status_messages": {
"init": "Workflow initialized",
"running": "Running workflow",
"waiting": "Waiting for input",
"completed": "Workflow completed",
"error": "Error in workflow"
}
}
class ChatManager:
"""
Manages the processing of chat requests, agent execution, and
@ -38,13 +49,9 @@ class ChatManager:
"""
self.mandate_id = mandate_id
self.user_id = user_id
self.ai_service = ChatService()
self.lucy_interface = get_lucydom_interface(mandate_id, user_id)
self.mydom = dom_interface(mandate_id, user_id)
self.agent_registry = get_agent_registry()
self.agent_registry.set_ai_service(self.ai_service)
# Set AI service in lucy interface for language support
self.lucy_interface.set_ai_service(self.ai_service)
self.agent_registry.set_mydom(self.mydom)
### Chat Management
@ -73,9 +80,9 @@ class ChatManager:
obj_workplan = project_manager_response.get("obj_workplan", [])
obj_user_response = project_manager_response.get("obj_user_response", "")
# Get detected language and set it in the lucy interface
# Get detected language and set it in the mydom interface
user_language = project_manager_response.get("user_language", "en")
self.lucy_interface.set_user_language(user_language)
self.mydom.set_user_language(user_language)
# 4. Save the response as a message in the workflow and add log entries
response_message = {
@ -203,7 +210,7 @@ JSON_OUTPUT = {{
}}
# Multiple agent tasks can be added here and should build logically on each other
],
"obj_user_response": "Information to the user about how his request will be solved.",
"obj_user_response": "Information to the user about how his request will be solved, in the language of the user's request.",
"user_language": "en" # Language code (e.g., en, de, fr, es) based on the user's request
}}
@ -227,9 +234,9 @@ JSON_OUTPUT = {{
4. If you use label for an existing file
"""
# Call the AI service through lucy_interface for language support
# Call the AI service through mydom for language support
logger.debug(f"Planning prompt: {prompt}")
project_manager_output = await self.lucy_interface.call_ai([
project_manager_output = await self.mydom.call_ai([
{
"role": "system",
"content": "You are an experienced project manager who analyzes user requests and creates work plans. You pay very careful attention to ensure that all document dependencies are correct and that no non-existent documents are defined as inputs. The output follows strictly the specified format."
@ -318,8 +325,8 @@ JSON_OUTPUT = {{
matching_documents.append(doc_ref)
break
# Use the lucy_interface for language-aware AI calls
final_prompt = await self.lucy_interface.call_ai([
# Use the mydom for language-aware AI calls
final_prompt = await self.mydom.call_ai([
{"role": "system", "content": "You are a project manager, who delivers results to a user."},
{"role": "user", "content": f"""
Give the final short feedback to the user with reference to the initial statement (obj_user_response). Provide a list of delivered files (files_delivered). If in the list of delivered files (files_delivered) some files from the original list (files_promised) are not available, then just give a comment on this, otherwise task is completed.
@ -359,7 +366,7 @@ JSON_OUTPUT = {{
"""
current_time = datetime.now().isoformat()
if workflow_id is None or not self.lucy_interface.get_workflow(workflow_id):
if workflow_id is None or not self.mydom.get_workflow(workflow_id):
# Create new workflow
new_workflow_id = str(uuid.uuid4()) if workflow_id is None else workflow_id
workflow = {
@ -390,13 +397,13 @@ JSON_OUTPUT = {{
"last_activity": workflow["last_activity"],
"message_ids": workflow["message_ids"] # Include message_ids
}
self.lucy_interface.create_workflow(workflow_db)
self.mydom.create_workflow(workflow_db)
self.log_add(workflow, GLOBAL_SETTINGS["workflow_status_messages"]["init"], level="info", progress=0)
self.log_add(workflow, GLOBAL_WorkflowLabels["workflow_status_messages"]["init"], level="info", progress=0)
return workflow
else:
# Load existing workflow
workflow = self.lucy_interface.load_workflow_state(workflow_id)
workflow = self.mydom.load_workflow_state(workflow_id)
# Ensure message_ids exists
if "message_ids" not in workflow:
@ -404,7 +411,7 @@ JSON_OUTPUT = {{
workflow["message_ids"] = [msg["id"] for msg in workflow.get("messages", [])]
# Update in database
self.lucy_interface.update_workflow(workflow_id, {"message_ids": workflow["message_ids"]})
self.mydom.update_workflow(workflow_id, {"message_ids": workflow["message_ids"]})
# Update status and increment round counter
workflow["status"] = "running"
@ -422,9 +429,9 @@ JSON_OUTPUT = {{
"last_activity": workflow["last_activity"],
"current_round": workflow["current_round"]
}
self.lucy_interface.update_workflow(workflow_id, workflow_update)
self.mydom.update_workflow(workflow_id, workflow_update)
self.log_add(workflow, GLOBAL_SETTINGS["workflow_status_messages"]["running"], level="info", progress=0)
self.log_add(workflow, GLOBAL_WorkflowLabels["workflow_status_messages"]["running"], level="info", progress=0)
return workflow
def workflow_finish(self, workflow: Dict[str, Any]) -> Dict[str, Any]:
@ -448,9 +455,9 @@ JSON_OUTPUT = {{
workflow["last_activity"] = workflow_update["last_activity"]
# Save workflow state to database - only relevant fields, not the messages list
self.lucy_interface.update_workflow(workflow["id"], workflow_update)
self.mydom.update_workflow(workflow["id"], workflow_update)
self.log_add(workflow, GLOBAL_SETTINGS["workflow_status_messages"]["completed"], level="info", progress=100)
self.log_add(workflow, GLOBAL_WorkflowLabels["workflow_status_messages"]["completed"], level="info", progress=100)
return workflow
async def workflow_summarize(self, workflow: Dict[str, Any], message_user: Dict[str, Any]) -> str:
@ -583,8 +590,8 @@ JSON_OUTPUT = {{
# Extract and provide only the relevant information as requested.
"""
# Call the AI service through lucy_interface for language support
processed_data = await self.lucy_interface.call_ai([
# Call the AI service through mydom for language support
processed_data = await self.mydom.call_ai([
{"role": "system", "content": "You are a document processing assistant. Extract only the relevant information as requested."},
{"role": "user", "content": ai_prompt}
])
@ -660,7 +667,7 @@ JSON_OUTPUT = {{
"workflow_round": workflow.get("current_round", 1),
"agent_type": agent_name,
"timestamp": datetime.now().isoformat(),
"language": self.lucy_interface.user_language # Pass language to agent
"language": self.mydom.user_language # Pass language to agent
}
}
@ -668,7 +675,7 @@ JSON_OUTPUT = {{
try:
# Process the task using the agent's standardized interface
logger.debug("TASK: "+self.parse_json2text(agent_task))
logger.debug(f"Agent '{agent_name}' AI service available: {agent.ai_service is not None}")
logger.debug(f"Agent '{agent_name}' AI service available: {agent.mydom is not None}")
agent_results = await agent.process_task(agent_task)
@ -753,7 +760,7 @@ JSON_OUTPUT = {{
file_content = content
# Save file to database
file_meta = self.lucy_interface.save_uploaded_file(file_content, label)
file_meta = self.mydom.save_uploaded_file(file_content, label)
if file_meta and "id" in file_meta:
file_id = file_meta["id"]
@ -827,14 +834,14 @@ JSON_OUTPUT = {{
workflow["last_activity"] = current_time
# Save to database - first the message itself
self.lucy_interface.create_workflow_message(message)
self.mydom.create_workflow_message(message)
# Then save the workflow with updated references
workflow_update = {
"last_activity": current_time,
"message_ids": workflow["message_ids"] # Update the message_ids field
}
self.lucy_interface.update_workflow(workflow["id"], workflow_update)
self.mydom.update_workflow(workflow["id"], workflow_update)
return message
@ -853,8 +860,8 @@ JSON_OUTPUT = {{
content = message.get("content", "")
try:
# Use the lucy_interface for language-aware AI calls
content_summary = await self.lucy_interface.call_ai([
# Use the mydom for language-aware AI calls
content_summary = await self.mydom.call_ai([
{"role": "system", "content": f"You are a chat message summarizer. Create a very concise summary (2-3 sentences, maximum 300 characters)"},
{"role": "user", "content": content}
])
@ -890,8 +897,8 @@ JSON_OUTPUT = {{
is_text = content.get("metadata", {}).get("is_text", False)
try:
# Use the lucy_interface for language-aware AI calls
summary = await self.lucy_interface.call_ai([
# Use the mydom for language-aware AI calls
summary = await self.mydom.call_ai([
{"role": "system", "content": "You are a content summarizer. Create very concise summary (1-2 sentences, maximum 200 characters) about this file."},
{"role": "user", "content": f"Summarize this {content_type} content briefly:\n\n{data}"}
])
@ -921,7 +928,7 @@ JSON_OUTPUT = {{
for file_id in file_ids:
try:
# Check if the file exists
file = self.lucy_interface.get_file(file_id)
file = self.mydom.get_file(file_id)
if not file:
logger.warning(f"File with ID {file_id} not found")
continue
@ -932,7 +939,7 @@ JSON_OUTPUT = {{
continue
# Load file content
file_content = self.lucy_interface.get_file_data(file_id)
file_content = self.mydom.get_file_data(file_id)
if file_content is None:
logger.warning(f"No content found for file with ID {file_id}")
continue
@ -1047,7 +1054,7 @@ JSON_OUTPUT = {{
data = data.encode('utf-8')
# Save file in the database
file_meta = self.lucy_interface.save_uploaded_file(data, name)
file_meta = self.mydom.save_uploaded_file(data, name)
if file_meta and "id" in file_meta:
# Update the Document with the File-ID
document["file_id"] = file_meta["id"]
@ -1123,7 +1130,7 @@ JSON_OUTPUT = {{
workflow_status = workflow.get("status", "running")
# Set agent_name from global settings
agent_name = GLOBAL_SETTINGS.get("system_name", "AI Assistant")
agent_name = GLOBAL_WorkflowLabels.get("system_name", "AI Assistant")
# Create log entry
log_entry = {
@ -1144,7 +1151,7 @@ JSON_OUTPUT = {{
workflow["logs"].append(log_entry)
# Save in database
self.lucy_interface.create_workflow_log(log_entry)
self.mydom.create_workflow_log(log_entry)
# Also log in logger
if level == "info":

View file

@ -35,9 +35,9 @@ class AgentAnalyst(AgentBase):
# Set default visualization settings
plt.style.use('seaborn-v0_8-whitegrid')
def set_dependencies(self, ai_service=None):
def set_dependencies(self, mydom=None):
"""Set external dependencies for the agent."""
self.ai_service = ai_service
self.mydom = mydom
async def process_task(self, task: Dict[str, Any]) -> Dict[str, Any]:
"""
@ -56,7 +56,7 @@ class AgentAnalyst(AgentBase):
output_specs = task.get("output_specifications", [])
# Check AI service
if not self.ai_service:
if not self.mydom:
return {
"feedback": "The Analyst agent requires an AI service to function.",
"documents": []
@ -105,9 +105,9 @@ class AgentAnalyst(AgentBase):
documents.append(document)
# Generate feedback
feedback = f"Analysis complete. Created {len(documents)} documents based on your requirements."
feedback = f"{analysis_plan.get('analysis_approach')}"
if analysis_plan.get("key_insights"):
feedback += f"\n\nKey insights: {analysis_plan.get('key_insights')}"
feedback += f"\n\n{analysis_plan.get('key_insights')}"
return {
"feedback": feedback,
@ -251,12 +251,11 @@ class AgentAnalyst(AgentBase):
Only return valid JSON. No preamble or explanations.
"""
try:
response = await self.ai_service.call_api([
response = await self.mydom.call_ai([
{"role": "system", "content": "You are a data analysis expert. Respond with valid JSON only."},
{"role": "user", "content": analysis_prompt}
])
], produce_user_answer = True)
# Extract JSON from response
json_start = response.find('{')
@ -375,10 +374,10 @@ class AgentAnalyst(AgentBase):
try:
# Get visualization code from AI
viz_code = await self.ai_service.call_api([
viz_code = await self.mydom.call_ai([
{"role": "system", "content": "You are a data visualization expert. Provide only executable Python code."},
{"role": "user", "content": viz_prompt}
])
], produce_user_answer = True)
# Clean code
viz_code = viz_code.replace("```python", "").replace("```", "").strip()
@ -504,10 +503,10 @@ class AgentAnalyst(AgentBase):
try:
# Get data processing code from AI
data_code = await self.ai_service.call_api([
data_code = await self.mydom.call_ai([
{"role": "system", "content": "You are a data processing expert. Provide only executable Python code."},
{"role": "user", "content": data_prompt}
])
], produce_user_answer = True)
# Clean code
data_code = data_code.replace("```python", "").replace("```", "").strip()
@ -630,10 +629,10 @@ class AgentAnalyst(AgentBase):
try:
# Get document content from AI
document_content = await self.ai_service.call_api([
document_content = await self.mydom.call_ai([
{"role": "system", "content": f"You are a data analysis expert creating a {format_type} document."},
{"role": "user", "content": analysis_prompt}
])
], produce_user_answer = True)
# Clean HTML or Markdown if needed
if format_type in ["md", "markdown"] and not document_content.strip().startswith("#"):

View file

@ -33,12 +33,13 @@ class AgentCoder(AgentBase):
]
# Executor settings
self.executor_timeout = APP_CONFIG.get("Agent_Coder_EXECUTION_TIMEOUT") # seconds
self.executor_timeout = int(APP_CONFIG.get("Agent_Coder_EXECUTION_TIMEOUT")) # seconds
self.execution_retry_limit = int(APP_CONFIG.get("Agent_Coder_EXECUTION_RETRY")) # max retries
self.temp_dir = None
def set_dependencies(self, ai_service=None):
def set_dependencies(self, mydom=None):
"""Set external dependencies for the agent."""
self.ai_service = ai_service
self.mydom = mydom
async def process_task(self, task: Dict[str, Any]) -> Dict[str, Any]:
"""
@ -58,7 +59,7 @@ class AgentCoder(AgentBase):
output_specs = task.get("output_specifications", [])
# Check if AI service is available
if not self.ai_service:
if not self.mydom:
logger.error("No AI service configured for the Coder agent")
return {
"feedback": "The Coder agent is not properly configured.",
@ -121,13 +122,13 @@ class AgentCoder(AgentBase):
"documents": quick_completion.get("documents", [])
}
else:
logger.debug(f"Code to generate, quick check responded: {quick_completion.get("prompt", "(no answer)")}")
logger.debug(f"Code to generate, quick check responded: {quick_completion.get('prompt', '(no answer)')}")
# If quick completion not possible, continue with code generation and execution
logger.info("Generating code to solve the task")
# 4. Generate code using AI
code, requirements = await self._generate_code(prompt, document_data)
code, requirements = await self._generate_code(prompt)
if not code:
return {
@ -136,21 +137,69 @@ class AgentCoder(AgentBase):
}
# 5. Replace the placeholder with actual input_files data
document_data_json = json.dumps(document_data)
document_data_json = repr(document_data)
code_with_data = code.replace("input_files = \"=== JSONLOAD ===\"", f"input_files = {document_data_json}")
# 6. Execute code and get results
# 6. Execute code with retry logic
retry_count = 0
max_retries = self.execution_retry_limit
execution_history = []
while retry_count <= max_retries:
execution_result = self._execute_code(code_with_data, requirements)
execution_history.append({
"attempt": retry_count + 1,
"code": code_with_data,
"result": execution_result
})
# Check if execution was successful
if execution_result.get("success", False):
logger.info(f"Code execution succeeded on attempt {retry_count + 1}")
break
# If we've reached max retries, exit the loop
if retry_count >= max_retries:
logger.info(f"Reached maximum retry limit ({max_retries}). Giving up.")
break
# Log the error and attempt to improve the code
error = execution_result.get("error", "Unknown error")
logger.info(f"Execution attempt {retry_count + 1} failed: {error}. Attempting to improve code.")
# Generate improved code based on error
improved_code, improved_requirements = await self._improve_code(
original_code=code_with_data,
error=error,
execution_result=execution_result,
attempt=retry_count + 1
)
if improved_code:
code_with_data = improved_code
requirements = improved_requirements
logger.info(f"Code improved for retry {retry_count + 2}")
else:
logger.warning("Failed to improve code, using original code for retry")
retry_count += 1
# 7. Process results and create output documents
documents = []
# Always add the code document
# Always add the final code document
documents.append({
"label": "generated_code.py",
"content": code_with_data
})
# Add execution history document
execution_history_str = json.dumps(execution_history, indent=2)
documents.append({
"label": "execution_history.json",
"content": execution_history_str
})
# Create documents based on execution results
if execution_result.get("success", False):
result_data = execution_result.get("result")
@ -179,6 +228,9 @@ class AgentCoder(AgentBase):
"content": execution_result.get("output", "")
})
if retry_count > 0:
feedback = f"Code executed successfully after {retry_count + 1} attempts. Generated output files based on specifications."
else:
feedback = "Code executed successfully. Generated output files based on specifications."
else:
# Execution failed
@ -187,6 +239,10 @@ class AgentCoder(AgentBase):
"label": "execution_error.txt",
"content": f"Error executing code:\n\n{error}"
})
if retry_count > 0:
feedback = f"Error during code execution after {retry_count + 1} attempts: {error}"
else:
feedback = f"Error during code execution: {error}"
return {
@ -194,6 +250,81 @@ class AgentCoder(AgentBase):
"documents": documents
}
async def _improve_code(self, original_code: str, error: str, execution_result: Dict[str, Any], attempt: int) -> Tuple[str, List[str]]:
"""
Improve code based on execution error.
Args:
original_code: The code that failed to execute
error: The error message
execution_result: Complete execution result dictionary
attempt: Current attempt number
Returns:
Tuple of (improved_code, requirements)
"""
# Create prompt for code improvement
improvement_prompt = f"""
Fix the following Python code that failed during execution. This is attempt {attempt} to fix the code.
ORIGINAL CODE:
{original_code}
ERROR MESSAGE:
{error}
STDOUT:
{execution_result.get('output', '')}
INSTRUCTIONS:
1. Fix all errors identified in the error message
2. Diagnose and fix any logical issues
3. Pay special attention to:
- Type conversions and data handling
- Error handling and edge cases
- Resource management (file handles, etc.)
- Syntax errors and typos
4. Keep the input_files handling logic intact
5. Maintain the same overall structure and purpose
OUTPUT:
- Your improved code MUST still define a 'result' variable as a dictionary
- Each output file should be a key in the result dictionary
- DO NOT remove the input_files assignment line structure
REQUIREMENTS:
Required packages should be specified as:
# REQUIREMENTS: library==version,library2>=version
- You may add/remove requirements as needed to fix the code
Return ONLY Python code without explanations or markdown.
"""
# Call AI service
messages = [
{"role": "system", "content": "You are an expert Python code debugger. Provide only fixed Python code without explanations or formatting."},
{"role": "user", "content": improvement_prompt}
]
try:
improved_content = await self.mydom.call_ai(messages, temperature=0.2)
# Extract code and requirements
improved_code = self._clean_code(improved_content)
# Extract requirements
requirements = []
for line in improved_code.split('\n'):
if line.strip().startswith("# REQUIREMENTS:"):
req_str = line.replace("# REQUIREMENTS:", "").strip()
requirements = [r.strip() for r in req_str.split(',') if r.strip()]
break
return improved_code, requirements
except Exception as e:
logger.error(f"Error improving code: {str(e)}")
return None, []
async def _check_quick_completion(self, prompt: str, content_extraction: List[Dict], output_specs: List[Dict]) -> Dict:
"""
Check if the task can be completed without writing and executing code.
@ -245,7 +376,7 @@ Only return valid JSON. Your entire response must be parseable as JSON.
"""
# Call AI service
logger.debug("Checking if task can be completed without code execution")
logger.debug(f"Checking if task can be completed without code execution: {check_prompt}")
messages = [
{"role": "system", "content": "You are an AI assistant that determines if tasks require code execution. Reply with JSON only."},
{"role": "user", "content": check_prompt}
@ -253,7 +384,7 @@ Only return valid JSON. Your entire response must be parseable as JSON.
try:
# Use a lower temperature for more deterministic response
response = await self.ai_service.call_api(messages, temperature=0.1)
response = await self.mydom.call_ai(messages, produce_user_answer = True, temperature=0.1)
# Parse response as JSON
if response:
@ -279,7 +410,7 @@ Only return valid JSON. Your entire response must be parseable as JSON.
# Default to requiring code execution
return None
async def _generate_code(self, prompt: str, input_files: List) -> Tuple[str, List[str]]:
async def _generate_code(self, prompt: str) -> Tuple[str, List[str]]:
"""
Generate Python code from a prompt with the input_files placeholder.
@ -297,20 +428,19 @@ Generate Python code to solve the following task:
TASK:
{prompt}
IMPORTANT:
- All input files are provided in the 'input_files' variable as a list of [filename, data, is_base64].
- The 'input_files' variable is already defined at the top of your code, DO NOT modify it.
- For each file, you can access:
- filename: The name of the file (e.g., "image.png")
- data: The content of the file (base64 encoded or plain text)
- is_base64: Boolean flag indicating if the data is base64 encoded
INPUT FILES:
- 'input_files' variable is provided as [[filename, data, is_base64], ...]
- For text files (is_base64=False): use data directly as string
- For binary files (is_base64=True): use base64.b64decode(data)
- To use a file's data:
- For text files (when is_base64=False): Use the data directly as a string
- For binary files (when is_base64=True): Use base64.b64decode(data) to get bytes
- Do not perform any additional base64 detection - rely on the is_base64 flag
CODE QUALITY:
- Use explicit type conversions where needed (int/float/str)
- Implement feature detection, not version checks
- Handle errors gracefully with appropriate fallbacks
- Follow latest API conventions for libraries
- Validate inputs before processing
OUTPUT:
- Your code MUST define a 'result' variable as a dictionary to store outputs.
- Each output file should be a key in the result dictionary.
- For example: result = {{"output.txt": "output text", "results.json": json_string}}
@ -318,10 +448,13 @@ IMPORTANT:
Your code must start with:
input_files = "=== JSONLOAD ===" # DO NOT CHANGE THIS LINE
REQUIREMENTS:
Required packages should be specified as:
# REQUIREMENTS: package1,package2,package3
# REQUIREMENTS: library==version,library2>=version
- Specify exact versions for critical libraries
- Use constraint operators (==,>=,<=) as needed
Return ONLY Python code without explanations or markdown formatting.
Return ONLY Python code without explanations or markdown.
"""
# Call AI service
@ -330,7 +463,7 @@ Return ONLY Python code without explanations or markdown formatting.
{"role": "user", "content": ai_prompt}
]
generated_content = await self.ai_service.call_api(messages, temperature=0.1)
generated_content = await self.mydom.call_ai(messages, temperature=0.1)
# Extract code and requirements
code = self._clean_code(generated_content)
@ -372,20 +505,23 @@ Return ONLY Python code without explanations or markdown formatting.
# 2. Install requirements if provided
if requirements:
logger.debug(f"Installing requirements: {requirements}")
logger.info(f"Installing requirements: {requirements}")
# Create requirements.txt
req_file = os.path.join(self.temp_dir, "requirements.txt")
with open(req_file, "w") as f:
f.write("\n".join(requirements))
x="\n".join(requirements)
logger.info(f"Requirements file: {x}.")
# Install requirements
try:
pip_result = subprocess.run(
[python_exe, "-m", "pip", "install", "-r", req_file],
capture_output=True,
text=True,
timeout=APP_CONFIG.get("Agent_Coder_INSTALL_TIMEOUT")
timeout=int(APP_CONFIG.get("Agent_Coder_INSTALL_TIMEOUT"))
)
if pip_result.returncode != 0:
logger.debug(f"Error installing requirements: {pip_result.stderr}")

View file

@ -27,9 +27,9 @@ class AgentDocumentation(AgentBase):
"knowledge_organization"
]
def set_dependencies(self, ai_service=None):
def set_dependencies(self, mydom=None):
"""Set external dependencies for the agent."""
self.ai_service = ai_service
self.mydom = mydom
async def process_task(self, task: Dict[str, Any]) -> Dict[str, Any]:
"""
@ -48,7 +48,7 @@ class AgentDocumentation(AgentBase):
output_specs = task.get("output_specifications", [])
# Check AI service
if not self.ai_service:
if not self.mydom:
return {
"feedback": "The Documentation agent requires an AI service to function.",
"documents": []
@ -201,7 +201,7 @@ class AgentDocumentation(AgentBase):
"""
try:
response = await self.ai_service.call_api([
response = await self.mydom.call_ai([
{"role": "system", "content": "You are a documentation expert. Respond with valid JSON only."},
{"role": "user", "content": analysis_prompt}
])
@ -371,10 +371,10 @@ class AgentDocumentation(AgentBase):
The introduction should be professional and engaging, formatted according to {format_type} standards.
"""
introduction = await self.ai_service.call_api([
introduction = await self.mydom.call_ai([
{"role": "system", "content": f"You are a documentation expert creating an introduction in {format_type} format."},
{"role": "user", "content": intro_prompt}
])
], produce_user_answer = True)
# Step 2: Generate executive summary (if applicable)
if document_type in ["report", "whitepaper", "case study"]:
@ -397,10 +397,10 @@ class AgentDocumentation(AgentBase):
Keep the summary focused and impactful, approximately 200-300 words.
"""
executive_summary = await self.ai_service.call_api([
executive_summary = await self.mydom.call_ai([
{"role": "system", "content": f"You are a documentation expert creating an executive summary in {format_type} format."},
{"role": "user", "content": summary_prompt}
])
], produce_user_answer = True)
else:
executive_summary = ""
@ -447,10 +447,10 @@ class AgentDocumentation(AgentBase):
Be thorough in your coverage of this section, providing substantive content.
"""
section_content = await self.ai_service.call_api([
section_content = await self.mydom.call_ai([
{"role": "system", "content": f"You are a documentation expert creating detailed content for the {section_title} section."},
{"role": "user", "content": section_prompt}
])
], produce_user_answer = True)
sections.append(section_content)
@ -474,10 +474,10 @@ class AgentDocumentation(AgentBase):
The conclusion should be professional and impactful, formatted according to {format_type} standards.
"""
conclusion = await self.ai_service.call_api([
conclusion = await self.mydom.call_ai([
{"role": "system", "content": f"You are a documentation expert creating a conclusion in {format_type} format."},
{"role": "user", "content": conclusion_prompt}
])
], produce_user_answer = True)
# Step 5: Assemble the complete document
if format_type in ["md", "markdown"]:

View file

@ -43,9 +43,9 @@ class AgentWebcrawler(AgentBase):
self.search_engine = APP_CONFIG.get("Agent_Webcrawler_SEARCH_ENGINE", "https://html.duckduckgo.com/html/?q=")
self.user_agent = APP_CONFIG.get("Agent_Webcrawler_USER_AGENT", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
def set_dependencies(self, ai_service=None):
def set_dependencies(self, mydom=None):
"""Set external dependencies for the agent."""
self.ai_service = ai_service
self.mydom = mydom
async def process_task(self, task: Dict[str, Any]) -> Dict[str, Any]:
"""
@ -63,7 +63,7 @@ class AgentWebcrawler(AgentBase):
output_specs = task.get("output_specifications", [])
# Check AI service
if not self.ai_service:
if not self.mydom:
return {
"feedback": "The Webcrawler agent requires an AI service to function effectively.",
"documents": []
@ -134,7 +134,7 @@ class AgentWebcrawler(AgentBase):
try:
# Get research plan from AI
response = await self.ai_service.call_api([
response = await self.mydom.call_ai([
{"role": "system", "content": "You are a web research planning expert. Create precise research plans in JSON format only."},
{"role": "user", "content": research_prompt}
])
@ -295,8 +295,8 @@ class AgentWebcrawler(AgentBase):
Only include information actually found in the content. No fabrications or assumptions.
"""
if self.ai_service:
summary = await self.ai_service.call_api([
if self.mydom:
summary = await self.mydom.call_ai([
{"role": "system", "content": "You summarize web content accurately and concisely, focusing only on what is actually in the content."},
{"role": "user", "content": summary_prompt}
])
@ -449,7 +449,7 @@ class AgentWebcrawler(AgentBase):
try:
# Generate report with AI
report_content = await self.ai_service.call_api([
report_content = await self.mydom.call_ai([
{"role": "system", "content": f"You create professional research reports in {template_format} format."},
{"role": "user", "content": report_prompt}
])

View file

@ -24,11 +24,11 @@ class AgentBase:
self.name = "base-agent"
self.description = "Basic agent functionality"
self.capabilities = []
self.ai_service = None
self.mydom = None
def set_dependencies(self, ai_service=None):
def set_dependencies(self, mydom=None):
"""Set external dependencies for the agent."""
self.ai_service = ai_service
self.mydom = mydom
def get_agent_info(self) -> Dict[str, Any]:
"""
@ -88,7 +88,7 @@ class AgentRegistry:
raise RuntimeError("Singleton instance already exists - use get_instance()")
self.agents = {}
self.ai_service = None
self.mydom = None
self._load_agents()
def _load_agents(self):
@ -146,16 +146,16 @@ class AgentRegistry:
except Exception as e:
logger.error(f"Error loading agent from module {module_name}: {e}")
def set_ai_service(self, ai_service):
def set_mydom(self, mydom):
"""Set the AI service for all agents."""
self.ai_service = ai_service
self.mydom = mydom
self.update_agent_dependencies()
def update_agent_dependencies(self):
"""Update dependencies for all registered agents."""
for agent_id, agent in self.agents.items():
if hasattr(agent, 'set_dependencies'):
agent.set_dependencies(ai_service=self.ai_service)
agent.set_dependencies(mydom=self.mydom)
def register_agent(self, agent):
"""
@ -167,7 +167,7 @@ class AgentRegistry:
agent_id = getattr(agent, 'name', "unknown_agent")
# Initialize agent with dependencies
if hasattr(agent, 'set_dependencies'):
agent.set_dependencies(ai_service=self.ai_service)
agent.set_dependencies(mydom=self.mydom)
self.agents[agent_id] = agent
logger.debug(f"Agent '{agent.name}' registered")
@ -182,8 +182,8 @@ class AgentRegistry:
if agent_identifier in self.agents:
agent = self.agents[agent_identifier]
# Ensure the agent has the AI service
if hasattr(agent, 'set_dependencies') and self.ai_service:
agent.set_dependencies(ai_service=self.ai_service)
if hasattr(agent, 'set_dependencies') and self.mydom:
agent.set_dependencies(mydom=self.mydom)
return agent
logger.error(f"Agent with identifier '{agent_identifier}' not found")
return None

View file

@ -1,5 +1,5 @@
"""
Interface to LucyDOM database.
Interface to LucyDOM database and AI Connectors.
Uses the JSON connector for data access with added language support.
"""
@ -12,9 +12,12 @@ from typing import Dict, Any, List, Optional, Union
import importlib
import hashlib
# DYNAMIC PART: Connectors to the Interface
from connectors.connector_db_json import DatabaseConnector
from modules.configuration import APP_CONFIG
from connectors.connector_aichat_openai import ChatService
# Basic Configurations
from modules.configuration import APP_CONFIG
logger = logging.getLogger(__name__)
# Custom exceptions for file handling
@ -132,18 +135,12 @@ class LucyDOMInterface:
# Language support methods
def set_ai_service(self, ai_service):
"""Set the AI service for API calls"""
self.ai_service = ai_service
def set_user_language(self, language_code: str):
"""Set the user's preferred language"""
self.user_language = language_code
logger.info(f"User language set to: {language_code}")
async def call_ai(self, messages: List[Dict[str, str]],
produce_user_answer: bool = False,
temperature: float = None) -> str:
async def call_ai(self, messages: List[Dict[str, str]], produce_user_answer: bool = False, temperature: float = None) -> str:
"""
Enhanced AI service call with language support
@ -161,14 +158,15 @@ class LucyDOMInterface:
# Add language instruction for user-facing responses
if produce_user_answer and self.user_language:
ltext= f"Please respond in '{self.user_language}' language."
if messages and messages[0]["role"] == "system":
if "language" not in messages[0]["content"].lower():
messages[0]["content"] = f"Please respond in {self.user_language} language. {messages[0]['content']}"
messages[0]["content"] = f"{ltext} {messages[0]['content']}"
else:
# Insert a system message with language instruction
messages.insert(0, {
"role": "system",
"content": f"Please respond in {self.user_language} language."
"content": ltext
})
# Call the AI service
@ -636,7 +634,7 @@ class LucyDOMInterface:
self.create_file_data(db_file["id"], file_content)
# Debug: Export file to static folder
if logger.isEnabledFor(logging.DEBUG): self._export_file_to_static(file_content, db_file["id"], file_name)
self._export_file_to_static(file_content, db_file["id"], file_name) # DEBUG TODO
# Debug: Verify database record was created
if not db_file:
@ -1174,7 +1172,7 @@ class LucyDOMInterface:
"message": log.get("message", ""),
"type": log.get("type", "info"),
"timestamp": log.get("timestamp", self._get_current_timestamp()),
"agent_name": log.get("agent_name", GLOBAL_SETTINGS.get("system_name", "AI Assistant")),
"agent_name": log.get("agent_name", "(undefined)"),
"status": log.get("status", "running"),
"progress": log.get("progress", 50)
}
@ -1250,17 +1248,6 @@ class LucyDOMInterface:
logger.error(f"Error loading workflow state: {str(e)}")
return None
# Global settings for the LucyDOM interface
GLOBAL_SETTINGS = {
"system_name": "AI Assistant", # Default system name for logs
"workflow_status_messages": {
"init": "Workflow initialized",
"running": "Running workflow",
"waiting": "Waiting for input",
"completed": "Workflow completed",
"error": "Error in workflow"
}
}
# Singleton factory for LucyDOMInterface instances per context
_lucydom_interfaces = {}
@ -1272,7 +1259,12 @@ def get_lucydom_interface(mandate_id: int = 0, user_id: int = 0) -> LucyDOMInter
"""
context_key = f"{mandate_id}_{user_id}"
if context_key not in _lucydom_interfaces:
_lucydom_interfaces[context_key] = LucyDOMInterface(mandate_id, user_id)
# Create new interface instance
interface = LucyDOMInterface(mandate_id, user_id)
# Initialize AI service
ai_service = ChatService()
interface.ai_service = ai_service # Directly set the attribute
_lucydom_interfaces[context_key] = interface
return _lucydom_interfaces[context_key]
# Init

View file

@ -72,6 +72,7 @@ class FileData(BaseModel):
id: int = Field(description="Unique ID of the data object")
data: str = Field(description="Binary content of the file as base64 string")
# Workflow model classes
class DocumentContent(BaseModel):
@ -99,7 +100,7 @@ class DataStats(BaseModel):
bytes_sent: Optional[int] = Field(None, description="Bytes sent")
bytes_received: Optional[int] = Field(None, description="Bytes received")
class Message(BaseModel):
class WorkflowMessage(BaseModel):
"""Message object in the workflow"""
id: str = Field(description="Unique ID of the message")
workflow_id: str = Field(description="Reference to the parent workflow")
@ -116,6 +117,19 @@ class Message(BaseModel):
content: Optional[str] = Field(None, description="Text content of the message")
agent_name: Optional[str] = Field(None, description="Name of the agent used")
class WorkflowLog(BaseModel):
"""Log entry for a workflow"""
id: str = Field(description="Unique ID of the log entry")
workflow_id: str = Field(description="ID of the associated workflow")
message: str = Field(description="Log message content")
type: str = Field(description="Type of log ('info', 'warning', 'error')")
timestamp: str = Field(description="Timestamp of the log entry")
agent_name: str = Field(description="Name of the agent that created the log")
status: str = Field(description="Status of the workflow at log time")
progress: Optional[int] = Field(None, description="Progress value (0-100)")
mandate_id: Optional[int] = Field(None, description="ID of the mandate")
user_id: Optional[int] = Field(None, description="ID of the user")
class Workflow(BaseModel):
"""Workflow object for multi-agent system"""
id: str = Field(description="Unique ID of the workflow")
@ -125,11 +139,13 @@ class Workflow(BaseModel):
status: str = Field(description="Status of the workflow ('running', 'completed')")
started_at: str = Field(description="Start timestamp")
last_activity: str = Field(description="Timestamp of the last activity")
data_stats: Optional[Dict[str, Any]] = Field(None, description="Total statistics")
current_round: int = Field(default=1, description="Current round/iteration of the workflow")
message_ids: List[str] = Field(default=[], description="List of message IDs in this workflow")
data_stats: Optional[Dict[str, Any]] = Field(None, description="Total statistics")
messages: List[Message] = Field(default=[], description="Message history")
logs: List[Dict[str, Any]] = Field(default=[], description="Log entries")
messages: List[WorkflowMessage] = Field(default=[], description="Message history (in-memory representation)")
logs: List[WorkflowLog] = Field(default=[], description="Log entries (in-memory representation)")
# Request models for the API

View file

@ -14,6 +14,10 @@ FRONTEND:
PRIO1:
CHECK: If pictures not displayed to check utf-8 encoding in the base64 string!!
STOP File export to static folder ("TODO)
add connector to myoutlook
todo an agent for "code writing and editing" connected to the codebase, working in loops over each document...
@ -26,8 +30,6 @@ Split big files into content-parts
PRIO2:
implement cleanup routines for files in lucydom_interface (File_Management_CLEANUP_INTERVAL): temp older than interval, all orphaned
Integrate NDA Text as modal form - Data governance agreement by login with checkbox
frontend to react

View file

@ -1,10 +0,0 @@
This is a test text file for the ChatManager workflow.
It contains some information for testing document processing.
The ChatManager should be able to process this file
and extract relevant information from it.
This file serves as an example for text-based documents that can be
used in a chat workflow.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 B

View file

@ -1,52 +0,0 @@
# REQUIREMENTS: Pillow
from PIL import Image
def calculate_image_pixels(image_path):
try:
with Image.open(image_path) as img:
width, height = img.size
total_pixels = width * height
return total_pixels
except Exception as e:
print(f"Error calculating image pixels: {e}")
return None
def calculate_text_characters(text_path):
try:
with open(text_path, 'r', encoding='utf-8') as file:
text = file.read()
total_characters = len(text)
return total_characters
except Exception as e:
print(f"Error calculating text characters: {e}")
return None
def main():
image_path = 'test_image'
text_path = 'test_document'
# Calculate total pixels in the image
total_pixels = calculate_image_pixels(image_path)
# Calculate total characters in the text document
total_characters = calculate_text_characters(text_path)
# Prepare the result dictionary
result = {
'total_pixels': total_pixels,
'total_characters': total_characters
}
# Write the result to a text file
try:
with open('result.txt', 'w') as result_file:
result_file.write(str(result))
except Exception as e:
print(f"Error writing result to file: {e}")
# Output the result
print(result)
if __name__ == "__main__":
main()

View file

@ -1,6 +0,0 @@
Execution error:
Traceback (most recent call last):
File "C:\Users\pmots\AppData\Local\Temp\code_exec_itmq0xhw\code_9cc3911d.py", line 3, in <module>
from PIL import Image
ModuleNotFoundError: No module named 'PIL'

View file

@ -1,50 +0,0 @@
from PIL import Image
def calculate_image_pixels(image_path):
try:
with Image.open(image_path) as img:
width, height = img.size
total_pixels = width * height
return total_pixels
except Exception as e:
print(f"Error calculating image pixels: {e}")
return None
def calculate_text_characters(text_path):
try:
with open(text_path, 'r', encoding='utf-8') as file:
text = file.read()
total_characters = len(text)
return total_characters
except Exception as e:
print(f"Error calculating text characters: {e}")
return None
def main():
image_path = 'test_image'
text_path = 'test_document'
# Calculate total pixels in the image
total_pixels = calculate_image_pixels(image_path)
# Calculate total characters in the text document
total_characters = calculate_text_characters(text_path)
# Prepare the result dictionary
result = {
'total_pixels': total_pixels,
'total_characters': total_characters
}
# Write the result to a text file
try:
with open('result.txt', 'w') as result_file:
result_file.write(str(result))
except Exception as e:
print(f"Error writing result to file: {e}")
# Output the result
print(result)
if __name__ == "__main__":
main()

View file

@ -13,7 +13,7 @@ from datetime import datetime
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(name)s - %(message)s',
handlers=[logging.StreamHandler()]
)
@ -112,7 +112,7 @@ async def run_chat_workflow(mandate_id: int, user_id: int, file_ids: List[int])
# Create user request
user_input = {
"prompt": "Bitte zähle mir zusammen wieviele Pixel das Bild hat und wieviele Zeichen der Text der Dokumente hat",
"prompt": "Bitte integriere den Text der Datei ins angefügte Bild",
"list_file_id": file_ids
}

66
testcode.py Normal file
View file

@ -0,0 +1,66 @@
input_files = [['test_document.txt', 'CiAgICBUaGlzIGlzIGEgdGVzdCB0ZXh0IGZpbGUgZm9yIHRoZSBDaGF0TWFuYWdlciB3b3JrZmxvdy4KICAgIEl0IGNvbnRhaW5zIHNvbWUgaW5mb3JtYXRpb24gZm9yIHRlc3RpbmcgZG9jdW1lbnQgcHJvY2Vzc2luZy4KICAgIAogICAgVGhlIENoYXRNYW5hZ2VyIHNob3VsZCBiZSBhYmxlIHRvIHByb2Nlc3MgdGhpcyBmaWxlCiAgICBhbmQgZXh0cmFjdCByZWxldmFudCBpbmZvcm1hdGlvbiBmcm9tIGl0LgogICAgCiAgICBUaGlzIGZpbGUgc2VydmVzIGFzIGFuIGV4YW1wbGUgZm9yIHRleHQtYmFzZWQgZG9jdW1lbnRzIHRoYXQgY2FuIGJlCiAgICB1c2VkIGluIGEgY2hhdCB3b3JrZmxvdy4KICAgIA==', True], ['test_document.txt', '\n This is a test text file for the ChatManager workflow.\n It contains some information for testing document processing.\n \n The ChatManager should be able to process this file\n and extract relevant information from it.\n \n This file serves as an example for text-based documents that can be\n used in a chat workflow.\n ', False], ['test_image.png', 'iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAA40lEQVR4nO3QsQEAIAyAsOr/P+sLZU9mJs4btu66xKzCrMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArNn7il4Bx2GaB88AAAAASUVORK5CYII=', True], ['test_image.png', 'Image (100x100, PNG, RGB)', False]] # DO NOT CHANGE THIS LINE
# REQUIREMENTS: Pillow==10.0.0
import base64
from PIL import Image, ImageDraw, ImageFont
import io
# Initialize result dictionary
result = {}
# Helper function to decode base64 data
def decode_base64(data: str) -> bytes:
return base64.b64decode(data)
# Extract input files
text_data = None
image_data = None
for file_info in input_files:
filename, data, is_base64 = file_info
if filename == 'test_document.txt' and not is_base64:
text_data = data
elif filename == 'test_image.png' and is_base64:
image_data = decode_base64(data)
# Validate inputs
if text_data is None:
raise ValueError("Text data not found in input files.")
if image_data is None:
raise ValueError("Image data not found in input files.")
# Load image
image = Image.open(io.BytesIO(image_data))
# Prepare to draw on the image
draw = ImageDraw.Draw(image)
# Use a basic font
try:
font = ImageFont.truetype("arial", 15)
except IOError:
font = ImageFont.load_default()
# Define text position
text_position = (10, 10)
# Overlay text onto image
draw.text(text_position, text_data, font=font, fill=(255, 255, 255))
# Save the resulting image to a bytes buffer
output_buffer = io.BytesIO()
image.save(output_buffer, format='PNG')
output_buffer.seek(0)
#result['integrated_image.png'] = base64.b64encode(output_buffer.getvalue()).decode('utf-8')
# binary_data = base64.b64decode(pic_str)
binary_data=output_buffer.getvalue()
# Write the binary data to a file
with open("./static/test_output.png", "wb") as f:
f.write(binary_data)
print("OK")
# Output the result dictionary
result