# Tool Choice

> tool_choice is the parameter that controls whether Claude can decide to use a tool ("auto"), must call any tool ("any"), or must call a specific tool by name. Use specific-tool forcing for mandatory-first-step operations like identity verification before refunds.

**Domain:** D2 · Tool Design + Integration (18% of CCA-F exam)
**Canonical:** https://claudearchitectcertification.com/concepts/tool-choice
**Last reviewed:** 2026-05-04

## Quick stats

- **Modes:** 3
- **Default:** auto
- **Forced use cases:** 2
- **Exam domain:** D2
- **Guarantee:** 100%

## What it is

tool_choice is a request-level parameter that controls whether Claude must, may, or must not call a tool. Three modes: {type: "auto"} (default, model decides), {type: "any"} (model must call any tool), and {type: "tool", name: "X"} (forced to a specific tool). It bridges between probabilistic (the model decides) and deterministic (you decide) tool execution. The wrong choice leaves 30% of your output as unparseable text when determinism is required.

Without override, Claude reads the message history, evaluates available tools, and decides whether a tool is needed at all. In refund flows this is dangerous: 30% of the time Claude asks clarifying questions instead of calling verify_customer. With {type: "tool", name: "verify_customer"} that 30% becomes 0%. The tradeoff is inflexibility: forced tool_choice removes legitimate text-only responses. Use forced sparingly, only for mandatory architectural checkpoints.

The incompatibility with extended thinking is architectural, not cosmetic. When thinking: {type: "enabled"} is active, tool_choice: "any" and forced both return 400 validation errors. You must fall back to tool_choice: "auto". Reasoning-heavy tasks cannot guarantee tool calls; the model reasons, then decides whether to act. The fix is reframing: use extended thinking for analysis, not for deterministic control of execution.

Production fails when tool_choice is mistaken for a safety valve instead of a contract enforcer. tool_choice: "any" does not make your loop robust if tool descriptions are vague, the model still misroutes. It ensures a tool is called, not that the right tool is called. Combining unclear descriptions + forced tool_choice = guaranteed wrong action. Fix descriptions first, then use tool_choice to enforce when, not what.

## How it works

When you call messages.create() with tool_choice, the request is validated before the forward pass. If thinking is enabled and tool_choice is anything other than auto, the API returns a 400 error immediately, no model inference happens. This check happens before token accounting, so invalid combinations fail instantly and cheaply. Valid combinations proceed to the transformer.

Inside the forward pass, the model sees three elements: the system prompt, the messages, and your tool_choice specification. With auto, Claude generates and samples a stop_reason: either end_turn (text is enough) or tool_use (tools needed). With any, the model is nudged to produce a tool_use block, sampling weights shift to favor tool calls over text termination. With forced, tool generation is implicit; sampling never considers end_turn.

The stop_reason is independent of tool_choice. A forced tool call still returns stop_reason: "tool_use". The tool's name is guaranteed to be X; the input might be empty or malformed, but the structure is assured. This is why forced tool_choice is safe for mandatory architecture gates: it guarantees the attempt, not the validity. Argument validation is still your harness's job.

Tool execution remains your responsibility. Even with tool_choice: "any", a tool call might have invalid input (missing required fields, wrong types). Your harness catches it, returns a structured error, and lets Claude retry. The difference is confidence: with auto, you code for text-or-tool branches and handle tool absence; with any, you code only for tool branches. Wrong inputs are handled identically in both.

## Where you'll see it in production

### Mandatory verification before refunds

Refund workflow forces verify_customer as the first call via tool_choice = {type:'tool', name:'verify_customer'}. Removes probabilistic skipping. Combined with PostToolUse policy hooks for amount limits, prompt-only enforcement of the same policy lands ~70% reliable in production.

### Guaranteed structured extraction from unknown documents

Document type may be invoice / receipt / contract. Use tool_choice='any' with three extraction tools (one per type). Model picks the right one if descriptions are clear. 'auto' would return free text 30% of the time.

### Subagent must take action

Research subagent must call at least one tool (search / load / query). Use tool_choice='any' so empty-text returns become impossible. Subagent always emits a tool call before returning.

## Code examples

### Three modes side-by-side

```python
from anthropic import Anthropic

client = Anthropic()

# Mode 1: 'auto' (default), model picks tool OR returns text
resp_auto = client.messages.create(
    model="claude-opus-4-5", max_tokens=512, tools=tools,
    tool_choice={"type": "auto"},
    messages=[{"role": "user", "content": "Help me with a refund."}],
)
# 30% chance: text only ("Sure, can you tell me your order number?")
# 70% chance: tool_use block with verify_customer or lookup_order

# Mode 2: 'any', model MUST call a tool, picks which one
resp_any = client.messages.create(
    model="claude-opus-4-5", max_tokens=512, tools=tools,
    tool_choice={"type": "any"},
    messages=[{"role": "user", "content": "Help me with a refund."}],
)
# 100% chance: tool_use block. Model picks (likely verify_customer first).

# Mode 3: forced, model MUST call this specific tool
resp_forced = client.messages.create(
    model="claude-opus-4-5", max_tokens=512, tools=tools,
    tool_choice={"type": "tool", "name": "verify_customer"},
    messages=[{"role": "user", "content": "Help me with a refund."}],
)
# 100% chance: tool_use block calling verify_customer.

```

