Agents
Agents are large language models (LLMs) that use tools in a loop to accomplish tasks.
They don’t just answer questions — they take action.
These three parts work together:
- LLMs think — they process input and decide what to do next
- Tools act — they extend what the model can do (read data, call APIs, write files)
- Loop coordinates — it runs the process until the task is complete
The loop manages:
- Context management – keeping track of what’s happened so far
- Stopping conditions – deciding when the job is done
Think of it like this:
Model decides → Tool executes → Result feeds back → Repeat until done.
The Agent Class
The Agent
class ties these parts together.
It connects the model, tools, and loop into a reusable system that can reason through multi-step tasks.
Here’s an example of an agent that finds and converts the weather:
import { Experimental_Agent as Agent, stepCountIs, tool } from "ai"
import { z } from "zod"
const weatherAgent = new Agent({
model: "openai/gpt-4o",
tools: {
weather: tool({
description: "Get the weather in a location (in Fahrenheit)",
parameters: z.object({
location: z.string().describe("The location to get the weather for"),
}),
execute: async ({ location }) => ({
location,
temperature: 72 + Math.floor(Math.random() * 21) - 10,
}),
}),
convertFahrenheitToCelsius: tool({
description: "Convert temperature from Fahrenheit to Celsius",
parameters: z.object({
temperature: z.number().describe("Temperature in Fahrenheit"),
}),
execute: async ({ temperature }) => {
const celsius = Math.round((temperature - 32) * (5 / 9))
return { celsius }
},
}),
},
stopWhen: stepCountIs(20),
})
const result = await weatherAgent.generate({
prompt: "What is the weather in San Francisco in celsius?",
})
console.log(result.text) // agent's final answer
console.log(result.steps) // steps taken by the agent
Here’s what happens automatically:
- The model calls the
weather
tool to get the temperature - It then calls
convertFahrenheitToCelsius
- Finally, it writes a natural-language answer
The agent runs the reasoning loop, manages context, and knows when to stop. You define the pieces — it handles the rest.
Why Use the Agent Class?
The Agent
class is the easiest way to build useful, reliable agents with the AI SDK.
It handles all the orchestration for you:
- Less boilerplate – no need to manually manage loops or messages
- Reusable – define once, use anywhere
- Easy to maintain – update model, tools, or logic in one place
Use Agent
for most cases.
When you need fine-grained control (like custom branching or deterministic flows), use lower-level functions like generateText
or streamText
.
Structured Workflows
Agents are powerful but non-deterministic — they reason dynamically, so the same input might take different paths.
When you need strict, predictable control (for example, billing or compliance tasks), use structured workflows instead. Combine:
generateText
for controlled reasoning- Conditional logic (
if
,switch
) for branching - Reusable functions for logic
- Error handling for robustness
These patterns give you the reliability of code with the flexibility of LLMs.
Loop Control
You can control both execution flow and settings at each step of the agent loop.
The AI SDK provides two built-in controls:
stopWhen
– defines when to stop executionprepareStep
– modifies settings (model, tools, messages, etc.) between steps
Stop Conditions
The stopWhen
parameter tells the agent when to stop looping.
By default, agents stop after one step with stepCountIs(1)
.
When you specify stopWhen
, the agent continues running until a condition is met.
You can combine built-in, multiple, or custom conditions.
Use Built-in Conditions
import { Experimental_Agent as Agent, stepCountIs } from "ai"
const agent = new Agent({
model: "openai/gpt-4o",
tools: {
// your tools
},
stopWhen: stepCountIs(20), // Stop after 20 steps
})
const result = await agent.generate(
"Analyze this dataset and create a summary report"
)
Combine Multiple Conditions
Stop when any condition is met:
import { Experimental_Agent as Agent, hasToolCall, stepCountIs } from "ai"
const agent = new Agent({
model: "openai/gpt-4o",
tools: {
// your tools
},
stopWhen: [
stepCountIs(20),
hasToolCall("someTool"), // Stop after calling 'someTool'
],
})
const result = await agent.generate("Research and analyze the topic")
Create Custom Conditions
Define your own stopping logic:
import { Experimental_Agent as Agent, StopCondition, ToolSet } from "ai"
const tools = {
/* your tools */
} satisfies ToolSet
const hasAnswer: StopCondition<typeof tools> = ({ steps }) => {
return steps.some((step) => step.text?.includes("ANSWER:")) ?? false
}
const agent = new Agent({
model: "openai/gpt-4o",
tools,
stopWhen: hasAnswer,
})
const result = await agent.generate(
'Find the answer and respond with "ANSWER: [your answer]"'
)
You can even stop based on cost or token usage:
const budgetExceeded: StopCondition<typeof tools> = ({ steps }) => {
const totalUsage = steps.reduce(
(acc, step) => ({
inputTokens: acc.inputTokens + (step.usage?.inputTokens ?? 0),
outputTokens: acc.outputTokens + (step.usage?.outputTokens ?? 0),
}),
{ inputTokens: 0, outputTokens: 0 }
)
const costEstimate =
(totalUsage.inputTokens * 0.01 + totalUsage.outputTokens * 0.03) / 1000
return costEstimate > 0.5 // Stop if cost exceeds $0.50
}
Prepare Step
prepareStep
runs before each iteration of the loop.
Use it to adjust settings, manage context, or change behavior dynamically.
If you return nothing, the agent continues with its current settings.
Dynamic Model Selection
Switch models based on task complexity:
import { Experimental_Agent as Agent } from "ai"
const agent = new Agent({
model: "openai/gpt-4o-mini", // Default model
tools: {
// your tools
},
prepareStep: async ({ stepNumber, messages }) => {
if (stepNumber > 2 && messages.length > 10) {
return { model: "openai/gpt-4o" }
}
return {}
},
})
const result = await agent.generate("...")
Context Management
Trim old messages to stay within context limits:
prepareStep: async ({ messages }) => {
if (messages.length > 20) {
return {
messages: [
messages[0], // Keep system message
...messages.slice(-10), // Keep last 10
],
}
}
return {}
}
Tool Selection
Control which tools are active at each step:
prepareStep: async ({ stepNumber }) => {
if (stepNumber <= 2)
return { activeTools: ["search"], toolChoice: "required" }
if (stepNumber <= 5) return { activeTools: ["analyze"] }
return { activeTools: ["summarize"], toolChoice: "required" }
}
You can also force specific tools:
prepareStep: async ({ stepNumber }) => {
if (stepNumber === 0) {
return { toolChoice: { type: "tool", toolName: "search" } }
}
if (stepNumber === 5) {
return { toolChoice: { type: "tool", toolName: "summarize" } }
}
return {}
}
Message Modification
Transform messages before each step:
prepareStep: async ({ messages }) => {
const processed = messages.map((msg) => {
if (msg.role === "tool" && msg.content.length > 1000) {
return { ...msg, content: summarizeToolResult(msg.content) }
}
return msg
})
return { messages: processed }
}
Access Step Information
Both stopWhen
and prepareStep
receive detailed context for decision-making:
prepareStep: async ({ model, stepNumber, steps, messages }) => {
const previousToolCalls = steps.flatMap((step) => step.toolCalls)
const previousResults = steps.flatMap((step) => step.toolResults)
if (previousToolCalls.some((call) => call.toolName === "dataAnalysis")) {
return { toolChoice: { type: "tool", toolName: "reportGenerator" } }
}
return {}
}
Manual Loop Control
When you need total control, build your own loop using the AI SDK Core functions (generateText
, streamText
).
This lets you customize every part of execution — from message handling to stopping logic.
Example: Manual Agent Loop
import { generateText, ModelMessage } from "ai"
const messages: ModelMessage[] = [{ role: "user", content: "..." }]
let step = 0
const maxSteps = 10
while (step < maxSteps) {
const result = await generateText({
model: "openai/gpt-4o",
messages,
tools: {
// your tools
},
})
messages.push(...result.response.messages)
if (result.text) break
step++
}
This approach gives you full control over:
- Message history
- Step-by-step decisions
- Custom stop logic
- Dynamic tool and model changes
- Error handling and recovery
Agents aren’t magic — they’re systems that think through problems one step at a time. Understand the pieces, wire them together clearly, and you’ll have a machine that can reason, act, and adapt — all without the guesswork.
On This Page
AgentsThe Agent ClassWhy Use the Agent Class?Structured WorkflowsLoop ControlStop ConditionsUse Built-in ConditionsCombine Multiple ConditionsCreate Custom ConditionsPrepare StepDynamic Model SelectionContext ManagementTool SelectionMessage ModificationAccess Step InformationManual Loop ControlExample: Manual Agent Loop