执行摘要
- 一句话:拒绝空tools数组并返回400
- 推荐动作:值得阅读 review 讨论中关于 OpenAI 行为演变的调查部分,展示了如何通过社区报告推断外部 API 变更。变更本身简单清晰,适合作为 API 兼容性修复的参考。
功能与动机
Issue #39741 报告空 tools 数组被接受并返回 HTTP 200,违反了 OpenAI API 行为。PR 引用 OpenAI 返回 400 错误码的事实,旨在修复协议兼容性。
实现拆解
- 增加前置校验:在
check_tool_usage 方法开头添加防御性类型检查和空数组判断,若 tools 为 [] 则抛出 ValueError。
- 移除 fallback 逻辑:原
tool_choice="required" 且空 tools 数组时静默转化为 "none" 的代码因前置校验不可达而被删除。
- 更新主测试:
test_chat_completion_request_validations.py 中将空数组用例从断言 tool_choice="none" 改为断言抛出 ValueError。
- 修复工具解析测试:
test_ernie45_moe_tool_parser.py 和 test_xlam_tool_parser.py 中移除构造请求时传入的 tools=[],因该值现在会被拒绝。
关键文件:
vllm/entrypoints/openai/chat_completion/protocol.py(模块 请求验证;类别 source;类型 core-logic;符号 check_tool_usage): 核心校验逻辑变更:新增空 tools 拒绝和防御性类型检查,移除旧的 fallback。
tests/tool_use/test_chat_completion_request_validations.py(模块 验证测试;类别 test;类型 test-coverage): 核心测试更新:将空数组用例从接受改为拒绝。
tests/tool_parsers/test_ernie45_moe_tool_parser.py(模块 工具解析测试;类别 test;类型 test-coverage): 因空数组被拒绝,移除构造请求时的 tools=[] 参数。
tests/tool_parsers/test_xlam_tool_parser.py(模块 工具解析测试;类别 test;类型 test-coverage): 同 Ernie45,移除 tools=[] 以通过新校验。
关键符号:check_tool_usage
关键源码片段
vllm/entrypoints/openai/chat_completion/protocol.py
核心校验逻辑变更:新增空 tools 拒绝和防御性类型检查,移除旧的 fallback。
# 文件 : vllm/entrypoints/openai/chat_completion/protocol.py
# check_tool_usage 校验器的新版本
@model_validator(mode='before')
@classmethod
def check_tool_usage(cls, data):
# 防御性:传播前序校验器的 ValueError
if isinstance(data, ValueError):
raise data
# 若非 dict(如已有实例传入),跳过校验
if not isinstance(data, dict):
return data
# 新增:拒绝空 tools 数组,匹配 OpenAI 行为(返回 400)
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 且提供了 tools 时默认为 'auto'
if 'tool_choice' not in data and data.get('tools'):
data['tool_choice'] = 'auto'
if 'tool_choice' in data and data['tool_choice'] == 'none':
return data
# ... 其余 tool_choice 有效性校验(略)
tests/tool_use/test_chat_completion_request_validations.py
核心测试更新:将空数组用例从接受改为拒绝。
# 文件 : tests/tool_use/test_chat_completion_request_validations.py
# 测试变更:空 tools 数组现在触发 ValueError
def test_chat_completion_request_with_no_tools():
# tools 键不存在 -> tool_choice='none'
request = ChatCompletionRequest.model_validate({
'messages': [{'role': 'user', 'content': 'Hello'}],
'model': 'facebook/opt-125m',
})
assert request.tool_choice == 'none'
# tools 键为 None -> tool_choice='none'
request = ChatCompletionRequest.model_validate({
'messages': [{'role': 'user', 'content': 'Hello'}],
'model': 'facebook/opt-125m',
'tools': None,
})
assert request.tool_choice == 'none'
# 变更:空 tools 数组现在应被拒绝
with pytest.raises(ValueError, match='must not be an empty array'):
ChatCompletionRequest.model_validate({
'messages': [{'role': 'user', 'content': 'Hello'}],
'model': 'facebook/opt-125m',
'tools': [], # 触发 ValueError
})
评论区精华
OpenAI 行为变化调查:DarkLight1337 注意到原始 fallback 声称对齐 OpenAI 行为,jigangz 通过社区报告确认 OpenAI 在 2024 年末已改变行为,现在接受空数组时返回 400。
增强校验健壮性:gemini-code-assist[bot] 建议添加类型守卫,被作者采纳并集成到最终代码中。
用户质疑:steelannelida 评论 "So we're matching 15-month-old behavior which contradicts the written spec? Why?",未得到直接回应。
- OpenAI 行为是否已改变? (correctness): 确认 OpenAI 行为变化,新校验正确。
- validator 防御性类型检查建议 (design): 采纳建议,后续提交中添加。
- 对行为变更的质疑 (question): 未得到回应,PR 已合并,用户可能需适应。
风险与影响
- 风险:主要风险是向前兼容性:之前发送
tools: [] 以禁用工具的客户端在更新后会收到 400 错误,需要调整为不传入 tools 或显式指定 tool_choice: "none"。另外,两个工具解析测试中移除了 tools=[],若这些测试原本依赖该行为来测试特定代码路径,可能减少覆盖。整体风险较低。
- 影响:影响使用 tool-calling 功能的用户和开发者。对 vLLM 系统本身无性能影响,属于请求验证层变更。对 API 兼容性是改进,降低与 OpenAI 的差异。
- 风险标记:API行为变更, 空tools兼容性, 测试覆盖调整
关联脉络
参与讨论