The request side: schemas and tool choice
Lesson 2 of 5 in Function Calling Deep Dive: The Wire Under Tool Use.
The request side of the contract has exactly two knobs: what tools exist and how free the model is to pick one. Most teams tune the first for weeks and never touch the second, which is backwards — tool choice is often the cheapest fix for a whole class of agent misbehaviour.
Start with the schema. Your parameters block is a JSON Schema object, and it does two jobs at once: it teaches the model the shape of a valid call, and it gives your runtime something to validate against before your code sees the arguments. Every keyword you leave out is a guess the model has to make.
enum — the difference between a hint and a closed set
"status": {"type": "string"} invites the model to write "in transit", "OPEN", or "pending delivery" — all plausible, none of them values your database knows. An enum turns an open-ended string into a five-way choice the model can hardly get wrong, and gives your validator a trivially checkable rule.
Rule of thumb: if your code contains a switch statement or an IN (...) clause over the value, that value wants an enum.
pattern and format — showing the shape instead of describing it
"pattern": "^CUS-[0-9]{8}quot; does something a description alone cannot: it constrains generation and fails validation loudly when the model improvises. Without it, a model that has not yet called lookup_customer will happily invent "CUS-00000001" or pass the customer’s email address, and your query will return an empty list that the model then reports as “you have no orders”.
format (date, date-time, email, uri) is weaker — support for validating formats varies by tooling — so pair it with an explicit description of the expected shape (YYYY-MM-DD).
required — the field that decides who owns the ambiguity
Anything in required is a question the model must answer before it can call the tool at all; anything optional is a decision you are delegating to your own defaults. Make customer_id required and a model that lacks one has to go get it. Make it optional with a silent server-side default and you have invited a cross-customer data leak that looks like a successful call.
Required-vs-optional is therefore a safety design, not just ergonomics: put identity and scope parameters in required, put behaviour tweaks in optional-with-defaults.
additionalProperties: false — refusing arguments you never declared
Models sometimes add fields that seem helpful: "include_cancelled": true, "customer_email": "...". With additionalProperties: false your validator rejects the call instead of silently ignoring a parameter the model believed was doing something. Silent ignoring is worse than failing, because the model then reasons as if its filter was applied.
Note that some providers require a stricter schema subset (no free-form extras, all keys listed) when you ask for guaranteed schema conformance — another reason to check the current reference rather than assume full JSON Schema support.
Descriptions inside the schema — the most under-used field on the wire
Per-parameter descriptions are read by the model with the same attention as the tool description, and they are where the operational knowledge lives: “as returned by lookup_customer. Never ask the user for this.” That one sentence prevents an entire failure mode.
If you find yourself writing usage rules into the system prompt that only apply to one parameter, they belong in that parameter’s description instead — closer to the decision, and they survive prompt refactors.
Now the second knob. On every request you also get to say how much freedom the model has about calling tools at all — four modes that every major dialect offers under some name or other.
| Mode | What the model may do | Reach for it when | The trap |
|---|---|---|---|
Auto (the usual default) | Answer in prose, or call one or more tools — the model decides. | Normal agent operation. This is the mode that makes it an agent at all: the model chooses whether to act. | A model that should have looked something up sometimes answers from memory instead. That is a symptom to fix in descriptions or with a stricter mode — not evidence the mode is broken. |
Required / any — must call some tool | Must emit at least one tool call this turn; it may choose which. | The first turn of a research or triage step where answering without evidence is always wrong. | If none of the declared tools actually fit, you have forced a bad call. The model will pick the least-wrong tool and fabricate arguments to fit it. |
Forced named tool — call this one | Must call the single tool you named, with arguments it fills in. | Extraction and classification: you want the model to fill a form, not to decide anything. Also the standard trick for reliable structured output before dedicated schema modes existed. | You have taken the steering wheel — this is a workflow step, not agent behaviour. Fine, as long as you know that is what you built. |
None — no tools this turn | Prose only, even though the tools are still declared and visible. | The final “now summarise what you found” turn, or a hard stop when the budget for actions is spent. | The tools still cost input tokens while declared. And a model that wanted to call one now has to answer without it — which can read as confident invention. |
Key terms: JSON Schema, tool choice, tool contract, structured outputs, system prompt
Interactive checkpoint quiz (2 questions) — open this page in a browser to take it.