Tool Use & Function Calling

A user asks a chatbot, "What's the weather in Nairobi right now, and what's 847 times 293?" A language model trained purely on next-token prediction has no thermometer and no reliable arithmetic circuit. It can only guess, often confidently and wrong. Tool use closes this gap. It lets the model emit a structured request — a function name and arguments. A surrounding program executes this request. The real result then feeds back into the conversation. Only after that does the model produce its final answer. This capability matters because it turns a static text predictor into a component of a live software system. Search engines, calculators, databases, code interpreters, and internal company APIs all become reachable. This happens the moment a model can describe, in a machine-parseable format, which one it wants and with what arguments. Modern production assistants depend entirely on this mechanism. Examples include customer support bots that check order status, coding assistants that run tests, and research assistants that query databases. Without it, every LLM deployment is limited to whatever knowledge was frozen into its weights at training time. The big picture is a division of labor. The language model handles reasoning, language understanding, and decision-making about what needs to be done. Deterministic external code handles doing it — fetching live data, performing exact computation, mutating a database, or calling a paid service. The model's output is no longer just prose. Sometimes it is a JSON object conforming to a schema the model has never seen executed, only described. Getting this handoff reliable — syntactically valid, semantically grounded, safely sandboxed — is the entire engineering challenge this article addresses. This article explains precisely how function calling works end-to-end. It covers how tools are described to a model, and how the model is trained or prompted to emit structured calls. It also explains how a runtime loop parses and executes those calls, and how results are re-injected. It covers the complexity and failure modes of this loop. It walks through a full worked trace, and surveys the common pitfalls that break production systems. It closes by connecting tool use to the broader idea of LLM Agents. There, this single-call mechanism is chained into multi-step autonomous behavior.

Key takeaways
  • Tool use lets an LLM emit structured calls (function name + arguments) that external code executes on its behalf.
  • Tools are described to the model as a schema (name, description, parameter types) inserted into the prompt or a dedicated API field.
  • The model is trained via Instruction Tuning and supervised examples to emit calls in a fixed, parseable format instead of free text.
  • A runtime loop parses the call, validates arguments against the schema, executes the function, and re-inserts the result as context.
  • Correctness depends on schema-constrained decoding or robust parsing; a malformed or hallucinated call is the dominant failure mode.
  • This mechanism is the foundational building block for LLM Agents, which chain many tool calls together to complete multi-step tasks.
The core insight

Function calling works by teaching a model to output a structured, machine-parseable request instead of prose, so a deterministic program — not the model — performs the real-world action and returns a fact the model can condition on.

The idea

Tool use (also called function calling) is the mechanism by which a language model, instead of answering a query directly from its parameters, produces a structured description of an external operation it wants performed. That description — conventionally a function name and a set of arguments, usually serialized as JSON — is intercepted by a runtime, executed against real code (an API, a database query, a calculator, a search index, a Python interpreter), and the result is inserted back into the model's context so it can compose a final, grounded answer. The core object here is the tool schema: a specification of a callable's name, a natural-language description of what it does and when to use it, and a typed parameter list (names, types, and whether each is required). This is typically expressed in JSON Schema. The model is given the set of available schemas — either concatenated into the system prompt or passed through a dedicated API field — and is trained or instructed to choose zero or one (or, in parallel-call variants, several) tools per turn, filling in arguments consistent with the schema and the user's request. Two properties distinguish tool use from ordinary generation. First, its output must be machine-parseable: a human reading prose can tolerate ambiguity, but a JSON parser cannot. Second, its correctness is externally checkable: unlike a free-text answer, an arguments object can be validated against a schema (right types, required fields present) before execution even begins, giving an early, cheap error-detection point absent from ordinary text generation.

How it works

The mechanism has four moving parts: tool description, structured decoding, execution, and result grounding, orchestrated in a loop.

1. Tool description. Every available function is described with a name, a description string, and a parameter schema. This description is injected into the model's input, either as a special "tools" field (in APIs that support it natively) or serialized into the prompt text. The model's job during training or fine-tuning is to learn the mapping from "user intent that matches this description" to "emit a call to this name with these arguments."

2. Decision + generation. On each turn the model must decide: does answering this require a tool at all, and if so, which one, with what arguments? This is a joint decision made through ordinary autoregressive decoding, but the target format is constrained — typically a special token or tag (e.g., a `` marker, or a `finish_reason: "tool_calls"` API signal) followed by a JSON object.

3. Parsing and validation. The runtime extracts the JSON, validates it against the declared schema (required keys present, types match, enum values legal). If validation fails, the runtime can either reject and re-prompt the model with the error, or attempt a repair (re-ask with the parse error appended as context).

4. Execution and re-injection. The runtime calls the actual function with the validated arguments, captures its return value (or exception), and appends it to the conversation as a new message — usually with a special role like `tool` — so the model's next generation step is conditioned on the real result rather than a hallucinated guess.

5. Loop or terminate. If the model's task requires further tool calls (chained retrieval, multi-step computation), the loop repeats; otherwise the model emits a final natural-language answer, marked with a normal `assistant`/stop signal instead of a tool-call signal. ``` function TOOL_AUGMENTED_GENERATE(user_query, tool_registry, max_rounds):

# tool_registry: map from tool_name -> {description, json_schema, python_callable} messages = [ {role: "system", content: SERIALIZE_TOOLS(tool_registry)}, {role: "user", content: user_query} ] for round in 1 .. max_rounds: response = MODEL_DECODE(messages) # may emit prose OR a structured tool call if response.type == "final_answer": return response.text # base case: model is done, no tool needed if response.type == "tool_call": call = PARSE_JSON(response.raw_text) # may raise ParseError if call is ParseError: messages.append({role: "tool", content: "ERROR: malformed JSON: " + call.message}) continue # give model a chance to self-correct schema = tool_registry[call.name].json_schema valid, err = VALIDATE_AGAINST_SCHEMA(call.arguments, schema) if not valid: messages.append({role: "tool", content: "ERROR: " + err}) continue # re-prompt with the validation error try: result = tool_registry[call.name].python_callable(call.arguments) except Exception as e: result = "ERROR: execution failed: " + str(e) messages.append({role: "assistant", content: response.raw_text}) # the call itself messages.append({role: "tool", content: SERIALIZE(result)}) # the grounding fact return "ERROR: exceeded max_rounds without final answer" # edge case: runaway loop ``` Two engineering choices sharpen reliability. Constrained decoding restricts the token sampler at each step to only tokens that keep the output a valid instance of the JSON Schema (via a grammar or finite automaton over the schema), making malformed JSON structurally impossible rather than merely unlikely. Two-stage validation (schema check before execution, exception handling during execution) means failures are caught at the cheapest possible point instead of silently propagating a wrong answer.

Figure
The tool-calling loop: model, parser, and executor exchange structured messages
Language Model context + tool schema → emits call or answer Parser / Validator JSON parse schema check Tool / API weather(), calc(), db.query(), ... raw tool_call text validated args return value grounded result / error Conversation state (messages array), growing left → right over rounds system: tool schemas user: query assistant: tool_call(...) tool: result payload assistant: final answer Each round appends two messages (call, result) until the model emits a final answer instead of a call.

Complexity & correctness

Per-round cost. One round of the loop costs one model forward pass over the accumulated context of length LL, which under a standard transformer is O(L2⋅d)O(L^2 \cdot d) time with self-attention (or O(L⋅d)O(L \cdot d) amortized per generated token with KV-caching across O(L)O(L) generated tokens, so O(L2d)O(L^2 d) total for the round dominated by attention over growing context), plus O(1)O(1) amortized cost for parsing and schema validation (JSON parsing is linear in the size of the call, which is small and bounded relative to LL), plus the cost of the external tool call itself, which is outside the model's control and can range from microseconds (a calculator) to seconds (a network API).

