Prhub

#45553 [Bugfix][Gemma4] Fix offline parser truncation, adjust_request token leak, and chat template sync

原始 PR 作者 lucianommartins 合并时间 2026-06-16 12:31 文件变更 5 提交数 2 评论 13 代码增减 +94 / -74

执行摘要

修复 Gemma4 离线解析截断和 thinking 禁用时的 token 泄漏

修复用户报告的三个 Gemma4 工具调用 bug:Issue #39069 指出离线解析截断含引号字符串;Issue #39130 指出 --reasoning-parser gemma4 结合 enable_thinking=false 时结构化输出(xgrammar)被绕过;以及 adjust_request 在 thinking 禁用时导致 token 泄漏。PR 旨在统一修复已知问题并与上游模板保持一致。

建议阅读此 PR 了解 Gemma4 工具调用问题的修复模式,特别是 adjust_requestis_reasoning_end 的逻辑。强烈关注后续 PR #45832 以解决 adjust_request 回归。对于 Gemma4 用户,建议升级并监控工具调用行为。

讨论亮点

Review 中核心讨论:

  • Isotr0py 建议删除 gemma4_utils.py:由于 Transformers v5 已提供类似功能,作者同意但作为单独变更处理(创建 Issue #45605)。
  • yzong-rh 报告 BFCL multi_turn_miss_func 下降 6.4%:提供 Before/After 数据,怀疑是模板变更引入的回归,有待进一步调查。
  • yzong-rh 质疑 preserve_thinking 默认值变更:作者解释这是根据 Gemma4 官方文档建议,默认 false 以符合上下文管理最佳实践。
  • bbrowning 指出 adjust_request 逻辑可能导致 thinking 禁用时解析完全失效:引擎需要特殊 token 才能工作,设置 skip_special_tokens=True 会破坏解析。该问题将在 #45832 中修复。

实现拆解

  1. 修复离线解析截断:在 vllm/tool_parsers/gemma4_utils.py 中,删除原有基于正则的 _parse_tool_arguments 实现,改为从 vllm.parser.gemma4 导入 _parse_gemma4_args 并委托给它,从而正确处理内部引号。
  2. 修复 is_reasoning_end 当 thinking 禁用时的行为:在 vllm/parser/gemma4.py 中,Gemma4Parser.__init__chat_template_kwargs 读取 enable_thinking 存储为 _thinking_enabled。修改 is_reasoning_end:当遇到 <|turn><|tool_response> 时,返回 not self._thinking_enabled(索引为 thinking 禁用时这些 token 表示 reasoning 已结束);并将最后的 fallthrough 从 self._reasoning_ended 改为 True,因为空 input_ids 且没有 reasoning token 即视为 reasoning 从未开始或已结束。
  3. 修复 adjust_request 绕过父类当 thinking 禁用时:在 Gemma4Parser 中新增 adjust_request 方法,检查请求的 chat_template_kwargsenable_thinking 是否为假,若为假则直接返回 request(保持默认 skip_special_tokens=True),避免父类强制设为 False 导致特殊 token 泄漏到内容中。
  4. 聊天模板同步:更新 examples/tool_chat_template_gemma4.jinja:修复 format_argumentNone 输出 null;默认 preserve_thinking 设为 false;添加 add_generation_prompt 守卫,确保在 tool 链中不重复添加 <|turn>model;优化 O(1) 的 continuation 检测;添加 enable_thinking=false 时在 assistant 首个消息前注入空的 thought channel 等。
  5. 测试适配:修正测试中 arguments 字段格式(从字符串改为 dict);修正 NO_REASONING 测试中 is_reasoning_end 预期从 False 改为 True 以匹配新逻辑。
文件 模块 状态 重要度
vllm/parser/gemma4.py 解析器 modified 6.86
vllm/tool_parsers/gemma4_utils.py 工具解析 modified 6.64
examples/tool_chat_template_gemma4.jinja 聊天模板 modified 5.08
tests/renderers/test_gemma4_chat_template.py 模板测试 modified 3.79
tests/reasoning/test_gemma4_reasoning_parser.py 推理测试 modified 3.42

关键符号

adjust_request is_reasoning_end _parse_tool_arguments

分析完成后,这里会展示 LLM 生成的相对完整源码片段和详细注释。

评论区精华

删除 gemma4_utils.py 的可能性 设计

Isotr0py 建议由于已支持 Transformers v5,可以删除此文件,用 transformers API 替代。

结论:作者同意,但作为单独变更,创建 Issue #45605 跟踪。 · 已解决

BFCL multi_turn_miss_func 指标下降 性能

yzong-rh 提供了 Before/After 对比,multi_turn_miss_func 下降 6.4%,怀疑是回归。

结论:评论表示可能不是本 PR 引起,但需进一步观察。 · unresolved

preserve_thinking 默认值变更是否故意 设计

yzong-rh 质疑默认值变更,认为可能导致上下文膨胀和 context rot。

结论:作者说明这是根据 Gemma4 文档建议更改,文档明确建议管理 thought 上下文。 · 已解决

adjust_request 逻辑导致 thinking 禁用时 parsing 失效 正确性

bbrowning 指出:"I don't think we should ever set skip_special_tokens=True for the new engine based Gemma4 parser. It has its own logic to ensure special tokens get consumed and don't leak." 并称当 thinking 禁用时所有 parsing 都失效。

结论:需要 #45832 修复,本 PR 的逻辑有误。 · unresolved

风险与影响

  1. adjust_request 回归(高):bbrowning 指出当 thinking 禁用时,跳过父类会导致 skip_special_tokens=True,而引擎依赖特殊 token 进行解析,可能导致工具调用完全失效。已由 #45832 跟进修复。
  2. BFCL 多轮指标下降(中):yzong-rh 报告 multi_turn_miss_func 下降 6.4%,虽然可能属于随机波动,但需持续监控。
  3. preserve_thinking 默认值变更兼容性(中):用户可能依赖旧行为(保留思考内容),升级后需要显式设置 preserve_thinking=true
  4. is_reasoning_end fallthrough 改为 True 的边缘情况(低):某些边界场景(如窗口包含部分 reasoning token)可能误报结束,但测试覆盖了正常情况。

主要影响使用 Gemma4 模型并启用 tool callingreasoning parser 的用户。修复了关键 bug,恢复了 thinking 禁用时结构化输出的功能,并同步聊天模板到上游版本。需注意 preserve_thinking 默认改为 false,以及 adjust_request 逻辑中未解决的风险。总体影响范围中等,但属于必要修复。

adjust_request 回归风险 BFCL 指标下降 preserve_thinking 默认变更兼容性

关联 Issue

#39069 [Bug]: gemma4_utils._parse_tool_arguments truncates string values containing internal quotes
#39130 [Bug]: `--reasoning-parser gemma4` silently disables structured output (xgrammar) when `enable_thinking=false`
#42300 [Bugfix] Fix Gemma4 streaming tool calls with accumulated parser state
#42776 [Bugfix] Gemma 4 Template Content + Tool Rendering
#42875 [Bugfix] Fix Gemma4 streaming tool calls lost when entire call arrives in one delta
#44522 Gemma-4 tool call parser leaks raw tokens (<|" and "|>) into streaming response instead of parsing to standard tool_calls JSON
#44715 [Bug]: gemma4_tool_parser._parse_gemma4_args does not strip <|"|> from dict-key positions
#45588 [Frontend] Replace legacy Gemma4 parsers with engine-based implementation

完整报告

参与讨论