造 AI Agent:循环、停止条件、和能说「不行」的东西

循环转起来只要一个 while。难的是让它停,而且停得对——因为让干活的那个判断干完没有,是让它给自己判卷。

位置
第 04 篇 / 共 5 篇
预计
4 分钟

上一篇讲了工作流。这一篇造 agent —— 也就是把「下一步做什么」交给模型。

循环本身很简单

去掉框架之后剩下的
for turn in range(max_turns):
msg = model(messages, tools=schemas)
messages.append(msg)
if not msg.tool_calls: # ← 它自己决定停
break
for call in msg.tool_calls: # ← 它自己决定用哪个工具
out = tools[call.name](**call.args)
messages.append(tool_result(call, out))
else:
return "达到轮数上限" # ← 有界,不许无限转

加粗的两行是全部的自主性,第三处是全部的安全感。 其余是管道。

框架会在这外面包很多东西 —— 追踪、会话、并发、编排 —— 但内核不变。看懂这十几行,再读任何框架的文档,你都能一眼认出它替你做了哪一段。

停止条件是真正难的地方

「循环」这个词容易让人以为难点在转起来。难的是停。

三类停止条件,可靠性差别很大:

一、固定轮数。 最钝:三轮可能没干完,也可能第一轮就该停。

二、模型自己说完成了。 最危险 —— 它几乎总是说完成了。让干活的那个判断干完没有,是让它给自己判卷。

三、独立判据。 交给一段确定性检查,或者一个没参与干活的模型。

第三类才是正解。这是银行业几十年的双人复核原则:录入大额转账的人和复核的人必须是两个人。搬到 agent 上就是 —— 写东西的和判断写完没有的,不能是同一个。

为什么它会夸自己

这不是智力问题,是结构问题。

它没法跳出自己的视角:它脑子里装的是「我为什么这么做」的一整套理由,所以它看到的不是结果,是那串说服自己的过程。

有个反直觉但很有用的结论:教一个独立的评估器变挑剔,比教生成器自我批评容易得多。 前者你可以直接给它一句「我假设这是坏的,除非你证明给我看」;后者你没法让作者跳出自己。

评估器要动手,不能只读

换个 agent 还不够。只读的评估器判的是「这看起来对吗」,不是「这跑起来对吗」。

有实践是把评估器接上浏览器工具,让它真的打开页面、点按钮、截图、查 DOM,判断依据于是从「这段代码看着没问题」变成「我点了按钮,页面跳转了,截图在这儿」。

判断的依据要从「看起来对」变成「我试过了」。验证器决定上限这条规律在多份材料里反复出现。

循环跑飞的代价要乘以轮数

单次调用跑飞,代价是一次调用。循环跑飞,代价乘以轮数 —— 而且经常是在没人看的时候乘

所以预算是三个数同时卡着:单次运行上限、每日上限、最大重试次数。少任何一个都留着一条跑不停的路。

两个实现细节我都踩过:

记账要记在动作之前。 计数器写在调用返回之后,超时和被限流的请求就不进账 —— 它们花了时间和算力却不消耗预算。

当心 SDK 自己的重试。 常见客户端库默认静默重试两次 —— 你眼里的一次调用,网络上是三次请求。重试只能有一层负责。

有上限还不够

这不是个别现象,是最常见的那一类MAST 标注了 7 个多 agent 框架的 1642 条执行轨迹(标注者一致性 κ=0.88),失败模式的分布是:

失败模式 占比
原地重复同一步 15.7%(单项最高)
不知道什么时候算结束 12.4%
不遵守任务规格 11.8%

排第一和排第二的,全都是「停不下来」。

它跑满了上限,上限没救它。 所以还需要一条:连续多少轮无进展就升级给人。

判据可以很土:连续三轮的失败信息完全一样,说明在原地打转,停。

边界与代价

agent 的自主性和可测量性是此消彼长的。 每多给一分自主,你比较两个版本的能力就少一分。先确认你需不需要比较。

「有界」不等于「安全」。 上限防的是无限循环,防不了「它在上限内做了不该做的事」。不可逆动作仍然需要一道显式的门。

评估器本身也会错。 换一个 agent 换来的是结构上的独立,不是能力上的更强。它的价值在于它没被生成过程说服过 —— 仅此而已,但这已经很值钱。