我最初注意到 GPT‑6 的 Steer,是因为它听起来终于解决了一个很自然的需求:模型还在工作,用户就能追加要求,让它调整方向。再加上异步工具调用和 WebSocket,很容易让人产生一种期待——模型一边推理,一边接收新信息,并随时改变接下来的输出。

这到底是推理方式发生了变化,还是客户端把传统的请求循环包装得更流畅了。从一个很简单的实验开始:让 Codex 等待 20 秒,再执行 nvidia-smi,看看等待期间它能不能继续分析。

结果表面上很符合预期。命令还在后台等待,Codex 已经发来一段说明,表示自己仍然能够继续处理这一回合。如果只看聊天界面,很容易把它当成“工具运行时模型仍在推理”的证据。

但继续翻看本地日志,就会发现这一回合实际上发起了五次独立的模型 API 请求。第一次执行工具大约一秒后,就返回了“仍在运行”和任务编号。客户端拿到这个返回值,才发起第二次模型请求,生成那段“等待时还能继续分析”的说明。

这里的区别很关键:底层命令没有结束,不代表模型发出的工具调用没有返回。 工具可以先把任务编号交回来,让命令留在后台。模型收到的依然是一次正常的工具结果,接下来依然可以进入下一轮生成。

把这次实验画成时序图,就清楚多了。下面省略了部分轮询,表示的是从日志里还原出的工具实验流程,并不是原生 response.steer 的协议流程。

sequenceDiagram autonumber participant U as 用户 participant C as Codex 客户端 participant M as 模型 API participant P as 后台命令 U->>C: 等待 20 秒,再运行 nvidia-smi C->>M: 第一次模型请求 M-->>C: 输出执行工具的调用指令 Note over M: 第一次模型响应结束 C->>P: 启动等待和命令执行 Note over C,P: 约 1 秒后,执行工具提前返回<br/>“仍在运行”及任务编号 C->>M: 第二次请求,带上工具返回值 Note over P: 命令仍在后台等待 M-->>C: 生成说明,并请求查询任务状态 C-->>U: 展示“等待时还能继续分析” Note over C,M: 后续请求中继续查询任务状态 P-->>C: 命令完成,结果被取回 C->>M: 带上命令结果再次请求模型 M-->>C: 生成最终回答 C-->>U: 展示结果

所以,这次验证的是后台任务可以和后续模型推理重叠运行,并没有证明同一次模型生成会在工具调用尚未返回时继续进行。它仍然可以用熟悉的 ReAct 式循环解释:模型提出动作,客户端执行工具,工具返回状态,再把状态交给模型。

为了确认这个解释,接着克隆 Codex 官方仓库,Codex检查了 2026 年 9 月 25 日的公开版本,提交为 4b1c0c30。执行工具里确实存在提前返回 Yielded、同时保留后台任务运行的逻辑。对于用户中途追加的输入,steer_input 则把消息放入待处理队列,由主循环在后续阶段读取。这个版本的 Responses WebSocket 请求枚举只有 response.create,未找到 response.steer 的调用。执行工具源码;输入队列源码;WebSocket 请求定义

这支持了客户端调度和队列续接的解释。不过,这个判断只适用于检查过的公开版本,不能直接断言桌面端的所有内部实现都一样。

接下来换一个更直观的测试:让 Codex 背诵《出师表》,在它输出时点击发送新消息。实际观察到,发送操作一直等到背诵结束,才显示提交成功。日志中,完整背诵消息记录完成之后,追加输入才进入模型历史,客户端随后发起新的请求。

这次追加消息没有改变正在输出的长文。但它仍然留下一个问题:等待究竟发生在界面、客户端队列,还是其他环节?本地日志没有覆盖点击发送到网络提交的完整链路,仅凭界面表现还无法确定原因。

一份直接测试原生接口的社区记录,提供了更关键的线索。PeopleX 开发者 joe_re 在 9 月 10 日测试了 GPT‑6 的 response.steer,公开了代码和结果文件。他让模型写约 2,000 字长文,在不同时间追加要求,五种场景都先完成原文,再生成修改后的内容。其中一次,服务端约 0.9 秒就确认接收了指令,却直到约 32.8 秒才启动后续响应。实测文章;测试仓库

他也在包含异步工具的实验中观察到了 incomplete(steered),也就是原响应确实因 steering 提前结束。不过,切换仍然发生在当前消息输出项完成之后。这个结果说明,Steer 能改变后续流程,但不保证在长文的任意位置立即插入新要求。实测事件分析

回头看官方文档,这个行为其实写得很明确:response.steer.accepted 只表示输入已经接收并排队,不代表模型已经采用。服务端会完成当前输出项及正在运行的托管工具工作,然后创建包含新指令的后续响应。原响应既可能提前结束,也可能正常完成后再续接。官方 Steering 指南

因此,最初的判断需要修正:“出现新的响应 ID”不能排除原生 Steer,“长文没有被立即打断”也不能单独证明它没用 Steer。 如果整篇文章属于一个消息输出项,原生接口也可能等整篇写完,才处理已经收到的追加要求。

另一个 OpenCode PR 提供了补充证据。作者报告,真实连接测试中,steering 被接收后由服务端自动续接,客户端没有重复发送 response.create。这体现了原生接口与本地队列的区别:续接由谁协调。但该报告注明 API Key 路径尚未实测,本次也未独立复现,不能把它当成所有场景的保证。PR 验证说明

“全双工”需要拆成两层看。WebSocket 可以双向收发消息,通信层当然能够全双工。但消息能发过去,与模型会在当时采用它,是两件不同的事。服务端完全可以先接收输入,等当前输出项结束,再组织下一段生成。

如果把“全双工推理”理解为模型持续生成的同时,能够随时吸收新输入并调整当前输出,那么目前观察到的 Steer 大概率还不是这种能力。更有证据支持的描述是:输入可以异步接收,生成则在允许的边界分段续接。至于底层如何暂停计算、复用 KV cache,公开资料和这些实验都不足以确定。

这并不意味着 Steer 没有价值。它更适合多步骤任务:调查、写代码、调用工具、检查结果,这些过程提供了更多调整方向的机会。它可以保留已经完成的工作,让新要求影响后续步骤。对于一次性生成长文,效果就可能弱一些——模型先写完,再补充或重写,体感依然像是在等。

这次排查给我最大的提醒,是不要把流畅的界面体验直接等同于模型内部机制的变化。Codex 的后台任务、客户端输入队列,以及 GPT‑6 原生的服务端 steering,都能让交互更灵活;但它们并不是同一件事。至少从目前的证据看,通信可以全双工,输入可以异步,而模型采用新信息的过程,仍然受到离散输出边界的约束。