[Frontend] glm47 tool parser: required-first schema order in prompt rendering and strict structural tags - #55558
Draft
JaredforReal wants to merge 2 commits into
Draft
JaredforReal wants to merge 2 commits into
JaredforReal wants to merge 2 commits into
Conversation
…first GLM-4.7/5.x renders tool schemas verbatim into the prompt, and the model tends to emit argument keys in the rendered property order. When a bulky optional field precedes a small required one (e.g. a "choices" array before a required "tag" label), the model often never returns to the required field once the object feels complete. Reproduced on GLM-5.3-Flash with a customer-reported request: 50-66% of collect_preferences calls omitted the required "tag" (temp=1; ~50% even at temp=0 - the first key of each item object is a near-tie between the content fields, p(tag-first) ~= 0). Reordering each object schema's "properties" so fields declared in "required" come first (in declaration order) changes the trajectory: with prompt -> tag -> choices the model commits to the required label right after the question (measured p(tag | prompt written) ~ 0.9998, vs ~0.0002 after a "choices"-first start). JSON Schema property order is not semantically meaningful, so validation and clients are unaffected. Opt in per tool parser via the reorder_tool_schema_required_first class attribute; enabled for the glm47 parser family. The reorder runs in OnlineRenderer right after the per-request tool dicts are built - the parser's adjust_request hook runs after chat-template rendering, which is too late to affect the prompt. Tested on zai-org/GLM-5.3-Flash (4x GB300, TP4, native FP8, --tool-call-parser glm47 --reasoning-parser glm45), customer-reported request, n=30 serial non-streaming runs per configuration: - baseline (original schema order): 50% (n=30) / 66% (n=100) failures - this change, original client payload: 13% (4/30) - same reorder applied client-side: 3% (1/30) Residual failures all start the item object with the optional "choices" key; temp=0 and streaming behave the same, and MTP spec decode on/off does not change the rate. Unit tests: pytest tests/tool_parsers/test_utils.py -k ReorderPropertiesRequiredFirst pytest tests/tool_parsers/test_glm47_moe_tool_parser.py This change was implemented with AI assistance (Kimi Code CLI). Co-authored-by: Kimi Code CLI <noreply@moonshot.cn> Signed-off-by: Jared Wen <w13431838023@gmail.com>
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…he required-first schema
The glm47 parser renders tool schemas into the prompt with required
properties first (reorder_tool_schema_required_first), but the strict-mode
structural tag was still built from the client's original schema order.
xgrammar's builtin glm_4_7 tag pins object keys to the schema's declared
order (any_order=False), so the prompt and the grammar disagreed: after the
model committed to a required key that the prompt placed early, every
optional key declared before it became unreachable and was silently dropped.
Declare reorder_tool_schema_required_first on ToolParser and apply the same
reorder to per-request copies of the tools before get_model_structural_tag,
so the grammar enforces exactly the order the model was shown.
request.tools is left untouched.
xgrammar's any_order=True is not an alternative: as documented in
GrammarCompiler.compile_json_schema, it only bounds the number of entries and
does not check key presence or uniqueness, so required keys can still be
missing (verified: {"a":1,"c":3} is accepted for required=["a","b"]).
Tests:
pytest tests/tool_parsers/test_glm47_moe_tool_parser.py -k StructuralTag (9 passed)
pytest tests/tool_parsers/test_utils.py -k Reorder (5 passed)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Jared Wen <jaredwen@inferact.ai>
Contributor
|
Documentation preview: https://vllm--55558.org.readthedocs.build/en/55558/ |
|
@JaredforReal Is this bug the reason for:
There is a similar report for sglang here: sgl-project/sglang#36669 |
Contributor
Author
|
thanks gaby, and no, this is for tool call related argument tag loss @gaby |
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.
Purpose
Fix a tool-calling failure mode of the glm47 parser family (GLM-4.5/4.6/4.7/5.x, incl. GLM-5.3-Flash): the model omits a required property of a nested object when a bulky optional property precedes it in the schema.
A request (a
collect_preferencestool whoseitems[]objects requireprompt+tag, with an optionalchoicesarray declared between them) fails 50-66% of the time on vLLM with GLM-5.3-Flash: the emitteditems[]objects are missingtag. The same failure reproduces on sglang at the same rate (46-60%), so this is a model-behavior boundary issue, not a stack bug.Mechanism (measured with prefix logprob probes on
/v1/completions):p(choices)=0.62vsp(prompt)=0.38,p(tag)~=0— the model never opens an object with the label field). Once it starts with the optionalchoicesarray, the object "feels complete" afterpromptandtagis skipped (p(tag)after choices ≈ 0.0002; after prompt ≈ 0.9998). At temp=0 the outcome is still non-deterministic — kernel noise flips the near-tie.Fix (two commits on this branch):
reorder_tool_schema_required_first: render each object schema'spropertieswithrequiredfields first (declaration order), recursing into nested object/array schemas. JSON Schema property order is not semantically meaningful, so validation and clients are unaffected. Opt-in per tool parser; enabled for the glm47 parser family. The reorder runs inOnlineRendererright after the per-request tool dicts are built — the parser'sadjust_requesthook runs after chat-template rendering, which is too late to affect the prompt.any_order=False), so previously the prompt and the grammar disagreed: after the model committed to a required key that the prompt placed early, every optional key declared before it became unreachable and was silently dropped (and we observed a ~1-2% runaway-to-max_tokenspathology under concurrency, consistent with the grammar fighting the model's key-order prior).any_order=Trueis not an alternative: perGrammarCompiler.compile_json_schemait only bounds the number of entries and does not check key presence or uniqueness.Not a duplicate: the only in-flight GLM-5.3-Flash PRs we found (#55219 KV layout, #55358 indexer move) are orthogonal; no open PR touches tool-schema rendering order or the glm47 structural tag.
Test Plan
Unit:
E2E on 4x GB300,
zai-org/GLM-5.3-Flash(native FP8, TP4,--tool-call-parser glm47 --reasoning-parser glm45), the request verbatim, serial non-streaming runs:Test Result
Schema-compliance failure rate (
items[]missing the requiredtag), before → after:strict: trueGlmMoeDsa, same parser/template)xinyuan/glm-5.3-flash-support), same payload, for referenceResidual failures all start the item object with the optional
choiceskey first (the J1 coin flip can still land wrong; only guided decoding or a model-side fix eliminates it).Strict mode eliminates the residual failures entirely:
"strict": trueon the tool definition withtool_choice: "auto"(free text and reasoning stay unconstrained; once the model starts a<tool_call>, its arguments are grammar-enforced to match the schema). No server-side flag needed beyond the parser flags above.Performance
Prompt-side reorder cost (
reorder_properties_required_first+ the per-requestmodel_dumpcopy it operates on): ~48 µs per request for the original schema (9 µs for the reorder walk alone; the deepcopy dominates andmodel_dumpwas already happening). Negligible against ~1s request latencies.Strict-mode guided decoding overhead, measured on the same 4x GB300 GLM-5.3-Flash setup with the request (~180 completion tokens, almost all inside the grammar-constrained
<arg_value>body — a near-worst-case constrained fraction):Strict is never slower (slightly faster in aggregate: constrained outputs are shorter and more regular). TTFT is unchanged once the grammar is compiled; the first request with a new schema pays the ~0.5s compile (cached per schema afterwards).
Behavioral scope note: the reorder changes the rendered prompt for every model served with
--tool-call-parser glm47/glm45(GLM-4.5/4.6/4.7/5.x). On the GLM-5.3 family it strictly helps (table above); other glm47-family models share the same emission-order prior.docs/features/tool_calling.mdupdated for the GLM-5.x entry.This change was implemented with AI assistance and reviewed by JaredforReal.