Prhub

#45376 [Bugfix] Set type/role explicitly in streaming message_start event

原始 PR 作者 waynehacking8 合并时间 2026-06-13 09:40 文件变更 2 提交数 1 评论 4 代码增减 +43 / -0

执行摘要

修复 Anthropic 流式响应中丢失 type/role 字段

Issue #45367 报告:/v1/messages 流式路径使用 model_dump_json(exclude_unset=True) 序列化 message_start 事件,而 AnthropicMessagesResponsetyperole 字段有默认值但未显式设置,导致序列化时被丢弃。严格的 Anthropic SDK 客户端(如 Claude Code)会验证这些字段,导致拒绝所有流式响应,而服务端日志显示 200 OK,使得问题难以排查。

该 PR 是一次精确的 bugfix,值得合入。其核心教训是 Pydantic 的 exclude_unset=True 与默认值字段的交互容易引发序列化问题,相关开发者应引以为戒。

讨论亮点

Reviewer @bbrowning 批准了 PR,并指出注释块略显冗长,但理解其有助于解释问题本质。提交者 @waynehacking8 感谢了 reporter @markbdean01 的端到端验证。

实现拆解

  1. 修复流式构造vllm/entrypoints/anthropic/serving.py):在 message_stream_converter 方法中,构造第一个 message_start 事件时,为 AnthropicMessagesResponse 显式传入 type="message"role="assistant",确保这两个字段在 Pydantic 模型中被视为已设置,从而在 exclude_unset=True 时仍被序列化。
  2. 添加回归测试tests/entrypoints/anthropic/test_anthropic_messages_conversion.py):新增 TestMessageStartIncludesTypeAndRole 类和 test_message_start_contains_message_type_and_role 方法,使用已有的测试框架模拟流式输入,解析 SSE 事件并断言 message_start 事件中的 message.type"message"message.role"assistant"
  3. 测试验证:修复前单个测试失败,修复后全部 34 个测试通过(CPU 测试,无需 GPU)。
文件 模块 状态 重要度
vllm/entrypoints/anthropic/serving.py 前端 modified 5.63
tests/entrypoints/anthropic/test_anthropic_messages_conversion.py 测试 modified 5.94

关键符号

TestMessageStartIncludesTypeAndRole test_message_start_contains_message_type_and_role sse_input

关键源码片段

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

核心修复文件:在流式 `message_start` 构造中显式设置 `type` 和 `role` 字段。

# vllm/entrypoints/anthropic/serving.py (lines 677-702)
# 显式设置 type 和 role,因为该事件用 exclude_unset=True 序列化,
# 默认值字段会被丢弃,而严格的 Anthropic SDK 客户端需要这些字段。
if first_item:
    chunk = AnthropicStreamEvent(
        type="message_start",
        message=AnthropicMessagesResponse(
            id=origin_chunk.id,
            # 显式设置,避免 exclude_unset=True 丢弃默认值
            type="message",
            role="assistant",
            content=[],
            model=origin_chunk.model,
            stop_reason=None,
            stop_sequence=None,
            usage=AnthropicUsage(
                input_tokens=origin_chunk.usage.prompt_tokens
                if origin_chunk.usage
                else 0,
                output_tokens=0,
            ),
        ),
    )
    first_item = False
    data = chunk.model_dump_json(exclude_unset=True)
    yield wrap_data_with_event(data, "message_start")
    continue
tests/entrypoints/anthropic/test_anthropic_messages_conversion.py test-coverage

新增回归测试,验证 `message_start` 事件包含正确的 `type` 和 `role` 字段。

# tests/entrypoints/anthropic/test_anthropic_messages_conversion.py (lines 1001-1034)
class TestMessageStartIncludesTypeAndRole:
    """Regression test for issue #45367: 流式 message_start 事件
    使用 exclude_unset=True 序列化时,默认的 type/role 字段被丢弃。
    """
​
    @pytest.mark.asyncio
    async def test_message_start_contains_message_type_and_role(self):
        async def sse_input():
            yield _make_stream_chunk(
                delta=DeltaMessage(content="Hello"),
                usage=UsageInfo(
                    prompt_tokens=20,
                    total_tokens=20,
                    completion_tokens=0,
                ),
            )
            yield _make_stream_chunk(finish_reason="stop")
            yield "data: [DONE]"
​
        converter = _make_stream_converter()
        output = []
        async for event in converter.message_stream_converter(sse_input()):
            output.append(event)
​
        events = _parse_sse_events(output)
​
        assert events[0][0] == "message_start"
        message = events[0][1]["message"]
        assert message["type"] == "message"
        assert message["role"] == "assistant"

评论区精华

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

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

风险与影响

风险极低:变更仅影响流式 message_start 事件的构造,且显式设置了正确的字段值,不会影响非流式路径或其他事件类型。

影响范围:影响所有使用 Anthropic Messages API 流式端点的用户,特别是使用严格 Anthropic SDK(如 Claude Code)的客户端。影响程度:修复前流式请求完全失败,修复后恢复正常。

关联 Issue

#45367 [Bug] /v1/messages streaming: message_start drops type/role (exclude_unset) — strict Anthropic SDK clients reject the stream

完整报告

参与讨论