Langfuse¶

Langfuse is an open-source LLM engineering platform with tracing/observability capabilities. Burr integrates with it through the OpenTelemetry-native Langfuse Python SDK, building on the opentelemetry integration.

Install the integration:

pip install "apache-burr[langfuse]"

Then add the bridge as a hook – credentials are read from the standard LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, and LANGFUSE_HOST environment variables:

from burr.core import ApplicationBuilder
from burr.integrations.langfuse import LangfuseBridge

app = (
    ApplicationBuilder()
    .with_graph(graph)
    .with_entrypoint("prompt")
    .with_hooks(LangfuseBridge())
    .build()
)
app.run(halt_after=["response"])  # logs one trace to Langfuse

Each application execution call becomes a Langfuse trace, each step becomes a span (with state/inputs/results captured as observation input/output), and spans opened through Burr’s tracing API become nested spans. Any additional OpenTelemetry LLM instrumentation (e.g. opentelemetry-instrumentation-openai) appears nested within the corresponding Burr step.

See the following resources for more information:

Reference for the various useful methods:

class burr.integrations.langfuse.LangfuseBridge(
langfuse_client: Langfuse | None = None,
*,
session_id: str | None = None,
user_id: str | None = None,
capture_state: bool = True,
tracer: Tracer | None = None,
tracer_provider: TracerProvider | None = None,
**langfuse_kwargs: Any,
)¶

Adapter to log Burr application execution to Langfuse.

  1. Each application execution call (run/step/iterate/stream_result/…) opens a root span, which defines the Langfuse trace

  2. Each step opens a span, capturing the action’s inputs/read state as the observation input and its result/written state as the observation output

  3. Each span opened through Burr’s tracing API (__tracer) opens a span

  4. Attributes logged through __tracer.log_attribute(s) are captured as observation metadata

Burr’s app_id maps to the Langfuse session (so multiple execution calls of the same application group together), and the partition_key maps to the Langfuse user - both can be overridden.

Basic usage - credentials are read from the standard LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, and LANGFUSE_HOST environment variables:

from burr.integrations.langfuse import LangfuseBridge

app = (
    ApplicationBuilder()
    .with_graph(graph)
    .with_entrypoint("prompt")
    .with_hooks(LangfuseBridge())
    .build()
)
app.run(halt_after=["response"])  # logs one trace to Langfuse

You can also pass credentials explicitly, or pass a pre-constructed client:

LangfuseBridge(public_key="pk-lf-...", secret_key="sk-lf-...", host="...")
# or, equivalently
LangfuseBridge(langfuse_client=my_langfuse_client)

The underlying client is available as bridge.langfuse_client – for example to flush() in short-lived scripts, or to score traces.

Note

With langfuse v4+, spans are filtered before export, and only LLM-relevant spans are exported by default. If you construct the Langfuse client yourself, pass should_export_span=burr_span_export_filter to its constructor so Burr spans are exported (see burr_span_export_filter()). When LangfuseBridge constructs the client for you, this is applied automatically.

__init__(
langfuse_client: Langfuse | None = None,
*,
session_id: str | None = None,
user_id: str | None = None,
capture_state: bool = True,
tracer: Tracer | None = None,
tracer_provider: TracerProvider | None = None,
**langfuse_kwargs: Any,
)¶

Initializes the Langfuse bridge.

Parameters:
  • langfuse_client – A pre-constructed Langfuse client to use. If not passed, one is created from langfuse_kwargs (falling back to the standard LANGFUSE_* environment variables), with the Burr span export filter applied.

  • session_id – Langfuse session ID to group traces under. Defaults to the Burr app_id.

  • user_id – Langfuse user ID to attach to traces. Defaults to the Burr partition_key (if set).

  • capture_state – Whether to capture state/inputs/results as observation input/output. Set to False if your state contains data you do not want sent to Langfuse.

  • tracer – OpenTelemetry tracer to use – for testing/advanced use. Defaults to a tracer named burr.integrations.langfuse from the global provider.

  • tracer_provider – OpenTelemetry tracer provider to use for both the Langfuse client and Burr spans. When passing a pre-constructed client that uses a custom provider, pass the same provider here.

  • langfuse_kwargs – Keyword arguments forwarded to the Langfuse constructor (e.g. public_key, secret_key, host). Only valid if langfuse_client is not passed.

post_run_step(
*,
state: State,
action: Action,
result: Dict[str, Any] | None,
exception: Exception,
**future_kwargs: Any,
)¶

Run after a step is executed.

Parameters:
  • state – State after step execution

  • action – Action that was executed

  • result – Result of the action

  • sequence_id – Sequence ID of the action

  • exception – Exception that was raised

  • future_kwargs – Future keyword arguments

pre_run_step(
*,
app_id: str,
partition_key: str,
sequence_id: int,
state: State,
action: Action,
inputs: Dict[str, Any],
**future_kwargs: Any,
)¶

Run before a step is executed.

Parameters:
  • state – State prior to step execution

  • action – Action to be executed

  • inputs – Inputs to the action

  • sequence_id – Sequence ID of the action

  • future_kwargs – Future keyword arguments

burr.integrations.langfuse.burr_span_export_filter(span: ReadableSpan) bool¶

You only need this if you construct the Langfuse client yourself – LangfuseBridge applies it automatically when it creates the client for you:

from langfuse import Langfuse
from burr.integrations.langfuse import LangfuseBridge, burr_span_export_filter

client = Langfuse(should_export_span=burr_span_export_filter)
app = ApplicationBuilder().with_hooks(LangfuseBridge(langfuse_client=client))...