Prhub

#39294 [Bugfix][Parser] Fix Mistral tool parser for HF tokenizers

原始 PR 作者 thomasmaindron 合并时间 2026-04-24 19:01 文件变更 1 提交数 5 评论 7 代码增减 +13 / -4

执行摘要

修复 Mistral 工具解析器在 HF tokenizer 下的 JSON 解析错误

参考 Issue #38818,用户报告使用 Devstral Small 2 模型和 HF tokenizer 格式时出现错误。PR body 明确指出:当使用 HF tokenizer 时,_is_pre_v11_tokeniser 始终返回 True,因为其仅检查 MistralTokenizer 实例。这导致 v11+ 模型的工具调用输出格式 [TOOL_CALLS]name[ARGS]{json_args} 被当作 JSON 解析,引发 ijson.common.IncompleteJSONError。

建议仔细阅读 _is_pre_v11_tokeniser 函数的修改和缓存策略的设计。由于缺少自动化测试,可以考虑后续补充针对 HF tokenizer 场景的测试用例,以巩固修复效果。

讨论亮点
  • 性能风险gemini-code-assist[bot] 指出 get_vocab() 返回大字典,频繁调用会严重影响热路径性能。作者通过缓存 _is_pre_v11 结果并在 __init__ 中提前计算解决。
  • 测试要求:合并者 chaunceyjiang 要求提供端到端测试输出。作者在 PR 描述中附上了非流式和流式工具调用的完整输出,验证了修复的正确性。

实现拆解

  1. 修正 _is_pre_v11_tokeniser 函数mistral_tool_parser.py:93-99):对非 Mistral tokenizer,通过检查词汇表中是否存在 [ARGS] token 来判断是否属于 v11+ 等效 tokenizer,从而路由到正确的解析路径。
  2. 缓存 _is_pre_v11 结果__init__ 方法):将检测结果提前计算并存储在 self._is_pre_v11 中,避免在流式解析的每条 token 上重复调用 get_vocab(),提升性能。同时流式解析路径改为使用缓存值。
  3. 剥离 [ARGS] 文本extract_tool_calls_generate_delta_tool_call):HF tokenizer 会将 [ARGS] 渲染为可见文本,在提取工具名称时予以删除,以保证工具名称正确。
  4. 流式解析路径优化extract_tool_calls_streaming):改用 self._is_pre_v11 替代重新调用函数,减少开销。
  5. 测试验证:通过手动运行 examples/online_serving/openai_chat_completion_client_with_tools.py 完成验证,未添加自动化测试。
文件 模块 状态 重要度
vllm/tool_parsers/mistral_tool_parser.py 工具解析器 modified 6.08

关键符号

_is_pre_v11_tokeniser MistralToolParser.extract_tool_calls MistralToolParser._generate_delta_tool_call MistralToolParser.extract_tool_calls_streaming

关键源码片段

vllm/tool_parsers/mistral_tool_parser.py core-logic

核心变更文件,修改了 tokenizer 版本检测逻辑、工具名称解析和流式解析路径

# vllm/tool_parsers/mistral_tool_parser.pydef _is_pre_v11_tokeniser(model_tokenizer: TokenizerLike) -> bool:
    # 对于 Mistral 原生 tokenizer,直接检查版本号
    if is_mistral_tokenizer(model_tokenizer):
        return model_tokenizer.version < 11
​
    # 对于 HuggingFace tokenizer,通过词汇表中是否存在 [ARGS] token
    # 来判断是否为 v11+ 等效 tokenizer
    # [ARGS] 是 v11+ 格式中专用的分隔标记
    vocab: dict[str, int] = getattr(model_tokenizer, "get_vocab", lambda: {})()
    return "[ARGS]" not in vocab # 若不存在 [ARGS] 则认为是 pre-v11class MistralToolParser(ToolParser):
    def __init__(self, tokenizer: TokenizerLike, tools: list[Tool] | None = None):
        # ... 前置初始化 ...
        # 缓存 _is_pre_v11 结果,避免在解析热路径中重复调用
        self._is_pre_v11 = _is_pre_v11_tokeniser(self.model_tokenizer)
        if self._is_pre_v11:
            # pre-v11 格式是纯 JSON,使用 ijson 流式解析
            self.parse_coro = ijson.parse_coro(
                self.update_stream_state_pre_v11_tokenizer()
            )
        # ... 其余初始化 ...
​
    def extract_tool_calls(self, model_output: str) -> list[dict[str, Any]]:
        # ... 解析逻辑 ...
        # HF tokenizer 会将 [ARGS] 输出为可见文本,需要剥离
        tool_name = tool_name.replace("[ARGS]", "")
        return tool_calls
​
    def extract_tool_calls_streaming(self, ...):
        # 使用缓存的 self._is_pre_v11 替代重复调用
        if self._is_pre_v11:
            return self._extract_tool_calls_streaming_pre_v11_tokenizer(...)
        # ...
​
    def _generate_delta_tool_call(self, delta_text: str) -> list[DeltaToolCall]:
        # ...
        # 同样在构建工具名称时剥离 [ARGS]
        self.current_tool_name = self.current_tool_name.replace("[ARGS]", "")
        # ...

评论区精华

get_vocab() 性能开销 性能

gemini-code-assist[bot] 指出 get_vocab() 返回大字典,如果在热路径上频繁调用 _is_pre_v11_tokeniser 会有性能问题。

结论:作者通过将 _is_pre_v11 结果缓存到 __init__ 中并在流式解析中直接使用 self._is_pre_v11 来解决。 · 已解决

要求提供测试输出 other

合并者 chaunceyjiang 要求使用 examples/online_serving/openai_chat_completion_client_with_tools.py 测试并在 PR 描述中贴出结果。

结论:作者在 PR 描述中提供了非流式和流式工具调用的完整输出,验证了修复的正确性。 · 已解决

风险与影响

  1. 回归风险:Token izer 版本检测逻辑变更可能影响其他 Mistral 模型,但原逻辑对 Mistral tokenizer 的行为保持不变。
  2. 性能风险_is_pre_v11_tokeniser 中调用 get_vocab() 可能开销较大,但已通过缓存规避。
  3. 缺少测试覆盖:本次变更未添加自动化测试(仅有手动验证),如果后续对 [ARGS] 格式有调整,可能缺失保护。
  4. 兼容性:假设 [ARGS] token 的存在是 v11+ 的可靠标志,若未来 tokenizer 格式变化可能需要更新。
  • 用户影响:使用 HF tokenizer 运行 Mistral 模型工具调用(如 Devstral Small 2)的用户将不再遇到 IncompleteJSONError,工具调用功能恢复正常。
  • 系统影响:仅影响 mistral_tool_parser.py,单文件、低侵入性。
  • 团队影响:为后续 Mistral 工具解析器维护提供了更清晰的检测逻辑。
缺少测试覆盖 依赖 tokenizer 词汇表

关联 Issue

#38818 [Bug]: Error when running Devstral Small 2 with HF format

完整报告

参与讨论