Skip to content

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.

Ընտրեք տեքստը՝ հայերեն թարգմանությունը տեսնելու համար։Highlight text to see Armenian. Machine-assisted translation; English is the source of truth.

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_use block, even if you ask for an explanation.
  • A forced call skips thinking. The response starts with the tool call and has no thinking block.

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:

  1. Trim and consolidate. Give an agent only the tools its job needs, and merge near-duplicates.
  2. Namespace names (k8s_, pagerduty_) so that tools from different services don’t blur together.
  3. Use tool search. Add tool_search_tool_regex_20251119 or tool_search_tool_bm25_20251119 and mark rarely used tools with defer_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

  • auto is the default. any forces some tool, tool forces one named tool, and none blocks 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 auto with strict there.
  • Parallel calls are on by default. Run independent reads concurrently and return all results in one user message. disable_parallel_tool_use goes inside tool_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

  1. Export ANTHROPIC_API_KEY and save the script from this lesson as parallel_checks.py.
  2. Run it and confirm that one response has three tool_use blocks and that all three results go back in a single user message.
  3. Add "disable_parallel_tool_use": True to tool_choice and run again. Count how many requests the same answer now takes.
  4. Switch the model to one that supports forced tool use (for example claude-haiku-5-5) and try tool_choice={"type": "any"}. Then try it on claude-sonnet-5-5 and read the 400 error.

Check yourself

4 questions. Original practice questions, not taken from the real exam.

  1. A platform agent connects six MCP servers that together expose about 120 tools. Tool definitions use a large part of every request, and Claude increasingly picks the wrong tool.

    1. Which approach is designed for this situation?

  2. On a model that supports forced tool use, you send tool_choice {"type": "tool", "name": "record_incident"} and ask Claude to briefly explain its severity choice before recording. The response contains only the tool_use block.

    2. Why, and what should you change?

  3. An extraction pipeline used tool_choice {"type": "any"} with a single parse_alert tool to always get structured output. After you switch the model to claude-opus-5-5, every request returns a 400 error.

    3. What is the recommended change?

  4. Asked "are checkout, payments and ledger healthy?", Claude returns three http_health tool_use blocks in one response. The tool is read-only.

    4. Which two statements about handling this are correct? (Choose two.)

    Choose 2.

Answer every question to check.