Tool use
Designing good tool schemas
Why descriptions matter more than you think.
The schema is a prompt
Most engineers treat a tool schema like an API contract: write the parameters, ship it, move on. That's a mistake. The schema is part of the prompt the model reads to decide whether and how to use your tool. Every field name, type, and description is a tiny instruction.
A model staring at a poorly-described tool will either ignore it, call it incorrectly, or pick the wrong tool from your list. A well-described tool gets called when it should and skipped when it shouldn't. The difference between these two outcomes is usually a few sentences.
A bad schema
Here is a schema that works on paper but fails in practice:
{
"name": "search",
"description": "Search.",
"parameters": {
"type": "object",
"properties": {
"q": {"type": "string"},
"n": {"type": "integer"},
},
"required": ["q"],
},
}What's wrong:
- "search" is ambiguous. The web? A database? Code? The model has to guess.
- "Search." gives no instruction on when to use it.
qandnare cryptic. The model can read them but it has to infer meaning.- No description on the parameters means the model invents conventions (does
nmean number of results, page number, or something else?).
This is the kind of tool a model will either skip or misuse.
A good schema
{
"name": "search_documentation",
"description": (
"Search the project's internal documentation by keyword. "
"Use this when the user asks about how a specific feature works "
"or where something is configured. Do not use for general web searches."
),
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Keywords to search for. Use 2 to 5 specific terms, not full sentences.",
},
"max_results": {
"type": "integer",
"description": "Maximum number of results to return.",
"default": 5,
"minimum": 1,
"maximum": 20,
},
},
"required": ["query"],
},
}What changed:
- The name says exactly what it searches.
- The description tells the model when to call it and when not to.
- Parameter names are full words.
- Each parameter has a description explaining the expected format.
- Defaults and bounds are explicit.
This is the kind of tool a model will pick correctly without help.
The five rules
1. Name tools by what they do, not by what they are
Bad: database, searcher, api. Good: query_users_table, search_documentation, fetch_pull_request. Verbs help. The name is the first thing the model sees.
2. Description: when to use it AND when not to use it
Models latch on to negative instructions. If you have multiple tools that look similar, "do not use for X" is the easiest way to prevent the wrong pick.
"description": (
"Read a file from the current project. "
"Use for source code files. "
"Do not use for binary files (use download_binary instead)."
)3. Use full parameter names
user_email beats email. start_date_iso beats start. Verbose parameter names are essentially extra documentation, and the model reads them on every call.
4. Describe formats explicitly
If a parameter expects ISO 8601 dates, say so. If a string should be 2 to 5 words, say so. The model will follow format instructions remarkably well if you provide them.
"start_date": {
"type": "string",
"description": "ISO 8601 date string, e.g. '2025-01-15'",
},5. Constrain enums when you can
If a field has a known set of values, use enum. The model is dramatically more reliable with enums than free-form strings.
"status": {
"type": "string",
"enum": ["open", "closed", "in_progress"],
"description": "The current status to filter by.",
},Schema design and tool count
There is a tension between "more tools, each narrow" and "fewer tools, each general." Both extremes fail.
| Approach | What goes wrong |
|---|---|
| 50 narrow tools | The model can't fit all definitions in attention well; it picks suboptimal tools |
| 1 god-tool with a "mode" parameter | The model has to learn how the modes interact; descriptions get muddled |
| 5-15 well-named tools | Usually the sweet spot |
If you have more than 15 tools, consider a router: one top-level tool whose description routes to a sub-agent that has access to a subset of the full tool list. We cover the orchestrator pattern in Track 2 and the MCP version in Track 3.
Test your schemas the way you'd test code
A schema is code that runs in a model's head, but you can still verify it. A few practical checks:
# 1. Does the model pick this tool when it should?
test_cases = [
("How do I configure auth?", "search_documentation"),
("What's the weather?", "get_weather"),
("Open my PR", "fetch_pull_request"),
]
for query, expected_tool in test_cases:
response = ollama.chat(messages=[{"role": "user", "content": query}], tools=tools)
actual = response.message.tool_calls[0].function.name if response.message.tool_calls else None
print(f"{query!r:50} expected={expected_tool} got={actual}")If the model picks the wrong tool, the fix is almost always in the descriptions. Iterate on the descriptions, not on the model.
Show, don't tell, when descriptions get long
For tricky tools, end the description with a 1-line example: "Example: search_documentation(query='auth middleware', max_results=3)". Models are excellent at pattern-matching from examples, often more so than from prose.
A schema is forever
Tool schemas leak into the model's context, the user's expectations, your logs, and any conversation history you persist. Renaming a parameter can break replays of past sessions. Treat schemas with the same care you treat database column names: easy to add, hard to change.
Key takeaway
Tool schemas are prompts for the model, not just contracts for your code. Name tools by their action, describe when (and when not) to use them, name parameters with full words, specify formats, and use enums where you can. Spending an extra five minutes on a schema is the highest-leverage thing you'll do for agent reliability.
Done with this lesson?