执行摘要
- 一句话:修复 Mistral 工具解析器在 HF tokenizer 下的 JSON 解析错误
- 推荐动作:建议仔细阅读
_is_pre_v11_tokeniser 函数的修改和缓存策略的设计。由于缺少自动化测试,可以考虑后续补充针对 HF tokenizer 场景的测试用例,以巩固修复效果。
功能与动机
参考 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 函数(
mistral_tool_parser.py:93-99):对非 Mistral tokenizer,通过检查词汇表中是否存在 [ARGS] token 来判断是否属于 v11+ 等效 tokenizer,从而路由到正确的解析路径。
- 缓存 _is_pre_v11 结果(
__init__ 方法):将检测结果提前计算并存储在 self._is_pre_v11 中,避免在流式解析的每条 token 上重复调用 get_vocab(),提升性能。同时流式解析路径改为使用缓存值。
- 剥离 [ARGS] 文本(
extract_tool_calls 和 _generate_delta_tool_call):HF tokenizer 会将 [ARGS] 渲染为可见文本,在提取工具名称时予以删除,以保证工具名称正确。
- 流式解析路径优化(
extract_tool_calls_streaming):改用 self._is_pre_v11 替代重新调用函数,减少开销。
- 测试验证:通过手动运行
examples/online_serving/openai_chat_completion_client_with_tools.py 完成验证,未添加自动化测试。
关键文件:
vllm/tool_parsers/mistral_tool_parser.py(模块 工具解析器;类别 source;类型 core-logic;符号 _is_pre_v11_tokeniser, MistralToolParser.init, MistralToolParser.extract_tool_calls, MistralToolParser._generate_delta_tool_call): 核心变更文件,修改了 tokenizer 版本检测逻辑、工具名称解析和流式解析路径
关键符号:_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
核心变更文件,修改了 tokenizer 版本检测逻辑、工具名称解析和流式解析路径
# vllm/tool_parsers/mistral_tool_parser.py
def _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-v11
class 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]", "")
# ...
评论区精华
风险与影响
- 风险:
- 回归风险:Token izer 版本检测逻辑变更可能影响其他 Mistral 模型,但原逻辑对 Mistral tokenizer 的行为保持不变。
- 性能风险:
_is_pre_v11_tokeniser 中调用 get_vocab() 可能开销较大,但已通过缓存规避。
- 缺少测试覆盖:本次变更未添加自动化测试(仅有手动验证),如果后续对
[ARGS] 格式有调整,可能缺失保护。
- 兼容性:假设
[ARGS] token 的存在是 v11+ 的可靠标志,若未来 tokenizer 格式变化可能需要更新。
- 影响:
- 用户影响:使用 HF tokenizer 运行 Mistral 模型工具调用(如 Devstral Small 2)的用户将不再遇到
IncompleteJSONError,工具调用功能恢复正常。
- 系统影响:仅影响
mistral_tool_parser.py,单文件、低侵入性。
- 团队影响:为后续 Mistral 工具解析器维护提供了更清晰的检测逻辑。
- 风险标记:缺少测试覆盖, 依赖 tokenizer 词汇表
关联脉络
- PR #39293 Pre-requisite changes for Mistral tool parser HF tokenizer support: 本 PR 依赖 #39293 的前置修改
参与讨论