Documentation
¶
Overview ¶
Package langfuse provides observation-centric Langfuse tracing on top of OpenTelemetry.
It exports OTLP/HTTP protobuf traces to Langfuse and can either own an isolated tracer provider or attach a Langfuse processor to an existing OpenTelemetry SDK tracer provider through Config. The package never changes global OpenTelemetry state.
Construct a Client with New, typically from ConfigFromEnv, stamp request-scoped identity with Client.WithTraceAttributes, and record work with Client.Observe:
lf, err := langfuse.New(ctx, langfuse.ConfigFromEnv())
if err != nil {
return err
}
defer lf.Shutdown(shutdownCtx)
err = lf.Observe(ctx, "answer-question", langfuse.TypeGeneration,
langfuse.ObservationAttributes{Input: question},
func(ctx context.Context, o *langfuse.Observation) error {
answer, err := callModel(ctx, question)
o.Update(langfuse.ObservationAttributes{Output: answer})
return err
})
Client.StartObservation is the lower-level pair for lifetimes that span functions; the context it returns carries the parent-child relationship, so observations started from it nest under their parent. Client.RecordScore submits evaluations and user feedback through the Langfuse REST scores endpoint, and Client.Flush and Client.Shutdown control the export lifecycle. A nil or disabled Client and the zero Observation are safe no-ops.
Index ¶
- type Client
- func (c *Client) Event(ctx context.Context, name string, values ObservationAttributes)
- func (c *Client) Flush(ctx context.Context) error
- func (c *Client) Observe(ctx context.Context, name string, observationType ObservationType, ...) error
- func (c *Client) RecordScore(ctx context.Context, score Score) error
- func (c *Client) Shutdown(ctx context.Context) error
- func (c *Client) StartObservation(ctx context.Context, name string, observationType ObservationType, ...) (context.Context, *Observation)
- func (c *Client) WithTraceAttributes(ctx context.Context, values TraceAttributes) context.Context
- type Config
- type Level
- type Observation
- type ObservationAttributes
- type ObservationType
- type PromptRef
- type Score
- type ScoreDataType
- type TraceAttributes
- type Usage
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client owns all Langfuse exporter, processor, and lifecycle state. Its zero value is a safe no-op.
func New ¶
New constructs a client and validates configuration without performing network I/O. A disabled configuration bypasses all other validation.
Example ¶
package main
import (
"context"
"log"
"time"
"github.com/fgn/go-langfuse"
)
func main() {
ctx := context.Background()
lf, err := langfuse.New(ctx, langfuse.Config{
BaseURL: "https://cloud.langfuse.com",
PublicKey: "pk-lf-...",
SecretKey: "sk-lf-...",
Environment: "production",
})
if err != nil {
log.Fatal(err)
}
defer func() {
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = lf.Shutdown(shutdownCtx)
}()
_, observation := lf.StartObservation(ctx, "nightly-import", langfuse.TypeSpan,
langfuse.ObservationAttributes{Input: "42 records"})
observation.End()
}
Output:
func (*Client) Event ¶
func (c *Client) Event(ctx context.Context, name string, values ObservationAttributes)
Event records an instantaneous event observation.
func (*Client) Flush ¶
Flush exports all ended observations and delivers every score accepted before the call; scores recorded after Flush begins do not extend the wait. A score mid-retry holds Flush until it is delivered or dropped, bounded by ctx.
Example ¶
package main
import (
"context"
"log"
"time"
"github.com/fgn/go-langfuse"
)
func main() {
ctx := context.Background()
lf, err := langfuse.New(ctx, langfuse.ConfigFromEnv())
if err != nil {
log.Fatal(err)
}
lf.Event(ctx, "job-completed", langfuse.ObservationAttributes{
Metadata: map[string]any{"records": 42},
})
// Short-lived jobs and serverless handlers flush before returning so
// batched observations are exported while the client stays usable.
flushCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := lf.Flush(flushCtx); err != nil {
log.Printf("flush: %v", err)
}
}
Output:
func (*Client) Observe ¶
func (c *Client) Observe( ctx context.Context, name string, observationType ObservationType, values ObservationAttributes, fn func(ctx context.Context, observation *Observation) error, ) error
Observe starts an observation, runs fn with the child context and the observation handle, and always ends the observation, including when fn panics. A non-nil error returned by fn is recorded through Observation.RecordError and returned unchanged, so fn does not need to record it itself. When fn panics, the observation is marked failed with the payload-free status "panic" — the panic value is never captured — and the panic propagates. On a nil, disabled, or stopped client fn still runs, receiving ctx unchanged and a no-op handle. A nil fn reports a diagnostic and starts no observation.
Example ¶
package main
import (
"context"
"log"
"github.com/fgn/go-langfuse"
)
func main() {
ctx := context.Background()
lf, err := langfuse.New(ctx, langfuse.ConfigFromEnv())
if err != nil {
log.Fatal(err)
}
err = lf.Observe(ctx, "retrieve-documents", langfuse.TypeRetriever,
langfuse.ObservationAttributes{Input: "safe api design"},
func(ctx context.Context, observation *langfuse.Observation) error {
// Child observations started from ctx nest under this one. The
// observation always ends, a returned error is recorded on it,
// and a panic is marked as a failure before it propagates.
documents, err := retrieve(ctx, "safe api design")
if err != nil {
return err
}
observation.Update(langfuse.ObservationAttributes{Output: documents})
return nil
})
if err != nil {
log.Printf("retrieve: %v", err)
}
}
func retrieve(ctx context.Context, query string) ([]string, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
return []string{"doc-" + query}, nil
}
Output:
func (*Client) RecordScore ¶
RecordScore submits one score through the Langfuse REST scores endpoint using the client's credentials and environment. The score is validated synchronously — every returned error marks a score that was not accepted — and then queued for asynchronous delivery with bounded retry (network errors and HTTP 408, 429, and 5xx responses, using the same backoff defaults as observation export), so transport failures never reach the caller: after the retry budget they are reported as payload-free OpenTelemetry diagnostics and the score is dropped. Client.Flush and Client.Shutdown drain accepted scores. When the queue is full a score is dropped with a diagnostic unless Config.BlockOnQueueFull waits for space, bounded by ctx. A disabled client returns nil without sending, and a shut-down client returns an error. The complete serialized score is limited to 128 KiB.
Example ¶
package main
import (
"context"
"log"
"github.com/fgn/go-langfuse"
)
func main() {
ctx := context.Background()
lf, err := langfuse.New(ctx, langfuse.ConfigFromEnv())
if err != nil {
log.Fatal(err)
}
rating := 4.0
err = lf.RecordScore(ctx, langfuse.Score{
ID: "feedback-42", // idempotent upsert key
Name: "user-feedback",
SessionID: "conversation-456",
NumericValue: &rating,
Comment: "clear and concise answer",
})
if err != nil {
log.Printf("record score: %v", err)
}
}
Output:
func (*Client) Shutdown ¶
Shutdown permanently stops this Client after draining queued observations and scores, bounded by ctx. In borrowed mode it shuts down the Langfuse processor using ctx before unregistering it; the provider and all unrelated processors remain owned by the application.
func (*Client) StartObservation ¶
func (c *Client) StartObservation( ctx context.Context, name string, observationType ObservationType, values ObservationAttributes, ) (context.Context, *Observation)
StartObservation starts an observation. Child work must use the returned context to preserve the parent-child relationship.
Example ¶
package main
import (
"context"
"fmt"
"log"
"github.com/fgn/go-langfuse"
)
func main() {
ctx := context.Background()
lf, err := langfuse.New(ctx, langfuse.ConfigFromEnv())
if err != nil {
log.Fatal(err)
}
// StartObservation suits lifetimes that span functions, such as ending a
// generation only after a stream is fully consumed.
generationCtx, generation := lf.StartObservation(ctx, "generate-answer",
langfuse.TypeGeneration, langfuse.ObservationAttributes{
Model: "gemini-2.5-flash",
Input: "What is context in Go?",
})
defer generation.End()
answer, usage, err := callModel(generationCtx, "What is context in Go?")
if err != nil {
generation.RecordError(err)
return
}
generation.Update(langfuse.ObservationAttributes{Output: answer, Usage: &usage})
}
func callModel(ctx context.Context, question string) (string, langfuse.Usage, error) {
if err := ctx.Err(); err != nil {
return "", langfuse.Usage{}, err
}
return fmt.Sprintf("answer to %q", question), langfuse.Usage{
InputTokens: 6,
OutputTokens: 8,
}, nil
}
Output:
func (*Client) WithTraceAttributes ¶
WithTraceAttributes returns a context that propagates request-scoped trace fields to observations subsequently started from it. If an SDK observation belonging to this Client is active, it is updated immediately as well.
Example ¶
package main
import (
"context"
"log"
"github.com/fgn/go-langfuse"
)
func main() {
ctx := context.Background()
lf, err := langfuse.New(ctx, langfuse.ConfigFromEnv())
if err != nil {
log.Fatal(err)
}
// Stamp request-scoped identity once; observations started from the
// returned context share the same trace name, user, session, and tags.
ctx = lf.WithTraceAttributes(ctx, langfuse.TraceAttributes{
Name: "chat-turn",
UserID: "user-123",
SessionID: "conversation-456",
Tags: []string{"chat"},
})
observationCtx, observation := lf.StartObservation(ctx, "chat-turn",
langfuse.TypeAgent, langfuse.ObservationAttributes{})
defer observation.End()
_ = observationCtx
}
Output:
type Config ¶
type Config struct {
// BaseURL is the Langfuse host or OTLP traces endpoint. It defaults to
// https://cloud.langfuse.com.
BaseURL string
// PublicKey identifies the Langfuse project.
PublicKey string
// SecretKey authenticates writes to the Langfuse project.
SecretKey string
// Environment and Release are stamped on every span observed by the
// Langfuse processor.
Environment string
Release string
// ServiceName overrides service.name on an SDK-owned provider. When empty,
// the standard OpenTelemetry resource.Default value is retained, including
// OTEL_SERVICE_NAME. It is ignored when TracerProvider is set.
ServiceName string
// TracerProvider attaches the Langfuse processor to a caller-owned provider. The client
// never replaces the global provider or shuts this provider down.
TracerProvider *sdktrace.TracerProvider
// MaxQueueSize bounds how many ended export-eligible spans the client
// buffers in memory while waiting for batch export. Zero selects the
// default of 2048; negative values are a validation error in [New].
MaxQueueSize int
// BlockOnQueueFull makes ending an exported observation, and recording a
// score, wait for buffer space instead of dropping when the
// corresponding queue is full. A sustained export outage can then stall
// goroutines that end observations or record scores, so it defaults to
// false: on a full queue new work is dropped with a diagnostic, matching
// OpenTelemetry defaults.
BlockOnQueueFull bool
// Disabled makes the complete client a safe no-op.
Disabled bool
// DisableContentCapture removes Input and Output supplied through
// ObservationAttributes. It does not remove content emitted by third-party
// OpenTelemetry instrumentation. Identifiers, metadata, model data, and
// usage are still recorded.
DisableContentCapture bool
// Mask applies only to Input, Output, and Metadata supplied through this
// Client. Each metadata map is passed as one complete value and must remain
// a map[string]any to be retained. It does not process identifiers, model
// fields, StatusMessage, [Observation.RecordError] text, or third-party
// spans and events.
// Mask may be called concurrently and therefore must be concurrency-safe.
// A panic is recovered and the affected value is omitted.
Mask func(value any) any
// contains filtered or unexported fields
}
Config configures a Langfuse client.
func ConfigFromEnv ¶
func ConfigFromEnv() Config
ConfigFromEnv returns configuration from the seven documented LANGFUSE_* variables. It intentionally does not read the deprecated LANGFUSE_HOST.
Example ¶
package main
import (
"context"
"log"
"time"
"github.com/fgn/go-langfuse"
)
func main() {
// ConfigFromEnv reads LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY,
// LANGFUSE_BASE_URL, LANGFUSE_TRACING_ENVIRONMENT, LANGFUSE_RELEASE,
// LANGFUSE_TRACING_ENABLED, and LANGFUSE_CONTENT_CAPTURE_ENABLED.
lf, err := langfuse.New(context.Background(), langfuse.ConfigFromEnv())
if err != nil {
log.Fatal(err)
}
defer func() {
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = lf.Shutdown(shutdownCtx)
}()
}
Output:
type Observation ¶
type Observation struct {
// contains filtered or unexported fields
}
Observation is a concurrency-safe handle around one OpenTelemetry span. Its zero value is a safe no-op.
func (*Observation) ID ¶
func (o *Observation) ID() string
ID returns the lowercase 16-character OpenTelemetry span ID, or an empty string for a no-op observation.
func (*Observation) RecordError ¶
func (o *Observation) RecordError(err error)
RecordError records an exception and marks the observation as failed. It does not end the observation. At most eight exception events are retained; later calls are omitted with one diagnostic. The error text is explicitly supplied content and is not processed by Config.Mask. Invalid UTF-8 or text over 64 KiB is replaced by the payload-free string "error".
func (*Observation) TraceID ¶
func (o *Observation) TraceID() string
TraceID returns the lowercase 32-character OpenTelemetry trace ID, or an empty string for a no-op observation.
func (*Observation) Update ¶
func (o *Observation) Update(values ObservationAttributes)
Update merges non-zero fields into an active observation. Structured model, usage, and cost maps replace their complete serialized attributes when set; metadata merges by top-level key. StartTime cannot be changed after start and is ignored with a diagnostic.
type ObservationAttributes ¶
type ObservationAttributes struct {
// Input and Output are explicit content. They are never inferred by the
// SDK, are subject to Config.DisableContentCapture, and pass through Mask.
Input any
Output any
// Metadata merges by top-level key across Update calls and passes through
// Mask. Empty maps are ignored and cannot clear earlier metadata. At most 32
// distinct keys are retained over an observation's lifetime; keys are
// limited to 200 bytes.
Metadata map[string]any
Level Level
// StatusMessage is explicit telemetry content, is limited to 16 KiB, and
// does not pass through Mask. Sanitize it before calling the SDK.
StatusMessage string
// Version overrides a propagated TraceAttributes.Version on this
// observation only.
Version string
// The remaining fields are honored only by generation and embedding
// observations, except Prompt, which Langfuse links only on generations.
Model string
ModelParameters map[string]any
Usage *Usage
CostDetails map[string]float64
Prompt *PromptRef
CompletionStartTime time.Time
// StartTime is honored only by StartObservation; Event and Update ignore
// it with a diagnostic.
StartTime time.Time
}
ObservationAttributes contains fields that can be set when an observation starts or in a later Update. Zero values are ignored and updates never clear an already recorded value. Each serialized structured value is limited to 1 MiB and the observation-level payload attributes are limited to 2 MiB in aggregate over the observation's current values. Separately bounded trace/client propagation and exception events sit outside that aggregate. Oversized fields are omitted with a payload-free OpenTelemetry diagnostic.
type ObservationType ¶
type ObservationType string
ObservationType identifies how Langfuse presents an observation.
const ( TypeSpan ObservationType = "span" TypeGeneration ObservationType = "generation" TypeEvent ObservationType = "event" TypeEmbedding ObservationType = "embedding" TypeAgent ObservationType = "agent" TypeTool ObservationType = "tool" TypeChain ObservationType = "chain" TypeRetriever ObservationType = "retriever" TypeEvaluator ObservationType = "evaluator" TypeGuardrail ObservationType = "guardrail" )
type Score ¶
type Score struct {
// ID makes submissions idempotent: Langfuse upserts scores by ID.
// Optional; the SDK generates a random ID when empty so retried
// deliveries cannot create duplicates.
ID string
// Name identifies the score series, for example "user-feedback".
// Required; at most 200 characters.
Name string
// TraceID, SessionID, and ObservationID select the score target. At
// least one of TraceID or SessionID is required, and ObservationID
// additionally requires TraceID.
TraceID string
SessionID string
ObservationID string
// Exactly one of NumericValue or StringValue must be set. Boolean
// scores use NumericValue 0 or 1 with ScoreTypeBoolean.
NumericValue *float64
StringValue *string
// DataType is optional; when empty, Langfuse infers NUMERIC or
// CATEGORICAL from the value type.
DataType ScoreDataType
// Comment is explicit content supplied by the caller. It is not
// processed by Config.Mask; sanitize it before calling the SDK.
Comment string
// Metadata is serialized as one JSON value.
Metadata map[string]any
}
Score is one evaluation or feedback value attached to a trace, a session, or an observation. Scores are submitted through the Langfuse REST API rather than the OpenTelemetry trace pipeline.
type ScoreDataType ¶
type ScoreDataType string
ScoreDataType identifies how Langfuse stores and aggregates a score value.
const ( ScoreTypeBoolean ScoreDataType = "BOOLEAN" ScoreTypeCategorical ScoreDataType = "CATEGORICAL" ScoreTypeCorrection ScoreDataType = "CORRECTION" ScoreTypeNumeric ScoreDataType = "NUMERIC" ScoreTypeText ScoreDataType = "TEXT" )
type TraceAttributes ¶
type TraceAttributes struct {
Name string
UserID string
SessionID string
// Tags merge in caller order, de-duplicate, and retain at most 64 values
// and 16 KiB of UTF-8 data over one trace context.
Tags []string
// Metadata merges by top-level key and retains at most 32 distinct keys in
// one trace context so required observation fields remain within default
// OpenTelemetry span limits. Keys are limited to 200 bytes.
Metadata map[string]any
Version string
}
TraceAttributes are request-scoped fields copied to an active SDK observation and to spans subsequently started from the returned context.
type Usage ¶
type Usage struct {
InputTokens int64
OutputTokens int64
CacheReadInputTokens int64
CacheCreationInputTokens int64
ReasoningOutputTokens int64
// Details contains additional provider-specific subsets. Keys prefixed
// input_ or output_ are subtracted from the corresponding base bucket.
// Such buckets must be mutually exclusive, must not overlap the typed
// cache/reasoning fields, and must be subsets of their inclusive total. The
// canonical input, output, total, cache, and reasoning keys are reserved.
// At most 64 provider-specific detail buckets are retained, with keys
// limited to 200 bytes.
Details map[string]int64
}
Usage records inclusive provider token counts. InputTokens includes cache tokens, and OutputTokens includes reasoning tokens. The SDK normalizes these values to Langfuse's exclusive usage_details representation while total remains the checked sum of the inclusive input and output totals.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
contrib
|
|
|
googlegenai
module
|
|
|
openai
module
|
|
|
examples
|
|
|
existingotel
command
|
|
|
quickstart
command
|
|
|
shortlived
command
|
|
|
streaming
command
|
|
|
internal
|
|
|
attributes
Package attributes owns Langfuse's OpenTelemetry attribute contract.
|
Package attributes owns Langfuse's OpenTelemetry attribute contract. |
|
diagnostic
Package diagnostic reports payload-free SDK diagnostics through OpenTelemetry.
|
Package diagnostic reports payload-free SDK diagnostics through OpenTelemetry. |
|
otlpreceiver
Package otlpreceiver provides a local OTLP/HTTP protobuf receiver for tests.
|
Package otlpreceiver provides a local OTLP/HTTP protobuf receiver for tests. |
|
processor
Package processor contains the Langfuse OpenTelemetry span processor.
|
Package processor contains the Langfuse OpenTelemetry span processor. |