09|Agent一次任务的完整循环:谁在思考,谁在执行
本章目录
本章预备知识
Agent是外围程序驱动的一串模型调用。请求、响应、工具结果与任务状态各有自己的生命周期;“模型说完成”和“环境真的达到目标”需要分开验证。
本章各题列出所需基础。可先完成相应基础课,也可通过自检确认掌握程度。
进入深读前的小检查:画一次“请求读文件→校验调用→读取→返回文件→再次调用模型”的环路,标出哪个箭头由程序执行,哪个判断由模型输出。
术语带虚线时可点击速查。公式中的字母先看定义;不要用猜缩写替代推导。
以下使用“修复仓库里exclude不生效”的原创教学任务。它不代表对用户真实仓库做了修改。读这一章时始终分清模型建议、Harness执行和环境真实状态。
沿着对象与状态理解本章
把模型、宿主、工具画成不同泳道后,问“谁真正改变了文件”就有明确答案。跟踪每条返回消息,再判断需要继续调用模型、执行工具还是结束;不要把模型说完成当成环境已经完成。
下方图解的固定输入在“输入与适用条件”中声明。它用于解释本章机制,不与本章其他独立例子的数值暗中接续。
把一个Agent任务拆成真正发生的几轮
同一句用户任务,为什么可能调用模型不止一次?
LLM生成下一步提议,宿主执行获准的动作,把结果再交给LLM。这是外层任务循环;每次模型调用内部还要经历逐Token生成。外层的一轮工具行动不等于内部生成一个Token,两种“循环”不能混用。
输入与适用条件:构造的只读教学任务:文件config.txt含timeout=30。泳道展示职责边界,不声称调用了真实商业API;数据和动作顺序固定。
1 / 8 · 1. 用户任务:读config.txt中的timeout
窄屏可左右滑动图形,或点击“放大图形”。文字解释紧随图后。
模型尚未看到磁盘文件。先区分用户给的任务与环境真实内容。
未读取默认静止。用“下一步”看完解释再继续;播放到最后一步自动停止。键盘聚焦播放器后,可用左右键逐步查看、空格播放/暂停。图中颜色辅助区分,含义同时用文字注明。
为什么需要
模型可以借助工具获得新证据,而不把一切都塞入参数。
怎样实现
按请求、校验、执行、结果回填、继续决策组织循环。
可能的代价与误解
忘记回填工具结果会让模型重复请求;把输出文本当执行证明会误报完成。
如何判断是否做对
对照每轮模型输入和工具日志,确认结果与call_id匹配。
Q033|用户输入一句话后,Agent怎样经过多轮模型调用完成任务?
核心回答
Agent把目标、规则、工具定义和当前证据组装成模型输入;模型返回普通文本或工具调用;Harness检查并执行调用,把结果作为新的上下文;模型依据观察继续决定下一步。循环以可验证完成、需要用户决策、失败或预算耗尽等条件结束。ReAct描述推理—行动—观察的结合,产品Harness负责把它落地为真实执行系统。[REACT][CODEX]
走完六轮,而不是只画一个循环箭头
| 轮次 | 模型此刻看到了什么 | 模型输出 | Harness/环境发生什么 |
|---|---|---|---|
| 1 | 用户目标、仓库规则、搜索工具描述 | 搜索exclude入口 | 搜索返回路径和行号 |
| 2 | 前述内容+搜索结果 | 读取实现与测试 | 返回带版本的代码片段 |
| 3 | 条件分支、失败案例 | 运行目标测试 | 进程退出1,输出断言失败 |
| 4 | 实际失败与相关代码 | 应用局部补丁 | 校验旧内容后修改文件 |
| 5 | 补丁结果与新版本 | 运行回归测试 | 记录命令、版本、退出码 |
| 6 | 当前diff和测试证据 | 完成说明 | Harness结束本任务 |
每轮输入可以包含历史,也可以通过服务端状态引用衔接,但逻辑上必须让模型知道与当前决策有关的结果。工具调用不是模型“想象自己已经读了文件”;必须真实执行,拿回真实输出。模型第3轮说“测试应该失败”不能代替运行结果。
模型可一次请求多个互不依赖工具,Harness也可能确定性地执行预设检查,因此真实流程不必每轮恰好一个动作。重要的是输入—决策—执行—反馈有明确对应关系。阅读文件、修改文件和测试结果之间的版本要能连接,否则会把旧证据用到新状态。
一个修复任务,为什么产生三次模型调用
输入与条件。任务是把 config.json 的超时从3改成5,工具有read、write、test。下面是确定的教学轨迹,而非真实模型自主运行。
| 步骤与对象 | 内部动作 | 中间结果 / 新状态 |
|---|---|---|
| 1调用1输入 | 用户任务+可用工具说明 | 模型提出read,尚未承诺文件已修改。 |
| 2工具1结果 | 读取版本v7,timeout=3 | 结果连同调用ID进入历史。 |
| 3调用2输入 | 任务+read调用+结果 | 模型提出在v7基础上写timeout=5。 |
| 4工具2结果 | 写入成功,得到v8 | 宿主仍未证明功能测试通过。 |
| 5调用3输入 | 新版本和测试返回pass | 模型归纳变更与验证;如果模型还需自行提出test,则还要增加一轮。 |
| 6计数区别 | 用户看见一个任务 | 后端可能有多次模型请求和多次工具操作。 |
把过程连起来。一轮模型调用的输出不是任务的全部生命周期;哪些工具由模型决策、哪些由宿主自动触发必须说清。
边界与反例。本例test由宿主固定触发,因此三次调用成立;不要与“每一步工具都须另一次模型提议”的配置混计。
依据与适用范围:[TOOLS]。示例输入和场景由本书明确设定。
追问:为什么不一次把所有文件读完,再让模型直接给补丁?
小仓库有时可以,但大仓库成本高且无关信息多。更重要的是下一步证据需求常由前一步发现决定。先发现入口才能知道该查哪条调用链;先跑测试才能知道真实失败。按需读取是在用环境反馈减少猜测,不是为了人为增加调用轮数。
追问:Agent是不是一定由LLM自由决定所有步骤?
不是。预设workflow可以固定检索、验证、审批等关键步骤;LLM在不确定部分做判断。动态Agent允许模型选择路径,灵活但更难预测成本和行为。实际系统常混合两者,应按任务的不确定性分配控制,而不是把确定性程序也全部改成Prompt。[AGENTS]
追问:如何判断某次失败是模型问题还是Harness问题?
先保存本轮可见输入、可调用工具与实际结果。若正确文件未返回,是检索/工具链问题;返回了但被裁掉,是上下文问题;模型生成合法调用而执行器找不到工具,是注册/路由问题;所有必要证据都可见却推断错误,才更接近模型推理问题。最终错误文本本身不能定位责任层。
Q034|Agent有哪些状态?为什么对话摘要不能替代任务数据库?
核心回答
至少分三类:模型可见的认知上下文、Harness的控制状态、外部环境的实际状态。它们可能暂时不一致。摘要表达“认为发生了什么”,任务记录表达“系统追踪到什么”,文件/数据库/远程服务才是副作用实际发生的位置。
以一次提交为例拆状态
模型说“准备修改filter.py”,这是计划。Harness生成调用c17并记录pending,这是控制状态。工具完成磁盘写入,文件内容变了,这是环境状态。随后进程在把成功写回任务数据库之前崩溃:环境已成功,控制状态仍pending,模型还未看到结果。
恢复时若只读对话摘要“修改未完成”,会重复写;若只读pending就当失败,也可能重复副作用。应查动作身份、预期旧版本、写后内容或远端幂等结果,把状态恢复为已成功或仍未知。不能让模型凭语气决定。
教学上可用以下对象分工:
Task: 目标、约束、完成条件、总体状态
Turn: 一次用户推进或运行阶段
ModelCall: 本轮输入版本、输出项、使用量
ToolCall: 调用ID、参数、授权、执行状态
Artifact: 文件/结果对象及版本
Evidence: 哪个版本被哪条命令验证
这不是要求所有项目新增六张表。简单本地Agent可以用日志和文件实现;核心是别把这些语义混成一句“已完成”。
对话说“写好了”,但磁盘与任务状态都不同意
输入与条件。同一时刻有三份内容:assistant文本“已更新”;执行日志 call-7=timeout;磁盘版本仍为v7。
| 步骤与对象 | 内部动作 | 中间结果 / 新状态 |
|---|---|---|
| 1列出对象 | 自然语言、调用记录、外部文件 | 它们不是同一份状态的三个可靠副本。 |
| 2核对事实 | 读取磁盘仍timeout=3 | 不能把assistant表述当作写入证据。 |
| 3记录状态 | 工具请求可能尚未完成 | 先写unknown或pending,不能靠一句话强行改成succeeded。 |
| 4检查点内容 | 目标、当前版本、调用ID、状态、下一步核查 | 恢复进程据此继续。 |
| 5新会话反例 | 只把“已更新”摘要交给后继Agent | 它可能跳过真正的写入和验证。 |
把过程连起来。执行状态以系统证据为准;对话提供计划和解释,却不能替代外部结果。
边界与反例。unknown的后续处理依工具能否查询、是否具有副作用而定。这里只描述可诊断的状态模型。
依据与适用范围:[DERIVE]。示例输入和场景由本书明确设定。
追问:LLM的KV缓存属于哪一类?
它是推理服务内部计算状态,支持模型连续生成,不是业务完成状态。KV丢失可能需要重新Prefill,但文件修改仍然存在;数据库丢失也不会自动从KV恢复出可靠操作日志。这两种故障需要不同恢复手段。
追问:Plan文件、Checkpoint和长期记忆分别保存什么?
Plan保存希望完成的步骤,Checkpoint保存可恢复的执行位置和状态,长期记忆保存未来可复用事实或经验。计划“测试”不证明测试运行过;Checkpoint中一次通过不代表跨版本永远通过;长期经验“此类接口要幂等”也不能替代当前调用的状态。
追问:为什么每个状态都持久化可能过度工程化?
不是所有中间推测都值得落库。可重做的只读搜索和临时推理可以轻量处理;不可逆操作、长期任务恢复点、关键证据更值得持久化。按丢失后果决定存储强度,而不是为所有Token做复杂审计。
Q035|无状态API与有状态会话各怎样构建下一轮输入?会话ID是不是长期记忆?
核心回答
无状态调用通常由客户端传入需要的历史;有状态接口可以引用服务端已有会话或响应状态,减少客户端搬运。两者都要遵守模型上下文与产品状态规则。有会话ID不等于永久可用、不等于跨任务知识治理,也不保证计算缓存命中。[CODEX][COMPACT]
两条等价目标、不同实现的链
在无状态风格中,第二轮输入可能是[规则,用户任务,助手调用c1,工具结果c1]。第三轮再附加新调用及结果。若客户端漏掉工具调用只发工具结果,协议可能无法正确配对;若只把工具结果粘成用户文本,还可能丢失来源和可信边界。
有状态风格可能发送previous_response_id + 新工具结果,服务端恢复必要上下文。它降低的是显式传输与状态管理负担,不意味着全部旧历史无限进入模型。服务仍可能截断、压缩、要求传递特定状态项,具体依接口而定。
跨会话记忆还需要选择保存内容、身份隔离、版本、更新与失效。一个能续接十分钟前响应的ID,没有自动完成这些治理。相反,即使所有模型调用无状态,也可以通过自己的知识库与任务Checkpoint实现良好长期能力。
同一个session_id不代表相同的模型输入
输入与条件。第1轮用户说“变量a=7”,第2轮问“a是多少”。用两种API风格对比。
| 步骤与对象 | 内部动作 | 中间结果 / 新状态 |
|---|---|---|
| 1无状态调用 | 第2轮只发送问题 | 模型没有收到a=7,session_id只是业务字段时不会自动补历史。 |
| 2显式历史 | 第2轮发送两条用户消息及必要中间回复 | 此时a=7进入本次上下文。 |
| 3服务端会话 | 用受支持的conversation或previous-response机制 | 服务端按接口约定恢复相关项目,但仍受上下文、权限和生命周期约束。 |
| 4宿主检查 | 记录实际请求中的消息或服务端历史引用 | 不能仅凭本地界面显示了旧消息就认定模型看见。 |
| 5失效反例 | 服务端状态已删除,但本地沿用同一字符串 | 会话键不再对应可恢复内容。 |
把过程连起来。状态的所有者可能是应用、API服务或文件系统,必须明确谁负责保存和重新提供。
边界与反例。字段名只是概念举例,具体跨请求行为应按所用API文档验证,不为所有SDK作统一保证。
追问:能否把某个厂商的压缩块复制给另一个模型?
不应假定可以。压缩块可能是该服务专用的opaque状态,格式与解释依赖模型/接口。跨模型交接通常需要可读的事实、约束和证据引用,或双方明确支持的协议。加密状态不是通用Markdown摘要。[COMPACT]
追问:重试一次相同模型请求是否会得到同一个工具调用ID?
不能普遍假定。传输重连、请求重试与重新生成是不同操作。重新生成可能产生新的调用项;业务去重不能仅依赖模型随机给出的ID。执行器应维护自己的稳定动作身份,并结合接口提供的幂等或响应恢复机制。
追问:为什么切换模型后长任务可能退化?
不同模型对工具格式、上下文、推理状态和指令的支持不同;能力、Tokenizer和压缩表示也可能变化。安全切换需要重新构建兼容上下文,保留关键事实和未完成副作用,不能仅改模型名称继续塞原始状态。
Q036|Agent何时应该停?为什么“继续直到完成”容易变成死循环?
核心回答
停止需要结合模型判断和可验证条件。模型可以提出已完成、受阻或需要澄清,Harness还要检查结果证据、未决动作和预算。没有明确完成定义,循环可能反复搜索、改写计划或运行同一失败操作。
用完成谓词代替情绪判断
教学任务可定义:目标测试通过;新增边界测试通过;当前diff只包含授权范围;验证对应当前代码版本;没有unknown写操作。满足这些并不证明软件绝对无缺陷,但清楚定义了本任务的交付依据。
循环还应区分“没有新证据”与“任务仍有进展”。连续三轮读取同一文件而没有新假设、相同参数反复失败、补丁反复在两种写法间切换,都提示进展停滞。可以降低动作自由度,要求明确下一次操作预期检验什么,或者输出受阻原因。
预算至少包括墙钟时间、调用次数、Token、工具费用和风险。达到预算不应伪装完成;应保存已完成/未完成内容和准确证据。在长任务中,局部完成可交付,剩余问题明确分离,比无限“再审一遍”更可靠。
同一搜索重做五次,何时应该停止
输入与条件。任务需要找函数foo,工具连续返回not_found。前5次查询字符串、搜索目录和仓库版本完全相同。
| 步骤与对象 | 内部动作 | 中间结果 / 新状态 |
|---|---|---|
| 1标记进展 | 定义“新路径、新证据或新状态”才算新增信息 | 重复not_found不算进展。 |
| 2第2次失败 | 检查大小写、目录和符号可能更名 | 允许有理由地改变一次搜索策略。 |
| 3第3次同结果 | 识别查询指纹相同、无新证据 | 停止盲目重复,转到目录或调用者定位。 |
| 4终止条件 | 预算用尽且关键证据仍缺 | 报告缺口,不把找不到变成“函数不存在”的无条件结论。 |
| 5成功条件 | 已修复、当前版本验证通过、原目标满足 | 达到后不因还能继续反思就再开无意义循环。 |
把过程连起来。停止规则同时需要预算、进展和验收条件;只有最大轮数会过早停或长期空转。
边界与反例。指纹可用简单元组,不要求为所有文本引入哈希体系;示例轮数不是产品推荐阈值。
依据与适用范围:[DERIVE]。示例输入和场景由本书明确设定。
追问:停止条件是否必须写成很多硬规则?
不必。简单任务可由直接结果核验结束。高风险、长周期或曾反复失败的环节才值得增加明确条件。规则是为阻止具体失败模式,不是替代模型所有判断。
追问:模型说缺信息时,应立即问用户吗?
先看可用工具是否能解决。文件路径、测试命令、现有配置可以从仓库读取,不应把本可自行检索的问题交给用户。真正涉及业务意图、不可恢复操作选择或权限边界时,用户决策才不可替代。
追问:为什么不能用“所有测试都绿”作为唯一完成条件?
测试可能没覆盖目标、没有实际运行、被跳过、对旧版本执行,甚至被Agent删掉。还需要检查运行清单、退出状态、版本、测试有效性及改动范围。完整验证链在第22章展开。