AI SDK 6

Agent Loop – ToolLoopAgent

Build agentic loops with tools using ToolLoopAgent in AI SDK 6.

Agent Loop – ToolLoopAgent

An 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.

How It Works

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

Defining a Tool

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,
});

Running the Agent (Streaming)

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);
}

Running the Agent (Non-streaming)

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."

Limiting Iterations with maxSteps

By 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);
}

What counts as a step?

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.


Adding a System Prompt

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,
});

Inspecting Steps

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);

Multiple Tools

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);
}

Full Working Example

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?");

Key Concepts

ConceptNotes
inputSchema: zodSchema(...)SDK 6 way to define tool inputs (replaces parameters)
strict: trueEnforces the schema strictly — recommended
tools objectKeys are the tool names the model will reference
tool.descriptionWhat the model reads to decide when to call the tool — be specific
maxStepsMax 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
stepsArray of each iteration: stepType, text, toolCalls, toolResults

When to Use

  • Multi-step research (search → summarise → answer)
  • Tasks that depend on external data at runtime (weather, stock prices, databases)
  • Pipelines where the number of steps isn't known in advance
  • Use maxSteps any time you want a safety cap on how many tool calls the model can make