Tool System¶
Agent’s tool system allows you to register Python functions that LLMs can call. This enables agents to interact with external systems, perform calculations, and access real-time data.
Basic Usage¶
The @tool Decorator¶
from agent import Agent, tool
@tool
def get_weather(city: str) -> str:
"""Get the current weather for a city."""
# Your implementation
return f"Weather in {city}: 72F, Sunny"
agent = Agent(
provider="openai",
model="gpt-4o",
tools=[get_weather],
)
response = agent.run("What's the weather in Tokyo?")
# The agent will call get_weather("Tokyo") and use the result
Multiple Tools¶
@tool
def search_web(query: str) -> str:
"""Search the web for information."""
return f"Search results for: {query}"
@tool
def read_file(path: str) -> str:
"""Read contents of a file."""
with open(path) as f:
return f.read()
@tool
def write_file(path: str, content: str) -> str:
"""Write content to a file."""
with open(path, 'w') as f:
f.write(content)
return f"Wrote {len(content)} chars to {path}"
agent = Agent(
provider="anthropic",
model="claude-sonnet",
tools=[search_web, read_file, write_file],
)
Tool Configuration¶
Custom Name and Description¶
@tool(
name="web_search",
description="Search the internet for current information about any topic.",
)
def search(query: str) -> str:
return f"Results for: {query}"
Timeout and Retries¶
@tool(timeout=30.0, max_retries=3)
def slow_api_call(endpoint: str) -> str:
"""Call an external API that might be slow."""
import requests
response = requests.get(endpoint, timeout=25)
return response.text
Type Annotations¶
Agent automatically generates JSON schemas from type annotations:
Basic Types¶
@tool
def process_data(
text: str, # -> {"type": "string"}
count: int, # -> {"type": "integer"}
ratio: float, # -> {"type": "number"}
enabled: bool, # -> {"type": "boolean"}
) -> str:
"""Process various data types."""
return f"Processed: {text}, {count}, {ratio}, {enabled}"
Complex Types¶
from typing import Optional
@tool
def search_items(
query: str,
tags: list[str], # -> {"type": "array", "items": {"type": "string"}}
limit: int = 10, # Optional with default (not in "required")
category: str | None = None, # Optional parameter
) -> str:
"""Search for items with filters."""
return f"Found items matching {query}"
Pydantic Models¶
from pydantic import BaseModel
class SearchParams(BaseModel):
query: str
max_results: int = 10
include_metadata: bool = False
@tool
def advanced_search(params: SearchParams) -> str:
"""Perform an advanced search with structured parameters."""
return f"Searching for: {params.query}"
Async Tools¶
import asyncio
@tool
async def fetch_url(url: str) -> str:
"""Fetch content from a URL asynchronously."""
import aiohttp
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
# Works with both sync and async agent methods
response = agent.run("Fetch https://example.com") # Runs async tool in executor
response = await agent.run_async("Fetch https://example.com") # Native async
Tool Results¶
Accessing Tool Calls¶
response = agent.run("What's 2+2 and what's the weather?")
if response.has_tool_calls:
for tool_call in response.tool_calls:
print(f"Tool: {tool_call.name}")
print(f"Arguments: {tool_call.arguments}")
print(f"ID: {tool_call.id}")
Tool Loop¶
Agent automatically handles the tool calling loop:
Agent sends request to LLM
LLM returns tool calls
Agent executes tools
Agent sends results back to LLM
Repeat until LLM returns final response
Configure the tool loop:
from agent.types import ToolLoopConfig
agent = Agent(
provider="openai",
model="gpt-4o",
tools=[...],
)
# Access via runtime (advanced)
agent._runtime.tool_loop.config.max_iterations = 5
agent._runtime.tool_loop.config.parallel_tool_execution = True
Error Handling¶
Tool Errors¶
@tool
def risky_operation(data: str) -> str:
"""Perform an operation that might fail."""
if not data:
raise ValueError("Data cannot be empty")
return f"Processed: {data}"
# By default, tool errors are returned to the LLM as error messages
# The LLM can then decide how to handle the error
Stop on Error¶
from agent.types import ToolLoopConfig
config = ToolLoopConfig(stop_on_error=True)
# Now tool errors will raise ToolExecutionError instead of continuing
Manual Tool Registration¶
For more control, create Tool objects directly:
from agent import Tool
from agent.types import ToolSpec
def my_function(x: int, y: int) -> int:
return x + y
spec = ToolSpec(
name="add_numbers",
description="Add two numbers together",
parameters={
"type": "object",
"properties": {
"x": {"type": "integer", "description": "First number"},
"y": {"type": "integer", "description": "Second number"},
},
"required": ["x", "y"],
},
)
tool = Tool(spec=spec, function=my_function)
agent = Agent(provider="openai", model="gpt-4o", tools=[tool])
Tool Registry¶
Manage tools programmatically:
from agent.tools import ToolRegistry
registry = ToolRegistry()
@tool
def tool_a(x: str) -> str:
return x
@tool
def tool_b(y: int) -> str:
return str(y)
registry.register(tool_a)
registry.register(tool_b)
print(registry.get("tool_a")) # Get by name
print(registry.get_all()) # List all tools
print(registry.specs()) # Get all ToolSpecs
Best Practices¶
1. Clear Descriptions¶
# Good - tells the LLM when to use this tool
@tool
def search_documentation(query: str) -> str:
"""Search the project documentation for API references,
tutorials, and examples. Use this when the user asks
about how to use specific features."""
...
# Bad - vague description
@tool
def search(q: str) -> str:
"""Search for stuff."""
...
2. Specific Return Values¶
# Good - structured, parseable output
@tool
def get_user_info(user_id: str) -> str:
user = fetch_user(user_id)
return json.dumps({
"name": user.name,
"email": user.email,
"created_at": user.created_at.isoformat(),
})
# Bad - ambiguous output
@tool
def get_user(id: str) -> str:
return str(fetch_user(id))
3. Error Messages¶
@tool
def database_query(sql: str) -> str:
"""Execute a read-only SQL query."""
try:
results = db.execute(sql)
return json.dumps(results)
except Exception as e:
return f"Error executing query: {e}. Please check the SQL syntax."
4. Idempotent Operations¶
@tool
def update_setting(key: str, value: str) -> str:
"""Update a configuration setting. Safe to call multiple times."""
config[key] = value
return f"Set {key} = {value}"
Next Steps¶
Structured Outputs - Type-safe LLM responses
Sessions - Multi-turn conversations with tools
Middleware - Hook into tool execution