diff --git a/agentpress/examples/example_agent/agent.py b/agentpress/examples/example_agent/agent.py index 87fc07a..1779b5d 100644 --- a/agentpress/examples/example_agent/agent.py +++ b/agentpress/examples/example_agent/agent.py @@ -7,16 +7,11 @@ import logging from typing import AsyncGenerator import sys -from agentpress.xml_tool_parser import XMLToolParser -from agentpress.xml_tool_executor import XMLToolExecutor -from agentpress.xml_results_adder import XMLResultsAdder async def run_agent(thread_id: str, max_iterations: int = 5): - # Initialize managers and tools thread_manager = ThreadManager() state_manager = StateManager() - # Initialize tools thread_manager.add_tool(FilesTool) thread_manager.add_tool(TerminalTool) @@ -24,12 +19,10 @@ async def init(): pass async def pre_iteration(): - # Update files state files_tool = FilesTool() await files_tool._init_workspace_state() async def after_iteration(): - # Ask the user for a custom message or use the default custom_message = input("Enter a message to send (or press Enter to use 'Continue!!!' as message): ") message_content = custom_message if custom_message else """ @@ -151,8 +144,8 @@ async def finalizer(): max_tokens=8096, tool_choice="auto", temporary_message=state_message, - native_tool_calling=True, - xml_tool_calling=False, + native_tool_calling=False, + xml_tool_calling=True, stream=True, execute_tools_on_stream=True, parallel_tool_execution=True diff --git a/agentpress/examples/example_agent/tools/files_tool.py b/agentpress/examples/example_agent/tools/files_tool.py index 485d52f..c27159b 100644 --- a/agentpress/examples/example_agent/tools/files_tool.py +++ b/agentpress/examples/example_agent/tools/files_tool.py @@ -138,7 +138,11 @@ async def _update_workspace_state(self): {"param_name": "file_path", "node_type": "attribute", "path": "."}, {"param_name": "file_contents", "node_type": "content", "path": "."} ], - description="Create a new file with the provided contents" + example=''' + + File contents go here + + ''' ) async def create_file(self, file_path: str, file_contents: str) -> ToolResult: try: @@ -177,7 +181,10 @@ async def create_file(self, file_path: str, file_contents: str) -> ToolResult: mappings=[ {"param_name": "file_path", "node_type": "attribute", "path": "."} ], - description="Delete a file at the given path" + example=''' + + + ''' ) async def delete_file(self, file_path: str) -> ToolResult: try: @@ -221,7 +228,12 @@ async def delete_file(self, file_path: str) -> ToolResult: {"param_name": "old_str", "node_type": "element", "path": "old_str"}, {"param_name": "new_str", "node_type": "element", "path": "new_str"} ], - description="Replace text in a file" + example=''' + + text to replace + replacement text + + ''' ) async def str_replace(self, file_path: str, old_str: str, new_str: str) -> ToolResult: try: diff --git a/agentpress/examples/example_agent/tools/terminal_tool.py b/agentpress/examples/example_agent/tools/terminal_tool.py index f5ad66a..5bd7eb7 100644 --- a/agentpress/examples/example_agent/tools/terminal_tool.py +++ b/agentpress/examples/example_agent/tools/terminal_tool.py @@ -46,7 +46,11 @@ async def _update_command_history(self, command: str, output: str, success: bool mappings=[ {"param_name": "command", "node_type": "content", "path": "."} ], - description="Execute a shell command in the workspace directory" + example=''' + + npm install package-name + + ''' ) async def execute_command(self, command: str) -> ToolResult: original_dir = os.getcwd() diff --git a/agentpress/examples/example_agent/workspace/index.html b/agentpress/examples/example_agent/workspace/index.html deleted file mode 100644 index abf96d6..0000000 --- a/agentpress/examples/example_agent/workspace/index.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - Modern Landing Page - - - -
- -
- -
-
-
-

Welcome to the Future

-

Transform your digital presence with our innovative solutions

-
- - -
-
-
-
- -
-

Our Features

-
-
-
🚀
-

