Basic Tool Structure
Tools are Python functions decorated with@tool:
Copy
from laddr import tool
@tool(
name="weather_lookup",
description="Fetches current weather for a given city",
parameters={
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name"
},
"units": {
"type": "string",
"description": "Units (metric/imperial)",
"default": "metric"
}
},
"required": ["city"]
}
)
def weather_lookup(city: str, units: str = "metric") -> dict:
"""Tool docstring: fetch current weather."""
try:
result = get_weather(city, units)
return {"status": "success", "data": result}
except Exception as e:
return {"status": "error", "error": str(e)}
The @tool Decorator
Syntax
Copy
@tool(
name: str, # Unique tool identifier
description: str, # What the tool does
parameters: dict, # JSON Schema for inputs
trace: bool = True, # Enable tracing
trace_mask: list = [] # Fields to redact in traces
)
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | str | ✅ | Unique identifier used by agents |
description | str | ✅ | Summary of tool’s purpose |
parameters | dict | ✅ | JSON Schema for tool inputs |
trace | bool | ❌ | Enable or disable logging (default: True) |
trace_mask | list | ❌ | Redact sensitive trace fields |
JSON Schema Types
String
Copy
parameters={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query"
}
},
"required": ["query"]
}
Number
Copy
parameters={
"type": "object",
"properties": {
"count": {
"type": "integer",
"description": "Number of results",
"minimum": 1,
"maximum": 100
}
}
}
Boolean
Copy
parameters={
"type": "object",
"properties": {
"include_metadata": {
"type": "boolean",
"description": "Include metadata in response"
}
}
}
Array
Copy
parameters={
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {"type": "string"},
"description": "List of items to process"
}
}
}
Object (Nested)
Copy
parameters={
"type": "object",
"properties": {
"filters": {
"type": "object",
"properties": {
"min_price": {"type": "number"},
"max_price": {"type": "number"}
}
}
}
}
Tool Patterns
API Wrapper
Copy
import requests
from laddr import tool
@tool(
name="api_call",
description="Call external API",
parameters={
"type": "object",
"properties": {
"endpoint": {"type": "string"},
"method": {"type": "string", "enum": ["GET", "POST"]},
"data": {"type": "object"}
},
"required": ["endpoint"]
}
)
def api_call(endpoint: str, method: str = "GET", data: dict = None):
"""Make API call."""
try:
if method == "GET":
response = requests.get(endpoint)
else:
response = requests.post(endpoint, json=data)
response.raise_for_status()
return {"status": "success", "data": response.json()}
except Exception as e:
return {"status": "error", "error": str(e)}
Data Transformer
Copy
from laddr import tool
@tool(
name="transform_data",
description="Transform data format",
parameters={
"type": "object",
"properties": {
"data": {"type": "array"},
"format": {"type": "string", "enum": ["json", "csv", "xml"]}
},
"required": ["data", "format"]
}
)
def transform_data(data: list, format: str) -> dict:
"""Transform data to specified format."""
if format == "json":
return {"format": "json", "data": data}
elif format == "csv":
# Convert to CSV
return {"format": "csv", "data": convert_to_csv(data)}
# ...
File Operations
Copy
from laddr import tool
import os
@tool(
name="read_file",
description="Read file contents",
parameters={
"type": "object",
"properties": {
"file_path": {"type": "string"}
},
"required": ["file_path"]
}
)
def read_file(file_path: str) -> dict:
"""Read file from filesystem."""
try:
if not os.path.exists(file_path):
return {"status": "error", "error": "File not found"}
with open(file_path, "r") as f:
content = f.read()
return {"status": "success", "content": content}
except Exception as e:
return {"status": "error", "error": str(e)}
Registering Tools with Agents
Single Tool
Copy
from laddr import Agent
agent = Agent(
name="weather_agent",
tools=[weather_lookup],
# ... other config
)
Multiple Tools
Copy
agent = Agent(
name="multi_tool_agent",
tools=[
weather_lookup,
api_call,
transform_data,
read_file
],
# ... other config
)
Tracing Configuration
Enable/Disable Tracing
Copy
@tool(
name="sensitive_tool",
description="Tool with sensitive data",
parameters={...},
trace=False # Disable tracing
)
def sensitive_tool(...):
pass
Mask Sensitive Fields
Copy
@tool(
name="api_with_auth",
description="API call with authentication",
parameters={...},
trace_mask=["api_key", "password"] # Redact these fields
)
def api_with_auth(api_key: str, password: str, ...):
# api_key and password will be redacted in traces
pass
Error Handling
Return Error Status
Copy
@tool(name="risky_tool", ...)
def risky_tool(...):
try:
result = perform_operation()
return {"status": "success", "data": result}
except ValueError as e:
return {"status": "error", "error": f"Invalid input: {e}"}
except Exception as e:
return {"status": "error", "error": str(e)}
Raise Exceptions
Copy
@tool(name="critical_tool", ...)
def critical_tool(...):
if not validate_input(...):
raise ValueError("Invalid input")
result = perform_operation()
return {"status": "success", "data": result}
Return error dictionaries for recoverable errors. Raise exceptions for critical failures.
Testing Tools
Manual Testing
Copy
# Test tool directly
result = weather_lookup(city="San Francisco", units="metric")
print(result)
# Test with invalid input
result = weather_lookup(city="")
print(result)
Unit Testing
Copy
import pytest
from tools.weather_tools import weather_lookup
def test_weather_lookup_success():
result = weather_lookup(city="San Francisco")
assert result["status"] == "success"
assert "data" in result
def test_weather_lookup_invalid_city():
result = weather_lookup(city="")
assert result["status"] == "error"
Integration Testing
Copy
async def test_tool_with_agent():
agent = Agent(
name="test_agent",
tools=[weather_lookup],
# ... config
)
result = await agent.process_task({
"query": "What's the weather in San Francisco?"
})
# Verify tool was called
assert "weather_lookup" in str(result)
Best Practices
1. Clear Descriptions
Copy
# ✅ Good
@tool(
name="search_web",
description="Search the web for information on a given topic",
...
)
# ❌ Bad
@tool(
name="search",
description="Searches stuff",
...
)
2. Validate Inputs
Copy
@tool(name="safe_tool", ...)
def safe_tool(value: int):
if value < 0:
return {"status": "error", "error": "Value must be positive"}
# ... rest of implementation
3. Consistent Return Format
Copy
# ✅ Good - Consistent format
return {"status": "success", "data": result}
return {"status": "error", "error": "message"}
# ❌ Bad - Inconsistent
return result
return {"error": "message"}
return {"success": True, "result": result}
4. Handle Errors Gracefully
Copy
@tool(name="robust_tool", ...)
def robust_tool(...):
try:
result = perform_operation()
return {"status": "success", "data": result}
except SpecificError as e:
# Handle specific error
return {"status": "error", "error": str(e)}
except Exception as e:
# Log unexpected errors
logger.error(f"Unexpected error: {e}")
return {"status": "error", "error": "Internal error"}
Advanced Patterns
Async Tools
Copy
from laddr import tool
import aiohttp
@tool(name="async_api_call", ...)
async def async_api_call(url: str):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return {"status": "success", "data": await response.json()}
Tool Composition
Copy
@tool(name="composite_tool", ...)
def composite_tool(...):
# Use other tools internally
result1 = helper_tool1(...)
result2 = helper_tool2(...)
return combine_results(result1, result2)
Next Steps
- MCP Integration - Connect to MCP servers
- Agent Configuration - Configure agents
- System Tools - Custom system tools
- Examples - See tool examples