Prhub

#43590 [Frontend][Responses API] Fold developer-role input messages into instructions

原始 PR 作者 chaunceyjiang 合并时间 2026-06-03 22:52 文件变更 2 提交数 5 评论 10 代码增减 +391 / -1

执行摘要

折叠 developer 角色消息为 system

Responses API 允许 input 数组包含 role: "developer" 的消息,但 vLLM 的 HF renderer 只接受标准角色,客户端(如 Codex)发送 developer 消息后返回 400 错误。此 PR 修复 #42475 和 #42407,实现 developer 消息的向后兼容折叠。

此 PR 值得阅读,展示了处理 API 角色兼容性的谨慎做法:检测、转换、合并,并选择在共享的 safe_apply_chat_template 中实现,而非特定于 Responses API。测试覆盖完整,设计决策中有 trade-off 讨论,适合作为类似兼容性需求的参考模式。

讨论亮点
  • 设计位置决策:sfeng33 建议将 normalization 从 responses/utils.py 移到 hf.pysafe_apply_chat_template,因为这是共享瓶颈,且不影响原生支持 developer 的模型(如 DeepSeek V3/V4)。作者采纳建议。
  • 条件限制修正:gemini-code-assist 指出最初在 responses/utils.py 中的 msg.get("type") == "message" 条件会跳过标准消息,且文本提取需支持 "text" 类型。最终版本在 hf.py 中移除了类型过滤,文本处理兼容多种类型。
  • 系统折叠风险讨论:cjackal 担心折叠多个 system 消息会改变模型行为;bbrowning 认为作为安全默认可接受,且仅在模板不支持 developer 时触发,并提及在 Qwen 3.6 上测试需要折叠。结论保留折叠逻辑,但未来可考虑可配置选项。
  • 标题澄清:DarkLight1337 指出 PR 标题 [Frontend][Responses API] Move... 实际是添加而非移动;作者修正为 Fold developer-role...

实现拆解

  1. 检测支持:在 vllm/renderers/hf.py 新增 _detect_developer_role_support,通过缓存检查 chat template 字符串是否包含 "developer"'developer',判断模板是否原生支持 developer 角色。
  2. 角色转换:新增 _convert_developer_to_system,遍历对话,将 role == "developer" 的消息改为 "system",并移除 tools 键(避免 chat template 误解析)。
  3. 合并 system 消息:新增 _consolidate_system_messages,将所有 system 消息合并为第一条消息,处理多部分文本内容,确保顺序合规(如 Qwen 要求 system 在首)。
  4. 集成到 safe_apply_chat_template:在消息传入 chat template 前,判断若存在 developer 消息且模板不支持 developer,则依次执行转换和合并,并记录一条 info_once 日志。
  5. 测试覆盖:在 tests/renderers/test_hf.py 新增 TestConvertDeveloperToSystemTestDetectDeveloperRoleSupport,验证转换、不可变性、tools 移除和检测正确性。
文件 模块 状态 重要度
vllm/renderers/hf.py HF 渲染器 modified 7.99
tests/renderers/test_hf.py 测试 modified 7.52

关键符号

_detect_developer_role_support _convert_developer_to_system _consolidate_system_messages safe_apply_chat_template

关键源码片段

vllm/renderers/hf.py core-logic

核心变更文件,新增 developer 角色检测、转换和 system 消息合并逻辑,并集成到 safe_apply_chat_template。

@lru_cache(maxsize=32)
def _detect_developer_role_support(chat_template: str) -> bool:
    # 检查 chat template 字符串中是否直接包含 "developer" 角色字面量
    # 使用缓存避免重复解析
    return '"developer"' in chat_template or "'developer'" in chat_template
​
​
def _convert_developer_to_system(
    conversation: list[ConversationMessage],
) -> list[ConversationMessage]:
    """将对话中的 developer 角色消息转换为 system 角色,同时移除 tools 键。"""
    converted: list[ConversationMessage] = []
    for msg in conversation:
        if msg["role"] == "developer":
            new_msg = dict(msg) # 创建副本,避免修改原始消息
            new_msg["role"] = "system"
            new_msg.pop("tools", None) # tools 键与 system 角色不兼容,弹出以免模板报错
            converted.append(new_msg) # type: ignore[arg-type]
        else:
            converted.append(msg)
    return converted
