Skip to content

Tool use explained: how an LLM calls your code

By SunnyKumar Jonwal 9 min read

A language model can't call an API, query a database, or read a file. All it can do is produce text. Tool use (also called function calling) is the trick that turns that text into action: you describe your functions to the model, it replies with a structured request to call one, and your code does the real work.

Once that clicks, a lot of agent behavior makes sense. The model isn't reaching into your system. It's writing a note that says "please run this," and you decide whether to.

What "a tool" is

A tool is three things: a name, a description, and a schema for its inputs. That's all the model ever sees. It doesn't see your implementation, your database, or your business logic.

Here's a tool definition in the shape Anthropic's API uses:

{
  "name": "get_order_status",
  "description": "Look up the current status of a customer order by its order number. Returns status, carrier, and estimated delivery date. Use this when the customer asks where their order is.",
  "input_schema": {
    "type": "object",
    "properties": {
      "order_number": {
        "type": "string",
        "description": "The order number, e.g. 'A-10482'"
      }
    },
    "required": ["order_number"]
  }
}

The schema is standard JSON Schema. The description is the part people tend to rush. It's the only guidance the model has about when to use the tool, so it carries most of the weight.

What the model does with it

You send the tool definitions along with the conversation. When the model decides a tool would help, it doesn't produce prose. It produces a structured block:

{"type": "tool_use", "id": "toolu_9f2", "name": "get_order_status",
 "input": {"order_number": "A-10482"}}

Then generation stops and the response comes back to you with a stop reason saying a tool was requested. Now it's your turn.

Your code parses the block, checks it's acceptable, runs get_order_status("A-10482") against your real system, and sends the output back as a result. The model reads the result and either answers the user or asks for another tool. The full mechanics of that repeat cycle are in how the agent loop works.

Two facts are worth pausing on.

The model doesn't execute anything. This is a security feature. Every tool call passes through your code, which means you can validate, log, rate-limit, or refuse it.

The model chooses; it doesn't guarantee. It generates arguments the way it generates any text, by prediction. Usually those arguments are right. Sometimes the model invents an order number, picks the wrong tool, or leaves out a required field. Treat tool inputs as untrusted user input, because in a real sense they are.

How the model decides which tool to use

Selection is driven by the descriptions and the conversation. The model reads something like "the customer asks where their package is" and matches it against tool descriptions that say "use this when the customer asks where their order is." No routing table exists, only language.

That has practical consequences.

  1. Descriptions are prompts. Write them as instructions: what the tool does, when to use it, when not to, and what it returns.
  2. Similar tools cause confusion. If you have search_orders and find_orders, the model will guess. Merge them, or make the difference explicit.
  3. Names matter. get_order_status beats tool_3 or orderLookupV2. Clear verb-noun names double as documentation.
  4. More tools means more mistakes. Every extra tool adds a chance of choosing wrong. Ten well-scoped tools usually outperform forty overlapping ones.

You can also steer selection directly. Most APIs let you force a specific tool, allow any tool, or forbid tools for a turn. Forcing is handy for structured extraction, where you want the model to fill in a schema every time.

Writing schemas the model can follow

Schemas do more than validate. They teach. A few habits that improve reliability:

  • Describe every field, not just the tool. "date: string" invites guesses. "date: ISO 8601 date, e.g. 2026-03-14" doesn't.
  • Use enums for fixed choices, so the model can't invent "status": "kinda done".
  • Keep inputs flat. Deeply nested objects raise the odds of a malformed call.
  • Mark required fields honestly. If something is optional, say so and explain the default.
  • Prefer strings and numbers over clever types. Simpler inputs, fewer surprises.

A useful trick is to think about what a new teammate would need to use the function correctly with only the documentation in front of them. If they'd have questions, the model will too.

Returning results

What you send back matters as much as what you accepted. The result is just text (or structured content) that goes into the context.

Keep it small and relevant. Returning a 5,000-line log when the model needed one line wastes tokens and buries the answer. Filter on your side. Return the fields that matter.

Make it readable. Clean, labeled output beats a raw dump. "status: shipped, carrier: UPS, eta: 2026-03-14" is easier to use than a nested blob with forty keys.

Say what happened when nothing happened. An empty result should read "No orders found for A-10482," not an empty string. Silence makes models improvise.

Include actionable errors. If the tool failed, tell the model why and what would fix it. "Order numbers look like A-12345; you sent 12345" lets it correct itself on the next try.

