Traces and Spans¶
Traces and spans are the core building blocks for observability in Opik.
Concepts¶
- Trace: Represents a complete request or workflow through your application
- Span: Represents a single operation within a trace (e.g., an LLM call, tool execution)
Spans can be nested to show parent-child relationships, creating a tree structure that represents your application's execution flow.
Creating Traces¶
client, _ := opik.NewClient()
// Create a simple trace
trace, _ := client.Trace(ctx, "my-trace")
// Create a trace with options
trace, _ := client.Trace(ctx, "my-trace",
opik.WithTraceInput(map[string]any{"prompt": "Hello"}),
opik.WithTraceMetadata(map[string]any{"user_id": "123"}),
opik.WithTraceTags("production", "v2"),
)
// End the trace
trace.End(ctx)
// End with output
trace.End(ctx, opik.WithTraceOutput(map[string]any{"response": "Hi!"}))
Creating Spans¶
Spans are created from traces or other spans:
// Create a span from a trace
span, _ := trace.Span(ctx, "process-input")
// Create a nested span from another span
childSpan, _ := span.Span(ctx, "validate-input")
// End spans (in reverse order)
childSpan.End(ctx)
span.End(ctx)
Span Types¶
Use span types to categorize operations:
// LLM call span
span, _ := trace.Span(ctx, "llm-call",
opik.WithSpanType(opik.SpanTypeLLM),
opik.WithSpanModel("gpt-4"),
opik.WithSpanProvider("openai"),
)
// Tool execution span
span, _ := trace.Span(ctx, "search-tool",
opik.WithSpanType(opik.SpanTypeTool),
)
// General processing span (default)
span, _ := trace.Span(ctx, "process-data",
opik.WithSpanType(opik.SpanTypeGeneral),
)
Span Options¶
| Option | Description |
|---|---|
WithSpanType(type) |
Set span type (LLM, Tool, General) |
WithSpanModel(model) |
Set the model name for LLM spans |
WithSpanProvider(provider) |
Set the provider name (openai, anthropic) |
WithSpanInput(data) |
Set input data |
WithSpanOutput(data) |
Set output data |
WithSpanMetadata(data) |
Set metadata |
WithSpanTags(tags...) |
Add tags |
Complete Example¶
func processQuery(ctx context.Context, client *opik.Client, query string) (string, error) {
// Create trace for the entire request
trace, _ := client.Trace(ctx, "process-query",
opik.WithTraceInput(map[string]any{"query": query}),
)
defer trace.End(ctx)
// Span for preprocessing
preprocessSpan, _ := trace.Span(ctx, "preprocess")
processedQuery := preprocess(query)
preprocessSpan.End(ctx, opik.WithSpanOutput(map[string]any{
"processed": processedQuery,
}))
// Span for LLM call
llmSpan, _ := trace.Span(ctx, "llm-call",
opik.WithSpanType(opik.SpanTypeLLM),
opik.WithSpanModel("gpt-4"),
opik.WithSpanInput(map[string]any{"prompt": processedQuery}),
)
response, err := callLLM(processedQuery)
if err != nil {
llmSpan.End(ctx, opik.WithSpanMetadata(map[string]any{"error": err.Error()}))
return "", err
}
llmSpan.End(ctx, opik.WithSpanOutput(map[string]any{"response": response}))
// Update trace with final output
trace.End(ctx, opik.WithTraceOutput(map[string]any{"result": response}))
return response, nil
}
Updating Traces and Spans¶
Update metadata after creation:
// Update trace
trace.Update(ctx,
opik.WithTraceMetadata(map[string]any{"status": "completed"}),
opik.WithTraceTags("success"),
)
// Update span
span.Update(ctx,
opik.WithSpanMetadata(map[string]any{"tokens": 150}),
)