Skip to content

fix(sarvam): emit speech timing for STT metrics - #5995

Merged
tinalenguyen merged 6 commits into
livekit:mainfrom
dhruvladia-sarvam:fix/sarvam-stt-speech-timing-metrics
Jun 10, 2026
Merged

fix(sarvam): emit speech timing for STT metrics#5995
tinalenguyen merged 6 commits into
livekit:mainfrom
dhruvladia-sarvam:fix/sarvam-stt-speech-timing-metrics

Conversation

@dhruvladia-sarvam

@dhruvladia-sarvam dhruvladia-sarvam commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Tracks Sarvam STT stream audio position and speech boundaries so START_OF_SPEECH, FINAL_TRANSCRIPT, and END_OF_SPEECH carry timing data that LiveKit can use for EOU metrics.
  • Defers final transcript emission until a speech-end timestamp is known, while preserving FINAL_TRANSCRIPT before END_OF_SPEECH ordering for both transcript-before-EOS and EOS-before-transcript provider ordering.
  • Adds focused Sarvam plugin tests for timestamp fallback behavior and event ordering without modifying audio_recognition.py or other STT providers.

Test plan

  • uv run pytest livekit-plugins/livekit-plugins-sarvam/tests/test_speech_timing.py livekit-plugins/livekit-plugins-sarvam/tests/test_language_probability.py -v
  • uv run ruff check livekit-plugins/livekit-plugins-sarvam/livekit/plugins/sarvam/stt.py livekit-plugins/livekit-plugins-sarvam/tests/test_speech_timing.py
  • python -m py_compile livekit-plugins/livekit-plugins-sarvam/livekit/plugins/sarvam/stt.py livekit-plugins/livekit-plugins-sarvam/tests/test_speech_timing.py
  • Local console smoke test with sarvam.STT(model="saaras:v3"), vad=None, and turn_handling={"turn_detection": "stt"} produced non-zero EOU metrics, including transcription_delay=0.146s and end_of_utterance_delay=0.501s.

Notes

  • This is scoped to livekit-plugins-sarvam's sarvam.STT path in stt.py; it does not change STTStreaming or framework turn detection behavior.
  • The console smoke test emitted a non-interactive terminal key-listener traceback from console mode, but the agent continued and emitted the expected EOU metrics.

Track Sarvam STT speech boundaries in the plugin so LiveKit can compute EOU metrics when using internal VAD with STT turn detection.

Co-authored-by: Cursor <cursoragent@cursor.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Keep Sarvam STT final transcripts visible immediately while still using delayed EOS ordering only when speech-end arrives before the transcript.

Co-authored-by: Cursor <cursoragent@cursor.com>

@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 new potential issues.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +1126 to +1133
async def _emit_pending_eos_after_timeout(self, timeout: float = 0.1) -> None:
try:
if timeout > 0:
await asyncio.sleep(timeout)
if self._pending_eos and not self._eos_emitted_for_utterance:
self._emit_end_of_speech()
except asyncio.CancelledError:
raise

@devin-ai-integration devin-ai-integration Bot Jun 7, 2026

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.

📝 Info: Orphaned FINAL_TRANSCRIPT can be emitted after END_OF_SPEECH when fallback fires before transcript

When the EOS fallback timer fires before the transcript arrives, _emit_end_of_speech sets _pending_eos=False and _eos_emitted_for_utterance=True. A subsequent late transcript then goes through the else branch at line 1662 (_send_final_transcript called directly), emitting a FINAL_TRANSCRIPT after END_OF_SPEECH without a preceding START_OF_SPEECH. This is tested explicitly in test_late_transcript_after_eos_fallback_is_emitted_after_eos and appears intentional (better to emit a late transcript than lose it), but downstream consumers like audio_recognition.py should handle FINAL events that arrive after EOS gracefully. The existing audio_recognition.py code does handle this since it processes FINAL_TRANSCRIPT independently of speech state.

Open in Devin Review

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

Cancel delayed EOS fallback work during stream shutdown and document the fallback timeout tradeoff for late transcripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
devin-ai-integration[bot]

This comment was marked as resolved.

@tinalenguyen tinalenguyen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

thank you for the PR! i left a small comment but otherwise i tested it and it lgtm

return None
return float(value)

def _ensure_utterance_timing_state(self) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

all these fields are already set in init, ithink we can remove this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah I see, had left it there as a guard for tests that use SpeechStream.new() without calling init. Now removed as suggested, thank you

@tinalenguyen
tinalenguyen merged commit 3026d37 into livekit:main Jun 10, 2026
15 checks passed
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