Skip to content

Fix: glm47 streaming parser drops closing tag when value ends with '<' - #24147

Closed
Evrard-Nil wants to merge 1 commit into
sgl-project:mainfrom
Evrard-Nil:fix/glm47-streaming-tool-call-trailing-lt
Closed

Evrard-Nil wants to merge 1 commit into
sgl-project:mainfrom
Evrard-Nil:fix/glm47-streaming-tool-call-trailing-lt

Conversation

@Evrard-Nil

@Evrard-Nil Evrard-Nil commented Apr 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Glm47MoeDetector._process_xml_to_json_streaming (the IN_VALUE state machine in python/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 streamed tool_calls.function.arguments ends 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:dev glm5-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):

{
{"ptn": 
"."
<</arg_value>     ← bug
}

Concatenated, the client receives {"ptn": ".<</arg_value>} unparseable.

The non-streaming detect_and_parse regex path is unaffected; only parse_streaming_increment is broken. Cloud / proxy layers that stream from SGLang and reassemble preserve the broken output even when the user requests stream: false.

Fix

When the buffer fails the prefix check, slide forward to the smallest k > 0 such that buffer[k:] is still a prefix of </arg_value>. Emit buffer[:k] as value content and keep buffer[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_than value .< 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_prefix value </arg_va (a longer 8-char prefix overlap) followed by the actual closing tag.

All 9 existing TestGlm47MoeDetector tests still pass, the change does not affect the prefix-from-zero or no-overlap paths.

Ran 11 tests in 0.003s
OK

Test plan

  • New tests fail on main, pass on this branch.
  • All existing TestGlm47MoeDetector tests pass.
  • pre-commit run --files clean on both touched files (ruff, isort, black, codespell).

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

@gemini-code-assist gemini-code-assist 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.

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.

@Evrard-Nil

Copy link
Copy Markdown
Contributor Author

@JustinTong0323 @CatherineSue when either of you has a moment, would you mind taking a look? Small, isolated fix in glm47_moe_detector.py (streaming state machine drops the closing tag when an arg value ends with <), with two regression tests covering it. Happy to address feedback. Thanks!

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

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:
sgl-kernel/ -> python/sglang/kernels/aot/, python/sglang/jit_kernel/
-> python/sglang/kernels/jit/, docs/ -> docs/docs/ (.mdx),
bench_serving.py -> benchmark/serving.py, test/srt/ -> test/registered/.

@github-actions github-actions Bot closed this Sep 1, 2026
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.

1 participant