Prhub

#39780 [Bugfix] Reject empty tools array with HTTP 400

vllm-project/vllm · 作者 jigangz · 合并时间 2026-04-16 12:08

分析状态 已生成
文件变更 4提交数 9 · 评论 10
代码增减 +23 / -23
bugfix frontend tool-calling v1

执行摘要

修复聊天完成请求中空工具数组验证,改为返回 HTTP 400 错误以匹配 OpenAI API。

Issue #39741报告了空工具数组被错误接受的问题,导致与OpenAI API行为不一致。PR body指出根本原因是data.get("tools")返回空列表在Python中为falsy,导致验证逻辑被跳过,这是PR #8568意外引入的副作用。

该PR值得精读,展示了协议兼容性修复的实践,特别是添加类型守卫和早期验证的设计模式,有助于理解vLLM前端验证器的演进。

讨论亮点

gemini-code-assist[bot]建议添加类型检查以提高验证器健壮性,匹配其他验证器(如check_structured_outputs_count),这被采纳并实现。DarkLight1337询问OpenAI行为是否改变,jigangz回应称可能是OpenAI更新了行为,原代码是历史工作around,最终确认变更以匹配当前OpenAI API。

实现拆解

  1. 修改核心验证逻辑:在vllm/entrypoints/openai/chat_completion/protocol.pycheck_tool_usage方法中,添加早期空数组验证(if data.get("tools") == []:)和类型守卫(处理ValueError和非字典输入),匹配文件中的其他验证器模式。
  2. 移除冗余回退块:删除同一方法中处理tool_choice="required"且工具为空列表的代码块,因为空工具现在会被早期拒绝,该块变得不可达。
  3. 更新主测试:修改tests/tool_use/test_chat_completion_request_validations.py中的test_chat_completion_request_with_no_tools测试,将原本期望空工具被接受的断言改为期望抛出ValueError,以覆盖新行为。
  4. 修复工具解析器测试:在tests/tool_parsers/test_ernie45_moe_tool_parser.pytests/tool_parsers/test_xlam_tool_parser.py中,移除测试代码中构造ChatCompletionRequest时传入的tools=[]参数,因为这些测试不依赖该字段,避免新验证器导致测试失败。
文件 模块 状态 重要度
vllm/entrypoints/openai/chat_completion/protocol.py 前端协议 modified 6.2
tests/tool_use/test_chat_completion_request_validations.py 请求验证 modified 4.86
tests/tool_parsers/test_ernie45_moe_tool_parser.py 工具解析 modified 3.42
tests/tool_parsers/test_xlam_tool_parser.py 工具解析 modified 3.42
vllm/entrypoints/openai/chat_completion/protocol.py core-logic

核心验证逻辑变更文件,修复了空工具数组验证的主要 bug。

@model_validator(mode="before")
@classmethod
def check_tool_usage(cls, data):
    # 添加类型守卫:如果data是ValueError实例,直接抛出,处理前一个验证器返回的错误
    if isinstance(data, ValueError):
        raise data
    # 如果data不是字典类型,直接返回,避免后续处理错误
    if not isinstance(data, dict):
        return data
​
    # 拒绝空工具数组,匹配OpenAI API行为,确保tools字段要么不提供,要么至少有一个工具
    if data.get("tools") == []:
        raise ValueError(
            "`tools` must not be an empty array. "
            "Either provide at least one tool or omit the field entirely."
        )
​
    # 后续逻辑保持不变:默认tool_choice、验证tool_choice与tools的匹配等
    if "tool_choice" not in data and data.get("tools"):
        data["tool_choice"] = "auto"
    # ... 其余验证步骤

关键符号

check_tool_usage

评论区精华

验证器健壮性改进 正确性

gemini-code-assist[bot] 建议在 check_tool_usage 中添加类型检查,以处理 data 可能不是字典或包含 ValueError 的情况,匹配文件中的其他验证器模式。

结论:采纳建议,添加了类型守卫代码(if isinstance(data, ValueError): raise data; if not isinstance(data, dict): return data)。 · 已解决

OpenAI 行为变化讨论 设计

DarkLight1337 询问原代码注释提到“override to behave like 'none' to align with OpenAI’s behavior”,是否意味着 OpenAI 行为已改变。jigangz 回应称可能是 OpenAI 更新了行为,原代码是历史工作 around,需匹配当前 API。

结论:确认变更以匹配当前 OpenAI API 行为,移除冗余回退块,添加空数组拒绝。 · 已解决

风险与影响

主要风险是API行为变更可能影响依赖空工具数组被静默接受的现有客户端,但测试已更新覆盖新行为,且变更仅限于验证逻辑,不影响核心推理路径或性能。此外,添加类型守卫减少了潜在的类型错误风险。

用户侧,传入空工具数组的请求现在会收到HTTP 400错误和明确错误消息,提升与OpenAI API的兼容性和用户体验。系统侧,修复了协议层的一个不一致性问题,增强了前端验证的健壮性。

API 行为变更 测试覆盖更新

关联 Issue

#39741 [Bug]: Empty tools array accepted with HTTP 200, should return 400

