How to create and integrate Python tools¶
This guide shows you how to create custom Python functions and make them available as tools for your Xaibo agents.
Create a Python tool function¶
Create a new Python file in your project's tools/
directory:
# tools/my_tools.py
from datetime import datetime, timezone
from xaibo.primitives.modules.tools.python_tool_provider import tool
@tool
def current_time():
"""Gets the current time in UTC"""
return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
@tool
def calculate_sum(numbers: list[float]) -> float:
"""Calculates the sum of a list of numbers"""
return sum(numbers)
@tool
def format_text(text: str, style: str = "uppercase") -> str:
"""Formats text according to the specified style
Args:
text: The text to format
style: Format style - 'uppercase', 'lowercase', or 'title'
"""
if style == "uppercase":
return text.upper()
elif style == "lowercase":
return text.lower()
elif style == "title":
return text.title()
else:
return text
Add tools to your agent configuration¶
Configure your agent to use the Python tool provider:
# agents/my_agent.yml
id: my-agent
description: An agent with custom Python tools
modules:
- module: xaibo.primitives.modules.llm.OpenAILLM
id: llm
config:
model: gpt-4.1-nano
- id: python-tools
module: xaibo.primitives.modules.tools.PythonToolProvider
config:
tool_packages: [tools.my_tools]
- module: xaibo.primitives.modules.orchestrator.StressingToolUser
id: orchestrator
config:
max_thoughts: 10
system_prompt: |
You are a helpful assistant with access to custom tools.
Use the available tools to help users with their requests.
Test your tools¶
Start your agent and test the tools:
# Start the development server
uv run xaibo dev
# Test with curl
curl -X POST http://127.0.0.1:9001/openai/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "my-agent",
"messages": [
{"role": "user", "content": "What time is it now?"}
]
}'
Add tools with complex parameters¶
Create tools that accept structured data:
# tools/advanced_tools.py
from typing import Dict, List, Optional
from dataclasses import dataclass
from xaibo.primitives.modules.tools.python_tool_provider import tool
@dataclass
class Person:
name: str
age: int
email: Optional[str] = None
@tool
def create_person_profile(name: str, age: int, email: str = None) -> Dict:
"""Creates a person profile with the given information
Args:
name: Person's full name
age: Person's age in years
email: Optional email address
"""
profile = {
"name": name,
"age": age,
"profile_id": f"{name.lower().replace(' ', '_')}_{age}"
}
if email:
profile["email"] = email
return profile
@tool
def search_database(query: str, filters: Dict = None, limit: int = 10) -> List[Dict]:
"""Searches a mock database with optional filters
Args:
query: Search query string
filters: Optional dictionary of filter criteria
limit: Maximum number of results to return
"""
# Mock implementation
results = [
{"id": 1, "title": f"Result for '{query}'", "score": 0.95},
{"id": 2, "title": f"Another match for '{query}'", "score": 0.87}
]
if filters:
# Apply mock filtering logic
results = [r for r in results if r["score"] >= filters.get("min_score", 0)]
return results[:limit]
Handle errors in tools¶
Add proper error handling to your tools:
# tools/robust_tools.py
import requests
from xaibo.primitives.modules.tools.python_tool_provider import tool
@tool
def fetch_weather(city: str) -> Dict:
"""Fetches weather information for a city
Args:
city: Name of the city
"""
try:
# Mock API call (replace with real weather API)
if not city or len(city.strip()) == 0:
raise ValueError("City name cannot be empty")
# Simulate API response
weather_data = {
"city": city,
"temperature": "22°C",
"condition": "Sunny",
"humidity": "65%"
}
return weather_data
except ValueError as e:
return {"error": f"Invalid input: {str(e)}"}
except Exception as e:
return {"error": f"Failed to fetch weather: {str(e)}"}
@tool
def validate_email(email: str) -> Dict:
"""Validates an email address format
Args:
email: Email address to validate
"""
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
is_valid = bool(re.match(pattern, email))
return {
"email": email,
"is_valid": is_valid,
"message": "Valid email format" if is_valid else "Invalid email format"
}
Use multiple tool packages¶
Configure multiple tool packages in your agent:
# agents/multi_tool_agent.yml
id: multi-tool-agent
modules:
- module: xaibo.primitives.modules.llm.OpenAILLM
id: llm
config:
model: gpt-4.1-nano
- id: python-tools
module: xaibo.primitives.modules.tools.PythonToolProvider
config:
tool_packages:
- tools.my_tools
- tools.advanced_tools
- tools.robust_tools
- module: xaibo.primitives.modules.orchestrator.StressingToolUser
id: orchestrator
Add tools from external packages¶
Use tools from installed Python packages:
# tools/external_tools.py
import json
import base64
from xaibo.primitives.modules.tools.python_tool_provider import tool
@tool
def encode_base64(text: str) -> str:
"""Encodes text to base64"""
return base64.b64encode(text.encode()).decode()
@tool
def decode_base64(encoded: str) -> str:
"""Decodes base64 text"""
try:
return base64.b64decode(encoded.encode()).decode()
except Exception as e:
return f"Error decoding: {str(e)}"
@tool
def format_json(data: str) -> str:
"""Formats JSON string with proper indentation"""
try:
parsed = json.loads(data)
return json.dumps(parsed, indent=2)
except json.JSONDecodeError as e:
return f"Invalid JSON: {str(e)}"
Best practices¶
Tool documentation¶
- Always include clear docstrings with parameter descriptions
- Use type hints for better tool schema generation
- Document expected return formats
Error handling¶
- Validate input parameters
- Return structured error information
- Don't let exceptions crash the agent
Tool naming¶
- Use descriptive function names
- Group related tools in the same module
- Avoid name conflicts between packages
Performance¶
- Keep tools lightweight and fast
- Use async functions for I/O operations when needed
- Cache expensive computations when appropriate
Troubleshooting¶
Tools not appearing¶
- Verify the tool package is listed in
tool_packages
- Check that functions have the
@tool
decorator - Ensure the Python module can be imported
Import errors¶
- Make sure all dependencies are installed
- Check Python path includes your tools directory
- Verify module names match file names
Tool execution errors¶
- Add logging to your tool functions
- Check the agent debug UI for detailed error messages
- Test tools independently before integrating
Debug and troubleshoot tools¶
Use Xaibo's built-in debugging capabilities to diagnose tool discovery, import, and execution issues.
Enable debug UI¶
Start your development server with the debug UI enabled:
# Start with debug UI (automatically enabled in dev mode)
uv run xaibo dev
# Access debug UI at http://localhost:9001
The debug UI provides:
- Real-time event tracing for all tool operations
- Visual representation of tool discovery and execution
- Detailed error logs and stack traces
- Performance metrics for tool calls
Use list_tools() for programmatic debugging¶
Access tool information programmatically using the list_tools()
method:
# debug_python_tools.py
import asyncio
from xaibo.primitives.modules.tools.python_tool_provider import PythonToolProvider
async def debug_python_tools():
"""Debug Python tool discovery and availability"""
config = {
"tool_packages": ["tools.my_tools", "tools.advanced_tools"]
}
provider = PythonToolProvider(config=config)
try:
# List all available tools
tools = await provider.list_tools()
print(f"Found {len(tools)} Python tools:")
for tool in tools:
print(f" - {tool.name}")
print(f" Description: {tool.description}")
print(f" Parameters: {len(tool.parameters)} params")
for param in tool.parameters:
print(f" - {param.name}: {param.type} ({'required' if param.required else 'optional'})")
print()
except Exception as e:
print(f"Error debugging tools: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
asyncio.run(debug_python_tools())
Step-by-step debugging procedure¶
Follow these steps to diagnose Python tool issues:
1. Verify tool package imports¶
Test if your tool packages can be imported correctly:
# Test package imports
try:
import tools.my_tools
print("✓ tools.my_tools imported successfully")
# Check for tool functions
tool_functions = [obj for obj in tools.my_tools.__dict__.values()
if hasattr(obj, "__xaibo_tool__")]
print(f"✓ Found {len(tool_functions)} tool functions")
except ImportError as e:
print(f"✗ Import failed: {e}")
2. Check tool discovery¶
Use the debug script to verify tools are discovered:
# Check if tools are found
tools = await provider.list_tools()
if not tools:
print("No tools discovered - check package configuration")
else:
print(f"Discovered {len(tools)} tools")
for tool in tools:
print(f" - {tool.name} from {tool.description}")
3. Test tool execution¶
Execute a simple tool to verify functionality:
# Test tool execution
try:
result = await provider.execute_tool("current_time", {})
print(f"Tool executed successfully: {result}")
except Exception as e:
print(f"Tool execution failed: {e}")
import traceback
traceback.print_exc()
4. Monitor with debug UI¶
- Start your agent with debug UI enabled
- Navigate to
http://localhost:9001
- Trigger tool operations through your agent
- Review the event trace for errors or performance issues
Common troubleshooting scenarios¶
Tools not discovered¶
Symptoms: list_tools()
returns empty list
Solutions:
- Verify package names in tool_packages
configuration
- Check that functions have the @tool
decorator
- Ensure packages can be imported from Python path
- Test imports manually
# Verify tool package configuration
config = {
"tool_packages": [
"tools.my_tools", # Check this path
"tools.advanced_tools" # Verify this exists
]
}
# Test manual import
try:
import tools.my_tools
print("Package imports successfully")
except ImportError as e:
print(f"Import error: {e}")
Import errors¶
Symptoms: ModuleNotFoundError, ImportError exceptions
Solutions:
- Ensure all dependencies are installed
- Check Python path includes your tools directory
- Verify module names match file names
- Add __init__.py
files to make directories packages
# Check Python path
python -c "import sys; print('\n'.join(sys.path))"
# Verify package structure
ls -la tools/
# Should show:
# __init__.py
# my_tools.py
# advanced_tools.py
# Test import manually
python -c "import tools.my_tools; print('Import successful')"
Missing @tool decorator¶
Symptoms: Functions exist but not discovered as tools
Solutions:
- Add @tool
decorator to functions
- Import decorator from correct module
- Verify decorator is applied before function definition
# Correct tool definition
from xaibo.primitives.modules.tools.python_tool_provider import tool
@tool # This decorator is required
def my_function():
"""Function description"""
return "result"
# Check if function is marked as tool
print(hasattr(my_function, "__xaibo_tool__")) # Should be True
Tool execution errors¶
Symptoms: Tools discovered but execution fails
Solutions: - Check function parameter types and names - Verify return values are JSON-serializable - Add proper error handling to tool functions - Test functions independently
# Debug tool parameters
tool = tools[0] # Get first tool
print(f"Tool name: {tool.name}")
print(f"Required parameters: {[p.name for p in tool.parameters if p.required]}")
print(f"Optional parameters: {[p.name for p in tool.parameters if not p.required]}")
# Test with correct parameters
try:
# Use actual parameter names from tool definition
params = {"text": "hello", "style": "uppercase"} # Example
result = await provider.execute_tool(tool.name, params)
print(f"Success: {result}")
except Exception as e:
print(f"Error: {e}")
Type annotation issues¶
Symptoms: Tool schema generation fails, parameter validation errors
Solutions: - Add proper type hints to function parameters - Use supported types (str, int, float, bool, list, dict) - Provide default values for optional parameters - Include comprehensive docstrings
# Good type annotations
@tool
def process_data(
text: str, # Required string parameter
count: int = 1, # Optional with default
options: dict = None, # Optional complex type
enabled: bool = True # Optional boolean
) -> dict: # Return type annotation
"""Process text data with options
Args:
text: Input text to process
count: Number of times to process (default: 1)
options: Additional processing options
enabled: Whether processing is enabled
Returns:
Dictionary with processing results
"""
if options is None:
options = {}
return {
"processed_text": text * count,
"options_used": options,
"was_enabled": enabled
}
Debug UI features¶
The debug UI at http://localhost:9001
provides:
Event trace viewer¶
- Real-time display of all tool-related events
- Expandable event details with full context
- Filtering by event type or tool name
- Export capabilities for offline analysis
Access these features by navigating to the debug UI after starting your development server with uv run xaibo dev
.