Skip to content

Meter STT usage on audio streamed to the provider - #9

Open
abhinav-t41 wants to merge 5 commits into
mainfrom
feat/stt-usage-metering
Open

Meter STT usage on audio streamed to the provider#9
abhinav-t41 wants to merge 5 commits into
mainfrom
feat/stt-usage-metering

Conversation

@abhinav-t41

@abhinav-t41 abhinav-t41 commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Problem

STT providers bill on audio streamed to them — silence included — but we only measured VAD-detected turn audio (AudioBufferProcessor turns), systematically underestimating real usage. A 5-minute call with 40 seconds of speech still streams ~5 minutes of audio to the provider.

Fix

  • New SttUsageMeterMixin counts bytes at the run_stt seam — the exact point audio leaves for the provider, after all mute/reconnect/empty-frame guards — on both InstrumentedDeepgramSTTService and InstrumentedElevenLabsSTTService. Seconds = bytes ÷ (sample_rate × 2) for 16-bit mono PCM.
  • At session teardown, one usage.stt trace event is recorded per run:
    • streamed_seconds — the billed quantity (should match provider dashboards within ~1s)
    • speech_seconds — VAD turn audio, kept for analytics
    • provider_reported_seconds — Deepgram's own Metadata.duration, accumulated across reconnects, as a built-in calibration check (null for ElevenLabs)
  • Adds session_duration_sec accessor on SttEvaluationSession; the existing turn-audio capture is unchanged.

Docs & tests

  • docs/cost-metering-design.md — the broader cross-provider cost-comparison design this is step 2 of.
  • server/tests/test_stt_usage_meter.py — byte→seconds math, zero guards, per-instance state.

Testing

  • uv run pytest -q: 29 passed; the 2 test_agent_config.py failures also fail on clean main (pre-existing, unrelated).
  • ruff check: no new violations (4 pre-existing on main).
  • Verify live: run a voice test call, then
    select payload from trace_events where event_type = 'usage.stt' order by created_at desc limit 1;
    — for Deepgram sessions streamed_secondsprovider_reported_seconds; for ElevenLabs compare against the dashboard usage.

abhinav-t41 and others added 3 commits July 15, 2026 01:27
STT providers bill on audio streamed to them (silence included), but we
only measured VAD-detected turn audio, systematically underestimating
real usage. Add SttUsageMeterMixin that counts bytes at the run_stt
seam — the exact point audio leaves for the provider — on both
instrumented STT services, and emit a usage.stt trace event at session
teardown with streamed_seconds (billing truth), speech_seconds (VAD
turn audio, kept for analytics), and provider_reported_seconds
(Deepgram's own duration from ListenV1Metadata, for calibration).

Also documents the broader cross-provider cost-comparison design in
docs/cost-metering-design.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TTS providers bill on characters sent over the wire, not characters the
user hears: Pipecat pushes each sentence to the provider as the LLM
streams it, so on interruption Deepgram has already received (and bills
for) text whose audio is discarded by Clear. Pipecat's built-in
TTSUsageMetricsData metric cannot measure this — the Deepgram websocket
service never emits it, and the base-class fallback drops accumulated
text on interruption, which is exactly where billing diverges from the
transcript.

Add TtsUsageMeterMixin counting characters at the run_tts seam — the
exact prepared text sent to the provider — on both instrumented TTS
services, and emit a usage.tts trace event (provider, model, voice,
sent_characters) at session teardown next to usage.stt.

Also rewrite docs/cost-metering-design.md §6 with the verified billing
semantics: sent = billed for Deepgram; ElevenLabs deducts credits on
successful generation so sent_characters is a tight upper bound there;
per-provider differences belong in the pricing catalog, not the meter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants