Skip to main content

Callback Events

The Go-AI SDK fires structured lifecycle events during text generation and agent execution. Each event is a typed struct delivered via callback fields on GenerateTextOptions, StreamTextOptions, and AgentConfig.

GenerateTextOptions, StreamTextOptions, AgentGenerateOptions, and AgentConfig use the TypeScript-aligned tool callback names OnToolExecutionStart and OnToolExecutionEnd. The event structs keep their Go names, OnToolCallStartEvent and OnToolCallFinishEvent.

All callbacks are optional and panic-safe — a panicking callback never aborts generation.

Event Types​

OnStartEvent​

Fired once at the beginning of a GenerateText or StreamText call, before any LLM request is made. Each struct below shows selected fields; see go doc ./pkg/ai <Type> for the complete list.

type OnStartEvent struct {
CallID string // correlates this event with other events for the same call
ModelProvider string
ModelID string
Prompt string
System string // Deprecated: use Instructions
Messages []types.Message
Tools []types.Tool
// ... generation parameters, ProviderOptions, RuntimeContext, etc.
}
FieldDescription
CallIDUnique ID for this generation call; correlates OnStart/OnStepStart/OnStepFinish/OnFinish events
ModelProviderProvider name (e.g., "openai", "anthropic")
ModelIDModel identifier (e.g., "gpt-4o")
PromptThe user prompt string
SystemThe system prompt string (deprecated alias for Instructions)
MessagesInitial conversation messages
ToolsTools available for the call

OnStepStartEvent​

Fired at the beginning of each generation step (each LLM call in a multi-step tool loop).

type OnStepStartEvent struct {
CallID string
StepNumber int // 0-indexed
Messages []types.Message
Tools []types.Tool
// ... ToolChoice, ActiveTools, Steps (prior step results), etc.
}
FieldDescription
CallIDCorrelates this event with the other events for this call
StepNumber0-indexed step counter
MessagesMessages sent to the LLM for this step
ToolsTools available for this step

OnToolCallStartEvent​

Fired before each individual tool is executed. Use this event with OnToolExecutionStart.

type OnToolCallStartEvent struct {
CallID string
ToolCallID string
ToolName string
ToolCall types.ToolCall
Messages []types.Message
// Args and StepNumber are deprecated; use ToolCall.Arguments and
// correlate with step events via CallID instead.
}
FieldDescription
CallIDCorrelates this event with the generation call that triggered it
ToolCallIDUnique identifier for this tool call
ToolNameName of the tool being called
ToolCallThe full tool call, including Arguments

OnToolCallFinishEvent​

Fired after each tool completes (success or failure). Use this event with OnToolExecutionEnd.

type OnToolCallFinishEvent struct {
CallID string
ToolCallID string
ToolName string
ToolCall types.ToolCall
ToolOutput types.ToolResult // exactly one of ToolOutput.Result / ToolOutput.Error is meaningful
ToolExecutionMs int64
Messages []types.Message
// Result, Error, Args, StepNumber, DurationMs are deprecated aliases
// for ToolOutput.Result, ToolOutput.Error, ToolCall.Arguments,
// CallID-based correlation, and ToolExecutionMs respectively.
}
FieldDescription
ToolOutputTool result; ToolOutput.Result is non-nil on success, ToolOutput.Error on failure
ToolExecutionMsTool execution time in milliseconds
Result (deprecated)Tool output on success (nil on failure)
Error (deprecated)Execution error on failure (nil on success)

OnStepFinishEvent​

Fired after each generation step completes, including after all tools for that step have been executed.

type OnStepFinishEvent struct {
CallID string
StepNumber int // 0-indexed
Text string
ToolCalls []types.ToolCall
ToolResults []types.ToolResult
FinishReason types.FinishReason
Usage types.Usage
Warnings []types.Warning
// ... Reasoning, Sources, Files, ProviderMetadata, Request, Response, etc.
}
FieldDescription
StepNumber0-indexed step counter
TextLLM text output for this step
ToolCallsTool calls made during this step
ToolResultsResults from tool executions in this step
FinishReasonWhy the LLM stopped (e.g., ToolCalls, Stop)
UsageToken usage for this step

OnFinishEvent​

Fired once when generation completes (after all steps).

