ToolLoopAgent
Creates a reusable AI agent capable of generating text, streaming responses, and using tools over multiple steps (a reasoning-and-acting loop). ToolLoopAgent is ideal for building autonomous, multi-step agents that can take actions, call tools, and reason over the results until a stop condition is reached.
Unlike single-step calls like generateText(), an agent can iteratively invoke tools, collect tool results, and decide next actions until completion or user approval is required.
import { ToolLoopAgent } from 'ai';
const agent = new ToolLoopAgent({ model: "xai/grok-4.6", instructions: 'You are a helpful assistant.', tools: { weather: weatherTool, calculator: calculatorTool, },});
const result = await agent.generate({ prompt: 'What is the weather in NYC?',});
console.log(result.text);For agents, runtimeContext is the shared runtime state that flows through the
loop. For guidance on runtimeContext, toolsContext, tool context, and
sensitive context filtering, see Runtime and Tool
Context.
Pass experimental_sandbox to generate() or stream() when tools need access to a command
or code execution environment.
To see ToolLoopAgent in action, check out these examples.
Import
import { ToolLoopAgent } from "ai"Constructor
Parameters
model:
instructions?:
allowSystemInMessages?:
tools?:
toolChoice?:
stopWhen?:
activeTools?:
toolOrder?:
toolApproval?:
experimental_toolCallers?:
output?:
prepareStep?:
include?:
repairToolCall?:
experimental_refineToolInput?:
onStart?:
provider:
modelId:
instructions:
messages:
tools:
toolChoice:
activeTools:
toolOrder:
maxOutputTokens:
temperature:
topP:
topK:
presencePenalty:
frequencyPenalty:
stopSequences:
seed:
maxRetries:
timeout:
headers:
providerOptions:
output:
abortSignal:
include:
runtimeContext:
toolsContext:
onStepStart?:
provider:
modelId:
instructions:
messages:
tools:
toolChoice:
activeTools:
toolOrder:
steps:
providerOptions:
timeout:
headers:
stopWhen:
output:
abortSignal:
include:
runtimeContext:
toolsContext:
onToolExecutionStart?:
callId:
toolCall:
messages:
toolContext:
onToolExecutionEnd?:
callId:
toolCall:
toolExecutionMs:
messages:
toolContext:
toolOutput:
onStepEnd?:
onStepFinish?:
onEnd?:
onFinish?:
runtimeContext?:
toolsContext:
telemetry?:
includeRuntimeContext?:
includeToolsContext?:
experimental_download?:
maxOutputTokens?:
temperature?:
topP?:
topK?:
presencePenalty?:
frequencyPenalty?:
stopSequences?:
seed?:
maxRetries?:
providerOptions?:
headers?:
callOptionsSchema?:
prepareCall?:
id?:
Properties
tools:
id:
Methods
generate()
Generates a response and triggers tool calls as needed, running the agent loop and returning the final result. Returns a promise resolving to a GenerateTextResult.
const result = await agent.generate({ prompt: 'What is the weather like?',});prompt:
messages:
abortSignal?:
timeout?:
experimental_sandbox?:
options?:
onStart?:
onStepStart?:
onToolExecutionStart?:
onToolExecutionEnd?:
onStepEnd?:
onStepFinish?:
onEnd?:
onFinish?:
Returns
The generate() method returns a GenerateTextResult object (see generateText for details).
stream()
Streams a response from the agent, including agent reasoning and tool calls, as they occur. Returns a StreamTextResult.
const stream = agent.stream({ prompt: 'Tell me a story about a robot.',});
for await (const chunk of stream.textStream) { console.log(chunk);}prompt:
messages:
abortSignal?:
timeout?:
experimental_sandbox?:
options?:
experimental_transform?:
onStart?:
onStepStart?:
onToolExecutionStart?:
onToolExecutionEnd?:
onStepEnd?:
onStepFinish?:
onEnd?:
onFinish?:
Returns
The stream() method returns a StreamTextResult object (see streamText for details).
Types
ActiveTools
type ActiveTools<TOOLS extends ToolSet> = | ReadonlyArray<keyof TOOLS & string> | undefined;Limits an agent step to the listed tool names. undefined means no tool restriction is applied.
InferAgentUIMessage
Infers the UI message type for the given agent instance. Useful for type-safe UI and message exchanges.
Basic Example
import { ToolLoopAgent, InferAgentUIMessage } from 'ai';
const weatherAgent = new ToolLoopAgent({ model: "xai/grok-4.6", tools: { weather: weatherTool },});
type WeatherAgentUIMessage = InferAgentUIMessage<typeof weatherAgent>;Example with Message Metadata
You can provide a second type argument to customize the metadata for each message. This is useful for tracking rich metadata returned by the agent (such as createdAt, tokens, finish reason, etc.).
import { ToolLoopAgent, InferAgentUIMessage } from 'ai';import { z } from 'zod';
// Example schema for message metadataconst exampleMetadataSchema = z.object({ createdAt: z.number().optional(), model: z.string().optional(), totalTokens: z.number().optional(), finishReason: z.string().optional(),});type ExampleMetadata = z.infer<typeof exampleMetadataSchema>;
// Define agent as usualconst metadataAgent = new ToolLoopAgent({ model: "xai/grok-4.6", // ...other options});
// Type-safe UI message type with custom metadatatype MetadataAgentUIMessage = InferAgentUIMessage< typeof metadataAgent, ExampleMetadata>;Examples
Basic Agent with Tools
import { ToolLoopAgent, isStepCount } from 'ai';import { weatherTool, calculatorTool } from './tools';
const assistant = new ToolLoopAgent({ model: "xai/grok-4.6", instructions: 'You are a helpful assistant.', tools: { weather: weatherTool, calculator: calculatorTool, }, stopWhen: isStepCount(3),});
const result = await assistant.generate({ prompt: 'What is the weather in NYC and what is 100 * 25?',});
console.log(result.text);console.log(result.steps); // Array of all steps taken by the agentStreaming Agent Response
const agent = new ToolLoopAgent({ model: "xai/grok-4.6", instructions: 'You are a creative storyteller.',});
const stream = agent.stream({ prompt: 'Tell me a short story about a time traveler.',});
for await (const chunk of stream.textStream) { process.stdout.write(chunk);}Agent with Output Parsing
import { z } from 'zod';
const analysisAgent = new ToolLoopAgent({ model: "xai/grok-4.6", output: { schema: z.object({ sentiment: z.enum(['positive', 'negative', 'neutral']), score: z.number(), summary: z.string(), }), },});
const result = await analysisAgent.generate({ prompt: 'Analyze this review: "The product exceeded my expectations!"',});
console.log(result.output);// Typed as { sentiment: 'positive' | 'negative' | 'neutral', score: number, summary: string }Example: Approved Tool Execution
import { ToolLoopAgent, ModelMessage, ToolApprovalResponse, tool } from 'ai';import { z } from 'zod';
const agent = new ToolLoopAgent({ model: "xai/grok-4.6", instructions: 'You are an agent with access to a weather API.', tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string(), }), execute: async ({ location }) => ({ location, temperature: 72, }), }), }, toolApproval: { weather: 'user-approval', },});
const messages: ModelMessage[] = [ { role: 'user', content: 'Is it raining in Paris today?' },];
const result = await agent.generate({ messages });const approvals: ToolApprovalResponse[] = [];
for (const part of result.content) { if (part.type === 'tool-approval-request') { approvals.push({ type: 'tool-approval-response', approvalId: part.approvalId, approved: true, }); }}
messages.push(...result.responseMessages);messages.push({ role: 'tool', content: approvals });
const approvedResult = await agent.generate({ messages });console.log(approvedResult.text);