Chapter 5 β Tools & Function Calling
βTools are what separate an agent from a chatbot. Without tools, an LLM can only talk. With tools, it can do.β
What Is a Tool?
A tool is any function that an agent can invoke to interact with the outside world. It could be a web search, a database query, a code interpreter, or an API call. The LLM doesnβt execute the tool itself β it requests a tool call, and your code executes it.
Defining Tools: The Schema
Tools are defined using JSON Schema. This tells the LLM what tools are available, what they do, and what parameters they accept.
OpenAI Format
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a given city. Use this when the user asks about weather conditions.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g., 'San Francisco'"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature units"
}
},
"required": ["city"]
}
}
}
]
Anthropic Format
tools = [
{
"name": "get_weather",
"description": "Get the current weather for a given city.",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "The city name"},
"units": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
]
Pro tip: The
descriptionfield is critical. The LLM uses it to decide when to call a tool. A vague description = unreliable tool use. Be specific about when the tool should be used and what it returns.
Building a Tool Registry
In practice, you map tool names to actual Python functions:
import json
import requests
# Define your tool functions
def get_weather(city: str, units: str = "celsius") -> str:
"""Fetch weather from a weather API."""
resp = requests.get(
"https://api.weatherapi.com/v1/current.json",
params={"key": WEATHER_API_KEY, "q": city}
)
data = resp.json()
temp = data["current"]["temp_c"] if units == "celsius" else data["current"]["temp_f"]
return f"{city}: {temp}Β°{'C' if units == 'celsius' else 'F'}, {data['current']['condition']['text']}"
def web_search(query: str) -> str:
"""Search the web using Tavily."""
from tavily import TavilyClient
client = TavilyClient(api_key=TAVILY_API_KEY)
results = client.search(query, max_results=3)
return "\n".join(r["content"] for r in results["results"])
# Registry: maps function names to callables
TOOL_REGISTRY = {
"get_weather": get_weather,
"web_search": web_search,
}
def execute_tool_call(tool_call) -> str:
"""Execute a tool call from the LLM."""
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
if fn_name not in TOOL_REGISTRY:
return f"Error: Unknown tool '{fn_name}'"
try:
return TOOL_REGISTRY[fn_name](**fn_args)
except Exception as e:
return f"Error executing {fn_name}: {str(e)}"
Parallel Tool Calls
Modern LLMs can request multiple tool calls in a single response. For example, βWhatβs the weather in Tokyo and Paris?β triggers two parallel get_weather calls:
# The LLM returns multiple tool_calls
message.tool_calls = [
ToolCall(id="call_1", function=Function(name="get_weather", arguments='{"city":"Tokyo"}')),
ToolCall(id="call_2", function=Function(name="get_weather", arguments='{"city":"Paris"}')),
]
# Execute all in parallel
import asyncio
async def execute_parallel(tool_calls):
tasks = [execute_tool_async(tc) for tc in tool_calls]
return await asyncio.gather(*tasks)
The Model Context Protocol (MCP)
MCP is an open standard (created by Anthropic, adopted widely) that standardizes how agents connect to tools. Think of it as βUSB for AI toolsβ β a universal plug that lets any agent use any tool server.
Why MCP Matters
| Without MCP | With MCP |
|---|---|
| Every agent integrates each tool differently | Standard protocol for all tools |
| Tool code lives inside the agent | Tools are separate servers, reusable across agents |
| Adding a new tool = changing agent code | Adding a new tool = connecting to a new server |
| No discovery mechanism | Agents can discover available tools at runtime |
MCP in Practice
# An MCP server exposes tools as a standardized service
from mcp.server import Server, Tool
server = Server("weather-tools")
@server.tool("get_weather")
async def get_weather(city: str, units: str = "celsius") -> str:
"""Get current weather for a city."""
# ... implementation ...
return f"{city}: 72Β°F, Sunny"
@server.tool("get_forecast")
async def get_forecast(city: str, days: int = 5) -> str:
"""Get weather forecast for a city."""
# ... implementation ...
return f"{days}-day forecast for {city}: ..."
# Run the server
server.run(transport="stdio")
# An MCP client (your agent) connects to the server
from mcp.client import Client
async with Client("weather-tools", transport="stdio") as client:
# Discover available tools
tools = await client.list_tools()
# Call a tool
result = await client.call_tool("get_weather", {"city": "Tokyo"})
Tool Safety and Sandboxing
β οΈ Critical: Tools execute real actions. A code execution tool can run
rm -rf /. A file write tool can overwrite production configs. Safety is not optional.
Safety Principles
# Example: confirmation gate for dangerous tools
DANGEROUS_TOOLS = {"delete_file", "execute_shell", "send_email"}
def execute_with_safety(tool_call):
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
if fn_name in DANGEROUS_TOOLS:
print(f"β οΈ Agent wants to call: {fn_name}({fn_args})")
if input("Allow? (y/n): ").lower() != "y":
return "Tool call denied by user."
return TOOL_REGISTRY[fn_name](**fn_args)
Building Great Tools: Best Practices
- Clear descriptions: Tell the LLM exactly when to use the tool and what it returns
- Structured output: Return structured data, not raw HTML or binary blobs
- Error messages: Return helpful errors the LLM can understand and recover from
- Idempotent when possible: Calling a tool twice with the same args should be safe
- Bounded output: Cap response size β donβt return 10MB of data into the context window
Whatβs Next
Tools give agents hands to interact with the world. But without memory, every interaction starts from scratch. Letβs explore how agents remember.
Next: Chapter 6 β Memory Systems β
β Previous: Chapter 4 β The Agent Loop Β· Next: Chapter 6 β Memory Systems β
Last updated: April 2026