Where tool use goes wrong

Most failures fall into a few buckets.

Wrong tool. The model picks a plausible but incorrect tool. Fix it with sharper descriptions, fewer overlapping tools, or an explicit note about when not to use each one.

Bad arguments. It fabricates an ID or passes a value in the wrong format. Validate on your side and return a helpful error instead of running the call. Better yet, give it a lookup tool so it doesn't have to guess IDs.

Missed tool. It answers from memory when it should have called a tool. Say in the system prompt when tools are required: "Never answer questions about order status without calling get_order_status."

Tool spam. It calls the same tool repeatedly, or calls three when one would do. Step limits and repeat detection help, and so does clearer guidance about what's already known.

Prompt injection through results. A tool result may contain text from the outside world (a web page, an email, a support ticket), and that text can contain instructions aimed at the model. This one deserves real attention, and prompt injection and the lethal trifecta explains why.

Tool use versus structured output

People often ask whether they need tools at all when they only want JSON back. The two ideas overlap, and it helps to separate them.

Tool use is for actions and lookups. The model requests something to be done, you do it, the result comes back, and the conversation continues.

Structured output is for shaping the final answer. You want the reply to match a schema: a list of extracted fields, a classification with a confidence, a plan in a fixed format. Nothing has to happen in the world.

A handy trick sits between them. You can define a "tool" whose only job is to receive the structured answer, and force the model to call it. The arguments you get back are validated against your schema, and you never execute anything. It's a common way to get dependable JSON from a model, and getting reliable JSON out of an LLM covers the options in detail.

Sequential, parallel, and dependent calls

Not every tool call is independent. Some depend on earlier results: you can't ask for the shipping status until you've found the order number. Others can run side by side.

The model works this out from context. When calls depend on each other, it will make them across separate turns. When they don't, it may batch them in one. You don't need to orchestrate this, but you do need to support both shapes in your loop, as the agent loop walkthrough shows.

Where you can, design tools so the dependent chain is short. A tool that takes a customer email and returns the latest order with its status saves two round trips over separate "find customer," "list orders," and "get status" tools. The catch is that bigger tools are less flexible, so keep the split at natural boundaries and aim for what the model will most often need in one go.

Testing a tool before a model touches it

A surprising amount of tool trouble is ordinary software trouble in disguise. Before you blame the model, test the function like any other:

  • Call it directly with typical inputs and confirm the output is clean and small.
  • Call it with bad inputs (missing field, wrong type, an ID that doesn't exist) and read the error text. Would a model know how to recover from it?
  • Check the worst case for output size. What does the largest realistic result look like in tokens?
  • Confirm it's safe to call twice. Models retry, and a tool that charges a card every time it runs needs an idempotency key.

Then test the tool with a model on a small set of realistic requests, and read the transcripts. You'll find things a schema check never would: a description that reads fine to you and confuses the model, or a field name that invites the wrong value. Ten realistic test requests will teach you more about a tool than an hour of staring at its definition.

A note on standards

Writing a bespoke tool definition for every app gets tedious, especially when the same GitHub or Slack tool is reinvented over and over. The Model Context Protocol tries to fix that by defining a common way for tool providers to describe their tools and for AI apps to discover and call them. If you've ever wished you could plug the same tool into several assistants without rewriting it, MCP explained is the place to go next.

A small checklist for every new tool

Before you ship a tool to a model, run through this.

  • Is the name a clear verb-noun?
  • Does the description say when to use it and when not to?
  • Is every input field described, with examples where formats matter?
  • Do you validate inputs before acting on them?
  • Is the output trimmed to what the model needs?
  • Do errors explain how to fix the problem?
  • Is the tool the least powerful version that does the job? A read-only lookup beats a general query runner.
  • Do you log every call with inputs and outputs?

That last point about power is where a lot of trouble starts. A tool that can run arbitrary SQL or shell commands hands the model enormous reach, and it hands the same reach to anyone who manages to steer the model. Start narrow, then widen only when you have a reason and a guard.

Putting it together

Tool use is a conversation protocol, not magic. The model reads descriptions, emits a structured request, and waits. Your code is the hands: it validates, executes, and reports back. Reliability comes from the boring parts, namely clear descriptions, tight schemas, useful results, and sensible limits.

When you're ready to see it working end to end, the next step is building a first agent with the Claude API, and for the design side, how to design tools an LLM can use goes deeper on the choices that separate a flaky tool from a dependable one.