---
title: Observability for LiveKit Agents with Langfuse
sidebarTitle: LiveKit
logo: /images/integrations/livekit_icon.svg
description: Trace real-time voice AI agents and multimodal conversations built with LiveKit Agents using Langfuse.
---

# Trace LiveKit Agents with Langfuse

This guide shows you how to integrate **Langfuse** with **LiveKit Agents** for observability and tracing of real-time voice AI applications. By following these steps, you'll be able to monitor, debug, and evaluate your LiveKit agents in the Langfuse dashboard.

> **What is LiveKit Agents?** [LiveKit Agents](https://docs.livekit.io/agents/) ([GitHub](https://github.com/livekit/agents)) is an open-source Python and Node.js framework for building production-grade multimodal and voice AI agents. It provides a complete set of tools and abstractions for feeding realtime media through AI pipelines, supporting both high-performance STT-LLM-TTS voice pipelines and speech-to-speech models with any AI provider.

> **What is Langfuse?** [Langfuse](https://langfuse.com) is an open-source AI engineering platform. It offers tracing and monitoring capabilities for LLM applications. Langfuse helps developers debug, analyze, and optimize their AI systems by providing detailed insights and integrating with a wide array of tools and frameworks through native integrations, OpenTelemetry, and dedicated SDKs.

## Get Started

LiveKit Agents includes built-in OpenTelemetry support. The Langfuse SDK registers as an OpenTelemetry span processor, so all you need is to create a `TracerProvider`, hand it to both LiveKit and Langfuse, and traces will appear in your Langfuse dashboard automatically.

LiveKit Agents ships for both Python and Node.js. Select your language in the tabs below.

<Steps>

### Step 1: Install Dependencies

<LangTabs items={["Python SDK", "JS/TS SDK"]}>
<Tab title="Python SDK">

```bash
pip install langfuse livekit-agents livekit-plugins-openai livekit-plugins-silero livekit-plugins-turn-detector opentelemetry-sdk python-dotenv
```

</Tab>
<Tab title="JS/TS SDK">

```bash
npm install @langfuse/otel @opentelemetry/sdk-trace-node @livekit/agents @livekit/agents-plugin-openai @livekit/agents-plugin-silero
```

</Tab>
</LangTabs>

### Step 2: Configure Langfuse SDK

Set up your Langfuse API keys. You can get these keys by signing up for a free [Langfuse Cloud](https://langfuse.com/cloud) account or by [self-hosting Langfuse](https://langfuse.com/self-hosting).

<LangTabs items={["Python SDK", "JS/TS SDK"]}>
<Tab title="Python SDK">

```bash filename=".env"
LANGFUSE_SECRET_KEY = "sk-lf-..."
LANGFUSE_PUBLIC_KEY = "pk-lf-..."
LANGFUSE_BASE_URL = "https://cloud.langfuse.com" # 🇪🇺 EU region
# Other Langfuse data regions include 🇺🇸 US: https://us.cloud.langfuse.com, 🇯🇵 Japan: https://jp.cloud.langfuse.com and ⚕️ HIPAA: https://hipaa.cloud.langfuse.com
```

</Tab>
<Tab title="JS/TS SDK">

```bash filename=".env"
LANGFUSE_SECRET_KEY = "sk-lf-..."
LANGFUSE_PUBLIC_KEY = "pk-lf-..."
LANGFUSE_BASE_URL = "https://cloud.langfuse.com" # 🇪🇺 EU region
# Other Langfuse data regions include 🇺🇸 US: https://us.cloud.langfuse.com, 🇯🇵 Japan: https://jp.cloud.langfuse.com and ⚕️ HIPAA: https://hipaa.cloud.langfuse.com
```

</Tab>
</LangTabs>

### Step 3: Set Up the Langfuse Tracer Provider

<LangTabs items={["Python SDK", "JS/TS SDK"]}>
<Tab title="Python SDK">

Create a shared `TracerProvider` and pass it to both LiveKit (via `set_tracer_provider`) and the Langfuse SDK. The Langfuse SDK automatically registers an exporter on the provider to send spans to Langfuse.

Because LiveKit generates many internal spans, set `should_export_span=lambda span: True` so that all telemetry is forwarded.

```python filename="agent.py"
import os

from langfuse import Langfuse
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.util.types import AttributeValue

from livekit.agents.telemetry import set_tracer_provider


def setup_langfuse(
    metadata: dict[str, AttributeValue] | None = None,
    *,
    base_url: str | None = None,
    public_key: str | None = None,
    secret_key: str | None = None,
) -> TracerProvider:
    public_key = public_key or os.getenv("LANGFUSE_PUBLIC_KEY")
    secret_key = secret_key or os.getenv("LANGFUSE_SECRET_KEY")
    base_url = base_url or os.getenv("LANGFUSE_BASE_URL") or os.getenv("LANGFUSE_HOST")

    if not public_key or not secret_key or not base_url:
        raise ValueError(
            "LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, and LANGFUSE_BASE_URL (or LANGFUSE_HOST) must be set"
        )

    trace_provider = TracerProvider()
    set_tracer_provider(trace_provider, metadata=metadata)

    Langfuse(
        public_key=public_key,
        secret_key=secret_key,
        base_url=base_url,
        tracer_provider=trace_provider,
        should_export_span=lambda span: True,
    )

    return trace_provider
```

</Tab>
<Tab title="JS/TS SDK">

Create a `NodeTracerProvider` configured with the `LangfuseSpanProcessor` and pass it to LiveKit via `telemetry.setTracerProvider()`. In OpenTelemetry JS SDK v2 (required by `@langfuse/otel` v5), providers are immutable, so the span processor must be supplied at construction time.

Because LiveKit generates many internal spans, set `shouldExportSpan: () => true` so that all telemetry is forwarded. Call `tracerProvider.register()` to also set it as the global OpenTelemetry provider, so spans from your own instrumentation (the OTel API or `@langfuse/tracing` helpers) are captured alongside LiveKit's.

Do **not** pass the `metadata` option to `telemetry.setTracerProvider()`. LiveKit handles that option by calling `provider.addSpanProcessor()`, an API that was removed in OpenTelemetry JS SDK v2 (which `@langfuse/otel` v5 depends on). Passing it throws `TypeError: provider.addSpanProcessor is not a function` and crashes the worker on job entry. To attach attributes to every span, add your own span processor to the `spanProcessors` array at construction time instead.

As of `@livekit/agents` v1.4.x, `setTracerProvider()` is still typed against OpenTelemetry JS SDK v1, while `@langfuse/otel` v5 uses SDK v2. The two `NodeTracerProvider` types are structurally incompatible, so TypeScript requires a cast. The cast is safe at runtime: LiveKit only calls `getTracer()` on the provider, which both SDK versions support.

```ts filename="agent.ts"
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { LangfuseSpanProcessor } from "@langfuse/otel";
import { telemetry } from "@livekit/agents";

export function setupLangfuse(): NodeTracerProvider {
  const tracerProvider = new NodeTracerProvider({
    spanProcessors: [
      // LiveKit emits many non-LLM spans; forward all of them to Langfuse
      new LangfuseSpanProcessor({ shouldExportSpan: () => true }),
    ],
  });

  // Register as the global OpenTelemetry provider so that spans you create in
  // your own code (via the OTel API or @langfuse/tracing helpers) are also
  // exported to Langfuse, not just LiveKit's internal spans.
  tracerProvider.register();

  // Pass the provider to LiveKit WITHOUT the metadata option (see warnings above).
  // The cast bridges the SDK v1/v2 type mismatch; it is safe at runtime.
  telemetry.setTracerProvider(
    tracerProvider as unknown as Parameters<
      typeof telemetry.setTracerProvider
    >[0],
  );

  return tracerProvider;
}
```

</Tab>
</LangTabs>

### Step 4: Use the Tracer Provider in Your Entrypoint

<LangTabs items={["Python SDK", "JS/TS SDK"]}>
<Tab title="Python SDK">

Call `setup_langfuse()` before `AgentSession.start()`. You can pass optional metadata (such as `langfuse.session.id`) that will be attached to every span.

```python filename="agent.py"
from livekit.agents import AgentSession, JobContext, metrics
from livekit.agents.voice import MetricsCollectedEvent
from livekit.plugins import silero


async def entrypoint(ctx: JobContext):
    trace_provider = setup_langfuse(
        metadata={
            "langfuse.session.id": ctx.room.name,
        }
    )

    async def flush_trace():
        trace_provider.force_flush()

    ctx.add_shutdown_callback(flush_trace)

    session = AgentSession(vad=silero.VAD.load())

    @session.on("metrics_collected")
    def _on_metrics_collected(ev: MetricsCollectedEvent):
        metrics.log_metrics(ev.metrics)

    await session.start(agent=MyAgent(), room=ctx.room)
```

You can find a full end-to-end Python example [on GitHub](https://github.com/livekit/agents/blob/main/examples/voice_agents/langfuse_trace.py).

</Tab>
<Tab title="JS/TS SDK">

Call `setupLangfuse()` inside your agent entrypoint before the session starts, and flush the provider on shutdown so no spans are lost when a job ends.

```ts filename="agent.ts"
import { type JobContext, defineAgent } from "@livekit/agents";

export default defineAgent({
  entry: async (ctx: JobContext) => {
    const tracerProvider = setupLangfuse();

    // Flush buffered spans when the job shuts down
    ctx.addShutdownCallback(async () => {
      await tracerProvider.forceFlush();
    });

    // ... start your AgentSession as usual (see the LiveKit Agents docs)
  },
});
```

</Tab>
</LangTabs>

### Step 5: View Traces in Langfuse

After running the agent, navigate to your [Langfuse Trace Table](https://cloud.langfuse.com). You will find detailed traces of the agent's execution, providing insights into every agent step, tool call, input, output, and performance metric.

![LiveKit trace in Langfuse](/images/docs/livekit-trace.png)

[Example trace in Langfuse](https://cloud.langfuse.com/project/cloramnkj0002jz088vzn1ja4/traces/5f00d917e418a8a41de5ad9b650daca0?observation=c9b49d21cbfee499&timestamp=2026-02-26T17%3A47%3A26.818Z&traceId=5f00d917e418a8a41de5ad9b650daca0)

</Steps>

## Interoperability with the Python SDK

You can use this integration together with the Langfuse [SDKs](/docs/observability/sdk/overview) to add additional attributes to the observation.

<Tabs items={["Decorator", "Context Manager"]}>
<Tab>

The [`@observe()` decorator](/docs/observability/sdk/instrumentation#custom-instrumentation) provides a convenient way to automatically wrap your instrumented code and add additional attributes to the observation.

```python
from langfuse import observe, propagate_attributes, get_client

langfuse = get_client()

@observe()
def my_llm_pipeline(input):
    # Add additional attributes (user_id, session_id, metadata, version, tags) to all spans created within this execution scope
    with propagate_attributes(
        user_id="user_123",
        session_id="session_abc",
        tags=["agent", "my-observation"],
        metadata={"email": "user@langfuse.com"},
        version="1.0.0"
    ):

        # YOUR APPLICATION CODE HERE
        result = call_llm(input)

        return result

# Run the function
my_llm_pipeline("Hi")
```

Learn more about using the Decorator in the [Langfuse SDK instrumentation docs](/docs/observability/sdk/instrumentation#custom-instrumentation).

</Tab>
<Tab>

The [Context Manager](/docs/observability/sdk/instrumentation#custom-instrumentation) allows you to wrap your instrumented code using context managers (with `with` statements), which allows you to add additional attributes to the observation.

```python
from langfuse import get_client, propagate_attributes

langfuse = get_client()

with langfuse.start_as_current_observation(
    as_type="span",
    name="my-observation",
    trace_context={"trace_id": "abcdef1234567890abcdef1234567890"},  # Must be 32 hex chars
) as observation:

    # Add additional attributes (user_id, session_id, metadata, version, tags)
    # to all observations created within this execution scope
    with propagate_attributes(
        user_id="user_123",
        session_id="session_abc",
        metadata={"experiment": "variant_a", "env": "prod"},
        version="1.0",
    ):
        # YOUR APPLICATION CODE HERE
        result = call_llm("some input")

# Flush events in short-lived applications
langfuse.flush()
```

Learn more about using the Context Manager in the [Langfuse SDK instrumentation docs](/docs/observability/sdk/instrumentation#custom-instrumentation).

</Tab>
</Tabs>

## Troubleshooting

<details>
<summary>No observations appearing</summary>

First, enable [debug mode](/docs/observability/sdk/advanced-features#logging--debugging) in the Python SDK:

```bash
export LANGFUSE_DEBUG="True"
```

Then run your application and check the debug logs:

- **OTel observations appear in the logs:** Your application is instrumented correctly but observations are not reaching Langfuse. To resolve this:
  1. Call [`langfuse.flush()`](/docs/observability/sdk/instrumentation#client-lifecycle--flushing) at the end of your application to ensure all observations are exported.
  2. Verify that you are using the correct API keys and base URL.
- **No OTel spans in the logs:** Your application is not instrumented correctly. Make sure the instrumentation runs before your application code.

</details>

<details>
<summary>Unwanted observations in Langfuse</summary>

The Langfuse SDK is based on OpenTelemetry. Other libraries in your application may emit OTel spans that are not relevant to you. These still count toward your [billable units](/docs/administration/billable-units), so you should filter them out. See [Unwanted spans in Langfuse](/faq/all/unwanted-http-database-spans) for details.

</details>

<details>
<summary>Missing attributes</summary>

Some attributes may be stored in the metadata object of the observation rather than being mapped to the Langfuse data model. If a mapping or integration does not work as expected, please [raise an issue on GitHub](/issues).

</details>

## Next Steps

Once you have instrumented your code, you can manage, evaluate and debug your application:

- [Manage prompts in Langfuse](/docs/prompts/get-started)
- [Add evaluation scores](/docs/evaluation/features/evaluation-methods/custom-scores)
- [Run LLM-as-a-judge Evaluators](/docs/scores/model-based-evals)
- [Create datasets](/docs/datasets/overview)
- [Create custom dashboards](/docs/analytics/custom-dashboards)
- [Test queries in the Playground](/docs/playground)

## References

- [LiveKit Agents Documentation](https://docs.livekit.io/agents/)
- [LiveKit Telemetry Guide](https://docs.livekit.io/agents/build/metrics/)
- [End-to-end Python example](https://github.com/livekit/agents/blob/main/examples/voice_agents/langfuse_trace.py)

## GitHub Discussions

<!-- agent-instructions -->

---

## Agent Instructions

This page is part of the [Langfuse](https://langfuse.com) documentation, published as plain Markdown for AI agents. Every page is available as Markdown by appending `.md` to its URL, or by sending an `Accept: text/markdown` header. This page: `https://langfuse.com/integrations/frameworks/livekit.md`.

### Querying these docs

If the answer is not on this page, query the documentation instead of guessing:

- **Semantic search** across all Langfuse docs, returning an answer with the relevant pages and excerpts. Ask a specific, self-contained question:

  ```bash
  curl -sG "https://langfuse.com/api/search-docs" --data-urlencode "query=How do I trace a LangGraph agent?"
  ```

- **Index of every page**: <https://langfuse.com/llms.txt>, with per-section indexes [llms-docs.txt](https://langfuse.com/llms-docs.txt), [llms-integrations.txt](https://langfuse.com/llms-integrations.txt), and [llms-self-hosting.txt](https://langfuse.com/llms-self-hosting.txt).

### Before writing Langfuse code

- **Install the [Langfuse Agent Skill](https://langfuse.com/docs/api-and-data-platform/features/agent-skill).** It encodes Langfuse's own best practices for instrumentation, prompt management, and evaluation, and materially improves results.
- **Read [What does a good trace look like?](https://langfuse.com/docs/observability/best-practices.md)** before instrumenting an application.
- **Verify endpoints, parameters, and response fields** against the [API reference](https://api.reference.langfuse.com) instead of inferring them from code examples.
- **Use the [Langfuse CLI](https://langfuse.com/docs/api-and-data-platform/features/cli)** (`npx langfuse-cli api <resource> <action>`) to read or write traces, prompts, datasets, and scores from the terminal.

Found an error in these docs? Please open an issue at <https://github.com/langfuse/langfuse-docs/issues>.
