proper xml schema result adding as user message, xml schema example

This commit is contained in:
marko-kraemer 2024-11-18 06:03:58 +01:00
parent 320dabb99d
commit 53557718e4
10 changed files with 89 additions and 554 deletions

View File

@ -7,16 +7,11 @@ from tools.terminal_tool import TerminalTool
import logging import logging
from typing import AsyncGenerator from typing import AsyncGenerator
import sys 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): async def run_agent(thread_id: str, max_iterations: int = 5):
# Initialize managers and tools
thread_manager = ThreadManager() thread_manager = ThreadManager()
state_manager = StateManager() state_manager = StateManager()
# Initialize tools
thread_manager.add_tool(FilesTool) thread_manager.add_tool(FilesTool)
thread_manager.add_tool(TerminalTool) thread_manager.add_tool(TerminalTool)
@ -24,12 +19,10 @@ async def run_agent(thread_id: str, max_iterations: int = 5):
pass pass
async def pre_iteration(): async def pre_iteration():
# Update files state
files_tool = FilesTool() files_tool = FilesTool()
await files_tool._init_workspace_state() await files_tool._init_workspace_state()
async def after_iteration(): 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): ") custom_message = input("Enter a message to send (or press Enter to use 'Continue!!!' as message): ")
message_content = custom_message if custom_message else """ message_content = custom_message if custom_message else """
@ -151,8 +144,8 @@ Current development environment workspace state:
max_tokens=8096, max_tokens=8096,
tool_choice="auto", tool_choice="auto",
temporary_message=state_message, temporary_message=state_message,
native_tool_calling=True, native_tool_calling=False,
xml_tool_calling=False, xml_tool_calling=True,
stream=True, stream=True,
execute_tools_on_stream=True, execute_tools_on_stream=True,
parallel_tool_execution=True parallel_tool_execution=True

View File

@ -138,7 +138,11 @@ class FilesTool(Tool):
{"param_name": "file_path", "node_type": "attribute", "path": "."}, {"param_name": "file_path", "node_type": "attribute", "path": "."},
{"param_name": "file_contents", "node_type": "content", "path": "."} {"param_name": "file_contents", "node_type": "content", "path": "."}
], ],
description="Create a new file with the provided contents" example='''
<create-file file_path="path/to/file">
File contents go here
</create-file>
'''
) )
async def create_file(self, file_path: str, file_contents: str) -> ToolResult: async def create_file(self, file_path: str, file_contents: str) -> ToolResult:
try: try:
@ -177,7 +181,10 @@ class FilesTool(Tool):
mappings=[ mappings=[
{"param_name": "file_path", "node_type": "attribute", "path": "."} {"param_name": "file_path", "node_type": "attribute", "path": "."}
], ],
description="Delete a file at the given path" example='''
<delete-file file_path="path/to/file">
</delete-file>
'''
) )
async def delete_file(self, file_path: str) -> ToolResult: async def delete_file(self, file_path: str) -> ToolResult:
try: try:
@ -221,7 +228,12 @@ class FilesTool(Tool):
{"param_name": "old_str", "node_type": "element", "path": "old_str"}, {"param_name": "old_str", "node_type": "element", "path": "old_str"},
{"param_name": "new_str", "node_type": "element", "path": "new_str"} {"param_name": "new_str", "node_type": "element", "path": "new_str"}
], ],
description="Replace text in a file" example='''
<str-replace file_path="path/to/file">
<old_str>text to replace</old_str>
<new_str>replacement text</new_str>
</str-replace>
'''
) )
async def str_replace(self, file_path: str, old_str: str, new_str: str) -> ToolResult: async def str_replace(self, file_path: str, old_str: str, new_str: str) -> ToolResult:
try: try:

View File

