Skip to content

fix(openai): report realtime transcription usage per session - #2521

Open
rosetta-livekit-bot[bot] wants to merge 2 commits into
mainfrom
turfed-hilltop-herds
Open

rosetta-livekit-bot[bot] wants to merge 2 commits into
mainfrom
turfed-hilltop-herds

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Ports livekit/agents#7303.

Reports OpenAI realtime transcription duration and token usage as streamed STT metrics. Preserves audio-token breakdowns in session usage and reports, and attributes each metric to the session current transcription model.

Source diff coverage
  • livekit-agents/livekit/agents/metrics/base.py: adapted to agents/src/metrics/base.ts; added total/audio token fields and an internal resolver because JS metrics are structural objects rather than Pydantic models.
  • livekit-agents/livekit/agents/metrics/usage.py: ported to agents/src/metrics/model_usage.ts; preserves and aggregates input audio tokens separately.
  • livekit-agents/livekit/agents/metrics/usage_collector.py: ported to agents/src/metrics/usage_collector.ts; extends the deprecated summary with STT token totals.
  • livekit-agents/livekit/agents/metrics/utils.py: ported to agents/src/metrics/utils.ts; logs input, output, total, and input-audio tokens.
  • livekit-plugins/livekit-plugins-openai/livekit/plugins/openai/realtime/realtime_model.py: adapted to plugins/openai/src/realtime/realtime_model.ts and plugins/openai/src/realtime/api_proto.ts; explicit TypeScript wire validation replaces Pydantic validation, and provider seconds are converted to framework milliseconds.
  • tests/test_metrics_usage.py: adapted to agents/src/metrics/model_usage.test.ts; ports omitted/explicit total-token behavior and STT token aggregation coverage.
  • tests/test_realtime/test_openai_realtime_model.py: adapted to plugins/openai/src/realtime/realtime_model.test.ts and agents/src/voice/report.test.ts; ports duration usage, token usage, malformed usage, session model updates, session aggregation, and report serialization.
  • Not applicable: none.

Target-only release/generated artifacts: agents/etc/agents.api.md, plugins/openai/etc/agents-plugin-openai.api.md, and .changeset/realtime-transcription-session-usage.md.

Validation

  • pnpm build: passed (all 40 workspace packages).
  • Source-equivalent focused tests: passed, 88 tests.
  • Changed-file Prettier check: passed.
  • Changed-file ESLint: passed with two pre-existing warnings.
  • Full agents suite run: 2712 passed, 5 skipped, 4 unrelated telemetry/loop_monitor.test.ts failures because this host exposes process rather than thread CPU accounting.
  • Full OpenAI suite run: 214 passed, 2 skipped; unrelated failures were a corrupt/missing Silero ONNX asset and a live validation request returning 404.
  • Full OpenAI package lint run: blocked by the existing plugins/openai/src/ws/llm.ts:127 no-misused-promises error.
  • cue-cli voice validation attempted; the configured Cue project returned 401 Unauthorized - invalid API key, so no live framework-event assertion was available.

Ported from livekit/agents#7303

Original PR description

Supersedes #5029, adopting @bml1g12's original transcription usage implementation.

Report OpenAI realtime ASR duration and token usage as STT metrics. Preserve audio-token counts in session usage and reports, attribute usage to each session's current transcription model, and mark realtime transcription as streamed. input_tokens includes both audio and text; input_audio_tokens is the audio subset.

Addresses AGT-3525.

Initial prompt and agent context

Model: GPT-6

can you take a look at pr 5209

5029, sorry

okay, can we create a new PR that supersedes this with those fixes in place?

@rosetta-livekit-bot
rosetta-livekit-bot Bot requested a review from a team as a code owner September 17, 2026 10:41
@changeset-bot

changeset-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7330703

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 39 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-azure Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-krisp Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-meta Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-protoface Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment on lines +74 to +75
/** Audio input tokens, a subset of inputTokens when reported by the provider. */
inputAudioTokens?: number;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Remote usage drops audio tokens

When inputAudioTokens reaches remote usage, sessionUsageToProto omits it. Remote consumers lose the audio-token billing breakdown.

Learn more

The session usage type now preserves audio input tokens, and local reports serialize them. Remote sessions use a separate protobuf conversion. That conversion copies STT input tokens, output tokens, and duration, but not inputAudioTokens in sessionUsageToProto. Every remote usage event and getSessionUsage response therefore drops this new value.

Example: A realtime transcription records 10 input tokens, including 8 audio tokens. Local session.usage contains inputAudioTokens: 8, while a remote client receives only 10 input tokens and cannot recover the audio subset.

Recommended fix: Add the audio-token field to the protocol's STTModelUsage, update @livekit/protocol, and map su.inputAudioTokens in sessionUsageToProto. Add coverage for both remote usage events and getSessionUsage responses.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +283 to +284
sttUsage.inputAudioTokens =
(sttUsage.inputAudioTokens ?? 0) + (metrics.inputAudioTokens ?? 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Unknown audio tokens become zero

For metrics without inputAudioTokens, ModelUsageCollector stores zero in the optional field. flatten() reports a measured zero instead of unavailable data.

Learn more

inputAudioTokens is optional because most STT providers do not report an audio-token breakdown. The collector initializes the field to zero and also assigns zero whenever an incoming metric omits it. Consequently, flatten() includes inputAudioTokens: 0 even though no provider supplied that measurement. The report serializer hides this through zero filtering, but direct session usage consumers still receive the false value.

Example: Deepgram emits an STT metric with no inputAudioTokens. The collector returns inputAudioTokens: 0, making consumers interpret the breakdown as reported and free rather than unavailable.

Recommended fix: Do not initialize inputAudioTokens in getSTTUsage. Increment it only when metrics.inputAudioTokens !== undefined, preserving undefined until a provider reports the field.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants