Skip to content

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

Open
rosetta-livekit-bot[bot] wants to merge 8 commits into
mainfrom
port-sarvam-stt-timing
Open

fix(sarvam): emit speech timing for STT metrics#1744
rosetta-livekit-bot[bot] wants to merge 8 commits into
mainfrom
port-sarvam-stt-timing

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Port of livekit/agents#5995.\n\nSummary:\n- track Sarvam streaming audio position per utterance\n- emit final transcript and end-of-speech timing so STT-based EOU metrics can use provider timing\n- emit Sarvam recognition usage when websocket metrics include audio duration\n\nValidation:\n- pnpm --filter @livekit/agents-plugins-test build\n- pnpm --filter @livekit/agents-plugin-silero build\n- pnpm --filter @livekit/agents-plugin-openai build\n- pnpm --filter @livekit/agents-plugin-sarvam build\n- pnpm --filter @livekit/agents-plugin-sarvam lint


Ported from livekit/agents#5995

Original PR description

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.

toubatbrian and others added 8 commits May 28, 2026 14:43
…1525)

Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com>
Co-authored-by: u9g <jason.lernerman@livekit.io>
Agent.llmNode now returns ReadableStream<ChatChunk | string | FlushSentinel>,
but the agent_v2 hook overrides and AgentHookAdapter still declared the
narrower ChatChunk | string union, so passing super.llmNode as the fallback
failed to type-check. Widen the override return types and the adapter's
fallback/return signatures to include FlushSentinel.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Brian Yin <brian.yin@livekit.io>
Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com>
Co-authored-by: u9g <jason.lernerman@livekit.io>
Catch end-call close listener errors to avoid unhandled rejections during shutdown, and make public tool type guards return false for null inputs.
@changeset-bot

changeset-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c201f8b

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

This PR includes changesets to release 34 packages
Name Type
@livekit/agents-plugin-sarvam Patch
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-assemblyai 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-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch
@livekit/agents-plugins-test 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.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment thread plugins/sarvam/src/stt.ts

@devin-ai-integration devin-ai-integration Bot Jun 10, 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.

🔴 Utterance state not reset on WebSocket reconnect causes missed START_OF_SPEECH and stale timing

When the WebSocket reconnects (e.g., after server idle timeout or error), #runWS only resets #speaking to false but does not reset the new utterance-tracking state variables: #pendingEos, #pendingFinalData, #utteranceSpeechEndAudioPos, #utteranceSpeechEndWallTime, #finalReceivedForUtterance, #eosEmittedForUtterance. If the previous session ended with #eosEmittedForUtterance = true (a completed utterance), or #pendingEos = true (an in-flight utterance), the new session inherits this stale state.

For example, after a completed utterance: #eosEmittedForUtterance = true persists. When a new transcript arrives in the new session, the guard at line 960 (!this.#speaking && !this.#pendingEos && !this.#eosEmittedForUtterance) evaluates to false because #eosEmittedForUtterance is still true. This skips emitting START_OF_SPEECH for the new utterance. The transcript then falls through to #sendFinalTranscript(td) at line 982 without a preceding START_OF_SPEECH, breaking the expected event sequence for the downstream voice pipeline (audio_recognition.ts:969-997).

Open in Devin Review

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

Comment thread plugins/sarvam/src/stt.ts
Comment on lines +561 to +563
#audioPosition = 0;
#utteranceStartAudioPos = 0;
#utteranceSpeechEndAudioPos?: 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.

🚩 #audioPosition accumulates across WebSocket sessions creating mixed time domains

#audioPosition (line 561) is initialized to 0 in the constructor and monotonically increases across WebSocket reconnections. The server, however, resets its audio timeline on each new session. This means that after a reconnect, the fallback startTime (from #utteranceStartAudioPos = #audioPosition) and endTime (from #utteranceSpeechEndAudioPos = #audioPosition) are in a cumulative client-side time domain, while server-provided speech_start/speech_end values are session-relative. When both sources are mixed (e.g., server provides speech_start but not speech_end), the resulting startTime and endTime can be in different time domains. The downstream consumer at agents/src/voice/audio_recognition.ts:828-834 converts endTime to wall-clock time via endTime * 1000 + inputStartedAt, which would produce an incorrect epoch time if endTime includes accumulated position from previous sessions. This is a design consideration rather than a clear bug, since the server usually provides both or neither timing fields.

Open in Devin Review

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

Base automatically changed from 1.5.0 to main July 2, 2026 13:48

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

Open in Devin Review

Comment on lines +797 to 799
const newToolNames = new Set(Object.keys(newToolCtx.functionTools));
const toolsAdded = [...newToolNames].filter((name) => !oldToolNames.has(name));
const toolsRemoved = [...oldToolNames].filter((name) => !newToolNames.has(name));

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.

🚩 Provider tool additions/removals are not tracked in AgentConfigUpdate during updateTools

In _startSession (agents/src/voice/agent_activity.ts:498-500), the initial AgentConfigUpdate includes both function tool names and provider tool IDs. However, updateTools at agents/src/voice/agent_activity.ts:786-799 only diffs function tool names (Object.keys(newToolCtx.functionTools)), so adding or removing a provider tool at runtime produces no AgentConfigUpdate entry in the chat history. The actual tool update still reaches the realtime session (line 814) and the chat context copy (line 818-819), so the pipeline works correctly — but the history won't reflect the change, which could affect summarization or debugging.

Open in 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.

1 participant