The model never calls anything

Here is the fact that reorganises everything else: when a model "calls a function," it does not run any code. It emits a piece of structured text — a JSON object that names one of the tools you offered it and fills in the arguments. That is the entire act. The model produces a request. Your code reads that request, decides whether to honour it, runs the real function itself, and hands the result back to the model on the next turn.

So "function calling" is a slight misnomer. The model proposes; your runtime disposes. Every consequential thing — whether the call runs at all, with what permissions, against which database, after which checks — happens in code you wrote, not inside the model. This is not a pedantic distinction. It is the reason the whole reliability and security story lives on your side of the boundary, and why "the AI deleted the record" is never quite true: the AI asked, and your code deleted it because you wired the request straight through.

The one-sentence model. The LLM returns a structured request naming a tool and its arguments; your code chooses whether to execute it. Nothing the model emits touches your systems until your code lets it.

The request–execute loop

A tool-use interaction is a short loop, and it helps to see the four moves in order. You send the model the user's message plus a list of available tools, each described by a name, a purpose, and a schema for its arguments. The model replies either with a normal answer or with a tool request. If it is a request, your code validates it, runs the actual function, and appends the result to the conversation. The model then continues — often calling another tool, sometimes several in sequence, until it has enough to answer in plain language.

Model proposes a call the boundary you control Your runtime validate · permit execute · log Your systems DB · ERP · APIs tool_call (JSON) result
The model only ever reaches your systems through code you wrote. That middle box — validate, permit, execute, log — is where correctness and safety live, not in the model.

Two things follow from this shape. First, the model can chain: a support request might trigger a lookup, then a second lookup based on the first result, then a draft reply — a small agent loop built entirely out of tool calls. Second, every tool result is just more text the model reads. That is why what you return from a tool matters as much as what you let it do — a point we come back to below.

A tool is a prompt, not just an API

The instinct is to treat a tool as a normal function you happen to expose to a model. That instinct is what produces tools an LLM cannot use well. The difference: your teammate can read the docs, ask you a question, and see the surrounding code. The model sees only the tool's name, its description, and its argument schema — and it decides which tool to reach for, and how to fill it, from those three things alone. The schema is not plumbing. It is instruction the model reads at the exact moment it decides what to do.

Name and describe for a reader who can't ask questions

A tool called get_data(type, filter) forces the model to guess what type accepts and what a valid filter looks like — and it will guess wrong under load. Split it into find_customer_by_email(email) and get_open_invoices(customer_id) and the choice becomes obvious from the names. Write the description for the caller, not the maintainer: say what the tool is for, when to use it versus a neighbour, and what it does not do. "Returns the customer's last 10 orders. Use only after you have a customer_id from find_customer. Does not include cancelled orders." is worth more than any amount of tuning in the system prompt.

Make a bad call impossible to express

The strongest lever is to shrink the space of calls the model can even form. A generic update_record(table, field, value) can set anything to anything — a prompt-injected instruction could drive it to write where it should not. Replace it with move_ticket_to_stage(ticket_id, stage) where stage is an enum of the four legal stages, and a whole class of wrong or malicious calls simply cannot be represented. Prefer enums to free strings, IDs to names, one narrow verb to one flexible setter. Every constraint you push into the schema is a mistake the model cannot make and a check you no longer have to write.

Write errors the model can recover from

When a tool fails, the error you return is the model's next prompt. Return Error 500 and the model has nothing to work with; it will retry blindly or give up. Return No customer found for ID 4711. IDs are six digits — if you only have a name, call find_customer_by_email first. and the model can correct itself on the next turn without a human. Good tool errors are steering, not just logging: they name what was wrong and point at the recoverable next step. This is one of the highest-leverage and most-skipped parts of tool design.

The boundary is where safety lives

Because the model's output is untrusted — and doubly so once any of its input comes from documents, emails, or web pages that an attacker can influence — a tool call is input from the internet, and you validate it like one. Three habits do most of the work.

Validate before you execute. Check the arguments against the schema, then against your own rules, before the function runs. Never pass a model's string straight into SQL, a shell, a file path, or an eval. The schema constrains shape; your code enforces meaning — that this customer_id belongs to a customer this user is allowed to see.

Permission the tool, not the prompt. "Only refund if the customer is eligible" written in the system prompt is a suggestion the model may ignore or be talked out of. The same rule enforced in the issue_refund tool — which checks eligibility itself and refuses otherwise — is a control. Anything you actually depend on belongs in code around the tool, where it holds regardless of what the model was persuaded to request.

Gate the irreversible. Reads are cheap to get wrong; writes, payments, and deletions are not. Split them: let the model freely call read-only tools, and route anything that changes the world or spends money through a human approval step or a hard confirmation. This is the same instinct behind securing LLM applications generally — narrow the blast radius of the one call that can hurt you.

A useful test before you expose a tool. Ask: "If a malicious instruction hidden in a document could choose this tool's arguments freely, what is the worst that happens?" If the answer is unacceptable, the fix is not a better prompt — it is a narrower tool, a permission check, or a human gate.

Why tool design beats prompt wording

Teams spend days polishing prompt phrasing and minutes on tool definitions, and it is the wrong ratio. Prompt wording nudges behaviour; tool design determines what is possible. A model with a clean, well-named set of narrow tools and honest error messages will behave well under a mediocre prompt. A model with one overloaded do_everything tool will misbehave under a beautifully written one, because you have handed it an interface where the wrong move is easy to make and hard to catch.

This is also why tool use is often the jump from a demo that talks to a system that works — and why the failures are quieter than people expect. When retrieval is the tool, the same design rules apply, which is why a RAG pipeline is really a search tool with a good schema. And when you evaluate the system, test the tool layer specifically: does the model pick the right tool, fill it correctly, and recover from a bad result? Those are exactly the cases a proper evaluation set should cover, because they fail in ways a casual demo never surfaces.

Where Tippel fits

Most of the engineering value in an LLM feature is in this layer: the set of tools, their schemas, the validation and permission checks around them, and the error messages that let the model self-correct. It is unglamorous, it rarely shows up in the demo, and it is where a system either holds up in production or embarrasses you. When we build agents and LLM systems, this is where a large share of the care goes — designing the boundary so the model can do useful work without being able to do damage.

If you are weighing an AI feature that needs to touch your real systems and want an honest read on how to draw that boundary, that is exactly what the AI Readiness Check is for — or you can just get in touch.