大家好,我是小木头。上一期,我们从 Codex 的源码出发介绍了它是怎样支持 Skills。这一期我们继续跟着 Codex 学智能体开发,来看一个更基础也更关键的问题:Agent Loop 是怎么实现的?普通的 AI 聊天通常是用户提出一个问题,应用调用一次模型然后得到一次回答。Codex 处理开发任务时却不一样,它可以先读取代码、再修改文件、然后运行测试;测试失败了它还会继续分析、再次修改,直到给出最终的回答。
那么 Agent Loop 是不是特别简单呢?无非就是给模型加上工具调用,只要模型返回的还不是最终的答案或者回答,外面就用 for 或者 while 循环执行工具再把结果交还给模型。从最小实现来看确实如此,但是如果 Agent Loop 真的只有这几行循环,Codex 为什么还需要一整套的任务运行时呢?比如一次返回多个工具哪些可以同时执行、工具出错以后怎样处理,以及用户中途追加要求应该什么时候加入上下文窗口快满了任务又怎样继续呢?
所以本期要看的不只是循环本身,还要快速看一下 Codex 在循环周围做了哪些工作。在进入流程之前先解释源码里经常出现的一个词 sampling,这里的 sampling 指的是模型根据当前上下文完成一次推理并产生输出。对应用来说通常就对应了一次模型的 API 调用,它不是完整任务只是任务中的一次模型调用。为了方便理解后面我们统一就说“模型调用”。
这样一来可以把三个层次分开:最外层是一条可以保存和恢复的对话,对话里面可以有多次用户的输入;中间层是一次任务的执行或者一次用户的任务从用户提出要求开始到 Codex 给出最终的结果为止都属于同一个任务;而最内层呢才是模型的调用,一次用户任务的内部可能会发生很多次模型调用。举个例子:第一次模型调用时模型要求读取文件,Codex 执行读取操作把结果写回上下文;第二次模型调用时模型根据文件内容提出了修改建议。
第三次模型调用时模型要求运行测试,测试失败以后还可以继续出现第四次、第五次、第六次等等的模型调用。从用户角度来看这只是一个任务,但在 Codex 内部模型已经和真实环境往返了很多次。那接下来我们把一次循环完整的走一遍,每次调用模型之前 Codex 都要先准备模型能够看得懂的上下文。这里不只有用户和模型之间的聊天,还包括模型之前提出的工具调用、工具执行后的结果以及运行过程中新增的用户要求。
这些内容也不是简单拼成一段长文字,而是按照类型和顺序保存:模型提出了哪个工具调用后面就要对应哪个执行结果。上下文准备完成以后 Codex 调用模型,模型可能返回普通的消息也可能返回一个或多个工具调用。如果是普通消息 Codex 会把它记录下来;那如果是工具调用呢?Codex 会先记录模型想做什么,再把工具名称参数交给外部的运行时。
这里要注意:模型本身不会读取文件也不会直接运行终端命令,模型只是提出动作真正改变文件和系统状态的是模型外面的运行时。工具执行完成以后结果会被转换成结构化的记录重新写回当前的会话,下一次模型调用开始时模型就能看到我刚才提出了什么动作、真实环境返回了什么结果。于是,一个完整的 Agent Loop 形成了:当前上下文进入模型,模型提出下一步;运行时执行工具,工具结果回到上下文然后模型继续判断。
只要仍然有动作需要处理这个循环呢就会继续,没有新的工具调用也没有其他继续条件时这次任务才会结束。这就是 Agent Loop 最核心的逻辑,而 Codex 相对复杂的部分主要集中在这条循环周围。第一它要维护结构化的历史,保证工具调用和工具结果能够正确的对应;第二同一次模型返回可能包含多个工具,能够确认彼此独立的读取操作是可以并行的。
可能修改环境共享状态或者相互依赖的操作则必须限制执行顺序;第三工具成功和可以反馈的错误都要回给模型,这样模型才能根据真实的结果调整下一步而不是工具一报错整个任务就直接结束;第四用户可以在运行时追加要求,新的消息会先进入到待处理区等当前步骤达到安全的位置再加入下一次的模型调用;第五循环必须有明确的继续、暂停和停止条件,取消等待用户确认无法恢复的错误都不能被当成普通的下一轮。
第六任务运行时变长以后还要处理上下文的压缩和执行记录的保存:一个让有限的上下文继续使用,另一个让任务的状态可以恢复。讲到这里咱们可以快速地把 Codex 和 pi-mono 放在一起看,咱们这里提到的是 pi-mono它是一个开源的智能体框架。大家或许早已听说过 Pi Agent它就是基于 pi-mono 开发出来的一款智能体产品,pi-mono 也是一个开源的智能体框架。
它的最小循环和 Codex 基本相同:准备上下文、调用模型、执行工具、把结果交还给模型然后判断是否继续。而且并行工具、错误反馈和中途追加消息这些能力 pi 也支持,所以两者的差异不是谁有 Agent Loop、谁没有。真正差异是这些能力被放在哪一层:pi 把底层的 Agent Loop 做成了一个比较轻量级可以单独嵌入的 TypeScript 库,底层主要处理消息、模型工具并持续向外发出事件。
会话持久化、上下文压缩和自动重试主要由上层的 AgentSession 组织,终端界面不在 Agent Loop 里面而是订阅这些事件把流式的文本、工具调用和执行结果呢展示出来。而 Codex 的做法更接近一套完整的产品运行时,会话状态、工具环境、Skills 和插件、执行记录上下文压缩停止检查以及传输状态都会直接参与一次用户任务的生命周期。
上下文压缩的位置就是一个很直观的例子:pi 通常在一次 Agent 运行结束以后由上层检查是否需要压缩再决定要不要继续,遇到上下文溢出时它也可以压缩后再重试。Codex 则可以在同一个用户任务还没有结束时先压缩历史然后直接回到当前的循环继续执行。所以 pi-mono 让我们更容易看清一个 Agent Loop 最小需要什么,而 Codex 展示的则是当这套循环进入真实的开发产品以后运行时还需要统一管理什么。
当然了这并不是孰优孰劣,pi 更强调轻量、可组合和多模型供应商的适配;而 Codex 则更强调一次任务执行中的状态一致性、执行控制和恢复能力。Codex 在 WebSocket 传输上还做了一个优化:在同一个用户任务内如果请求配置不变而且历史只是顺序追加,它可以引用上一轮的响应只发送新增的内容;如果历史经过压缩回滚或改写就回退完整的请求。
这一点可以简单理解为节省传输的办法,它不是 Agent Loop 成立的必要条件因此最小版本的 Agent Loop 确实很短:调用模型、保存输出,如果出现工具调用就执行工具并保存结果然后继续;如果没有需要处理的动作就返回最终的结果。真正困难的是保证每次进入下一轮时状态仍然正确、操作仍然安全、错误仍然可以处理、任务仍然可以恢复。
while 循环负责让流程再次执行,智能体运行时负责保证下一轮仍然可执行、可控制、可恢复。Codex 能够完成多步骤的开发任务不是因为模型一次回答的足够长,也不是因为模型 API 会自动替它执行工具。它把一次用户任务展开成多次的模型调用,模型负责提出下一步,模型外面的运行时负责执行、记录继续和停止。
这才是 Agent 从回答问题走向执行任务的关键。如果这期内容对你有帮助,欢迎关注小木头也别忘了一键三连哦!好了那我们就下期再见。