Prhub

#44283 [Anthropic] Support system role messages inside messages array

原始 PR 作者 chaunceyjiang 合并时间 2026-06-03 02:13 文件变更 3 提交数 1 评论 2 代码增减 +173 / -17

执行摘要

支持 Anthropic messages 数组内联 system 角色

根据 issue #44000,Claude Code CLI 2.1.154+ 版本会在 messages 数组中发送 role=system 的消息,而 vLLM 的 Anthropic 协议仅允许 user 和 assistant 角色,导致请求被拒绝并返回 400 错误。本 PR 扩展了角色枚举并调整了消息处理逻辑,以兼容此类客户端行为。

该 PR 解决了一个实际的客户端兼容性问题,实现简洁且测试充分,推荐合并。设计上值得关注的点是:通过先收集再合并的方式处理两处 system 信息来源,而不是分别追加,避免消息顺序错误。

讨论亮点

无 reviewer 讨论,仅有一位贡献者(aleksandaryanakiev)表示该方案更好,关闭了自己的 PR。没有未解决的争议。

实现拆解

  1. 扩展消息角色枚举:在 vllm/entrypoints/anthropic/protocol.py 中,将 AnthropicMessage.role 的类型从 Literal['user', 'assistant'] 改为 Literal['user', 'assistant', 'system'],使得 Pydantic 模型能够接受 system 角色。

  2. 重构系统消息处理:在 vllm/entrypoints/anthropic/serving.py_convert_system_message 方法中,不再仅处理顶层 system 字段,而是先收集顶层 system 内容,然后遍历 messages 数组,提取所有 role=system 的消息内容(支持字符串和内容块),并统一拼接到 system_parts 列表中。最后将所有部分合并为一个 system 消息。

  3. 跳过重复处理:在 _convert_messages 方法中,增加对 role=system 的跳过逻辑,避免将内联 system 消息再次转换为普通消息导致重复。

  4. 测试覆盖:新增 TestInlineSystemMessageInMessagesArray 测试类,包含五个测试用例,覆盖内联 system 与顶层 system 合并、纯字符串内联、列表内容内联、多个内联 system 以及内联 system 与顶层 system 字符串共存等场景。

文件 模块 状态 重要度
tests/entrypoints/anthropic/test_anthropic_messages_conversion.py 测试 modified 7.04
vllm/entrypoints/anthropic/serving.py 服务层 modified 6.72
vllm/entrypoints/anthropic/protocol.py 协议层 modified 4.89

关键符号

_convert_system_message _convert_messages AnthropicMessage

关键源码片段

vllm/entrypoints/anthropic/serving.py core-logic

核心变更:重构 _convert_system_message 以支持从 messages 数组中提取 system 消息;_convert_messages 跳过 system 角色。

@classmethod
def _convert_system_message(
    cls,
    anthropic_request: AnthropicMessagesRequest | AnthropicCountTokensRequest,
    openai_messages: list[dict[str, Any]],
) -> None:
    """Convert Anthropic system message to OpenAI format.
    Now also extracts system messages embedded in the messages array.
    """
    system_parts: list[str] = []
​
    # 1. Process top-level system field
    if anthropic_request.system:
        if isinstance(anthropic_request.system, str):
            system_parts.append(anthropic_request.system)
        else:
            for block in anthropic_request.system:
                if block.type == "text" and block.text:
                    # Strip Claude Code's attribution header to improve prefix caching
                    if block.text.startswith("x-anthropic-billing-header"):
                        continue
                    system_parts.append(block.text)
​
    # 2. Extract inline system messages from the messages array
    for msg in anthropic_request.messages:
        if msg.role != "system":
            continue
        if isinstance(msg.content, str):
            system_parts.append(msg.content)
        else:
            for block in msg.content:
                if block.type == "text" and block.text:
                    if block.text.startswith("x-anthropic-billing-header"):
                        continue
                    system_parts.append(block.text)
​
    # 3. Emit a single merged system message
    if system_parts:
        openai_messages.append({"role": "system", "content": "".join(system_parts)})
​
​
@classmethod
def _convert_messages(
    cls, messages: list, openai_messages: list[dict[str, Any]]
) -> None:
    """Convert Anthropic messages to OpenAI format, skipping system messages."""
    for msg in messages:
        if msg.role == "system":
            continue # Already handled in _convert_system_message
        openai_msg: dict[str, Any] = {"role": msg.role}
        if isinstance(msg.content, str):
            openai_msg["content"] = msg.content
        else:
            cls._convert_message_content(msg, openai_msg, openai_messages)
        if not (msg.role == "user" and "content" not in openai_msg):
            openai_messages.append(openai_msg)

评论区精华

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

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

风险与影响

本次变更仅放宽了请求验证和增加了消息处理逻辑,对现有请求兼容。风险较低:

  • 合并顺序:顶层 system 内容先于内联 system 内容,如果用户同时提供两者,内联 system 会追加在后面,这可能改变某些依赖顺序的客户端行为,但 Anthropic 官方 API 也支持两者。
  • 性能影响:增加了一次遍历 messages 数组的步骤,但仅在转换时,影响微乎其微。
  • 向后兼容:所有之前合法的请求仍然合法,且处理逻辑一致。

用户影响:之前因为 400 错误无法使用的 Claude Code CLI 2.1.154+ 用户现在可以正常工作。对于其他使用 Anthropic 兼容 API 的客户端,内联 system 消息也被支持。

系统影响:处理路径没有增加显著开销。

团队影响:无,单文件改动且经过测试。

兼容性变更 低风险

关联 Issue

#44000 [Bug]: Claude Code CLI >= 2.1.154 sends ctx/msg/system roles and breaks vLLM Anthropic Messages API validation

完整报告

参与讨论