Prhub

#47185 [Refactor][GPT-OSS] Harmony Responses API Refactor to use HarmonyParser

原始 PR 作者 yzong-rh 合并时间 2026-07-01 07:23 文件变更 9 提交数 2 评论 4 代码增减 +424 / -854

执行摘要

统一 HarmonyParser,合并上下文类并修复流式 bug

GPT-OSS 在 Responses API 中原本有并行的 HarmonyContext 和 StreamingHarmonyContext,使用不同的解析方式,导致多个 bug(issue #45742)。PR body 明确说明目标为 'Make gpt-oss in Responses API use the unified HarmonyParser' 以及 'Consolidate HarmonyContext and HarmonyStreamingContext',以统一逻辑、修复已知问题。

建议精读 vllm/entrypoints/openai/responses/context.pystreaming_events.py,理解如何通过保留解析器状态 (last_append_segments) 而非维持独立子类来统一流式/非流式上下文。该设计模式值得在其他类似场景参考。

讨论亮点

代码审查中 bbrowning 指出一个测试断言写法的 nit:assert output_items[0].status == ('incomplete' if incomplete else 'completed') 被手写为冗余形式。作者 yzong-rh 立即修正并重新运行测试。bbrowning 最终批准,评论 'The before/after scores look good, the attached session looks to be handling real-world tool calling properly'。

实现拆解

  1. 合并上下文类vllm/entrypoints/openai/responses/context.py):移除 StreamingHarmonyContext,将其流式特性(如 last_append_segmentslast_append_flush_status)并入 HarmonyContext。原先在 StreamingHarmonyContext.append_output() 中的流式解析逻辑现在统一由 HarmonyContext.append_output() 处理。__init__ 强制要求 response_parserHarmonyParser 实例,并去除对 get_streamable_parser_for_assistant() 的依赖。

  2. 调整转换函数以支持 incomplete 状态vllm/entrypoints/openai/responses/harmony.py):为 _parse_function_call_parse_final_message_parse_mcp_call 等函数增加 incomplete 参数,使输出项的 status 可标记为 "incomplete"harmony_to_response_output() 改为接收 function_tool_namesincomplete,整合原先由 parser_state_to_response_output() 处理的“未完成”逻辑。

  3. 重构流式事件发射vllm/entrypoints/openai/responses/streaming_events.py):emit_content_delta_events() 不再依赖 StreamingHarmonyContext,改为接收 Segment 对象,从 segment 中提取 deltachannelrecipientemit_previous_item_done_events() 增加对零 delta 项的静默跳过保护(标记为 TODO 的已知 bug)。StreamingState.reset_for_new_item()current_content_index 重置为 -1 以修复 #45742。

  4. 简化服务层vllm/entrypoints/openai/responses/serving.py):移除 _make_response_output_items_with_harmony()parser_state_to_response_output() 调用,将非流式输出构造内联到 responses_full_generator() 中。流式与非流式在创建上下文时统一使用 HarmonyContext,不再区分。

  5. 测试配套

    • tests/entrypoints/unit_tests/test_context.py:用 FakeHarmonyParser 替代 MagicMock,提供可控的 process_chunk/flush 返回值,使 HarmonyContext 测试更真实。
    • tests/entrypoints/openai/responses/test_harmony_utils.py:大规模重写,对 harmony_to_response_output 进行参数化测试(channel、recipient、incomplete),移除对已删除函数 parser_state_to_response_output 的引用。
    • tests/entrypoints/openai/responses/test_serving_responses.py:新增 test_zero_delta_items_should_preserve_streaming_lifecycle(xfail)以记录已知缺陷。
文件 模块 状态 重要度
vllm/entrypoints/openai/responses/context.py 响应上下文 modified 8.69
vllm/entrypoints/openai/responses/harmony.py 格式转换 modified 8.59
vllm/entrypoints/openai/responses/streaming_events.py 流式事件 modified 7.25
vllm/entrypoints/openai/responses/serving.py 服务层 modified 7.45
tests/entrypoints/openai/responses/test_harmony_utils.py 测试 modified 7.81
tests/entrypoints/unit_tests/test_context.py 测试 modified 7.46
tests/entrypoints/openai/responses/test_serving_responses.py 测试 modified 6.34
tests/entrypoints/openai/responses/conftest.py 测试 modified 3.97
tests/entrypoints/openai/responses/test_harmony.py 测试 modified 3.32

