外观
智能体循环(Agent Loop)
如果整个 Agent 领域只能记住一张图,那一定是这张:模型输出 → 执行动作 → 观察结果 → 再喂回模型,如此往复,直到任务完成。这个循环叫 Agent Loop(智能体循环),它是「聊天机器人」和「智能体」之间的分水岭——前者是一次函数调用,后者是一个带状态的进程。
本页是全站最核心的一页。我们会回答四个问题:为什么单次 LLM 调用不够;这个循环内部到底发生了什么(附一份可以直接照抄改写的伪代码);循环应该在什么时候停下来(比想象中难得多);以及真实产品——Claude Code、SWE-agent、OpenHands——的循环设计有什么值得抄的差异。最后给一张失控模式对策表,这是你调 Agent 时每天都会遇到的东西。
一、为什么需要循环:单次调用解决不了多步任务
先想清楚一个朴素的问题:为什么不能一次调用就把活干完?
假设任务是「修复这个仓库里登录接口的 500 错误」。一个足够强的模型,理论上可以一口气输出完整的补丁。但实践中这条路走不通,原因不是模型不够聪明,而是信息不在场:
- 模型不知道登录接口的代码长什么样,它得先读文件;
- 读完发现报错来自下游的数据库查询,它得再读另一个文件;
- 改了代码,不知道改对没有,它得跑测试;
- 测试挂了,报错信息决定了下一步是修代码还是修测试。
每一步需要什么信息,取决于上一步看到了什么。任务的目标是预先知道的,但路径不可预知——这正是循环存在的理由:把「下一步做什么」的决定推迟到拿到最新观察之后,让模型基于真实的环境反馈逐步逼近答案,而不是在信息不全时一次性赌一个完整方案。
Anthropic 在《Building effective agents》里把这个区别讲得很干净:workflow 是预定义代码路径编排 LLM 调用(路径是写死的),agent 是 LLM 动态指挥自己的流程和工具使用(路径是模型现场决定的)。Agent Loop 就是后者的运行载体。
一个判断标准
如果你的任务路径可以完全画成流程图——先做什么、后做什么、每个分支怎么处理都能预先写死——那你要的是 workflow,不是 Agent Loop。循环的价值恰恰在于路径画不出来。大量「Agent 项目」的失败,是把本该用 workflow 的事硬塞进了 loop,付出了不可预测的成本和延迟,却没买到灵活性。
二、经典循环解剖:感知 → 思考 → 行动 → 观察
把循环拆开,每一轮迭代(iteration / step)固定走四拍:
┌─────────────────────────────────────────┐
│ │
▼ │
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ 感知 │──▶│ 思考 │──▶│ 行动 │──▶│ 观察 │──┘
│ Perceive │ │ Think │ │ Act │ │ Observe │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
组装上下文 LLM 推理决策 解析并执行工具 结果回灌上下文- 感知(Perceive):不是真的有传感器。对 LLM Agent 来说,「感知」就是组装这一轮发给模型的 context:系统提示、工具清单、任务描述、历史消息、上一步的观察结果。这一步是纯粹的工程,也是 上下文工程 的主战场。
- 思考(Think):调用 LLM。模型基于当前 context 输出决策——可能是一段推理加上一个工具调用(ReAct 风格),也可能是结构化的 tool_use 块(native function calling 风格)。这是循环里唯一「智能」的部分,其余三拍都是确定性代码。
- 行动(Act):把模型输出的动作解析出来,在真实环境里执行——跑 shell 命令、读写文件、调 API。这里要处理模型输出不合法、工具不存在、执行超时等各种脏情况。
- 观察(Observe):把执行结果(stdout、报错、文件内容、返回值)格式化成消息,追加进消息历史。下一轮迭代的「感知」会把它重新组装进 context。观察是循环的燃料,循环本质上是用观察驱动的上下文累积过程。
四拍里,思考和行动合起来常被叫作「模型的一步」,感知和观察是脚手架(harness / scaffold)的活。2024 年之后业界越来越清楚的一个事实是:同一个模型,套不同的脚手架,跑出来的成绩天差地别——SWE-agent 用没微调过的 GPT-4 Turbo 纯靠接口设计把 SWE-bench 解决率做到 12.5%,是此前最好成绩 3.8% 的三倍多。脚手架不是包装纸,是战斗力。
三、一份完整的 Agent Loop 伪代码
下面这份伪代码覆盖了生产级 loop 的所有必要组件:上下文组装、模型调用、工具解析与执行、观察回灌、停止条件、步数上限、错误兜底。它用 Python 风格书写,语法对齐 Anthropic Messages API 的 tool use 流程(tool_use / tool_result 块、stop_reason),但不绑定任何框架——事实上 Anthropic 自己也是这么建议的:很多模式直接用 API 几十行就能实现,理解底层比套框架重要。
python
MAX_STEPS = 40 # 步数硬上限:成本与失控的保险丝
MAX_CONSEC_ERRORS = 3 # 连续错误熔断阈值
def agent_loop(task: str, tools: list[Tool]) -> str:
# ── 1. 初始化消息历史(循环的唯一状态)─────────────────
messages = [
{"role": "system", "content": build_system_prompt(tools)},
{"role": "user", "content": task},
]
consec_errors = 0
# ── 2. 主循环 ────────────────────────────────────────
for step in range(MAX_STEPS):
# 2a. 感知:组装上下文(此处可做压缩/裁剪,见上下文工程)
context = assemble_context(messages)
# 2b. 思考:调用模型,附带工具清单
response = llm.create(
model="claude-sonnet-4-6",
messages=context,
tools=[t.schema for t in tools], # name + description + input_schema
)
# 把模型的原始输出完整追加进历史(思考过程也要留痕)
messages.append({"role": "assistant", "content": response.content})
# 2c. 停止判定:模型没调工具 = 它认为做完了
if response.stop_reason == "end_turn":
return extract_text(response) # 提取最终答复,退出循环
# 2d. 行动:逐个解析并执行工具调用
tool_results = []
for block in response.tool_calls():
try:
tool = find_tool(tools, block.name) # 工具不存在会抛异常
result = tool.execute(block.input) # 真实执行(带超时)
consec_errors = 0 # 成功一次就清零错误计数
tool_results.append(tool_result_block(block.id, result))
except Exception as e:
# 关键设计:错误不当异常抛出去,而是作为观察喂回模型
# 让模型自己看到失败并修正,这是 Agent 容错的核心机制
consec_errors += 1
tool_results.append(tool_result_block(
block.id, f"Error: {e}", is_error=True))
# 2e. 观察回灌:执行结果作为 user 消息进入历史
messages.append({"role": "user", "content": tool_results})
# 2f. 错误熔断:连续失败说明模型陷入了解不了的局面
if consec_errors >= MAX_CONSEC_ERRORS:
return f"连续 {consec_errors} 次工具执行失败,人工介入。"
# ── 3. 步数耗尽:循环的保险丝烧断了 ────────────────────
return f"已达最大步数 {MAX_STEPS},任务未完成,请人工检查。"这份伪代码里有几个值得逐条品味的设计决策:
- 状态就是消息历史,没有别的。整个 Agent 的记忆、进度、中间结论全在
messages数组里。这让循环天然可序列化、可恢复、可审计——把这个数组存下来,就是完整的 trace。 - 错误是观察,不是异常。工具报错(命令不存在、参数非法、执行超时)被包装成
is_error=True的 tool_result 喂回模型。模型看到报错后通常能自我修正——这是 ReAct 论文里就验证过的机制,也是现代 Agent 鲁棒性的主要来源。但连续失败要熔断,否则就是烧钱死循环。 - 停止靠
stop_reason,不靠字符串匹配。模型输出里没有工具调用(Anthropic API 表现为stop_reason == "end_turn")就意味着它决定收工。这是 function calling 时代最主流的停止信号,下一节细讲。 - 步数上限是必须的,不是可选的。没有
MAX_STEPS的 loop 等于没有刹车的车。2026 年 8 月 ollama 仓库就有一个真实案例:某模型在 Claude Code 兼容端点上陷入自我维持的循环,连续 193 次发出几乎相同的工具调用、烧掉约 3100 万 input token,直到人工打断。
伪代码与生产的距离
这份代码能用,但生产级 loop 至少还要加:context 超长时的压缩/截断(见 上下文工程)、每步的结构化日志与回放(见 可观测性)、成本计量与预算熔断(见 成本工程)、以及危险操作的人工审批闸口(见 Human-in-the-Loop)。循环本身简单,让循环可靠才难。
四、停止条件的艺术
「什么时候停」是 Agent Loop 设计里最被低估的问题。停早了任务没做完,停晚了烧钱甚至搞破坏。实践中有四类停止机制,生产系统通常组合使用。
4.1 模型自述完成(end_turn)
Function calling 时代的默认机制:模型不再发出 tool call、只输出自然语言,循环就结束。优点是零成本、零侵入;缺点是模型的「做完了」和实际做完了经常不是一回事——模型可能因为上下文太长而遗忘原始需求、因为连续受挫而提前认输、或者干脆偷懒。它回答的是「模型想停」,不是「任务已完成」。
4.2 结构化 finish 工具
给模型一个显式的 finish(或 submit、done)工具,约定只有调用它才算任务结束。SWE-agent 就是典型:模型必须调用 submit 命令提交补丁,循环才终止。好处是完成变成一个明确的、可携带参数的动作(比如提交最终答案、补丁路径),还可以在 finish 的执行逻辑里加验证闸——提交前自动跑一遍测试,不过就把失败结果作为观察打回去,拒绝收工。这把「模型自述」升级成了「模型自述 + 环境验证」,是消除过早收工最有效的手段。
4.3 预算耗尽(步数 / token / 时间 / 金额)
硬约束保险丝:MAX_STEPS 只是最常见的一种。生产系统通常同时设多道:最大迭代数、最大 token 消耗、最长墙钟时间、最大金额。SWE-agent 把预算控制作为一等公民,超出成本上限即中止。预算耗尽时的退出行为要想清楚:直接抛结果是最差的,好的做法是把当前进展、已确认的结论、卡在哪一步整理成交接报告再退出。
4.4 僵局检测(loop detection)
模型没说停、预算没耗尽,但循环已经死了——连续 N 步发出相同或近乎相同的动作、观察结果不再变化、context 增长停滞。这类「僵尸循环」靠前面三种机制都拦不住,需要显式检测:对最近 K 步的动作做相似度比较(最简单的就是哈希去重),命中阈值就中断或降级处理(比如强制模型先总结现状再决策)。上一节提到的 193 次相同工具调用的真实事故,一个三步窗口的重复检测就能拦下。
| 停止机制 | 回答的问题 | 优点 | 风险 |
|---|---|---|---|
| 模型自述完成 | 模型想停吗 | 零成本、自然 | 过早收工、偷懒 |
| finish 工具 + 验证 | 任务真做完了吗 | 完成可验证、带产物 | 验证逻辑要写对,否则误判 |
| 预算耗尽 | 还烧得起吗 | 兜底可靠 | 中断即丢进展(需交接报告) |
| 僵局检测 | 循环还活着吗 | 专杀僵尸循环 | 相似度阈值误伤正常重试 |
五、ReAct 模式精讲
Agent Loop 是骨架,ReAct 是给骨架注入灵魂的决策模式。它出自论文《ReAct: Synergizing Reasoning and Acting in Language Models》(Shunyu Yao 等,普林斯顿大学与 Google Brain 团队,2022 年 10 月上传 arXiv,编号 arXiv:2210.03629,后发表于 ICLR 2023,口头报告)。论文基于 PaLM-540B,在 HotpotQA、FEVER 两个知识问答基准和 ALFWorld、WebShop 两个交互式决策基准上做了系统验证。
5.1 核心机制:三个字段的交替
ReAct 的洞察朴素到事后看像废话:让模型每步先输出一段推理轨迹(Thought),再输出动作(Action),环境返回观察(Observation),三者交错进行:
Thought 1: 我需要先找到登录接口的路由定义,应该在 app/routes 目录下。
Action 1: search_dir["login", "app/routes"]
Observation 1: 找到 3 个文件:auth.py, session.py, oauth.py ...
Thought 2: auth.py 最相关,先读它,重点看数据库查询部分。
Action 2: view["app/routes/auth.py"]
Observation 2: (文件内容...第 47 行:db.query(User).filter_by(...))
Thought 3: 第 47 行的 filter_by 传参在 username 为 None 时会抛异常……
Action 3: edit["app/routes/auth.py", ...]为什么是交错而不是分开?论文的论证是:Thought 让 Action 有依据(动作是从推理里推出来的,不是瞎猜的),Observation 让 Thought 不跑偏(推理被真实环境反馈不断纠偏)。纯推理(Chain-of-Thought)容易在事实缺失时幻觉出错误前提并一路滚雪球;纯行动(只有 Action 没有 Thought)则缺乏任务分解和异常处理的能力。两者协同,互相锚定。
5.2 ReAct 今天的形态
一个常见的误解是「ReAct 过时了,现在都是 native function calling」。恰恰相反:function calling 是 ReAct 的工业化封装。今天 Claude、GPT 等模型的 tool_use 机制,本质就是把 Thought(assistant 消息里的文本/思考块)+ Action(tool_use 块)+ Observation(tool_result 块)的三元组用 API 结构固定下来,省掉了 prompt 里教模型按格式输出的麻烦。你去看 Claude Code 或任何现代编码 Agent 的 trace,里面仍然是原汁原味的 Thought/Action/Observation 交替。ReAct 不是被取代了,是被内置了。
5.3 ReAct 的失败模式
原论文自己就诚实地记录了失败案例,最典型的是 reasoning-trace drift(推理漂移):长链条里后面的 Thought 越来越多地基于前面自己生成的 Thought 而不是基于 Observation 来推理,逐渐脱离环境实际,在错误的方向上自我强化。ALFWorld 上的不少失败就源于此。工程上的对策:定期让模型对照原始目标和已有观察做「现实检查」,或者对长轨迹做 规划 层的显式纠偏——这也解释了为什么长任务上纯 ReAct 往往需要外挂规划模块。
六、真实产品的循环设计对比
Agent Loop 的理论各家相同,落到工程里差异很大。挑三个最有代表性的系统对比——它们的差异点几乎就是「loop 设计决策空间」本身。
6.1 Claude Code:极简主循环 + 停止即交付
Claude Code 的主循环是三者里最接近第三节伪代码的:一个 while 循环,反复调用模型,只要响应里还有 tool_use 块就执行工具、把 tool_result 塞回去继续;直到模型给出不含工具调用的文本响应(stop_reason == "end_turn"),循环结束、文本交付给用户。没有显式 finish 工具,没有规划器,没有验证闸——停止条件完全交给模型的自述。
这个极简是刻意的:Anthropic 在《Building effective agents》里明确说,最成功的实现往往不是复杂框架,而是简单、可组合的模式,并建议开发者直接用 API 搭、理解底层再考虑框架。Claude Code 把复杂度转移到了别处——工具设计(Read/Grep/Edit 等小而清晰的工具)、系统提示、权限闸(危险命令需用户批准)、以及 CLAUDE.md 项目记忆。loop 本身薄如纸,厚的是 loop 周围的东西。
6.2 SWE-agent:一切为了 ACI
SWE-agent(普林斯顿 NLP 团队,NeurIPS 2024,arXiv:2405.15793)的循环同样朴素——模型输出一条命令,环境执行,结果回灌——但它证明了循环里执行的那层接口比循环本身重要一百倍。它提出的 ACI(Agent-Computer Interface,智能体-计算机接口)是一组专为 LLM 设计的命令与反馈格式,核心设计包括:
- 搜索结果限流:
find_file、search_dir等命令单次最多返回约 50 条结果,防止观察淹没 context; - 窗口式文件查看器:有状态的 file viewer 每次只显示约 100 行,配
scroll_up/scroll_down导航,而不是把整文件灌进来; - 行内反馈的编辑命令:编辑命令附带语法检查,改坏了立即报错打回,而不是等到跑测试才发现;
- 显式
submit:模型必须调用 submit 命令提交补丁,循环才终止(4.2 节的 finish 工具模式)。
效果上的证据很硬:同一个没微调的 GPT-4 Turbo,靠这套 ACI 把 SWE-bench 解决率做到 12.5%,是当时最好非交互方法 3.8% 的三倍多;论文还系统消融了 ACI 各设计对成绩的影响。结论一句话:工具设计 > 模型能力,在 loop 的预算里,先把钱花在观察和行动这两拍上。
6.3 OpenHands:事件流作为一等公民
OpenHands(前身为 OpenDevin,arXiv:2407.16741)把循环抽象成三个组件:Agent 抽象、事件流(Event Stream)、Agent 运行时(Runtime)。它最关键的设计是把「消息历史」升级成一个严格按时间排列的事件流——agent 的每个 action 和环境的每个 observation 都是事件流里的一条记录,agent 决策时从事件流组装 prompt,执行后新事件再追加回流。
这个抽象带来的工程红利:
- 状态即事件流:整个 agent 状态可序列化、可回放、可分叉,天然支持断点恢复和人工审计;
- 观察者模式:其他组件(UI、日志、评估器)可以挂在事件流上做订阅,不用侵入 loop 本体;
- 多 agent 委托:子 agent 的执行也被建模为事件(delegation action / observation),主循环不用改结构。
OpenHands 的默认 agent 是 CodeActAgent,走 CodeAct 路线:动作不是零散的 JSON 工具调用,而是可组合的 Python 代码段,在沙箱里执行,stdout/stderr 作为观察回灌。
6.4 三者对比
| 维度 | Claude Code | SWE-agent | OpenHands |
|---|---|---|---|
| 循环骨架 | while + tool_use,极简 | 单命令步进,极简 | 事件流驱动,抽象最厚 |
| 停止条件 | 模型自述(end_turn) | 显式 submit 命令 | finish 动作 / 预算熔断 |
| 动作形态 | 结构化工具调用 | ACI 定制命令 | CodeAct:Python 代码 |
| 观察设计 | 工具原样返回 + 截断 | 限流/窗口化,精心裁剪 | 事件流统一建模 |
| 核心赌注 | 强模型 + 薄脚手架 | 接口设计决定成败 | 抽象换可扩展性 |
| 适用场景 | 交互式编码助手 | 学术评测/批处理任务 | 通用软件开发平台 |
三家没有谁的 loop 是「对的」,差异完全来自场景:交互式产品要极简低延迟,学术评测要可控可复现,平台要可扩展。你设计自己的 loop 时,先想清楚站哪一排。
七、常见失控模式与对策
Agent Loop 最臭名昭著的特性是:它能干活,也能以极高的效率烧光你的预算同时什么都不干。以下是三类最高发的失控模式,以及经实践验证的对策。
7.1 死循环(infinite loop)
症状:模型反复调用同一个工具、传入相同参数、得到相同结果,永不停止。典型诱因:工具报错信息没回灌清楚(模型不知道失败了)、模型卡在「再试一次说不定就行」的局部最优。极端案例就是前文提到的 193 次相同调用烧掉 3100 万 token 的真实事故。
对策:动作重复检测(最近 K 步动作哈希去重,命中即熔断);把工具错误以醒目的 is_error 形式回灌;MAX_STEPS 和成本双保险丝永远在场。
7.2 原地打转(thrashing)
症状:没有严格重复,但循环在两个状态之间震荡——改 A 文件导致测试挂,改回去另一个测试挂,再改回来……轨迹在涨,进展为零。和死循环的区别是动作序列不重复,哈希检测抓不到。
对策:观察级别的停滞检测(最近 N 步的观察相似度过高即判定停滞);停滞时强制模型停下来做「现状总结 + 路径反思」再继续,打断震荡的惯性;任务层面设里程碑,超期未到里程碑即升级处理(换策略、求助人工)。这类问题本质上是规划缺失,参见 规划与任务分解。
7.3 过早收工(premature exit)
症状:任务做了一半,模型自信满满地输出「已完成」然后 end_turn。诱因包括:长 context 里原始目标被稀释、连续受挫后的退缩、以及模型对「完成」的标准本来就比人低。这是三种失控里最隐蔽的——循环正常结束了,只是结果不对。
对策:最有效的是 4.2 节的验证闸——finish 不是终点,环境验证通过才是;其次是提示工程层面把完成标准写成可检查的清单(「提交前必须:所有测试通过、改动覆盖需求第 2、3 条」);对长任务用 Todo 清单机制锚定目标。评测层面,这类失败要靠端到端任务成功率才能暴露,单看 trace 往往觉得「每步都挺合理」,见 Agent 评测。
| 失控模式 | 可观测信号 | 根因 | 首选对策 |
|---|---|---|---|
| 死循环 | 动作严格重复 | 错误未回灌 / 局部最优 | 动作哈希去重 + 熔断 |
| 原地打转 | 观察停滞、轨迹震荡 | 无规划、无记忆 | 停滞检测 + 强制反思 |
| 过早收工 | 提前 end_turn | 目标稀释 / 完成标准低 | finish 工具 + 环境验证闸 |
一条朴素但救命的经验
上线任何 Agent 之前,先跑一个「失控演习」:故意给它一个无解的任务(比如让它修复一个不存在的 bug),观察循环的行为。它应该在预算内体面地认输并解释卡点,而不是烧穿限额。无解任务下的表现,比有解任务下的成功率更能预测生产事故的烈度。
参考资料
- ReAct: Synergizing Reasoning and Acting in Language Models (arXiv:2210.03629) —— ReAct 原始论文,Yao et al.,ICLR 2023,本页第五节的依据。
- ReAct 项目主页 —— 论文配套代码与演示。
- Building effective agents — Anthropic Engineering —— workflow 与 agent 的分野、极简 loop 优先的工程哲学,第一、六节引用。
- SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering (arXiv:2405.15793) —— ACI 设计与消融实验,12.5% vs 3.8% 的出处,第六节引用。
- OpenHands: An Open Platform for AI Software Developers as Generalist Agents (arXiv:2407.16741) —— 事件流架构与 CodeActAgent,第六节引用。
- Basic agentic loop with Claude and tool calling — Temporal AI Cookbook —— Anthropic API 上 agentic loop 的可运行实现,与第三节伪代码互证。
- ollama issue #17617:模型陷入自我维持的工具调用循环 —— 193 次相同调用、约 3100 万 token 的真实失控事故记录,第四、七节引用。