Loop cost. If a task requires kk sequential tool calls (a chain — e.g., look up a city, then look up its weather), total cost is ∑i=1kO(Li2d)\sum_{i=1}^{k} O(L_i^2 d) where LiL_i grows by roughly the size of each call-and-result pair, since every prior round's messages remain in context. In the worst case, with no context truncation, this is O(k⋅Lmax⁡2⋅d)O(k \cdot L_{\max}^2 \cdot d) where Lmax⁡L_{\max} is the final context length — quadratic accumulation is the practical reason production agents cap `max_rounds` and periodically summarize or truncate old tool outputs.

Space. Context memory is O(L)O(L) tokens (dominated by the growing message list plus the KV-cache, which is O(L⋅d⋅layers)O(L \cdot d \cdot \text{layers}) for a standard transformer), which is the binding constraint on how many rounds a single conversation can sustain before context-window limits force truncation or summarization.

Correctness argument. The mechanism's correctness rests on a simple invariant maintained across the loop: > Invariant: at the start of every round, every fact currently in the model's context that resulted from a tool call is one the runtime actually executed and verified — never a fabricated or unexecuted call. This invariant is established by construction: the runtime only appends a `tool` message after it has (a) successfully parsed the call, (b) validated arguments against the schema, and (c) executed the callable and captured a real return value or a real exception. It is preserved across rounds because every subsequent round's context is built strictly by appending to a history that already satisfies the invariant — nothing is retroactively altered. The invariant does not guarantee the model's final answer is correct — that depends on the model correctly reasoning over true facts, which is a separate (harder, unverifiable) property of the generation policy, not of the tool-calling protocol itself. What the protocol does guarantee is that any factual claim attributable to a tool call is grounded in an actual execution, which eliminates one entire class of hallucination (fabricated tool outputs) while leaving another class open (misinterpreting correct tool outputs, or hallucinating a plausible-looking call the model was never actually able to execute correctly — e.g., calling `get_weather(city="Nairobi")` when the user meant a different Nairobi).

Honesty about tradeoffs. Constrained decoding guarantees syntactic validity (the invariant's precondition (b) is always satisfiable) but does not guarantee semantic validity — a syntactically perfect call can still have the wrong city, wrong units, or wrong function chosen entirely. Schema validation catches type errors cheaply but cannot catch a subtly wrong argument value that happens to have the right type (e.g., `"NY"` vs `"New York, NY"`). And the loop's termination is not automatically guaranteed — an unbounded model could call tools forever chasing a task it cannot complete, which is why `max_rounds` is a hard architectural safeguard, not merely a tuning parameter.

Worked example

Consider the query: "Convert 100 USD to EUR and tell me if it's enough for a €85 hotel room." The system has two tools registered: `get_exchange_rate(base: string, target: string) -> float` and `calculator(expression: string) -> float`.

RoundModel outputParser actionTool executedResult injected
1`tool_call: get_exchange_rate(base="USD", target="EUR")`JSON valid; schema check passes (both required strings present)`get_exchange_rate("USD","EUR")` → real API call`{"rate": 0.92}`
2`tool_call: calculator(expression="100 * 0.92")`JSON valid; single required string present`calculator("100 * 0.92")` → `92.0``{"result": 92.0}`
3(model attempts) `tool_call: calculator(expresion="92 - 85")`Parse succeeds but schema validation fails: key `expresion` (typo) does not match required key `expression`not executed`ERROR: missing required field "expression"`
4`tool_call: calculator(expression="92 - 85")`Schema valid`calculator("92 - 85")` → `7.0``{"result": 7.0}`
5`final_answer: "100 USD converts to €92, which is €7 more than the €85 room — yes, it's enough."`No tool call; loop terminates——

This trace is deliberately non-trivial: round 3 shows the validator catching a realistic model error (a typoed argument key) before any execution occurs, and the runtime feeding the precise error back rather than either crashing or silently guessing. Round 4 shows self-correction succeeding on the very next attempt because the error message pinpointed the exact schema violation. The final answer is grounded in two real numeric facts (`0.92`, `7.0`) that the model never computed itself — it composed language around numbers it did not generate, which is exactly the intended division of labor. Notice also what would happen without tool use: a model asked to do "100 * 0.92" and "92 - 85" purely by autoregressive arithmetic is prone to silent errors on multi-digit multiplication, especially with more decimal places or larger numbers — a well-documented weakness distinct from, but related to, why models struggle with precise multi-step symbolic tasks. The tool-calling protocol sidesteps this entirely by delegating the arithmetic to code, which is not merely "more reliable" but categorically exact.

Common pitfalls & traps

PitfallWhy it happensConsequenceFix
Malformed JSON in the callFree-text decoding lets the model drift off-schema, especially with long argument valuesParser crash or silent misparseConstrained/grammar-based decoding; strict re-prompt-on-error loop
Hallucinated tool nameModel invents a plausible function not in the registryRuntime lookup fails, or worse, silently no-opsWhitelist-only dispatch; explicit "unknown tool" error fed back
Argument type mismatch (e.g., string where int expected)Model treats schema as a loose suggestion rather than a strict contractDownstream function throws, or coerces incorrectlySchema validation before execution, not just try/except around it
Prompt injection via tool outputA retrieved webpage or document contains text instructing the model to take a new, unauthorized actionModel treats untrusted tool output as a new instructionClearly delimit tool outputs as data, not instructions; sanitize/strip imperative text; least-privilege tool permissions
Infinite or excessive tool-call loopsModel doesn't recognize task completion, or oscillates between two callsRunaway cost, latency, possible rate-limit bansHard `max_rounds` cap; loop/cycle detection on repeated identical calls
Silent unit/format mismatchArgument value is syntactically valid but semantically wrong (Celsius vs Fahrenheit, wrong date format)Correct-looking but factually wrong final answerExplicit units/format in schema description; unit tests on tool wrappers
Overloading one tool call with too many argumentsModel tries to do multi-step work in a single malformed call to reduce round countPartial or garbled executionDecompose into single-responsibility tools; encourage sequential calls

Pioneers & history

Structured tool use in language models grew out of two converging threads: decades of research on semantic parsing and API grounding in classical NLP, and the sudden capability jump of large instruction-tuned transformers that could follow complex formatting instructions reliably enough for structured output to become practical at scale.

Origins · 1 minPercy Liang and how this was discovered
PL
Percy Liang
1981–present · Stanford computer scientist whose work on semantic parsing and grounded language understanding laid groundwork for mapping natural language to executable structured queries

Liang's research on semantic parsing — translating natural-language questions into executable logical forms or database queries — established the core idea, decades before modern LLMs, that language could be systematically converted into structured, executable representations rather than free text.

History
1996
— Early semantic parsing systems learn to map natural-language questions directly into SQL and logical-form queries.
2019
— Instruction-tuned and few-shot large language models demonstrate reliable structured output (JSON, code) purely from prompting.
2022
— Retrieval-augmented and tool-augmented LLM research (e.g., systems combining reasoning traces with external tool calls) shows large accuracy gains on knowledge- and computation-heavy tasks.
2023
— Major LLM API providers ship native "function calling" / "tools" fields, formalizing schema-constrained structured output as a first-class model capability.
2024
— Grammar-constrained and JSON-schema-constrained decoding become standard infrastructure, making malformed tool calls a solvable engineering problem rather than a persistent reliability risk.

Where this matters

Production customer-support assistants use function calling to check order status, process refunds, and update account details by invoking internal company APIs rather than merely describing what a human agent would need to do. Coding assistants use it to run test suites, execute shell commands, and query a codebase's symbol index, turning a text model into something that can verify its own suggestions rather than merely emit plausible-looking code. Enterprise data assistants use it to translate natural-language business questions into database queries, letting non-technical users interact with structured data through conversation. Consumer assistants use it for real-time information — weather, stock prices, restaurant reservations, flight status — none of which can live inside frozen model weights. Search-augmented chat products use a tool call to a search index as their primary grounding mechanism against hallucination, feeding retrieved documents back as tool results before composing an answer, which connects directly to Prompting & Prompt Engineering techniques for controlling how retrieved context is incorporated. Every major LLM API provider now exposes a dedicated tools/functions field precisely because this pattern is so load-bearing across products; it is the mechanism through which Instruction Tuning translates into deployable, action-taking systems, and the single-call loop described here is the atomic unit that, chained and given persistent memory and planning, becomes the subject of LLM Agents.

Ask the tutor: How does schema-constrained (grammar-based) decoding actually get implemented at the token level, and what does it cost in latency?

Is function calling the same thing as an LLM agent?

No. Function calling is the single-turn mechanism — describe a tool, emit a structured call, execute it, inject the result. An LLM agent is a larger system built on top of this mechanism: it chains many tool calls together, maintains longer-term state or memory, plans multi-step strategies, and often decides autonomously when to stop. Function calling is the atomic action-taking primitive; agency is the orchestration layer that decides which actions to take, in what order, and when to revise a plan based on tool results. Every agent relies on function calling, but not every use of function calling constitutes an agent — a single weather lookup in a chatbot is tool use without any agentic planning involved.

Why can't the model just be trained to know all the facts instead of calling tools?

Because a model's parameters are frozen at training time, while facts like weather, stock prices, and account balances change continuously and cannot be memorized in advance. Even for static facts, model parameters are a lossy, imprecise store — arithmetic and exact lookups are notoriously unreliable from pure next-token prediction, especially as numbers grow larger or precision requirements increase. Tool use also enables actions, not just facts: sending an email, updating a database, or executing code are operations no amount of training can bake into static weights, since they require touching live external state at request time rather than recalling training-time information.

What happens if the model calls a tool with the wrong arguments but the arguments are still schema-valid?

This is the central limitation of the protocol: schema validation only checks syntactic and type correctness, not semantic correctness. A call like `get_weather(city="Springfield")` is perfectly valid JSON with a valid string argument, yet ambiguous, since dozens of cities share that name. The runtime cannot catch this class of error automatically; mitigations include richer schemas (structured location objects with state/country fields), tool implementations that return disambiguation errors when a lookup is ambiguous, and training the model on examples that demonstrate asking clarifying questions rather than guessing when arguments are underspecified.

How does constrained decoding actually force valid JSON output?

Constrained decoding compiles the target JSON Schema into a finite-state or pushdown automaton over tokens, then masks the model's output distribution at each decoding step to only the tokens that keep the generated sequence a valid prefix of some schema-conforming string. Concretely, after the model emits `{"city": `, the automaton knows the next token must begin a valid string value (given the schema says `city` is type `string`), so tokens like `42` or an unescaped quote are assigned zero probability before sampling. This guarantees syntactic validity by construction rather than by hoping the model's training generalizes perfectly, though it adds a modest per-token compute overhead for grammar-state tracking.

Can a model call multiple tools in a single round?

Yes — many modern APIs support "parallel tool calls," where the model emits a list of independent tool-call objects in one generation instead of one, and the runtime executes them concurrently (or sequentially) before injecting all results in the next round. This is a valid optimization when calls are independent (e.g., fetching weather for three different cities the user asked about) since it collapses what would be three sequential round-trips into one, cutting both latency and the quadratic context-growth cost described in the complexity analysis. It does not apply when a later call depends on an earlier call's result — that case still requires sequential rounds.

Why do production systems cap the number of tool-calling rounds?

Because nothing in the base mechanism guarantees termination: a model can, in principle, keep calling tools indefinitely if it fails to recognize task completion, gets stuck oscillating between two calls, or misinterprets an ambiguous result as requiring further lookup. Left unbounded, this risks runaway API costs, hitting external rate limits, unbounded latency for the user, and context windows filling up with redundant tool-call history. A `max_rounds` cap is a simple, deterministic circuit breaker; more sophisticated systems add repeated-call detection (if the same call with the same arguments appears twice, force a different behavior) and cost budgets per conversation.

Does tool use eliminate hallucination entirely?

No — it eliminates one specific class of hallucination (fabricated tool results that were never actually executed), because the grounding invariant guarantees every `tool` message in context reflects a real execution. It does not prevent the model from misinterpreting a correct tool result, choosing the wrong tool for the task, passing subtly wrong arguments that happen to validate against the schema, or simply reasoning incorrectly over otherwise-true facts when composing its final answer. Tool use is a grounding mechanism for facts and computations that live outside the model, not a general cure for reasoning errors.

Before this

Where this leads