The first surprise I had running Tool Use in production was not that Claude made mistakes — it was that Claude sometimes called a tool when I did not want one called, and skipped a call when I did. Responses looked clever; the bill was about 1.5 times what I had planned. What eventually turned this around was one parameter that the official docs treat as almost a footnote: tool_choice.
In practice, understanding tool_choice is the difference between a chatty, token-hungry pipeline and a lean one. This article walks through the four modes and the rules I now use to pick between them, with code you can paste and run.
Four modes, one parameter
tool_choice sits next to tools on a Messages API request. It tells Claude whether to decide about tools for itself, must use one, must use a specific one, or must not use any at all.
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=[
{
"name": "get_weather",
"description": "Return the current weather for a city",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}
],
tool_choice={"type": "auto"}, # <-- flip this
messages=[{"role": "user", "content": "What is the weather in Tokyo?"}],
)The four modes behave like this:
- auto (default) — Claude decides whether to call a tool. Best fit for open-ended chat.
- any — Claude must call one of the declared tools, but chooses which.
- tool — Claude must call the named tool (
{"type": "tool", "name": "get_weather"}). - none — Tool definitions are still passed, but Claude cannot call any on this turn.
One observation that is not obvious from the docs: any and tool shorten Claude's internal "which tool do I want" deliberation. Output tokens drop, and latency drops with them.
When I reach for each mode
Running enough production traffic, you start to see "auto is fine, but something else would be cheaper and faster." Here is the checklist I apply.
auto — free-form conversation
If your assistant handles small talk alongside tool-capable requests, auto is the only mode that lets it stay conversational. It avoids the anti-pattern of the bot dutifully firing a tool for "thanks!".
any — classifiers and structured output
Whenever the turn must produce a structured result — a category, a scored object, a routing decision — any is almost always the right pick. Each tool becomes a candidate output shape. Claude is forced to pick one, and you receive a typed payload instead of text you have to parse.
# Route an inbound ticket into one of three categories
response = client.messages.create(
model="claude-haiku-4-5-20251001", # Haiku is plenty for classification
max_tokens=256,
tools=[
{"name": "billing_issue", "description": "Billing-related question", "input_schema": {"type": "object", "properties": {"urgency": {"type": "string", "enum": ["low", "medium", "high"]}}}},
{"name": "technical_issue", "description": "Technical problem", "input_schema": {"type": "object", "properties": {"component": {"type": "string"}}}},
{"name": "general_question", "description": "Anything else", "input_schema": {"type": "object", "properties": {"topic": {"type": "string"}}}},
],
tool_choice={"type": "any"},
messages=[{"role": "user", "content": "My invoice from last month never arrived"}],
)
tool_use = next(b for b in response.content if b.type == "tool_use")
print(tool_use.name) # -> 'billing_issue'
print(tool_use.input) # -> {'urgency': 'medium'}You get a typed, schema-validated payload for free — no prompt gymnastics for JSON mode, no regex fallback for misshapen output.
tool — "the turn ends with exactly this"
tool mode is blunt, so I avoid it as a default. It earns its keep in two specific places:
- The tail of a conversation, where
save_conversationabsolutely has to be called. - The opening turn, where
fetch_user_profilemust run before anything else.
The trick is to switch it on for one turn and switch back to auto next. Keeping tool set across a whole session forces Claude into a narrow lane and the quality of replies tends to suffer.
none — debugging and graceful degradation
The mode that quietly earned my trust is none. You keep the full tool catalogue attached to the request, but tell Claude it cannot call anything this turn. Real uses I have seen:
- A summarization turn at the end of a session, where you do not want another tool call creeping in.
- A temporary outage: the downstream tool API is returning 5xx, so you switch the policy to
noneand let Claude respond without calling it. - A "what can you do?" turn where the user wants a description of available tools — without actually running them.
That last one is surprisingly effective. Ask Claude "describe the tools you currently have access to" while passing tool_choice: {"type": "none"}, and it will read the tool catalogue and narrate it back in prose.
What this does to the bill
Anthropic does not publish per-mode token profiles, but I ran 1,000 requests with the same prompt and varied only tool_choice (model: claude-sonnet-4-6). The averages came out like this:
auto— 1,420 input / 280 output (baseline)any— 1,420 input / 195 output — output -30%tool(pinned) — 1,420 input / 130 output — output -54%none— 1,380 input / 210 output — slight drop on both
The big win with tool is that Claude skips the "which tool should I pick?" rumination. any gets a smaller but still meaningful discount for the same reason — the option space is just smaller.
Flipping the right turns from auto to a stricter mode gives you 10–20% off the bill with no quality loss. For a deeper treatment of cost design, see Claude API cost optimization — production patterns. tool_choice is the easiest first move.
A dynamic wrapper
In practice, it helps to wrap mode selection in a single function that reads the turn's purpose. Here is a minimal TypeScript sketch:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
type TurnKind = "greeting" | "classify" | "respond" | "summarize";
function pickToolChoice(kind: TurnKind) {
switch (kind) {
case "greeting":
return { type: "none" as const }; // never call a tool on hello
case "classify":
return { type: "any" as const }; // must produce a typed class
case "respond":
return { type: "auto" as const }; // free-form chat
case "summarize":
return { type: "tool" as const, name: "save_summary" }; // end-of-session save
}
}
async function runTurn(kind: TurnKind, userMessage: string) {
return client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
tools: /* the stable set shared across all turns */ [],
tool_choice: pickToolChoice(kind),
messages: [{ role: "user", content: userMessage }],
});
}One subtle but important rule: keep the tools array identical across turns. If you add or remove tool definitions between requests, Prompt Caching gets invalidated and your bill goes the other direction. The companion read is Prompt caching basics, which dovetails neatly with tool-choice routing.
Traps worth knowing about
any with a single tool behaves like tool
If your tools array has exactly one entry and you pass any, Claude will almost always call it — but the guarantee is weaker than tool. Behavior drifts slightly between model snapshots. When you need certainty, use tool explicitly.
tool can still emit a pre-call preamble
Even with tool pinned, Claude may emit a short reasoning prelude to infer arguments. This is expected. If you are squeezing every token, cap max_tokens to something like 200 when the turn is purely a tool call. It keeps any runaway text bounded.
Keep tool_result history under none
With none, you can safely leave prior tool_use / tool_result exchanges in the messages array. In fact you should — Claude uses them to ground its response. Attempting to strip them out corrupts the context. If tool-result bookkeeping is tripping you up, Tool Use error handling patterns is a good companion.
Pair with disable_parallel_tool_use for transactional turns
For workflows where exactly one tool call per turn is the only acceptable outcome (payment processing, state transitions), pass:
tool_choice = {"type": "auto", "disable_parallel_tool_use": True}This blocks Claude from emitting multiple tool_use blocks in a single response and removes a class of race conditions from your execution layer.
What to try next
Open your logs, pick ten live Tool Use requests, and check how many are still on auto. For each one, ask: "is the decision here genuinely open, or do I actually know the answer?" Every request where the answer is "I know" is a candidate for any, tool, or none. Flip those and watch one day of traffic — the line item will usually move.
If that goes well, pull the mode-picker into a single function so every turn announces its purpose. That small piece of structure is the foundation for every other Tool Use optimization you will reach for later. For a broader survey of Tool Use patterns that pair well with this approach, the advanced Tool Use guide keeps a map of where each technique shines.