Skip to content

Add mod: fix tool_choice enforcement for shared parser engines (Qwen3 et al.) - #354

Open
ptorsten wants to merge 1 commit into
eugr:mainfrom
ptorsten:fix-tool-choice-enforcement
Open

Add mod: fix tool_choice enforcement for shared parser engines (Qwen3 et al.)#354
ptorsten wants to merge 1 commit into
eugr:mainfrom
ptorsten:fix-tool-choice-enforcement

Conversation

@ptorsten

@ptorsten ptorsten commented Aug 22, 2026

Copy link
Copy Markdown

What

New mod mods/fix-tool-choice-enforcement patching a vLLM bug: tool_choice="required", named tool choice, and strict tools are silently unenforced whenever the reasoning and tool parsers resolve to the same parser engine — which is the recommended configuration for Qwen3 (--reasoning-parser qwen3 --tool-call-parser qwen3_xml/qwen3_coder) and every other engine-backed pair (DeepSeek V3.2/V4, MiniMax M2, Gemma4, Kimi K2, GLM 4.7, Seed-OSS, Nemotron V3, Inkling, Mistral).

Why

ParserManager.get_parser has a shared-engine shortcut that returns the engine class directly, bypassing DelegatingParser.adjust_request — the only place the xgrammar structural tag is applied. VLLM_ENFORCE_STRICT_TOOL_CALLING defaults to true and has no effect on this path. The failure is invisible whenever the model naturally wants to call a tool; it surfaces when no offered tool fits the prompt — the model then answers tool_choice="required" requests in plain prose with no error.

Found via tool-eval-bench TC-45 ("tool_choice=required Compliance") on Qwen3.8-27B-NVFP4.

The mod

Standard run.sh + diff, following the fix-qwen3-coder-next pattern:

  • applies the three-file vLLM patch to site-packages (patch -p1, tolerant of already-fixed images)
  • runs a self-check: the resolved qwen3 parser class must carry structural_tag_model

Verification (nightly-20260821-based runtime, Qwen3.8-27B-NVFP4 on GB10)

  • Patch applies cleanly; self-check prints tool-choice enforcement fix active: Qwen3Parser
  • required now forces a call even when no offered tool fits the prompt (impossible without a binding grammar); named tool choice enforced; auto and plain chat unregressed
  • Speculative decoding (DFlash2) acceptance unaffected (77.9%)
  • tool-eval-bench full 69, seed 42: 87 → 88, with TC-45 fail → pass as the only verdict change; runtime and median turn latency unchanged
  • All 3,797 upstream tests/parser/engine tests pass with the patch applied

The same fix is being submitted to vLLM upstream; once it lands in nightlies, run.sh degrades gracefully ("Patch not applicable, skipping").

🤖 Generated with Claude Code

vLLM silently skips xgrammar structural-tag enforcement of
tool_choice="required", named tool choice, and strict tools whenever the
reasoning and tool parsers resolve to the same parser engine — which is the
recommended config for Qwen3 (qwen3 + qwen3_xml/qwen3_coder) and every other
engine-backed pair (DeepSeek V3.2/V4, MiniMax M2, Gemma4, Kimi K2, GLM 4.7,
Seed-OSS, Nemotron V3, Inkling, Mistral). ParserManager.get_parser returns
the engine class directly on that path, bypassing
DelegatingParser.adjust_request, the only place the structural tag was
applied. The model can then answer tool_choice="required" requests in
plain text with no error.

Found via tool-eval-bench TC-45 on Qwen3.8-27B-NVFP4; verified on the
nightly-20260821-based runtime: the mod applies, the self-check passes, the
grammar forces a call even when no offered tool fits the prompt,
tool_choice=auto stays unconstrained, and spec-decode acceptance is
unaffected. tool-eval-bench score 87 -> 88 with TC-45 fail -> pass as the
only change. Fix also being submitted to vLLM upstream.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AYRyJkYP9PuXXombMaPbuT
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