Skip to content

fix(tokenize): ignore stray closing tags in the XML depth check - #7742

Merged
xianshijing-lk merged 1 commit into
livekit:mainfrom
ANDROIDHASSAN:fix/xml-stray-closing-tag
Oct 14, 2026
Merged

xianshijing-lk merged 1 commit into
livekit:mainfrom
ANDROIDHASSAN:fix/xml-stray-closing-tag

Conversation

@ANDROIDHASSAN

@ANDROIDHASSAN ANDROIDHASSAN commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Problem: with xml_aware=True, the sentence stream won't emit a token for which _has_unclosed_xml_tags is true, so a wrapping tag like <emotion>…</emotion> stays in one chunk. The depth counter in that check is decremented on every closing tag, including one whose opener isn't in the text. That happens when the opener was emitted in an earlier chunk, or the LLM never sent it. The counter then goes negative and cancels out the next real opening tag, so the wrapping tag looks closed and its sentences are split across TTS chunks.

_has_unclosed_xml_tags("</b> Now <emotion value='sad'>I am sorry.")  # False on main, should be True

Streaming "Okay then.</b> Now <emotion value='sad'>I am sorry. Truly sorry.</emotion> Bye." word by word through blingfire.SentenceTokenizer(min_sentence_len=1, stream_context_len=5, xml_aware=True) gives on main:

['Okay then.', "</b> Now <emotion value='sad'>I am sorry.", 'Truly sorry.', '</emotion> Bye.']

Without the stray </b>, the same text keeps the <emotion> span in one chunk.

Fix: clamp the counter at zero, so a closing tag without an opener in the text no longer affects the check.

Tests

One new case in tests/test_tokenizer_xml_markup.py: TestStreamingTokenizer::test_stray_closing_tag_does_not_unbalance_a_later_wrapping_tag. It checks _has_unclosed_xml_tags directly, and it checks that the streamed <emotion> span stays in one chunk. Both assertions fail on main.

$ uv run pytest tests/test_tokenizer_xml_markup.py tests/test_tokenizer.py tests/test_expressive_toggle.py tests/test_transcript_sync_markup.py tests/test_transcript_sync_timed.py -q
109 passed

ruff format and ruff check are clean.

Scope: one line in _has_unclosed_xml_tags, and nothing else reads its depth counter. Text without a stray closing tag gets the same result as before.

…h check

_has_unclosed_xml_tags decremented its depth counter on every closing
tag, so a closing tag whose opener is not in the text (already emitted
in an earlier chunk, or never sent by the LLM) drove it negative and
cancelled out a later opening tag. The stream then treated an open
wrapping tag as closed and split its sentences across TTS chunks.

Clamp the counter at zero.
@ANDROIDHASSAN
ANDROIDHASSAN requested a review from a team as a code owner October 11, 2026 13:36

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

Devin Review

@ANDROIDHASSAN ANDROIDHASSAN changed the title fix(tokenize): keep a stray closing tag from unbalancing the XML depth check fix(tokenize): ignore stray closing tags in the XML depth check Oct 11, 2026
@ANDROIDHASSAN
ANDROIDHASSAN force-pushed the fix/xml-stray-closing-tag branch from ff94feb to bca4cb3 Compare October 13, 2026 07:32

@xianshijing-lk xianshijing-lk left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this looks safe, thanks for the fix

@xianshijing-lk
xianshijing-lk merged commit aad6a4b into livekit:main Oct 14, 2026
15 checks passed
@ANDROIDHASSAN
ANDROIDHASSAN deleted the fix/xml-stray-closing-tag branch October 14, 2026 22:26
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