Models can't do — only say
A language model can't reliably do arithmetic or look up a calendar date. It can only emit text. A tool changes that: it's a named local function the runtime exposes to the model. The model never runs the function itself — it just says which one it wants and with what arguments. Everything real happens on your side of the wire.
That split is the whole trick, and it has two halves:
- Decide — the model reads the prompt and emits a tool call: a name plus
arguments, like
{ tool: "calc", args: { a: 340, pct: 17 } }. This is the part the model is good at: picking the right tool for the job. - Dispatch — your runtime takes that decision, finds the matching local function, runs it on real inputs, and feeds the result back into the loop so the model can keep going.
Watch the agent do step 1. It reads "17% of 340" and "what day is 2026-12-25?", decides it needs a calculator and a date tool, and reaches for them — then folds the real results into its answer.
Notice what the model didn't do: it never computed 57.8 in its head. It named a tool and waited. The number came from a function call.
Now you build the dispatcher
The model already made its decisions — you get a fixed array of emitted tool
calls. The interesting work is step 2, the part the runtime owns: routing each
decision to the right function and running it. Below, the local calc and
weekday functions are written for you, and the print/loop scaffolding is wired —
but the tools registry is empty, so every call falls through to "unknown tool".
Register each tool name so tools[call.tool] resolves, and the loop will dispatch
calc(340,17%) => 57.8, weekday(2026-12-25) => Friday, and a final
handled 2 tool calls.
The model picks the tool; your runtime is the tool. A decision with no dispatch table is just a string — the loop only closes when a real function runs and hands the answer back.