A tool is how a model touches the world. Everything an agent can do, whether that is reading a file, searching a database, or sending an email, happens because a tool was offered to it. This guide explains what a tool really is, and why most tool problems are really problems with how the tool is described.
It is a good one to read before any problem where the model has the right idea but cannot get what it needs.
01
A tool is two things
When you build a tool for a model, you actually build two separate pieces.
- The implementation. An ordinary Python function. It does the real work and returns a string.
- The schema. A structured description of that function: its name, what it is for, and the arguments it accepts. This is what the model reads.
The model never sees your function. It never sees your code, your defaults, or your comments. It sees the schema and nothing else. If something is not in the schema, then as far as the model is concerned it does not exist.
Keep this in mind for the rest of the guide. The schema is the model's entire understanding of your tool.
02
What a schema looks like
Here is a tool that looks up a customer's orders. The format is the one most model APIs use, a JSON-like dictionary.
1LIST_ORDERS = {2 "type": "function",3 "function": {4 "name": "list_orders",5 "description": "List a customer's orders, newest first. Returns one line per order.",6 "parameters": {7 "type": "object",8 "properties": {9 "customer_id": {"type": "string", "description": "The customer's id, like C-1042."},10 "limit": {"type": "integer", "description": "How many orders to return. Default 10."},11 },12 "required": ["customer_id"],13 },14 },15}It has four parts worth knowing.
nameis what the model writes when it wants the tool.descriptionsays what the tool does and when to use it.propertieslists every argument the model is allowed to send, with a type and, ideally, a short explanation.requirednames the arguments the model must always send. Everything else is optional.
When you call the model, you pass a list of these schemas. That list is the menu. On each call, the model reads the menu and decides whether to answer or to order something off it.
03
The model can only send what the schema lists
This is the rule that causes the most confusion, so it is worth saying plainly.
Suppose your Python function is written like this:
1def list_orders(customer_id, limit=10, offset=0, status=None):2 ...But the schema above only lists customer_id and limit. The model can send those two. It cannot send offset or status, however much the function supports them. It has never heard of them. Writing "use offset to see older orders" in the system prompt will not help much either, because the model has no argument by that name to put it in.
Whenever a model keeps getting too much, too little, or the wrong slice of something, ask whether the knob it needs exists in the schema. If it does not, no prompt will create it. The fix is to change the schema.
The reverse also matters. If the schema promises an argument that the implementation ignores, the model will rely on a feature that does not exist and be confused when nothing changes. The schema and the implementation have to agree.
04
Descriptions are prompts
The text in description is not a code comment. It is read by the model, every call, and it influences which tool the model picks and how it fills in the arguments.
Compare two descriptions for the same tool.
"Get orders""List a customer's orders, newest first. Returns one line per order. Use get_order for the full details of a single order."
The second tells the model what comes back, in what order, how much detail to expect, and where to go for more. A model reading it can make a good decision. A model reading the first has to guess.
Good descriptions usually answer four questions:
- What does this tool do, in one sentence?
- What does the result look like?
- When should I use this instead of another tool?
- Are there limits I should know about?
The same goes for each argument. A short description such as "The customer's id, like C-1042" prevents the model from sending a name or an email address instead.
05
Arguments: required, optional, and defaults
Each argument you add is a choice you are handing to the model, and each choice is a chance to get it wrong. A few habits help.
- Require only what the tool cannot work without. Make everything else optional, with a sensible default in the implementation.
- State the default in the description. "Default 10" tells the model it can leave the argument out.
- Prefer simple types. Strings, integers, and short lists are reliable. Deeply nested objects are not.
- Use an enum when there is a fixed set of choices. If
statuscan only beopen,shipped, orcancelled, list those values in the schema so that the model cannot invent a fourth. - Validate in the implementation anyway. The model will occasionally send a wrong type or an out-of-range number. Check it and return a clear message.
06
Results are text the model has to read
A tool returns a string, and the model reads it exactly the way you would read a printout. This changes what makes a result good.
- Make it easy to scan. One order per line, with labeled fields, beats a raw dump.
- Say what you did. "Showing 10 of 87 orders, newest first" tells the model that more exist and how to ask for them.
- Keep it bounded. A tool that returns an entire database table, or a whole file, can fill the model's working space with noise before it has done anything useful. Give tools a way to return a slice, and a sensible limit when the model does not ask for one.
- Make errors informative.
ERROR: no customer with id C-9999lets the model recover. A bareERRORor an empty string does not.
07
Size is a budget
Everything a tool returns is added to the conversation, and the whole conversation is sent again on every later call. So a large result does not cost tokens once. It costs tokens on every remaining step.
Tasks usually have a token budget. A single oversized result can use most of it, and the next call may not fit at all. When a run fails with a budget error, look at the largest tool result in the trace. Ask whether the model needed all of it, and whether it had any way to ask for less.
08
A short checklist
Before you trust a tool, read its schema as if you were the model and had never seen the code.
- Can I tell what this tool does and when to use it?
- Do I know what every argument means, and which ones I can leave out?
- Is there any argument I would want that is missing?
- Do I know what the result will look like, and how big it could get?
- If it fails, will the message tell me what to try next?
09
Where this shows up
Problem 03 is about a tool whose schema does not let the model ask for what it needs. Problem 08 is about a menu that has grown too large and confusing. Both come back to the same rule from this guide: the model can only use what the menu shows it, so the menu is where you do the work. For a deeper look at choosing and shaping a whole menu, see designing a tool set.