Tools
Tools let your agent interact with the world.
The @tool decorator
The simplest way to create a tool:
from pure_agents import tool
@tool
def search(query: str) -> str:
"""Search the web for information."""
return f"Results for: {query}"
The decorator extracts:
- Name from the function name
- Description from the docstring
- Parameters from type hints
Type hints
pureagents converts Python types to JSON Schema:
@tool
def calculate(a: int, b: float, operation: str) -> float:
"""Perform a calculation."""
if operation == "add":
return a + b
elif operation == "multiply":
return a * b
| Python type | JSON Schema |
|---|---|
str | string |
int | integer |
float | number |
bool | boolean |
list | array |
dict | object |
Async tools
Tools can be async:
@tool
async def fetch_data(url: str) -> str:
"""Fetch data from a URL."""
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.text
Multiple tools
Pass multiple tools to the agent:
agent = Agent(tools=[search, calculate, fetch_data])
The agent decides which tool to use based on the user's request.
Tool class
For more control, create a Tool directly:
from pure_agents import Tool
def my_function(x: str) -> str:
return x.upper()
my_tool = Tool(
name="uppercase",
description="Convert text to uppercase",
parameters={"x": {"type": "string", "description": "Text to convert"}},
fn=my_function,
)
agent = Agent(tools=[my_tool])
Tool timeout
Set per-tool execution limits:
@tool(timeout=10)
async def slow_search(query: str) -> str:
"""Search with 10-second limit."""
# If this takes > 10s, raises TimeoutError
return await external_api.search(query)
Tool groups
Organise tools and enable/disable them dynamically:
@tool(group="web")
def search(query: str) -> str:
"""Search the web."""
return f"Results for {query}"
@tool(group="web")
def fetch(url: str) -> str:
"""Fetch a URL."""
return f"Content of {url}"
@tool(group="math")
def calculate(expr: str) -> str:
"""Evaluate expression."""
return str(eval(expr))
# Start with only web tools enabled
agent = Agent(
tools=[search, fetch, calculate],
enabled_groups=["web"],
)
# Toggle groups at runtime
agent.enable_group("math")
agent.disable_group("web")
Tool choice
Control how the agent uses tools:
# Let agent decide (default)
agent = Agent(tools=[...], tool_choice="auto")
# Force tool use
agent = Agent(tools=[...], tool_choice="required")
# Disable tools for this agent
agent = Agent(tools=[...], tool_choice="none")
Parallel execution
When the LLM returns multiple tool calls, they run in parallel:
@tool
async def fetch_weather(city: str) -> str:
await asyncio.sleep(1) # Simulated API
return f"Weather in {city}: sunny"
@tool
async def fetch_news(topic: str) -> str:
await asyncio.sleep(1) # Simulated API
return f"News about {topic}: 5 articles"
agent = Agent(tools=[fetch_weather, fetch_news])
# Both tools run in parallel (~1s, not ~2s)
result = await agent.run("Get weather in Madrid and news about AI")
Best practices
- Clear docstrings: The LLM uses them to decide when to call the tool
- Type hints: Always include them for proper schema generation
- Simple return values: Return strings or values that convert cleanly to strings
- Handle errors: Wrap tool logic in try/except if needed
- Use timeouts: Protect against slow external services
- Use groups: Organise related tools for easy management