1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
17 small wins to finish your pathNext lesson →
Tools and multi-step calls
A tool has a description, a Zod input schema and an execute function. With stopWhen, the SDK sends tool results back to the model until it answers.
import { tool } from "ai";
import { z } from "zod";
const ORDERS: Record<string, string> = { "A-1001": "shipped on 12 March", "A-1002": "waiting for stock" };
export const lookupOrder = tool({
description: "Look up the status of an order.",
inputSchema: z.object({ orderId: z.string().describe("The order id, like A-1001") }),
execute: async ({ orderId }) => ORDERS[orderId] ?? "no such order",
});import { generateText } from "ai";
import { shopModel } from "./shop-model.ts";
import { lookupOrder } from "./tools.ts";
const result = await generateText({ model: shopModel, tools: { lookupOrder }, prompt: "Where is A-1001?" });
console.log(JSON.stringify(result.text));
console.log(result.finishReason);
console.log(result.toolCalls.map((c) => [c.toolName, c.input]));
console.log(result.toolResults.map((r) => r.output));npx tsx one-step.tsThe model called lookupOrder, the SDK validated the input against the schema and ran execute, and the result is in toolResults. But text is empty and finishReason is tool-calls: by default a call is a single step, so the model never saw the tool's result.
import { generateText, isStepCount } from "ai";
import { shopModel } from "./shop-model.ts";
import { lookupOrder } from "./tools.ts";
const result = await generateText({
model: shopModel,
tools: { lookupOrder },
stopWhen: isStepCount(5),
prompt: "Where is A-1001?",
});
console.log(result.text);
result.steps.forEach((step, i) => console.log(i, step.finishReason, step.toolCalls.length, JSON.stringify(step.text)));npx tsx loop.tsstopWhen: isStepCount(5) allows up to five steps. Step 0 called the tool; the SDK sent its result back, and in step 1 the model answered, so the loop ended before the limit. steps records each one, which is what to log when an agent does something unexpected.
execute runs on your server
The model only chooses the input.
execute must check anything important, such as whether this customer owns this order, because a model can be talked into calling a tool with any input.Try it yourself
- Ask about
A-9999. - Add a second tool and print
result.steps[0].toolCalls. - Remove
executefrom the tool and look attoolResults.
Slow is fine. Stopping is the only problem.