03: Tool Calling
Structured output makes the shape stable. The facts can still be invented.
The user asks:
Will it rain tomorrow afternoon? If yes, make the Hangzhou trip easier.
The model does not know live weather. It may guess, and the guess may sound convincing. We do not want the travel assistant to rely on luck.
This chapter adds tool calling: make "check weather" a Python function.
Code First
from __future__ import annotations
import json
from pathlib import Path
from agent_patterns_lab.runtime import Tool, ToolRegistry, Tracer
def main() -> None:
tracer = Tracer()
def get_weather(args: dict) -> str:
return json.dumps(
{
"city": args["city"],
"date": args["date"],
"forecast": "light rain after 15:00",
"temperature_c": "18-23",
},
ensure_ascii=False,
)
tools = ToolRegistry(
[
Tool(
name="get_weather",
description="Get a simple weather forecast for a city and date",
handler=get_weather,
)
]
)
out = tools.call("get_weather", {"city": "Hangzhou", "date": "tomorrow"}, tracer=tracer)
print(out)
trace_path = tracer.export_jsonl(Path(".traces") / "20_tool_calling.jsonl")
print(f"[trace] {trace_path}")
if __name__ == "__main__":
main()
Run:
uv run python examples/20_tool_calling.py
Expected output:
{"city": "Hangzhou", "date": "tomorrow", "forecast": "light rain after 15:00", "temperature_c": "18-23"}
A Tool Is Just A Function
The smallest tool call is:
tool name + args -> Python function -> tool result
This example has one tool:
def get_weather(args: dict) -> str:
return json.dumps(
{
"city": args["city"],
"date": args["date"],
"forecast": "light rain after 15:00",
"temperature_c": "18-23",
},
ensure_ascii=False,
)
It returns a JSON string because later code or models can read fields from it.
ToolRegistry Is The Allowlist
Register the tool:
tools = ToolRegistry(
[
Tool(
name="get_weather",
description="Get a simple weather forecast for a city and date",
handler=get_weather,
)
]
)
This boundary matters.
Later, the model may request a tool. Python still decides what can actually run. If a tool is not registered, it cannot be called.
This chapter calls the tool directly so the tool boundary is visible:
out = tools.call("get_weather", {"city": "Hangzhou", "date": "tomorrow"}, tracer=tracer)
In the agent loop chapter, the model will output a tool action and Python will execute it.
What It Fixes
Tool calling gives some facts back to code:
| Before | After |
|---|---|
| Model guesses weather | Python tool returns weather |
| Model casually states temperature | Tool returns temperature_c |
| Source is hard to inspect | Trace contains the tool call and result |
Now the travel assistant can say "light rain after 15:00" because a tool returned it.
What It Does Not Fix
Tool calling gives the program a capability. It does not decide the next step.
There are two different shapes now:
- Known path: check weather, draft route, format answer.
- Unknown path: if weather is rainy, search indoor places; if a place is closed, search again.
The first does not need an agent. Code can control the path.
Next: use the more predictable option first in 04: Workflow.