A tool call is a structured request the model emits instead of prose. The runtime executes it, feeds the result back, and the loop continues. Mechanically that is all there is to it - which is why the interesting variation is entirely in how the tools are defined.
- 01Model sees tool list
- 02Chooses one
- 03Emits arguments
- 04Runtime executes
- 05Result returned
- 06Model continues
What the model actually sees
Names, descriptions and a JSON schema per parameter. Nothing else. Not the implementation, not the documentation, not the tribal knowledge about which one is the "right" one. If a distinction is not in the description, the model cannot use it.
{
name: "find_slots",
description:
"Find bookable appointment slots for a service. Returns an empty " +
"list when nothing is available - that is a normal answer, not an " +
"error, and should not be retried. Times are in the business's " +
"local timezone. Do not use for rescheduling; use move_booking.",
parameters: {
service: { type: "string", enum: ["consult", "repair", "pickup"] },
earliest: { type: "string", format: "date-time" },
latest: { type: "string", format: "date-time" }
}
}Three things in that description are doing real work: the empty-result semantics, the timezone statement, and the pointer to the other tool. Each of them removes a specific failure we have watched happen.
The two failure modes
- Wrong tool. Almost always a description problem - two tools whose descriptions do not clearly separate their cases.
- Wrong arguments. Almost always a schema problem - a free-text field where an enum belonged, or an ambiguous unit.
Both are fixable without touching the model, which is fortunate, because changing the model tends to move these failures rather than remove them.