Tool Use Agents
A tool-use agent turns words into checked actions: the model names a function and its arguments, real code runs it, and the result comes back as context.
Why Does This Exist?
A model alone is a locked room. Ask for Saturday's weather in Lisbon and it answers with a number it cannot know, because its weights froze months ago and weather changes by the hour. It cannot measure, fetch, or click. It can only continue text.
A tool-use agent opens one door: the model emits a structured request naming a real function with typed arguments, and a separate program checks that request, runs it, and pastes the result back. The model still writes the final sentence. It just stops inventing the number inside it.
This page covers that round trip: the contract, the three handoffs, and the check that keeps it safe. The loop this round trip plugs into is ReAct. Splitting the work into steps first lives in task decomposition. Searching before answering lives in retrieval-augmented agents, and running code lives in code execution agents.
Basics first: a tool is an ordinary function your code can run, such as a weather lookup. A schema is the written contract for calling it: the name, when it applies, and each argument's name and type. The model writes the request. It never runs anything itself.
Think of It Like This
A concierge who phones the specialist
You ask a hotel concierge for tomorrow's ferry times to an island you have never visited. A good concierge does not recite times from memory to avoid an awkward pause. They pick up the phone, give the ferry office the exact route and date, and read back whatever the office says. The request is precise enough that someone else can act on it, and the answer comes from the party that actually knows.
A model with tools plays the concierge, never the ferry office. It names the lookup and supplies the arguments, then waits for the real answer instead of filling the silence with a guess.
Where it stops: you trust the ferry office because you dialled the number yourself. Never extend that trust to the model. The harness checks every call against the schema before anything runs, because the concierge in this story is a fluent guesser with a phone.
How It Actually Works
Nothing works until the harness tells the model what exists. Each tool is declared with three parts: a name (get_weather), a plain-language description ("current forecast for a city on a date"), and typed parameters (city as string, date as string). That declaration is the contract the model fills in.
The round trip in three handoffs
Call the model's output , a pair of name plus arguments. Call the check , which returns either ok or an error string. In plain words: is what the model wants to happen, and is the gate that decides whether it may.
First, the model emits as data, not as code, so nothing executes. Second, the harness runs : every required field present, every value the right type. On failure it sends the error back as text and the model retries. On success it runs the real function outside the model with exactly those arguments. Third, the return value lands in the conversation as a new message, and the model continues with the fact in front of it.
Worked example: one Lisbon forecast
You ask: "Weather in Lisbon on Saturday?" The model emits get_weather(city="Lisbon", date="Saturday").
The harness runs its two type checks: city is a string, date is a string. Two checks, two passes, so it runs the lookup. Back comes {"temp_c": 24, "condition": "clear"}.
The model answers with the real number: 24C. Convert it for an American reader: . Multiply first: . Divide: . Add: . So 24C is 75.2F, and the sentence the model now writes carries a checked fact: clear skies, 24C (75.2F). That is the diagram above with the same call and the same number.
The trade is direct. Each call buys freshness and real effects, and pays in latency plus the tokens spent sending schemas and results back and forth. Skip tools for anything the weights already hold.
Code
The point here is the boundary drawn in code. This stdlib-only check validates arguments before anything runs, then converts the returned temperature so the answer can quote both units:
from typing import Any
SCHEMA: dict[str, type] = {"city": str, "date": str}
def validate_call(args: dict[str, Any]) -> str | None: """Return None when args are safe to execute, else the error to send back.""" for field, kind in SCHEMA.items(): if field not in args: return f"missing required field: {field}" if not isinstance(args[field], kind): return f"{field} should be {kind.__name__}, got {type(args[field]).__name__}" return None
def c_to_f(temp_c: float) -> float: return temp_c * 9 / 5 + 32
good = {"city": "Lisbon", "date": "Saturday"}bad = {"city": "Lisbon", "date": 20260814}print(validate_call(good)) # -> Noneprint(validate_call(bad)) # -> date should be str, got intprint(c_to_f(24)) # -> 75.2The good call passes and returns None, the signal for safe to execute. The bad call fails on date, and the harness would hand that string back for a retry. The last line reproduces the worked example: .
Watch Out For
Running a side effect on a call you never validated
Symptom: a booking, email, or payment fires with arguments nobody checked, usually on a path someone marked trusted to save a round trip. A check that runs only sometimes is no check at all. The one skipped call carries the hallucinated argument.
Route every call, read-only or not, through a single choke point that runs validation before the function underneath executes. Validate every time, not just when the call looks suspicious.
Trusting a call because the shape is right
Symptom: every type check passes and the answer is still wrong. The user said the coast, the model filled the city with Lisbon, and the harness had nothing to reject because a string is a string. Shape validation cannot see invention.
Ground arguments against the conversation before any side effect: every value the call carries should trace to something the user said or a tool returned. When a required argument is missing, ask for it. Never let the model guess it quietly.
The Quick Version
- Tool use lets a model emit a typed request naming a tool and its arguments, while only the harness executes anything.
- Each tool is declared as a contract: a name, a plain-language description the model uses to time the call, and a typed parameter schema.
- The round trip is three handoffs: the model emits the call, the harness validates and runs it, and the result returns as a new message.
- A Lisbon call passes two type checks and returns 24C, which converts to 75.2F for the final answer.
- A valid shape can still carry an invented value, so ground arguments against the conversation before side effects.