Skip to content

fix(tool_parser): coerce XML tool-call args by declared schema type (qwen_xml, glm4_moe)#1841

Open
key4ng wants to merge 4 commits into
mainfrom
fix/tool-parser-js-schema-coercion
Open

fix(tool_parser): coerce XML tool-call args by declared schema type (qwen_xml, glm4_moe)#1841
key4ng wants to merge 4 commits into
mainfrom
fix/tool-parser-js-schema-coercion

Conversation

@key4ng

@key4ng key4ng commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Description

Problem

JavaScript collapse — root cause found, fixable (the clearest issue)

SMG's qwen_xml and glm4_moe tool parsers blindly type-coerce XML parameter values, ignoring the schema:

case schema type model emits vLLM keeps SMG coerces to result
isComplete String true "true" true (bool) ❌ Expected String, got bool
limit String 4 "4" 4 (int) ❌ got int
coordinates String [60,30] "[60,30]" [60,30] (list) ❌ got list
chart String {...} "{...}" {...} (dict) ❌ got dict

qwen_xml::safe_val and glm4_moe do serde_json::from_str(value) and keep the parsed type. JS schemas declare many params as String, so coercion produces the wrong type → BFCL rejects. vLLM keeps them as strings (schema-correct).

The fix already exists in-repo: minimax_m2 uses helpers::param_types_for_function + helpers::coerce_by_schema_type (schema-aware) — which is why minimax is only −6 vs qwen −32 / glm −40. The gateway already passes tools to the parser (parse_complete_with_tools); qwen_xml just doesn't override it (ignores tools), and glm4_moe parses blindly. Fix: make both use schema-aware coercion like minimax_m2. This should also recover much of qwen/glm's live_multiple (−8.5) and multi_turn loss (same String-param coercion across categories).

Observed on the nightly BFCL A/B (simple_javascript, SMG vs vLLM): qwen3.6 −32, glm-5.2 −40, deepseek-v4 −10, minimax −6 (minimax already schema-aware).

Solution

qwen_xml and glm4_moe now coerce each XML argument by its declared JSON-schema type via helpers::param_types_for_function + helpers::coerce_by_schema_type:

  • a string parameter keeps a numeric/bool/array/object-looking value as a string,
  • typed params (integer/number/boolean/object/array) are parsed,
  • unknown params (not in the schema) fall back to the previous blind inference, so behavior is unchanged when no schema is available.

Both parsers now thread tools through the complete (parse_complete_with_tools) and streaming (parse_incremental) paths; parse_complete (no tools) preserves the old inference. Mirrors the existing minimax_m2 implementation.

Changes

  • crates/tool_parser/src/parsers/qwen_xml.rsparse_complete_with_tools override + schema-aware coerce_value (keeps safe_val as the no-schema fallback); thread tools through streaming.
  • crates/tool_parser/src/parsers/glm4_moe.rs — schema-aware coercion in parse_arguments (extracted blind inference into infer_value as the fallback); parse_complete_with_tools override; thread tools through.
  • Unit tests in both parsers: String-typed params with numeric/bool/array/object values stay strings; non-string params still coerce; no-schema path unchanged.

Test Plan

cargo test -p tool-parser (lib + integration, incl. existing qwen/glm suites) all pass; new coercion tests added. cargo +nightly fmt and cargo clippy -p tool-parser --all-targets --all-features -- -D warnings clean.

Checklist
  • cargo +nightly fmt passes
  • cargo clippy --all-targets --all-features -- -D warnings passes (tool_parser crate)
  • (Optional) Documentation updated
  • (Optional) Please join us on Slack #sig-smg to discuss, review, and merge PRs

Summary by CodeRabbit

  • New Features
    • Made tool-call parsing schema-aware in both complete and streaming modes for supported parsers, using provided tool definitions to interpret argument values.
    • Added “complete parsing with tools” methods to apply schema-based coercion when tools are available.
  • Bug Fixes
    • Improved consistency between streamed and non-streamed argument interpretation.
    • Updated string coercion to unwrap JSON string literals (while preserving prior behavior when no tools/schema are provided).
  • Tests
    • Added/extended coverage for schema-aware coercion and streaming consistency.

qwen_xml and glm4_moe blindly JSON-parsed XML parameter values, coercing
string-typed args ("4", "true", "[60,30]", "{...}") to int/bool/array/object.
BFCL JavaScript schemas declare many params as string, so the coerced type was
wrong and the call was rejected (simple_javascript: qwen -32, glm -40 vs vLLM,
which keeps them as strings).

Coerce by the function's declared schema (helpers::param_types_for_function +
coerce_by_schema_type): a `string` param stays a string, typed params are
parsed, and unknown types fall back to the previous inference. minimax_m2
already did this. The gateway already passes tools via parse_complete_with_tools;
both parsers now honor it in the complete and streaming paths.

Signed-off-by: key4ng <rukeyang@gmail.com>
@github-actions github-actions Bot added the tool-parser Tool/function call parser changes label Jun 24, 2026
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cea409cc-c4dd-46e2-8152-2162d182ca74

📥 Commits

Reviewing files that changed from the base of the PR and between deb8645 and c26fa18.

📒 Files selected for processing (1)
  • crates/tool_parser/src/parsers/helpers.rs

📝 Walkthrough

Walkthrough

Tool parsers now coerce tool-call arguments by declared schema when tools are provided, while preserving prior inference behavior when no tools are supplied. Both complete and streaming parsing paths were updated to accept tools.

Changes

Schema-aware coercion for tool parsers

