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
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 run_agent(thread_id: str, max_iterations: int = 5):
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 @@ Current development environment workspace state:
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

View File

@ -138,7 +138,11 @@ class FilesTool(Tool):
{"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='''
<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:
try:
@ -177,7 +181,10 @@ class FilesTool(Tool):
mappings=[
{"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:
try:
@ -221,7 +228,12 @@ class FilesTool(Tool):
{"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='''
<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:
try:

View File

@ -46,7 +46,11 @@ class TerminalTool(Tool):
mappings=[
{"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:
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 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

View File

@ -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 openapi_schema(schema: Dict[str, Any]):
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='''
<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):
xml_schema = XMLTagSchema(tag_name=tag_name, description=description)
xml_schema = XMLTagSchema(tag_name=tag_name, example=example)
# Add mappings
if mappings:

View File

@ -129,3 +129,16 @@ class ToolRegistry:
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

View File

@ -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"<tool-result id='{tool_id}'>\n"
f"{result}\n"
f"</tool-result>"
)
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
"""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)