> Use forced for mandatory steps (verification before refunds). Use 'any' when a tool must be called but choice is open. Use 'auto' when text is a valid response.

### tool_choice incompatibility with extended thinking

```typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

// ❌ This will fail, extended thinking + 'any' is rejected
try {
  await client.messages.create({
    model: "claude-opus-4-5",
    max_tokens: 16000,
    thinking: { type: "enabled", budget_tokens: 10000 },
    tools,
    tool_choice: { type: "any" }, // Validation error
    messages: [{ role: "user", content: "Analyze and act." }],
  });
} catch (e) {
  // 400: tool_choice 'any' or specific incompatible with thinking
}

// ✓ Correct, extended thinking + 'auto' works
await client.messages.create({
  model: "claude-opus-4-5",
  max_tokens: 16000,
  thinking: { type: "enabled", budget_tokens: 10000 },
  tools,
  tool_choice: { type: "auto" }, // Compatible
  messages: [{ role: "user", content: "Analyze and act." }],
});

```

> If you enable extended_thinking, tool_choice must be 'auto'. Forced and 'any' modes are rejected at request time. Plan accordingly.

## Looks-right vs actually-wrong

| Looks right | Actually wrong |
|---|---|
| Use 'any' to guarantee deterministic output. | 'any' forces a tool call but the model still picks which one. If two tool descriptions overlap, the wrong one fires. For true determinism, use forced ({type:'tool', name:X}). |
| Forced tool_choice removes the need for hooks. | Forced ensures the tool is called, it does NOT validate inputs. Add PreToolUse hooks for argument validation (e.g., refund amount within policy). |
| Use 'any' or forced when extended_thinking is on for safer behavior. | Both modes fail validation when thinking is enabled. Only 'auto' is compatible. If you need both thinking and a guaranteed tool call, drop thinking or accept text fallback. |
| Set tool_choice: "any" once at session start and Claude will keep calling tools every turn. | tool_choice is per-request, not per-session. Each messages.create() call evaluates it independently. If turn 5 omits tool_choice, the default auto returns and Claude may emit text. Pin it on every call that needs the constraint, or wrap the SDK in a helper that injects it automatically. |
| Setting disable_parallel_tool_use: true is unrelated to tool_choice. | It's actually nested inside the tool_choice object: {type: "auto", disable_parallel_tool_use: true}. The flag forces sequential tool calls, useful when downstream side effects must happen in order. Most exam-tested gotcha: developers expect a top-level parameter, never find it, and ship buggy parallel writes. |

## Comparison

| Mode | Behavior | Use case | Extended thinking |
| --- | --- | --- | --- |
| auto (default) | Model picks: tool OR text | Open-ended assistance | Compatible |
| any | Must call a tool; model picks which | Subagent must act; structured-only flows | Incompatible |
| {type:'tool', name:X} | Must call this exact tool | Mandatory first steps (verify before refund) | Incompatible |
| none | Tools available but model returns text | Rare: only allow tools as fallback | Compatible |
| auto + disable_parallel_tool_use | Picks 0 or 1 tool, never multiple in parallel | Sequential side-effect tools (writes, payments) | Compatible |
| any + disable_parallel_tool_use | Must call exactly one tool, no parallelism | Mandatory single action; no fan-out | Incompatible |

## Decision tree

1. **Is a SPECIFIC tool mandatory (verify-before-refund, identity gate)?**
   - **Yes:** Use forced: tool_choice={type:'tool', name:'X'}. Combine with PreToolUse hooks for argument validation.
   - **No:** Go to next question.

2. **Must the model take SOME tool action (any tool)?**
   - **Yes:** Use tool_choice={type:'any'}. Ensure tool descriptions disambiguate clearly.
   - **No:** Use tool_choice={type:'auto'}. Text fallback is acceptable.

3. **Are you using extended_thinking?**
   - **Yes:** Only 'auto' is compatible. Drop forced/any or drop thinking.
   - **No:** All modes available. Pick based on the policy questions above.

4. **Do you need to prevent Claude from calling multiple tools in one turn?**
   - **Yes:** Add disable_parallel_tool_use: true to the tool_choice object. Critical when tool results have ordered side effects (payments, writes, sends).
   - **No:** Leave it off; parallel tool use cuts latency on independent reads.

5. **Are you in an agentic loop where the FIRST turn must be a tool but later turns can return text?**
   - **Yes:** Pin forced or 'any' on the first call only; switch to 'auto' for subsequent turns. tool_choice is per-request, exploit the per-turn flexibility.
   - **No:** Use the same mode every turn for consistency.

## Exam-pattern questions

### Q1. You enable extended_thinking and set tool_choice: any. The API returns a 400 error. Why?

Extended thinking is incompatible with any and forced tool_choice. Use auto only when thinking is enabled. The model reasons, then decides whether to call a tool.