type OnFinishEvent struct {
CallID string
Text string
ToolCalls []types.ToolCall
ToolResults []types.ToolResult
FinishReason types.FinishReason
Steps []types.StepResult
Usage types.Usage // usage of the final step
TotalUsage types.Usage // sum across all steps
Output interface{} // parsed structured output, if configured
// ... Reasoning, Sources, Files, Warnings, ProviderMetadata, etc.
}
FieldDescription
TextFinal generated text
ToolResultsTool results from the final step
FinishReasonFinal finish reason
StepsAll steps taken during generation
TotalUsageAggregated token usage across all steps

Notify Utility​

The SDK exposes the Notify function used internally for safe event dispatch:

// Listener is a function that receives a typed event.
type Listener[E any] func(ctx context.Context, event E)

// Notify safely dispatches event to each listener.
// Panics in any listener are recovered and do not propagate.
func Notify[E any](ctx context.Context, event E, listeners ...Listener[E])

Notify is useful for fan-out scenarios where you want to dispatch the same event to multiple independent listeners.


Usage​

With GenerateText​

result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Prompt: "What is 2+2?",
Tools: []types.Tool{calculatorTool},
StopWhen: []ai.StopCondition{ai.IsStepCount(5)},
OnStart: func(ctx context.Context, e ai.OnStartEvent) {
log.Printf("[start] model=%s/%s prompt=%q", e.ModelProvider, e.ModelID, e.Prompt)
},
OnStepStart: func(ctx context.Context, e ai.OnStepStartEvent) {
log.Printf("[step %d start] messages=%d", e.StepNumber, len(e.Messages))
},
OnToolExecutionStart: func(ctx context.Context, e ai.OnToolCallStartEvent) {
log.Printf("[tool start] %s args=%v", e.ToolName, e.Args)
},
OnToolExecutionEnd: func(ctx context.Context, e ai.OnToolCallFinishEvent) {
if e.Error != nil {
log.Printf("[tool error] %s: %v", e.ToolName, e.Error)
} else {
log.Printf("[tool finish] %s result=%v", e.ToolName, e.Result)
}
},
OnStepFinishEvent: func(ctx context.Context, e ai.OnStepFinishEvent) {
log.Printf("[step %d finish] finish_reason=%s tokens=%d",
e.StepNumber, e.FinishReason, safeTokens(e.Usage.TotalTokens))
},
OnFinishEvent: func(ctx context.Context, e ai.OnFinishEvent) {
log.Printf("[finish] steps=%d total_tokens=%d text=%q",
len(e.Steps), safeTokens(e.TotalUsage.TotalTokens), e.Text)
},
})

With AgentConfig (ToolLoopAgent)​

Callbacks on AgentConfig fire for every call to Execute. They are merged with any per-call callbacks:

agent := agent.NewToolLoopAgent(agent.AgentConfig{
Model: model,
Tools: tools,
StopWhen: []ai.StopCondition{ai.IsStepCount(10)},
OnStart: func(ctx context.Context, e ai.OnStartEvent) {
log.Printf("Agent starting: model=%s", e.ModelID)
},
OnToolExecutionStart: func(ctx context.Context, e ai.OnToolCallStartEvent) {
log.Printf("Tool call: %s", e.ToolName)
},
OnToolExecutionEnd: func(ctx context.Context, e ai.OnToolCallFinishEvent) {
if e.Error != nil {
log.Printf("Tool failed: %s: %v", e.ToolName, e.Error)
}
},
OnFinishEvent: func(ctx context.Context, e ai.OnFinishEvent) {
log.Printf("Agent done: %d steps", len(e.Steps))
},
})

Event Ordering​

For a single GenerateText call with one tool call, the event order is:

OnStart
└── Step 1
├── OnStepStart
├── OnToolExecutionStart (for each tool execution)
├── OnToolExecutionEnd (for each tool execution)
└── OnStepFinish
└── Step 2
├── OnStepStart
└── OnStepFinish
OnFinish

Callback Merging in Agents​

When using ToolLoopAgent, callbacks set in AgentConfig are merged with any per-call callbacks. Both fire in order: settings-level first, then call-level.

This is useful for attaching persistent observability (logging, metrics) at the agent level while allowing per-request overrides.


See Also​