Tools¶
Tools are functions that agents can call to interact with the world. Tinygent makes it easy to turn any Python function into an agent-compatible tool.
What is a Tool?¶
A tool is a Python function that:
- Has a clear purpose (described in docstring)
- Has typed parameters (for schema generation)
- Returns a value (for agent observation)
- Is decorated with
@tool,@register_tool,@reasoning_tool, or@jit_tool
Example:
from tinygent.tools import tool
@tool
def get_weather(location: str) -> str:
"""Get the current weather in a given location.
Args:
location: The city or location to get weather for
Returns:
A string describing the weather
"""
return f'The weather in {location} is sunny with a high of 75°F.'
When you pass this tool to an agent, the LLM can:
- See the function name:
get_weather - See the description:
"Get the current weather..." - See the parameters:
location: str - Call it when needed:
get_weather(location="Prague") - Use the result:
"The weather in Prague is sunny..."
Tool Decorators¶
Tinygent provides 4 tool decorators for different use cases:
1. @tool - Simple Tools¶
Best for: Quick local tools, no global registration needed
from tinygent.tools import tool
@tool
def calculator(expression: str) -> float:
"""Evaluate a mathematical expression.
Args:
expression: A valid Python math expression
Returns:
The result of the calculation
"""
return eval(expression)
# Use directly in agent
agent = build_agent('react', llm='openai:gpt-4o-mini', tools=[calculator])
Features:
- Lightweight, no registration
- Great for simple use cases
- Perfect for inline definitions
2. @register_tool - Globally Registered Tools¶
Best for: Reusable tools, CLI usage, multi-agent systems
from tinygent.tools import register_tool
@register_tool(use_cache=True)
def search_web(query: str) -> str:
"""Search the web for information."""
# Call web search API
return f"Results for: {query}"
# Discover from global registry
from tinygent.core.runtime.tool_catalog import GlobalToolCatalog
registry = GlobalToolCatalog().get_active_catalog()
search = registry.get_tool('search_web')
Features:
- Global discovery: Access from anywhere
- Caching: Optional result caching with
use_cache=True - CLI support: Usable in
tinyCLI terminal - Reusability: Share across multiple agents
Caching Example:
@register_tool(use_cache=True)
def expensive_api_call(query: str) -> str:
"""Call an expensive API."""
import time
time.sleep(2) # Simulate slow API
return f"Result for {query}"
# First call: Takes 2 seconds
result1 = expensive_api_call(query="test")
# Second call with same args: Instant (cached)
result2 = expensive_api_call(query="test")
# Check cache stats
print(expensive_api_call.cache_info())
# CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)
# Clear cache
expensive_api_call.clear_cache()
3. @reasoning_tool - Tools with Reasoning¶
Best for: Complex operations that benefit from explaining "why"
Reasoning tools require the agent to provide a rationale before calling:
from tinygent.tools import register_reasoning_tool
@register_reasoning_tool(
reasoning_prompt='Explain why you are performing this search.'
)
def search_database(query: str) -> str:
"""Search the internal database."""
return f"Database results for: {query}"
Agent interaction:
Agent: I need to search the database for user information.
Reasoning: I'm searching for "John Doe" because the user asked about their account status.
Action: search_database(query="John Doe")
Observation: Found user record for John Doe
When to use:
- High-cost operations (API calls, computations)
- Actions that need justification (delete, modify)
- Debugging agent decision-making
4. @jit_tool - Just-In-Time Code Generation¶
Best for: Dynamic operations, code generation, flexible workflows
JIT tools generate and execute code at runtime based on agent instructions:
from tinygent.tools import jit_tool
@jit_tool(jit_instruction='Generate code to count from 1 to n, yielding each number.')
def count(n: int):
"""Count from 1 to n, yielding each number."""
for i in range(1, n + 1):
yield i
# Agent can dynamically modify behavior
result = list(count(n=5)) # [1, 2, 3, 4, 5]
When to use:
- Dynamic code generation
- Flexible operations
- When tool behavior needs runtime customization
Tool Schemas¶
Tools can accept two input styles:
Style 1: Pydantic Models¶
Best for: Complex inputs, validation, documentation
from pydantic import Field
from tinygent.core.types import TinyModel
class WeatherInput(TinyModel):
location: str = Field(..., description='The city or location')
units: str = Field('celsius', description='Temperature units: celsius or fahrenheit')
include_forecast: bool = Field(False, description='Include 5-day forecast')
@register_tool
def get_weather(data: WeatherInput) -> str:
"""Get detailed weather information."""
forecast = ' + 5-day forecast' if data.include_forecast else ''
return f"Weather in {data.location}: 22°{data.units[0].upper()}{forecast}"
Benefits:
- Runtime validation (Pydantic)
- Rich field descriptions
- Default values
- Type safety
Style 2: Regular Parameters¶
Best for: Simple inputs, quick tools
@register_tool
def multiply(a: int, b: int) -> int:
"""Multiply two numbers together."""
return a * b
# Call with kwargs
result = multiply(a=3, b=4) # 12
# Or with dict
result = multiply({'a': 5, 'b': 6}) # 30
Benefits:
- Less boilerplate
- Quick to write
- Familiar Python syntax
Async Tools¶
Tools can be async for I/O operations:
import httpx
@register_tool
async def fetch_url(url: str) -> str:
"""Fetch content from a URL."""
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.text
# Agents automatically handle async tools
agent = build_agent('react', llm='openai:gpt-4o-mini', tools=[fetch_url])
result = agent.run('Fetch https://example.com')
Generator Tools¶
Tools can yield results for streaming:
@tool
async def stream_data(query: str):
"""Stream data from a source."""
for i in range(5):
await asyncio.sleep(0.5)
yield f"Chunk {i}: {query}"
# Agent receives results as they arrive
result = list(stream_data(query="test"))
# ['Chunk 0: test', 'Chunk 1: test', ...]
Error Handling¶
Tools should raise descriptive errors:
@tool
def divide(a: float, b: float) -> float:
"""Divide two numbers."""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
# Agent receives error and tries alternative approach
agent = build_agent('react', llm='openai:gpt-4o-mini', tools=[divide])
result = agent.run('What is 10 divided by 0?')
# Agent: "Division by zero is undefined in mathematics."
Tool Composition¶
Combine tools for complex workflows:
@register_tool
def search_products(category: str) -> list[str]:
"""Search for products in a category."""
return ['Product A', 'Product B', 'Product C']
@register_tool
def get_product_details(product_name: str) -> dict:
"""Get detailed information about a product."""
return {
'name': product_name,
'price': 99.99,
'in_stock': True
}
# Agent chains tools
agent = build_agent(
'react',
llm='openai:gpt-4o-mini',
tools=[search_products, get_product_details]
)
result = agent.run('Find electronics and tell me about Product A')
# Agent calls: search_products('electronics') → get_product_details('Product A')
Best Practices¶
1. Clear Docstrings¶
# Bad
@tool
def process(data: str) -> str:
"""Process data.""" # Too vague
return data.upper()
# Good
@tool
def uppercase_text(text: str) -> str:
"""Convert text to uppercase.
Args:
text: The text to convert
Returns:
The text in uppercase
Example:
uppercase_text("hello") -> "HELLO"
"""
return text.upper()
2. Type Hints¶
# Bad
@tool
def add(a, b): # No types
return a + b
# Good
@tool
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
3. Single Responsibility¶
# Bad - Does too much
@tool
def fetch_and_analyze_and_summarize(url: str) -> str:
"""Fetch URL, analyze content, and summarize."""
content = fetch(url)
analyzed = analyze(content)
return summarize(analyzed)
# Good - Split into focused tools
@tool
def fetch_url(url: str) -> str:
"""Fetch content from a URL."""
return fetch(url)
@tool
def analyze_text(text: str) -> dict:
"""Analyze text content."""
return analyze(text)
@tool
def summarize_analysis(analysis: dict) -> str:
"""Create summary from analysis."""
return summarize(analysis)
4. Validation¶
from pydantic import Field, field_validator
class SearchInput(TinyModel):
query: str = Field(..., min_length=1, max_length=200)
max_results: int = Field(10, ge=1, le=100)
@field_validator('query')
def validate_query(cls, v):
if not v.strip():
raise ValueError('Query cannot be empty')
return v.strip()
@register_tool
def search(data: SearchInput) -> str:
"""Search with validation."""
return f"Searching for: {data.query}"
Tool Registry¶
Access registered tools globally:
from tinygent.core.runtime.tool_catalog import GlobalToolCatalog
# Get active catalog
catalog = GlobalToolCatalog().get_active_catalog()
# List all tools
all_tools = catalog.list_tools()
print(all_tools) # ['get_weather', 'search_web', 'calculator', ...]
# Get specific tool
weather_tool = catalog.get_tool('get_weather')
# Call it
result = weather_tool(location='Prague')
Advanced: Hidden Tools¶
Mark tools as hidden for internal use:
@register_tool(hidden=True)
def internal_helper(data: str) -> str:
"""Internal tool, not exposed to agents."""
return process_internally(data)
# Not visible in default tool lists
catalog = GlobalToolCatalog().get_active_catalog()
visible_tools = catalog.list_tools() # Doesn't include 'internal_helper'
# But still accessible if you know the name
helper = catalog.get_tool('internal_helper')
Next Steps¶
- Agents: Use tools with agents
- Custom Tools Guide: Build advanced tools
- Examples: See tool usage examples
Examples¶
Check out:
examples/tool-usage/main.py- All tool decorator typesexamples/function-calling/main.py- Function calling patternspackages/tiny_brave/- Real-world tool: Brave search