Course Content
Agents & Tools Interview Prep
6 sections · 40 lessons
What is the purpose of the tool_choice parameter, and how do you use it?
What you need to know
The modes
| Anthropic | OpenAI | Behaviour | Typical use |
|---|---|---|---|
{"type": "auto"} | "auto" | Model chooses text or tools | Normal agent turns |
{"type": "any"} | "required" | Must call at least one tool | A router that must always dispatch |
{"type": "tool", "name": "x"} | {"type": "function", "name": "x"} | Must call tool x | Forced structured extraction |
{"type": "none"} | "none" | Tools visible but not callable | Final "write the answer" turn |
OpenAI also offers {"type": "allowed_tools", ...} to limit the model to a subset of the declared tools for one turn.
Parallel calls are a separate switch
By default, a model may return several tool calls in one turn. To limit it to at most one:
- Anthropic: add
"disable_parallel_tool_use": trueinsidetool_choice. - OpenAI: set
parallel_tool_calls: falseon the request.
Use this when your tools are not safe to run together — for example two writes to the same booking.
Forced tool use and the newest models
As of 2026, some of the newest Claude models (for example Claude Opus 5.5) reject any and named-tool tool_choice with a 400 error. On those models:
- Use
autoand say plainly in the prompt which tool to call. - Keep
strict: trueon the tool so arguments stay schema-valid. - If you forced a tool only to get JSON back, use structured outputs (
output_config.format) instead. - Check that a call was actually made, and retry once if not.
This is a good example of why the model name belongs in config and the harness should not assume every model supports every option.
1def call_router(messages):2 resp = llm.call(messages, ROUTE_TOOLS,3 tool_choice=llm.force_any_or_auto()) # "any" if the model supports it4 calls = [b for b in resp.content if b.type == "tool_use"]5 if not calls:6 raise NoRouteChosen(resp) # retry once, then fall back to a default route7 return calls[0]force_any_or_auto() lives in the llm wrapper, next to the model name, so a model upgrade changes one file.
A real-life example
A GitHub triage bot uses tool choice at three points:
- Routing — every new issue must be classified as
bug,feature,questionorspam. The classifier turn uses a singleclassify_issuetool with an enum, forced (on a model that supports forcing) so the output is always a structured label. - Investigation — normal agent turns with
auto, so the model can search issues, read CI logs or ask for more information. - Final comment — after 6 investigation steps, the harness sends one last turn with
tool_choice: none, forcing the model to write the comment from what it has instead of starting another search.
When the team tried a newer model for step 1, requests failed with a 400. They switched that route to auto with "Call classify_issue exactly once" in the prompt, kept strict: true, and added a check-and-retry. Classification accuracy stayed the same.
Follow-up questions to expect
- "Why not force a tool on every turn?" — Then the model can never answer in text or ask a clarifying question, so the conversation cannot end naturally.
- "How do you get guaranteed JSON without tool choice?" — Structured outputs: pass a JSON Schema as the response format; the reply itself is constrained to it.
- "Does forcing a tool affect quality?" — Forcing can skip the model's usual reasoning text before the call; for hard decisions, keep
autoand guide it in the prompt.