fix(tokenize): don't stall TTS streaming on angle-bracketed prose - #6701
Open
priyam-garg wants to merge 1 commit into
Open
fix(tokenize): don't stall TTS streaming on angle-bracketed prose#6701priyam-garg wants to merge 1 commit into
priyam-garg wants to merge 1 commit into
Conversation
_XML_TAG_RE matched any "<letter...>", so a markdown autolink or an
angle-bracketed address parsed as an open XML tag. With xml_aware=True
(expressive agents) the depth counter in _has_unclosed_xml_tags then never
returned to zero, and every later sentence was held until flush -- streaming
TTS degraded to end-of-turn batching for the rest of the turn.
Measured on main, first sentence emitted before flush:
text xml_aware=True False
Docs are at <https://docs.livekit.io> now. stalled streams
Email me at <bob@example.com> please. stalled streams
Press <ctrl+c> to quit. stalled streams
Require the tag name to be followed by whitespace, "/" or ">", as it is in
real markup. Hyphens join the name so xAI's <higher-pitch> and
<build-intensity> still match.
This is the same regression class as the bare "<" and digit-named "<5>"
fixes that TestPlainTextAngleBrackets already documents; the existing
test_tag_shaped_text_streams_when_not_xml_aware covers <bob@example.com>
but only asserts the xml_aware=False path, so the broken case sat next to
a passing test.
filter_markdown does not remove these spans (it rewrites [text](url) but
leaves autolinks and addresses intact), so they do reach the tokenizer.
There was a problem hiding this comment.
Pull request overview
This PR fixes an XML-markup detection regression in the sentence/token streaming layer that caused expressive (xml_aware=True) TTS streaming to stall after encountering angle-bracketed prose (e.g., markdown autolinks, emails, key chords). It tightens the XML tag-name matcher so only markup-shaped constructs affect the “unclosed tag depth” logic, restoring sentence-by-sentence streaming.
Changes:
- Refines
_XML_TAG_REto require the tag name be followed by whitespace,/, or>(and allows hyphenated tag names). - Adds targeted unit tests for angle-bracketed prose and hyphenated tags to prevent depth-counter stalls.
- Adds end-to-end streaming tests ensuring autolinks/emails still stream with
xml_aware=True.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
livekit-agents/livekit/agents/tokenize/token_stream.py |
Tightens XML tag regex so angle-bracketed prose doesn’t increment XML depth and stall streaming, while keeping real (including hyphenated) tags working. |
tests/test_tokenizer_xml_markup.py |
Adds regression + end-to-end streaming coverage for autolinks/emails under xml_aware=True and ensures hyphenated tags remain recognized. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
_XML_TAG_REmatched any<letter...>, so a markdown autolink or an angle-bracketedaddress parsed as an open XML tag. With
xml_aware=True(expressive agents) the depthcounter in
_has_unclosed_xml_tagsthen never returned to zero, and every latersentence was held until flush — streaming TTS degraded to end-of-turn batching for the
rest of the turn.
Measured on
main, first sentence emitted before flush:Require the tag name to be followed by whitespace,
/or>, as it is in real markup.Hyphens join the name so xAI's
<higher-pitch>/<build-intensity>still match.This is the same regression class as the bare
<and digit-named<5>fixes thatTestPlainTextAngleBracketsalready documents; the existingtest_tag_shaped_text_streams_when_not_xml_awarecovers<bob@example.com>but onlyasserts the
xml_aware=Falsepath, so the broken case sat next to a passing test.filter_markdowndoes not remove these spans (it rewrites[text](url)but leavesautolinks and addresses intact), so they do reach the tokenizer.
All three consumers of the regex were checked — the depth counter,
_is_xml_only, and_xml_wrap_tokenizer's strip-and-remap. Stricter matching is correct for all three: anautolink is text, so it shouldn't be stripped before blingfire either.
Testing
Full
--unitgate, before and after:main(baseline)The +4 are the new tests. The 9 errors are a pre-existing
Event loop is closedINTERNALERROR from
tests/concurrency.pyon Python 3.14 — identical on both sides,unrelated to this change.
I don't have data on how often models emit angle-bracket autolinks, so I'm not claiming
a frequency. The case for the fix is that it's one line, strictly more correct, and when
it does fire the cost is every remaining sentence in the turn.