Skip to content
Go back

从 Codex 源码学 Agent 工程|01 Agent 循环:请求、工具调用与结束条件

Edit page

用修空字符串测试的小例子,讲清 Codex 一轮 turn 里模型请求、工具调用、call id 对账与结束判断如何串起来。

这个系列拆开 Codex 的源码,看一个主流 Agent 里有哪些核心机制、它们怎么落地,以及对自己写 Agent 有什么参考价值。整套内容按主题拆成多集视频;今天先讲Agent 循环——从收到一次用户请求,到工具调用,再到结束条件。

用一个小例子:某个函数对空字符串的返回值和测试预期对不上。你让 Codex 修好它,同时尽量保持其他输入的原有行为。接下来它通常会:先找相关测试、读函数实现、改代码、跑测试;若再失败,就带着这次报错继续排查。下面按源码里的时间线,看每一步为什么这么走。

一轮工作:请求 → 工具 → 再请求

从用户要求出发,宿主(负责发请求、调工具、收回结果的那部分代码)把已有信息整理成模型请求。模型看完后可能提出读文件或跑命令;宿主把调用交给对应工具执行,把结果写回,再发起下一次请求。绕完一圈,上下文已经变了:多了文件内容、执行结果,也可能多了一条错误信息——模型才有新依据继续往下做。

源码里通常把「从用户发出要求到这一轮结束」叫做一个 turn(一轮)。一轮里可以有多次模型请求:第一次可能还没见过代码,先调工具读测试;拿到内容后再分析、提出修改;修改结果回来后再决定跑测试。你只说了一句话,背后可能已经问过模型很多次,前一次的结果会变成后一次的输入。

一次模型响应可以包含多个工具调用,并不限于每次一个。互不依赖的读取有时可以并行;「先改再测」这类依赖则要在调度时排好。运行时不会自动理解所有业务依赖关系。

模型每次到底看到了什么

影响下一步决策的材料大致包括:

  • 指令:告诉模型怎么工作;
  • 用户要求:这次要解决什么;
  • 历史:已经做过什么;
  • 工具定义:现在能调用哪些能力、参数怎么传。

如果只告诉模型「空字符串测试失败」,它并不知道磁盘上的函数写成什么样;改完代码也不会凭空知道新内容——必须通过读文件、跑命令等工具拿到依据。可用工具也会放进当次请求;能不能真正执行,还要回到当前能力和执行规则去检查。

源码里有一份类似「这一步共用配置与能力快照」的对象(可理解为 step context):选哪个模型、什么设置、有哪些工具,在这里对齐,避免「准备请求」和「执行工具」各认一套配置。执行结果和后来加入的要求,都要有机会进入下一次决策。

工具调用如何进入执行

一份可执行的调用至少要说明:调哪个工具、传什么参数、带一个调用编号(源码里的 call id)。模型若只说「我准备测试一下」,本身还不是可执行调用。模型输出是流式到达的;宿主认出工具调用后会解析成内部结构,经路由器/注册表找到处理器(文件工具、命令工具,或经 MCP 接入的工具等)。

进入执行前还有「阀门」:是否要审批、用什么样的沙箱,按工具和当前策略处理。有的操作可直接执行,有的要等用户,有的会被拒绝——并非每次都弹审批窗。工具名找不到、参数不符、命令出错,都会形成相应结果或错误路径。调度还要处理并发与取消:哪些可并行、等待中被中断时怎么收尾。

用 call id 把结果对回调用

左右两边共享同一个调用编号,即使一轮里有多个工具调用,也能知道这段输出在回答哪一次请求。例如跑测试返回退出码 1,带着断言失败、指出空字符串不符合预期——程序把工具结果写入会话历史;多个工具可能先后完成,结果按加入次序收集。响应结束后还会等尚未收齐的结果,下一次请求模型才能读到「刚才跑了哪条命令、返回了什么、哪里失败」。

失败结果本身很有价值:能缩小排查范围。若把错误丢掉,模型就缺了继续修复所需的信息。还要注意:界面可以一边跑一边刷日志,但模型收到的是进入请求的工具结果;这两条输出路径各自处理。界面上看到某一行错误,推不出模型已经读到那一行。调试时可顺着记录查:call id 是否对上、结果有没有写入历史、下一次请求里有没有这份结果。

这一轮何时继续、何时结束

工具跑完、结果记好,并不等于这一轮该结束。还要判断:模型是否还需要后续处理;输入队列里还有没有待处理内容。只要需要继续,就可以再发模型请求——比如测试结果回来了,还要决定怎么修;又比如中途你补了一句「别改变其他输入的行为」,这条也进队列,要在合适位置纳入后续处理。

两个条件都没有时,再看结束检查(例如配置的 stop hooks):仍可能给出有效的继续指令,宿主记下来再让循环往前走;没有这类请求才结束当前轮次。内层正常返回后,外层还会复查输入队列——用户新消息可能恰好在准备结束时到达。取消与执行错误有各自的退出路径。

结束条件和验收还要分开核对:测试到底跑了没有、空字符串问题消化到什么程度、其他输入行为有没有受影响——都要有实际依据。

串一遍空字符串修复

  1. 查到测试后,先看用例要求什么,再读函数实现;
  2. 空字符串处理不符合预期 → 提出修改,工具写文件;
  3. 再跑测试;若仍失败,错误回到历史,下一次带着错误再分析;
  4. 比较:测试想要什么、当前做到哪、新错误相对前一次有何变化;修改应针对发现的问题,而不是反复执行相同命令却不增加信息。

沿失败分支「再查 → 再改 → 再验证」没有预设次数,取决于每一步结果以及是否还允许继续。相关测试支持修复后,还要核对「保持其他输入行为」是否一并检查过。

给自己写 Agent 时怎么对照

  • 准备请求时:能查到送了哪些指令、历史和工具;
  • 收到调用时:能查到选了哪个工具、传了什么参数;
  • 执行后:能找到实际返回,并能用 call id 对应前后关系;
  • 进入下一次请求前:能说明为什么继续;准备结束时:能看见是哪一个条件让它停下。

出了问题就有具体位置:输入少了、工具没执行、结果没回写,还是继续条件处理错了。串起来看:模型根据已有信息提出动作,程序负责记录和执行,新结果进入请求后再影响下一步。

下一期会把时间拉长:目标要跑很久、上下文越积越多、一轮结束后目标还没完成时,Codex 怎么持续工作。


Edit page
Share this post on:

Previous Post
从 Codex 源码学 Agent 工程|02 长任务架构:Codex 如何持续工作
Next Post
从 Codex 源码学 Agent 工程:系列导读