The phrase "the model called the function" is convenient and wrong, and the misunderstanding it creates is the reason so many tool-using systems have security holes in the same place. A language model cannot execute anything. It produces text. Everything that happens after that is your program's decision — which is inconvenient framing right up until you realise it is also where all your control lives.
What actually happens
You describe your tools to the model as name, description and parameter schema. The model, when it judges one relevant, emits a structured request naming the tool and its arguments.
Your code receives that request, decides whether to honour it, executes it if so, and sends the result back as another message. The model never touches your systems — it only ever asks.
The loop, step by step
Five stages, and the boundary between the model's world and yours sits between stages two and three.
Notice that the model's turn ends at step two. It has no way to wait for a result, no ability to retry, no view of what happened. It emitted a request and stopped generating. Everything at step three happens while the model is not running at all.
💡 The model is writing a request, not making one
A useful reframing: the model is filling in a form. It has read the form's labels — your tool descriptions — and produced values it believes fit. It has never seen the office the form goes to.
This is why tool descriptions matter so much and why the model's confidence tells you nothing about whether the call is appropriate. It is describing what it thinks should happen, from a menu you wrote.
What the model actually sees
Your entire tool surface, from the model's perspective, is this:
That is all. Not your implementation, not your endpoint, not your credentials. The description is the entire basis on which the model decides whether this tool is the right one — which makes it a prompt, not documentation.
The consequences are practical. A description saying "gets weather" produces a tool called for forecasts, historical data and climate questions it cannot answer. The version above rules those out explicitly, and that sentence does more for reliability than any amount of prompt engineering elsewhere. This is the substance of designing tool schemas an LLM can use.
Why the arguments are always well-formed
Tool calls do not arrive as prose the model hopes you can parse. The same machinery behind structured outputs applies: during generation, the sampler is restricted to tokens that keep the output valid against your schema.
So you will not get invalid JSON, and you will not get a field of the wrong type. This is worth knowing precisely because of what it does not guarantee.
🚨 Valid is not authorised
The schema promises shape. It says nothing about whether the values are ones this user may act on.
Authorisation, bounds and path checks belong in your handler, exactly as they would for a request from a browser. Treat every tool call as untrusted input from the internet — because if the conversation contains anything a third party wrote, that is what it is.
Sending results back
The result goes into the conversation as a message tied to the call's ID. Two things about it are worth deliberate thought.
Errors should be returned, not thrown. A model that receives an error message can adapt — correct the city name, narrow the range, try a different tool. A model that receives nothing because your code raised an exception cannot do anything at all.
Results consume context. Everything you return stays in the conversation for every subsequent turn. Returning a full API response because it was easier than filtering it will fill the context window with fields the model never uses, and on a multi-step task those accumulate quickly. Return what the task needs.
⚠️ Tool results are untrusted content
Whatever your tool returns enters the model's context with the same standing as your own instructions. If it came from a web page, a database record a user wrote, an email or a file, it can carry instructions aimed at the model.
This is prompt injection arriving through the tool channel, and it is the main reason a read tool plus an act tool is a more dangerous combination than either alone. The read tool supplies the payload; the act tool executes the consequence.
Several calls at once
Models can emit multiple tool calls in a single turn when the calls do not depend on one another. Running them concurrently is a straightforward latency win.
You cannot make the second case parallel by asking nicely — the dependency is real. What you can do is provide a tool that does both, if the pairing is common. Collapsing a frequent two-step sequence into one well-named tool removes a round trip and a chance for the model to go wrong in between.
The failure modes you will meet
| Symptom | Usual cause | Fix |
|---|---|---|
| Tool never gets called | Description too vague | State when to use it |
| Wrong tool chosen | Overlapping descriptions | Say what each is not for |
| Invented argument values | Missing from conversation | Mark required; validate |
| Calls the same tool repeatedly | Result not answering it | Improve the result content |
| Answers without calling | Model thinks it knows | Instruct it to always verify |
| Stops mid-task | No loop, or hit a cap | Check your loop logic |
Nearly every row resolves to the same underlying advice: the model's behaviour is determined by what you told it about the tools and what you sent back. Both are text you control. When a tool-using system behaves oddly, read your descriptions and your results before reaching for a bigger model.
One structural fix outweighs the rest — keep the tool count small. Every additional tool is another opportunity to select the wrong one, and the degradation is not linear. Twelve well-differentiated tools beat forty overlapping ones, and if you genuinely need forty, route to a subset first rather than presenting them all.
When not to use tool calling
If your code already knows which function to run, do not ask a model to choose. Tool calling buys you dynamic selection, and you pay for it in latency, tokens and unpredictability.
- Fixed sequence? Write the sequence. That is a workflow, not an agent.
- One tool, always called? Call it, then send the result as context.
- Extracting fields from text? That is structured output, not a tool call.
- Deterministic transformation? Ordinary code, no model involved.
Designing tool schemas?
Format and validate JSON schemas in your browser — nothing is uploaded to a server.
Open JSON Formatter →Summary
- The model never executes anything. It emits a request; your code decides.
- Its turn ends at the request. Everything after happens while it is not running.
- Descriptions are prompts, not docs — they are the entire basis for tool selection.
- Constrained decoding guarantees shape, not safety. Validate and authorise in your handler.
- Return errors as results so the model can adapt instead of stalling.
- Tool results are untrusted content and enter context with full standing.
- Independent calls can run in parallel; dependent ones genuinely cannot.
- Fewer, sharper tools beat a long menu.
Frequently Asked Questions
How does an LLM call a tool?
It does not, directly. The model emits structured output naming a tool and its arguments, your code parses that, decides whether to run it, executes it, and passes the result back into the conversation as a new message. The model only ever produces text and receives text — every actual execution happens in your program.
Does the model have access to my API or database?
No. The model sees only your tool definitions — names, descriptions and parameter schemas — and whatever results you choose to send back. It has no network access, no credentials and no idea what your function does internally. It is describing a request, not making one.
Why does the model sometimes invent tool arguments?
Because generating arguments is generation, not lookup. If a required parameter is not present in the conversation the model may produce a plausible-looking value rather than asking for it. Marking parameters required in the schema, describing them precisely, and validating in code before executing are the defences.
What is the difference between tool calling and structured outputs?
Mechanically they are close relatives — both constrain the model to emit JSON matching a schema. The difference is intent: structured outputs shape the final answer to your user, while tool calling produces an intermediate request that your code acts on, with the result fed back so the model can continue.
Should I validate tool arguments if the schema already constrains them?
Yes, always. Schema constraint guarantees the shape is valid, not that the values are safe or sensible. A well-formed request can still name another user's record, ask for a million rows, or specify a path outside the allowed directory. Type checking is not authorisation.