Skip to main content
LiveKit Agents 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.
To trace LiveKit Agents with Braintrust’s TypeScript SDK, use auto-instrumentation, manual instrumentation, or OpenTelemetry. Auto-instrumentation and manual instrumentation trace sessions that use a STT-LLM-TTS pipeline, 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 to your agent’s project. We strongly recommend using @braintrust/audio to compress recordings and reduce upload size.
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 for the caller and the agent. Without them, traces include spans and metadata but no audio.
.env
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.
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.
After a call ends, its trace appears on the 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 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.
livekit_agent.js
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 with a TypeScript loader.
If you’re using a bundler, see Trace LLM calls for plugin and loader setup.
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 is on.
TypeScript
Register the same shutdown callback as in Setup so the session’s last spans are sent before the job process exits.
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().

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

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.
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 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() in a shutdown callback, as shown in Setup.

Configure recording encoding

The @braintrust/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:
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:
configure.mjs
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() 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.
configure.mjs

Tracing resources

OpenTelemetry

Braintrust attaches to LiveKit’s built-in OpenTelemetry pipeline by registering a span processor with LiveKit’s tracer provider.
Audio attachments aren’t supported with OpenTelemetry. To record call audio, use auto-instrumentation or manual instrumentation.

Setup

1

Install dependencies

@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

.env

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.
livekit_agent.ts

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