@ -46,7 +46,11 @@ class TerminalTool(Tool):
mappings=[ mappings=[
{"param_name": "command", "node_type": "content", "path": "."} {"param_name": "command", "node_type": "content", "path": "."}
], ],
description="Execute a shell command in the workspace directory" example='''
<execute-command>
npm install package-name
</execute-command>
'''
) )
async def execute_command(self, command: str) -> ToolResult: async def execute_command(self, command: str) -> ToolResult:
original_dir = os.getcwd() original_dir = os.getcwd()

View File

@ -1,85 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern Landing Page</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header class="header">
<nav class="nav">
<div class="logo">Brand</div>
<ul class="nav-links">
<li><a href="#home">Home</a></li>
<li><a href="#features">Features</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
<button class="mobile-nav-toggle" aria-label="Menu">
<span></span>
<span></span>
<span></span>
</button>
</nav>
</header>
<main>
<section id="home" class="hero">
<div class="hero-content">
<h1>Welcome to the Future</h1>
<p>Transform your digital presence with our innovative solutions</p>
<div class="cta-group">
<button class="cta-button primary">Get Started</button>
<button class="cta-button secondary">Learn More</button>
</div>
</div>
<div class="hero-shape"></div>
</section>
<section id="features" class="features">
<h2>Our Features</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">🚀</div>
<h3>Fast Performance</h3>
<p>Lightning-quick load times and smooth interactions</p>
</div>
<div class="feature-card">
<div class="feature-icon">🎨</div>
<h3>Beautiful Design</h3>
<p>Stunning visuals that capture attention</p>
</div>
<div class="feature-card">
<div class="feature-icon">📱</div>
<h3>Responsive</h3>
<p>Perfect display on all devices</p>
</div>
</div>
</section>
<section id="about" class="about">
<div class="about-content">
<h2>About Us</h2>
<p>We're dedicated to creating exceptional digital experiences that drive results.</p>
</div>
</section>
<section id="contact" class="contact">
<h2>Get in Touch</h2>
<form class="contact-form">
<input type="text" placeholder="Name" required>
<input type="email" placeholder="Email" required>
<textarea placeholder="Message" required></textarea>
<button type="submit">Send Message</button>
</form>
</section>
</main>
<footer class="footer">
<p>&copy; 2024 Brand. All rights reserved.</p>
</footer>
<script src="script.js"></script>
</body>
</html>

View File

@ -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();
});
});

View File

@ -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;
}
}

View File

@ -1,13 +1,13 @@
import json import json
import logging import logging
import os import os
import uuid
from typing import List, Dict, Any, Optional, Type, Union, AsyncGenerator from typing import List, Dict, Any, Optional, Type, Union, AsyncGenerator
from agentpress.llm import make_llm_api_call from agentpress.llm import make_llm_api_call
from agentpress.tool import Tool, ToolResult from agentpress.tool import Tool, ToolResult
from agentpress.tool_registry import ToolRegistry from agentpress.tool_registry import ToolRegistry
from agentpress.llm_response_processor import LLMResponseProcessor from agentpress.llm_response_processor import LLMResponseProcessor
from agentpress.base_processors import ToolParserBase, ToolExecutorBase, ResultsAdderBase from agentpress.base_processors import ToolParserBase, ToolExecutorBase, ResultsAdderBase
import uuid
from agentpress.xml_tool_parser import XMLToolParser from agentpress.xml_tool_parser import XMLToolParser
from agentpress.xml_tool_executor import XMLToolExecutor from agentpress.xml_tool_executor import XMLToolExecutor

View File

