Fix: glm47 streaming parser drops closing tag when value ends with '<' - #24147
Evrard-Nil wants to merge 1 commit into
Conversation
In `Glm47MoeDetector._process_xml_to_json_streaming`, the IN_VALUE state
machine accumulates characters into `_xml_tag_buffer` and only retains the
buffer while it is a prefix of "</arg_value>" from position 0. When a
value ends with a character that overlaps the start of the closing tag
(e.g. ".<" followed immediately by "</arg_value>"), the buffer briefly
becomes "<<", which is not a prefix from position 0. The current code
ejects the entire buffer as content, so the second '<' — the one that
actually starts "</arg_value>" — is consumed as value content, the rest
of the closing tag follows the same fate, and the closer is never
detected. The streamed `tool_calls.function.arguments` then contains
literal "</arg_value>" and an unclosed string, which clients reject as
malformed JSON.
Slide forward when the prefix check fails: find the smallest k > 0 such
that buffer[k:] is still a prefix of "</arg_value>", emit buffer[:k] as
content, and keep buffer[k:] buffered for the next character. For the
".<" case this leaves a single '<' in the buffer to seed the closing
tag match. Behavior is unchanged for any value whose tail does not
overlap the start of "</arg_value>".
Repro: a GLM-5.1-FP8 SGLang server with `--tool-call-parser glm47`
emitting a string-typed argument whose value ends in '<' (occurs in the
wild on long-context tool-calling sessions). Two streaming regression
tests cover the trailing-'<' case and a longer-overlap case
("</arg_va" + closing).
There was a problem hiding this comment.
Code Review
This pull request fixes a bug in the GLM4-7B MoE detector where argument values ending with characters that overlap with the closing tag (e.g., "<") would cause the closing tag to be missed and leaked into the JSON output. The fix involves sliding the XML tag buffer to retain potential prefixes of the closing tag instead of clearing it entirely. New unit tests were added to cover these edge cases in streaming scenarios. I have no feedback to provide.
|
@JustinTong0323 @CatherineSue when either of you has a moment, would you mind taking a look? Small, isolated fix in |
|
Thanks @Evrard-Nil. Closing this because it has had no updates in 122 days. Reopen it if the work is still relevant. Some directories moved recently, so an older branch may need retargeting: |
Summary
Glm47MoeDetector._process_xml_to_json_streaming(the IN_VALUE state machine inpython/sglang/srt/function_call/glm47_moe_detector.py) cannot recover when an argument value ends with a character that overlaps the start of</arg_value>. For a value like.<followed immediately by the closing tag, the buffer accumulates<<. That string is not a prefix of</arg_value>from position 0, so the current code ejects the entire buffer as value content including the second<, which is the actual start of the closing tag. The remaining 11 characters of</arg_value>are then ejected one by one as content, the closer is never detected, and the streamedtool_calls.function.argumentsends up containing a literal</arg_value>substring with an unclosed string. Clients reject the result as malformed JSON.Repro
Observed on a deployed GLM-5.1-FP8 SGLang server (
lmsysorg/sglang:devglm5-hopper-*) running with--tool-call-parser glm47, on a long-context tool-calling session where the model emitted a string-typed argument whose value ended in<. Streamed arguments deltas (verbatim from the server):Concatenated, the client receives
{"ptn": ".<</arg_value>}unparseable.The non-streaming
detect_and_parseregex path is unaffected; onlyparse_streaming_incrementis broken. Cloud / proxy layers that stream from SGLang and reassemble preserve the broken output even when the user requestsstream: false.Fix
When the buffer fails the prefix check, slide forward to the smallest
k > 0such thatbuffer[k:]is still a prefix of</arg_value>. Emitbuffer[:k]as value content and keepbuffer[k:]in the buffer for the next character. For<<this leaves one<buffered to seed the closing-tag match; for any value whose tail does not overlap the start of the closing tag, behavior is unchanged.Tests
Two new unit tests in
test/registered/unit/function_call/test_function_call_parser.py::TestGlm47MoeDetector:test_streaming_value_ending_with_less_thanvalue.<followed by</arg_value>, exercised with three chunk arrangements (single chunk, value/close split, char-by-char). Both fail before the fix and pass after.test_streaming_value_ending_with_closing_tag_prefixvalue</arg_va(a longer 8-char prefix overlap) followed by the actual closing tag.All 9 existing
TestGlm47MoeDetectortests still pass, the change does not affect the prefix-from-zero or no-overlap paths.Test plan
main, pass on this branch.TestGlm47MoeDetectortests pass.pre-commit run --filesclean on both touched files (ruff, isort, black, codespell).