Skip to main content

Event callbacks

The Go AI SDK provides per-call event callbacks that you can pass to GenerateText, StreamText, Embed, EmbedMany, and Rerank 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:

package main

import (
"context"
"fmt"
"log"

"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

func main() {
ctx := context.Background()
p := openai.New(openai.Config{APIKey: "your-api-key"})
model, err := p.LanguageModel("gpt-4o")
if err != nil {
log.Fatal(err)
}

result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Prompt: "What is the weather in San Francisco?",
OnStart: func(ctx context.Context, event ai.OnStartEvent) {
fmt.Println("Generation started:", event.ModelID)
},
OnFinishEvent: func(ctx context.Context, event ai.OnFinishEvent) {
fmt.Println("Total tokens:", event.TotalUsage.GetTotalTokens())
},
})
if err != nil {
log.Fatalf("Generation failed: %v", err)
}

fmt.Println(result.Text)
}

Available callbacks​

GenerateText / StreamText​

CallbackEvent typeDescription
OnStartOnStartEventCalled when generation begins, before any LLM calls.
OnStepStartOnStepStartEventCalled when a step (LLM call) begins, before the provider is called.
OnToolExecutionStartOnToolCallStartEventCalled when a tool's Execute function is about to run.
OnToolExecutionEndOnToolCallFinishEventCalled when a tool's Execute function completes or errors.
OnStepFinishEventOnStepFinishEventCalled when a step (LLM call) completes.
OnFinishEventOnFinishEventCalled when the entire generation completes (all steps finished).

All six callbacks receive (ctx context.Context, event <EventType>) and are panic-safe. They fire in addition to the legacy OnStepFinish and OnFinish callbacks. OnToolCallStart and OnToolCallFinish remain as deprecated aliases for OnToolExecutionStart and OnToolExecutionEnd.

Embed / EmbedMany​

CallbackEvent typeDescription
ExperimentalOnStartEmbedOnStartEventCalled before the embedding model is invoked.
ExperimentalOnFinishEmbedOnFinishEventCalled after the embedding model returns.

Embed callbacks receive (event <EventType>) without a context parameter.

Rerank​

CallbackEvent typeDescription
ExperimentalOnStartRerankOnStartEventCalled before the reranking model is invoked.
ExperimentalOnFinishRerankOnFinishEventCalled after the reranking model returns.

Rerank callbacks receive (event <EventType>) without a context parameter.

Event reference​

GenerateText / StreamText​

OnStartEvent​

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

FieldTypeDescription
ModelProviderstringThe provider name (e.g., "openai").
ModelIDstringThe model identifier (e.g., "gpt-4o").
SystemstringThe system message provided to the model.
PromptstringThe prompt string if using the Prompt option.
Messages[]types.MessageThe messages array if using the Messages option.
Tools[]types.ToolThe tools available for this generation.
Temperature*float64Sampling temperature.
MaxTokens*intMaximum number of tokens to generate.
TopP*float64Top-p (nucleus) sampling parameter.
TopK*intTop-k sampling parameter.
FrequencyPenalty*float64Frequency penalty.
PresencePenalty*float64Presence penalty.
StopSequences[]stringSequences that stop generation.
Seed*intRandom seed for reproducible generation.
ExperimentalContextinterface{}User-defined context flowing through the lifecycle.

OnStepStartEvent​

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

FieldTypeDescription
StepNumberint0-indexed step number.
ModelProviderstringThe provider name.
ModelIDstringThe model identifier.
SystemstringThe system message for this step.
Messages[]types.MessageThe messages sent to the model for this step.
Tools[]types.ToolThe tools available for this step.
PreviousSteps[]types.StepResultResults from previous steps (empty for the first step).
ExperimentalContextinterface{}User-defined context object.

OnToolCallStartEvent​

Called before a tool's Execute function runs.

FieldTypeDescription
ToolCallIDstringUnique identifier for this tool call.
ToolNamestringName of the tool being called.
Argsmap[string]anyInput arguments passed to the tool.
StepNumberint0-indexed step where this tool call occurs.
ModelProviderstringThe provider name.
ModelIDstringThe model identifier.
Messages[]types.MessageThe conversation messages at tool execution time.
ExperimentalContextinterface{}User-defined context object.

OnToolCallFinishEvent​

Called after a tool's Execute function completes or errors. Exactly one of Result or Error will be non-nil.