Layer / File(s) Summary
Coercion helpers
crates/tool_parser/src/parsers/helpers.rs
coerce_by_schema_type now unwraps JSON string literals for declared string types and keeps existing typed coercion and fallback behavior for other cases.
Glm4Moe schema-aware parsing
crates/tool_parser/src/parsers/glm4_moe.rs
Glm4MoeParser now derives parameter types from tools, coerces known schema types, falls back to inferred values for unknown types, and routes complete and streaming parsing through the tool-aware path.
Qwen XML schema-aware parsing
crates/tool_parser/src/parsers/qwen_xml.rs
QwenXmlParser now HTML-unescapes parameter values, coerces them by declared schema type when tools are present, preserves no-tools inference behavior, and applies the same logic in streaming parsing.
Schema-aware coercion tests
crates/tool_parser/src/parsers/glm4_moe.rs, crates/tool_parser/src/parsers/qwen_xml.rs
Tests cover string preservation, typed coercion, no-tools inference behavior, and streaming consistency; the new Function import supports test tool schema setup.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

tests

Suggested reviewers

  • slin1237
  • CatherineSue

Poem

🐇 I nibbled the schema, then hopped through the tune,
Strings stayed as strings by the light of the moon.
Tools in my paws made the parsing align,
and streaming crumbs twinkled in orderly line.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: schema-aware coercion of XML tool-call arguments in qwen_xml and glm4_moe.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tool-parser-js-schema-coercion

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the schema-aware coercion changes in both qwen_xml and glm4_moe parsers. The implementation correctly mirrors the existing minimax_m2 pattern — param_types_for_function + coerce_by_schema_type are threaded through both the complete and streaming paths, with proper fallback to blind inference (safe_val/infer_value) when no schema is available. The extracted infer_value and coerce_value functions are behaviorally equivalent to the code they replaced. Test coverage is solid — string-typed params with numeric/bool/array/object values, non-string typed params, and the no-schema path are all exercised.

@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 introduces schema-aware parameter coercion for both the GLM-4 MoE and Qwen XML tool parsers. By checking the declared parameter types from the tools' JSON schemas, the parsers can now preserve string types for values that look like numbers, booleans, arrays, or objects, matching the behavior of vLLM. The review feedback suggests optimizing the Qwen XML parser by replacing the HashMap-based parameter type lookup with a zero-allocation helper function to avoid costly heap allocations and string cloning, especially during hot streaming paths.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread crates/tool_parser/src/parsers/qwen_xml.rs
Comment thread crates/tool_parser/src/parsers/qwen_xml.rs
Comment thread crates/tool_parser/src/parsers/qwen_xml.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/tool_parser/src/parsers/glm4_moe.rs`:
- Line 282: Add a streaming regression test for schema-aware coercion in the GLM
incremental path. The issue is that `parse_tool_calls_from_text` in
`Glm4MoeParser` forwards `tools` during streaming, but the current coverage only
validates `parse_complete_with_tools`. Add a test that drives the
incremental/streaming parser path and asserts tool argument coercion behaves the
same as the complete parse path, using the relevant `Glm4MoeParser` parsing
methods and tool-schema inputs to catch regressions there.

In `@crates/tool_parser/src/parsers/qwen_xml.rs`:
- Around line 245-257: The schema-aware logic in parse_and_stream_parameters
needs coverage for streamed parameter fragments, not just complete parses. Add
an incremental test around qwen_xml parsing that feeds partial XML fragments
into parse_and_stream_parameters/parse_xml_format, concatenates the emitted
ToolCallItem parameters, and verifies schema coercion keeps string-typed fields
as strings even when values look like 4 or true. Use the existing parser helpers
and current_function_name-based type lookup so the test exercises the same
streaming path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0e6db6f5-364d-4957-a15c-815363f9d6d3

📥 Commits

Reviewing files that changed from the base of the PR and between a01cc63 and 24ba480.

📒 Files selected for processing (2)
  • crates/tool_parser/src/parsers/glm4_moe.rs
  • crates/tool_parser/src/parsers/qwen_xml.rs

Comment thread crates/tool_parser/src/parsers/glm4_moe.rs
Comment thread crates/tool_parser/src/parsers/qwen_xml.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 24ba480bd3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/tool_parser/src/parsers/qwen_xml.rs
Comment thread crates/tool_parser/src/parsers/glm4_moe.rs
Add parse_incremental tests for qwen_xml and glm4_moe asserting string-typed
params stay strings (and non-string ones coerce) during streaming, not just
parse_complete_with_tools (CodeRabbit).

Signed-off-by: key4ng <rukeyang@gmail.com>
key4ng added 2 commits June 24, 2026 17:05
coerce_by_schema_type kept a string param verbatim, so a model emitting a JSON
string literal ("4") for a string param produced '"4"' (extra quotes) — a
regression vs the old safe_val path. Unwrap a JSON string literal to its content
while still keeping bare 4/true/[60,30] as strings. Fixes the qwen_xml/glm4_moe
string fast-path and keeps minimax_m2 (same helper) consistent.

Signed-off-by: key4ng <rukeyang@gmail.com>
'unparseable' -> plain wording; no code change.

Signed-off-by: key4ng <rukeyang@gmail.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c26fa182d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


// Coerce by the function's declared schema so e.g. a `string` parameter
// whose value looks numeric/bool/array stays a string (matches vLLM).
let param_types = helpers::param_types_for_function(tools, &function_name);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve strings declared through nullable schemas

When a tool schema represents an optional string with a JSON Schema union such as {"anyOf":[{"type":"string"},{"type":"null"}]} or "type":["string","null"], param_types_for_function produces no entry because it only reads scalar type values. This newly added schema-aware path then calls coerce_value(..., None) and falls back to the old inference, so <parameter=limit>4</parameter> is still emitted as numeric 4 instead of string "4" for nullable string parameters; the same schema lookup is used by the GLM path as well.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tool-parser Tool/function call parser changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant