> ## Documentation Index
> Fetch the complete documentation index at: https://langchain-5e9cc07a-preview-voicet-1782781101-fbe4599.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Trace LiveKit applications

Trace your [LiveKit Agents](https://docs.livekit.io/agents/) voice agents to LangSmith with the LangSmith LiveKit integration. For high-level conventions, see [Voice tracing fundamentals](/langsmith/trace-voice-fundamentals).

<Note>
  The LiveKit integration requires `langsmith[livekit]`. It is in development, so its API may change.
</Note>

The integration captures each conversation as a single LangSmith trace, with a span for every pipeline stage (STT, LLM, TTS) grouped by turn, plus LiveKit's per-stage latency and token metrics. You enable it with one call and do not create any spans yourself.

## Install

Install the integration along with the LiveKit plugins your agent uses:

<CodeGroup>
  ```bash pip theme={null}
  pip install "langsmith[livekit]" "livekit-agents[openai,silero,turn-detector]"
  ```

  ```bash uv theme={null}
  uv add "langsmith[livekit]" "livekit-agents[openai,silero,turn-detector]"
  ```
</CodeGroup>

## Set environment variables

The integration reads your LangSmith credentials from the environment and exports to LangSmith for you:

```bash .env theme={null}
LANGSMITH_API_KEY=<your-langsmith-api-key>
LANGSMITH_TRACING=true
LANGSMITH_PROJECT=livekit-voice
LIVEKIT_URL=<your-livekit-url>
LIVEKIT_API_KEY=<your-livekit-api-key>
LIVEKIT_API_SECRET=<your-livekit-api-secret>
OPENAI_API_KEY=<your-openai-api-key>
```

## Set up tracing

Import `configure_livekit` and call it once before creating your `AgentServer`. It builds the tracer provider, registers the LangSmith span processor, and wires it into LiveKit:

```python theme={null}
from langsmith.integrations.livekit import configure_livekit
from livekit import agents
from livekit.agents import Agent, AgentServer, AgentSession

# Enable tracing before creating agents.
configure_livekit()

server = AgentServer()

@server.rtc_session()
async def my_agent(ctx: agents.JobContext):
    session = AgentSession(
        stt="deepgram/nova-2:en",
        llm="openai/gpt-5.4-mini",
        tts="openai/tts-1:alloy",
    )
    await session.start(room=ctx.room, agent=Agent(instructions="You are a helpful assistant."))
```

This works for both the STT/LLM/TTS cascade and speech-to-speech models: build the `AgentSession` with a realtime model (for example, `lk_openai.realtime.RealtimeModel(...)`) and the tracing setup is unchanged.

### Use your own tracer provider

There are two ways to install the tracing provider. `configure_livekit()` is the quick path: it builds a `TracerProvider`, registers the LangSmith span processor, and wires it into LiveKit. To use a `TracerProvider` you already manage, construct the processor yourself, add it to your provider, and register that provider with LiveKit's tracer hook. LiveKit only emits spans through the provider its tracer is bound to:

```python theme={null}
from livekit.agents import telemetry
from opentelemetry.sdk.trace import TracerProvider

from langsmith.integrations.livekit import LiveKitLangSmithSpanProcessor

provider = TracerProvider()  # your own provider
provider.add_span_processor(LiveKitLangSmithSpanProcessor())
telemetry.set_tracer_provider(provider)
```

## Record the conversation audio

The integration attaches the call recording to the conversation root span. How you capture that recording differs between local development and production.

### Development: embed a local file

In console and local development, enable LiveKit's session recording and point `audio_path_provider` at the `audio.ogg` LiveKit writes under `ctx.session_directory`. The integration reads that file and embeds the bytes in the trace.

```python theme={null}
from pathlib import Path

_audio_path: Path | None = None
configure_livekit(audio_path_provider=lambda: _audio_path)

@server.rtc_session()
async def my_agent(ctx: agents.JobContext):
    global _audio_path
    _audio_path = ctx.session_directory / "audio.ogg"
    await session.start(
        room=ctx.room,
        agent=Agent(instructions="You are a helpful assistant."),
        record={"audio": True},
    )
```

In console mode, also pass `--record` on the command line. The recording reflects what was played to the client, so a barge-in shows up truncated.

<Warning>
  Do not use `audio_path_provider` in production. In a deployed worker, `ctx.session_directory` is an ephemeral temporary directory that LiveKit deletes when the session ends, so there is no durable file to embed.
</Warning>

### Production: record with Egress and attach the file

In production, record the room with [LiveKit Egress](https://docs.livekit.io/home/egress/overview/) into your own object storage, then attach the finished recording to the trace as a real audio attachment. Egress finishes uploading after the call ends, so the integration holds the conversation's root span open until you supply the bytes:

1. Call `processor.expect_recording(thread_id)` when you start egress. The root span stays open.
2. After the call, wait for egress to complete, download the file from your storage, and call `processor.complete_recording(thread_id, audio_bytes)`. The integration embeds the bytes and exports the trace.

Use the same `thread_id` for both calls and for `set_thread_id`, so the integration can match the recording to the conversation.

```python theme={null}
import os

from livekit import agents, api
from livekit.agents import Agent, AgentServer, AgentSession
from langsmith.integrations.livekit import configure_livekit, set_thread_id

RECORDING_BUCKET = os.environ["RECORDING_BUCKET"]

processor = configure_livekit()
server = AgentServer()

@server.rtc_session()
async def my_agent(ctx: agents.JobContext):
    thread_id = ctx.room.name
    set_thread_id(thread_id)  # groups this conversation's spans into a thread
    key = f"recordings/{thread_id}.ogg"

    # Start an audio-only room-composite egress to your storage.
    lkapi = api.LiveKitAPI()  # reads LIVEKIT_URL / LIVEKIT_API_KEY / LIVEKIT_API_SECRET
    egress = await lkapi.egress.start_room_composite_egress(
        api.RoomCompositeEgressRequest(
            room_name=ctx.room.name,
            audio_only=True,
            file_outputs=[
                api.EncodedFileOutput(
                    file_type=api.EncodedFileType.OGG,
                    filepath=key,
                    s3=api.S3Upload(
                        bucket=RECORDING_BUCKET,
                        region=os.environ["AWS_REGION"],
                        access_key=os.environ["AWS_ACCESS_KEY_ID"],
                        secret=os.environ["AWS_SECRET_ACCESS_KEY"],
                    ),
                )
            ],
        )
    )
    # Hold the trace open until the recording is ready.
    processor.expect_recording(thread_id)

    async def attach_recording():
        try:
            await wait_for_egress(lkapi, egress.egress_id)  # poll until EGRESS_COMPLETE
            audio = download_from_storage(RECORDING_BUCKET, key)  # your storage client
            processor.complete_recording(thread_id, audio, name="call.ogg")
        except Exception:
            processor.complete_recording(thread_id, None)  # release without audio

    ctx.add_shutdown_callback(attach_recording)

    session = AgentSession(...)
    await session.start(room=ctx.room, agent=Agent(instructions="..."))
```

`wait_for_egress` polls [`list_egress`](https://docs.livekit.io/home/egress/api/) until the status is `EGRESS_COMPLETE` (or subscribe to the `egress_ended` webhook), and `download_from_storage` reads the object with your cloud provider's client. LiveKit Egress also writes to [Google Cloud Storage and Azure](https://docs.livekit.io/home/egress/overview/): swap `s3=` for `gcp=api.GCPUpload(...)` or `azure=api.AzureBlobUpload(...)`.

<Note>
  Because the trace's root span is held until `complete_recording` runs, always call it, including on failure with `data=None`, so the trace is not left open. If the worker stops first, the integration flushes the trace without audio.
</Note>

### Self-hosted LiveKit

This flow does not depend on LiveKit Cloud. [LiveKit Server](https://docs.livekit.io/home/self-hosting/local/) and [Egress](https://docs.livekit.io/home/self-hosting/egress/) are open source, and the integration works the same whether your agent connects to LiveKit Cloud or to your own deployment. With self-hosted Egress you can also write the recording to a local or shared volume, in which case the development `audio_path_provider` path can read it directly without a storage download.

For the underlying attachment API used by both paths, see [Upload files with traces](/langsmith/upload-files-with-traces).

## Next steps

<CardGroup cols={2}>
  <Card title="Voice fundamentals" icon="waveform" href="/langsmith/trace-voice-fundamentals">
    Core conventions for tracing voice agents.
  </Card>

  <Card title="Upload files with traces" icon="paperclip" href="/langsmith/upload-files-with-traces">
    Attach the conversation audio recording to your trace.
  </Card>
</CardGroup>

***

<div className="source-links">
  <Callout icon="terminal-2">
    [Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
  </Callout>

  <Callout icon="edit">
    [Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/trace-with-livekit.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
  </Callout>
</div>
