Prompting agents and tool use
A tool-using model can do more than talk: you expose functions β look up an order, search the docs, send an email β and the model decides when to call them, with what arguments, and reads the results back before it answers. An agent is the same idea in a loop: call a tool, look at the result, decide the next step, repeat until the task is done.
None of that decision-making is magic. The model chooses whether to call a tool, and which one, by reading two pieces of text you wrote: the tool descriptions and the system prompt. Both are prompts, and vague wording in either is why agents over-call, under-call, guess arguments, or loop forever.
The tool description is a prompt
The most under-appreciated prompt in an agent is the description attached to each tool. It is what the model reads to decide whether this tool fits the moment β so write it like an instruction, not a label.
import { tool } from "ai";
import { z } from "zod";
const lookupOrder = tool({
description:
"Look up the status of a customer order by its ID. Use this whenever the user asks where an order is or whether it shipped. Do not use it for refunds β that is refundOrder.",
inputSchema: z.object({
orderId: z.string().describe("The order ID, e.g. AC-10432. Ask the user if it is not known."),
}),
execute: async ({ orderId }) => getOrderStatus(orderId),
});
Notice what the description does beyond naming the tool: it says when to use it and when not to. A description that reads only "looks up an order" leaves the model to guess the boundaries, and guessing is where wrong-tool calls come from. The .describe() on the parameter is a prompt too β it tells the model what a valid argument looks like and what to do when it does not have one.
The system prompt sets the policy
Tool descriptions decide which tool; the system prompt decides the model's overall stance toward calling tools at all. A few rules prevent most agent misbehaviour:
- Prefer a tool over your own memory for anything specific to this customer β orders, balances, account settings. Do not answer those from memory.
- Never invent an argument. If you need an order ID or an email and do not have it, ask the user for it instead of guessing.
- Call one tool at a time, read the result, and stop as soon as you can answer. Do not call tools you do not need.
- If a tool returns an error twice, stop and tell the user plainly rather than retrying endlessly.
The failures, and what fixes each
Looping. The classic agent failure: it calls tools indefinitely, never deciding it is done. The fixes are a stopping condition in the prompt ("stop as soon as you can answer"), a hard stopWhen / max-steps limit in code as a backstop, and a clear definition of what "done" looks like for the task. Never rely on the prompt alone here β cap the steps in code as well.
Guessing arguments. Asked to look up an order with no ID in the conversation, a model will often invent a plausible-looking one rather than admit it is missing. The instruction "never invent an argument β ask the user" is the single highest-value line in an agent system prompt, and repeating the point in the parameter's own .describe() reinforces it.
Over-calling. The model reaches for a tool when it could just answer, adding latency and cost. This usually traces to tool descriptions that claim too broad a scope, or a system prompt that oversells tools. Tighten the "use this whenβ¦" clause on the description.
Under-calling. The opposite: the model answers a customer-specific question from memory and gets it wrong. Fix it with the explicit "prefer a tool over your own memory for anything specific to this customer" rule β models will not reach for a tool they were not told to prefer.
Tool arguments are structured output
Every tool call is the model producing structured output β the arguments β against the schema you defined. Everything in the structured output tutorial applies: a typed schema constrains the shape, clear field descriptions carry the meaning, and validation on your side catches the rest. An agent is, in large part, a system prompt plus a set of well-described tools plus a loop with a limit.
From a real codebase
Several of these patterns show up together in fraud-investigation-agent-java, a Spring AI agent that investigates a flagged fraud case. Its tools are described exactly as this tutorial argues β the description says what the tool returns, and the parameter says what a valid argument looks like:
@Tool(description = "Count how many transactions the customer made in the last 10 minutes (velocity signal).")
public String checkVelocityRules(@ToolParam(description = "customer id, e.g. C-1") String customerId) { ... }
But the sharper lessons are architectural β the ones a prompt alone cannot deliver:
- It splits gathering from deciding. One system prompt investigates and is told to βgather enough evidence to judge the case, then stopβ¦ do NOT decide the outcomeβ; a separate step produces the structured
Disposition(approve/monitor/block). Two jobs, two prompts, so neither contaminates the other. - The critical step runs in code, not as a tool. The mandatory sanctions/watchlist screen is not exposed as a tool for the model to choose β the service runs it for every case, because, in its own words, a sanctions hit is βtoo important to leave to the model's whim about whether to call the tool.β
- The dangerous action is gated by a human. If the disposition is
block, code β not the model β calls a human approver, and the account is blocked only on approval.
Together those carry one lesson to any agent you build: the prompt decides, but the code keeps the model away from the actions it must not take alone. The related fraud-triage-with-llm shows the same tool-use loop in Python, with the anti-guessing rule stated plainly in its system prompt β βbase your decision only on the evidence you retrieve β do not assume values you have not looked up.β
The instructions in your tool descriptions and agent system prompt are task prompts, and the same things break them β vagueness, missing boundaries, no definition of done. When a description or a rule is task-shaped, paste it into the checker to catch the ambiguity before the agent acts on it.