What makes a good tool schema, and why the description outranks the name
The function-calling note made one point in passing that deserves its own page: tool definitions are prompt text, and the model chooses tools by reading them. That turns schema writing from a formality into the main lever you have over whether the right tool gets called with the right arguments. This note goes through each field, shows a weak schema rewritten, and explains why the description, not the name, is where selection is decided.
Name the tool for the action it performs, in one consistent style. Spend most of your effort on the description: what it does, when to use it, when not to, what it costs or changes, and how it relates to neighbouring tools. Give every parameter an explicit type, a description with format and an example, and an enum wherever the values are fixed. Put in required only what the function cannot run without, so the model is never forced to invent a value. The name is a label of two or three tokens; the description is the only place the conditions for calling can live, which is why it decides selection.
Contents
01The schema is a contract the model reads
A tool schema has two readers. Your code reads the name to dispatch the call and the schema to validate the arguments. The model reads all of it as text, rendered into its context ahead of the conversation, and from that text alone decides whether to call the tool and what to pass. Nothing else about your function is visible to it: not the implementation, not the docstring, not the database behind it.
So a schema is doing two jobs at once, a machine contract and a briefing. Most of what goes wrong in tool use, the wrong tool chosen, a tool called when none was needed, an argument invented, comes from a briefing that left something out. The fields below are the places to put it.
02Naming
- Name the action. A verb and an object:
search_user_database,create_support_ticket,get_order_status. A reader should know what happens from the name alone. - Be specific enough to differ from your other tools.
process_data,run_queryandhandle_requestsay nothing. If you have three search tools, their names should say what each searches. - One style, consistently. Snake case or camel case, verbs first, the same verb for the same kind of operation across tools:
get_for reads,create_for writes,search_for queries that return lists. Consistency lets the model generalise from one tool to the next. - Keep it short. The name is an identifier, not a sentence. Conditions, caveats and formats belong in the description.
A good name is necessary and not sufficient. It gets the tool noticed; it does not tell the model when to reach for it.
03The description
This is the highest-leverage field in the whole definition, and the one most often left as a fragment. A good description answers four questions, usually in three to five sentences.
- What does it do, and what does it return? "Retrieves active account details, billing status and subscription tier for one customer."
- When should it be used? The trigger conditions, in the terms a user would use. "Use when the user asks about their plan, their invoice, or whether their account is active."
- When should it not be used, or what should be used instead? "Does not return order history; use
list_ordersfor that." This is where overlapping tools are told apart. - What does it cost or change? Side effects and limits. "Sends the email immediately and cannot be recalled." "Slow; takes up to 10 seconds." "Returns at most 50 results."
Formatting rules for arguments can go here too when they apply to the tool as a whole, though each parameter's own description is the better home for its format.
04Parameter types and enums
- Explicit primitive types.
string,integer,number,boolean,arraywith anitemstype,objectwith its properties. An untyped parameter is a guess for the model and a parse hazard for you. - Enums for fixed vocabularies. If a field takes one of a known set, list the set:
"enum": ["celsius", "fahrenheit"]. The model then picks from the list instead of paraphrasing, and in strict mode the decoder is constrained to it, so an invalid value becomes impossible rather than merely unlikely. - A description on every parameter. The name
datedoes not say which date or in what format. "Delivery date in YYYY-MM-DD format, e.g. 2026-10-25" does. Examples in parameter descriptions are cheap and the model uses them. - Formats and ranges in words. JSON Schema's
format,minimumandpatternkeywords are not all honoured by every provider's validator. State the rule in the description as well, where the model will certainly read it. - Keep nesting shallow. Deeply nested objects are harder for the model to fill correctly and some strict modes limit nesting depth. Flatten where you can.
05Required and optional
The required array tells the model which fields it must supply. Two rules.
- Require what the function cannot run without. An order lookup needs an order id; mark it required so the model asks the user for it rather than calling with nothing.
- Leave the rest optional. A field with a sensible default in your backend, such as a result limit or a unit, should not be required. If it is, the model has to produce a value it was never told, and it will invent one: a plausible-looking customer id, a date that is probably wrong, a limit of 10 because 10 is common. Forced fields are where fabricated arguments come from.
Some strict modes require every property to be listed in required so that the output shape is fully determined. Optional fields are then expressed as nullable types, ["string", "null"], and the model passes null to mean "not supplied". The intent is the same; the spelling differs. Check your provider's rules before turning strict on.
06A schema rewritten
A weak schema, of the kind that gets written in a hurry:
{
"name": "user_info",
"description": "Gets user data.",
"input_schema": {
"type": "object",
"properties": {
"id": {"type": "string"},
"filter": {"type": "string"}
}
}
}
The same tool, written as a briefing:
{
"name": "get_user_account_details",
"description": "Retrieves active account details, billing status and subscription tier
for one customer, looked up by system user ID or email address. Use when the user
asks about their plan, billing, or whether their account is active. Does not return
order history (use list_orders) or support tickets (use list_tickets). Read-only.",
"input_schema": {
"type": "object",
"properties": {
"user_id": {
"type": "string",
"description": "System user ID (e.g. usr_8f3a21) or the account email address."
},
"account_status": {
"type": "string",
"enum": ["active", "suspended", "closed"],
"description": "Optional. Return details only if the account is in this status."
}
},
"required": ["user_id"]
}
}
| Field | Weak | Rewritten |
|---|---|---|
| Name | user_info: noun, no action, could mean anything | get_user_account_details: verb, object, scope |
| Description | Three words; no trigger, no boundary, no side-effect statement | What, when, when not, which neighbour to use instead, read-only |
| Parameters | id of what? filter on what, in what syntax? | Named for their meaning, typed, described with examples; the filter is an enum |
| Required | Missing, so the model may call with nothing | The one field the lookup needs; the filter stays optional |
07Why descriptions decide selection
It is tempting to think the model matches the user's words to tool names. It does not, for two reasons.
A name carries almost no information
A name is two or three tokens. The user's request is a sentence or a paragraph, phrased in whatever way the user chose, often without the words in the name at all: "is my card still being charged?" has no overlap with get_user_account_details. The model attends over the whole rendered definition, and the description is the part with enough text to connect "card being charged" to "billing status". The name labels the tool; the description is what the request is matched against.
Boundaries can only be written in prose
Real tool sets overlap. A product has search_internal_kb and search_web; a CRM has get_customer and search_customers. Names cannot say which to prefer, what one covers that the other does not, or in which order to try them. Descriptions can:
search_internal_kb "Searches internal company policy, HR documents and employee benefits. Use this first for any company-specific question, before search_web. Returns up to 10 passages with source titles." search_web "Searches the public web. Use for general knowledge and current events, or when search_internal_kb returned nothing relevant. Never use for questions about company policy."
With those two descriptions the model has a priority rule, a scope for each, and a fallback path. With the names alone it has a coin flip. Every rule you want the model to follow about choosing among tools has to be written somewhere it reads, and the description is that place. The system prompt can carry such rules too, but the description travels with the tool and is read at the moment of choosing.
08Beyond the single schema
- Fewer, clearer tools beat many overlapping ones. Every extra tool is more text to read and another chance to pick wrong. Merge tools that differ only by a parameter; split a tool whose description needs the word "or" more than twice.
- The tool's output is part of the contract. What comes back is read by the model as an observation. Return structured, compact results with the fields the model needs, named consistently with the inputs, and say in the description what the output looks like.
- Test schemas like prompts. Collect requests that should and should not trigger each tool, run them, and measure selection and argument accuracy. A description change is a prompt change and can regress.
- Large tool sets need loading on demand. Past a few dozen tools, send a search tool and mark the rest deferred, so only the relevant definitions enter the context for a given request. Good descriptions matter even more here, because they are what the search matches on.
09Takeaways
- The model only knows what the schema says. Write it as a briefing, not a declaration.
- Name the action, consistently and briefly. Then stop; the name is a label.
- The description answers what, when, when not, and what it costs. Three to five sentences; it is the highest-leverage text you will write.
- Type everything, enum the fixed sets, describe every parameter with a format and an example.
- Require only what the function cannot run without. Forced optional fields are where invented arguments come from.
- Descriptions decide selection because a name has no room for conditions and the request rarely contains the name's words.
- Overlapping tools need their boundaries written down, in the descriptions, with a priority and a fallback.