Event Callbacks

The AI SDK provides per-call event callbacks that you can pass to generateText and streamText to observe lifecycle events. This is useful for building observability tools, logging systems, analytics, and debugging utilities.

Basic Usage

Pass callbacks directly to generateText or streamText:

import { generateText } from 'ai';
const result = await generateText({
model: openai('gpt-4o'),
prompt: 'What is the weather in San Francisco?',
experimental_onStart: event => {
console.log('Generation started:', event.model.modelId);
},
onFinish: event => {
console.log('Generation finished:', event.totalUsage);
},
});

Available Callbacks

experimental_onStart:

(event: OnStartEvent) => void | Promise<void>

experimental_onStepStart:

(event: OnStepStartEvent) => void | Promise<void>

experimental_onToolCallStart:

(event: OnToolCallStartEvent) => void | Promise<void>

experimental_onToolCallFinish:

(event: OnToolCallFinishEvent) => void | Promise<void>

onStepFinish:

(event: OnStepFinishEvent) => void | Promise<void>

onFinish:

(event: OnFinishEvent) => void | Promise<void>

Event Reference

experimental_onStart

Called when the generation operation begins, before any LLM calls are made.

const result = await generateText({
model: openai('gpt-4o'),
prompt: 'Hello!',
experimental_onStart: event => {
console.log('Model:', event.model.modelId);
console.log('Temperature:', event.temperature);
},
});

model:

{ provider: string; modelId: string }

system:

string | SystemModelMessage | Array<SystemModelMessage> | undefined

prompt:

string | Array<ModelMessage> | undefined

messages:

Array<ModelMessage> | undefined

tools:

ToolSet | undefined

toolChoice:

ToolChoice | undefined

activeTools:

Array<keyof TOOLS> | undefined

maxOutputTokens:

number | undefined

temperature:

number | undefined

topP:

number | undefined

topK:

number | undefined

presencePenalty:

number | undefined

frequencyPenalty:

number | undefined

stopSequences:

string[] | undefined

seed:

number | undefined

maxRetries:

number

timeout:

TimeoutConfiguration | undefined

headers:

Record<string, string | undefined> | undefined

providerOptions:

ProviderOptions | undefined

stopWhen:

StopCondition | Array<StopCondition> | undefined

output:

Output | undefined

abortSignal:

AbortSignal | undefined

include:

{ requestBody?: boolean; responseBody?: boolean } | undefined

functionId:

string | undefined

metadata:

Record<string, unknown> | undefined

experimental_context:

unknown

experimental_onStepStart

Called before each step (LLM call) begins. Useful for tracking multi-step generations.

const result = await generateText({
model: openai('gpt-4o'),
prompt: 'Hello!',
experimental_onStepStart: event => {
console.log('Step:', event.stepNumber);
console.log('Messages:', event.messages.length);
},
});

stepNumber:

number

model:

{ provider: string; modelId: string }

system:

string | SystemModelMessage | Array<SystemModelMessage> | undefined

messages:

Array<ModelMessage>

tools:

ToolSet | undefined

toolChoice:

LanguageModelV3ToolChoice | undefined

activeTools:

Array<keyof TOOLS> | undefined

steps:

ReadonlyArray<StepResult>

providerOptions:

ProviderOptions | undefined

timeout:

TimeoutConfiguration | undefined

headers:

Record<string, string | undefined> | undefined

stopWhen:

StopCondition | Array<StopCondition> | undefined

output:

Output | undefined

abortSignal:

AbortSignal | undefined

include:

{ requestBody?: boolean; responseBody?: boolean } | undefined

functionId:

string | undefined

metadata:

Record<string, unknown> | undefined

experimental_context:

unknown

experimental_onToolCallStart

Called before a tool's execute function runs.

const result = await generateText({
model: openai('gpt-4o'),
prompt: 'What is the weather?',
tools: { getWeather },
experimental_onToolCallStart: event => {
console.log('Tool:', event.toolCall.toolName);
console.log('Input:', event.toolCall.input);
},
});

stepNumber:

number | undefined

model:

{ provider: string; modelId: string } | undefined

toolCall:

TypedToolCall
TypedToolCall

type:

'tool-call'

toolCallId:

string

toolName:

string

input:

unknown

messages:

Array<ModelMessage>

abortSignal:

AbortSignal | undefined

functionId:

string | undefined

metadata:

Record<string, unknown> | undefined

experimental_context:

unknown

experimental_onToolCallFinish

Called after a tool's execute function completes or errors. Uses a discriminated union on the success field.

const result = await generateText({
model: openai('gpt-4o'),
prompt: 'What is the weather?',
tools: { getWeather },
experimental_onToolCallFinish: event => {
console.log('Tool:', event.toolCall.toolName);
console.log('Duration:', event.durationMs, 'ms');
if (event.success) {
console.log('Output:', event.output);
} else {
console.error('Error:', event.error);
}
},
});

stepNumber:

number | undefined

model:

{ provider: string; modelId: string } | undefined

toolCall:

TypedToolCall
TypedToolCall

type:

'tool-call'

toolCallId:

string

toolName:

string

input:

unknown

messages:

Array<ModelMessage>

abortSignal:

AbortSignal | undefined

durationMs:

number

functionId:

string | undefined

metadata:

Record<string, unknown> | undefined

experimental_context:

unknown

success:

boolean

output:

unknown

error:

unknown

onStepFinish

Called after each step (LLM call) completes. Provides the full StepResult.

const result = await generateText({
model: openai('gpt-4o'),
prompt: 'Hello!',
onStepFinish: event => {
console.log('Step:', event.stepNumber);
console.log('Finish reason:', event.finishReason);
console.log('Tokens:', event.usage.totalTokens);
},
});

stepNumber:

number

model:

{ provider: string; modelId: string }

finishReason:

'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other'

usage:

LanguageModelUsage
LanguageModelUsage

inputTokens:

number | undefined

outputTokens:

number | undefined

totalTokens:

number | undefined

text:

string

toolCalls:

Array<TypedToolCall>

toolResults:

Array<TypedToolResult>

content:

Array<ContentPart>

reasoning:

Array<ReasoningPart>

reasoningText:

string | undefined

files:

Array<GeneratedFile>

sources:

Array<Source>

warnings:

CallWarning[] | undefined

request:

LanguageModelRequestMetadata

response:

LanguageModelResponseMetadata

functionId:

string | undefined

metadata:

Record<string, unknown> | undefined

experimental_context:

unknown

providerMetadata:

ProviderMetadata | undefined

onFinish

Called when the entire generation completes (all steps finished). Includes aggregated data.

const result = await generateText({
model: openai('gpt-4o'),
prompt: 'Hello!',
onFinish: event => {
console.log('Total steps:', event.steps.length);
console.log('Total tokens:', event.totalUsage.totalTokens);
console.log('Final text:', event.text);
},
});

steps:

Array<StepResult>

totalUsage:

LanguageModelUsage
LanguageModelUsage

inputTokens:

number | undefined

outputTokens:

number | undefined

totalTokens:

number | undefined

stepNumber:

number

model:

{ provider: string; modelId: string }

finishReason:

'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other'

usage:

LanguageModelUsage

text:

string

toolCalls:

Array<TypedToolCall>

toolResults:

Array<TypedToolResult>

content:

Array<ContentPart>

reasoning:

Array<ReasoningPart>

reasoningText:

string | undefined

files:

Array<GeneratedFile>

sources:

Array<Source>

warnings:

CallWarning[] | undefined

request:

LanguageModelRequestMetadata

response:

LanguageModelResponseMetadata

functionId:

string | undefined

metadata:

Record<string, unknown> | undefined

experimental_context:

unknown

providerMetadata:

ProviderMetadata | undefined

Use Cases

Logging and Debugging

import { generateText } from 'ai';
const result = await generateText({
model: openai('gpt-4o'),
prompt: 'Hello!',
experimental_onStart: event => {
console.log(`[${new Date().toISOString()}] Generation started`, {
model: event.model.modelId,
provider: event.model.provider,
});
},
onStepFinish: event => {
console.log(
`[${new Date().toISOString()}] Step ${event.stepNumber} finished`,
{
finishReason: event.finishReason,
tokens: event.usage.totalTokens,
},
);
},
onFinish: event => {
console.log(`[${new Date().toISOString()}] Generation complete`, {
totalSteps: event.steps.length,
totalTokens: event.totalUsage.totalTokens,
});
},
});

Tool Execution Monitoring

import { generateText } from 'ai';
const result = await generateText({
model: openai('gpt-4o'),
prompt: 'What is the weather?',
tools: { getWeather },
experimental_onToolCallStart: event => {
console.log(`Tool "${event.toolCall.toolName}" starting...`);
},
experimental_onToolCallFinish: event => {
if (event.success) {
console.log(
`Tool "${event.toolCall.toolName}" completed in ${event.durationMs}ms`,
);
} else {
console.error(`Tool "${event.toolCall.toolName}" failed:`, event.error);
}
},
});

Error Handling

Errors thrown inside callbacks are caught and do not break the generation flow. This ensures that monitoring code cannot disrupt your application:

const result = await generateText({
model: openai('gpt-4o'),
prompt: 'Hello!',
experimental_onStart: () => {
throw new Error('This error is caught internally');
// Generation continues normally
},
});