From 3c0634f6bc930a2ea09acf58444436fbd330c853 Mon Sep 17 00:00:00 2001 From: marko-kraemer Date: Sun, 6 Apr 2025 17:45:02 +0100 Subject: [PATCH] fix duplicate agent run --- backend/agent/api.py | 37 +- backend/agent/{tools => }/coder/files_tool.py | 0 backend/agent/coder/prompt.py | 100 ++++++ .../agent/{tools => }/coder/search_tool.py | 0 .../agent/{tools => }/coder/terminal_tool.py | 0 backend/agent/prompt.py | 136 ++++--- backend/agent/run.py | 4 +- backend/agent/test_prompt.py | 59 ---- backend/agent/tools/message_tool.py | 203 +++++++++++ backend/agent/workspace/index.html | 331 ++++++++++++++++++ backend/agentpress/response_processor.py | 2 +- backend/agentpress/thread_manager.py | 66 +--- backend/agentpress/tool.py | 16 +- backend/services/llm.py | 2 +- 14 files changed, 736 insertions(+), 220 deletions(-) rename backend/agent/{tools => }/coder/files_tool.py (100%) create mode 100644 backend/agent/coder/prompt.py rename backend/agent/{tools => }/coder/search_tool.py (100%) rename backend/agent/{tools => }/coder/terminal_tool.py (100%) delete mode 100644 backend/agent/test_prompt.py create mode 100644 backend/agent/tools/message_tool.py create mode 100644 backend/agent/workspace/index.html diff --git a/backend/agent/api.py b/backend/agent/api.py index 412fdc8b..52f036f7 100644 --- a/backend/agent/api.py +++ b/backend/agent/api.py @@ -512,6 +512,9 @@ async def run_agent_background(agent_run_id: str, thread_id: str, instance_id: s agent_gen = run_agent(thread_id, stream=True, thread_manager=thread_manager) + # Collect all responses to save to database + all_responses = [] + async for response in agent_gen: # Check if stop signal received if stop_signal_received: @@ -521,12 +524,13 @@ async def run_agent_background(agent_run_id: str, thread_id: str, instance_id: s # Store response in memory if agent_run_id in active_agent_runs: active_agent_runs[agent_run_id].append(response) + all_responses.append(response) total_responses += 1 - # Periodically log progress for long-running agents - if total_responses % 100 == 0: - duration = (datetime.now(timezone.utc) - start_time).total_seconds() - logger.info(f"Agent run {agent_run_id} still running after {duration:.1f}s, processed {total_responses} responses (instance: {instance_id})") + # # Periodically log progress for long-running agents + # if total_responses % 100 == 0: + # duration = (datetime.now(timezone.utc) - start_time).total_seconds() + # logger.info(f"Agent run {agent_run_id} still running after {duration:.1f}s, processed {total_responses} responses (instance: {instance_id})") # Signal all done if we weren't stopped if not stop_signal_received: @@ -535,11 +539,13 @@ async def run_agent_background(agent_run_id: str, thread_id: str, instance_id: s # Add completion message to the stream if agent_run_id in active_agent_runs: - active_agent_runs[agent_run_id].append({ + completion_message = { "type": "status", "status": "completed", "message": "Agent run completed successfully" - }) + } + active_agent_runs[agent_run_id].append(completion_message) + all_responses.append(completion_message) # Update the agent run status in the database completion_timestamp = datetime.now(timezone.utc).isoformat() @@ -549,7 +555,8 @@ async def run_agent_background(agent_run_id: str, thread_id: str, instance_id: s try: update_result = await client.table('agent_runs').update({ "status": "completed", - "completed_at": completion_timestamp + "completed_at": completion_timestamp, + "responses": json.dumps(all_responses) }).eq("id", agent_run_id).execute() if hasattr(update_result, 'data') and update_result.data: @@ -597,11 +604,16 @@ async def run_agent_background(agent_run_id: str, thread_id: str, instance_id: s # Add error message to the stream if agent_run_id in active_agent_runs: - active_agent_runs[agent_run_id].append({ + error_response = { "type": "status", "status": "error", "message": error_message - }) + } + active_agent_runs[agent_run_id].append(error_response) + if 'all_responses' in locals(): + all_responses.append(error_response) + else: + all_responses = [error_response] # Initialize if not exists # Update the agent run with the error - with retry completion_timestamp = datetime.now(timezone.utc).isoformat() @@ -611,7 +623,8 @@ async def run_agent_background(agent_run_id: str, thread_id: str, instance_id: s update_result = await client.table('agent_runs').update({ "status": "failed", "error": f"{error_message}\n{traceback_str}", - "completed_at": completion_timestamp + "completed_at": completion_timestamp, + "responses": json.dumps(all_responses if 'all_responses' in locals() else []) }).eq("id", agent_run_id).execute() if hasattr(update_result, 'data') and update_result.data: @@ -667,6 +680,4 @@ async def run_agent_background(agent_run_id: str, thread_id: str, instance_id: s except Exception as e: logger.warning(f"Error deleting active run key: {str(e)}") - logger.info(f"Agent run background task fully completed for: {agent_run_id} (instance: {instance_id})") - - + logger.info(f"Agent run background task fully completed for: {agent_run_id} (instance: {instance_id})") \ No newline at end of file diff --git a/backend/agent/tools/coder/files_tool.py b/backend/agent/coder/files_tool.py similarity index 100% rename from backend/agent/tools/coder/files_tool.py rename to backend/agent/coder/files_tool.py diff --git a/backend/agent/coder/prompt.py b/backend/agent/coder/prompt.py new file mode 100644 index 00000000..41fd600a --- /dev/null +++ b/backend/agent/coder/prompt.py @@ -0,0 +1,100 @@ +''' +Agent prompt configuration with instructions for XML-based tool usage. +This defines the system prompt that instructs the agent how to properly use the XML tool syntax. +''' + +SYSTEM_PROMPT = ''' +You are an AI agent specialized in helping users with coding tasks and web development projects. + +# XML-BASED TOOLS GUIDE + +You have access to several tools to help with files, searching code, and executing commands. These tools must be used with specific XML syntax. + +## File Operations + +### Reading Files +To read a file, use the `read_file` tag: +``` + + +``` + +### Writing Files +To create or overwrite a file, use the `write_to_file` tag: +``` + +Your file content here + +``` + +### Replacing Content in Files +To modify parts of a file, use the `replace_in_file` tag with search/replace blocks: +``` + +<<<<<<< SEARCH +[exact content to find] +======= +[new content to replace with] +>>>>>>> REPLACE + +``` + +### Deleting Files +To delete a file, use the `delete_file` tag: +``` + + +``` + +## Searching and Code Navigation + +### Listing Directory Contents +To list the contents of a directory, use the `list_dir` tag: +``` + + +``` + +### Searching for Text Pattern +To search for text patterns in files, use the `grep_search` tag: +``` + + +``` + +### Finding Files +To search for files by name, use the `file_search` tag: +``` + + +``` + +## Terminal Operations + +### Executing Commands +To run a command in the terminal, use the `execute_command` tag: +``` + +npm install react +true + +``` + +# USING TOOLS + +1. **Choose the right tool** for each task based on what you're trying to accomplish. +2. **Use the exact XML syntax** shown in the examples above. +3. **Provide all required parameters** for each tool. +4. **Process the results** returned by the tools to inform your next actions. +5. **Use appropriate tools based on the extent of changes** needed. + +When executing commands that might modify the system (installing packages, removing files, etc.), always set `requires_approval` to true. + +Always provide clear explanations to the user about what actions you're taking and why. +''' + +def get_system_prompt(): + ''' + Returns the system prompt with XML tool usage instructions. + ''' + return SYSTEM_PROMPT \ No newline at end of file diff --git a/backend/agent/tools/coder/search_tool.py b/backend/agent/coder/search_tool.py similarity index 100% rename from backend/agent/tools/coder/search_tool.py rename to backend/agent/coder/search_tool.py diff --git a/backend/agent/tools/coder/terminal_tool.py b/backend/agent/coder/terminal_tool.py similarity index 100% rename from backend/agent/tools/coder/terminal_tool.py rename to backend/agent/coder/terminal_tool.py diff --git a/backend/agent/prompt.py b/backend/agent/prompt.py index 41fd600a..599e635c 100644 --- a/backend/agent/prompt.py +++ b/backend/agent/prompt.py @@ -1,97 +1,79 @@ -''' -Agent prompt configuration with instructions for XML-based tool usage. -This defines the system prompt that instructs the agent how to properly use the XML tool syntax. -''' +SYSTEM_PROMPT = """ +You are a powerful general purpose AI assistant capable of helping users with a wide range of tasks. As a versatile assistant, you combine deep knowledge across many domains with helpful problem-solving skills to deliver high-quality responses. You excel at understanding user needs, providing accurate information, and offering creative solutions to various challenges. -SYSTEM_PROMPT = ''' -You are an AI agent specialized in helping users with coding tasks and web development projects. +You are capable of: +1. Information gathering, fact-checking, and documentation +2. Data processing, analysis, and visualization +3. Writing multi-chapter articles and in-depth research reports +4. Creating websites, applications, and tools +5. Using programming to solve various problems beyond development +6. Various tasks that can be accomplished using computers and the internet -# XML-BASED TOOLS GUIDE +The tasks you handle may include answering questions, performing research, drafting content, explaining complex concepts, or helping with specific technical requirements. As a professional assistant, you'll approach each request with expertise and clarity. -You have access to several tools to help with files, searching code, and executing commands. These tools must be used with specific XML syntax. +Your main goal is to follow the USER's instructions at each message, delivering helpful, accurate, and clear responses tailored to their needs. +FOLLOW THE USER'S QUESTIONS, INSTRUCTIONS AND REQUESTS AT ALL TIMES. -## File Operations +Remember: +1. ALWAYS follow the exact response format shown above +2. When using str_replace, only include the minimal changes needed +3. When using full_file_rewrite, include ALL necessary code +4. Use appropriate tools based on the extent of changes +5. Focus on providing accurate, helpful information +6. Consider context and user needs in your responses +7. Handle ambiguity gracefully by asking clarifying questions when needed +8. ISSUE ONLY ONE SINGLE XML TOOL CALL AT A TIME - complete one action before proceeding to the next -### Reading Files -To read a file, use the `read_file` tag: -``` - - -``` + +You have access to these tools through XML-based tool calling: +- create_file: Create new files with specified content +- delete_file: Remove existing files +- str_replace: Replace specific text in files +- full_file_rewrite: Completely rewrite an existing file with new content +- terminal_tool: Execute shell commands in the workspace directory +- message_notify_user: Send a message to user without requiring a response. Use for acknowledging receipt of messages, providing progress updates, reporting task completion, or explaining changes in approach +- message_ask_user: Ask user a question and wait for response. Use for requesting clarification, asking for confirmation, or gathering additional information +- idle: A special tool to indicate you have completed all tasks and are entering idle state + -### Writing Files -To create or overwrite a file, use the `write_to_file` tag: -``` - -Your file content here - -``` + +RESPONSE FORMAT – STRICTLY Output XML tags for tool calling -### Replacing Content in Files -To modify parts of a file, use the `replace_in_file` tag with search/replace blocks: -``` - -<<<<<<< SEARCH -[exact content to find] -======= -[new content to replace with] ->>>>>>> REPLACE - -``` +You must only use ONE tool call at a time. Wait for each action to complete before proceeding to the next one. -### Deleting Files -To delete a file, use the `delete_file` tag: -``` - - -``` + +file contents here + -## Searching and Code Navigation + +text to replace +replacement text + -### Listing Directory Contents -To list the contents of a directory, use the `list_dir` tag: -``` - - -``` + +New file contents go here, replacing all existing content + -### Searching for Text Pattern -To search for text patterns in files, use the `grep_search` tag: -``` - - -``` + + -### Finding Files -To search for files by name, use the `file_search` tag: -``` - - -``` + +command here + -## Terminal Operations + +Message text to display to user + -### Executing Commands -To run a command in the terminal, use the `execute_command` tag: -``` - -npm install react -true - -``` + +Question text to present to user + -# USING TOOLS + -1. **Choose the right tool** for each task based on what you're trying to accomplish. -2. **Use the exact XML syntax** shown in the examples above. -3. **Provide all required parameters** for each tool. -4. **Process the results** returned by the tools to inform your next actions. -5. **Use appropriate tools based on the extent of changes** needed. + -When executing commands that might modify the system (installing packages, removing files, etc.), always set `requires_approval` to true. - -Always provide clear explanations to the user about what actions you're taking and why. -''' +""" def get_system_prompt(): ''' diff --git a/backend/agent/run.py b/backend/agent/run.py index 26cd87b2..672a6329 100644 --- a/backend/agent/run.py +++ b/backend/agent/run.py @@ -7,7 +7,7 @@ from agent.tools.terminal_tool import TerminalTool from agent.tools.wait_tool import WaitTool # from agent.tools.search_tool import CodeSearchTool from typing import Optional -from agent.test_prompt import get_system_prompt +from agent.prompt import get_system_prompt from agentpress.response_processor import ProcessorConfig from dotenv import load_dotenv @@ -155,4 +155,4 @@ if __name__ == "__main__": load_dotenv() # Ensure environment variables are loaded # Run the test function - asyncio.run(test_agent()) + asyncio.run(test_agent()) \ No newline at end of file diff --git a/backend/agent/test_prompt.py b/backend/agent/test_prompt.py deleted file mode 100644 index c939f0ad..00000000 --- a/backend/agent/test_prompt.py +++ /dev/null @@ -1,59 +0,0 @@ - -SYSTEM_PROMPT = """ -You are a powerful AI web development partner helping users create modern web applications. As an expert web development partner, you combine deep technical expertise with best practices to deliver high-quality, scalable web solutions. You excel at modern web development, creating responsive user interfaces, crafting polished visuals with CSS, and implementing robust functionality with JavaScript. - -The task will require modifying or creating web applications, implementing new features, optimizing performance, or simply answering technical questions. As a professional web development assistant, you'll approach each request with the expertise of a senior web developer. - -Your main goal is to follow the USER's instructions at each message, delivering high-quality code solutions, thoughtful architectural advice, and clear technical explanations in a non-technical friendly way. -FOLLOW THE USER'S QUESTIONS, INSTRUCTIONS AND REQUESTS AT ALL TIMES. - -Remember: -1. ALWAYS follow the exact response format shown above -2. Use CDN links for external libraries (if needed) -3. When using str_replace, only include the minimal changes needed -4. When using full_file_rewrite, include ALL necessary code -5. Use appropriate tools based on the extent of changes -6. Focus on creating maintainable and scalable web applications -7. Implement proper error handling and edge cases - - -You have access to these tools: -- create_file: Create new files with specified content -- delete_file: Remove existing files -- str_replace: Replace specific text in files -- full_file_rewrite: Completely rewrite an existing file with new content -- terminal_tool: Execute shell commands in the workspace directory - - - -RESPONSE FORMAT – STRICTLY Output XML tags - - -file contents here - - - -text to replace -replacement text - - - -New file contents go here, replacing all existing content - - - - - - -command here - - - - -""" - -def get_system_prompt(): - ''' - Returns the system prompt with XML tool usage instructions. - ''' - return SYSTEM_PROMPT \ No newline at end of file diff --git a/backend/agent/tools/message_tool.py b/backend/agent/tools/message_tool.py new file mode 100644 index 00000000..69f6208d --- /dev/null +++ b/backend/agent/tools/message_tool.py @@ -0,0 +1,203 @@ +import os +from typing import List, Optional, Union +from agentpress.tool import Tool, ToolResult, openapi_schema, xml_schema + +class MessageTool(Tool): + """Tool for user communication and interaction. + + This tool provides methods for notifying users and asking questions, with support for + attachments and user takeover suggestions. + """ + + def __init__(self): + super().__init__() + + @openapi_schema({ + "type": "function", + "function": { + "name": "message_notify_user", + "description": "Send a message to user without requiring a response. Use for acknowledging receipt of messages, providing progress updates, reporting task completion, or explaining changes in approach.", + "parameters": { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Message text to display to user" + }, + "attachments": { + "anyOf": [ + {"type": "string"}, + {"items": {"type": "string"}, "type": "array"} + ], + "description": "(Optional) List of attachments to show to user, can be file paths or URLs" + } + }, + "required": ["text"] + } + } + }) + @xml_schema( + tag_name="message-notify-user", + mappings=[ + {"param_name": "text", "node_type": "content", "path": "."}, + {"param_name": "attachments", "node_type": "attribute", "path": ".", "required": False} + ], + example=''' + + Task completed successfully! + + ''' + ) + async def message_notify_user(self, text: str, attachments: Optional[Union[str, List[str]]] = None) -> ToolResult: + """Send a notification message to the user without requiring a response. + + Args: + text: The message to display to the user + attachments: Optional file paths or URLs to attach to the message + + Returns: + ToolResult indicating success or failure of the notification + """ + try: + # Convert single attachment to list for consistent handling + if attachments and isinstance(attachments, str): + attachments = [attachments] + + # Format the response message + response_text = f"NOTIFICATION: {text}" + + # Add attachments information if present + if attachments: + attachment_list = "\n- ".join(attachments) + response_text += f"\n\nAttachments:\n- {attachment_list}" + + return self.success_response(response_text) + except Exception as e: + return self.fail_response(f"Error sending notification: {str(e)}") + + @openapi_schema({ + "type": "function", + "function": { + "name": "message_ask_user", + "description": "Ask user a question and wait for response. Use for requesting clarification, asking for confirmation, or gathering additional information.", + "parameters": { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Question text to present to user" + }, + "attachments": { + "anyOf": [ + {"type": "string"}, + {"items": {"type": "string"}, "type": "array"} + ], + "description": "(Optional) List of question-related files or reference materials" + }, + "suggest_user_takeover": { + "type": "string", + "enum": ["none", "browser"], + "description": "(Optional) Suggested operation for user takeover" + } + }, + "required": ["text"] + } + } + }) + @xml_schema( + tag_name="message-ask-user", + mappings=[ + {"param_name": "text", "node_type": "content", "path": "."}, + {"param_name": "attachments", "node_type": "attribute", "path": ".", "required": False}, + {"param_name": "suggest_user_takeover", "node_type": "attribute", "path": ".", "required": False} + ], + example=''' + + Would you like to continue with this approach? + + ''' + ) + async def message_ask_user(self, text: str, attachments: Optional[Union[str, List[str]]] = None, + suggest_user_takeover: str = "none") -> ToolResult: + """Ask the user a question and wait for a response. + + Args: + text: The question to present to the user + attachments: Optional file paths or URLs to attach to the question + suggest_user_takeover: Optional suggestion for user takeover (none, browser) + + Returns: + ToolResult indicating the question was successfully sent + """ + try: + # Convert single attachment to list for consistent handling + if attachments and isinstance(attachments, str): + attachments = [attachments] + + # Format the question message + response_text = f"QUESTION: {text}" + + # Add attachments information if present + if attachments: + attachment_list = "\n- ".join(attachments) + response_text += f"\n\nAttachments:\n- {attachment_list}" + + # Add user takeover suggestion if not "none" + if suggest_user_takeover and suggest_user_takeover != "none": + response_text += f"\n\nSuggested takeover: {suggest_user_takeover}" + + return self.success_response(response_text, requires_response=True) + except Exception as e: + return self.fail_response(f"Error asking user: {str(e)}") + + @openapi_schema({ + "type": "function", + "function": { + "name": "idle", + "description": "A special tool to indicate you have completed all tasks and are about to enter idle state.", + "parameters": { + "type": "object" + } + } + }) + @xml_schema( + tag_name="idle", + mappings=[], + example=''' + + ''' + ) + async def idle(self) -> ToolResult: + """Indicate that the agent has completed all tasks and is entering idle state. + + Returns: + ToolResult indicating successful transition to idle state + """ + try: + return self.success_response("Entering idle state") + except Exception as e: + return self.fail_response(f"Error entering idle state: {str(e)}") + + +if __name__ == "__main__": + import asyncio + + async def test_message_tool(): + message_tool = MessageTool() + + # Test notification + notify_result = await message_tool.message_notify_user( + "Processing has completed successfully!", + attachments=["results.txt", "output.log"] + ) + print("Notification result:", notify_result) + + # Test question + ask_result = await message_tool.message_ask_user( + "Would you like to proceed with the next phase?", + attachments="summary.pdf", + suggest_user_takeover="browser" + ) + print("Question result:", ask_result) + + asyncio.run(test_message_tool()) diff --git a/backend/agent/workspace/index.html b/backend/agent/workspace/index.html new file mode 100644 index 00000000..dd334f3e --- /dev/null +++ b/backend/agent/workspace/index.html @@ -0,0 +1,331 @@ + + + + + + My Portfolio + + + + +
+
+

My Portfolio

+ +
+
+
+
+
+
+
+ +
+
+
+

Hello, I'm Your Name

+

Web Developer & Designer

+ +
+
+
+ +
+
+

About Me

+
+
+
+ +
+
+
+

Hello! I'm a passionate web developer with a keen eye for design and a love for creating seamless user experiences. With a background in [Your Background], I bring a unique perspective to every project I work on.

+

I enjoy solving complex problems and turning ideas into reality through clean and efficient code. When I'm not coding, you can find me [Your Hobbies/Interests].

+
+
+ + Education: [Your Education] +
+
+ + Experience: [Years] years +
+
+ + Location: [Your Location] +
+
+ Download Resume +
+
+
+
+ +
+
+

My Skills

+
+
+

Frontend Development

+
+
+
+

HTML5

+
+
+
+
+
+
+

CSS3

+
+
+
+
+
+
+

JavaScript

+
+
+
+
+
+
+

React

+
+
+
+
+
+
+ +
+

Other Skills

+
+
+
+

SQL

+
+
+
+
+
+
+

Git

+
+
+
+
+
+
+

Responsive Design

+
+
+
+
+
+
+

UI/UX

+
+
+
+
+
+
+
+
+
+ +
+
+

My Projects

+
+ + + + +
+
+
+
+
+ +
+
+
+

Project Title 1

+

A brief description of the project and your role in it. Explain the technologies used and the problems solved.

+
+ HTML + CSS + JavaScript +
+ +
+
+ +
+
+
+ +
+
+
+

Project Title 2

+

A brief description of the project and your role in it. Explain the technologies used and the problems solved.

+
+ Figma + UI/UX + Prototyping +
+ +
+
+ +
+
+
+ +
+
+
+

Project Title 3

+

A brief description of the project and your role in it. Explain the technologies used and the problems solved.

+
+ React + Node.js + MongoDB +
+ +
+
+ +
+
+
+ +
+
+
+

Project Title 4

+

A brief description of the project and your role in it. Explain the technologies used and the problems solved.

+
+ Python + Data Analysis + Visualization +
+ +
+
+
+
+
+ +
+
+

Get In Touch

+
+
+
+
+ +
+
+

Email

+

your.email@example.com

+
+
+
+
+ +
+
+

Phone

+

+1 (123) 456-7890

+
+
+
+
+ +
+
+

Location

+

City, Country

+
+
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+
+ + + +
+ +
+ + + + \ No newline at end of file diff --git a/backend/agentpress/response_processor.py b/backend/agentpress/response_processor.py index a5e63a9d..e03d12dc 100644 --- a/backend/agentpress/response_processor.py +++ b/backend/agentpress/response_processor.py @@ -768,7 +768,7 @@ class ResponseProcessor: continue # Validate required parameters - missing = [mapping.param_name for mapping in schema.mappings if mapping.param_name not in params] + missing = [mapping.param_name for mapping in schema.mappings if mapping.required and mapping.param_name not in params] if missing: logger.error(f"Missing required parameters: {missing}") logger.error(f"Current params: {params}") diff --git a/backend/agentpress/thread_manager.py b/backend/agentpress/thread_manager.py index 644cf276..85e90a7f 100644 --- a/backend/agentpress/thread_manager.py +++ b/backend/agentpress/thread_manager.py @@ -202,19 +202,7 @@ class ThreadManager: openapi_tool_schemas = self.tool_registry.get_openapi_schemas() logger.debug(f"Retrieved {len(openapi_tool_schemas) if openapi_tool_schemas else 0} OpenAPI tool schemas") - # 5. Track this agent run in the database - run_id = str(uuid.uuid4()) - client = await self.db.client - run_data = { - 'id': run_id, - 'thread_id': thread_id, - 'status': 'running', - 'started_at': 'now()', - } - await client.table('agent_runs').insert(run_data).execute() - logger.debug(f"Created agent run record with ID: {run_id}") - - # 6. Make LLM API call + # 5. Make LLM API call - removed agent run tracking logger.info("Making LLM API call") try: llm_response = await make_llm_api_call( @@ -228,17 +216,10 @@ class ThreadManager: ) logger.debug("Successfully received LLM API response") except Exception as e: - # Update agent_run status to error - await client.table('agent_runs').update({ - 'status': 'error', - 'error': str(e), - 'completed_at': 'now()' - }).eq('id', run_id).execute() - logger.error(f"Failed to make LLM API call: {str(e)}", exc_info=True) raise - # 7. Process LLM response using the ResponseProcessor + # 6. Process LLM response using the ResponseProcessor if stream: logger.info("Processing streaming response") response_generator = self.response_processor.process_streaming_response( @@ -247,32 +228,8 @@ class ThreadManager: config=processor_config ) - # Wrap the generator to update the agent_run when complete - async def wrapped_generator(): - responses = [] - try: - async for chunk in response_generator: - responses.append(chunk) - yield chunk - - # Update agent_run to completed when done - await client.table('agent_runs').update({ - 'status': 'completed', - 'responses': json.dumps(responses), - 'completed_at': 'now()' - }).eq('id', run_id).execute() - logger.debug(f"Updated agent run {run_id} to completed status") - except Exception as e: - # Update agent_run to error - await client.table('agent_runs').update({ - 'status': 'error', - 'error': str(e), - 'completed_at': 'now()' - }).eq('id', run_id).execute() - logger.error(f"Error in streaming response: {str(e)}", exc_info=True) - raise - - return wrapped_generator() + # Return the generator directly without agent run updates + return response_generator else: logger.info("Processing non-streaming response") try: @@ -281,23 +238,8 @@ class ThreadManager: thread_id=thread_id, config=processor_config ) - - # Update agent_run to completed - await client.table('agent_runs').update({ - 'status': 'completed', - 'responses': json.dumps([response]), - 'completed_at': 'now()' - }).eq('id', run_id).execute() - logger.debug(f"Updated agent run {run_id} to completed status") - return response except Exception as e: - # Update agent_run to error - await client.table('agent_runs').update({ - 'status': 'error', - 'error': str(e), - 'completed_at': 'now()' - }).eq('id', run_id).execute() logger.error(f"Error in non-streaming response: {str(e)}", exc_info=True) raise diff --git a/backend/agentpress/tool.py b/backend/agentpress/tool.py index bcf09851..c804602e 100644 --- a/backend/agentpress/tool.py +++ b/backend/agentpress/tool.py @@ -29,10 +29,12 @@ class XMLNodeMapping: param_name (str): Name of the function parameter node_type (str): Type of node ("element", "attribute", or "content") path (str): XPath-like path to the node ("." means root element) + required (bool): Whether the parameter is required (defaults to True) """ param_name: str node_type: str = "element" path: str = "." + required: bool = True @dataclass class XMLTagSchema: @@ -50,20 +52,22 @@ class XMLTagSchema: mappings: List[XMLNodeMapping] = field(default_factory=list) example: Optional[str] = None - def add_mapping(self, param_name: str, node_type: str = "element", path: str = ".") -> None: + def add_mapping(self, param_name: str, node_type: str = "element", path: str = ".", required: bool = True) -> None: """Add a new node mapping to the schema. Args: param_name: Name of the function parameter node_type: Type of node ("element", "attribute", or "content") path: XPath-like path to the node + required: Whether the parameter is required """ self.mappings.append(XMLNodeMapping( param_name=param_name, node_type=node_type, - path=path + path=path, + required=required )) - logger.debug(f"Added XML mapping for parameter '{param_name}' with type '{node_type}' at path '{path}'") + logger.debug(f"Added XML mapping for parameter '{param_name}' with type '{node_type}' at path '{path}', required={required}") @dataclass class ToolSchema: @@ -173,7 +177,7 @@ def openapi_schema(schema: Dict[str, Any]): def xml_schema( tag_name: str, - mappings: List[Dict[str, str]] = None, + mappings: List[Dict[str, Any]] = None, example: str = None ): """ @@ -185,6 +189,7 @@ def xml_schema( - param_name: Name of the function parameter - node_type: "element", "attribute", or "content" - path: Path to the node (default "." for root) + - required: Whether the parameter is required (default True) example: Optional example showing how to use the XML tag Example: @@ -213,7 +218,8 @@ def xml_schema( xml_schema.add_mapping( param_name=mapping["param_name"], node_type=mapping.get("node_type", "element"), - path=mapping.get("path", ".") + path=mapping.get("path", "."), + required=mapping.get("required", True) ) return _add_schema(func, ToolSchema( diff --git a/backend/services/llm.py b/backend/services/llm.py index 8c58e184..58d0c545 100644 --- a/backend/services/llm.py +++ b/backend/services/llm.py @@ -210,7 +210,7 @@ async def make_llm_api_call( for attempt in range(MAX_RETRIES): try: logger.debug(f"Attempt {attempt + 1}/{MAX_RETRIES}") - logger.debug(f"API request parameters: {json.dumps(params, indent=2)}") + # logger.debug(f"API request parameters: {json.dumps(params, indent=2)}") response = await litellm.acompletion(**params) logger.info(f"Successfully received API response from {model_name}")