Responses API、background request 与取消语义#

前面的结构化生成与 API 章节已经讲了 OpenAI-compatible chat/completions、tool parser、response_format 和 tool-call 约束怎样进入采样参数,但如果没有一章把 Responses API 单独讲透,这一部分就仍然会缺一个很关键的表面:它不仅是又一个兼容接口,更重要的是它把 background request、response retrieval、response cancellation 和 built-in tool context 串成了一条更长的工作流。

这章真正要补齐的,不是“又多一个 API”,而是“响应何时不再是一次性结果,而变成一个可被追踪、检索和取消的运行中实体”。只要这一层讲稳了,第 6 节对外部接口表面的覆盖就不再只停在 chat/completions 两条主线。

先把 Responses API 放回系统主线#

很多读者会自然把 Responses API 理解成“只是返回格式不同”。源码给出的现实要更强:一旦进入它的 background 模式,请求就不再只是一次性的 request-response,而会长出:

  • background
  • previous_response_id
  • request_id / response_id
  • response store
  • background task lifecycle
  • retrieve / cancel 语义

下面这张图的职责,就是把这种“工作流化”明确画出来:

flowchart LR
    Req["ResponsesRequest"] --> Make["create_responses()"]
    Make --> Run["foreground full response"]
    Make --> Bg["background task + response_store"]
    Bg --> Get["retrieve_responses()"]
    Bg --> Cancel["cancel_responses()"]

图里最重要的不是“比 chat 多几个框”,而是它把 Responses API 的核心区别说透了:一旦走 background,请求就变成了一个之后还能被查询和取消的运行中实体。

ResponsesRequest 更像工作流控制对象,而不是单次生成请求#

这也是为什么它值得单独成章。protocol.pyResponsesRequest 最值得注意的,并不是字段多,而是这些字段合在一起已经明显偏向工作流语义:

  • background
  • previous_response_id
  • request_id
  • store
  • tools
  • tool_choice
  • priority

它们共同说明,这个 schema 不只在描述“这一轮生成需要什么”,而是在描述一条可能会延续、引用历史、被取消、被检索的响应任务。这和 chat/completions 那种更像一次性交互的表面已经有了非常明显的语义差异。

create_responses() 才是这条工作流真正的汇合点#

如果只读一个函数,最值得先看的就是 serving_responses.py::create_responses()。因为它几乎把整条链都串起来了:

  1. 验证 model 是否存在
  2. 处理 previous_response_id,必要时从 response_store 取历史响应
  3. 构造 request prompts / engine prompts
  4. 根据是否使用 harmony / tool server 组装上下文
  5. 构造 GenerateReqInput(...),其中可能包含 rid=request.request_idbackground=request.background
  6. foreground 时走完整 responses_full_generator(...) 或 streaming generator
  7. background 时先把响应状态写成 queued,再启动后台 task

也就是说,这个 surface 的关键不在“请求怎样进 runtime”,而在“请求进入 runtime 后,系统是否还要继续替你记住它”。

response_idrid 被绑定得很紧,是一个很值钱的设计决定#

源码里 background 路径下,response.id 与请求时的 request_id 会保持一致,而取消时又直接:

self.tokenizer_manager.abort_request(rid=response_id)

这说明 Responses API 并没有发明另一套内部身份系统,而是把外部 resp_* 身份直接绑定到了 runtime 的 request identity 上。这个设计非常成熟,因为它让:

  • retrieve / cancel
  • response store
  • runtime abort

三者之间自然对齐,而不需要额外再维护一张映射表。技术书如果把这点说清,读者就更容易理解为什么这个接口不像“只是另一个 JSON schema”。

previous_response_id 让它真正长成工作流接口#

chat/completions 更像“给定输入,生成输出”;Responses API 则允许通过 previous_response_id 把新请求挂到上一次响应的上下文之上。这会显著改变请求语义:

  • 系统不只处理当前输入
  • 还会显式回看上一段 response store 中的上下文

这类能力非常适合系统书点破,因为它能帮助读者真正接受:Responses 不是普通 surface 的同义改写,而是在请求生命周期本身上多长出了一层历史引用语义。

background task 生命周期不是“多一个布尔值”,而是一条正式状态链#

一旦进入 background 模式,系统会:

  1. 先在 response_store 中写入一个 queued 状态的 ResponsesResponse
  2. 创建 _run_background_request(...) task
  3. background_tasks 字典追踪这个 task
  4. 在 task 结束后再自动从 background_tasks 里清理

这说明 background 不是“把请求扔给另一个线程就算了”,而是一套显式状态与资源回收语义。这也是为什么它值得单独成章:这里已经明显是工作流管理,而不是单次推理接口。

retrieve_responses()cancel_responses() 说明了“响应实体化”这件事#

这两个接口共同揭示了一个很关键的设计事实:在 Responses API 里,response 不再只是原始 HTTP 连接里的一次性结果,而是一个可以被查询、被取消、被持续引用的实体。

尤其取消语义特别值得读。它不是只改 response_store 里的状态,而是同时会:

  • 先把 response.status = "cancelled"
  • 再调用 abort_request(rid=response_id)
  • 如果 background task 还活着,则显式取消 task

这意味着取消并不是“接口层自己把状态改了”,而是会真正落到 runtime 主线里的 abort。这一点一旦讲清,Responses 的工作流人格就会比“另一个兼容接口”清晰得多。

tool server / built-in tool session 在这里更显眼,不是偶然#

create_responses() 还会在某些路径下显式管理 tool session 上下文。这说明这个 surface 比普通 chat 更愿意承载:

  • 带工具上下文的长工作流
  • built-in tool 的持续状态
  • 结果回填后的下一轮 continuation

也正因此,它和 6.15 MCP tool server、browser/python 工具与工作流接口 以及 6.17 Harmony built-in tools、tool session 与二段生成回路 会天然回扣。

最容易出现的三种误判#

第一,误把 Responses API 当成“只是另一种 response schema”。
它实际上引入了 background、retrieve、cancel 和历史引用语义。

第二,误以为 background request 只是多一个布尔值。
源码里它会显式长出 response store 和 task lifecycle。

第三,误以为取消只发生在接口层。
实际取消会真正落到 abort_request(rid=response_id) 这条 runtime 主线。

真正顺着这条工作流读源码时,更稳的顺序#

建议按下面顺序:

  1. 先看 ResponsesRequest / ResponsesResponse
  2. 再看 create_responses()
  3. 然后看 _run_background_request()
  4. 最后看 retrieve_responses() / cancel_responses()

这条顺序的价值,在于它先让你接受“响应被实体化了”,再去看它怎样被后台执行和后续控制。

小结#

这一章真正补齐的,是第 6 节里一条特别重要的工作流支线:Responses API 不只是兼容接口,它把请求延长成了一个可排队、可查询、可取消、可挂住历史上下文的响应实体,而 background execution 又和 runtime abort 在这里真正接上了。

把这条链讲清之后,结构化生成与 API 这一节对外部表面的覆盖就更接近完整系统,而不再只停在 chat/completions 两条主线。