FieldTypeDescription
ToolCallIDstringUnique identifier for this tool call.
ToolNamestringName of the tool that was called.
Argsmap[string]anyInput arguments passed to the tool.
ResultanyThe tool's return value (nil on error).
ErrorerrorThe error from tool execution (nil on success).
DurationMsint64Execution time of the tool call in milliseconds.
StepNumberint0-indexed step where this tool call occurred.
ModelProviderstringThe provider name.
ModelIDstringThe model identifier.
Messages[]types.MessageThe conversation messages at tool execution time.
ExperimentalContextinterface{}User-defined context object.

OnStepFinishEvent​

Called after each step (LLM call) completes.

FieldTypeDescription
StepNumberint0-indexed step number.
ModelProviderstringThe provider name.
ModelIDstringThe model identifier.
TextstringThe generated text from this step.
ToolCalls[]types.ToolCallTool calls made during this step.
ToolResults[]types.ToolResultResults of tool calls from this step.
FinishReasontypes.FinishReasonWhy the generation finished ("stop", "length", "tool-calls", etc.).
Usagetypes.UsageToken usage for this step.
Warnings[]types.WarningWarnings from the provider.
ExperimentalContextinterface{}User-defined context object.

OnFinishEvent​

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

FieldTypeDescription
TextstringThe full generated text.
ToolCalls[]types.ToolCallTool calls aggregated across all steps.
ToolResults[]types.ToolResultTool results aggregated across all steps.
FinishReasontypes.FinishReasonWhy the final step finished.
Steps[]types.StepResultResults from all steps in the generation.
TotalUsagetypes.UsageAggregated token usage across all steps.
Warnings[]types.WarningWarnings aggregated across all steps.
ExperimentalContextinterface{}Final state of the user-defined context.

Embed / EmbedMany​

EmbedOnStartEvent​

Called when the embedding operation begins, before the embedding model is called. Both Embed and EmbedMany share the same event type; the OperationID field distinguishes them ("ai.embed" vs "ai.embedMany").

FieldTypeDescription
CallIDstringUnique identifier for this embed call.
OperationIDstringOperation type: "ai.embed" or "ai.embedMany".
ProviderstringThe embedding provider name.
ModelIDstringThe embedding model identifier.
Values[]stringThe input text(s) being embedded.
MaxRetriesintMaximum retries for failed requests.
Headersmap[string]stringAdditional HTTP headers sent with the request.
ProviderOptionsmap[string]interface{}Provider-specific options.
FunctionIDstringTelemetry function identifier.
Metadatamap[string]anyAdditional telemetry metadata.

EmbedOnFinishEvent​

Called when the embedding operation completes. For Embed, Embeddings contains a single vector. For EmbedMany, it contains one vector per input value.

FieldTypeDescription
CallIDstringMatches the CallID from the corresponding start event.
OperationIDstringOperation type: "ai.embed" or "ai.embedMany".
ProviderstringThe embedding provider name.
ModelIDstringThe embedding model identifier.
Value[]stringThe input text(s) that were embedded.
Embeddings[][]float64The resulting embedding vectors.
Usagetypes.EmbeddingUsageToken usage for the embedding operation.
Warnings[]types.WarningWarnings from the provider.
ProviderMetadatajson.RawMessageProvider-specific metadata.
Responses[]types.EmbeddingResponseHTTP response metadata (headers, body). Length 1 for Embed; one per batch chunk for EmbedMany.
FunctionIDstringTelemetry function identifier.
Metadatamap[string]anyAdditional telemetry metadata.

Rerank​

RerankOnStartEvent​

Called when the reranking operation begins, before the reranking model is called.

FieldTypeDescription
CallIDstringUnique identifier for this rerank call.
OperationIDstringAlways "ai.rerank".
ProviderstringThe reranking provider name.
ModelIDstringThe reranking model identifier.
QuerystringThe query to rerank documents against.
Documentsinterface{}The documents being reranked ([]string or []map[string]interface{}).
TopN*intNumber of top documents to return (nil means all).
MaxRetriesintMaximum retries for failed requests.
Headersmap[string]stringAdditional HTTP headers sent with the request.
ProviderOptionsmap[string]interface{}Provider-specific options.
FunctionIDstringTelemetry function identifier.

RerankOnFinishEvent​

Called when the reranking operation completes, after the reranking model returns.

