JJoeven

Curriculum/Tools & Function Calling

Descriptions Are Prompts

The tool description is a docstring the model will obey poorly. Write when to use it, when not to, and which ids it accepts.

beginner20 min7 / 24

“Get a user” is a bad description.

Better: “Look up a customer by stable user_id (not email). Use after you already extracted the id. Do not guess ids.”

That paragraph is policy for one tool. Write it like a docstring you would merge. The model will still disobey. The description is how you make disobedience visible in evals and rarer in traces. It is not a security boundary. Permissions are a later lesson. Do not skip the docstring because “the runtime will catch it.” Catching is slower and costlier than not calling.

One table, two renders
RegistryPrompt docsDispatcher

Generate the manual from the same list you run.

One table, two renders

Descriptions are prompts that travel with the schema. They are not the system prompt. They should not be a novel. They should be the minimum the chooser needs: when to use, when not to, which identifiers, read versus write, what the return looks like in one sentence.

Names must not rhyme

search_docs vs search_web vs search_tickets — the model will mix them up if names and descriptions overlap. Make names boring and distinct. Put “read-only” in the description of getters. Put “side effect, needs approval” on writes if the host will even advertise them.

Do not advertise a tool the registry does not have. Generate the prompt snippet from the same list you dispatch. If a tool is disabled, it disappears from both the prompt and the dispatcher. A leftover sentence is how you get unknown_tool loops.

Verbs in names help: get_, list_, search_, create_, refund_. Noun-only names (user, ticket) collide. Version suffixes belong on breaking changes, not on every Tuesday.

What belongs in the description

When to use it: the user question shape, the id you already have, the state you must be in. When not to: emails instead of ids, guesses, writes hidden as reads, “use this for everything.” Which ids: usr_ prefix, invoice ids from get_invoice, not from the user’s memory of a PDF.

What it returns: “a small JSON object with ok and ticket, or error not_found.” If the result can be truncated, say so. If the tool is async, say it returns job_id.

What it does not return: secrets, full logs, other tenants. That sentence is for humans who will write the handler. The model may still ask. The handler still redacts.

Do not paste the JSON Schema into the description. The schema is already sent as structured parameters. Duplicating it wastes tokens and drifts.

One table, two renders

Store name, schema, and description in one table. Render docs for the prompt from that table. Render OpenAPI or MCP tools/list from that table. If a field exists in MCP and not in the prompt, you will get calls you did not document. If a field exists in the prompt and not in the validator, you will get extra keys.

The live box stores when and not_for next to the name. pick is a toy chooser: it shows that an email question should skip get_user. Real models are worse than this if-list. Evals should still include “find user by email” and expect search_tickets or a dedicated email lookup — not get_user with a guessed id.

Live PythonOpen full playgroundpython
Output
Run to execute this in your browser. Nothing is sent to a server.

What printed: usr_9 may consider get_user. The email question skips it. Tickets consider search. The registry keys are the only names. There is no third tool hiding in a prompt file. That is the design.

What goes wrong

One paragraph reused on five tools. Descriptions that say “use this to call the API” with no API named. Joke descriptions. 40-page SDK dumps. Names that differ by one letter (send_mail / send_email). Tools left in the prompt after the handler was deleted. All of these show up as wrong-tool rates, not as compile errors.

Teams also put policy only in the system prompt: “never refund.” Then they advertise refund with a friendly description. The chooser follows the description. Put the restriction in the allowlist first. Then omit the tool. Then, if it must exist for another role, say “billing sessions only” in the description and still enforce in policy.

How to test descriptions

You cannot unit-test English the way you test schema. You can keep a fixture list of user questions and expected tool names, run a cheap model or a fake chooser, and fail CI when get_user fires on email. When you change a description, re-run that list. If you cannot point at the list, you are editing poetry.

Descriptions drift unless they are generated

A support bot and a billing bot should not share a handwritten appendix of tool docs. They share handlers, maybe. They do not share the enabled set. Generate the paragraph the model sees from the same row that holds the schema. When billing disables search_web, the sentence about search_web must vanish in the same deploy. If a writer updates Notion and forgets git, you now have two sources of truth. Git wins. Notion is a copy.

Evals belong next to the paragraph. “Find the user by email” should expect a skip of get_user. “Close ticket 9182” should not consider a getter. You will not get those failures from schema tests. You get them from a question list. When a description change lands, re-run that list before you celebrate the wording.

Overlap is a naming bug more often than a prose bug. search_docs and search_tickets will swap in traces until one is search_kb and the other is search_jira. Spend the rename. Keep the old name as a denied alias for a week if you must, with a structured error “use search_kb.” Do not leave two live tools that rhyme.

Read versus write belongs in the first sentence of the description because choosers skim. “Read-only. Look up a ticket by id.” is better than a lyric about empathy. Irreversible belongs too, even though approval is a runtime gate. The description reduces accidental emits. The gate stops the ones that still emit.

How agents use this

The loop should receive tool docs as data from the registry, not as a handwritten appendix. When a tool is off, it is off in the assembler and in the dispatcher. Wrong-tool problems are often naming problems. Fix names before you add another sentence to the system prompt.

Write descriptions like you write error hints: short, specific, testable. If a sentence cannot be violated in an eval, it does not belong. “Be careful” cannot be violated. “Do not pass email as user_id” can.

Note:Descriptions help the chooser. They do not replace schema, dispatch, or permissions.

Check your understanding

What belongs in a tool description?