Langfuse Integration¶
Langfuse observability is optional and decoupled via the event system: it is not injected by default.
When injected, telemetry flows through two coordinated pathways:
- Run-level root span and session grouping via
run_observability_context, which bundles all nested phase spans, component traces, and LLM generations into a searchable session in the Langfuse dashboard. - Event-level observations via
LangfuseObserver, which translates domain events into spans, LLM completions into native generations (with full prompt, response, latency, and token metrics), and evaluation metrics into scores.
Setup & Injection¶
To use Langfuse:
- Configure credentials: Set
LANGFUSE_PUBLIC_KEY,LANGFUSE_SECRET_KEY, and optionallyLANGFUSE_HOSTin your environment or.envfile. - Inject the observer: Instantiate
LangfuseObserverand register it on yourEventEmitter(such asSimpleEventEmitter). If Langfuse is not needed, simply omit this step. - Wrap in root context (optional): Wrap the pipeline execution in
run_observability_contextto correlate all traces under a unified root span and session ID.
import os
from pipeline import Pipeline
from pipeline.events import SimpleEventEmitter, LangfuseObserver
from pipeline.runs.observability import run_observability_context
# 1. Instantiate the event emitter and inject LangfuseObserver
emitter = SimpleEventEmitter()
try:
langfuse_observer = LangfuseObserver()
emitter.register_observer(langfuse_observer)
except RuntimeError:
# Safely skipped if the optional langfuse package is not installed
pass
# 2. Optionally wrap execution to bind root session and trace context
with run_observability_context(
name="pipeline.run",
session_id="experiment-kg-01", # Groups traces into a searchable session
run_id="run-123", # Correlates trace and events
version="0.1.0",
tags=["dataset:scierc", "mode:production"],
):
pipeline = Pipeline(config=config, event_emitter=emitter)
# execute pipeline...
Observation Mapping¶
LangfuseObserver listens for events emitted across the pipeline lifecycle and maps them to corresponding Langfuse observation primitives:
Pipeline Lifecycle & Components¶
| Pipeline Event | Observation Type | Observation Name | Captured Fields & Metadata |
|---|---|---|---|
ComponentStarted |
span |
component.<name> |
Component name, input description |
ComponentCompleted |
span |
component.<name> |
Execution duration (duration_seconds), success status, output description |
PhaseCompleted |
span |
phase.<name> |
Artifact count, duration (duration_seconds), success status, error message |
LLM & Embedding Generations¶
| Pipeline Event | Observation Type | Observation Name | Captured Fields & Metadata |
|---|---|---|---|
LLMGenerationCompleted |
generation |
llm.<operation> |
Model name, model parameters, prompt input, JSON/text completion output, token usage (prompt_tokens, completion_tokens, total_tokens), latency, cache status, prompt metadata (prompt_name, prompt_version, prompt_label) |
EmbeddingGenerationCompleted |
generation |
embed.<operation> |
Model name, batch size (text_count), total characters, token usage (prompt_tokens, total_tokens), vector dimension (vector_dim), latency, cache status |
LLMDurationMeasured |
generation |
llm.<operation> |
Fallback latency measurement if detailed generation event is unavailable |
Phase Domain Spans¶
| Pipeline Event | Phase | Observation Type | Observation Name | Captured Fields & Metadata |
|---|---|---|---|---|
ChunksGenerated |
Phase 1 (Chunking) | span |
phase1.chunking |
document_id, document_title, chunk_count, token_count |
EntityLinkingCandidatesRetrieved |
Phase 2 (Linking) | span |
phase2.linking.candidates |
mention_id, mention_name, candidate_count, top candidate list |
EntityLinkingReranked |
Phase 2 (Linking) | span |
phase2.linking.rerank |
mention_id, mention_name, candidate_id, candidate_name, cross-encoder score, accepted flag, threshold |
DenseCandidatesGenerated |
Phase 3 (Dense Extract) | span |
phase3.dense.candidates |
entities_count, candidate_count, top candidate pairs |
RerankerScoreAssigned |
Phase 3 (Dense Extract) | span |
phase3.dense.rerank |
candidate_pair, entity_a, entity_b, payloads, reranker score, accepted flag, threshold, recovery attempts |
LLMRelationDecoded |
Phase 3 (Dense Extract) | span |
phase3.dense.llm_extract |
candidate_pair, extracted relation, direction, extraction confidence |
EntityMaturationSynthesized |
Phase 4 (Maturation) | span |
phase4.maturation.synthesize |
entity_id, entity_name, envelope_count, top_k_used, synthesized_description |
FusionDecisionMade |
Phase 5 (Fusion) | span |
phase5.fusion.decision |
entities_fused, fusion_type, confidence, decision reason / rationale |
Evaluation & Metrics¶
| Pipeline Event | Observation Type | Observation Name | Captured Fields & Metadata |
|---|---|---|---|
EvaluationScoreLogged |
score + span |
eval.score.<metric_name> |
Langfuse numeric score (metric_name, value, comment), span with target_id, metric_name, score, and comment |
EvaluationCompleted |
score(s) + span |
evaluation.<evaluation_id> |
Langfuse numeric scores for each evaluation metric, summary span with evaluation_id, metrics dictionary, and outcome |
Session Bundling & Run Searchability¶
To bundle an entire multi-phase execution into a single, cohesive, searchable session in Langfuse:
- Specify or generate a
session_id(defaults torun_idif omitted). - All child spans, retrieval observations, and LLM generations automatically inherit the active trace ID and
session_id. - In the Langfuse Dashboard under Sessions, search by
session_idto inspect the total token consumption, aggregated costs, and end-to-end latency across all pipeline phases.
Disk Cache Observability¶
When DiskCachedStructuredLLM serves a prediction from local cache:
- An
LLMGenerationCompletedevent is emitted withcached=Trueandprompt_tokens=0, completion_tokens=0. - Langfuse records a generation with 0 billable tokens and <1ms latency, enabling direct visualization of cache hit rates and monetary cost savings in the dashboard.
Prompt Management with Langfuse¶
Episteme supports managing and versioning prompt bundles in Langfuse via LangfusePromptProvider.
from pipeline.prompts import LangfusePromptProvider
from pipeline.config import PipelineConfig
# 1. Initialize prompt provider (with automatic fallback to local defaults)
prompt_provider = LangfusePromptProvider()
# 2. Populate PipelineConfig with managed prompts by label (e.g. 'production')
config = PipelineConfig()
config = prompt_provider.populate_config(config, label_or_version="production")
# Or fetch specific bundles:
ner_bundle = prompt_provider.get_bundle("ner_extraction", label_or_version="production")
Prompt Lifecycle & Reproducibility¶
- Eager Resolution: Prompts and their version metadata are fetched before the pipeline run starts and populated into
PipelineConfig. - Run Manifests & Fingerprints: Run manifests capture the prompt text, version, and name in method fingerprints. Updating a prompt version in Langfuse invalidates only the affected pipeline phase.
- Trace Linking: When generating structured predictions,
LangfuseObserverrecords theprompt_name,prompt_version, andprompt_labelon the Langfuse generation observation.
Operational Notes¶
- Spans and generations are automatically closed and updated.
- Observability is non-blocking and strictly best-effort: network or logging failures will never disrupt pipeline execution.
- Sensitive or large fields (e.g. oversized candidate lists) are truncated to keep dashboard views clean and performant.