Agents and tools · Lesson 2

Write a tool the model can actually use

You give an agent a search tool. It calls it with the customer's entire question as the query string, gets nothing back, calls it again with the same string, and then apologises and makes something up. The obvious conclusion is that the model is not good enough for this.

Look at what you handed it:

name: search
description: Searches the database.
parameters:
  query: string

Nothing there says what is in the database, what a good query looks like, or what comes back. You would not be able to use this tool either. The model is not guessing badly. It is guessing, because guessing is all you left it.

A tool definition is a prompt. The name, the description and every parameter name are text the model reads before deciding what to do, and they are the only thing it has. Most tool definitions are written as API documentation for a colleague who can ask you a question. The model cannot ask.

Name it after the job

oms_query means something to you and nothing to a model choosing between six tools. get_order_status says what it is for. Internal service names, team initials and version numbers all cost you accuracy at the moment of selection, which is the moment that matters most, because a wrong tool call is not recovered by a good argument.

Say when not to use it

Nearly every description says what the tool does. Almost none say when to reach for something else, and that is the sentence that prevents the expensive mistakes.

Looks up the current status of one order by its id. Use this when the customer names a specific order. Do not use it to find orders by date, customer or product: use find_orders for that. Returns nothing if the id does not exist, which usually means the customer read it off the wrong email.

That last line matters more than it looks. An empty result with no explanation gets interpreted as "no orders exist", and the agent tells a paying customer they have never bought anything.

Keep the parameters few, flat and closed

Every free-text parameter is a place to invent. Every optional parameter is a decision you have asked the model to make with no information.

status: one of [pending, shipped, delivered, cancelled]

is a parameter that cannot be got wrong. Compare it with filters: string, which will receive "status=shipped AND date>last week" on the third call, because somewhere in the training data that syntax exists and nothing you wrote ruled it out. Where a value comes from a known set, say the set. Where it has a format, give an example in the description rather than describing the format in words.

Nested objects are worth avoiding for the same reason. A shallow tool with six named parameters is called correctly more often than an elegant one taking a single structured request.

Errors are the only feedback it gets

The model cannot see your logs. What it sees is whatever your error string says, and it will act on that string immediately. So the string is a teaching opportunity you get for free, and most systems waste it.

400 Bad Request teaches nothing. The retry is identical to the first call.

date must be YYYY-MM-DD. You sent "5th March". Try "2026-03-05". gets fixed on the next call about nine times out of ten in my experience, and it costs you one line in an exception handler.

The same applies to empty results. [] and no orders matched. The customer may have ordered under a different email address. produce very different next moves.

Overlapping tools are the usual cause

When a model picks the wrong tool, the reason is nearly always that two of them could plausibly have applied and nothing in either description ruled the other out. Adding a seventh tool makes the sixth harder to choose.

The fix is either to merge them or to make each description explicitly name the other and say when it wins. Read your tool list the way the model does: all at once, with no context about your architecture, and ask which two you could confuse.

Where this doesn't help

None of this rescues a task that should not be a tool call at all. If the right action is fully determined by the input, route it in code. A model asked to choose between one option and no options will still sometimes choose neither.

Past roughly a dozen tools, selection accuracy falls off no matter how well each one is written, and the answer stops being better descriptions. It becomes fewer tools in front of the model at once, chosen by something deterministic before the model is asked.

And a well-written tool definition cannot fix a tool that is slow or flaky. An agent that waits eight seconds for a lookup will get retried by an impatient user, and the second run is a fresh set of guesses rather than a continuation of the first.

The move

Read your tool definitions as though you had never seen the system, then rewrite each description to say when not to use it, close every parameter you can into an enum, and make one error message name the fix. Then watch which tool gets called wrongly, because that pair of descriptions is where the work is.

Exercise

Here is a tool definition. Name "search", description "Searches the database", one parameter "query" of type string. Rewrite it for a support agent that looks up orders, and say which of your changes you expect to fix the most wrong calls.

How this gets marked

  • 20%Renames the tool after what it is for rather than after the system behind it.
  • 30%The description says when to reach for something else, not only what this does.
  • 25%Replaces free text with named parameters and a fixed set of values wherever one exists.
  • 25%Says what a failure returns, in words that tell the caller what to do differently.

The lesson is free and stays free. Marking is the part that costs us a model call, so it needs a name to record the score against.