A tool definition is an unusual artifact. It is read by two completely different consumers — a runtime that validates it as a schema, and a language model that reads it as prose and decides whether to use it. Most tool design problems come from writing for only one of those readers.
First, a Clarification
Agents do not build tools. A developer defines tools; the agent exposes them to the model and executes them when requested.
This follows from the split covered in who decides tool calling — the model selects, the agent executes, and the developer determines what exists at all. Tool design is therefore a design-time activity, and it constrains everything the system can ever do.
Anatomy of a Tool Definition
| Element | Read by | Purpose |
|---|---|---|
| Name | Both | Identifier, and a strong signal of what it does |
| Description | The model | When to use it, when not to |
| Parameter schema | Both | Structure the model must produce; validation for the runtime |
| Parameter descriptions | The model | How to fill each field correctly |
| Return value | The model | Re-enters context as text |
Parameters are normally expressed as JSON Schema. The runtime uses it to reject malformed calls; the model uses it to construct well-formed ones. Some providers constrain decoding against the schema, which guarantees structural validity but not sensible argument values.
Descriptions Are Prompts
This is the highest-leverage point in tool design and the most commonly neglected.
The description is not documentation for a human colleague. It is text the model reads at selection time, and it is the primary determinant of whether the right tool gets picked. Treat it as prompt engineering.
| Weak | Strong |
|---|---|
| “Searches the database.” | “Search indexed documents by keyword or phrase. Returns up to 10 matching excerpts with their source IDs. Use for finding content when you do not already know the document ID. Do not use to fetch a document you already have an ID for — use fetch_document instead.” |
The strong version does four things the weak version does not: states what comes back, states when to use it, states when not to, and names the alternative. That last element resolves most tool-confusion problems, because selection errors are usually confusion between two similar tools rather than misunderstanding one.
Negative guidance is underused. Telling the model what a tool is not for is often more effective than elaborating what it is for.
Granularity
The central design question: many narrow tools or few broad ones?
| Many narrow tools | Few broad tools | |
|---|---|---|
| Selection accuracy | Degrades past a certain count | Better, if boundaries are clear |
| Argument construction | Simpler per call | More complex, more failure modes |
| Context cost | High — every definition is tokens on every request | Lower |
| Permission scoping | Precise | Coarse |
| Calls needed per task | More | Fewer |
Two practical heuristics:
Split when the verb changes. Reading and deleting are different operations with different risk profiles. They should not share a tool with a mode parameter — that makes a destructive action reachable through an argument value rather than through an explicit tool choice, which is bad for both selection and permission scoping.
Merge when the model cannot tell them apart. If choosing between two tools requires knowing internal implementation details the model has no visibility into, they should be one tool with the routing handled internally.
Every tool definition costs tokens on every request, since definitions are part of the assembled prompt. Large tool sets consume context before any work begins, and selection accuracy tends to fall as the set grows. Where a system genuinely needs many tools, filtering to a relevant subset per request usually works better than exposing all of them.
Parameter Design
- Use enums wherever values are constrained. An enum removes an entire class of invalid arguments and tells the model exactly what is permitted.
- Minimise required parameters. Each one is something the model can get wrong or omit. Sensible defaults reduce failure.
- Do not require values the model cannot know. Internal IDs, session tokens and account identifiers should be injected by the agent, never requested from the model — it will invent them.
- Prefer flat structures. Deeply nested objects produce more malformed calls than flat parameter lists.
- Describe every parameter, including format expectations. A date field should say which format.
- Avoid free-form strings where structure exists. A filter expressed as a query language the model must compose is far more error-prone than explicit fields.
The third point is worth emphasising. A tool requiring user_id invites the model to fabricate one that looks plausible. The agent knows the real user ID — it should supply it at execution time rather than exposing it as a parameter.
Return Value Design
Whatever a tool returns becomes text in the model’s context. This makes return shape a context-budget decision, not just an API decision.
| Pattern | Why |
|---|---|
| Return the minimum useful | Full API responses waste context on fields the model will not use |
| Paginate large results | Return a page plus a continuation handle rather than everything |
| Include identifiers | The model needs them to make follow-up calls |
| Use stable, readable keys | The model reads these as text; cryptic field names hurt |
| Summarise verbose payloads | Trim before the result re-enters context |
| Signal emptiness explicitly | “No matching records found” beats an empty array |
The last row matters more than it looks. An empty result returned as [] often produces a hallucinated answer, because the model treats it as uninformative rather than as a definite negative. An explicit message stating nothing was found is much more likely to produce an honest “I could not find that”.
Error Handling
The governing principle: return errors to the model as data, do not raise them as exceptions that break the loop.
A model that receives “Error: start_date must be in YYYY-MM-DD format, received ‘last Tuesday'” can correct itself and retry. An exception that terminates the agent loop gives the user a failure for a recoverable mistake.
| Error type | Handling |
|---|---|
| Invalid arguments | Return a message naming the field and the expected format |
| Not found | Return explicitly; the model may try different parameters |
| Permission denied | Return without leaking why or what exists |
| Transient failure | Retry in the agent; surface only after retries fail |
| Rate limited | Handle in the agent — the model cannot wait |
Two boundaries. Retry logic belongs in the agent, not the model — a model told to wait will simply call again immediately. And error messages must not leak information the caller is not entitled to, since anything returned enters context and may reach the user.
Common Tool Patterns
| Pattern | Purpose |
|---|---|
| Search then fetch | One tool finds candidates and returns IDs; another retrieves full content. Avoids loading everything into context |
| Read-only mirror | Separate read and write tools even over the same resource, so permissions can differ |
| Dry run | A preview tool returning what would happen, paired with a commit tool |
| Approval gate | Destructive actions return a confirmation request the agent routes to a human |
| Explicit finish | A terminal tool the model calls when done, giving the agent a clean loop exit |
| Delegation | A tool invoking a sub-agent with its own tools, keeping the parent’s set small |
The dry-run pattern is particularly effective for irreversible operations. The model proposes, the preview shows the effect, a human or a rule approves, and only then does the commit tool run.
Anti-Patterns
- The god tool. One tool with an
actionparameter switching between unrelated operations. Wrecks selection, permissions and validation simultaneously. - Overlapping tools. Two tools that could both plausibly serve a request, with nothing in their descriptions distinguishing them.
- Implementation-named tools. Names reflecting internal services rather than user-facing capability mean nothing to the model.
- Stateful sequences. Tools requiring a specific call order with hidden state between them. The model will get the order wrong.
- Unbounded returns. A tool that can return an entire dataset will eventually be called in a way that does.
- Silent failure. Returning an empty or default result on error, so the model proceeds confidently on nothing.
- Exposing internal identifiers as parameters. The model will fabricate them.
Testing Tools
Tool quality is testable in two separable layers, and separating them is what makes debugging tractable.
- Execution. Ordinary software testing — does the tool do the right thing given valid arguments, and fail gracefully given invalid ones?
- Selection. Given a realistic request and the full tool set, does the model choose the right tool with sensible arguments? This is evaluated by running representative prompts and checking what was requested.
Selection failures are fixed by editing descriptions, adjusting granularity or trimming the tool set — not by changing the model. When an agent picks wrong tools, the description is almost always the cheaper fix.
Key Takeaways
- Developers define tools; the agent exposes and executes them
- A tool definition is an API contract and a prompt simultaneously
- Descriptions are prompt engineering — state when to use, when not to, and the alternative
- Split when the verb changes; merge when the model cannot distinguish them
- Every definition costs tokens on every request — large tool sets degrade selection
- Never expose identifiers the model cannot know — it will fabricate them
- Return errors as data so the model can recover; keep retries in the agent
- Signal empty results explicitly — an empty array invites hallucination
Frequently Asked Questions (FAQ)
Q: Does the agent build its own tools?
No. A developer defines tools at design time. The agent includes those definitions in each request and executes the ones the model asks for. The model can only request capabilities that were defined in advance.
Q: How important is the tool description?
It is the single highest-leverage element. The description is what the model reads when deciding which tool to use, so it functions as prompt engineering rather than documentation. Most selection errors are fixed by sharpening descriptions rather than by changing models.
Q: How many tools is too many?
There is no fixed number, but selection accuracy degrades as the set grows and every definition consumes context on every request. Where many tools are genuinely needed, filtering to a relevant subset per request generally works better than exposing all of them at once.
Q: Should I build one flexible tool or several specific ones?
Split when the underlying verb changes — reading and deleting should never share a tool with a mode parameter, since that makes a destructive action reachable through an argument value. Merge when distinguishing two tools would require implementation knowledge the model does not have.
Q: What should a tool return?
The minimum useful information, including any identifiers needed for follow-up calls. Return values become text in the model’s context, so full API responses waste budget. Paginate large results and state explicitly when nothing was found.
Q: How should tool errors be handled?
Return them to the model as readable data rather than raising exceptions that break the agent loop. A message naming the invalid field and expected format lets the model correct itself. Retries and rate limiting belong in the agent, since the model cannot wait.
Q: Why does my agent keep choosing the wrong tool?
Usually because two tool descriptions do not clearly distinguish the tools, or because a description omits when not to use it. Adding negative guidance and naming the alternative tool resolves most cases.
Q: Should tools accept user IDs or session tokens as parameters?
No. Values the model cannot legitimately know should be injected by the agent at execution time. Exposing them as parameters invites the model to fabricate plausible-looking values, and it moves authorisation into the prompt where it does not belong.
Related Reading: