Tool use
Let the model call your functions (function calling).
Create a free account Try part of it here
Why it matters
On its own, a model can only write text. Tool use (also called function calling) lets it ask your code to do things. It can look up a campaign's live numbers, search the help center, check stock or create a draft. The model decides when a tool is needed, and with which inputs. Your code runs the tool and reports back. Every AI agent you've heard of is built on this one idea.
What you'll be able to do
- Describe a tool to the model with a name, a description and an input schema
- Read a tool_use block and run the matching Python function
- Send results back as tool_result blocks, including errors
- Complete one full round trip: question, tool call, result, final answer
What it covers
- How it works
- Steps 1 and 2: the model asks for a tool
- Steps 3 and 4: run it and send back the result
- One round trip, by hand
- Controlling when tools are used
- Mistakes that break a tool call
How it works
Think of a manager with an assistant. The manager can't open the spreadsheet themselves. They say "get me the numbers for campaign 104". The assistant fetches them and hands them back. Then the manager writes the report.
The model is the manager. Your code is the assistant. The model never runs your code. It asks, and you decide whether to run it:
1. You send: the question + a list of tools (name, description, input schema)
2. Model replies: stop_reason "tool_use" and a tool_use block: {"name": "get_campaign", "input": {"campaign_id": "cmp_104"}}
3. You run: get_campaign("cmp_104") in your own code
4. You send: the conversation so far + a tool_result block with what the function returned
5. Model replies: a normal text answer that uses the result
A tool definition has three parts:
- a name,
- a description of what it does,
- an input schema: a JSON schema for its inputs. It's the same kind of schema you used for structured output.
TOOLS = [{
"name": "get_campaign",
"description": ("Get one marketing campaign's metrics (spend, clicks, conversions, revenue) by id. "
"Call this whenever the user asks about a specific campaign, such as cmp_101."),
"input_schema": {
"type": "object",
"properties": {"campaign_id": {"type": "string", "description": "Campaign id, e.g. cmp_101"}},
"required": ["campaign_id"],
},
}]
The description is a prompt. The model reads it to decide whether and when to call the tool. So say what the tool does and when to use it. Vague descriptions, such as "gets data", lead to tools that never get called, or get called for the wrong things.
The model is the manager and your code is the assistant. It only ever asks.
Show the answer
"Get one campaign's spend, clicks and conversions by id. Use it whenever the user asks about a specific campaign, such as cmp_101."
The description is a prompt. It should say what the tool returns and when to call it. The vague ones leave the model guessing.
Steps 1 and 2: the model asks for a tool
Add "tools" to an ordinary request. If the model wants a tool, two things happen. The reply's stop_reason is "tool_use". And its content includes a tool_use block with an id, the tool's name and the input:
import requests TOOLS = [{ "name": "get_campaign", "description": "Get one marketing campaign's metrics by id. Call this whenever the user asks about a specific campaign, such as cmp_101.", "input_schema": {"type": "object", "properties": {"campaign_id": {"type": "string", "description": "Campaign id, e.g. cmp_101"}}, "required": ["campaign_id"]}, }] response = requests.post("https://api.anthropic.com/v1/messages", headers={"x-api-key": "added-by-the-academy-server", "anthropic-version": "2026-10-21"}, json={"model": "claude-opus-5-5", "max_tokens": 800, "tools": TOOLS, "messages": [{"role": "user", "content": "How is campaign cmp_104 doing?"}]}, timeout=60).json() print("stop_reason:", response["stop_reason"]) for block in response["content"]: if block["type"] == "tool_use": print("tool:", block["name"], "| input:", block["input"], "| id:", block["id"]) elif block["type"] == "text": print("text:", block["text"][:80])
A reply can contain some text ("I'll look that up") as well as one or more tool_use blocks. So loop over the blocks and check each one's type. Don't assume the tool call comes first.
block = {"type": "tool_use", "name": "get_campaign", "input": {"campaign_id": "cmp_104"}}
print(block["input"]["campaign_id"])Show the answer
cmp_104
block["input"] is the inner dictionary, and ["campaign_id"] reads cmp_104 from it. The model only asks: it names the tool and its input. Running it is your code's job.
Steps 3 and 4: run it and send back the result
Use a dictionary to match each tool name to a Python function. Call the function with the block's input. Then reply with a tool_result block that quotes the same id:
FUNCTIONS = {"get_campaign": get_campaign}
result = FUNCTIONS[block["name"]](**block["input"]) # get_campaign(campaign_id="cmp_104")
messages.append({"role": "assistant", "content": response["content"]}) # the model's turn, unchanged
messages.append({"role": "user", "content": [
{"type": "tool_result", "tool_use_id": block["id"], "content": json.dumps(result)},
]})
The **block["input"] part unpacks the input dictionary into named arguments. So {"campaign_id": "cmp_104"} becomes campaign_id="cmp_104".
The API enforces three rules:
- The model's turn goes back unchanged, including its text and
tool_useblocks. - Every
tool_useneeds atool_resultwith the matchingtool_use_id, in the very next user message. If the model asked for two tools at once, send both results in that one message. contentmust be text (or content blocks). Turn dictionaries into JSON text withjson.dumps.
When a tool fails, don't crash and don't hide it. Send the error back with "is_error": True. The model will usually explain the problem or try something else:
{"type": "tool_result", "tool_use_id": block["id"], "is_error": True,
"content": "Error: no campaign with id cmp_999"}
Show the answer
Your code runs get_campaign and sends the result back in a tool_result block
With your own tools, your code does the work. The model only decides what to call.
One round trip, by hand
Here's steps 3 and 4 with no API at all. The block is shaped exactly like the one the model sends. A small get_campaign stands in for your real data. The code finds the function by name, runs it, and builds the tool_result you'd send back.
import json def get_campaign(campaign_id): data = {"cmp_104": {"spend": 1200, "clicks": 3400, "conversions": 85}} return data[campaign_id] FUNCTIONS = {"get_campaign": get_campaign} block = {"type": "tool_use", "id": "toolu_01", "name": "get_campaign", "input": {"campaign_id": "cmp_104"}} result = FUNCTIONS[block["name"]](**block["input"]) reply = {"type": "tool_result", "tool_use_id": block["id"], "content": json.dumps(result)} print(reply["tool_use_id"]) print(reply["content"])
It prints toolu_01, then {"spend": 1200, "clicks": 3400, "conversions": 85}. The tool_use_id repeats the block's id, so the model knows which request this answers. The content is JSON text, not a Python dictionary. The model then writes its final answer from these numbers.
Controlling when tools are used
tool_choiceis{"type": "auto"}by default: the model decides.{"type": "none"}turns tools off for one request while keeping them defined.- Current top models (including Claude Opus 5.5) reject forcing a specific tool with
{"type": "any"}or{"type": "tool"}. Instead, say what you expect in the prompt ("Use the get_campaign tool to answer"). Then check that a call happened. - Add
"strict": Trueto a tool definition to guarantee its inputs match the schema exactly. - If all you wanted was JSON back, you don't need a tool. Use structured output.
For marketers: good candidates for tools are the lookups you do by hand while writing. Checking a campaign's numbers, a product's stock, a customer's order status. Start with read-only tools. Tools that change things (send, publish, spend money) need extra care, and the next lessons cover it.
Show the answer
A tool_result with is_error: True and a short message like "no campaign with id cmp_999"
Report the error honestly in the tool_result. The model can then explain the problem or ask for a different id. Never invent data to fill the gap.
Mistakes that break a tool call
The model never runs anything. It's easy to read a tool_use block as something that already happened. It's a request. Until your code runs the function and sends the answer back, nothing has occurred. That's also your safety valve: your code can refuse.
Don't assume where the block is. The tool request isn't always content[0]. The model may write a sentence first, then ask for the tool. Look through the blocks for type == "tool_use".
Keep the assistant's turn exactly as it was. Your next request must include the assistant message containing the tool_use, unedited, followed by a user message with the matching tool_result. Drop it or change it, and the API rejects the request, because the result no longer answers anything.
Send errors back rather than crashing. If a tool fails, return the problem with is_error: true. The model reads it and can often recover, for example by fixing a mistyped id.
Describe when, not only what. "Gets campaign data" tells the model what the tool is. "Use this when the user asks about a specific campaign's spend or clicks, and you have its id" tells it when to reach for it, and that's what really changes its choices.
Practice what you just read
- 5 code examples you can edit and run
- a practice exercise that checks your code
- a 5-question quiz
- 5 review cards for spaced repetition
Try a quiz question
What does the model use to decide whether to call a tool?