### Q2. Your refund flow uses tool_choice: any to guarantee a tool call. The agent calls lookup_order instead of verify_customer first. Fix?

Switch to forced ({type: "tool", name: "verify_customer"}). any lets the model pick; only forced guarantees a specific tool. Use forced for mandatory architecture gates.

### Q3. The user asks a simple question and your forced tool_choice calls process_refund with empty input. What went wrong?

Forced tool_choice removes the model's option to return text. For ambiguous requests where text is a valid response, use auto. Forced is for mandatory operations only.

### Q4. Your support agent uses tool_choice: auto. 30% of the time it asks clarifying questions instead of calling tools. Is this a bug?

Not a bug, that's auto behavior. The model decides whether a tool is needed. If you need guaranteed tool calls, switch to any (model picks) or forced (you pick). Each has a tradeoff.

### Q5. You set tool_choice: any but the agent calls a tool and immediately returns valid input. Then a clarifying tool... Why is it slower than expected?

Each tool call is a full turn round-trip. If the same task can be done in fewer calls (e.g. one tool with more params instead of three sequential calls), redesign the tool surface for fewer turns.

### Q6. Your forced tool_choice produces tool calls with hallucinated arguments. How do you guard against this?

Force the tool, then validate inputs in your harness. Return structured errors via tool_result if inputs are invalid. The model retries with corrected arguments. Forced tool_choice does not validate inputs.

### Q7. Why is tool_choice: any rejected when extended_thinking is on, but allowed without it?

Extended thinking generates reasoning tokens before the final response. Forcing a tool call would constrain the post-thinking output, breaking the reasoning contract. Anthropic disabled the combination at the API layer.

### Q8. You want both deep reasoning and a guaranteed tool call. Is that possible?

Not in a single turn. Use extended thinking with auto for the reasoning step, then a follow-up turn with forced tool_choice if the model decides to act. Two turns, not one.

## FAQ

### Q1. What's the latency cost of tool_choice: "any" vs "auto"?

Negligible at the API layer, but "any" removes the model's option to short-circuit with text, so you almost always pay for one extra round-trip (the tool execution + result append). On simple queries that "auto" would answer in one turn, "any" doubles your latency.

### Q2. Can I dynamically change tool_choice based on conversation state?

Yes, and it's a common pattern. Start with forced for mandatory verification, switch to "auto" once verified, switch to "any" for action-taking phases. Wrap the SDK call in a state machine that sets tool_choice per turn based on a flag like case.verified.

### Q3. Does tool_choice: "none" actually exist?

Yes, {type: "none"} makes tools available but tells the model not to call them. Used rarely, mostly when you want the model to discuss tools without invoking them (e.g. a help-text turn explaining what tools the agent has). For production, tool_choice simply absent is more idiomatic.

### Q4. If forced tool_choice + extended thinking errors out, can I work around it?

Two patterns: (1) two-call approach, first call uses thinking with "auto" to plan, second call uses forced without thinking to execute; (2) drop thinking and use a stronger system prompt to compensate. The exam answer is: there's no flag to override, the incompatibility is structural.

### Q5. Does tool_choice affect prompt caching?

It does NOT invalidate the cache. The tool_choice parameter is part of the request body but not part of the cached prefix. You can vary it freely across calls and still hit the same cached tools and system prefix.

### Q6. What happens if I set tool_choice: {type: "tool", name: "X"} but tool X isn't in the tools array?

400 validation error before model inference. The API rejects the request because the named tool can't be resolved. Check your tool registry by name on every request that uses forced choice.

### Q7. Why does "any" sometimes still pick the same tool repeatedly even with multiple tools available?

Description quality determines selection, not tool_choice. If two descriptions overlap, the model has a slight bias toward the first matching one. Audit overlap; the fix is description rewriting, not adding more tools.

### Q8. Can I use forced tool_choice with the Batch API?

Yes. Each batched request can carry its own tool_choice. Useful for bulk extraction pipelines: 1000 documents, all forced to the same extract_fields tool. The 50% batch discount applies, structured-extraction batches are one of the cheapest LLM patterns.

### Q9. How do I debug a forced tool_choice that returns malformed input?

Forced choice guarantees the call, not the input quality. If tool_use.input is wrong, fix two things: (1) tighten the JSON Schema with enum, format, and explicit required; (2) rewrite the description to spell out input rules ("customer_id MUST start with cus_"). Then add a PreToolUse validator hook as the safety net.

### Q10. Is there a way to force a tool only when certain text appears in the user message?

Not directly via tool_choice, you'd compute that in your application code. Inspect the user message, set tool_choice accordingly, then send. The API has no conditional tool_choice, all decision logic lives in your harness, which is the right separation of concerns.

---

**Source:** https://claudearchitectcertification.com/concepts/tool-choice
**Vault sources:** ACP-T03 §4.2 modes table; GAI-K04 §7
**Last reviewed:** 2026-05-04

**Evidence tiers** — 🟢 official Anthropic doc / API contract · 🟡 partial doc / inferred · 🟠 community-derived · 🔴 disputed.
