Skip to main content

How the model decides to call a tool

Module 2 · Lesson 3 · 8 min read

TL;DR

On each turn the model looks at the conversation and the available tool descriptions and makes one decision: answer directly, or emit a tool call? You don't script that choice — but you can shape it with good descriptions and constrain it with the tool_choice setting (auto, any, a specific tool, or none).

The idea

You never write "if the user asks math, call the calculator." The model decides, every turn, on its own. It weighs two things:

  1. The request — what is the user actually asking for?
  2. The tool descriptions — does any tool's description match that need?

If a description matches, the model emits a structured tool-use request instead of a normal text answer. If nothing fits, it just answers. This is why Lesson 2 mattered: the description is the input to this decision. Vague descriptions produce bad decisions; sharp ones produce good decisions.

Steering the decision with tool_choice

Most of the time you let the model decide. But sometimes you need to force the issue — and there's a setting for that. It controls whether the model is allowed to choose:

tool_choice controls whether the model may decide for itself.
tool_choiceWhat it doesWhen to use it
autoModel decides each turn: answer or call a toolThe default — almost always what you want
anyMust call some tool, but the model picks whichYou know a tool is needed but not which one
a named toolMust call that exact tool this turnStructured extraction — force one specific call
noneMay not call any tool; text onlyTemporarily disable tools without removing them

Analogy

auto is letting your colleague decide whether to reach for a calculator. any is saying "use a tool for this." A named tool is "use the calculator, specifically." none is "no tools right now, just talk me through it." You're not doing the work — you're setting how much freedom they have to pick.

Why descriptions still beat forcing

Forcing a tool with tool_choice is a blunt instrument. If you set any or a named tool, the model must call it even when calling it makes no sense — so a greeting like "hi" can trigger a pointless lookup. That's why the durable lever is the description: with auto, a well-written description gets the tool called at the right moments and skipped at the wrong ones, with no forcing at all.

Reach for tool_choice when you have a structural reason (you're extracting data and always want the one tool), not as a patch for a fuzzy description.

Picking between several tools

Give the model one tool and the only question is whether to call it. Give it five and the question becomes which one — answered the same way: the request matched against each tool's description. So with several tools, description quality compounds. If lookup_by_email and lookup_by_order_id both say "look up a customer," the model can't choose well. Name what's distinct about each, and it routes correctly:

ToolDescription that routes correctly
lookup_by_email"Find a customer account using their email address."
lookup_by_order_id"Find an order using its order ID (format ORD-12345)."
calculator"Evaluate an arithmetic expression."

Common mistake

Leaving tool_choice on any or a named tool "to be safe" forces a tool call on every turn — including turns where the model should just reply. The result is needless tool calls, extra latency, and odd behavior on simple messages. Default to auto and fix decisions through descriptions; force only when the task truly requires it.

Key takeaways

Key takeaways

  • Each turn the model chooses: answer directly, or emit a tool-use call.
  • The decision is driven by the request matched against tool descriptions.
  • tool_choice constrains that freedom: auto (default), any, a named tool, or none.
  • With several tools, the model routes by description — keep them distinct.
  • Prefer shaping behavior with good descriptions over forcing tools.
Go deeper: what a tool-use request actually looks like

When the model decides to call a tool, it doesn't run anything — it returns a structured tool-use block in its response, something like:

{ "type": "tool_use", "id": "toolu_01A...", "name": "calculator",
  "input": { "expression": "47362 * 1989" } }

Your application sees that block, runs the matching function, and sends the result back as a tool-result block tied to the same id. The model never executed code — it asked, in a structured way your code can dispatch on. Two important consequences:

  • The model can request several tool calls in one turn (you'll see this in the multi-tool lesson). Each gets its own id.
  • Because the result is matched back by id, you can run independent tool calls in parallel and return them together.

That request/result handshake is the atom the whole tool loop is built from — which is the next lesson.

Optional — designing a tool set, not just tools

As the tool count grows, treat the set as a system:

  • Minimize overlap. Two tools that do almost the same thing force a coin-flip. Merge them, or sharpen their descriptions so the boundary is obvious.
  • Name the unit of work. Prefer a few well-scoped tools (lookup_account, create_ticket) over one mega-tool with a mode argument — the model reasons about named capabilities better than about flags.
  • Make results self-describing. A result of { "found": false } is clearer to the model than an empty array. Tell it what happened in words it can act on.

A good tool set is one where, for any request, the right call is the obvious one. That's a property of your descriptions and boundaries — not of the model.

Build this in AI Fluens Studio

Reading is step one. Open Studio and build a working agent end-to-end — every concept in this course is something you ship and run for real.

Open AI Fluens Studio