Creating Custom Tools¶
Learn how to create powerful custom tools for your agents using decorators.
Overview¶
Custom tools allow you to extend your agent's capabilities with any functionality you need. Tools are registered using the @agent.tool()
decorator.
from react_agent_framework import ReactAgent
agent = ReactAgent(name="Assistant")
@agent.tool()
def my_custom_tool(input: str) -> str:
"""Tool description that the LLM will see"""
# Your implementation
return result
Basic Tool Creation¶
Simple Tool¶
from react_agent_framework import ReactAgent
agent = ReactAgent(name="Assistant")
@agent.tool()
def get_current_time() -> str:
"""Get the current time"""
from datetime import datetime
now = datetime.now()
return f"Current time: {now.strftime('%H:%M:%S')}"
# Agent can now use this tool
answer = agent.run("What time is it?")
Tool with Parameters¶
@agent.tool()
def greet(name: str) -> str:
"""Greet someone by name"""
return f"Hello, {name}! Nice to meet you!"
answer = agent.run("Greet Alice")
# Output: "Hello, Alice! Nice to meet you!"
Custom Name and Description¶
Override the tool name and description:
@agent.tool(
name="temperature_converter",
description="Convert temperature between Celsius and Fahrenheit"
)
def convert_temp(input_str: str) -> str:
"""Internal function docstring"""
# Parse input like "25 C to F" or "77 F to C"
# ... implementation ...
return result
Type Hints and Validation¶
Use type hints for better LLM understanding:
@agent.tool()
def calculate_area(length: float, width: float) -> str:
"""
Calculate the area of a rectangle.
Args:
length: Length in meters
width: Width in meters
Returns:
Area in square meters
"""
area = length * width
return f"Area: {area} m²"
Error Handling¶
Always handle errors gracefully:
@agent.tool()
def divide_numbers(expression: str) -> str:
"""Divide two numbers. Format: 'a / b'"""
try:
parts = expression.split("/")
a = float(parts[0].strip())
b = float(parts[1].strip())
if b == 0:
return "Error: Cannot divide by zero"
result = a / b
return f"{a} / {b} = {result}"
except (ValueError, IndexError) as e:
return f"Error: Invalid format. Use 'number / number'"
except Exception as e:
return f"Error: {str(e)}"
Complete Example¶
Personal assistant with multiple custom tools:
from react_agent_framework import ReactAgent
from datetime import datetime
import random
agent = ReactAgent(
name="Personal Assistant",
provider="gpt-4o-mini"
)
@agent.tool()
def get_datetime() -> str:
"""Get current date and time"""
now = datetime.now()
return f"Date: {now.strftime('%Y-%m-%d')}, Time: {now.strftime('%H:%M:%S')}"
@agent.tool()
def random_number(range_str: str) -> str:
"""Generate random number. Format: 'min-max' (e.g., '1-100')"""
try:
min_val, max_val = map(int, range_str.split("-"))
number = random.randint(min_val, max_val)
return f"Random number between {min_val} and {max_val}: {number}"
except Exception as e:
return f"Error: Use format 'min-max' (e.g., '1-100')"
@agent.tool()
def convert_temperature(input_str: str) -> str:
"""Convert temperature. Format: 'C to F: 25' or 'F to C: 77'"""
try:
if "C to F" in input_str.upper():
celsius = float(input_str.split(":")[-1].strip())
fahrenheit = (celsius * 9/5) + 32
return f"{celsius}°C = {fahrenheit}°F"
elif "F to C" in input_str.upper():
fahrenheit = float(input_str.split(":")[-1].strip())
celsius = (fahrenheit - 32) * 5/9
return f"{fahrenheit}°F = {celsius:.2f}°C"
else:
return "Use format: 'C to F: value' or 'F to C: value'"
except Exception as e:
return f"Conversion error: {str(e)}"
# Use the tools
answer = agent.run("What time is it?")
answer = agent.run("Generate a random number between 1 and 100")
answer = agent.run("Convert 25 Celsius to Fahrenheit")
Advanced Patterns¶
Tool with External API¶
import requests
@agent.tool()
def get_weather(city: str) -> str:
"""Get current weather for a city"""
try:
# Example with a weather API
api_key = "your_api_key"
url = f"https://api.weather.com/v1/current?city={city}&key={api_key}"
response = requests.get(url, timeout=5)
response.raise_for_status()
data = response.json()
temp = data.get("temperature")
condition = data.get("condition")
return f"Weather in {city}: {temp}°C, {condition}"
except requests.RequestException as e:
return f"Error fetching weather: {str(e)}"
except Exception as e:
return f"Error: {str(e)}"
Tool with State¶
class DatabaseTool:
def __init__(self):
self.cache = {}
def create_tool(self, agent):
@agent.tool()
def query_database(query: str) -> str:
"""Query the database"""
# Check cache
if query in self.cache:
return f"Cached: {self.cache[query]}"
# Simulate database query
result = f"Result for: {query}"
self.cache[query] = result
return result
return query_database
# Usage
agent = ReactAgent(name="DB Agent")
db_tool = DatabaseTool()
db_tool.create_tool(agent)
Async Tool (Future Support)¶
import asyncio
@agent.tool()
def fetch_data(url: str) -> str:
"""Fetch data from URL"""
# For now, use sync wrapper
async def _fetch():
# async implementation
pass
# Run in event loop
loop = asyncio.get_event_loop()
result = loop.run_until_complete(_fetch())
return result
Best Practices¶
1. Clear Docstrings¶
The LLM uses docstrings to understand when to use your tool:
# ✅ Good - clear and specific
@agent.tool()
def send_email(recipient: str, subject: str, body: str) -> str:
"""
Send an email to a recipient.
Args:
recipient: Email address
subject: Email subject line
body: Email message content
Returns:
Success or error message
"""
# Implementation
pass
# ❌ Bad - vague
@agent.tool()
def send_email(recipient: str, subject: str, body: str) -> str:
"""Send email"""
pass
2. Always Return Strings¶
Tools should return strings for the LLM to interpret:
# ✅ Good
@agent.tool()
def calculate(expression: str) -> str:
result = eval(expression)
return f"Result: {result}"
# ❌ Bad - returns number
@agent.tool()
def calculate(expression: str) -> float:
return eval(expression) # LLM expects string!
3. Handle All Errors¶
Never let exceptions crash the agent:
@agent.tool()
def risky_operation(input: str) -> str:
"""Perform a risky operation"""
try:
# Risky code
result = perform_operation(input)
return f"Success: {result}"
except ValueError as e:
return f"Validation error: {str(e)}"
except ConnectionError as e:
return f"Connection failed: {str(e)}"
except Exception as e:
return f"Unexpected error: {str(e)}"
4. Use Type Hints¶
from typing import List, Dict, Optional
@agent.tool()
def process_data(
items: str, # Tools receive strings from LLM
operation: str
) -> str:
"""Process a list of items"""
# Parse string input
item_list = items.split(",")
# Type hints help with internal logic
results: List[str] = []
for item in item_list:
# Process each item
results.append(f"Processed: {item}")
return ", ".join(results)
5. Descriptive Tool Names¶
# ✅ Good - clear purpose
@agent.tool(name="convert_celsius_to_fahrenheit")
def convert_temp(celsius: str) -> str:
pass
# ❌ Bad - vague
@agent.tool(name="convert")
def convert_temp(celsius: str) -> str:
pass
6. Input Validation¶
@agent.tool()
def book_flight(details: str) -> str:
"""Book a flight. Format: 'FROM to TO on DATE'"""
try:
# Parse and validate
parts = details.split()
if len(parts) != 5 or parts[1].lower() != "to" or parts[3].lower() != "on":
return "Invalid format. Use: 'FROM to TO on DATE'"
origin = parts[0]
destination = parts[2]
date = parts[4]
# Validate date format
from datetime import datetime
try:
datetime.strptime(date, "%Y-%m-%d")
except ValueError:
return "Invalid date. Use YYYY-MM-DD format"
# Book flight
return f"Flight booked: {origin} → {destination} on {date}"
except Exception as e:
return f"Booking error: {str(e)}"
Testing Your Tools¶
Test tools independently before using with agents:
# Create agent and tool
agent = ReactAgent(name="Test Agent")
@agent.tool()
def my_tool(input: str) -> str:
"""Test tool"""
return f"Processed: {input}"
# Test directly
result = my_tool("test input")
print(result) # "Processed: test input"
# Test with agent
answer = agent.run("Use my_tool with input 'hello'")
print(answer)
Common Patterns¶
Data Fetcher¶
@agent.tool()
def fetch_user_data(user_id: str) -> str:
"""Get user information by ID"""
# Fetch from database/API
user = get_user_from_db(user_id)
return f"User {user_id}: {user.name}, {user.email}"
Calculator¶
@agent.tool()
def calculate(expression: str) -> str:
"""Evaluate mathematical expression"""
try:
result = eval(expression, {"__builtins__": {}}, {})
return f"{expression} = {result}"
except:
return "Invalid expression"
Formatter¶
@agent.tool()
def format_json(data: str) -> str:
"""Format JSON data"""
import json
try:
obj = json.loads(data)
formatted = json.dumps(obj, indent=2)
return formatted
except:
return "Invalid JSON"
Next Steps¶
-
Use Built-in Tools
Explore available built-in tools
-
MCP Integration
Connect to external tool servers
-
:material-code: Examples
See complete examples