← All tutorials

Prompting agents and tool use

The model calls a tool, reads the result, loops, and produces an answer when doneModeldecide next stepRun toolyour codeAnswerwhen donecall toolresultstop when done
An agent loops β€” call a tool, read the result, decide again β€” and stops when it can answer. Code caps the loop.

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:

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.