Skip to content

ElevenLabsRealtimeSTTService: server re-sends stale partial_transcript messages, opening phantom user turns #5197

Description

@vahidkowsari

Summary

Related to #5196 (duplicate finals). The ElevenLabs Scribe v2 realtime server re-sends partial_transcript messages that carry no new information, and ElevenLabsRealtimeSTTService pushes each one as a fresh InterimTranscriptionFrame. With the default user-turn start chain (TranscriptionUserTurnStartStrategy(use_interim=True)), a stale repeat arriving while the bot is speaking opens a phantom user turn and cancels the in-flight bot reply mid-sentence.

Server behavior (probed against the live API, 2026-08-02)

wss://api.elevenlabs.io/v1/speech-to-text/realtime, model_id=scribe_v2_realtime, commit_strategy=manual:

  • Shortly after a commit, the server re-sends a partial_transcript repeating the text that was just committed.
  • The last uncommitted partial is re-emitted roughly every 1s indefinitely — even when the subsequent input audio is pure silence (all-zero PCM).

Observed timeline (audio with speech, commit at 1.70s, silence streamed afterwards):

[1.31s] partial_transcript  "Hello. How's your day going?"
[1.41s] partial_transcript  "Hello. How's your day going?"   <- identical repeat
[1.70s] >> commit
[1.81s] partial_transcript  "Hello. How's your day going?"   <- post-commit echo of committed text
[1.81s] committed_transcript
[1.91s] committed_transcript_with_timestamps
[2.92s] partial_transcript  "Good."                          <- uncommitted tail
[3.82s] partial_transcript  "Good."                          <- 1s-cadence refresh, input is silence
[4.83s] partial_transcript  "Good."
...

Impact

In a voice pipeline, the post-commit echo partial lands ~0.1-1s after the final — right as the bot starts answering — and triggers an interruption. In our production-like testing every bot reply was cut off within ~1s.

Suggested fix

Dedupe in _on_partial_transcript: drop a partial whose text equals the previous partial's text (refresh, no new info) or the last committed text (post-commit echo), clearing the committed-echo suppression once genuinely new text arrives. Genuine partial progression (text grows/changes) passes through, preserving the soft-speech turn-start fallback.

Affects at least 1.6.0 and 1.7.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions