Tools Protocol Specification¶
The Tools Protocol defines the interface for tool providers in Xaibo agents. It standardizes tool discovery, execution, and result handling across different tool implementations.
Source: src/xaibo/core/protocols/tools.py
ToolProviderProtocol¶
The core protocol interface for tool provider implementations.
@runtime_checkable
class ToolProviderProtocol(Protocol):
"""Protocol for providing and executing tools"""
async def list_tools(self) -> List[Tool]:
"""List all available tools provided by this provider"""
...
async def execute_tool(self, tool_name: str, parameters: Dict[str, Any]) -> ToolResult:
"""Execute a tool with the given parameters"""
...
Methods¶
list_tools() -> List[Tool]
¶
Retrieve all tools available from this provider.
Returns:
List[Tool]
: List of tool definitions with names, descriptions, and parameter schemas
Example:
tools = await tool_provider.list_tools()
for tool in tools:
print(f"{tool.name}: {tool.description}")
for param_name, param in tool.parameters.items():
print(f" {param_name} ({param.type}): {param.description}")
execute_tool(tool_name: str, parameters: Dict[str, Any]) -> ToolResult
¶
Execute a specific tool with provided parameters.
Parameters:
tool_name
(str
, required): Name of the tool to executeparameters
(Dict[str, Any]
, required): Tool parameters as key-value pairs
Returns:
ToolResult
: Execution result with success status, result data, or error information
Raises:
ToolNotFoundError
: If the specified tool is not availableToolExecutionError
: If tool execution fails
Example:
result = await tool_provider.execute_tool(
"get_weather",
{"city": "Paris", "units": "metric"}
)
if result.success:
print(f"Weather data: {result.result}")
else:
print(f"Error: {result.error}")
Data Models¶
Tool¶
Definition of an executable tool with its interface specification.
Source: src/xaibo/core/models/tools.py:15
Fields¶
Field | Type | Required | Description |
---|---|---|---|
name |
str |
Yes | Unique identifier for the tool |
description |
str |
Yes | Human-readable description of tool functionality |
parameters |
Dict[str, ToolParameter] |
No | Parameter definitions (default: empty dict) |
Example¶
tool = Tool(
name="get_weather",
description="Get current weather information for a city",
parameters={
"city": ToolParameter(
type="string",
description="Name of the city",
required=True
),
"units": ToolParameter(
type="string",
description="Temperature units",
required=False,
default="celsius",
enum=["celsius", "fahrenheit", "kelvin"]
)
}
)
ToolParameter¶
Parameter definition for tool inputs.
Source: src/xaibo/core/models/tools.py:6
Fields¶
Field | Type | Required | Description |
---|---|---|---|
type |
str |
Yes | Parameter type (string, number, boolean, object, array) |
description |
str |
No | Human-readable parameter description |
required |
bool |
No | Whether parameter is required (default: False) |
default |
Any |
No | Default value if parameter not provided |
enum |
List[str] |
No | List of allowed values for string parameters |
Supported Types¶
Type | Description | Example Values |
---|---|---|
string |
Text values | "hello" , "world" |
number |
Numeric values | 42 , 3.14 , -10 |
integer |
Integer values | 42 , -10 , 0 |
boolean |
Boolean values | true , false |
object |
JSON objects | {"key": "value"} |
array |
JSON arrays | [1, 2, 3] , ["a", "b"] |
Example¶
param = ToolParameter(
type="string",
description="The city name to get weather for",
required=True
)
enum_param = ToolParameter(
type="string",
description="Temperature units",
required=False,
default="celsius",
enum=["celsius", "fahrenheit", "kelvin"]
)
number_param = ToolParameter(
type="number",
description="Maximum temperature threshold",
required=False,
default=30.0
)
ToolResult¶
Result of tool execution with success status and data.
Source: src/xaibo/core/models/tools.py:22
Fields¶
Field | Type | Required | Description |
---|---|---|---|
success |
bool |
Yes | Whether the tool execution succeeded |
result |
Any |
No | Tool execution result data (if successful) |
error |
str |
No | Error message (if execution failed) |
Success Result¶
result = ToolResult(
success=True,
result={
"temperature": 22.5,
"humidity": 65,
"conditions": "partly cloudy"
}
)
Error Result¶
Implementation Example¶
from xaibo.core.protocols.tools import ToolProviderProtocol
from xaibo.core.models.tools import Tool, ToolParameter, ToolResult
import asyncio
import json
class WeatherToolProvider:
"""Example tool provider for weather information"""
def __init__(self, config: dict):
self.api_key = config.get("api_key")
self.base_url = config.get("base_url", "https://api.weather.com")
async def list_tools(self) -> List[Tool]:
"""Return available weather tools"""
return [
Tool(
name="get_current_weather",
description="Get current weather conditions for a city",
parameters={
"city": ToolParameter(
type="string",
description="City name",
required=True
),
"units": ToolParameter(
type="string",
description="Temperature units",
required=False,
default="celsius",
enum=["celsius", "fahrenheit", "kelvin"]
)
}
),
Tool(
name="get_weather_forecast",
description="Get weather forecast for multiple days",
parameters={
"city": ToolParameter(
type="string",
description="City name",
required=True
),
"days": ToolParameter(
type="integer",
description="Number of days to forecast",
required=False,
default=5
)
}
)
]
async def execute_tool(self, tool_name: str, parameters: Dict[str, Any]) -> ToolResult:
"""Execute a weather tool"""
try:
if tool_name == "get_current_weather":
return await self._get_current_weather(parameters)
elif tool_name == "get_weather_forecast":
return await self._get_weather_forecast(parameters)
else:
return ToolResult(
success=False,
error=f"Unknown tool: {tool_name}"
)
except Exception as e:
return ToolResult(
success=False,
error=f"Tool execution failed: {str(e)}"
)
async def _get_current_weather(self, parameters: Dict[str, Any]) -> ToolResult:
"""Get current weather for a city"""
city = parameters.get("city")
units = parameters.get("units", "celsius")
if not city:
return ToolResult(
success=False,
error="City parameter is required"
)
# Simulate API call
await asyncio.sleep(0.1) # Simulate network delay
# Mock weather data
weather_data = {
"city": city,
"temperature": 22.5 if units == "celsius" else 72.5,
"units": units,
"humidity": 65,
"conditions": "partly cloudy",
"wind_speed": 10
}
return ToolResult(
success=True,
result=weather_data
)
async def _get_weather_forecast(self, parameters: Dict[str, Any]) -> ToolResult:
"""Get weather forecast for a city"""
city = parameters.get("city")
days = parameters.get("days", 5)
if not city:
return ToolResult(
success=False,
error="City parameter is required"
)
# Simulate API call
await asyncio.sleep(0.2)
# Mock forecast data
forecast = {
"city": city,
"forecast": [
{
"date": f"2024-01-{i+1:02d}",
"high": 25 + i,
"low": 15 + i,
"conditions": "sunny" if i % 2 == 0 else "cloudy"
}
for i in range(days)
]
}
return ToolResult(
success=True,
result=forecast
)
# Verify protocol compliance
assert isinstance(WeatherToolProvider({}), ToolProviderProtocol)
Error Handling¶
Tool providers should handle errors gracefully and return appropriate ToolResult objects:
class ToolError(Exception):
"""Base exception for tool errors"""
pass
class ToolNotFoundError(ToolError):
"""Tool not found"""
pass
class ToolExecutionError(ToolError):
"""Tool execution failed"""
pass
class ToolParameterError(ToolError):
"""Invalid tool parameters"""
pass
# Example error handling in execute_tool
async def execute_tool(self, tool_name: str, parameters: Dict[str, Any]) -> ToolResult:
try:
# Validate tool exists
tools = await self.list_tools()
tool_names = [t.name for t in tools]
if tool_name not in tool_names:
return ToolResult(
success=False,
error=f"Tool '{tool_name}' not found. Available tools: {tool_names}"
)
# Validate parameters
tool = next(t for t in tools if t.name == tool_name)
validation_error = self._validate_parameters(tool, parameters)
if validation_error:
return ToolResult(
success=False,
error=validation_error
)
# Execute tool
result = await self._execute_tool_impl(tool_name, parameters)
return ToolResult(success=True, result=result)
except Exception as e:
return ToolResult(
success=False,
error=f"Unexpected error: {str(e)}"
)
Testing¶
Mock implementation for testing:
class MockToolProvider:
"""Mock tool provider for testing"""
def __init__(self, tools: List[Tool], results: Dict[str, Any]):
self.tools = tools
self.results = results
self.call_history = []
async def list_tools(self) -> List[Tool]:
return self.tools
async def execute_tool(self, tool_name: str, parameters: Dict[str, Any]) -> ToolResult:
self.call_history.append((tool_name, parameters))
if tool_name in self.results:
result_data = self.results[tool_name]
if isinstance(result_data, Exception):
return ToolResult(success=False, error=str(result_data))
else:
return ToolResult(success=True, result=result_data)
else:
return ToolResult(success=False, error=f"Tool {tool_name} not mocked")
# Usage in tests
mock_tools = [
Tool(name="test_tool", description="A test tool", parameters={})
]
mock_results = {
"test_tool": {"message": "success"}
}
mock_provider = MockToolProvider(mock_tools, mock_results)
assert isinstance(mock_provider, ToolProviderProtocol)
# Test execution
result = await mock_provider.execute_tool("test_tool", {})
assert result.success
assert result.result == {"message": "success"}
Integration with LLM Function Calling¶
Tools integrate with LLM function calling through the LLMOptions:
# Get tools from provider
tools = await tool_provider.list_tools()
# Use in LLM options
options = LLMOptions(
temperature=0.7,
functions=tools # Tools available for function calling
)
# Generate response with tools
response = await llm.generate(messages, options)
# Execute any tool calls
if response.tool_calls:
for call in response.tool_calls:
result = await tool_provider.execute_tool(call.name, call.arguments)
# Add result to conversation history
messages.append(LLMMessage.function_result(call.id, call.name, str(result.result)))
Best Practices¶
Tool Design¶
- Clear Names: Use descriptive, action-oriented tool names
- Comprehensive Descriptions: Explain what the tool does and when to use it
- Parameter Validation: Validate all input parameters
- Error Handling: Return meaningful error messages
- Idempotency: Tools should be safe to call multiple times
Parameter Definitions¶
- Type Safety: Use appropriate parameter types
- Required Fields: Mark required parameters explicitly
- Default Values: Provide sensible defaults where possible
- Enums: Use enums for limited choice parameters
- Documentation: Include clear parameter descriptions
Result Handling¶
- Consistent Format: Use consistent result structures
- Success Indication: Always indicate success/failure clearly
- Error Details: Provide actionable error messages
- Serializable Results: Ensure results can be JSON serialized
- Size Limits: Keep results reasonably sized for LLM context