C4 · lesson 3 · 10 min read
tool_choice, parallel calls and tool count
Control whether and which tool Claude calls, handle several calls in one turn, and keep large tool sets accurate with tool search.
Հայերեն
Three decisions shape how Claude uses your tools: whether it has to call one, how many calls it can make per turn, and how many tools it has to choose from.
The four tool_choice options
tool_choice |
Meaning | Default when |
|---|---|---|
{"type": "auto"} |
Claude decides whether to call a tool | tools are provided |
{"type": "any"} |
Claude must call one of the tools | — |
{"type": "tool", "name": "x"} |
Claude must call tool x |
— |
{"type": "none"} |
Claude can’t call any tool | no tools are provided |
With any and tool, the API prefills the assistant turn so it starts with a
tool call. Two consequences:
- Claude writes no text before the
tool_useblock, even if you ask for an explanation. - A forced call skips thinking. The response starts with the tool call and
has no
thinkingblock.
If you want an explanation, or thinking before the call, use auto and say in the
prompt which tool to use.
Classic uses for forcing: an extraction step where the “tool” is really an output
schema (record_incident with severity, service and summary), or a pipeline stage
that must produce exactly one structured call. Also note that changing
tool_choice between requests invalidates the prompt cache for the messages.
Tool definitions and the system prompt stay cached.
Parallel tool use
Claude 4 and later models may return several tool_use blocks in one
response when the calls are independent. For example, “check checkout,
payments and ledger” produces three http_health calls at once. The API doesn’t
prescribe an order:
- Run independent, read-only calls concurrently (
asyncio.gather,Promise.all) to cut latency. - Run calls with side effects or ordering needs one after another.
- Return all results in one user message. Sending each result in its own message is the most common mistake, and it teaches Claude to stop making parallel calls.
To turn it off, set disable_parallel_tool_use: true inside tool_choice.
It isn’t a top-level parameter:
| Combined with | Effect |
|---|---|
auto |
At most one tool call per response (or none) |
any / tool |
Exactly one tool call |
# parallel_checks.py - needs ANTHROPIC_API_KEY in the environment
import asyncio
import anthropic
client = anthropic.AsyncAnthropic()
tools = [{
"name": "http_health",
"description": "GET /healthz for one internal service and return the HTTP status and latency. "
"Read-only and safe to call in parallel. Use one call per service.",
"input_schema": {"type": "object",
"properties": {"service": {"type": "string", "enum": ["checkout", "payments", "ledger"]}},
"required": ["service"]},
}]
async def http_health(service: str) -> str:
await asyncio.sleep(0.5) # stand-in for a real HTTP call
return f"{service}: 200 OK in 42ms"
async def main() -> None:
messages = [{"role": "user", "content": "Are checkout, payments and ledger healthy?"}]
while True:
resp = await client.messages.create(model="claude-sonnet-5-5", max_tokens=1024,
tools=tools, messages=messages)
messages.append({"role": "assistant", "content": resp.content})
calls = [b for b in resp.content if b.type == "tool_use"]
if resp.stop_reason != "tool_use":
print("".join(b.text for b in resp.content if b.type == "text"))
return
print(f"{len(calls)} call(s) in this turn")
outputs = await asyncio.gather(*(http_health(**c.input) for c in calls))
messages.append({"role": "user", "content": [
{"type": "tool_result", "tool_use_id": c.id, "content": out} for c, out in zip(calls, outputs)
]})
asyncio.run(main())
How many tools?
Every definition costs context, and choice gets harder as the list grows. The tool search docs say selection accuracy degrades past 30–50 tools, and a typical setup with five MCP servers can use about 55k tokens on definitions alone. Your options, in order:
- Trim and consolidate. Give an agent only the tools its job needs, and merge near-duplicates.
- Namespace names (
k8s_,pagerduty_) so that tools from different services don’t blur together. - Use tool search. Add
tool_search_tool_regex_20251119ortool_search_tool_bm25_20251119and mark rarely used tools withdefer_loading: true. You still send every definition, but only searched-for tools enter the context. Keep your 3–5 most-used tools non-deferred, and never defer the search tool itself. The docs suggest it from about 10 tools, or when definitions use more than 10k tokens.
Recap
autois the default.anyforces some tool,toolforces one named tool, andnoneblocks tools.- Forcing skips any text and thinking before the call. It errors with manual
extended thinking and on Opus 5.5 and Sonnet 5.5, so use
autowithstrictthere. - Parallel calls are on by default. Run independent reads concurrently and return
all results in one user message.
disable_parallel_tool_usegoes insidetool_choice. - Keep the tool list small and namespaced. Past roughly 10 tools, consider tool
search with
defer_loading.
$ ./try-it.sh
Try it: parallel health checks
- Export
ANTHROPIC_API_KEYand save the script from this lesson asparallel_checks.py. - Run it and confirm that one response has three
tool_useblocks and that all three results go back in a single user message. - Add
"disable_parallel_tool_use": Truetotool_choiceand run again. Count how many requests the same answer now takes. - Switch the model to one that supports forced tool use (for example
claude-haiku-5-5) and trytool_choice={"type": "any"}. Then try it onclaude-sonnet-5-5and read the 400 error.
Check yourself
4 questions. Original practice questions, not taken from the real exam.