Designing good tools
The model can only use your tools as well as you describe them. Learn to name, describe and shape tools so the model picks the right one and recovers from mistakes.
In 60 seconds
Designing good tools
The model can only use your tools as well as you describe them. Learn to name, describe and shape tools so the model picks the right one and recovers from mistakes.
Five rules for good tools
| Rule | Weak | Better |
|---|---|---|
| Clear name, verb first | data, helper2 | search_orders, get_order_status |
| Description says when to use it, and when not | "Gets orders." | "Look up one order by its id. Use when the user gives an order id. For searching by name or date use search_orders." |
| Narrow, one job per tool | run_sql(query) | get_order_status(order_id) |
| Simple, typed parameters | One free-text input string | order_id (string, pattern like ORD-123456), limit (integer, 1 to 20) |
| Results that are short and useful | The full 300-field database row | Only the fields the task needs, in a small JSON object |
Narrow tools are safer and easier
run_sql or run_shell can do anything, so the model must write it perfectly, and an attacker who controls the model's input can make it do anything too. A narrow tool like get_order_status(order_id) can only do one thing. It is easier for the model to use and much easier for you to protect. This is least privilege applied to tools.Errors the model can act on
Error 500KeyError: 'id'Invalid input- A 200-line stack trace
No order ORD-12345. Order ids have 6 digits, like ORD-123456.Missing order_id. Ask the user for it.limit must be 1 to 20; you sent 500.Service busy. Try again once; if it fails, tell the user.
Read tools and write tools
- Read tools can usually run without asking. They still need access limits (which user's data?).
- Write tools should be few and narrow. Risky ones need a confirmation step from a person.
- Make write tools idempotent where possible: calling them twice with the same request has the same effect as calling once. A common way is an idempotency key, a unique id for each request, so a retry does not send two refunds or two emails.
- Never let a write tool do more than its name says.
cancel_ordershould not also refund, email and close the account.
get_order_status = {
"name": "get_order_status",
"description": (
"Look up the delivery status of ONE order by its id. "
"Use when the user gives an order id like ORD-123456. "
"Read-only: it never changes the order. "
"To find orders by date or product, use search_orders instead."
),
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"pattern": "^ORD-[0-9]{6}$",
"description": "Order id, e.g. ORD-123456",
}
},
"required": ["order_id"],
"additionalProperties": False,
},
}parameters or input_schema) differs between providers. The ideas stay the same. Check your provider's current docs.How many tools?
Watch and read more
Lab
Three tool definitions for a school library agent, with schemas, clear errors and a read/write split.
The problem
A school library wants an agent that helps students find and reserve books. Design its tools on paper or in the starter file.
- Write definitions for
search_books,get_bookandreserve_book: name, description (with "use when" and "do not use for"), and a JSON schema for the parameters. - Mark each tool as read or write. For the write tool, say how you make it idempotent.
- Write three error messages each tool might return, in words the model can act on.
- List one tool you decided not to give the agent, and why.
{
"name": "search_books",
"description": "TODO: what it does. Use when ... Do not use for ...",
"parameters": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "TODO" },
"limit": { "type": "integer", "minimum": 1, "maximum": 10 }
},
"required": ["query"],
"additionalProperties": false
}
}You are done when
Hard questions
Try to answer before you reveal. If you can answer these, you understood the lesson.
Q1The model keeps calling <code>search_books</code> when the student gives an exact book id. Name two fixes that do not involve changing the model.Reveal
First, improve the descriptions: say in search_books "if the user gives a book id, use get_book instead", and in get_book give an example id. Second, make the id format clear in the schema (a pattern), and possibly have search_books detect an id and return a hint like "This looks like a book id; use get_book". Then check traces again to confirm the fix.
Q2Why is "reserve_book" safer with a <code>request_id</code> parameter, even if the model never makes mistakes?Reveal
Networks fail. The tool may reserve the book but the reply may be lost, so your loop retries. Without a request id, the second call makes a second reservation. With it, the server sees the same id and returns the first result. The risk comes from retries and timeouts, not only from the model.
Questions people ask
Should the description be long or short?
Long enough to answer: what does it do, when should I use it, when should I not, what do the parameters mean, and what does it return? A few clear sentences usually beat both one word and a whole page.
Is it bad to give the model a general tool like run_python?
Not always, but it is powerful and risky. If you do, run it in a sandbox with no secrets, limited network and limits on time and memory. For most business tasks, a few narrow tools are safer and more reliable. See Sandboxing: the room with no windows.
What should a tool return when nothing is found?
A clear, normal result, such as {"orders": [], "note": "No orders in this date range"}. An empty string or an error can make the model think the tool broke and try again and again.
How do I know which tool descriptions confuse the model?
Look at traces of real runs. If the model often calls the wrong tool, or calls one tool with missing arguments, that description needs work. The lesson Evaluating and debugging agents shows how to collect these traces.
Lesson test
5 questions. Get 3 right (60%) to pass and complete this lesson.