执行摘要
- 一句话:折叠 developer 角色消息为 system
- 推荐动作:此 PR 值得阅读,展示了处理 API 角色兼容性的谨慎做法:检测、转换、合并,并选择在共享的
safe_apply_chat_template 中实现,而非特定于 Responses API。测试覆盖完整,设计决策中有 trade-off 讨论,适合作为类似兼容性需求的参考模式。
功能与动机
Responses API 允许 input 数组包含 role: "developer" 的消息,但 vLLM 的 HF renderer 只接受标准角色,客户端(如 Codex)发送 developer 消息后返回 400 错误。此 PR 修复 #42475 和 #42407,实现 developer 消息的向后兼容折叠。
实现拆解
- 检测支持:在
vllm/renderers/hf.py 新增 _detect_developer_role_support,通过缓存检查 chat template 字符串是否包含 "developer" 或 'developer',判断模板是否原生支持 developer 角色。
- 角色转换:新增
_convert_developer_to_system,遍历对话,将 role == "developer" 的消息改为 "system",并移除 tools 键(避免 chat template 误解析)。
- 合并 system 消息:新增
_consolidate_system_messages,将所有 system 消息合并为第一条消息,处理多部分文本内容,确保顺序合规(如 Qwen 要求 system 在首)。
- 集成到 safe_apply_chat_template:在消息传入 chat template 前,判断若存在 developer 消息且模板不支持 developer,则依次执行转换和合并,并记录一条
info_once 日志。
- 测试覆盖:在
tests/renderers/test_hf.py 新增 TestConvertDeveloperToSystem 和 TestDetectDeveloperRoleSupport,验证转换、不可变性、tools 移除和检测正确性。
关键文件:
vllm/renderers/hf.py(模块 HF 渲染器;类别 source;类型 core-logic;符号 _detect_developer_role_support, _convert_developer_to_system, _consolidate_system_messages): 核心变更文件,新增 developer 角色检测、转换和 system 消息合并逻辑,并集成到 safe_apply_chat_template。
tests/renderers/test_hf.py(模块 测试;类别 test;类型 test-coverage;符号 TestConvertDeveloperToSystem, test_converts_role, test_removes_tools_key, test_no_developer_messages_unchanged): 新增完整测试套件,覆盖角色转换、工具键移除、不可变性、检测逻辑及边界情况。
关键符号:_detect_developer_role_support, _convert_developer_to_system, _consolidate_system_messages, safe_apply_chat_template
关键源码片段
vllm/renderers/hf.py
核心变更文件,新增 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": "
".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(...)
评论区精华
风险与影响
- 风险:
- 核心路径变更:
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 折叠的影响;测试覆盖完整,降低回归风险。
- 风险标记:核心路径变更, 兼容性风险, 行为改变风险, 字符串检测不精确
关联脉络
参与讨论