Prhub

#44686 Fix Harmony tool descriptions for optional fields

原始 PR 作者 shenoyvvarun 合并时间 2026-06-10 14:29 文件变更 2 提交数 1 评论 3 代码增减 +57 / -2

执行摘要

修复 Harmony 工具描述为空字段时的 pydantic 错误

OpenAI 兼容的 Chat Completions 和 Responses tool schema 允许 function tool 的 description 字段省略或为 null,但 openai_harmony.ToolDescription 需要字符串。这导致当用户在 tools 参数中不提供 description 时,vLLM 返回 400 Bad Request。

建议快速合入。这是明显的兼容性问题,修复简单且测试覆盖充分。值得关注的是 create_tool_definition 函数的后续演进,可能需要对其他字段做类似的防御性处理。

讨论亮点

讨论集中在是否需要修复以及如何修复。作者 shenoyvvarun 提供了详细的错误复现信息(pydantic 校验错误),证实了用户会收到 400 错误。作者提出了两条路径:修复为默认空字符串,或者返回 400 错误给客户。最终选择了向下兼容的修复方案。

实现拆解

  1. 修改核心转换函数:在 vllm/entrypoints/openai/parser/harmony_utils.pycreate_tool_definition 函数中,将 ToolDescription.new()description 参数从直接传入字段值改为 tool.function.description or ""(ChatCompletion)和 tool.description or ""(Responses)。
  2. 新增测试类:在 tests/entrypoints/openai/parser/test_harmony_utils.py 中新增 TestCreateToolDefinition 类,包含三个测试用例:test_chat_completion_omitted_description_defaults_to_empty_stringtest_chat_completion_none_description_defaults_to_empty_stringtest_response_tool_none_description_defaults_to_empty_string
  3. 调整导入:在测试文件中增加了 from openai.types.responses import FunctionToolfrom vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionToolsParam 以支持新测试用例。
文件 模块 状态 重要度
vllm/entrypoints/openai/parser/harmony_utils.py 前端解析 modified 5.07
tests/entrypoints/openai/parser/test_harmony_utils.py 测试 modified 7.0

关键符号

create_tool_definition

关键源码片段

vllm/entrypoints/openai/parser/harmony_utils.py core-logic

核心变更文件,修改了 create_tool_definition 函数,使 description 字段在缺失或为 None 时默认转为空字符串。

# vllm/entrypoints/openai/parser/harmony_utils.pydef create_tool_definition(tool: ChatCompletionToolsParam | Tool):
    """将 OpenAI 兼容的 tool 定义转换为 Harmony 的 ToolDescription。    OpenAI 的 Chat Completions 和 Responses API 允许 description 字段
    省略或为 None,但 Harmony 的 ToolDescription 要求字符串类型,因此
    在实例化时使用 `or ""` 做防御性处理。
    """
    if isinstance(tool, ChatCompletionToolsParam):
        return ToolDescription.new(
            name=tool.function.name,
            description=tool.function.description or "", # 修复点
            parameters=tool.function.parameters,
        )
    return ToolDescription.new(
        name=tool.name,
        description=tool.description or "", # 修复点
        parameters=tool.parameters,
    )
tests/entrypoints/openai/parser/test_harmony_utils.py test-coverage

新增测试类 TestCreateToolDefinition,覆盖了三种缺失 / 空 description 的场景,确保修复正确且不回归。

# tests/entrypoints/openai/parser/test_harmony_utils.pyimport pytest
from openai.types.responses import FunctionTool
from openai_harmony import DeveloperContent, Message, Rolefrom tests.entrypoints.openai.utils import verify_harmony_messages
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionToolsParam
from vllm.entrypoints.openai.parser.harmony_utils import (
    auto_drop_analysis_messages,
    create_tool_definition, # 新增导入
    extract_function_from_recipient,
    get_encoding,
    get_system_message,
    has_custom_tools,
    is_function_recipient,
    parse_chat_input_to_harmony_message,
    parse_chat_output,
)
from vllm.entrypoints.openai.responses.harmony import (
    response_input_to_harmony,
    response_previous_input_to_harmony,
)# 定义一个通用的参数字典,供所有测试用例复用
_TOOL_PARAMETERS = {
    "type": "object",
    "properties": {"status": {"type": "string"}},
    "required": ["status"],
    "additionalProperties": False,
}
​
​
class TestCreateToolDefinition:
    # 测试 Chat Completion 中 description 字段完全省略
    def test_chat_completion_omitted_description_defaults_to_empty_string(self):
        tool = ChatCompletionToolsParam(
            function={
                "name": "report_status",
                "parameters": _TOOL_PARAMETERS,
            }
        )
​
        tool_definition = create_tool_definition(tool)
​
        assert tool_definition.name == "report_status"
        assert tool_definition.description == ""
        assert tool_definition.parameters == _TOOL_PARAMETERS
​
    # 测试 Chat Completion 中 description 字段显式设为 None
    def test_chat_completion_none_description_defaults_to_empty_string(self):
        tool = ChatCompletionToolsParam(
            function={
                "name": "report_status",
                "description": None,
                "parameters": _TOOL_PARAMETERS,
            }
        )
​
        tool_definition = create_tool_definition(tool)
​
        assert tool_definition.name == "report_status"
        assert tool_definition.description == ""
        assert tool_definition.parameters == _TOOL_PARAMETERS
​
    # 测试 Responses API 中 description 字段显式设为 None
    def test_response_tool_none_description_defaults_to_empty_string(self):
        tool = FunctionTool(
            name="report_status",
            description=None,
            parameters=_TOOL_PARAMETERS,
            type="function",
        )
​
        tool_definition = create_tool_definition(tool)
​
        assert tool_definition.name == "report_status"
        assert tool_definition.description == ""
        assert tool_definition.parameters == _TOOL_PARAMETERS

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

风险极低。仅对 create_tool_definition 函数中 description 参数做了防御性处理(or ""),不会影响已有显式提供 description 的场景,因为非空字符串不受影响。

  • 用户:修复了使用 Harmony 解析路径时,不传或传 null description 导致的 400 错误。
  • 系统:无性能影响,变更仅 2 行逻辑。
  • 团队:增强了 vLLM 与 OpenAI API 兼容性,降低了用户接入门槛。
无显著风险

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论