FieldTypeDescription
CallIDstringMatches the CallID from the corresponding start event.
OperationIDstringAlways "ai.rerank".
ProviderstringThe reranking provider name.
ModelIDstringThe reranking model identifier.
Documentsinterface{}The documents that were reranked.
QuerystringThe query that documents were reranked against.
Ranking[]ai.RerankItemReranked results sorted by relevance score (descending). Each item has OriginalIndex, Score, and Document.
Warnings[]types.WarningWarnings from the provider.
Responsetypes.RerankResponseResponse metadata (id, timestamp, modelId, headers).
ProviderMetadatajson.RawMessageProvider-specific metadata.
FunctionIDstringTelemetry function identifier.

Use cases​

Logging and debugging​

Track the full lifecycle of a generation with timestamps:

package main

import (
"context"
"fmt"
"log"
"time"

"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

func main() {
ctx := context.Background()
p := openai.New(openai.Config{APIKey: "your-api-key"})
model, err := p.LanguageModel("gpt-4o")
if err != nil {
log.Fatal(err)
}

result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Prompt: "Hello!",
OnStart: func(ctx context.Context, event ai.OnStartEvent) {
fmt.Printf("[%s] Generation started: model=%s provider=%s\n",
time.Now().Format(time.RFC3339), event.ModelID, event.ModelProvider)
},
OnStepFinishEvent: func(ctx context.Context, event ai.OnStepFinishEvent) {
fmt.Printf("[%s] Step %d finished: reason=%s tokens=%d\n",
time.Now().Format(time.RFC3339), event.StepNumber,
event.FinishReason, event.Usage.GetTotalTokens())
},
OnFinishEvent: func(ctx context.Context, event ai.OnFinishEvent) {
fmt.Printf("[%s] Generation complete: steps=%d totalTokens=%d\n",
time.Now().Format(time.RFC3339), len(event.Steps),
event.TotalUsage.GetTotalTokens())
},
})
if err != nil {
log.Fatalf("Generation failed: %v", err)
}

fmt.Println(result.Text)
}

Tool execution monitoring​

Track tool call durations and detect failures:

package main

import (
"context"
"fmt"
"log"

"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

func main() {
ctx := context.Background()
p := openai.New(openai.Config{APIKey: "your-api-key"})
model, err := p.LanguageModel("gpt-4o")
if err != nil {
log.Fatal(err)
}

result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Prompt: "What is the weather?",
// Tools: []types.Tool{getWeather},
OnToolExecutionStart: func(ctx context.Context, event ai.OnToolCallStartEvent) {
fmt.Printf("Tool %q starting...\n", event.ToolName)
},
OnToolExecutionEnd: func(ctx context.Context, event ai.OnToolCallFinishEvent) {
if event.Error == nil {
fmt.Printf("Tool %q completed in %dms\n",
event.ToolName, event.DurationMs)
} else {
fmt.Printf("Tool %q failed: %v\n",
event.ToolName, event.Error)
}
},
})
if err != nil {
log.Fatalf("Generation failed: %v", err)
}

fmt.Println(result.Text)
}

Embedding observability​

Monitor embedding operations with token usage tracking:

package main

import (
"context"
"fmt"
"log"

"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

func main() {
ctx := context.Background()
p := openai.New(openai.Config{APIKey: "your-api-key"})
model, err := p.EmbeddingModel("text-embedding-3-small")
if err != nil {
log.Fatal(err)
}

result, err := ai.EmbedMany(ctx, ai.EmbedManyOptions{
Model: model,
Inputs: []string{"sunny day at the beach", "rainy afternoon in the city"},
ExperimentalOnStart: func(event ai.EmbedOnStartEvent) {
fmt.Printf("Embedding started (%s): model=%s values=%d\n",
event.OperationID, event.ModelID, len(event.Values))
},
ExperimentalOnFinish: func(event ai.EmbedOnFinishEvent) {
fmt.Printf("Embedding complete (%s): tokens=%d vectors=%d\n",
event.OperationID, event.Usage.TotalTokens, len(event.Embeddings))
},
})
if err != nil {
log.Fatalf("Embedding failed: %v", err)
}

fmt.Printf("Generated %d embeddings\n", len(result.Embeddings))
}

Error handling​

Errors that occur inside callbacks are caught internally and do not break the generation, embedding, or reranking flow. The Notify helper wraps each callback invocation in a deferred recover, so panics are silently absorbed. This ensures that monitoring code cannot disrupt your application:

result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Prompt: "Hello!",
OnStart: func(ctx context.Context, event ai.OnStartEvent) {
panic("this panic is recovered internally")
// Generation continues normally
},
})
// err is nil — the callback panic did not propagate

If you need to capture callback errors for debugging, handle them within the callback itself (e.g., log them or send them to an error tracking service).