关键符号

_update_num_reasoning_tokens HarmonyContext.__init__ HarmonyContext.append_output HarmonyContext.append_tool_output _parse_function_call _parse_final_message _parse_mcp_call parser_state_to_response_output harmony_to_response_output _make_response_output_items_with_harmony emit_content_delta_events emit_previous_item_done_events reset_for_new_item

关键源码片段

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

核心变更文件:合并 StreamingHarmonyContext 到 HarmonyContext,移除对 get_streamable_parser_for_assistant 的依赖,新增 last_append_segments/last_append_flush_status 以支持流式状态追踪。同时删除了 _update_num_reasoning_tokens 方法,将流式 token 处理逻辑整合进 append_output。

# vllm/entrypoints/openai/responses/context.py ( 关键片段 )class HarmonyContext(ConversationContext):
    def __init__(
        self,
        messages: list,
        available_tools: list[str],
        function_tool_names: frozenset[str], # 必填
        response_parser: Parser | None = None,
    ):
        from vllm.parser.harmony import HarmonyParser, Segment
        assert isinstance(response_parser, HarmonyParser)
​
        self._messages = messages
        self.response_parser: HarmonyParser = response_parser
        self.finish_reason: str | None = None
        self.available_tools = available_tools
        self.function_tool_names = function_tool_names
        self._tool_sessions: dict[str, ClientSession | Tool] = {}
        self.called_tools: set[str] = set()
​
        self.num_init_messages = len(messages)
        self.num_prompt_tokens = 0
        self.num_output_tokens = 0
        self.num_cached_tokens = 0
        self.num_reasoning_tokens = 0
        self.num_tool_output_tokens = 0
​
        # 新增:追踪最近一次 append_output 产生的 segment 和 flush 状态
        self.last_append_segments: list[Segment] = []
        self.last_append_flush_status: bool | HarmonyError = False
​
        # Turn 追踪(复用原先逻辑)
        self.current_turn_metrics = TurnMetrics()
        self.all_turn_metrics: list[TurnMetrics] = []
        self.is_first_turn = True
        self.first_tok_of_message = True
        self.kv_transfer_params: dict[str, Any] | None = None
​
    def append_output(self, output: RequestOutput) -> None:
        if self.first_tok_of_message:
            self.finish_reason = None
            self._update_prefill_token_usage(output)
​
        output_token_ids = output.outputs[0].token_ids
        for token_id in output_token_ids:
            chunk_result = self.response_parser.process_chunk([token_id])
            # 流式与非流式统一通过 parser 获取 segment
            segment = self.response_parser.flush()
            if segment:
                self.last_append_segments.append(segment)
        # ... 后续 token 计数与状态更新

评论区精华

测试中断言写法 nit style

bbrowning 指出 test_harmony_utils.py 中关于 status 的断言未使用简洁三元表达式。

结论:作者立即修正并重新运行测试。 · 已解决

风险与影响

  1. 回归风险:合并上下文类可能影响非流式场景,尤其是 _update_num_reasoning_tokens 被移除,替代逻辑是否准确覆盖所有 token 计数场景存疑。
  2. 未修已知 bug_process_harmony_streaming_events() 中零 delta 项被静默丢弃的问题未解决,可能影响特定边缘 case。
  3. 外部解析器依赖HarmonyParser 变为强制依赖,若 response_parser 类型不匹配将触发 assert,对分支集成可能不友好。
  4. 测试覆盖:虽然测试文件大增,但主要针对 harmony_to_response_output,对 append_output 的流式内部状态(如 last_append_segments)测试可能不足。

用户影响:GPT-OSS 模型在 Responses API 流式模式下的工具调用和内容索引恢复正常,不再因 IndexError 崩溃。系统影响:代码量减少约 430 行,上下文创建逻辑简化,但新增了 HarmonyParser 类型断言。团队影响:维护者需理解合并后的 HarmonyContext 同时服务于流式和非流式;开发者需注意 function_tool_names 参数变为必填。

核心路径变更 重构合并 流式逻辑变更 已知未修 bug

关联 Issue

#45742 [Bug]: Responses API streaming for GPT-OSS Harmony crashes OpenAI SDK with `IndexError` due to incorrect `content_index` logic

完整报告

参与讨论