@ -26,7 +26,7 @@ class XMLTagSchema:
"""Schema for XML tool tags with improved node mapping""" """Schema for XML tool tags with improved node mapping"""
tag_name: str # Root tag name (e.g. "str-replace") tag_name: str # Root tag name (e.g. "str-replace")
mappings: List[XMLNodeMapping] = field(default_factory=list) 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: def add_mapping(self, param_name: str, node_type: str = "element", path: str = ".") -> None:
"""Add a new node mapping""" """Add a new node mapping"""
@ -97,7 +97,7 @@ def openapi_schema(schema: Dict[str, Any]):
def xml_schema( def xml_schema(
tag_name: str, tag_name: str,
mappings: List[Dict[str, str]] = None, 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. Decorator for XML schema tools with improved node mapping.
@ -108,7 +108,7 @@ def xml_schema(
- param_name: Name of the function parameter - param_name: Name of the function parameter
- node_type: "element", "attribute", or "content" - node_type: "element", "attribute", or "content"
- path: Path to the node (default "." for root) - 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: Example:
@xml_schema( @xml_schema(
@ -118,11 +118,16 @@ def xml_schema(
{"param_name": "old_str", "node_type": "element", "path": "old_str"}, {"param_name": "old_str", "node_type": "element", "path": "old_str"},
{"param_name": "new_str", "node_type": "element", "path": "new_str"} {"param_name": "new_str", "node_type": "element", "path": "new_str"}
], ],
description="Replace text in a file" example='''
<str-replace file_path="path/to/file">
<old_str>text to replace</old_str>
<new_str>replacement text</new_str>
</str-replace>
'''
) )
""" """
def decorator(func): def decorator(func):
xml_schema = XMLTagSchema(tag_name=tag_name, description=description) xml_schema = XMLTagSchema(tag_name=tag_name, example=example)
# Add mappings # Add mappings
if mappings: if mappings:

View File

@ -129,3 +129,16 @@ class ToolRegistry:
for tool_info in self.tools.values() for tool_info in self.tools.values()
if tool_info['schema'].schema_type == SchemaType.OPENAPI 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

View File

@ -11,68 +11,65 @@ class XMLResultsAdder(ResultsAdderBase):
def __init__(self, thread_manager): def __init__(self, thread_manager):
super().__init__(thread_manager) super().__init__(thread_manager)
self.pending_tool_results = {} self.message_added = False
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"<tool-result id='{tool_id}'>\n"
f"{result}\n"
f"</tool-result>"
)
return '\n\n'.join(response_parts)
async def add_initial_response(self, thread_id: str, content: str, tool_calls: Optional[List[Dict[str, Any]]] = None): 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.""" """Add initial response without modifications."""
formatted_content = self._format_xml_response(content, tool_calls)
message = { message = {
"role": "assistant", "role": "assistant",
"content": formatted_content "content": content
} }
await self.add_message(thread_id, message) await self.add_message(thread_id, message)
self.message_added = True self.message_added = True
async def update_response(self, thread_id: str, content: str, tool_calls: Optional[List[Dict[str, Any]]] = None): 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: if not self.message_added:
await self.add_initial_response(thread_id, content, tool_calls) await self.add_initial_response(thread_id, content, tool_calls)
return return
formatted_content = self._format_xml_response(content, tool_calls)
message = { message = {
"role": "assistant", "role": "assistant",
"content": formatted_content "content": content
} }
await self.update_message(thread_id, message) await self.update_message(thread_id, message)
async def add_tool_result(self, thread_id: str, result: Dict[str, Any]): async def add_tool_result(self, thread_id: str, result: Dict[str, Any]):
"""Store tool result for inclusion in the XML response.""" """Add tool result as a user message."""
tool_call_id = result['tool_call_id'] try:
self.pending_tool_results[tool_call_id] = result['content'] # 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)
# Update the message to include the new result if assistant_msg:
messages = await self.list_messages(thread_id) content = assistant_msg['content']
for msg in reversed(messages): # Find the opening XML tag for this tool call
if msg['role'] == 'assistant': tool_start = content.find(f'<{result["name"]}')
content = msg['content'] if tool_start >= 0:
tool_calls = msg.get('tool_calls', []) tag_end = content.find('>', tool_start)
await self.update_response(thread_id, content, tool_calls) if tag_end >= 0:
break 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)