Agent 循环设计:停止、反思、分支
Agent 的核心是个循环,但"循环"设计得好不好决定它是智能还是乱跑。本文讲清 Agent 循环的几个关键设计:停止条件(何时结束)、反思(self-reflection,做完一步自评)、分支与回溯、最大步数与预算、以及在循环里加"人工检查点"把不可逆操作拦住。
本文要点
- 主题聚焦:AI、AI Agent
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
很多人第一次写 Agent,写完都觉得"就这?":套个 while True,把 prompt 塞进去,加几个 tool 调用,跑起来看着它自己查资料、改代码、调 API,确实有点魔法。但你很快会遇到三类崩盘:它停不下来(烧钱)、它跑偏了还自我感觉良好(智障)、它把生产库 DROP 了(事故)。
这些问题的根都不在模型,在循环。Agent 的"智能"有一半住在那个循环里——什么时候停、什么时候反思、什么时候换路、什么时候叫人——这些工程决策,决定了同样一个模型跑出来是"能用"还是"灾难"。
这篇文章拆解 Agent 循环的几个关键设计点,都是我在生产里踩过的坑。
一、循环的本质:思考 → 行动 → 观察
最朴素的 Agent 循环叫 ReAct(Reason + Act),三个阶段反复轮转:
┌─────────────────────────────────────────────┐
│ Thought(思考):我现在该干嘛? │
│ ↓ │
│ Action(行动):调用某个 tool │
│ ↓ │
│ Observation(观察):拿到 tool 的返回值 │
│ ↓ │
│ 回到 Thought,直到任务完成 │
└─────────────────────────────────────────────┘
伪代码长这样:
state = {"messages": [system, user]}
while True:
msg = llm(state["messages"])
state["messages"].append(msg)
if msg.done: # 停止条件
break
for call in msg.tool_calls:
result = run_tool(call)
state["messages"].append(result)
看起来就十行,但每一个分支判断都是设计决策。一个能上生产的循环,至少要回答清楚:什么时候停?做错了怎么办?走死胡同怎么办?花超了怎么办?危险动作谁拦? 这五个问题答不好,Agent 就是个不稳定的炸药包。
二、停止条件:什么时候该喊停
停止条件是循环设计里最容易被忽视、却最烧钱的一环。我见过太多 Agent 因为"不知道自己完成了"而无限绕圈:要么反复确认、要么反复"优化"、要么在两个状态间反复横跳。
一个稳健的 Agent 至少要有 五道停止信号,且是 OR 关系(任意一个触发就停):
- 任务完成信号:模型显式输出
done=true/ 调用finishtool / 输出约定的终止 token。这是"主动停"。 - 最大步数上限:
max_steps=30,硬性兜底。这是"被动停"的第一道。 - 预算上限:时间(如 10 分钟)或 token(如 200k)超了就停。比步数更精确,因为有的 step 烧 token 不烧步。
- 用户中断:UI 上一个"停止"按钮,把
cancel信号打进循环。 - 死循环检测:连续 N 步重复同样的 action、或状态长时间不变,主动打断。
死循环检测很多人不做,我强烈建议加。一个简单有效的实现:
recent_actions = state["recent_actions"][-5:]
if len(set(hash(a) for a in recent_actions)) == 1:
# 连续 5 步做一模一样的事,打断
raise LoopDetected("agent stuck")
更狠一点,看"状态相似度":如果连续 10 步的 observation 内容向量化后余弦相似度都 > 0.95,基本就是它在原地打转。
一个观点:停止条件不是"加一个就够了",而是层层兜底。完成信号是优雅退出,其余都是保险栓。没有保险栓的 Agent,迟早会在某个边角 case 上烧掉一整夜的预算。
三、反思(self-reflection):让 Agent 自评"我做得对吗"
ReAct 的原始论文里,Agent 只往前走,不回头看。但人解决复杂问题时有个关键动作:做完一步,停下来问一句"刚才这步对吗?够吗?要不要改?" 这就是 self-reflection。
实证数据很扎实:在 SWE-bench、WebArena 这类复杂任务上,加了 self-reflection 的 Agent 成功率普遍提升 10–30%。原因是 LLM 一次性规划经常出错(尤其步骤多的任务),但让它"回头看"它往往能发现自己哪儿错了——评判比生成容易。
实现上,反思可以插在两个粒度:
- 每步反思:每个 action 之后,加一轮轻量自评。
- 阶段反思:每完成一个子目标(比如"修完一个文件""跑完一次测试"),做一次更深的复盘。
反思的 prompt 结构大致是:
你刚才的目标: <goal>
你这几步的动作: <actions>
观察到的结果: <observations>
请判断:
1. 目标是否已完成?给出置信度。
2. 中间有没有走错的路?有的话错在哪?
3. 如果还没完成,下一步最该做什么?是否需要回退?
反思的产物不是"一段感想",而是一个 结构化决策:继续 / 回退 / 终止。这个决策要写回 state,驱动循环。
Action → Observation → Reflect →
├─ continue: 进入下一 Thought
├─ backtrack: 回到某历史节点换路
└─ stop: 任务完成或判定无解
一个观点:反思不是越多越好。每步反思会显著增加 token 和延迟,在简单任务上纯属浪费。我的实践是——任务步数预计 > 5、或工具结果不确定性高(比如浏览器自动化、外部 API)时才开反思;简单的一次查询类任务,反思反而是负优化。另外反思要换一个 prompt 角色(让它"扮演评审者"),否则模型容易自我肯定、看不出自己的错。
四、分支与回溯:树搜索式 Agent
ReAct 是一条路走到底,走错了只能靠反思硬掰回来。但有些任务(比如复杂调试、多解方案探索)天然需要"试一条路,不行换一条"——这就是树搜索式 Agent。
核心思想:把 Agent 的探索过程建模成一棵树,每个节点是一个状态,每个 action 是一条边。走某条边失败后,回退到分叉点,换一条边。
root
/ \
search-A search-B
/ \ |
ok fail ok ← 回退到 root, 选 search-B, 再走
实现要点有三个:
- 状态快照:每个节点保存一份可恢复的 state(不只是 messages,还有文件改动、环境变量等副作用点)。
- 评分函数:给每个分支打分(可以用一个小 LLM、或基于规则的启发式),决定优先扩展哪条。
- 回退机制:失败后能"撤销"副作用——这是最难的部分。改了文件要能回滚(
git是天然的状态快照),调了外部 API(发了邮件、提交了订单)就没法回退,这种操作根本不该放进可回溯的分支里。
def explore(node, budget):
if node.is_goal() or budget <= 0:
return node
for action in node.possible_actions():
child = node.step(action)
result = explore(child, budget - 1)
if result.solved:
return result
# 失败,记录原因,尝试下一个 action
return node # 所有分支都失败
一个观点:树搜索式 Agent 在公开 benchmark 上效果惊艳,但生产里慎用。原因有二:一是代价巨大(同一个问题可能探索多条路径,token 翻倍),二是副作用回退在真实系统里几乎不可能做干净。我的建议是只在纯信息空间的任务上用(比如"从多个文档里找答案""生成多个方案让用户挑"),涉及真实世界副作用的任务还是老老实实走线性 + 反思。
五、预算与上限:防失控的三道闸
一个没有上限的 Agent 循环,本质就是一个"按 token 计费的死循环"。LLM 调用比传统循环贵三个数量级,几小时不管它能烧掉你一整月的 API 配额。
防失控至少要有三道闸,从松到紧:
| 闸门 | 粒度 | 作用 |
|---|---|---|
| 步数上限 | 粗 | 防 Agent 在任务里无限绕,最易实现 |
| 时间上限 | 中 | 防卡在某个慢 tool(比如爬一个不响应的页面) |
| token 上限 | 细 | 最精确的成本控制,直接对应钱 |
预算的关键设计是:预算是全局共享的,不是每个 tool 一份。否则 Agent 会在一个 tool 上把预算花光。我习惯维护一个 Budget 对象,每次 LLM 调用和 tool 调用都从里面扣:
class Budget:
def __init__(self, max_steps, max_seconds, max_tokens):
self.steps = max_steps
self.deadline = time.time() + max_seconds
self.tokens = max_tokens
def spend(self, steps=1, tokens=0):
self.steps -= steps
self.tokens -= tokens
if self.steps <= 0 or self.tokens <= 0:
raise BudgetExceeded()
if time.time() > self.deadline:
raise Timeout()
超预算时,优雅降级比硬报错好——让 Agent 收到一句"预算还剩 5%,请尽快收尾",它往往能在一个 step 内把当前进度总结给用户,而不是留下一个半成品。
六、人工检查点:把不可逆操作拦住
Agent 最危险的时刻,是它要执行不可逆操作的那一步:rm -rf、DROP TABLE、发邮件、推 commit、付款、调用生产环境的写接口。这些动作一旦做了就回不来,而 LLM 又天生有"自信地犯错"的毛病。
解法是 human-in-the-loop:在这类动作前,强制插入一个检查点,暂停循环,等人确认。
DANGEROUS_TOOLS = {"delete_file", "run_migration", "send_email", "deploy"}
while True:
msg = llm(state)
for call in msg.tool_calls:
if call.name in DANGEROUS_TOOLS:
approval = await request_human_approval(call)
if not approval:
state.append({"role": "tool",
"content": "用户拒绝了此操作,请换方案"})
continue
result = run_tool(call)
工程上有几个细节决定它好不好用:
- 危险分级:不是所有动作都要打断。
read_file不拦,delete_file拦,deploy_to_prod不仅拦还要二次确认。给每个 tool 打risk_level标签。 - 上下文充足:审批时不能只甩一句"要执行 delete_file,同意吗?",要把 Agent 的完整推理链路(为什么要删、删的是哪个文件、删了会怎样)展示给审批人,否则人也是瞎点。
- 超时策略:人 10 分钟没回应,默认拒绝还是默认通过?我强烈建议默认拒绝,宁可卡住也不要默认放行。
- 可批量化:如果一个任务里要删 50 个文件,逐个确认会把人逼疯。提供"本次任务内同类操作统一授权"的选项,但要限定范围(同一次会话、同一类 tool、同一目录)。
一个观点:检查点不是"降低 Agent 自主性",而是"扩大 Agent 能被授权做的工作范围"。一个有检查点的 Agent,可以被放心地接入生产环境;一个没有的,只敢在沙盒里玩。检查点是 Agent 从 demo 走向生产的必经之路。
七、循环的工程:持久化、trace、并发
最后讲三个"不性感但决定能不能上线"的工程问题。
状态持久化:长任务(几十分钟、跨小时)循环里,进程随时可能挂——OOM、网络抖动、用户关浏览器。循环状态必须能落盘、能恢复。把 state 序列化成 JSON 存 Redis/DB,每步 checkpoint,重启时从最后一步接着跑。这是 Agent 从"玩具"到"产品"的分水岭。
state = load_or_init()
while not done(state):
state = step(state)
save(state) # 每步落盘
Trace 与可观测:Agent 出错时,你需要能看到完整推理链——每一步的 Thought、调了什么 tool、传了什么参数、返回了什么。这是调试的命脉。把每步的结构化事件打到 trace 系统(LangSmith、Langfuse、Arize Phoenix 都行),按 run_id 串成树。没有 trace 的 Agent 是黑盒,黑盒没法运维。
并发与并行尝试:树搜索里我提到"多分支并行尝试",工程上靠 asyncio / 线程池让多个 branch 同时跑。但要注意:只有纯函数式的、无副作用的分支才能并行(比如同时让两个思路去搜文档);涉及共享环境(同一个文件系统、同一个 DB)的并行只会互相踩踏。并行带来的复杂度(结果合并、资源竞争、错误处理)往往超过收益,默认串行、确实有收益才上并行。
小结
- 循环的本质是 think → act → observe,但"转起来"只是及格线。决定 Agent 好不好用的是循环里的判断:何时停、何时反思、何时换路。
- 停止条件要层层兜底。完成信号 + 最大步数 + 预算上限 + 用户中断 + 死循环检测,五道闸 OR 起来,缺一不可。
- 反思能显著提升复杂任务成功率,但要换角色、看场景,简单任务开反思是负优化。
- 预算的三道闸(步数/时间/token)对应三种失控模式,且要全局共享、可优雅降级。
- 人工检查点是不可逆操作的标配,它不是降低自主性,而是扩大 Agent 能被授权的工作范围。
Agent 的智能,一半在模型,一半在循环。模型决定天花板,循环决定下限——而生产环境里,下限才是决定生死的那条线。
Related