CClaude Cert Prep
ExplainerCCAR-P · P38 min read

Tool Use and tool_choice in Claude, Explained

How the tool-use loop works, why descriptions drive selection, and the tool_choice trap the exam loves to test.

The short answer

You define tools as JSON-schema functions with clear descriptions; Claude returns a tool_use block, you execute the tool and send back a tool_result, and the loop repeats until Claude stops. The key trap: tool_choice:"auto" PERMITS a tool but never FORCES one. Only "any" or a named tool forces a call. Valid input JSON is not proof the call is semantically right.

Tool use is how Claude reaches beyond text generation to call your code, query a database, or hit an external API. It is not a separate endpoint or a special model mode: tools are a feature of the ordinary Messages API. You describe the tools available, Claude decides whether and how to call them, and your application executes the call and feeds the result back. Everything hinges on two things you control precisely: the tool definitions you write and the tool_choice you set.

This explainer is answer-first. The single most-missed idea, and the one certification questions probe repeatedly, is that tool_choice:"auto" gives Claude permission to use a tool but never obligates it. Confusing "permitted" with "forced" produces broken agents and wrong answers. We walk the full loop, show how description quality drives selection, lay out every tool_choice option, cover parallel calls, and close with the distinction between JSON that validates and JSON that is actually correct.

The tool-use loop, end to end

A tool-use turn is a round trip between your application and Claude. You send a request that includes a list of tool definitions. Claude reads the conversation and the tool descriptions, then either answers directly or emits one or more tool_use blocks naming a tool and supplying an input object. Your code executes each requested tool and returns the outcome as a tool_result block in a new user message. Claude reads those results and continues, possibly calling more tools, until it produces a final answer and stops.

  • You call the Messages API with tools=[...] describing what is available.
  • Claude responds; if it wants a tool, stop_reason is "tool_use" and the content contains tool_use block(s), each with a name, an input object, and a unique id.
  • Your application runs the tool for each block and builds a tool_result block that echoes the matching tool_use_id.
  • You append Claude's assistant message and then a user message carrying the tool_result(s), and call the API again.
  • The loop ends when stop_reason is "end_turn" — Claude has what it needs and answers in plain text.

SDKs offer a "tool runner" helper that drives this loop automatically: you supply tool functions, and it calls the API, executes tools, feeds results back, and repeats until Claude is done. A manual loop is only needed when you want to own the control flow yourself. Either way, the underlying request/response shape is identical.

Schemas and descriptions drive tool selection

Each tool has three parts: a name, a description, and an input_schema written in JSON Schema. Claude never sees your implementation — only these three fields. The description is not documentation for humans; it is the primary signal Claude uses to decide when a tool applies. A vague description produces missed calls and misfires; a precise one, prescriptive about when to reach for the tool, measurably improves the right-call rate.

  • Name tools by action: get_weather, search_orders, send_email — specific beats generic.
  • Write descriptions that say WHEN to call, not just what the tool does: "Call this when the user asks about current prices or recent events."
  • Describe every property in the schema, and use enum for fields with a fixed set of values.
  • Mark only truly required parameters in required; give the rest sensible optional defaults.

tool_choice: the auto trap

tool_choice controls whether Claude is allowed or required to use a tool on a given request. There are four values, and the exam-relevant distinction is between permitting and forcing. auto — the default — lets Claude decide; it may answer in plain text and use no tool at all. If your flow depends on a tool actually running, auto will not guarantee it.

tool_choiceBehaviorForces a tool call?
{"type":"auto"}Claude decides whether to use a tool (default)No — permits, does not force
{"type":"any"}Claude must use at least one of the available toolsYes
{"type":"tool","name":"..."}Claude must use the one named toolYes
{"type":"none"}Claude cannot use any tool this turnNo — forbids

Any tool_choice value can also carry disable_parallel_tool_use:true, which caps Claude at a single tool call per response. Forcing with "any" or a named tool is useful for structured extraction or when a step must always run; leaving it on auto is right for conversational agents that should choose freely.

Parallel tool calls

