Skip to content

Fix glm47 parser dropping the outer } on object-valued final arguments - #32721

Closed
yuzhouo7 wants to merge 1 commit into
sgl-project:mainfrom
yuzhouo7:fix/glm47-finalizer-outer-brace
Closed

yuzhouo7 wants to merge 1 commit into
sgl-project:mainfrom
yuzhouo7:fix/glm47-finalizer-outer-brace

Conversation

@yuzhouo7

@yuzhouo7 yuzhouo7 commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

_process_xml_to_json_streaming() emits the outer { of the argument object on the first parameter, but it never emits a matching }. Every other brace it writes comes from json.dumps() of an argument value. Closing the outer object is therefore solely _finalize_tool_call()'s job.

Today the finalizer skips that close whenever the accumulated argument text already ends in }:

elif not self._last_arguments.endswith("}") and not self._sent_empty_object:

That suffix test conflates the close of an object-valued final argument with the close of the outer argument object. When the last argument is itself an object, the trailing } belongs to that argument, so the finalizer concludes the outer object is already closed and skips it:

final argument streamed on main with this PR
object-valued {"mode": "fast", "options": {"topic": "t"} — outer } missing, invalid JSON {"mode": "fast", "options": {"topic": "t"}}
scalar {"options": {"topic": "t"}, "mode": "fast"} — valid identical, unchanged

As a result, agent frameworks can reject otherwise-correct tool calls whenever the final argument is object-valued.

Modifications

Drop the suffix heuristic; close the outer object whenever it is still open.

not self._sent_empty_object is sufficient here. Reaching this branch already excludes the "no parameters seen yet" case, so if _sent_empty_object is false at this point, at least one parameter has been streamed and the outer object is open. Equivalently, at this branch not _sent_empty_object implies not _is_first_param — which is why the latter is not spelled out in the condition.

The state invariants this rests on:

  • _is_first_param is cleared exactly when the outer { is appended (json_output += "{" if self._is_first_param else ", ").
  • _sent_empty_object is set only on paths that have already emitted a complete empty {} object, so if it is false in this branch, the non-empty outer object still needs its closing }.
  • _reset_streaming_state() resets these flags together after each finalized tool call, so they cannot drift apart between calls.

Scope. Only final arguments whose serialization ends in } are affected — object values, including empty and nested ones. Arrays, numbers and strings (which end in " even when the text inside ends in }) are byte-identical before and after, and the no-argument {} path is untouched.

This PR extends the existing TestGlm47MoeDetector suite in test/registered/unit/function_call/test_function_call_parser.py. Three grouped regression tests cover the nine scenarios below; six of them fail on main and pass with this PR:

case main this PR
object-valued final argument ✗ ✓
empty object {} as final value ✗ ✓
nested object final ✗ ✓
single object argument ✗ ✓
two object-final calls in one response ✗ ✓
no-arg call followed by object-final call ✗ ✓
scalar final ✓ ✓
array final (asserts no over-close) ✓ ✓
no-argument tool → {} ✓ ✓

Note: #24147 is open against the same file. It fixes a different bug in a different function, where a closing tag is dropped when a value ends with <, and does not overlap textually with this change.

Accuracy Tests

Not applicable — this changes only the JSON framing of streamed tool-call arguments, never model outputs. No kernel or model forward code is touched.

Speed Tests and Profiling

Not applicable — the change removes one string comparison from a path that runs once per tool call.

Checklist


CI States

Latest PR Test (Base): ❌ Run #30505650747
Latest PR Test (Extra): ❌ Run #30505650600

@yuzhouo7
yuzhouo7 requested a review from JustinTong0323 as a code owner July 29, 2026 03:22
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@Dovis01

Dovis01 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Hi, @yuzhouo7

Verified by reading the code and reproducing the issue on main.

_process_xml_to_json_streaming() emits the outer { when the first argument is streamed, but it never emits the matching outer }. Any trailing } produced while parsing arguments belongs to the argument value itself, such as an object-valued parameter.

Because of this, the old endswith("}") check cannot distinguish between the closing brace of the final argument value and the closing brace of the outer arguments object. When the last argument is an object, the parser skips the outer close and produces invalid JSON.

The proposed fix is correct: at that branch, not self._sent_empty_object means at least one argument has already been streamed and the outer object is still open, so adding } is required.

The issue only affects streaming tool calls. Scalar, array, and no-argument cases remain unchanged, while the non-streaming path is unaffected because it rebuilds the arguments from the raw XML.

Thx for pointing this issue!

The finalizer owns the outer `}` of the argument object but skipped it
whenever the accumulated text already ended in `}` -- which is also the
case when the final argument is itself an object, so those calls emitted
invalid JSON. Adds the first tests for this detector; 6 of the 9 fail
without the fix.
@yuzhouo7

yuzhouo7 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Closing as superseded: the production fix for the glm47 parser dropping the outer } on object-valued final args landed on main via #36626 (elif not self._is_first_param and not self._sent_empty_object).

Rebase onto current main would leave no production diff; keeping this open would only duplicate that fix. Thanks for the review attention on this path.

@yuzhouo7 yuzhouo7 closed this Sep 9, 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.

2 participants