fix(tool_parser): treat qwen_xml tool-call arg values literally#1899
fix(tool_parser): treat qwen_xml tool-call arg values literally#1899key4ng wants to merge 2 commits into
Conversation
The qwen_xml parser HTML-unescaped every tool-call argument value, so any value containing an entity-like substring (&, <, ', ...) was silently mutated on parse. The Qwen3-Coder XML format is not HTML-escaped on render (the chat template emits values via `| tojson | safe` / `| string`), so parsing must not unescape it. vLLM's Qwen3CoderToolParser, Qwen-Agent, and Qwen's chat template all pass argument values through verbatim. Drop the html_unescape call from safe_val and coerce_value (keeping the JSON / schema-type coercion), delete the now-dead html_unescape helper, and invert the unit/integration tests that asserted decoding. Add a golden conformance test asserting entity-containing values round-trip unchanged on both the inference and string-schema paths. Signed-off-by: key4ng <rukeyang@gmail.com>
📝 WalkthroughWalkthroughRemoves HTML-entity decoding from Qwen XML tool argument parsing while retaining JSON and schema-based coercion. Unit and integration tests now verify that named, numeric, and hexadecimal entity-like substrings remain unchanged. ChangesLiteral value parsing
Estimated code review effort: 2 (Simple) | ~12 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Clean removal of incorrect HTML-entity decoding in the Qwen XML parser. The change aligns with vLLM's Qwen3CoderToolParser and the Qwen chat template, both of which pass argument values through verbatim. Tests comprehensively cover the schema-less inference path, the schema-typed string path, and the mixed HTML-entity-in-JSON edge case. No issues found.
There was a problem hiding this comment.
Code Review
This pull request removes HTML-entity decoding from the Qwen XML parser to ensure argument values are treated literally, aligning with vLLM's Qwen3CoderToolParser and Qwen-Agent. Tests have been updated and added to verify that HTML entities are preserved verbatim. The review feedback correctly points out a mismatch in the newly added test test_arg_values_with_entities_roundtrip_unchanged where the function name in the XML template (write_file) does not match the function name defined in tool_with_props (f), which prevents the schema-typed path from being properly exercised.
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.
| let text = format!( | ||
| "<tool_call>\n<function=write_file>\n\ | ||
| <parameter=content>\n{literal}\n</parameter>\n\ | ||
| </function>\n</tool_call>" | ||
| ); |
There was a problem hiding this comment.
In this test, the XML input uses <function=write_file>, but the tools list is created using tool_with_props, which defines a function named "f".
Because of this mismatch, helpers::param_types_for_function(tools, "write_file") returns an empty map, meaning the schema-typed path (coerce_value) does not find the "content" parameter's declared type ("string") and silently falls back to the schema-less safe_val path. As a result, the schema-typed string path is not actually being tested here.
Changing the function name in the XML template to "f" ensures that the schema-typed path is correctly matched and exercised.
let text = format!(
"<tool_call>\n<function=f>\n\\\n <parameter=content>\n{literal}\n</parameter>\n\\\n </function>\n</tool_call>"
);|
triggered bfcl eval: https://github.com/lightseekorg/smg/actions/runs/29046920019 |
…icial API Reframe the doc/test comments around the primary source: Qwen's own official API (DashScope) returns tool-call argument values with HTML entities intact, verified live on both Qwen3-Coder and Qwen3.5 (the two families SMG maps to the qwen_xml parser). vLLM, SGLang, and Qwen-Agent are cited as corroborating rather than as the canonical reference. No behavior change. Signed-off-by: key4ng <rukeyang@gmail.com>
Description
Problem
qwen_xml's value decoder ranhtml.unescapeon every tool-call argument value, so any argument containing a literal HTML entity (&,<,', …) was silently mutated on parse (&→&).The Qwen3-Coder XML tool format is not HTML-escaped on render — the chat template emits argument values via
| tojson | safe/| string, so the model emits literal characters. vLLM'sQwen3CoderToolParser, Qwen-Agent, and Qwen's chat template all pass argument values through verbatim. SMG was the only implementation that unescaped, and it applies no matching escape on its own render side, so its parse was not the inverse of its render.Impact: a coding agent writing an HTML/XML/Markdown snippet had its argument silently corrupted before it reached the tool:
<parameter=content>\n<a>Tom & Jerry</a>\n</parameter><a>Tom & Jerry</a>✅<a>Tom & Jerry</a>❌Because the assistant tool call is re-serialized into the next turn's prompt, the mutated value also propagated into subsequent turns.
Fixes #1888.
Solution
Treat argument values literally — drop the
html_unescapecall from bothsafe_val(schema-less inference path) andcoerce_value(schema-typed path), matching vLLM'sqwen3coderparser. JSON / schema-type coercion is unchanged.Changes
html_unescapefromsafe_valandcoerce_valueincrates/tool_parser/src/parsers/qwen_xml.rs(keep trim + JSON / schema-type coercion).html_unescapehelper (~70 lines).test_arg_values_with_entities_roundtrip_unchangedcovering both the inference and string-schema paths — this class of divergence now fails in CI, not as a benchmark score.Test Plan
Before: value
<a>Tom & Jerry</a> <x> it'sparsed to<a>Tom & Jerry</a> <x> it's(entities decoded).After: the same value round-trips unchanged.
Checklist
cargo +nightly fmtpassescargo clippy --all-targets --all-features -- -D warningspassesSummary by CodeRabbit
stringparsing.