Writing tool schemas a model actually uses correctly
A tool definition is a prompt. Its name, its description and its parameter names are the only things the model has to decide whether to call it.
| Step | Who | Contains |
|---|---|---|
| 1. Request | You | messages + tool definitions |
| 2. Tool call | Model | A tool_use block with an id and arguments |
| 3. Execution | Your code | Validate the arguments before running anything |
| 4. Result | You | A tool_result referencing the same id |
| 5. Answer | Model | Prose, or another tool call |
The description is a routing instruction, not documentation. Saying when NOT to use it is the single most effective line.
{
"name": "search_orders",
"description": "Find a customer's orders by email or order number. Use this whenever the user asks about an order's status, contents or delivery date. Do NOT use it for refunds or cancellations — those go to manage_order.",
"input_schema": {
"type": "object",
"properties": {
"email": { "type": "string", "description": "Customer email. Provide this OR order_number." },
"order_number": { "type": "string", "description": "Order reference, format ORD-000000." },
"since": { "type": "string", "description": "Only orders on or after this date (YYYY-MM-DD)." },
"status": { "type": "string", "enum": ["pending", "shipped", "delivered", "cancelled"] }
},
"required": ["status"]
}
}
Accuracy starts degrading well before the context does — around ten is a practical ceiling. Beyond that, route to a smaller toolset first rather than expanding the list.
Always. Tool arguments are model output, which makes them untrusted input. Content retrieved from elsewhere can carry an injected instruction that reaches your tools this way.