Tool calling is an API contract the model can read
A tool definition tells the model what a function does, when to use it and what arguments it takes. Descriptions are read as instructions, so vague names and thin descriptions produce wrong calls. Narrow, well-named tools with explicit enums and clear failure messages get used correctly far more often.
A tool definition looks like configuration and behaves like a prompt. The model reads your names and descriptions and decides from them alone whether this is the right function for the situation. That makes writing them a communication problem more than a schema problem.
Write the description for a new colleague
Imagine a capable engineer who joined this morning, knows nothing about your system, and has only these definitions to go on. That is precisely the reader.
So search with the description "searches" is close to useless. search_customer_invoices with "Find invoices for one customer by email or customer ID. Returns at most 50, newest first. Use for billing questions; does not cover refunds β see search_refunds" is a tool that gets called correctly.
Say when not to use it
Boundaries are as informative as capabilities. A sentence naming the adjacent tool for the adjacent job prevents the most common class of wrong call, which is a reasonable tool applied to a slightly different question.
Narrow beats flexible
There is a strong temptation to build one tool with a mode parameter covering six operations. It is tidier code and worse for the model, which now has to get two decisions right instead of one.
Six small tools with distinct names and distinct descriptions are chosen more accurately. Share the implementation underneath if you like; keep the surface separate.
Make bad arguments unrepresentable
Every argument you leave open is one the model can fill imaginatively. Use enums for fixed sets, state formats explicitly, give defaults, and mark what is genuinely required.
A field described as "the status" invites "pending", "Pending", "in progress" and "awaiting". A field constrained to three values invites one of three values.
Failures should teach
The response to a bad call is another chance to communicate. "No customer found with that email β try search_customers by name" is a result the model uses productively on the very next step.
Treat your tool layer as a conversation with a competent stranger, and most of the mysterious behaviour disappears.
Frequently asked questions
How many tools can a model handle at once?
Accuracy starts degrading well before the technical limit, and overlapping tools are worse than numerous ones. If two tools could plausibly serve the same request, the model will sometimes pick wrong. Prefer a smaller set with sharp boundaries.
Should a tool return an error or throw?
Return it as a result, phrased so the model knows what to do differently. "Date must be YYYY-MM-DD, received 3rd May" gets corrected on the next step. A stack trace does not.