Prhub

#44361 [Bugfix] Responses API assistant EasyInputMessageParam input

原始 PR 作者 yzong-rh 合并时间 2026-06-23 20:54 文件变更 4 提交数 3 评论 4 代码增减 +93 / -13

执行摘要

修复 Responses API 助手消息字符串内容解析错误

用户在使用Responses API时,传入形如{"role": "assistant", "type": "message", "content": "some string"}的输入会触发BadRequestError,错误信息包含2 validation errors for ValidatorIterator: 0.ResponseOutputTextParam, 0.ResponseOutputRefusalParam。根本原因是assistant的字符串内容被不当强制为ResponseOutputMessage,导致Pydantic尝试将字符串中的每个字符解析为输出内容项。

建议阅读本PR,特别是protocol.pyinput_item_parsing的改动模式。它展示了在Pydantic强制转换前增加类型守卫以绕过验证错误的典型手法。同时关注讨论中关于缺少模型定义的思考,提示团队后续需重构以消除对字典的依赖。

讨论亮点
  • bbrowning 评论:在vllm/entrypoints/openai/responses/utils.py中直接处理字典类型的assistant消息,是否意味着缺少一个对应的Pydantic模型定义?感觉我们缺少了与ResponseOutputMessage对应的简单字符串版本模型。
  • yzong-rh 回应:EasyInputMessageParam在openai包中被定义为TypedDict,我们没有创建自己的Pydantic模型。可以通过自定义Pydantic模型并强制转换来解决,但当前改动镜像了已有行为。这个已知问题已在多个地方存在(如utils.py 285-295行),需要更全面的修复。

实现拆解

  1. 协议层防护(vllm/entrypoints/openai/responses/protocol.py:在ResponsesRequest.input_item_parsing方法中,针对type: message, role: assistant的输入,增加对content类型的检查。如果content不是列表(即字符串),则直接保留原始字典,不再尝试创建ResponseOutputMessage对象,避免后续Pydantic校验错误。
  2. 工具调用构造扩展(vllm/entrypoints/openai/responses/utils.py:在_construct_message_from_response_item函数中新增分支,处理字典类型的assistant消息。提取content字段,若为字符串直接使用,若为列表则取第一个元素的text字段,然后与之前的assistant消息合并或生成新消息,确保多轮对话对话历史正确。
  3. 测试用例补充(tests/entrypoints/openai/responses/test_function_call_parsing.py:新增test_assistant_string_content_stays_easyinput验证字符串内容的assistant消息不会被强制转换;新增test_assistant_output_style_content_coerced验证列表内容的assistant消息仍然能够正确转换为ResponseOutputMessage
  4. construct_chat_messages_with_tool_call测试增强(tests/entrypoints/openai/responses/test_responses_utils.py:新增参数化用例reasoning-easyinput-assistant,验证reasoning后跟随字典格式assistant消息的合并场景,确保回归覆盖。
文件 模块 状态 重要度
vllm/entrypoints/openai/responses/protocol.py 协议层 modified 6.86
vllm/entrypoints/openai/responses/utils.py 工具层 modified 6.31
tests/entrypoints/openai/responses/test_function_call_parsing.py 测试 modified 6.23
tests/entrypoints/openai/responses/test_responses_utils.py 测试 modified 4.27

关键符号

input_item_parsing _construct_message_from_response_item test_assistant_string_content_stays_easyinput test_assistant_output_style_content_coerced

关键源码片段

vllm/entrypoints/openai/responses/protocol.py core-logic

核心修复:在 `input_item_parsing` 中增加字符串内容检查,避免不当强制转换

# vllm/entrypoints/openai/responses/protocol.py
# 在 ResponsesRequest.input_item_parsing 中,处理 assistant 消息时
# 增加对 content 类型的判断:字符串内容保留原始 dict,避免 Pydantic 错误elif item_type == "message" and item.get("role") == "assistant":
    content = item.get("content")
    # 如果 content 是字符串,则是合法 EasyInputMessageParam
    # 不强制转换为 ResponseOutputMessage
    if not isinstance(content, list):
        processed_input.append(item)
        continue
​
    # 只有列表内容才尝试转为 ResponseOutputMessage
    original_item = item
    item = dict(item)
    if "id" not in item:
        item["id"] = f"msg_{random_uuid()}"
    if "status" not in item:
        item["status"] = "completed"
    # 补充 annotations 字段等逻辑 ...
    try:
        processed_input.append(ResponseOutputMessage(**item))
    except ValidationError:
        processed_input.append(original_item)
vllm/entrypoints/openai/responses/utils.py core-logic

辅助修复:扩展 `_construct_message_from_response_item` 处理字典格式 assistant 消息

# vllm/entrypoints/openai/responses/utils.py
# 在 _construct_message_from_response_item 中新增 elif 分支
# 处理未被协议层强制转换的 EasyInput 字典格式elif isinstance(item, dict) and item.get("role") == "assistant":
    content = item.get("content")
    text: str | None = None
    if isinstance(content, str):
        text = content
    elif isinstance(content, list) and content:
        # 注意:这里只取第一个文本片段,多段内容会丢失
        text = content[0].get("text")
    if text is not None:
        if prev_assistant_msg:
            previous_content = prev_assistant_msg.get("content")
            if previous_content is None:
                prev_assistant_msg["content"] = text
                return None
        return {"role": "assistant", "content": text}

评论区精华

是否需要为字符串内容创建专门 Pydantic 模型 设计

bbrowning 指出直接处理字典格式可能意味着缺少模型定义;yzong-rh 回应 EasyInputMessageParam 是 OpenAI 的 TypedDict,当前改动镜像已有行为,但确实需要更全面的修复。

结论:确认已知问题,本 PR 不解决,作为后续工作 · 已解决

风险与影响

  • 兼容性风险:改动仅限于输入解析路径,对于已存在的ResponseOutputMessage列表类型内容行为不变,向下兼容。
  • 功能覆盖不完整:讨论中已指出,construct_chat_messages_with_tool_call中提取列表内容时只取content[0].get("text"),对于多段input_text类型的列表会丢失部分内容。但此风险在修复前已存在,本PR未解决。
  • 测试覆盖:新增的两个单元测试和参数化用例覆盖了核心路径,但缺少对嵌套内容(如拒绝响应refusal字段)的测试,属于已知缺口。
  • 用户影响:所有使用Responses API并传入字符串内容assistant消息的用户将不再遇到验证错误,多轮对话正常工作。涉及非harmony和harmony两种模式。
  • 系统影响:改动集中在请求输入解析阶段,对推理性能无影响。
  • 团队影响:明确了EasyInputMessageParamResponseOutputMessage的边界,为后续创建专用Pydantic模型提供了基础。
多部分内容处理不完整 缺少专用 Pydantic 模型

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论