Skip to content

fix(soniox): widen final span to all timed tokens, never regress end_time - #6976

Open
rkfshakti wants to merge 1 commit into
livekit:mainfrom
rkfshakti:fix/soniox-span-timing
Open

fix(soniox): widen final span to all timed tokens, never regress end_time#6976
rkfshakti wants to merge 1 commit into
livekit:mainfrom
rkfshakti:fix/soniox-span-timing

Conversation

@rkfshakti

Copy link
Copy Markdown

Fixes #6885

Problem

A FINAL_TRANSCRIPT emitted at a natural endpoint can carry a start_time/end_time span far shorter than the speech its own text represents — e.g. "He's trying." with a 0.18s span.

Root cause

_TokenAccumulator.update() took start_time from the first token that carried start_ms and overwrote end_time from every token that carried end_ms:

if "start_ms" in token and not self._has_start_time:
    self._has_start_time = True
    self.start_time = float(token["start_ms"])
if "end_ms" in token:
    self.end_time = float(token["end_ms"])

Two failure mechanisms (both confirmed against the fake-WS harness):

  1. Late timing: leading tokens arrive without start_ms/end_ms, only a trailing token has them → the span collapses onto that trailing token.
  2. Regressing end: a trailing token whose end_ms is smaller than an earlier one pulls the end backwards.

merged_speech_data() (the interim path) already guards both with min(...)/max(...); the final path did not.

Fix

  • start_time = earliest start_ms seen (not just the first)
  • end_time = latest end_ms seen (never regresses)

Regression tests cover both mechanisms. Verified: 2 of 3 new tests fail on the old code, all 31 soniox tests pass with the fix.

…time

The endpoint path builds SpeechData from the final accumulator alone.
update() took start_time from the first token that carried start_ms and
overwrote end_time from every token that carried end_ms. When the
leading tokens of an utterance arrive without timing keys and only a
trailing token has them, the emitted span collapsed onto that trailing
token (e.g. two words in 180ms); a trailing token whose end_ms regressed
pulled the end backwards by the same mechanism.

start_time is now the earliest start_ms seen and end_time the latest
end_ms seen, matching the min/max guards merged_speech_data() already
applies on the interim path. Regression tests cover both mechanisms
(issue livekit#6885).
@rkfshakti
rkfshakti requested a review from a team as a code owner August 25, 2026 10:37
@CLAassistant

CLAassistant commented Aug 25, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@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: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

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.

soniox: endpoint final can report a span far shorter than its own text (2 words in 180ms)

2 participants