← All lessons
Agents

Function calling: how a model uses tools

The model runs nothing: it describes what it wants to do, in a structured way. Understanding that step explains why agents work, and why they fail exactly where they do.

A language model generates text. Function calling is the mechanism that lets it generate, instead of text, a structured request: I would like to call this tool, with these parameters.

Without
  You: "What's the weather in Rome?"
  LLM: "I don't have access to real-time data, but usually..."

With
  You: "What's the weather in Rome?"
  LLM: { tool: "weather", params: { city: "Rome" } }
  System: [executes] → "sunny, 22 degrees"
  LLM: "It's sunny in Rome, 22 degrees."

The part almost everyone misses: the model executes nothing. It produces a request. Your code executes it and hands the result back. All the safety of an agent lives in that gap: between the request and the execution sits your program, and that is where the controls go.

The full loop

1. you declare the available tools, with their descriptions
2. the user's question arrives
3. the model chooses: answer, or ask for a tool?
      ├── no tool needed → direct answer
      └── tool needed → it produces the request
4. your code executes and gets a result
5. the result goes back to the model as a new message
6. the model answers — or asks for another tool, and round we go

Steps 3-6 are exactly the agent loop seen from the inside. An agent, technically, is this running in a while.

The descriptions are the real program

The model picks a tool by reading the description you gave it. There is no compiler, no type check: there is a sentence in plain language, and a choice made on it.

Bad    "description": "Weather function"
       → the model does not know when to use it

Good   "description": "Returns the current weather for a city.
        Use it when the user asks about weather, temperature or forecasts."
       → the model knows when to reach for it, and when not to

The same goes for parameters: city should be documented as “city name in English, no region” if that is what you expect. Half the failures that look like model stupidity are descriptions written by someone who already knew the answer.

One operational consequence: tool descriptions live in the context, always, on every request. Thirty well-described tools are thousands of tokens spent before the user has even spoken, and they occupy the window the actual work needed.

How many tools

Few. Five to ten well-defined ones beat thirty generic ones, and not for elegance: the more tools there are, the more chances of picking a similar but wrong one — and the wrong choice does not produce an error, it produces an action.

When tools grow in number, the answer is not to describe them better: it is to give the model only a subset, the one relevant to the task at hand.

Parallel calls, and why they matter

Recent models can request several tools at once:

"What's the weather in Rome and Milan?"
  → [weather(Rome), weather(Milan)]     one round, two requests

It is not only speed: if the two requests were sequential, the second would start after seeing the first result, and the model could be swayed by it. In parallel they stay independent, which is what you want when they genuinely are.

When a tool fails

This is the case people forget to handle, and the one that shows up most.

Bad    the tool errors → nobody tells the model
       → the model fills the gap: it invents a plausible result

Good   the tool errors → the error goes back to the model as the result
       → the model says so, or tries another route

The error message has to be returned as content, not swallowed as a silent exception: a model that reads “file does not exist” changes strategy, a model that receives nothing carries on as if everything went fine.

One thing worth knowing about retries: retrying the identical call rarely helps, because the model tends to regenerate the same request. The useful retry is the one where the error is descriptive: “unknown city: use the English name” produces a different second call, “error 500” does not.

Native or standardised

Function calling is the base mechanism, and every provider exposes it in its own format: the same ideas, written slightly differently. Defining tools in your own code gives full control and ties the application to that format.

Above this mechanism there is a standardisation layer, meant to make tools reusable across different applications instead of rewriting them each time: that is the subject of the MCP lesson.

What the model needs to do this well

Not every model uses tools equally well, and the difference shows up in form before quality: small models produce malformed requests — missing parameters, JSON that never closes, invented tool names. Below a certain size, locally, function calling stops being dependable with more than one or two simple tools.

It is a good criterion when picking a local model for an agent: not “how well it writes”, but how well it respects a format under pressure. The lesson on choosing a model goes into it.

In short

ConceptIn one line
Function callingThe model requests a tool, it does not run it
Who executesYour code: that is where confirmations and limits go
The loopDeclare, request, execute, return, repeat
The descriptionsThey are the program: the model chooses by reading them
How many toolsFive to ten relevant ones, not thirty generic
Hidden costDescriptions occupy context on every request
ErrorsReturn them to the model, descriptively, or it invents
Small modelsThey get the form wrong before the content

Related lessons

  • What agents are: from advice to action

    A chatbot tells you how, an agent does it. The observe-think-act loop, the four parts it is made of, and the limits to set before handing over the keys.

  • MCP: the universal socket for tools

    Every application used to rewrite its own integrations. MCP standardises how a model discovers and uses external tools: what actually changes, and what it brings along in terms of risk.

  • The system prompt

    The stage directions a model receives before stepping out. It is the difference between a generic assistant and a tool that does what you actually need.