A tool is a name, a description, and a JSON Schema for arguments. The model sees only those strings, so they are prompt engineering, not documentation.
Rules that measurably reduce failed calls:
- Verb-first names: `get_invoice`, not `invoice`. - Describe *when to use it and when not to*. - Mark required fields and use enums instead of free text. - Keep the registry small; above ~15 tools, group them behind a router tool.
ts
export const getInvoice = {
type: "function",
function: {
name: "get_invoice",
description:
"Fetch one invoice by id. Use when the user names an invoice or asks about its status. " +
"Do NOT use for lists or date-range searches — use search_invoices for that.",
parameters: {
type: "object",
properties: {
invoice_id: { type: "string", description: "Invoice id like INV-10231" },
include: { type: "string", enum: ["summary", "line_items"], default: "summary" },
},
required: ["invoice_id"],
additionalProperties: false,
},
},
} as const;Always re-validate arguments server-side with Zod before executing: the model is a client, and clients lie.
Exercise: take a tool the model keeps misusing, rewrite only its description to say what it is *not* for, and re-measure. Description edits usually beat model upgrades.