Prhub

#46030 [Refactor] Responses API parser state into conversation context

原始 PR 作者 chaunceyjiang 合并时间 2026-06-23 13:42 文件变更 3 提交数 3 评论 0 代码增减 +112 / -62

执行摘要

将 Responses API 解析器状态移入 ConversationContext,复用单解析器实例

PR body 指明:'Use a single parser for each request instead of reinitializing a new parser every time.'

建议阅读此 PR,特别是 _make_response_parser 的引入和 ConversationContext 基类的设计。它展示了如何将外部状态(解析器)优雅地融入上下文中,是 Responses API 演进的重要一步。

讨论亮点

本 PR 无公开讨论记录。审核人 sfeng33 直接批准(APPROVED),无额外评论。因此无设计争议或未解决疑虑。

实现拆解

  1. vllm/entrypoints/openai/responses/context.pyConversationContext 基类中添加 response_parser: Parser | None = None 类属性,使所有子类自动拥有该属性。
  2. 修改 SimpleContext.__init__ 接受 response_parserparser_clstokenizerrequestchat_template_kwargs 等关键字参数,支持两种创建解析器的方式:直接传入实例或延迟通过 parser_cls 构造。
  3. 修改 ParsableContext.__init__ 新增 response_parser 参数,并优先使用传入的实例;若未传入且 parser_cls 存在,则回退到内部创建。所有原 parser_instance 的使用统一改为 self.response_parser
  4. 修改 HarmonyContextStreamingHarmonyContext 的构造器接受 response_parser 参数并存储在 self.response_parser 中。
  5. vllm/entrypoints/openai/responses/serving.pyOpenAIServingResponses 中新增 _make_response_parser 方法,根据 self.parser 配置创建单次请求的解析器实例。在 _create_responses 方法中提前调用该方法,将得到的 response_parser 传递给所有上下文对象。同时移除原来在循环中重复创建和调用 reasoning_parser_cls 的逻辑,改为通过 context.response_parser.reasoning_parser 访问。
  6. 更新 tests/entrypoints/openai/responses/test_serving_responses.py,修改 _make_simple_context_with_output 函数和 _mock_parser_with_reasoning 等测试辅助函数,使其接受并传递 response_parser,确保测试用例适配新的构造方式。
文件 模块 状态 重要度
vllm/entrypoints/openai/responses/serving.py 响应服务 modified 7.33
vllm/entrypoints/openai/responses/context.py 上下文管理 modified 7.01
tests/entrypoints/openai/responses/test_serving_responses.py 测试 modified 5.58

关键符号

_make_response_parser SimpleContext.__init__ ConversationContext ParsableContext.__init__ _create_responses

关键源码片段

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

核心服务类,新增 Response parser 工厂方法并重构请求处理流程

def _make_response_parser(
    self,
    request: ResponsesRequest,
    tokenizer: TokenizerLike,
    chat_template_kwargs: dict[str, Any],
) -> Parser | None:
    # 若服务未配置解析器,则返回 None
    if self.parser is None:
        return None
    # 使用服务级 parser 类实例化一个请求级解析器
    return self.parser(
        tokenizer,
        request.tools,
        chat_template_kwargs=chat_template_kwargs,
    )# 在 _create_responses 中的调用点
chat_template_kwargs = self._effective_chat_template_kwargs(request)
response_parser = self._make_response_parser(
    request, tokenizer, chat_template_kwargs
)# 之后所有上下文构造都传入这个单例解析器
context: ConversationContext
if self.use_harmony:
    if request.stream:
        context = StreamingHarmonyContext(
            messages, available_tools, function_tool_names,
            response_parser=response_parser,
        )
    else:
        context = HarmonyContext(
            messages, available_tools, function_tool_names,
            response_parser=response_parser,
        )
else:
    if envs.VLLM_USE_EXPERIMENTAL_PARSER_CONTEXT:
        context = ParsableContext(
            response_messages=messages,
            tokenizer=tokenizer,
            parser_cls=self.parser,
            request=request,
            response_parser=response_parser,
            ...
        )
    else:
        context = SimpleContext(response_parser=response_parser)# 后续通过 context.response_parser 访问解析器,避免重复实例化
vllm/entrypoints/openai/responses/context.py core-logic

定义 ConversationContext 基类及子类构造变化

class ConversationContext(ABC):
    # 类属性,默认 None;子类实例化时在 __init__ 中覆盖为实例属性
    response_parser: Parser | None = None
​
    # 原有抽象方法 ...class SimpleContext(ConversationContext):
    """Cannot handle MCP tool calls."""
​
    def __init__(
        self,
        *,
        response_parser: Parser | None = None,
        parser_cls: type[Parser] | None = None,
        tokenizer: TokenizerLike | None = None,
        request: ResponsesRequest | None = None,
        chat_template_kwargs: dict[str, Any] | None = None,
    ):
        self.last_output = None
        # 优先使用外部传入的 parser 实例,否则通过 parser_cls 构建
        self.response_parser = response_parser or (
            parser_cls(
                tokenizer,
                request.tools,
                chat_template_kwargs=chat_template_kwargs,
            )
            if parser_cls is not None
            and tokenizer is not None
            and request is not None
            else None
        )
        # 其余初始化不变 ...
tests/entrypoints/openai/responses/test_serving_responses.py test-coverage

测试适配新构造方式,验证重构不破坏功能

def _make_simple_context_with_output(
    text: str,
    token_ids: list[int],
    response_parser: Parser | None = None,
) -> SimpleContext:
    """Create a SimpleContext with a RequestOutput containing the given text."""
    # 现在必须传入 response_parser,因为 SimpleContext 构造器需要它
    ctx = SimpleContext(response_parser=response_parser)
    completion = CompletionOutput(
        index=0,
        text=text,
        token_ids=token_ids,
        cumulative_logprob=0.0,
        logprobs=None,
        finish_reason=None,
        stop_reason=None,
    )
    req_output = RequestOutput(
        request_id="req",
        prompt="hi",
        prompt_token_ids=[7, 8],
        prompt_logprobs=None,
        outputs=[completion],
        finished=False,
        num_cached_tokens=0,
    )
    ctx.append_output(req_output)
    return ctx# 测试用例中调用点示例
response_parser = serving._make_response_parser(
    request, tokenizer, serving._effective_chat_template_kwargs(request)
)
context = SimpleContext(response_parser=response_parser)

评论区精华

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

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

风险与影响

主要技术风险:

  • 回归风险:重构涉及 Responses API 的核心生成路径,原 SimpleContext() 无参数构造行为保持兼容,但若外部代码直接实例化 HarmonyContextParsableContext 且未传入 response_parser,其行为可能因回退创建而改变。不过当前仅框架内部使用,影响有限。
  • 线程安全ConversationContextresponse_parser 属性为类属性(默认 None),但在实例化时通过 __init__ 覆盖为实例属性,不会影响其他实例。但需注意类属性声明可能被误解。风险低。
  • 解析器生命周期:解析器实例在 context 中持有,若请求包含多轮调用,解析器状态可能累积,但设计上预期如此(单次请求复用一个解析器)。需确保解析器在请求结束后的清理。

对用户无直接影响,所有 API 行为和响应格式不变。对系统内部,解析器实例从每次创建变为上下文持有,减少重复初始化开销。对开发团队,此重构为后续流式解析、解析器上下文增强等特性铺平了道路,提升了代码的可维护性和扩展性。

核心路径变更

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论