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

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.

@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.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

βœ… Devin Review: No Issues Found

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

Open in Devin Review

@rkfshakti

Copy link
Copy Markdown
Author

Friendly ping for review β€” this fixes the Soniox span-timing collapse from #6885 (end_time regressing onto a late-timed trailing token). All CI checks (unit, type-check across 3.10/3.13, ruff, Devin review, CLA) are green on the current head. The auto-assigner skipped reviewer assignment, so this may have fallen through the cracks β€” could a maintainer pick it up or point me at the right reviewer? Happy to update onto latest main if needed.

…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 force-pushed the fix/soniox-span-timing branch from 294ee46 to 4e649aa Compare September 12, 2026 17:18

@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.

Devin Review

Comment on lines +719 to +723
if "start_ms" in token:
start_ms = float(token["start_ms"])
if not self._has_start_time or start_ms < self.start_time:
self._has_start_time = True
self.start_time = start_ms

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.

🟑 Late timing still collapses transcript spans

When only a trailing token is timed, _TokenAccumulator assigns that token's start and end to the entire transcript. No earlier start_ms exists, so final spans remain shorter than their text.

Learn more

The accumulator can only minimize timestamps it receives. If every leading token lacks timing, the first observed start_ms still belongs to the trailing token. The new test at test_token_accumulator_span_covers_all_timed_tokens preserves this collapsed 180 ms span, so it passes both implementations and does not verify its stated regression.

Example: Tokens " He's" and " trying" have no timing. Token "." has start_ms=43663 and end_ms=43843. Both implementations emit " He's trying." from 43.663 to 43.843 seconds.

Recommended fix: Define a fallback start source for leading untimed tokens, such as provider speech-onset metadata or another reliable stream timestamp. Track that fallback separately from token timing, and add an end-to-end assertion that the final span begins before 43.663 seconds for this case.

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.

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

2 participants