Fast Performance

-

Lightning-quick load times and smooth interactions

-
-
-
🎨
-

Beautiful Design

-

Stunning visuals that capture attention

-
-
-
📱
-

Responsive

-

Perfect display on all devices

-
-
-
- -
-
-

About Us

-

We're dedicated to creating exceptional digital experiences that drive results.

-
-
- -
-

Get in Touch

-
- - - - -
-
-
- - - - - - \ No newline at end of file diff --git a/agentpress/examples/example_agent/workspace/script.js b/agentpress/examples/example_agent/workspace/script.js deleted file mode 100644 index fdc5510..0000000 --- a/agentpress/examples/example_agent/workspace/script.js +++ /dev/null @@ -1,61 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - const mobileNavToggle = document.querySelector('.mobile-nav-toggle'); - const navLinks = document.querySelector('.nav-links'); - const header = document.querySelector('.header'); - let lastScroll = 0; - - mobileNavToggle.addEventListener('click', () => { - navLinks.classList.toggle('active'); - const spans = mobileNavToggle.querySelectorAll('span'); - spans[0].style.transform = navLinks.classList.contains('active') ? 'rotate(45deg) translate(8px, 8px)' : ''; - spans[1].style.opacity = navLinks.classList.contains('active') ? '0' : '1'; - spans[2].style.transform = navLinks.classList.contains('active') ? 'rotate(-45deg) translate(7px, -7px)' : ''; - }); - - window.addEventListener('scroll', () => { - const currentScroll = window.pageYOffset; - - if (currentScroll <= 0) { - header.style.transform = 'translateY(0)'; - return; - } - - if (currentScroll > lastScroll && !header.classList.contains('scroll-down')) { - header.style.transform = 'translateY(-100%)'; - } else if (currentScroll < lastScroll && header.classList.contains('scroll-down')) { - header.style.transform = 'translateY(0)'; - } - - lastScroll = currentScroll; - }); - - const observerOptions = { - threshold: 0.1, - rootMargin: '0px 0px -50px 0px' - }; - - const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - entry.target.style.opacity = '1'; - entry.target.style.transform = 'translateY(0)'; - } - }); - }, observerOptions); - - document.querySelectorAll('section').forEach(section => { - section.style.opacity = '0'; - section.style.transform = 'translateY(20px)'; - section.style.transition = 'opacity 0.6s ease-out, transform 0.6s ease-out'; - observer.observe(section); - }); - - const form = document.querySelector('.contact-form'); - form.addEventListener('submit', (e) => { - e.preventDefault(); - const formData = new FormData(form); - const data = Object.fromEntries(formData); - console.log('Form submitted:', data); - form.reset(); - }); -}); \ No newline at end of file diff --git a/agentpress/examples/example_agent/workspace/styles.css b/agentpress/examples/example_agent/workspace/styles.css deleted file mode 100644 index 9e05405..0000000 --- a/agentpress/examples/example_agent/workspace/styles.css +++ /dev/null @@ -1,343 +0,0 @@ -:root { - --primary-color: #6366f1; - --secondary-color: #4f46e5; - --text-color: #18181b; - --light-text: #71717a; - --background: #ffffff; - --glass-bg: rgba(255, 255, 255, 0.7); - --glass-border: rgba(255, 255, 255, 0.3); - --section-padding: 5rem 2rem; - --gradient-1: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%); - --gradient-2: linear-gradient(135deg, #c084fc 0%, #a855f7 100%); - --shadow-1: 0 10px 30px -10px rgba(99, 102, 241, 0.2); - --shadow-2: 0 20px 40px -15px rgba(99, 102, 241, 0.1); -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; - line-height: 1.6; - color: var(--text-color); -} - -.header { - position: fixed; - width: 100%; - background: var(--glass-bg); - backdrop-filter: blur(10px); - border-bottom: 1px solid var(--glass-border); - box-shadow: var(--shadow-1); - z-index: 1000; -} - -.nav { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1rem 2rem; - max-width: 1200px; - margin: 0 auto; -} - -.logo { - font-size: 1.5rem; - font-weight: bold; - color: var(--primary-color); -} - -.nav-links { - display: flex; - gap: 2rem; - list-style: none; -} - -.nav-links a { - text-decoration: none; - color: var(--text-color); - font-weight: 500; - transition: color 0.3s ease; -} - -.nav-links a:hover { - color: var(--primary-color); -} - -.mobile-nav-toggle { - display: none; -} - -.hero { - height: 100vh; - display: flex; - align-items: center; - justify-content: center; - text-align: center; - background: var(--gradient-1); - position: relative; - overflow: hidden; - padding: var(--section-padding); -} - -.hero::before { - content: ''; - position: absolute; - width: 150%; - height: 150%; - background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 60%); - animation: rotate 20s linear infinite; -} - -@keyframes rotate { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} - -.hero-content { - max-width: 800px; - position: relative; - z-index: 1; - background: var(--glass-bg); - backdrop-filter: blur(10px); - padding: 3rem; - border-radius: 1rem; - border: 1px solid var(--glass-border); - box-shadow: var(--shadow-2); -} - -.hero h1 { - font-size: 3.5rem; - margin-bottom: 1rem; - line-height: 1.2; -} - -.hero p { - font-size: 1.25rem; - color: var(--light-text); - margin-bottom: 2rem; -} - -.cta-group { - display: flex; - gap: 1rem; - justify-content: center; - margin-top: 2rem; -} - -.cta-button { - padding: 1rem 2rem; - font-size: 1.1rem; - border: none; - border-radius: 0.5rem; - cursor: pointer; - transition: all 0.3s ease; - position: relative; - overflow: hidden; -} - -.cta-button::before { - content: ''; - position: absolute; - top: 50%; - left: 50%; - width: 0; - height: 0; - background: rgba(255, 255, 255, 0.2); - border-radius: 50%; - transform: translate(-50%, -50%); - transition: width 0.6s ease, height 0.6s ease; -} - -.cta-button:hover::before { - width: 300%; - height: 300%; -} - -.cta-button.primary { - background: var(--gradient-1); - color: white; - box-shadow: var(--shadow-1); -} - -.cta-button.secondary { - background: var(--glass-bg); - color: var(--primary-color); - border: 1px solid var(--primary-color); -} - -.cta-button:hover { - background: var(--secondary-color); -} - -.features { - padding: var(--section-padding); - background: var(--background); -} - -.features h2 { - text-align: center; - margin-bottom: 3rem; - font-size: 2.5rem; -} - -.features-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 2rem; - max-width: 1200px; - margin: 0 auto; -} - -.feature-card { - padding: 2rem; - text-align: center; - background: var(--glass-bg); - backdrop-filter: blur(10px); - border: 1px solid var(--glass-border); - border-radius: 1rem; - transition: all 0.3s ease; - box-shadow: var(--shadow-1); -} - -.feature-card:hover { - transform: translateY(-5px) scale(1.02); - box-shadow: var(--shadow-2); - background: var(--gradient-2); - color: white; -} - -.feature-icon { - font-size: 2.5rem; - margin-bottom: 1rem; -} - -.about { - padding: var(--section-padding); - background: #f3f4f6; -} - -.about-content { - max-width: 800px; - margin: 0 auto; - text-align: center; -} - -.about h2 { - font-size: 2.5rem; - margin-bottom: 1.5rem; -} - -.contact { - padding: var(--section-padding); - background: var(--background); -} - -.contact h2 { - text-align: center; - margin-bottom: 3rem; - font-size: 2.5rem; -} - -.contact-form { - display: flex; - flex-direction: column; - gap: 1rem; - max-width: 600px; - margin: 0 auto; -} - -.contact-form input, -.contact-form textarea { - padding: 1rem; - background: var(--glass-bg); - backdrop-filter: blur(10px); - border: 1px solid var(--glass-border); - border-radius: 0.5rem; - font-size: 1rem; - transition: all 0.3s ease; -} - -.contact-form input:focus, -.contact-form textarea:focus { - outline: none; - border-color: var(--primary-color); - box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2); - transform: translateY(-2px); -} - -.contact-form textarea { - height: 150px; - resize: vertical; -} - -.contact-form button { - padding: 1rem; - background: var(--primary-color); - color: white; - border: none; - border-radius: 0.5rem; - cursor: pointer; - transition: background-color 0.3s ease; -} - -.contact-form button:hover { - background: var(--secondary-color); -} - -.footer { - padding: 2rem; - text-align: center; - background: #f3f4f6; -} - -@media (max-width: 768px) { - .nav-links { - display: none; - position: absolute; - top: 100%; - left: 0; - right: 0; - background: var(--background); - padding: 1rem; - flex-direction: column; - align-items: center; - gap: 1rem; - } - - .nav-links.active { - display: flex; - } - - .mobile-nav-toggle { - display: block; - background: none; - border: none; - cursor: pointer; - padding: 0.5rem; - } - - .mobile-nav-toggle span { - display: block; - width: 25px; - height: 3px; - background: var(--text-color); - margin: 5px 0; - transition: 0.3s; - } - - .hero h1 { - font-size: 2.5rem; - } - - .features-grid { - grid-template-columns: 1fr; - } -} \ No newline at end of file diff --git a/agentpress/thread_manager.py b/agentpress/thread_manager.py index 51c82ce..17c568f 100644 --- a/agentpress/thread_manager.py +++ b/agentpress/thread_manager.py @@ -1,13 +1,13 @@ import json import logging import os +import uuid from typing import List, Dict, Any, Optional, Type, Union, AsyncGenerator from agentpress.llm import make_llm_api_call from agentpress.tool import Tool, ToolResult from agentpress.tool_registry import ToolRegistry from agentpress.llm_response_processor import LLMResponseProcessor from agentpress.base_processors import ToolParserBase, ToolExecutorBase, ResultsAdderBase -import uuid from agentpress.xml_tool_parser import XMLToolParser from agentpress.xml_tool_executor import XMLToolExecutor diff --git a/agentpress/tool.py b/agentpress/tool.py index bde9ac5..6c8dccb 100644 --- a/agentpress/tool.py +++ b/agentpress/tool.py @@ -26,7 +26,7 @@ class XMLTagSchema: """Schema for XML tool tags with improved node mapping""" tag_name: str # Root tag name (e.g. "str-replace") mappings: List[XMLNodeMapping] = field(default_factory=list) - description: Optional[str] = None + example: Optional[str] = None # Changed from description to example def add_mapping(self, param_name: str, node_type: str = "element", path: str = ".") -> None: """Add a new node mapping""" @@ -97,7 +97,7 @@ def decorator(func): def xml_schema( tag_name: str, mappings: List[Dict[str, str]] = None, - description: str = None + example: str = None # Changed from description to example ): """ Decorator for XML schema tools with improved node mapping. @@ -108,7 +108,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) - description: Optional description of the tool + example: Optional example showing how to use the XML tag Example: @xml_schema( @@ -118,11 +118,16 @@ def xml_schema( {"param_name": "old_str", "node_type": "element", "path": "old_str"}, {"param_name": "new_str", "node_type": "element", "path": "new_str"} ], - description="Replace text in a file" + example=''' + + text to replace + replacement text + + ''' ) """ def decorator(func): - xml_schema = XMLTagSchema(tag_name=tag_name, description=description) + xml_schema = XMLTagSchema(tag_name=tag_name, example=example) # Add mappings if mappings: diff --git a/agentpress/tool_registry.py b/agentpress/tool_registry.py index d3b079d..0e9ce88 100644 --- a/agentpress/tool_registry.py +++ b/agentpress/tool_registry.py @@ -129,3 +129,16 @@ def get_openapi_schemas(self) -> List[Dict[str, Any]]: for tool_info in self.tools.values() if tool_info['schema'].schema_type == SchemaType.OPENAPI ] + + def get_xml_examples(self) -> Dict[str, str]: + """Get all XML tag examples from registered tools. + + Returns: + Dict[str, str]: Dictionary mapping tag names to their examples + """ + examples = {} + for tool_info in self.xml_tools.values(): + schema = tool_info['schema'] + if schema.xml_schema and schema.xml_schema.example: + examples[schema.xml_schema.tag_name] = schema.xml_schema.example + return examples diff --git a/agentpress/xml_results_adder.py b/agentpress/xml_results_adder.py index 47aa519..af7fae3 100644 --- a/agentpress/xml_results_adder.py +++ b/agentpress/xml_results_adder.py @@ -11,68 +11,65 @@ class XMLResultsAdder(ResultsAdderBase): def __init__(self, thread_manager): super().__init__(thread_manager) - self.pending_tool_results = {} - - def _format_xml_response(self, content: str, tool_calls: Optional[List[Dict[str, Any]]] = None) -> str: - """Format the response content with XML tool results.""" - response_parts = [] - - # Add any non-XML content first - non_xml_content = [] - lines = content.split('\n') - for line in lines: - if not (line.strip().startswith('<') and line.strip().endswith('>')): - non_xml_content.append(line) - if non_xml_content: - response_parts.append('\n'.join(non_xml_content)) - - # Add XML blocks with their results - if tool_calls: - for tool_call in tool_calls: - tool_id = tool_call['id'] - if tool_id in self.pending_tool_results: - result = self.pending_tool_results[tool_id] - response_parts.append( - f"\n" - f"{result}\n" - f"" - ) - - return '\n\n'.join(response_parts) + self.message_added = False async def add_initial_response(self, thread_id: str, content: str, tool_calls: Optional[List[Dict[str, Any]]] = None): - """Add initial response with XML formatting.""" - formatted_content = self._format_xml_response(content, tool_calls) + """Add initial response without modifications.""" message = { "role": "assistant", - "content": formatted_content + "content": content } await self.add_message(thread_id, message) self.message_added = True async def update_response(self, thread_id: str, content: str, tool_calls: Optional[List[Dict[str, Any]]] = None): - """Update response with XML formatting.""" + """Update response without modifications.""" if not self.message_added: await self.add_initial_response(thread_id, content, tool_calls) return - formatted_content = self._format_xml_response(content, tool_calls) message = { "role": "assistant", - "content": formatted_content + "content": content } await self.update_message(thread_id, message) async def add_tool_result(self, thread_id: str, result: Dict[str, Any]): - """Store tool result for inclusion in the XML response.""" - tool_call_id = result['tool_call_id'] - self.pending_tool_results[tool_call_id] = result['content'] - - # Update the message to include the new result - messages = await self.list_messages(thread_id) - for msg in reversed(messages): - if msg['role'] == 'assistant': - content = msg['content'] - tool_calls = msg.get('tool_calls', []) - await self.update_response(thread_id, content, tool_calls) - break \ No newline at end of file + """Add tool result as a user message.""" + try: + # Get the original tool call to find the root tag + messages = await self.list_messages(thread_id) + assistant_msg = next((msg for msg in reversed(messages) + if msg['role'] == 'assistant'), None) + + if assistant_msg: + content = assistant_msg['content'] + # Find the opening XML tag for this tool call + tool_start = content.find(f'<{result["name"]}') + if tool_start >= 0: + tag_end = content.find('>', tool_start) + if tag_end >= 0: + root_tag = content[tool_start:tag_end + 1] + # Create a simple reference message as user role + result_message = { + "role": "user", + "content": f"Result for {root_tag}\n{result['content']}" + } + await self.add_message(thread_id, result_message) + return + + # Fallback if we can't find the root tag + result_message = { + "role": "user", + "content": f"Result for {result['name']}:\n{result['content']}" + } + await self.add_message(thread_id, result_message) + + except Exception as e: + logging.error(f"Error adding tool result: {e}") + # Ensure the result is still added even if there's an error + result_message = { + "role": "user", + "content": f"Result for {result['name']}:\n{result['content']}" + } + await self.add_message(thread_id, result_message) \ No newline at end of file