Skip to content

Feature proposal: attachOtelTracer(session, tracer) helper for converting metrics_collected events into OpenTelemetry spans #1407

@karan-dhir

Description

@karan-dhir

Summary

Add an opt-in helper that converts the SDK's existing metrics_collected events into OpenTelemetry spans for STT, LLM, and TTS. Filing per CONTRIBUTING.md ("open an issue first to discuss the viability and scope") — happy to PR once design is agreed.

Why

Production voice agents need per-turn observability (Tempo, Honeycomb, etc.). Today the canonical path is to subclass inference.STT/LLM/TTS and span-wrap the call sites — we shipped a wrapper doing exactly that and it works, but it's strictly worse than what the SDK could offer:

  • SDK-version risk every release (private fields, prototype checks, etc.)
  • No clean access to requestId from outside
  • Every team reinvents the same shape, with subtly different attribute names

Reading agents/src/stt/stt.ts, the SDK already emits everything a span needs:

this.emit('metrics_collected', {
  type: 'stt_metrics',
  requestId, timestamp, durationMs, label,
  audioDurationMs, streamed,
  metadata: { modelProvider, modelName },
});

Same for llm_metrics and tts_metrics. The helper just bridges these to OTel.

Proposed API

import { trace } from '@opentelemetry/api';
import { attachOtelTracer } from '@livekit/agents/otel'; // or a separate package

const tracer = trace.getTracer('voice-agent');
attachOtelTracer(session, tracer);
// optionally:
attachOtelTracer(session, tracer, {
  spanNames: { stt: 'stt.transcribe', llm: 'llm.completion', tts: 'tts.synthesize' },
  activeContext: () => myAppContext,         // for parent-context propagation
  attributes: { stt: (m) => ({ ... }), ... } // optional attribute renamer
});

Implementation is small — listen to the three event types, build a span at timestamp - durationMs, set OTel GenAI-conventional attributes, end at timestamp. @opentelemetry/api stays as a peer dep so the main package is untouched for users who don't want OTel.

Decisions only maintainers can make

  1. Distribution: subpath export @livekit/agents/otel vs. separate package @livekit/agents-otel?
  2. Attribute naming: strict OTel GenAI semantic conventions (gen_ai.system, gen_ai.request.model, gen_ai.usage.input_tokens, etc.) — or LiveKit-flavored (livekit.stt.*) — or both with gen_ai.* as default and a renamer hook?
  3. Parent-context propagation: take an activeContext?: () => Context | undefined callback, rely solely on context.active() at emission time, or both?

What I can contribute

Happy to PR the helper + tests + a docs/cookbook page once shape is agreed. Will follow:

  • REUSE-3.2 SPDX headers on new files
  • pnpm -w format:write and pnpm -w lint:fix
  • TypeDoc on every new public symbol
  • CLA on first PR

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions