Tool Use (Function Calling)
Tool use lets Claude interact with the outside world: database queries, a weather API, sending email, running code. Claude does not run the function itself (for client tools) — it tells you which tool to call and with what input; your code runs it and sends the result back.
7.1 Agentic loop
- 1.1. Request: messages + tools definitions
- 2.2. Claude: stop_reason: tool_use
- 3.3. Your code: run the function
- 4.4. tool_result: send back in a user message
- 5.5. Claude: reads the result and moves on
- 6.6. Final answer: stop_reason: end_turn
The loop continues until Claude stops asking for tools
7.2 Tool definition and manual loop
tools = [{
"name": "get_order_status",
"description": "Returns the current status of an order by order ID. Use it when the user tracks an order.",
"input_schema": {
"type": "object",
"properties": {"order_id": {"type": "string", "description": "For example ORD-1234"}},
"required": ["order_id"],
"additionalProperties": False,
},
"strict": True, # will match the input schema exactly
}]
messages = [{"role": "user", "content": "Where is my order ORD-1234?"}]
while True:
r = client.messages.create(model="claude-opus-5-5", max_tokens=4000,
tools=tools, messages=messages)
messages.append({"role": "assistant", "content": r.content})
if r.stop_reason != "tool_use":
break
results = []
for b in r.content:
if b.type == "tool_use":
out = get_order_status(**b.input) # your function
results.append({"type": "tool_result", "tool_use_id": b.id, "content": out})
messages.append({"role": "user", "content": results}) # all results in ONE message7.3 Tool Runner (SDK helper)
Instead of writing the loop yourself, you can use the SDK's Tool Runner — it runs the request → run tool → send back cycle for you.
from anthropic import beta_tool
@beta_tool
def get_weather(location: str) -> str:
"""Tells the weather of a city.
Args:
location: Name of the city, for example Delhi.
"""
return f"{location}: 31°C, sunny"
runner = client.beta.messages.tool_runner(
model="claude-opus-5-5", max_tokens=16000, tools=[get_weather],
messages=[{"role": "user", "content": "Weather in Delhi?"}],
)
for message in runner: # each iteration is one message; stops when Claude is done
print(message)7.4 Important concepts
| Concept | Detail |
|---|---|
| tool_choice | auto (default, Claude decides), none (no tools). On newer models (Opus 5.5, Sonnet 5.5, Fable 5.1) forced any / tool gives a 400 — name the tool in the prompt instead |
| Parallel tool use | Several tool_use blocks in one response. Send all the results in a single user message |
| is_error: true | If a tool fails, send the tool_result with the error; do not drop it |
| strict: true | Guarantee that the tool input matches the schema exactly |
| Good description | What the tool does, when to use it, when not to — the biggest quality factor |
| Tool search | With very many tools, use defer_loading + the tool search tool; only the needed tools get loaded |
| Programmatic tool calling | Claude calls your tools from inside code execution (fewer round trips) |
7.5 Anthropic's built-in tools
| Tool | Where it runs | What it does |
|---|---|---|
| Web search | Server (Anthropic) | Internet search, with citations |
| Web fetch | Server | Fetches the content of a URL that appears in the conversation |
| Code execution | Server sandbox | Run Python/bash, create files, data analysis |
| Bash | Client (your system) | Shell commands — schema-less, Anthropic-defined |
| Text editor | Client | View/create files, edit with str_replace |
| Computer use | Client or hosted | Look at screenshots and drive the mouse/keyboard |
| Memory tool | Client | Remember things in files across conversations |
| Advisor tool | Server | A smaller model gets advice from a bigger model |
| MCP connector | Server | Tools of a remote MCP server straight from the API |
r = client.messages.create(
model="claude-opus-5-5", max_tokens=16000,
tools=[{"type": "web_search_20260209", "name": "web_search", "max_uses": 5}],
messages=[{"role": "user", "content": "Tell me this week's AI news"}],
) # server tool: no loop to write, the result comes in this same response