By default Claude may request several tools in a single assistant message — one content block per call. This is a feature, not a glitch: independent lookups (weather in three cities, three files to read) can run concurrently. Your job is to execute them all and return every result together.

  • Execute all requested tool_use blocks, then return ALL tool_result blocks in ONE user message.
  • Splitting results across multiple messages silently trains Claude to stop making parallel calls.
  • For a tool that fails, still return its tool_result with is_error:true — never drop it.
  • To force at most one call per turn, set disable_parallel_tool_use:true on tool_choice.

Schema-valid is not semantically correct

A tool call can pass JSON Schema validation and still be wrong. Schema validity guarantees the shape — required fields present, types correct, enums respected. It says nothing about whether the values make sense for the user's actual intent. "strict":true tightens the guarantee that input matches the schema exactly, but it cannot verify meaning.

  • Valid but wrong: book_flight with destination "Paris" when the user asked for Paris, Texas but Claude assumed France.
  • Valid but wrong: a date field formatted correctly as an ISO date but pointing at the wrong day.
  • Valid but wrong: the correct schema for the WRONG tool — right shape, wrong action.
  • Parse tool inputs with a real JSON parser (json.loads / JSON.parse); never raw-string-match the serialized input, since escaping can vary.

A worked example

Below is a single tool definition and the tool_use block Claude returns for it. Note that the assistant message stops with stop_reason "tool_use", and your application must reply with a tool_result echoing the id before Claude can produce a final answer.

code
{
  "tools": [
    {
      "name": "get_weather",
      "description": "Get current weather for a location. Call this when the user asks about current conditions, temperature, or forecast for a place.",
      "input_schema": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "City and state, e.g. San Francisco, CA"
          },
          "unit": {
            "type": "string",
            "enum": ["celsius", "fahrenheit"],
            "description": "Temperature unit"
          }
        },
        "required": ["location"]
      }
    }
  ],
  "tool_choice": { "type": "auto" }
}

// Claude's response (stop_reason: "tool_use"):
{
  "role": "assistant",
  "content": [
    {
      "type": "tool_use",
      "id": "toolu_01A9",
      "name": "get_weather",
      "input": { "location": "Paris, France", "unit": "celsius" }
    }
  ]
}

// Your reply — a user message carrying the matching tool_result:
{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01A9",
      "content": "18C and partly cloudy"
    }
  ]
}

Key takeaways

  • →Tool use is a loop on the Messages API: define tools, Claude emits tool_use, you execute and return tool_result, repeat until stop_reason is end_turn.
  • →A tool is just name + description + input_schema; the description drives when Claude selects it, so be prescriptive about when to call.
  • →tool_choice:"auto" PERMITS a tool but never FORCES one — only "any" or {"type":"tool","name":...} forces a call; "none" forbids tools.
  • →The API is stateless: resend full history and match every tool_result to its tool_use_id.
  • →Parallel tool calls are default; return all tool_result blocks in one user message, and use disable_parallel_tool_use to cap at one.
  • →Schema-valid JSON is not semantically correct — validate meaning and gate destructive actions in your own code.
  • →Always parse tool inputs with a real JSON parser rather than string-matching the serialized text.

Now practice it

Reading builds recognition; practice builds judgment. Try these on the P3 material.

Frequently asked

Does tool_choice:"auto" make Claude use a tool?

No. auto only permits tool use; Claude may still answer in plain text with no tool call. To guarantee a call, use {"type":"any"} (any available tool) or {"type":"tool","name":"..."} (a specific tool).

What is the difference between "any" and "tool"?

"any" forces Claude to call at least one of the available tools but lets it choose which. "tool" with a name forces that one specific tool. Both guarantee a call; only "tool" pins the choice.

How do I stop Claude from making parallel tool calls?

Add disable_parallel_tool_use:true to your tool_choice object (it works with any tool_choice value). This caps Claude at one tool call per response instead of the default, which allows several.

If a tool call passes JSON Schema validation, is it correct?

Not necessarily. Validation confirms the shape — required fields, types, enums — but not the meaning. A call can be perfectly valid yet target the wrong entity or the wrong tool. Check values against your business rules and gate risky actions behind confirmation.

What links a tool_result to the tool call it answers?

The tool_use_id. Every tool_use block Claude emits has a unique id; your tool_result must set tool_use_id to that same value. Because the API is stateless, you also resend the assistant tool_use message and the user tool_result message on the next request.

All explainers

Independent, unofficial study material from Claude Cert Prep. Not affiliated with Anthropic.