​
​
def _consolidate_system_messages(
    conversation: list[ConversationMessage],
) -> list[ConversationMessage]:
    """将所有 system 消息合并为第一条消息。    某些 chat template(如 Qwen 3.6)要求 system 消息必须是第一条。
    developer 转 system 后可能出现多条 system 或 system 不在首位,此函数将其合并。
    """
    system_contents: list[str] = []
    non_system: list[ConversationMessage] = []
    needs_consolidation = False
    for i, msg in enumerate(conversation):
        if msg["role"] == "system":
            # 如果 system 不在首位或已收集到多条,则需要合并
            if i > 0 or system_contents:
                needs_consolidation = True
            content = msg.get("content", "")
            # 处理列表类型的内容(OpenAI 风格的多部分消息)
            if isinstance(content, list):
                parts = []
                for part in content:
                    if isinstance(part, dict) and "text" in part:
                        parts.append(part["text"])
                    elif isinstance(part, str):
                        parts.append(part)
                content = "\n".join(parts)
            if content:
                system_contents.append(content)
        else:
            non_system.append(msg)
​
    if not needs_consolidation:
        return conversation # 无需折叠,直接返回原始列表
​
    merged: ConversationMessage = {
        "role": "system",
        "content": "\n\n".join(system_contents),
    }
    return [merged, *non_system]
# 在 safe_apply_chat_template 中,解析 chat template 之后、应用之前插入逻辑
if any(msg["role"] == "developer" for msg in conversation) and \
        not _detect_developer_role_support(chat_template):
    conversation = _convert_developer_to_system(conversation)
    conversation = _consolidate_system_messages(conversation)
    logger.info_once(
        "Chat template does not support the 'developer' message role. "
        "Converting developer messages to 'system' role.",
    )# 继续现有逻辑 ...
resolved_kwargs = resolve_chat_template_kwargs(...)

评论区精华

Normalization 逻辑放置位置 设计

sfeng33 建议将 developer 消息转换逻辑从 `responses/utils.py` 移到 `hf.py` 的 `safe_apply_chat_template`,因为这是所有 chat template 调用的共享瓶颈,且不影响原生支持 developer 的模型(如 DeepSeek V3/V4)。

结论:作者采纳建议,将逻辑移至 `safe_apply_chat_template`。 · 已解决

条件限制导致标准消息被跳过 正确性

gemini-code-assist 指出最初在 `responses/utils.py` 中 `msg.get("type") == "message"` 条件会跳过标准消息,且文本提取需支持 `"text"` 类型。

结论:最终版本在 `hf.py` 中移除了类型过滤,文本处理兼容多种类型。 · 已解决

系统消息折叠的风险 设计

cjackal 和 bbrowning 讨论折叠多个 system 消息可能改变模型行为;bbrowning 认为作为安全默认可接受,且仅在模板不支持 developer 时触发,并提及在 Qwen 3.6 上测试需要折叠。

结论:保留折叠逻辑,但未来可考虑可配置选项。 · 已解决

PR 标题误导 other

DarkLight1337 指出 PR 标题 `[Frontend][Responses API] Move...` 实际是添加而非移动;

结论:作者修正标题为 `[Frontend][Responses API] Fold developer-role...` · 已解决

风险与影响

  • 核心路径变更safe_apply_chat_template 是 Chat Completion 和 Responses API 的公共函数,新增逻辑影响所有通过 HF renderer 的请求,即使不涉及 developer 角色(但检测开销很小)。
  • 兼容性风险:字符串检测方式("developer" in chat_template)可能漏检通过变量或动态构造支持 developer 的模板,导致不必要折叠。
  • 行为改变风险:合并 system 消息可能改变模型对多条 system 消息的预期行为,但仅在模板不支持 developer 且请求中包含 developer 消息时触发。
  • 性能考量_detect_developer_role_support 使用 lru_cache,缓存键为整个模板字符串,可能增加内存但检索迅速。
  • 用户:Codex CLI 用户可直接使用,不再遇到 'Unexpected message role' 错误;其他使用 Responses API 且 chat template 不识别 developer 的客户端也受益。
  • 系统:更改限制在 HF renderer,不影响其他 renderer(DeepSeek V3/V4、Mistral、Harmony)。所有通过 safe_apply_chat_template 的请求(包括 Chat Completion API)均受潜在影响,但 developer 角色使用较少。
  • 团队:较小变更,但需关注后续用户反馈关于 system 折叠的影响;测试覆盖完整,降低回归风险。
核心路径变更 兼容性风险 行为改变风险 字符串检测不精确

关联 Issue

#42407 [Doc]: which version of vllm supports codex ?
#42475 [Doc]: Integration Issue: Qwen3.6-27B-FP8 with Codex via vLLM Server

完整报告

参与讨论