> ## Documentation Index
> Fetch the complete documentation index at: https://docs.braintrust.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# LiveKit Agents

> Trace real-time voice AI pipelines built with LiveKit Agents, including LLM turns, speech-to-text, text-to-speech, and function tool calls

[LiveKit Agents](https://livekit.io/) is a framework for building real-time voice and video AI applications. Braintrust traces LiveKit Agents applications to capture voice interactions, agent sessions, and realtime model usage.

**TypeScript**

To trace LiveKit Agents with Braintrust's TypeScript SDK, use auto-instrumentation, manual instrumentation, or [OpenTelemetry](#opentelemetry-typescript). Auto-instrumentation and manual instrumentation trace sessions that use a [STT-LLM-TTS pipeline](https://docs.livekit.io/agents/models/pipelines), and require Braintrust v3.39.0 or later and `@livekit/agents` v1.9.1 to v1.9.x. Audio recording requires Braintrust v3.41.0 or later.

## Auto-instrumentation

To trace LiveKit Agents without modifying your application code, run your agent with Braintrust's import hook. Braintrust records LiveKit's session, turn, and model activity directly through the Braintrust SDK, without requiring an OpenTelemetry pipeline. If your app also sets a LiveKit tracer provider that exports somewhere other than Braintrust, Braintrust leaves it in place.

### Setup

These steps add Braintrust to an existing LiveKit Agents app. They trace each session and record its audio.

**1. Install Braintrust**

Add `braintrust` and [`@braintrust/audio`](https://docs.braintrust.dev/sdks/typescript/related/audio) to your agent's project. We strongly recommend using `@braintrust/audio` to compress recordings and reduce upload size.

```bash pnpm theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
pnpm add braintrust @braintrust/audio
```

```bash npm theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
npm install braintrust @braintrust/audio
```

**2. Set your environment variables**

Add your Braintrust API key and the two audio recording flags to your agent's `.env` file. `BRAINTRUST_CAPTURE_USER_AUDIO_ATTACHMENTS` and `BRAINTRUST_CAPTURE_AGENT_AUDIO_ATTACHMENTS` turn on [audio recording](#audio-attachments-typescript-auto) for the caller and the agent. Without them, traces include spans and metadata but no audio.

```bash title=".env" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
BRAINTRUST_API_KEY=your-api-key
BRAINTRUST_CAPTURE_USER_AUDIO_ATTACHMENTS=true
BRAINTRUST_CAPTURE_AGENT_AUDIO_ATTACHMENTS=true
```

**3. Initialize a logger and run your agent with the import hook**

Initialize a Braintrust logger in your agent's entry file, and flush it in a shutdown callback in your agent's `entry` function. When a job ends, LiveKit Agents closes the session, which waits for the recording to finish uploading, and then exits the job process. Without the flush, the session's last spans can be lost when the process exits.

```javascript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { initLogger } from "braintrust";

const logger = initLogger({ projectName: "my-livekit-project" }); // Replace with your project name

// In your agent's entry function:
ctx.addShutdownCallback(async () => {
  await logger.flush();
});
```

Then run the agent with Braintrust's import hook. `--env-file` loads your `.env` file and requires Node.js 20.6.0 or later. LiveKit runs each job in a child process that inherits these flags, so the hook also applies to your sessions.

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
node --env-file=.env --import braintrust/hook.mjs livekit_agent.js dev
```

After a call ends, its trace appears on the [** Logs**](https://www.braintrust.dev/app/~/logs) page of your project. The `session` root span holds the call recording, with turn, model, and speech spans nested under it. See [What Braintrust traces](#what-traced-typescript-auto) for the full list.

### Example agent

This minimal pipeline agent uses separate speech-to-text, LLM, and text-to-speech models and is traced with the setup above. It uses the `@livekit/agents-plugin-openai` plugin and the LiveKit and OpenAI credentials from your `.env` file.

```javascript title="livekit_agent.js" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { fileURLToPath } from "node:url";
import { cli, defineAgent, ServerOptions, voice } from "@livekit/agents";
import * as openai from "@livekit/agents-plugin-openai";
import { initLogger } from "braintrust";

const logger = initLogger({ projectName: "my-livekit-project" }); // Replace with your project name

export default defineAgent({
  entry: async (ctx) => {
    const session = new voice.AgentSession({
      stt: new openai.STT({ model: "gpt-4o-mini-transcribe", useRealtime: true }),
      llm: new openai.LLM({ model: "gpt-5-mini" }),
      tts: new openai.TTS({ model: "gpt-4o-mini-tts", voice: "alloy" }),
    });

    ctx.addShutdownCallback(async () => {
      await logger.flush();
    });

    await session.start({
      agent: new voice.Agent({
        instructions: "You are a helpful voice AI assistant.",
      }),
      room: ctx.room,
    });
  },
});

cli.runApp(new ServerOptions({ agent: fileURLToPath(import.meta.url) }));
```

The auto-instrumentation example uses plain JavaScript so `node --import` can run the file directly. The Braintrust APIs work the same in TypeScript projects. Compile your TypeScript to JavaScript and run the compiled file with the import hook, or [run TypeScript directly](https://docs.braintrust.dev/instrument/trace-llm-calls.md#auto-instrumentation) with a TypeScript loader.

> [!NOTE]
>
> If you're using a bundler, see [Trace LLM calls](https://docs.braintrust.dev/instrument/trace-llm-calls.md#auto-instrumentation) for plugin and loader setup.

> [accordion] Set up manually
>
> To trace LiveKit without the import hook, wrap the `@livekit/agents` module with `wrapLiveKit()` before you create sessions. Sessions created through the wrapped module's `voice.AgentSession` are fully traced, including speech-to-text spans, and call audio when [audio recording](#audio-attachments-typescript-auto) is on.
>
> ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
> import * as agents from "@livekit/agents";
> import { initLogger, wrapLiveKit } from "braintrust";
>
> const logger = initLogger({ projectName: "my-livekit-project" }); // Replace with your project name
>
> const { voice } = wrapLiveKit(agents);
>
> // Create sessions with the wrapped module
> const session = new voice.AgentSession({ stt, llm, tts });
> ```
>
> Register the same shutdown callback as in [Setup](#setup-typescript-auto) so the session's last spans are sent before the job process exits.

> [accordion] Opt out of LiveKit Agents tracing
>
> If you use auto-instrumentation for other libraries but want to exclude LiveKit Agents, add `livekit` to the `BRAINTRUST_DISABLE_INSTRUMENTATION` environment variable. This also disables `wrapLiveKit()`.
>
> ```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
> BRAINTRUST_DISABLE_INSTRUMENTATION=livekit node --env-file=.env --import braintrust/hook.mjs livekit_agent.js dev
> ```

### What Braintrust traces

Auto-instrumentation and `wrapLiveKit()` produce the same spans:

* Session spans (`session`), the root span covering the full agent session.
* User turn spans (`user_turn`), with child user speech spans (`user_speech`) that hold the transcript, and speech-to-text spans (`stt`) with the final transcript, model, provider, confidence, and language.
* Agent turn spans (`agent_turn`), covering the agent's reply.
* LLM spans (`llm_response`), with the chat context as input, the assistant's reply and tool calls as output, model and provider, the function tools and tool choice available to the reply in `tools` and `tool_choice` metadata, token usage, and time to first token.
* Tool spans named after each function tool, with tool arguments and output.
* Text-to-speech spans (`tts`).
* Agent speech spans (`agent_speech`), with the text the user heard and whether the user interrupted it.
* LiveKit lifecycle spans (`livekit.<name>`), such as `livekit.session_start`, `livekit.on_enter`, `livekit.eou_wait`, `livekit.llm_request_run`, and `livekit.tts_request`, with LiveKit's original nesting and attributes. Requires v3.40.0 or later.

Other LiveKit span attributes are recorded in `metadata`, with `lk.*` keys renamed to `contrib.livekit.*`. LLM and text-to-speech calls that LiveKit makes inside a traced session are recorded in the `llm_response` and `tts` spans, not as separate provider spans such as OpenAI spans.

### Audio attachments

> [!WARNING]
>
> **Early development notice:** LiveKit Agents audio capture and `@braintrust/audio` are in early development (alpha). Expect backwards-incompatible changes between releases.

Audio recording is off by default and requires Braintrust v3.41.0 or later. Each side of the call has its own environment variable, which takes `1` or `true`:

* `BRAINTRUST_CAPTURE_USER_AUDIO_ATTACHMENTS` records the caller's audio that the session receives.
* `BRAINTRUST_CAPTURE_AGENT_AUDIO_ATTACHMENTS` records the agent's audio that played.

> [!NOTE]
>
> In v3.41.0 and later, `BRAINTRUST_CAPTURE_ATTACHMENTS` no longer records LiveKit Agents audio. If you used it to record calls, set the two variables above instead.

Braintrust attaches the recording to the `session` span. See [Configure recording encoding](#compress-recordings-typescript-auto) to change how recordings are encoded.

Braintrust records 24 kHz, two-channel audio, with the caller on the first channel and the agent on the second. If you record only one side, the other channel is silent.

`session.close()` resolves only after the recording finishes uploading. LiveKit Agents closes the session when the job ends, before it exits the job process. If you close the session yourself, await `session.close()`. To send the session's last spans before the process exits, call [`logger.flush()`](https://docs.braintrust.dev/sdks/typescript/api-reference#flush) in a shutdown callback, as shown in [Setup](#setup-typescript-auto).

#### Configure recording encoding

The [`@braintrust/audio`](https://docs.braintrust.dev/sdks/typescript/related/audio) package compresses recordings to Opus audio in Ogg files. It requires Node.js 20 or later. Braintrust finds and loads the package from `node_modules` automatically, so you don't import it in your code. The default settings work without any `configureInstrumentation()` call.

If you deploy a bundle without `node_modules`, import the package in your app so your bundler includes it:

```javascript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import "@braintrust/audio";
```

Without the package, recordings upload as raw PCM (`audio/pcm`, 16-bit little-endian interleaved samples, without a WAV header). Recordings also fall back to raw PCM, with a warning, when the package fails to load, doesn't match your `braintrust` version, or the `audio` options contain an unsupported value.

The encoding settings are optional tuning. To change them, pass `audio` options for the `livekit` integration to `configureInstrumentation()`, in the same separate file that you load before the hook:

```javascript title="configure.mjs" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { configureInstrumentation } from "braintrust/instrumentation";

configureInstrumentation({
  integrations: {
    livekit: {
      audio: {
        codec: { name: "opus", options: { bitrate: 32_000 } },
        container: { name: "ogg" },
      },
    },
  },
});
```

| Setting | Default | Description |
| - | - | - |
| `codec.name` | `"opus"` | `"opus"`, or `"pcm"` to always record raw PCM, even when `@braintrust/audio` is installed. |
| `codec.options.bitrate` | `64000` | Target bitrate in whole bits per second, from `500` to `512000`. |
| `codec.options.complexity` | `5` | Encoder effort, from `0` (fastest) to `10`. |
| `codec.options.application` | `"audio"` | Encoder tuning: `"audio"`, `"voip"` (favors speech), or `"lowdelay"`. |
| `container.name` | `"ogg"` | File format for Opus audio. `"ogg"` is the only value. |

Braintrust splits the recording into attachments that each hold about 4 MiB of raw PCM, about 44 seconds of audio. Compressed attachments cover the same duration in fewer bytes. To change the size, set `streamingAttachmentBufferBytes` with [`configureInstrumentation()`](https://docs.braintrust.dev/sdks/typescript/api-reference#configureinstrumentation) from `braintrust/instrumentation`. Call it in a separate file, and load that file before the hook. A call from a file that imports `braintrust` has no effect.

```javascript title="configure.mjs" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { configureInstrumentation } from "braintrust/instrumentation";

configureInstrumentation({ streamingAttachmentBufferBytes: 1024 * 1024 });
```

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
node --env-file=.env --import ./configure.mjs --import braintrust/hook.mjs livekit_agent.js dev
```

### Tracing resources

* [LiveKit Agents overview](https://docs.livekit.io/agents/): LiveKit Agents documentation
* [Braintrust package on npm](https://www.npmjs.com/package/braintrust)
* [`@braintrust/audio` package on npm](https://www.npmjs.com/package/@braintrust/audio)

## OpenTelemetry

Braintrust attaches to LiveKit's built-in OpenTelemetry pipeline by registering a span processor with LiveKit's tracer provider.

> [!WARNING]
>
> Audio attachments aren't supported with OpenTelemetry. To record call audio, use [auto-instrumentation or manual instrumentation](#audio-attachments-typescript-auto).

### Setup

**1. Install dependencies**

```bash pnpm theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
pnpm add @braintrust/otel @livekit/agents @livekit/agents-plugin-openai "@opentelemetry/sdk-trace-node@^1.30" "@opentelemetry/exporter-trace-otlp-http@^0.57" "@opentelemetry/resources@^1.30" "@opentelemetry/semantic-conventions@^1.28" dotenv
```

```bash npm theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
npm install @braintrust/otel @livekit/agents @livekit/agents-plugin-openai "@opentelemetry/sdk-trace-node@^1.30" "@opentelemetry/exporter-trace-otlp-http@^0.57" "@opentelemetry/resources@^1.30" "@opentelemetry/semantic-conventions@^1.28" dotenv
```

> [!NOTE]
>
> `@livekit/agents` is built against OpenTelemetry JS 1.x. Keep every OpenTelemetry package on its 1.x-compatible version, as pinned above. Upgrading any of them to 2.x breaks LiveKit at build or run time. The OTLP exporter packages use a separate `0.x` version sequence that moves in step with the stable 1.x packages, so `@opentelemetry/exporter-trace-otlp-http` pins to `^0.57` rather than `^1`.

**2. Set your environment variables**

```bash title=".env" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
BRAINTRUST_API_KEY=your-api-key
BRAINTRUST_PARENT=project_name:livekit-demo
OPENAI_API_KEY=your-openai-api-key
```

### Configure tracing

Configure Braintrust's span processor and set it as LiveKit's tracer provider. Keep `filterAISpans` set to `false` so LiveKit's session, speech, and turn spans are included.

```typescript title="livekit_agent.ts" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import "dotenv/config.js";
import { fileURLToPath } from "url";
import { BraintrustSpanProcessor, setupOtelCompat } from "@braintrust/otel";
import {
  cli,
  defineAgent,
  type JobContext,
  ServerOptions,
  telemetry,
  voice,
} from "@livekit/agents";
import * as openai from "@livekit/agents-plugin-openai";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { Resource } from "@opentelemetry/resources";
import {
  ATTR_SERVICE_NAME,
  ATTR_SERVICE_VERSION,
} from "@opentelemetry/semantic-conventions";

function setupBraintrustTelemetry() {
  setupOtelCompat();

  const spanProcessor = new BraintrustSpanProcessor({
    apiKey: process.env.BRAINTRUST_API_KEY,
    apiUrl: process.env.BRAINTRUST_API_URL,
    parent: process.env.BRAINTRUST_PARENT,
    filterAISpans: false,
  });

  const provider = new NodeTracerProvider({
    resource: new Resource({
      [ATTR_SERVICE_NAME]: "livekit-agent",
      [ATTR_SERVICE_VERSION]: "1.0.0",
    }),
  });

  provider.addSpanProcessor(spanProcessor);
  provider.register();

  telemetry.setTracerProvider(provider, {
    metadata: {
      component: "livekit-agent",
    },
  });
}

export default defineAgent({
  entry: async (ctx: JobContext): Promise<void> => {
    setupBraintrustTelemetry();

    const session = new voice.AgentSession({
      llm: new openai.realtime.RealtimeModel({
        voice: "coral",
        temperature: 0.8,
      }),
    });

    await session.start({
      agent: new voice.Agent({
        instructions: `You are a helpful and friendly voice AI assistant`,
      }),
      room: ctx.room,
    });

    const greetParticipant = async () => {
      session.say("Hello! I'm your voice assistant. How can I help you today?");
    };

    let greeted = ctx.room.remoteParticipants.size > 0;
    if (greeted) greetParticipant();

    ctx.room.once("participantConnected", () => {
      if (!greeted) {
        greeted = true;
        greetParticipant();
      }
    });

    function waitForRoomDisconnect(room: typeof ctx.room) {
      return new Promise<void>((resolve) => {
        room.once("disconnected", () => resolve());
      });
    }

    await waitForRoomDisconnect(ctx.room);
  },
});

cli.runApp(
  new ServerOptions({
    agent: fileURLToPath(import.meta.url),
  })
);
```

### What Braintrust traces

These spans come from LiveKit's native OpenTelemetry instrumentation. With the OpenAI realtime model, the LLM, speech-to-text, and text-to-speech work happens inside a single realtime model call rather than as separate spans.

* Session spans (`agent_session`), the root span covering the full agent session
* Agent turn spans (`agent_turn`), with the model request, token usage, and realtime model metrics (`gen_ai.*`, `lk.realtime_model_metrics`)
* User speaking spans (`user_speaking`), with participant and speech detection details
* Agent speaking spans (`agent_speaking`), covering the agent's spoken response
* Activity lifecycle spans (`start_agent_activity`, `on_enter`, `on_exit`, `drain_agent_activity`)

### Tracing resources

* [OpenTelemetry Integration](https://docs.braintrust.dev/integrations/sdk-integrations/opentelemetry/send-traces-and-logs.md): Braintrust's OpenTelemetry support
* [Trace application logic](https://docs.braintrust.dev/instrument/trace-application-logic.md): Add custom spans and attributes to your traces
* [LiveKit Agents overview](https://docs.livekit.io/agents/): LiveKit Agents documentation

**Python**

To trace LiveKit Agents with Braintrust's Python SDK, use auto-instrumentation or OpenTelemetry. Auto-instrumentation is the recommended path for most users.

## Auto-instrumentation

Braintrust patches the internals of LiveKit Agents at startup to emit spans directly through the Braintrust SDK, without requiring an OpenTelemetry pipeline.

### Setup

**1. Install the SDK**

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
pip install braintrust livekit-agents livekit-plugins-openai
```

**2. Set your environment variables**

```bash title=".env" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
BRAINTRUST_API_KEY=your-api-key
BRAINTRUST_PARENT=project_name:livekit-demo
OPENAI_API_KEY=your-openai-api-key
```

### Trace your agent

Call `auto_instrument()` once at startup, before any LiveKit imports or client creation:

```python title="agent.py" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import braintrust

# Set up Braintrust before importing LiveKit so all agent sessions are traced
braintrust.auto_instrument()
braintrust.init_logger(project="my-livekit-project")  # Replace with your project name

from livekit import agents
from livekit.agents import Agent, AgentSession, RoomInputOptions
from livekit.plugins import openai

class Assistant(Agent):
    def __init__(self) -> None:
        super().__init__(instructions="You are a helpful voice AI assistant.")

async def entrypoint(ctx: agents.JobContext):
    session = AgentSession(llm=openai.realtime.RealtimeModel(voice="coral"))
    await session.start(
        room=ctx.room,
        agent=Assistant(),
        room_input_options=RoomInputOptions(),
    )

if __name__ == "__main__":
    agents.cli.run_app(agents.WorkerOptions(entrypoint_fnc=entrypoint))
```

> [accordion] Set up manually
>
> To instrument only specific sessions rather than patching globally, use `setup_livekit_agents()` instead of `auto_instrument()`:
>
> ```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
> from braintrust.integrations.livekit_agents import setup_livekit_agents
>
> setup_livekit_agents()
> ```

> [accordion] Opt out of LiveKit Agents tracing
>
> If you use `auto_instrument()` for other libraries but want to exclude LiveKit Agents, pass `livekit_agents=False`:
>
> ```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
> braintrust.auto_instrument(livekit_agents=False)
> ```

### What Braintrust traces

The example above uses the OpenAI realtime model, which handles the LLM, speech-to-text, and text-to-speech work inside a single model call. A component [STT-LLM-TTS pipeline](https://docs.livekit.io/agents/models/pipelines) runs those as separate steps, each producing its own spans.

* Session spans (`livekit_agent_session`), with the full voice turn lifecycle
* User speaking spans (`user_speaking`), with speech detection events
* Agent speaking spans (`agent_speaking`), covering the agent's spoken response audio.
* Tool spans (`function_tool`), with tool name, arguments, and output (when your agent defines tools).
* Additional spans when you use a [STT-LLM-TTS pipeline](https://docs.livekit.io/agents/models/pipelines) instead of a realtime model:
  * LLM spans (`llm_request_run`), with model inputs, outputs, and token usage.
  * Text-to-speech spans (`tts_request`), with input text and latency metrics.
  * Speech-to-text spans (`stt_processing`), with transcript and latency metrics, and STT token usage (`input_audio_tokens` and `total_tokens`) in metadata when the STT provider reports it.
  * VAD spans (`vad_endpointing`), with silence and speech duration metrics.
  * End-of-utterance spans (`eou_detection`), with speech and silence timing.

### Audio attachments

Audio attachments are disabled by default. Two environment variables control whether audio frames are attached to spans:

* Set `BRAINTRUST_CAPTURE_AGENT_AUDIO_ATTACHMENTS=true` to attach the agent's spoken response audio to `agent_speaking` spans.
* Set `BRAINTRUST_CAPTURE_USER_AUDIO_ATTACHMENTS=true` to attach user microphone audio to `user_speaking` spans.

> [!WARNING]
>
> As of v0.32.0, agent audio attachments are disabled by default. If you captured audio before v0.32.0, set `BRAINTRUST_CAPTURE_AGENT_AUDIO_ATTACHMENTS=true` to restore audio capture on `agent_speaking` spans.

### Tracing resources

* [Trace LLM calls](https://docs.braintrust.dev/instrument/trace-llm-calls.md) — Auto-instrumentation setup and supported libraries
* [LiveKit Agents overview](https://docs.livekit.io/agents/) — LiveKit Agents documentation

## OpenTelemetry

Braintrust attaches to LiveKit's built-in OpenTelemetry pipeline by registering a span processor with LiveKit's tracer provider.

> [!WARNING]
>
> Audio attachments aren't supported with OpenTelemetry. To record call audio, use [auto-instrumentation](#audio-attachments-python-auto).

### Setup

**1. Install dependencies**

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
pip install "braintrust[otel]" livekit-agents livekit-plugins-openai livekit-plugins-noise-cancellation opentelemetry-sdk
```

**2. Set your environment variables**

```bash title=".env" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
BRAINTRUST_API_KEY=your-api-key
BRAINTRUST_PARENT=project_name:livekit-demo
OPENAI_API_KEY=your-openai-api-key
```

### Configure tracing

Configure Braintrust's span processor and set it as LiveKit's tracer provider.

```python title="livekit_agent.py" theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
from braintrust.otel import BraintrustSpanProcessor
from livekit import agents
from livekit.agents import Agent, AgentSession, RoomInputOptions
from livekit.agents.telemetry import set_tracer_provider
from livekit.plugins import noise_cancellation, openai
from opentelemetry.sdk.trace import TracerProvider

def setup_braintrust_telemetry():
    """Setup Braintrust OTEL telemetry for agent monitoring"""
    trace_provider = TracerProvider()
    trace_provider.add_span_processor(BraintrustSpanProcessor())
    set_tracer_provider(trace_provider)

class Assistant(Agent):
    def __init__(self) -> None:
        super().__init__(instructions="You are a helpful voice AI assistant.")

async def entrypoint(ctx: agents.JobContext):
    # Setup telemetry
    setup_braintrust_telemetry()

    # Create agent session with OpenAI realtime model
    session = AgentSession(llm=openai.realtime.RealtimeModel(voice="coral"))

    # Start session with assistant agent
    await session.start(
        room=ctx.room,
        agent=Assistant(),
        room_input_options=RoomInputOptions(
            noise_cancellation=noise_cancellation.BVC(),
        ),
    )

# Run script locally with `python livekit_agent.py console`
if __name__ == "__main__":
    agents.cli.run_app(agents.WorkerOptions(entrypoint_fnc=entrypoint))
```

You can add attributes to spans using `span.set_attribute()` to enrich your traces with custom metadata.

### What Braintrust traces

These spans come from LiveKit's native OpenTelemetry instrumentation. With the OpenAI realtime model, the LLM, speech-to-text, and text-to-speech work happens inside a single realtime model call rather than as separate spans.

* Session spans (`agent_session`), the root span covering the full agent session
* Agent turn spans (`agent_turn`), with the model request, token usage, and realtime model metrics (`gen_ai.*`, `lk.realtime_model_metrics`)
* User speaking spans (`user_speaking`), with participant and speech detection details
* Agent speaking spans (`agent_speaking`), covering the agent's spoken response
* Activity lifecycle spans (`start_agent_activity`, `on_enter`, `on_exit`, `drain_agent_activity`)

### Tracing resources

* [OpenTelemetry Integration](https://docs.braintrust.dev/integrations/sdk-integrations/opentelemetry/send-traces-and-logs.md) — Braintrust's OpenTelemetry support
* [LiveKit Agents overview](https://docs.livekit.io/agents/) — LiveKit Agents documentation


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.