Prhub

#40531 [Bugfix][Parser] Fix Mistral pre-v11 tool parser failing on trailing model output

原始 PR 作者 dougbtv 合并时间 2026-04-23 04:35 文件变更 2 提交数 9 评论 16 代码增减 +66 / -18

执行摘要

修复 Mistral pre-v11 工具解析器因尾随数据导致的 JSON 解析失败。

PR body指出:'Mistral-7B-Instruct-v0.3 tool calls fail with JSONDecodeError: Extra data in the pre-v11 extract_tool_calls() path when the model emits trailing tokens after the JSON tool call array.' 根因是json.loads()要求整个字符串是有效JSON,而模型输出包含尾随数据如换行符或额外文本,导致解析失败。

该PR值得精读,展示了如何处理模型输出中的非标准JSON,以及如何通过测试确保修复的健壮性。关注json.JSONDecoder().raw_decode()的使用、regex回退路径的修复和.get()的权衡,这些设计决策对类似解析场景有借鉴意义。

讨论亮点
  • gemini-code-assist[bot]指出regex回退路径中arguments未序列化:'The regex fallback path successfully parses the JSON array using raw_decode, but it fails to re-serialize the arguments dictionaries into strings. This will cause a ValidationError.' dougbtv回应并修复了此问题。
  • juliendenize询问raw_decode在regex路径中的必要性:'i'm not sure to fully get why we have a raw_decode here as well. Isn't the regex supposed to catch a dictionnary here?' dougbtv解释后改为使用json.loads(),因为正则已提取边界。
  • juliendenize质疑.get()处理缺失arguments键的合理性:'what is the rational behind this get ? shouldn't we have arguments in dict with 100% certainty?' dougbtv回应基于实际测试,模型可能生成无arguments键的JSON,使用.get()可避免KeyError并减少500错误。

实现拆解

  1. 修改核心解析逻辑:在vllm/tool_parsers/mistral_tool_parser.pyextract_tool_calls方法中,将json.loads(stringified_tool_calls)替换为json.JSONDecoder().raw_decode(stringified_tool_calls),以解析第一个有效JSON值并忽略尾随数据。
  2. 修复regex回退路径:在同一个方法的except分支中,确保通过正则表达式提取JSON后,对arguments字段使用json.dumps()重新序列化为字符串,避免后续FunctionCall构造时的ValidationError。
  3. 处理缺失arguments键:使用.get("arguments", {}).get("arguments", "{}")处理模型可能生成的没有arguments键的JSON,防止KeyError并提高鲁棒性。
  4. 更新测试覆盖:在tests/tool_parsers/test_mistral_tool_parser.py中,添加trailing_data_after_json测试用例到非流式、流式token-by-token和单块流式测试中,验证尾随数据处理;并将test_extract_tool_calls_pre_v11_regex_fallback_raises重命名为test_extract_tool_calls_pre_v11_regex_fallback,从期望失败改为期望成功。
  5. 清理冗余测试:移除单块流式测试中的xfail标记,因为尾随数据用例已由其他测试覆盖。
文件 模块 状态 重要度
vllm/tool_parsers/mistral_tool_parser.py 工具解析器 modified 6.51
tests/tool_parsers/test_mistral_tool_parser.py 测试模块 modified 5.88

关键符号

extract_tool_calls

关键源码片段

vllm/tool_parsers/mistral_tool_parser.py core-logic

源码主文件,包含核心解析逻辑变更,修复了尾随数据解析失败和 regex 回退路径问题。

# 在 extract_tool_calls 方法中,处理 pre-v11 路径
stringified_tool_calls = raw_tool_calls[0].strip()
try:
    # 使用 raw_decode 解析第一个有效的 JSON 值,
    # 忽略模型可能在工具调用数组后发出的尾随令牌。
    tool_calls, _ = json.JSONDecoder().raw_decode(stringified_tool_calls)
except json.JSONDecodeError:
    try:
        # 使用正则表达式回退路径提取 JSON 数组
        raw_tool_call = self.tool_call_regex.findall(stringified_tool_calls)[0]
        tool_calls = json.loads(raw_tool_call) # 正则已提取边界,使用 loads 即可
        tool_calls = [
            {
                "name": tool_call["name"],
                "arguments": json.dumps(
                    tool_call.get("arguments", {}), # 处理可能缺失的 arguments 键
                    ensure_ascii=False,
                ),
            }
            for tool_call in tool_calls
        ]
    except (IndexError, json.JSONDecodeError):
        logger.exception("Error in extracting tool call from response.")
        return ExtractedToolCallInformation(
            tools_called=False,
            tool_calls=[],
            content=stringified_tool_calls,
        )
else:
    # 主路径成功解析后,重新序列化 arguments
    tool_calls = [
        {
            "name": tool_call["name"],
            "arguments": json.dumps(
                tool_call.get("arguments", {}), # 同样处理缺失键
                ensure_ascii=False,
            ),
        }
        for tool_call in tool_calls
    ]

评论区精华

regex 回退路径中 arguments 序列化问题 正确性

gemini-code-assist[bot] 指出 regex 回退路径解析 JSON 后未重新序列化 arguments 字典为字符串,会导致 FunctionCall 构造时 ValidationError。

结论:dougbtv 修复了此问题,在 except 分支中添加了 json.dumps() 重新序列化。 · 已解决

raw_decode 在 regex 路径中的使用 设计

juliendenize 询问为什么在 regex 回退路径中使用 raw_decode,认为正则已提取 JSON 边界。

结论:dougbtv 回应并改为使用 json.loads(),因为正则提取后无需 raw_decode。 · 已解决

.get() 处理缺失 arguments 键的合理性 设计

juliendenize 质疑使用 .get() 处理缺失 arguments 键的必要性,认为模型应始终生成 arguments 键。

结论:dougbtv 基于实际测试数据(Mistral-7B-Instruct-v0.3 中约 10% 请求缺失键)解释,使用 .get() 可避免 KeyError 并提高鲁棒性。 · 已解决

风险与影响

  • 回归风险:修改了核心解析逻辑,可能影响其他Mistral模型或工具调用场景,但测试覆盖良好,降低了风险。
  • 性能风险json.JSONDecoder().raw_decode()可能比json.loads()稍慢,但影响可忽略,因为解析开销较小。
  • 兼容性风险:使用.get()处理缺失arguments键可能掩盖模型输出错误,但基于实际测试(Mistral-7B-Instruct-v0.3中约10%请求缺失键),这提高了系统鲁棒性。
  • 测试覆盖风险:新增测试用例覆盖了尾随数据场景,但需确保其他边缘情况(如嵌套JSON)仍被覆盖。
  • 对用户:修复了Mistral-7B-Instruct-v0.3工具调用因尾随数据导致的失败问题,提升服务稳定性和用户体验。
  • 对系统:解析器更健壮,能处理模型输出中的尾随数据和缺失键,减少服务器500错误,提高可靠性。
  • 对团队:测试用例增加,便于未来维护和回归测试;讨论中展示了实际测试数据(如30次请求中26次成功),增强了修复的可信度。
核心路径变更 兼容性风险

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论