完整报告

执行摘要

  • 一句话:修复聊天完成请求中空工具数组验证,改为返回HTTP 400错误以匹配OpenAI API。
  • 推荐动作:该PR值得精读,展示了协议兼容性修复的实践,特别是添加类型守卫和早期验证的设计模式,有助于理解vLLM前端验证器的演进。

功能与动机

Issue #39741报告了空工具数组被错误接受的问题,导致与OpenAI API行为不一致。PR body指出根本原因是data.get("tools")返回空列表在Python中为falsy,导致验证逻辑被跳过,这是PR #8568意外引入的副作用。

实现拆解

  1. 修改核心验证逻辑:在vllm/entrypoints/openai/chat_completion/protocol.pycheck_tool_usage方法中,添加早期空数组验证(if data.get("tools") == []:)和类型守卫(处理ValueError和非字典输入),匹配文件中的其他验证器模式。
  2. 移除冗余回退块:删除同一方法中处理tool_choice="required"且工具为空列表的代码块,因为空工具现在会被早期拒绝,该块变得不可达。
  3. 更新主测试:修改tests/tool_use/test_chat_completion_request_validations.py中的test_chat_completion_request_with_no_tools测试,将原本期望空工具被接受的断言改为期望抛出ValueError,以覆盖新行为。
  4. 修复工具解析器测试:在tests/tool_parsers/test_ernie45_moe_tool_parser.pytests/tool_parsers/test_xlam_tool_parser.py中,移除测试代码中构造ChatCompletionRequest时传入的tools=[]参数,因为这些测试不依赖该字段,避免新验证器导致测试失败。

关键文件:

  • vllm/entrypoints/openai/chat_completion/protocol.py(模块 前端协议;类别 source;类型 core-logic;符号 check_tool_usage): 核心验证逻辑变更文件,修复了空工具数组验证的主要bug。
  • tests/tool_use/test_chat_completion_request_validations.py(模块 请求验证;类别 test;类型 test-coverage;符号 test_chat_completion_request_with_no_tools): 主测试文件,更新以覆盖空工具数组被拒绝的新行为。
  • tests/tool_parsers/test_ernie45_moe_tool_parser.py(模块 工具解析;类别 test;类型 test-coverage): 工具解析器测试文件,移除不必要的tools=[]参数以避免新验证器导致测试失败。
  • tests/tool_parsers/test_xlam_tool_parser.py(模块 工具解析;类别 test;类型 test-coverage): 工具解析器测试文件,移除不必要的tools=[]参数以避免新验证器导致测试失败。

关键符号:check_tool_usage

关键源码片段

vllm/entrypoints/openai/chat_completion/protocol.py

核心验证逻辑变更文件,修复了空工具数组验证的主要bug。

@model_validator(mode="before")
@classmethod
def check_tool_usage(cls, data):
    # 添加类型守卫:如果data是ValueError实例,直接抛出,处理前一个验证器返回的错误
    if isinstance(data, ValueError):
        raise data
    # 如果data不是字典类型,直接返回,避免后续处理错误
    if not isinstance(data, dict):
        return data
​
    # 拒绝空工具数组,匹配OpenAI API行为,确保tools字段要么不提供,要么至少有一个工具
    if data.get("tools") == []:
        raise ValueError(
            "`tools` must not be an empty array. "
            "Either provide at least one tool or omit the field entirely."
        )
​
    # 后续逻辑保持不变:默认tool_choice、验证tool_choice与tools的匹配等
    if "tool_choice" not in data and data.get("tools"):
        data["tool_choice"] = "auto"
    # ... 其余验证步骤

评论区精华

gemini-code-assist[bot]建议添加类型检查以提高验证器健壮性,匹配其他验证器(如check_structured_outputs_count),这被采纳并实现。DarkLight1337询问OpenAI行为是否改变,jigangz回应称可能是OpenAI更新了行为,原代码是历史工作around,最终确认变更以匹配当前OpenAI API。

  • 验证器健壮性改进 (correctness): 采纳建议,添加了类型守卫代码(if isinstance(data, ValueError): raise data; if not isinstance(data, dict): return data)。
  • OpenAI行为变化讨论 (design): 确认变更以匹配当前OpenAI API行为,移除冗余回退块,添加空数组拒绝。

风险与影响

  • 风险:主要风险是API行为变更可能影响依赖空工具数组被静默接受的现有客户端,但测试已更新覆盖新行为,且变更仅限于验证逻辑,不影响核心推理路径或性能。此外,添加类型守卫减少了潜在的类型错误风险。
  • 影响:用户侧,传入空工具数组的请求现在会收到HTTP 400错误和明确错误消息,提升与OpenAI API的兼容性和用户体验。系统侧,修复了协议层的一个不一致性问题,增强了前端验证的健壮性。
  • 风险标记:API行为变更, 测试覆盖更新

关联脉络

  • PR #39217 [Mistral Grammar] Fix tool and reasoning parsing: 同样涉及tool-calling和前端验证,展示了工具解析相关修复的延续。

参与讨论