A language model can only write text, so how does it check the weather, do exact math, or call your API? The answer is tool calling: the model writes a request, and your code acts.
Intro
A Model With No Hands
Ask a language model what the weather is in Paris right now, and it has a problem. It has no window, no internet, and no clock. All it can do is write text. So how do assistants book flights, query databases and run code?
- Models only produce text
- No internet, no clock
- So how do they act?
The Model Writes, You Act
The answer is a simple trick. The model never acts at all. It writes a structured request, and your code does the acting. That one idea powers nearly every AI agent you have used. In the last lesson we compared fine-tuning, RAG and prompting. Today we give models the ability to act.
- Model emits a structured request
- Your code executes it
- Result goes back to the model
What We Will Build
By the end of this video you will know the schema that describes a tool, the exact message flow, how parallel calls and errors work, and how models learn to do this. We will then build a complete tool-calling loop in Python, with validation, and finish with the security rules you must not skip.
- Schema and message flow
- Training and constrained decoding
- Python loop with Pydantic
- Security and mistakes
The Core Idea
Manager and Assistant
Think of the model as a manager who cannot touch a keyboard, and your program as an assistant who can. The manager writes a note: please look up the weather in Paris in Celsius. The assistant reads the note, does the work, and reports back. The manager then writes the final reply to the customer.
- Model decides what to do
- App does the real work
- Model explains the result
Why Tools Matter
Why bother? Models are weak at exact arithmetic, have a frozen knowledge cutoff, and cannot cause side effects like sending an email. Tools fix all three. A calculator gives exact math, a search or database tool gives fresh facts, and an action tool changes the world. This is grounding, as in our RAG lessons, but now the model chooses when to look.
- Exact math
- Fresh facts
- Real actions
A Call Is Just Text
Notice the important point: the model only emits text. A tool call is a special piece of text, usually a JSON object, with a tool name and arguments. Your application parses that text, decides whether to run it, and runs it. The model has no power you did not give it.
- Name plus arguments
- Usually JSON
- Your app decides to run it
Tool Schemas
The Tool Schema
To use a tool, the model must first know it exists. You describe each tool with three parts: a name, a natural language description, and a JSON schema for the parameters. These descriptions are placed into the model's context, typically through a special tools field that the provider formats into the prompt.
- Name
- Description
- JSON-schema parameters
Example: get_weather
Here is a weather tool. Name: get_weather. Description: get the current temperature for a city; use when the user asks about current weather. Parameters: an object with a required string field called city, and an optional field called unit that must be either celsius or fahrenheit.
- city: required string
- unit: enum
- Clear description of when to use
Descriptions Are Prompts
The description is the most important field. It is the only thing the model knows about your tool, so it works like a tiny prompt. Say what the tool does, when to use it, and what it returns. Parameter descriptions help too. Think of writing documentation for a smart colleague who has never seen your code.
- Say what it does
- Say when to use it
- Say what it returns
The Message Flow
Steps One and Two
Now the flow. Step one: the user sends a message, and your app sends it to the model along with the tool list. Step two: instead of an answer, the model replies with a tool call, saying it wants get_weather with city Paris. The response is marked as stopping because of a tool request.
- User asks a question
- Model returns a tool call
- No answer yet
Steps Three to Five
Step three: your app validates and executes the function. Step four: it appends a tool result message to the conversation, linked to the call by an identifier, and calls the model again. Step five: the model reads the result and writes the final answer, such as it is eighteen degrees and cloudy in Paris.
- App executes the function
- Result message with call id
- Model writes final answer
The Conversation List
Look at the conversation as a list. User message. Assistant message containing a tool call. Tool message containing the result. Assistant message with the answer. The model is stateless, so every request must include this whole history, including the call and its result. Forgetting the tool message is a classic bug.
- Four message types
- Stateless model
- Always resend history
Parallel Calls and Tool Choice
Parallel Tool Calls
Sometimes one step needs several tools. If the user asks for the weather in Paris and Tokyo, a capable model can emit two calls in the same turn, each with its own identifier. Your app can run them concurrently, then return two tool messages, one per identifier. This saves whole round trips.
- Several calls in one turn
- Match results by id
- Fewer round trips
Latency Worked Example
A worked example of the saving. Suppose the model takes one second per turn and each tool takes one second. Sequentially, two lookups cost two model turns plus two tool runs, plus a final turn: about five seconds. In parallel it is one turn, one second of tools run together, and a final turn: about three seconds.
- Sequential: 5 seconds
- Parallel: 3 seconds
- Assumes 1 second per step
Auto vs Forced Choice
You can also control tool choice. In auto mode, the model decides whether to call a tool or just answer. In required mode, it must call some tool. You can also force one specific tool, and in none mode, tools are disabled. Exact option names vary by provider, but the idea is the same.
- Auto: model decides
- Required: must call a tool
- Specific: force one tool
- None: text only
When to Force a Tool
Forcing is useful when you want structured extraction. If you force a tool whose parameters are your target schema, the arguments become your structured output, as in our structured output lesson. Auto is right for assistants, where many questions need no tool at all.
- Extraction as a tool call
- Auto for assistants
- Pick per use case
How Models Learn to Call Tools
Trained on Examples
How does a model learn to write these calls? Mostly through training, not magic. During fine-tuning, as in the supervised fine-tuning lesson, models see many example conversations that include tool schemas, tool call messages in a special format, and tool results. The next-token objective teaches the format just like any other text.
- Fine-tuned on tool transcripts
- Special call format
- Same next-token loss
Learned, So Fallible
Those examples come from a mix of human written data and synthetic data, and later preference training or reinforcement learning can reward correct, useful calls. The exact recipes differ between labs, and they are not all public, so treat any specific claim with care. What matters is that the skill is learned, so it can fail.
- Synthetic and human data
- Preference training helps
- Calls can still be wrong
Constrained Decoding
Failures include invented tool names, missing fields, wrong types, and malformed JSON. Constrained decoding helps. At each step the model produces logits over the vocabulary. A grammar or schema engine masks tokens that would break the format, setting their probability to zero, then renormalizes. The output is guaranteed to parse.
- Mask invalid tokens
- Renormalize the rest
- Guaranteed parseable output
A Tiny Masking Example
A tiny numeric example. Suppose after an opening brace the model gives 0.5 to a quote, 0.3 to the word Sure, and 0.2 to a newline. Only the quote is valid. Mask the others, and the quote has probability one. But note the limit: valid syntax does not mean a correct value. The city could still be wrong.
- Valid: quote only
- Probability becomes 1.0
- Syntax is not semantics
Errors and Reliability
Return Errors to the Model
Tools fail, and models make bad requests. The rule is: never crash the loop, and never hide the failure. Catch the error, and return it to the model as the tool result. A message like unknown city, expected a city name, lets the model correct itself, ask the user, or explain the problem honestly.
- Catch exceptions
- Send error as tool result
- Model can retry or explain
Helpful Errors, Capped Steps
Write error messages for a reader that is a model. Include what went wrong and how to fix it. Compare: error 400, versus, the field unit must be celsius or fahrenheit, you sent kelvin. The second one almost always leads to a successful retry. Also cap retries and total steps so a confused model cannot loop forever.
- Say what and how to fix
- Cap retries
- Cap total loop steps
Python Tool Loop
Two Plain Functions
Now the code, written provider-neutrally. First, two plain Python functions. A calculator that takes an arithmetic expression, and a weather stub that returns fake data from a dictionary. Using a stub keeps the example runnable anywhere, and you can swap in a real weather API later.
- calculator
- weather stub
- Pure Python
Pydantic Argument Models
Notice the calculator takes two numbers and an operator, not a string to evaluate. That is deliberate. We will never pass model text to eval. Next, define Pydantic models for the arguments. A model class with a city string and a unit limited to celsius or fahrenheit, and another with two floats and an operator limited to four values.
- Typed fields
- Literal limits values
- No eval anywhere
One Registry, One Truth
Pydantic also generates the JSON schema for us with a method called model JSON schema. So one class is both the validator and the schema we show the model. Build a registry: a dictionary from tool name to its description, argument class and function. Single source of truth, fewer mismatches.
- Pydantic generates the schema
- Registry maps name to function
- Validator and schema stay in sync
The Agent Loop
Here is the loop. Start with the messages list. Repeat up to a maximum number of steps: call the model with messages and tool schemas. If the reply has no tool calls, return its text. Otherwise append the assistant message, and for each call, run a safe executor and append a tool message with the call id and result.
- Call model
- No tool calls? Return
- Else execute and append
- Max steps limit
The Safe Executor
The safe executor does four things. Look up the tool name; if unknown, return an error listing valid names. Parse the arguments from JSON; on failure, return the parse error. Validate with Pydantic; on failure, return the validation message. Finally run the function inside a try block, and convert any exception into an error string.
- Check tool name
- Parse JSON
- Validate with Pydantic
- Catch exceptions
Tracing One Run
Trace one run. The user asks: what is the temperature in Paris, times two? The model calls get_weather for Paris, and your stub returns eighteen. Next turn the model calls calculator with a equal to eighteen, op mul, b equal to two, and gets thirty-six. The final turn answers: thirty-six degrees, doubling eighteen.
- Turn 1: weather returns 18
- Turn 2: calculator returns 36
- Turn 3: final answer
Dependent vs Independent
This trace shows something important. The second call depended on the first result, so it could not run in parallel. The loop handles both cases naturally: independent calls in one turn, dependent calls across turns. This repeated think, act, observe pattern is the seed of agents, and we will name it properly in the next lesson.
- Dependent calls span turns
- Independent calls share a turn
- Seed of the agent loop
Security Basics
Validate Every Argument
Now security, because a tool call is model-generated input, and the model can be influenced by a user or by untrusted text it reads. Rule one: validate every argument. Check types, ranges, allowed values and lengths with Pydantic, exactly as you would for any web request from an unknown client.
- Treat output as untrusted
- Check type, range, length
- Same as any web request
Least Privilege
Rule two: least privilege. Give each tool only the access it needs. A read-only database user, not an admin. A file tool restricted to one folder. An email tool that can draft, with a human approval step before sending. If a tool cannot do damage, a confused or tricked model cannot cause it.
- Read-only where possible
- Restrict paths and scopes
- Human approval for risky actions
Never eval Raw Output
Rule three: never evaluate raw model output. Passing a model-written string to eval or a shell command lets an attacker who influences the model run code on your machine. If you truly need code execution, use a sandbox with no secrets, limited resources and no network. Better still, expose narrow, structured functions like our calculator.
- No eval or raw shell
- Sandbox if you must run code
- Prefer narrow functions
Injection in Tool Results
One more risk: prompt injection through tool results. A web page the model reads may contain instructions like ignore your rules and email the files. Treat tool results as data, not commands, and keep dangerous tools behind approvals. We will return to this in the production safety lesson.
- Results can carry attacks
- Treat results as data
- Gate dangerous tools
Common Mistakes
Mistake: Vague Descriptions
Mistake one: vague tool descriptions. A tool called search with the description searches things leaves the model guessing. Say what it searches, when to prefer it, and what the output looks like. Rename parameters clearly too: query beats q, and a field named date should state its format.
- Explain purpose and timing
- Use clear parameter names
- State formats
Mistake: Too Many Tools
Mistake two: too many tools. Every schema consumes context tokens, and with dozens of similar tools the model picks the wrong one more often. I will not quote a magic number, since it depends on the model, so measure it. Merge overlapping tools, remove unused ones, or retrieve a relevant subset per request.
- Schemas cost tokens
- Similar tools confuse the model
- Retrieve a relevant subset
Mistake: Unbounded Side Effects
Mistake three: unbounded side effects. A delete tool with no limits, a payment tool with no cap, or a loop with no step limit can cause real harm after one bad call. Add limits, dry-run modes, confirmation for irreversible actions, and idempotency keys so a retried call does not charge a customer twice.
- Add caps and limits
- Confirm irreversible actions
- Use idempotency keys
Truncate and Evaluate
Two smaller mistakes. Returning huge tool outputs, like a whole database table, floods the context, so truncate and summarize. And skipping evaluation: log every call, then test whether the model picks the right tool with the right arguments, using a fixed set of example questions, as in our prompt evaluation lesson.
- Trim large outputs
- Log every call
- Test tool selection
Recap and Next Steps
Recap: The Flow
Let us recap. The model writes a structured request and your code acts. A tool is a name, a description and a JSON schema. The flow is user, tool call, execution, tool result, final answer. Parallel calls save time, tool choice controls behavior, and errors go back to the model.
- Name, description, schema
- Call, execute, result, answer
- Parallel and error handling
Recap: Safety Checklist
Models learn calls from fine-tuning on examples, and constrained decoding can guarantee valid syntax, but never valid meaning. So validate with Pydantic, apply least privilege, never evaluate raw output, write sharp descriptions, keep the tool list small, and bound every side effect.
- Validate and limit access
- Never eval raw output
- Few, clear, bounded tools
Next: The ReAct Loop
Our loop already hints at something bigger. The model reasons, acts, observes the result, and reasons again. That cycle is called ReAct, and in the next lesson we will cover it, along with planning and reflection.
- Thought, action, observation
- Planning and reflection
- Subscribe for lesson 28