ToolLoopAgentAn agent loop lets the model call tools (functions) repeatedly until it has enough information to produce a final answer. ToolLoopAgent is the dedicated class for this in AI SDK 6 — import it directly from ai.
User prompt
↓
Model decides to call a tool
↓
Tool executes → result returned to model
↓
Model decides: call another tool? or give final answer?
↓
Loop ends → final response returned
Use tool() with inputSchema: zodSchema(...) (SDK 6 pattern):
import { tool, zodSchema } from "ai";
import { z } from "zod";
const getWeatherTool = tool({
description: "Get the weather for a location",
inputSchema: zodSchema(
z.object({
city: z.string().describe("The city to get the weather for"),
})
),
execute: async ({ city }) => {
// In real code: call a weather API
return `The weather in ${city} is 25°C and Sunny`;
},
strict: true,
});
Use agent.stream() and iterate over textStream:
import { ToolLoopAgent } from "ai";
import { openai } from "@ai-sdk/openai";
const agent = new ToolLoopAgent({
model: openai("gpt-4.1-nano-2025-04-14"),
tools: { getWeather: getWeatherTool },
});
const { textStream } = await agent.stream({
prompt: "What is the weather in New York?",
});
for await (const text of textStream) {
process.stdout.write(text);
}
Use agent.generate() when you need the full response at once:
const { text, steps } = await agent.generate({
prompt: "What is the weather in Paris?",
});
console.log(text); // → "The weather in Paris is 25°C and Sunny."
maxStepsBy default, the loop runs until the model stops calling tools. Use maxSteps to cap iterations and prevent runaway loops:
const agent = new ToolLoopAgent({
model: openai("gpt-4.1-nano-2025-04-14"),
tools: { getWeather: getWeatherTool },
maxSteps: 5, // model can call tools at most 5 times before being forced to answer
});
const { textStream } = await agent.stream({
prompt: "What is the weather in Tokyo, London, and Sydney?",
});
for await (const text of textStream) {
process.stdout.write(text);
}
Each model call (whether it calls a tool or produces a final answer) counts as one step. For a 3-city query, the loop might look like:
Step 1 → call getWeather(Tokyo)
Step 2 → call getWeather(London)
Step 3 → call getWeather(Sydney)
Step 4 → produce final answer
Set maxSteps to expected number of tool calls + 1 to be safe.
const agent = new ToolLoopAgent({
model: openai("gpt-4.1-nano-2025-04-14"),
system: "You are a helpful weather assistant. Always give temperatures in Celsius.",
tools: { getWeather: getWeatherTool },
maxSteps: 5,
});
agent.generate() returns steps — useful for debugging what the model did at each iteration:
const { text, steps } = await agent.generate({
prompt: "What is the weather in Berlin and Madrid?",
});
for (const step of steps) {
console.log("--- Step", step.stepType, "---");
console.log("Text:", step.text);
console.log("Tool calls:", step.toolCalls); // what the model requested
console.log("Tool results:", step.toolResults); // what was returned
}
console.log("Final answer:", text);
const searchWebTool = tool({
description: "Search the web for information",
inputSchema: zodSchema(z.object({ query: z.string() })),
execute: async ({ query }) => `Results for "${query}": ...`,
strict: true,
});
const agent = new ToolLoopAgent({
model: openai("gpt-4.1-nano-2025-04-14"),
tools: {
getWeather: getWeatherTool,
searchWeb: searchWebTool,
},
maxSteps: 10,
});
const { textStream } = await agent.stream({
prompt: "What's the weather in London and summarise today's AI news?",
});
for await (const text of textStream) {
process.stdout.write(text);
}
import { config } from "dotenv";
config({ path: ".env.local" });
import { tool, zodSchema, ToolLoopAgent } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
const getWeatherTool = tool({
description: "Get the weather for a location",
inputSchema: zodSchema(
z.object({
city: z.string().describe("The city to get the weather for"),
})
),
execute: async ({ city }) => {
return `The weather in ${city} is 25°C and Sunny`;
},
strict: true,
});
const askQuestion = async (prompt: string) => {
const agent = new ToolLoopAgent({
model: openai("gpt-4.1-nano-2025-04-14"),
tools: { getWeather: getWeatherTool },
maxSteps: 5,
});
const { textStream } = await agent.stream({ prompt });
for await (const text of textStream) {
process.stdout.write(text);
}
};
await askQuestion("What is the weather in New York?");
| Concept | Notes |
|---|---|
inputSchema: zodSchema(...) | SDK 6 way to define tool inputs (replaces parameters) |
strict: true | Enforces the schema strictly — recommended |
tools object | Keys are the tool names the model will reference |
tool.description | What the model reads to decide when to call the tool — be specific |
maxSteps | Max loop iterations; set to expected tool calls + 1 to be safe |
agent.stream() | Returns { textStream } — stream the final answer token by token |
agent.generate() | Returns { text, steps } — full response + step-by-step inspection |
steps | Array of each iteration: stepType, text, toolCalls, toolResults |
maxSteps any time you want a safety cap on how many tool calls the model can make