Most tool-calling failures get blamed on the model. Often the tool is the problem.
A vague name, an overloaded schema, or an unstructured error forces the model to guess. The demo survives because the prompt quietly compensates. Production traffic finds every ambiguity the prompt missed.
A good agent tool has three contracts:
- a selection contract the model can understand;
- an execution contract the runtime can validate;
- a failure contract the orchestrator can act on.
This is the implementation layer beneath the agent design patterns that work in production.
Design for one clear job
Compare these tools:
manage_customer(data)
customer_get_profile(customer_id)customer_update_shipping_address(customer_id, address, reason)
The first tool is compact and almost meaningless. The second set exposes intent, required identifiers, and the side effect.
There is a tradeoff. Hundreds of tiny tools make selection harder and consume context. One giant tool moves the ambiguity into an action parameter. Group operations when they share one resource, permission model, and error vocabulary. Split them when the consequences or approval requirements differ.
invoice_get and invoice_refund should not be two modes of an innocent-sounding invoice_manage tool. A read and a financial side effect deserve different controls.
Write descriptions like operational documentation
The description should answer four questions:
- What does the tool do?
- When should the agent use it?
- When should it not use it?
- What does it return or fail to return?
Weak:
Searches orders.
Useful:
Searches orders visible to the authenticated user. Use this when the user doesnot know an order ID and provides a date, status, or product clue. Do not use itto retrieve another customer's orders. Returns at most 20 lightweight ordersummaries; call order_get for line items and delivery events.
That extra detail reduces selection errors and prevents the model from assuming capabilities the tool does not have. Anthropic's tool guidance similarly emphasizes detailed descriptions, meaningful names, and high-signal responses.
Make the schema remove choices
JSON Schema should constrain ambiguity, not merely describe it.
{"type": "object","properties": {"order_id": {"type": "string","pattern": "^ord_[a-z0-9]{12}$"},"reason": {"type": "string","enum": ["duplicate", "damaged", "not_received", "other"]},"amount_cents": {"type": "integer","minimum": 1}},"required": ["order_id", "reason", "amount_cents"],"additionalProperties": false}
Prefer enums over free text when the backend has a closed set. Use explicit units in names: amount_cents, timeout_seconds, distance_km. Reject additional properties. Separate nullable from optional; they communicate different intent.
Strict structured-output modes can improve schema adherence, but they do not authorize the operation or prove the arguments are semantically correct. Runtime validation still owns those decisions.
Put identity and authorization outside the arguments
Never trust the model to supply the acting user's identity.
# Wrong: model controls the security principalrefund_order(user_id, order_id, amount_cents)# Better: runtime injects identity from authenticated contextrefund_order(ctx.authenticated_user, order_id, amount_cents)
The tool gateway should evaluate permission at execution time against the actual resource. Hiding a tool from the model is useful for reducing mistakes, but it is not access control.
For consequential tools, attach metadata the runtime—not the prompt—enforces:
{"side_effect": "financial","approval": "required_above_5000_cents","retry": "idempotent_with_key","audit": "full"}
Pair this with the retry rules in Idempotency and Retries for Reliable AI Agents.
Return small, typed results
Tool output becomes model input. A 200-field API response wastes context and makes the next decision harder.
Return the fields required for the next step:
{"status": "succeeded","refund_id": "ref_7g91k2","amount_cents": 4200,"customer_message_required": true}
Use stable identifiers, explicit states, and normalized timestamps. Avoid mixing human prose with machine state in one string.
Give failures a contract too
Something went wrong leaves the model with two bad options: repeat blindly or invent an explanation.
Return structured failures:
{"status": "rejected","code": "approval_required","retryable": false,"message": "Refunds above €50 require supervisor approval.","next_action": "request_approval"}
Useful categories include:
invalid_arguments: model may correct the call;not_authorized: do not retry;approval_required: pause and surface the decision;rate_limited: retry after a specific delay;dependency_unavailable: retry within policy;uncertain: reconcile before repeating the side effect;business_rule_failed: explain the rule to the user.
The orchestrator decides what happens next. The model can communicate the result, but it should not turn retryable: false into another call.
Evaluate tools as a catalog
Tool tests should include more than valid schema examples.
Create cases for:
- choosing the correct tool among close alternatives;
- correctly deciding that no tool is needed;
- supplying valid required arguments;
- asking the user for genuinely missing information;
- refusing tools outside the user's authority;
- responding correctly to every error code;
- avoiding redundant tool calls;
- completing the task with the smallest safe sequence.
Trace selection, arguments, validation, latency, result, and retries using the production observability schema. Repeated invalid arguments usually point to the description or schema before they point to model quality.
Tool review checklist
- The name communicates resource and action.
- The description says when to use and not use the tool.
- Parameters have explicit formats, units, bounds, and enums.
- Additional properties are rejected.
- Authenticated identity comes from runtime context.
- Authorization is checked against the resource at execution time.
- Side effects, approvals, retries, and audits are declared as metadata.
- Results contain only information needed for the next decision.
- Errors include a stable code, retryability, and next action.
- Evaluation covers selection, abstention, arguments, and trajectories.




