mirror of https://github.com/kortix-ai/suna.git
native tool call wip
This commit is contained in:
parent
91081bc646
commit
6eb516e61d
|
@ -22,7 +22,6 @@ Remember:
|
|||
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
|
||||
|
||||
<available_tools>
|
||||
You have access to these tools through XML-based tool calling:
|
||||
|
@ -38,12 +37,12 @@ You have access to these tools through XML-based tool calling:
|
|||
|
||||
"""
|
||||
|
||||
|
||||
#Wait for each action to complete before proceeding to the next one.
|
||||
RESPONSE_FORMAT = """
|
||||
<response_format>
|
||||
RESPONSE FORMAT – STRICTLY Output XML tags for tool calling
|
||||
|
||||
You must only use ONE tool call at a time. Wait for each action to complete before proceeding to the next one.
|
||||
|
||||
<create-file file_path="path/to/file">
|
||||
file contents here
|
||||
</create-file>
|
||||
|
|
|
@ -4,7 +4,6 @@ import uuid
|
|||
from agentpress.thread_manager import ThreadManager
|
||||
from agent.tools.files_tool import FilesTool
|
||||
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.prompt import get_system_prompt
|
||||
|
@ -23,7 +22,6 @@ async def run_agent(thread_id: str, stream: bool = True, thread_manager: Optiona
|
|||
print("Adding tools to thread manager...")
|
||||
thread_manager.add_tool(FilesTool)
|
||||
thread_manager.add_tool(TerminalTool)
|
||||
thread_manager.add_tool(WaitTool)
|
||||
# thread_manager.add_tool(CodeSearchTool)
|
||||
|
||||
system_message = {
|
||||
|
@ -31,7 +29,12 @@ async def run_agent(thread_id: str, stream: bool = True, thread_manager: Optiona
|
|||
"content": get_system_prompt()
|
||||
}
|
||||
|
||||
model_name = "bedrock/anthropic.claude-3-7-sonnet-20250219-v1:0" #groq/deepseek-r1-distill-llama-70b
|
||||
model_name = "anthropic/claude-3-5-sonnet-latest"
|
||||
|
||||
#anthropic/claude-3-7-sonnet-latest
|
||||
#openai/gpt-4o
|
||||
#groq/deepseek-r1-distill-llama-70b
|
||||
#bedrock/anthropic.claude-3-7-sonnet-20250219-v1:0
|
||||
|
||||
files_tool = FilesTool()
|
||||
|
||||
|
@ -55,12 +58,13 @@ Current development environment workspace state:
|
|||
llm_model=model_name,
|
||||
llm_temperature=0.1,
|
||||
llm_max_tokens=8000,
|
||||
tool_choice="any",
|
||||
processor_config=ProcessorConfig(
|
||||
xml_tool_calling=False,
|
||||
native_tool_calling=True,
|
||||
execute_tools=True,
|
||||
execute_on_stream=True,
|
||||
tool_execution_strategy="sequential",
|
||||
tool_execution_strategy="parallel",
|
||||
xml_adding_strategy="user_message"
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,176 +0,0 @@
|
|||
"""
|
||||
Wait Tool for testing sequential vs parallel tool execution.
|
||||
|
||||
This tool provides methods with configurable delays to test and demonstrate
|
||||
the different tool execution strategies in AgentPress.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from agentpress.tool import Tool, ToolResult, openapi_schema, xml_schema
|
||||
from utils.logger import logger
|
||||
|
||||
|
||||
class WaitTool(Tool):
|
||||
"""Tool that introduces configurable delays.
|
||||
|
||||
This tool is useful for testing and demonstrating sequential vs parallel
|
||||
tool execution strategies by creating observable delays.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the WaitTool."""
|
||||
super().__init__()
|
||||
logger.info("Initialized WaitTool for testing execution strategies")
|
||||
|
||||
@xml_schema(
|
||||
tag_name="wait",
|
||||
mappings=[
|
||||
{"param_name": "seconds", "node_type": "attribute", "path": "."},
|
||||
{"param_name": "message", "node_type": "content", "path": "."}
|
||||
],
|
||||
example='''
|
||||
<wait seconds="3">This will wait for 3 seconds</wait>
|
||||
'''
|
||||
)
|
||||
@openapi_schema({
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "wait",
|
||||
"description": "Wait for a specified number of seconds",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"seconds": {
|
||||
"type": "number",
|
||||
"description": "Number of seconds to wait"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "Message to include in the result"
|
||||
}
|
||||
},
|
||||
"required": ["seconds"]
|
||||
}
|
||||
}
|
||||
})
|
||||
async def wait(self, seconds: float, message: str = "") -> ToolResult:
|
||||
"""Wait for the specified number of seconds.
|
||||
|
||||
Args:
|
||||
seconds: Number of seconds to wait
|
||||
message: Optional message to include in result
|
||||
|
||||
Returns:
|
||||
ToolResult with success status and timing information
|
||||
"""
|
||||
try:
|
||||
# Limit the wait time to a reasonable range
|
||||
seconds = min(max(0.5, float(seconds)), 10.0)
|
||||
|
||||
logger.info(f"WaitTool: Starting wait for {seconds} seconds")
|
||||
start_time = asyncio.get_event_loop().time()
|
||||
|
||||
# Perform the actual wait
|
||||
await asyncio.sleep(seconds)
|
||||
|
||||
end_time = asyncio.get_event_loop().time()
|
||||
elapsed = end_time - start_time
|
||||
|
||||
logger.info(f"WaitTool: Completed wait of {elapsed:.2f} seconds")
|
||||
|
||||
# Format the result
|
||||
if message:
|
||||
result = f"Waited for {elapsed:.2f} seconds with message: {message}"
|
||||
else:
|
||||
result = f"Waited for {elapsed:.2f} seconds"
|
||||
|
||||
return self.success_response(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"WaitTool error: {str(e)}")
|
||||
return self.fail_response(f"Error during wait operation: {str(e)}")
|
||||
|
||||
@xml_schema(
|
||||
tag_name="wait-sequence",
|
||||
mappings=[
|
||||
{"param_name": "count", "node_type": "attribute", "path": "."},
|
||||
{"param_name": "seconds", "node_type": "attribute", "path": "."},
|
||||
{"param_name": "label", "node_type": "attribute", "path": "."}
|
||||
],
|
||||
example='''
|
||||
<wait-sequence count="3" seconds="1" label="Test" />
|
||||
'''
|
||||
)
|
||||
@openapi_schema({
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "wait_sequence",
|
||||
"description": "Execute a sequence of waits with the same duration",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"description": "Number of sequential waits to perform"
|
||||
},
|
||||
"seconds": {
|
||||
"type": "number",
|
||||
"description": "Duration of each wait in seconds"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Label to identify this wait sequence"
|
||||
}
|
||||
},
|
||||
"required": ["count", "seconds"]
|
||||
}
|
||||
}
|
||||
})
|
||||
async def wait_sequence(self, count: int, seconds: float, label: str = "Sequence") -> ToolResult:
|
||||
"""Perform a sequence of waits with progress reporting.
|
||||
|
||||
Args:
|
||||
count: Number of sequential waits to perform
|
||||
seconds: Duration of each wait in seconds
|
||||
label: Label to identify this wait sequence
|
||||
|
||||
Returns:
|
||||
ToolResult with success status and sequence information
|
||||
"""
|
||||
try:
|
||||
# Validate and limit parameters
|
||||
count = min(max(1, int(count)), 5)
|
||||
seconds = min(max(0.5, float(seconds)), 5.0)
|
||||
|
||||
logger.info(f"WaitTool: Starting wait sequence '{label}' with {count} iterations of {seconds}s each")
|
||||
|
||||
# Perform the sequential waits
|
||||
result_parts = []
|
||||
total_time = 0
|
||||
|
||||
for i in range(count):
|
||||
start = asyncio.get_event_loop().time()
|
||||
|
||||
# Log the wait start
|
||||
logger.info(f"WaitTool: Sequence '{label}' - Starting iteration {i+1}/{count}")
|
||||
|
||||
# Perform the wait
|
||||
await asyncio.sleep(seconds)
|
||||
|
||||
# Calculate elapsed time
|
||||
elapsed = asyncio.get_event_loop().time() - start
|
||||
total_time += elapsed
|
||||
|
||||
# Add to results
|
||||
result_parts.append(f"Step {i+1}/{count}: Waited {elapsed:.2f}s")
|
||||
logger.info(f"WaitTool: Sequence '{label}' - Completed iteration {i+1}/{count} in {elapsed:.2f}s")
|
||||
|
||||
# Compile the final result
|
||||
result = f"Wait Sequence '{label}' completed in {total_time:.2f} seconds:\n"
|
||||
result += "\n".join(result_parts)
|
||||
|
||||
return self.success_response(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"WaitTool error in sequence '{label}': {str(e)}")
|
||||
return self.fail_response(f"Error during wait sequence: {str(e)}")
|
|
@ -0,0 +1,233 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Innovative Solutions | Transform Your Business</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar">
|
||||
<div class="container">
|
||||
<div class="logo">
|
||||
<h1>InnovateTech</h1>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="#features">Features</a></li>
|
||||
<li><a href="#benefits">Benefits</a></li>
|
||||
<li><a href="#testimonials">Testimonials</a></li>
|
||||
<li><a href="#pricing">Pricing</a></li>
|
||||
<li><a href="#contact" class="cta-button">Get Started</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<header class="hero">
|
||||
<div class="container">
|
||||
<div class="hero-content">
|
||||
<h1>Transform Your Business with Smart Solutions</h1>
|
||||
<p>Empower your company with cutting-edge technology and innovative strategies that drive growth.</p>
|
||||
<div class="cta-buttons">
|
||||
<a href="#contact" class="primary-button">Start Free Trial</a>
|
||||
<a href="#demo" class="secondary-button">Watch Demo</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-image">
|
||||
<img src="https://via.placeholder.com/600x400" alt="Business Innovation">
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Features Section -->
|
||||
<section id="features" class="features">
|
||||
<div class="container">
|
||||
<h2>Why Choose Us</h2>
|
||||
<div class="features-grid">
|
||||
<div class="feature-card">
|
||||
<i class="fas fa-rocket"></i>
|
||||
<h3>Fast Implementation</h3>
|
||||
<p>Get up and running quickly with our streamlined onboarding process.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
<h3>Secure & Reliable</h3>
|
||||
<p>Enterprise-grade security to protect your valuable business data.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
<h3>Scalable Solution</h3>
|
||||
<p>Grow your business with a platform that scales with your needs.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<i class="fas fa-headset"></i>
|
||||
<h3>24/7 Support</h3>
|
||||
<p>Round-the-clock support to help you succeed.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Benefits Section -->
|
||||
<section id="benefits" class="benefits">
|
||||
<div class="container">
|
||||
<h2>Benefits That Drive Success</h2>
|
||||
<div class="benefits-container">
|
||||
<div class="benefit-content">
|
||||
<h3>Increase Productivity</h3>
|
||||
<p>Streamline your workflows and boost team efficiency by up to 50%.</p>
|
||||
<ul>
|
||||
<li>Automated task management</li>
|
||||
<li>Real-time collaboration tools</li>
|
||||
<li>Integrated communication systems</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="benefit-image">
|
||||
<img src="https://via.placeholder.com/500x300" alt="Productivity Benefits">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Testimonials Section -->
|
||||
<section id="testimonials" class="testimonials">
|
||||
<div class="container">
|
||||
<h2>What Our Clients Say</h2>
|
||||
<div class="testimonials-grid">
|
||||
<div class="testimonial-card">
|
||||
<div class="testimonial-content">
|
||||
<p>"This solution has transformed how we operate. Our productivity has increased by 200% since implementation."</p>
|
||||
<div class="testimonial-author">
|
||||
<img src="https://via.placeholder.com/60x60" alt="John Smith">
|
||||
<div>
|
||||
<h4>John Smith</h4>
|
||||
<p>CEO, Tech Solutions Inc.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="testimonial-card">
|
||||
<div class="testimonial-content">
|
||||
<p>"The best investment we've made for our business. Customer support is outstanding!"</p>
|
||||
<div class="testimonial-author">
|
||||
<img src="https://via.placeholder.com/60x60" alt="Sarah Johnson">
|
||||
<div>
|
||||
<h4>Sarah Johnson</h4>
|
||||
<p>Operations Director, Global Corp</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Pricing Section -->
|
||||
<section id="pricing" class="pricing">
|
||||
<div class="container">
|
||||
<h2>Simple, Transparent Pricing</h2>
|
||||
<div class="pricing-grid">
|
||||
<div class="pricing-card">
|
||||
<h3>Starter</h3>
|
||||
<div class="price">$29<span>/month</span></div>
|
||||
<ul>
|
||||
<li>Up to 5 users</li>
|
||||
<li>Basic features</li>
|
||||
<li>Email support</li>
|
||||
<li>2GB storage</li>
|
||||
</ul>
|
||||
<a href="#contact" class="pricing-button">Get Started</a>
|
||||
</div>
|
||||
<div class="pricing-card featured">
|
||||
<h3>Professional</h3>
|
||||
<div class="price">$99<span>/month</span></div>
|
||||
<ul>
|
||||
<li>Up to 20 users</li>
|
||||
<li>Advanced features</li>
|
||||
<li>Priority support</li>
|
||||
<li>10GB storage</li>
|
||||
</ul>
|
||||
<a href="#contact" class="pricing-button">Get Started</a>
|
||||
</div>
|
||||
<div class="pricing-card">
|
||||
<h3>Enterprise</h3>
|
||||
<div class="price">Custom</div>
|
||||
<ul>
|
||||
<li>Unlimited users</li>
|
||||
<li>All features</li>
|
||||
<li>24/7 support</li>
|
||||
<li>Unlimited storage</li>
|
||||
</ul>
|
||||
<a href="#contact" class="pricing-button">Contact Us</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contact Section -->
|
||||
<section id="contact" class="contact">
|
||||
<div class="container">
|
||||
<h2>Ready to Get Started?</h2>
|
||||
<div class="contact-container">
|
||||
<form id="contact-form" class="contact-form">
|
||||
<div class="form-group">
|
||||
<input type="text" id="name" name="name" placeholder="Your Name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="email" id="email" name="email" placeholder="Your Email" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" id="company" name="company" placeholder="Company Name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<textarea id="message" name="message" placeholder="Your Message" required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="submit-button">Get Started</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h3>InnovateTech</h3>
|
||||
<p>Transforming businesses with innovative solutions.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Quick Links</h4>
|
||||
<ul>
|
||||
<li><a href="#features">Features</a></li>
|
||||
<li><a href="#benefits">Benefits</a></li>
|
||||
<li><a href="#pricing">Pricing</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Contact</h4>
|
||||
<p>Email: info@innovatetech.com</p>
|
||||
<p>Phone: (555) 123-4567</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Follow Us</h4>
|
||||
<div class="social-links">
|
||||
<a href="#"><i class="fab fa-facebook"></i></a>
|
||||
<a href="#"><i class="fab fa-twitter"></i></a>
|
||||
<a href="#"><i class="fab fa-linkedin"></i></a>
|
||||
<a href="#"><i class="fab fa-instagram"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2024 InnovateTech. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -113,6 +113,9 @@ class ResponseProcessor:
|
|||
# Tool index counter for tracking all tool executions
|
||||
tool_index = 0
|
||||
|
||||
# Track finish reason
|
||||
finish_reason = None
|
||||
|
||||
logger.info(f"Starting to process streaming response for thread {thread_id}")
|
||||
logger.info(f"Config: XML={config.xml_tool_calling}, Native={config.native_tool_calling}, "
|
||||
f"Execute on stream={config.execute_on_stream}, Execution strategy={config.tool_execution_strategy}")
|
||||
|
@ -121,6 +124,11 @@ class ResponseProcessor:
|
|||
async for chunk in llm_response:
|
||||
# Default content to yield
|
||||
|
||||
# Check for finish_reason
|
||||
if hasattr(chunk, 'choices') and chunk.choices and hasattr(chunk.choices[0], 'finish_reason') and chunk.choices[0].finish_reason:
|
||||
finish_reason = chunk.choices[0].finish_reason
|
||||
logger.info(f"Detected finish_reason: {finish_reason}")
|
||||
|
||||
if hasattr(chunk, 'choices') and chunk.choices:
|
||||
delta = chunk.choices[0].delta if hasattr(chunk.choices[0], 'delta') else None
|
||||
|
||||
|
@ -195,7 +203,8 @@ class ResponseProcessor:
|
|||
if hasattr(tool_call.function, 'name'):
|
||||
tool_call_data['function']['name'] = tool_call.function.name
|
||||
if hasattr(tool_call.function, 'arguments'):
|
||||
tool_call_data['function']['arguments'] = tool_call.function.arguments
|
||||
# Ensure arguments is a string
|
||||
tool_call_data['function']['arguments'] = tool_call.function.arguments if isinstance(tool_call.function.arguments, str) else json.dumps(tool_call.function.arguments)
|
||||
|
||||
# Yield the chunk data
|
||||
yield {
|
||||
|
@ -204,7 +213,7 @@ class ResponseProcessor:
|
|||
}
|
||||
|
||||
# Log the tool call chunk for debugging
|
||||
logger.debug(f"Yielded native tool call chunk: {tool_call_data}")
|
||||
# logger.debug(f"Yielded native tool call chunk: {tool_call_data}")
|
||||
|
||||
if not hasattr(tool_call, 'function'):
|
||||
continue
|
||||
|
@ -486,6 +495,13 @@ class ResponseProcessor:
|
|||
# Increment tool index for next tool
|
||||
tool_index += 1
|
||||
|
||||
# Finally, if we detected a finish reason, yield it
|
||||
if finish_reason:
|
||||
yield {
|
||||
"type": "finish",
|
||||
"finish_reason": finish_reason
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing stream: {str(e)}", exc_info=True)
|
||||
yield {"type": "error", "message": str(e)}
|
||||
|
@ -542,7 +558,7 @@ class ResponseProcessor:
|
|||
"type": "function",
|
||||
"function": {
|
||||
"name": tool_call.function.name,
|
||||
"arguments": tool_call.function.arguments
|
||||
"arguments": tool_call.function.arguments if isinstance(tool_call.function.arguments, str) else json.dumps(tool_call.function.arguments)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1001,7 +1017,48 @@ class ResponseProcessor:
|
|||
):
|
||||
"""Add a tool result to the thread based on the specified format."""
|
||||
try:
|
||||
# Always add results as user or assistant messages, never as 'tool' role
|
||||
# Check if this is a native function call (has id field)
|
||||
if "id" in tool_call:
|
||||
# Format as a proper tool message according to OpenAI spec
|
||||
function_name = tool_call.get("function_name", "")
|
||||
|
||||
# Format the tool result content - tool role needs string content
|
||||
if isinstance(result, str):
|
||||
content = result
|
||||
elif hasattr(result, 'output'):
|
||||
# If it's a ToolResult object
|
||||
if isinstance(result.output, dict) or isinstance(result.output, list):
|
||||
# If output is already a dict or list, convert to JSON string
|
||||
content = json.dumps(result.output)
|
||||
else:
|
||||
# Otherwise just use the string representation
|
||||
content = str(result.output)
|
||||
else:
|
||||
# Fallback to string representation of the whole result
|
||||
content = str(result)
|
||||
|
||||
logger.info(f"Formatted tool result content: {content[:100]}...")
|
||||
|
||||
# Create the tool response message with proper format
|
||||
tool_message = {
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call["id"],
|
||||
"name": function_name,
|
||||
"content": content
|
||||
}
|
||||
|
||||
logger.info(f"Adding native tool result for tool_call_id={tool_call['id']} with role=tool")
|
||||
|
||||
# Add as a tool message
|
||||
await self.add_message(
|
||||
thread_id=thread_id,
|
||||
type="tool", # Special type for tool responses
|
||||
content=tool_message,
|
||||
is_llm_message=True
|
||||
)
|
||||
return
|
||||
|
||||
# For XML and other non-native tools, continue with the original logic
|
||||
# Determine message role based on strategy
|
||||
result_role = "user" if strategy == "user_message" else "assistant"
|
||||
|
||||
|
@ -1040,7 +1097,6 @@ class ResponseProcessor:
|
|||
except Exception as e2:
|
||||
logger.error(f"Failed even with fallback message: {str(e2)}", exc_info=True)
|
||||
|
||||
|
||||
def _format_xml_tool_result(self, tool_call: Dict[str, Any], result: ToolResult) -> str:
|
||||
"""Format a tool result as an XML tag or plain text.
|
||||
|
||||
|
@ -1060,7 +1116,6 @@ class ResponseProcessor:
|
|||
function_name = tool_call["function_name"]
|
||||
return f"Result for {function_name}: {str(result)}"
|
||||
|
||||
|
||||
# At class level, define a method for yielding tool results
|
||||
def _yield_tool_result(self, context: ToolExecutionContext) -> Dict[str, Any]:
|
||||
"""Format and return a tool result message."""
|
||||
|
|
|
@ -58,7 +58,7 @@ class ThreadManager:
|
|||
|
||||
Args:
|
||||
thread_id: The ID of the thread to add the message to.
|
||||
type: The type of the message (e.g., 'text', 'image_url', 'tool_call').
|
||||
type: The type of the message (e.g., 'text', 'image_url', 'tool_call', 'tool', 'user', 'assistant').
|
||||
content: The content of the message. Can be a dictionary, list, or string.
|
||||
It will be stored as JSONB in the database.
|
||||
is_llm_message: Flag indicating if the message originated from the LLM.
|
||||
|
@ -116,6 +116,17 @@ class ThreadManager:
|
|||
else:
|
||||
messages.append(item)
|
||||
|
||||
# Ensure tool_calls have properly formatted function arguments
|
||||
for message in messages:
|
||||
if message.get('tool_calls'):
|
||||
for tool_call in message['tool_calls']:
|
||||
if isinstance(tool_call, dict) and 'function' in tool_call:
|
||||
# Ensure function.arguments is a string
|
||||
if 'arguments' in tool_call['function'] and not isinstance(tool_call['function']['arguments'], str):
|
||||
# Log and fix the issue
|
||||
logger.warning(f"Found non-string arguments in tool_call, converting to string")
|
||||
tool_call['function']['arguments'] = json.dumps(tool_call['function']['arguments'])
|
||||
|
||||
return messages
|
||||
|
||||
except Exception as e:
|
||||
|
@ -214,7 +225,70 @@ class ThreadManager:
|
|||
tool_choice=tool_choice if processor_config.native_tool_calling else None,
|
||||
stream=stream
|
||||
)
|
||||
logger.debug("Successfully received LLM API response")
|
||||
logger.debug("Successfully received raw LLM API response stream/object")
|
||||
|
||||
# # --- BEGIN ADDED DEBUG LOGGING ---
|
||||
# async def logging_stream_wrapper(response_stream):
|
||||
# stream_ended = False
|
||||
# final_chunk_metadata = None
|
||||
# last_chunk = None # Store the last received chunk
|
||||
# try:
|
||||
# chunk_count = 0
|
||||
# async for chunk in response_stream:
|
||||
# chunk_count += 1
|
||||
# last_chunk = chunk # Keep track of the last chunk
|
||||
|
||||
# # Try to access potential finish reason or metadata directly from chunk
|
||||
# finish_reason = None
|
||||
# if hasattr(chunk, 'choices') and chunk.choices and hasattr(chunk.choices[0], 'finish_reason'):
|
||||
# finish_reason = chunk.choices[0].finish_reason
|
||||
|
||||
# logger.debug(f"--> Raw Chunk {chunk_count}: Type={type(chunk)}, FinishReason={finish_reason}, Content={getattr(chunk.choices[0].delta, 'content', None)}, ToolCalls={getattr(chunk.choices[0].delta, 'tool_calls', None)}")
|
||||
|
||||
# # Store metadata if it contains finish_reason
|
||||
# if finish_reason:
|
||||
# final_chunk_metadata = {"finish_reason": finish_reason}
|
||||
# logger.info(f"--> Raw Stream: Detected finish_reason='{finish_reason}' in chunk {chunk_count}")
|
||||
|
||||
# yield chunk
|
||||
# stream_ended = True
|
||||
# logger.info(f"--> Raw Stream: Finished iterating naturally after {chunk_count} chunks.")
|
||||
# except Exception as e:
|
||||
# logger.error(f"--> Raw Stream: Error during iteration: {str(e)}", exc_info=True)
|
||||
# stream_ended = True # Assume ended on error
|
||||
# raise
|
||||
# finally:
|
||||
# if not stream_ended:
|
||||
# logger.warning("--> Raw Stream: Exited wrapper unexpectedly (maybe client stopped iterating?)")
|
||||
|
||||
# # Log the entire last chunk received
|
||||
# if last_chunk:
|
||||
# try:
|
||||
# # Try converting to dict if it's an object with model_dump
|
||||
# last_chunk_data = last_chunk.model_dump() if hasattr(last_chunk, 'model_dump') else vars(last_chunk)
|
||||
# logger.info(f"--> Raw Stream: Last Raw Chunk Received: {last_chunk_data}")
|
||||
# except Exception as log_ex:
|
||||
# logger.warning(f"--> Raw Stream: Could not serialize last chunk for logging: {log_ex}")
|
||||
# logger.info(f"--> Raw Stream: Last Raw Chunk (repr): {repr(last_chunk)}")
|
||||
# else:
|
||||
# logger.warning("--> Raw Stream: No chunks were received or stored.")
|
||||
|
||||
# # Attempt to get final metadata if stream has an attribute for it (depends on litellm/provider)
|
||||
# final_metadata = getattr(response_stream, 'response_metadata', {})
|
||||
# if final_chunk_metadata: # Prioritize finish_reason found in-stream
|
||||
# final_metadata.update(final_chunk_metadata)
|
||||
# logger.info(f"--> Raw Stream: Final Metadata (if available): {final_metadata}")
|
||||
|
||||
# # Wrap the stream only if it's streaming mode
|
||||
# if stream and hasattr(raw_llm_response, '__aiter__'):
|
||||
# llm_response = logging_stream_wrapper(raw_llm_response)
|
||||
# logger.debug("Wrapped raw LLM stream with logging wrapper.")
|
||||
# else:
|
||||
# # If not streaming, just use the raw response (might be a dict/object)
|
||||
# llm_response = raw_llm_response
|
||||
# logger.debug("Not wrapping non-streaming LLM response.")
|
||||
# # --- END ADDED DEBUG LOGGING ---
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to make LLM API call: {str(e)}", exc_info=True)
|
||||
raise
|
||||
|
|
|
@ -19,6 +19,7 @@ import litellm
|
|||
from utils.logger import logger
|
||||
|
||||
# litellm.set_verbose=True
|
||||
litellm.modify_params=True
|
||||
|
||||
# Constants
|
||||
MAX_RETRIES = 3
|
||||
|
@ -311,4 +312,3 @@ if __name__ == "__main__":
|
|||
print("\n✅ integration test completed successfully!")
|
||||
else:
|
||||
print("\n❌ Bedrock integration test failed!")
|
||||
|
||||
|
|
|
@ -94,8 +94,8 @@ async def test_streaming_tool_call():
|
|||
"""Test tool calling with streaming to observe behavior."""
|
||||
# Setup conversation
|
||||
messages = [
|
||||
{"role": "system", "content": "You are a helpful assistant with access to file management tools."},
|
||||
{"role": "user", "content": "Create an HTML file named hello.html with a simple Hello World message."}
|
||||
{"role": "system", "content": "You are a helpful assistant with access to file management tools. YOU ALWAYS USE MULTIPLE TOOL FUNCTION CALLS AT ONCE. YOU NEVER USE ONE TOOL FUNCTION CALL AT A TIME."},
|
||||
{"role": "user", "content": "Create 10 random files with different extensions and content."}
|
||||
]
|
||||
|
||||
print("\n=== Testing streaming tool call ===\n")
|
||||
|
@ -105,10 +105,10 @@ async def test_streaming_tool_call():
|
|||
print("Sending streaming request...")
|
||||
stream_response = await make_llm_api_call(
|
||||
messages=messages,
|
||||
model_name="gpt-4o",
|
||||
model_name="anthropic/claude-3-5-sonnet-latest",
|
||||
temperature=0.0,
|
||||
tools=[CREATE_FILE_SCHEMA],
|
||||
tool_choice={"type": "function", "function": {"name": "create_file"}},
|
||||
tool_choice="auto",
|
||||
stream=True
|
||||
)
|
||||
|
||||
|
@ -123,10 +123,12 @@ async def test_streaming_tool_call():
|
|||
|
||||
# Storage for accumulated tool calls
|
||||
tool_calls = []
|
||||
last_chunk = None # Variable to store the last chunk
|
||||
|
||||
# Process each chunk
|
||||
async for chunk in stream_response:
|
||||
chunk_count += 1
|
||||
last_chunk = chunk # Keep track of the last chunk
|
||||
|
||||
# Print chunk number and type
|
||||
print(f"\n--- Chunk {chunk_count} ---")
|
||||
|
@ -204,6 +206,24 @@ async def test_streaming_tool_call():
|
|||
else:
|
||||
print("\nNo tool calls accumulated from streaming response.")
|
||||
|
||||
# --- Added logging for last chunk and finish reason ---
|
||||
finish_reason = None
|
||||
if last_chunk:
|
||||
try:
|
||||
if hasattr(last_chunk, 'choices') and last_chunk.choices:
|
||||
finish_reason = last_chunk.choices[0].finish_reason
|
||||
last_chunk_data = last_chunk.model_dump() if hasattr(last_chunk, 'model_dump') else vars(last_chunk)
|
||||
print("\n--- Last Chunk Received ---")
|
||||
print(f"Finish Reason: {finish_reason}")
|
||||
print(f"Raw Last Chunk Data: {json.dumps(last_chunk_data, indent=2)}")
|
||||
except Exception as log_ex:
|
||||
print("\n--- Error logging last chunk ---")
|
||||
print(f"Error: {log_ex}")
|
||||
print(f"Last Chunk (repr): {repr(last_chunk)}")
|
||||
else:
|
||||
print("\n--- No last chunk recorded ---")
|
||||
# --- End added logging ---
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in streaming test: {str(e)}", exc_info=True)
|
||||
|
||||
|
|
Loading…
Reference in New Issue