停止条件
最大步数、预算上限、完成判定与死循环检测。
一句话理解:每个 Agent 循环都需要三道闸门:做成了怎么停,做不成怎么停,没有进展怎么停。不能只靠模型自己说“我做完了”。
Workflow 的终点写在代码里,走完最后一个节点就结束。Agent 不同:它的循环默认在“模型不再调用工具”时结束,这意味着什么时候停,默认由模型自己决定。
于是会出现两种代价很高的失败。一种是停得太早:证据还没查全,模型就给出一个听起来很合理的结论。另一种是停不下来:同一个接口反复重试、在两个方案之间来回切换、一遍遍压缩上下文却毫无进展,直到烧完预算。一个生产级 Agent 必须把“停下来”当成一项正式设计,而不是留给模型临场发挥。
循环默认怎么结束
回顾「Agent Loop 详解」中的伪代码:模型的回复里没有工具调用时,循环就结束。主流框架都是这样设计的:
- Claude Agent SDK:模型给出不含工具调用的回复时结束;同时提供
max_turns(最大工具调用轮数)和max_budget_usd(按客户端成本估算的预算上限)两个限制,默认都不限制。文档明确建议:对于开放式任务,设置预算是生产环境 Agent 的良好默认做法。 - OpenAI Agents SDK:模型输出符合预期类型的文本、并且没有工具调用时,视为最终输出;超过
max_turns时抛出MaxTurnsExceeded异常。
也就是说,框架给了你“模型觉得完成了”这一个出口,再加上一个硬上限。中间的完成判定、无进展检测、异常升级,都需要你自己设计。
四类停止条件
| 类型 | 回答的问题 | 典型做法 | 客服工单排查中的例子 |
|---|---|---|---|
| 完成判定 | 真的做成了吗? | 程序检查可验证的完成标准 | 每条工单都有原因和工具证据,或明确标记“证据不足” |
| 硬上限 | 最多允许花多少? | 最大轮数、预算、总耗时、单个工具的调用次数 | 单条工单最多 15 轮、总耗时 5 分钟(示例值) |
| 无进展检测 | 是不是在原地打转? | 重复调用检测、重复错误检测、状态无变化检测 | 同一接口、同样参数调用 3 次仍失败 |
| 外部中断 | 是否需要人或规则介入? | 用户中断、审批被拒、触发风险规则 | 发现需要修改订单状态,超出只读权限,必须转人工 |
四类停止条件像围在循环四周的闸门,任何一道被触发,循环都要停下来:
完成判定:把“做完”写成可检查的条件
“完成”要尽可能由程序判断,而不是只听模型的自述。不同场景的完成标准示例:
| 场景 | 模型容易说的“完成” | 可检查的完成标准 |
|---|---|---|
| Coding Agent 修 Bug | “已修复该问题” | 相关测试通过,构建通过,没有新增失败 |
| SEO 诊断 | “已完成全站检查” | 报告中的页面数等于站点地图中的页面数;每个问题都引用了脚本输出 |
| 销售下降分析 | “原因是缺货” | 结论中引用的数字能与原始数据核对,各因素解释的下降之和与总下降幅度大致吻合 |
| 客服工单排查 | “原因已查明” | 每个结论都关联至少一次工具调用的结果 |
当模型宣布完成、而程序检查不通过时,常见的处理方式是:把未通过的具体项反馈给模型,让它继续工作,但这个“打回”的次数本身也要设上限。
这个“宣布完成 → 程序检查 → 打回”的过程如下:
硬上限:不是拍脑袋定的
最大轮数、预算上限设多少,应该来自数据,而不是凭感觉:
- 收集一批成功完成的任务轨迹,统计轮数、Token 消耗和耗时的分布。
- 以成功任务的较高分位数(比如 P95)为基础,留出一定余量来设上限。
- 上线后持续观察“撞上限”的比例:比例过高,说明上限太紧或任务太难;几乎为零,说明可以收紧以控制失控风险。
不同任务类型应该有不同的上限。“查一条工单”和“排查一百条工单”用同一个上限,必然有一个不合理。成本测算方法见「成本模型」。
无进展检测:识别原地打转
常见的“空转”信号,以及一个简单的检测思路:
import json
from collections import Counter
class ProgressGuard:
def __init__(self, max_same_call=3, max_same_error=3, max_idle_turns=5):
self.calls = Counter()
self.errors = Counter()
self.idle_turns = 0
self.limits = (max_same_call, max_same_error, max_idle_turns)
def check(self, tool_name, args, result, state_changed):
max_same_call, max_same_error, max_idle_turns = self.limits
key = (tool_name, json.dumps(args, sort_keys=True, ensure_ascii=False))
self.calls[key] += 1
if self.calls[key] >= max_same_call:
return "repeated_call" # 同一工具、同样参数反复调用
if result.get("error"):
self.errors[(tool_name, result["error"])] += 1
if self.errors[(tool_name, result["error"])] >= max_same_error:
return "repeated_error" # 同一个错误反复出现
self.idle_turns = 0 if state_changed else self.idle_turns + 1
if self.idle_turns >= max_idle_turns:
return "no_progress" # 连续多轮没有新的有效信息
return None
其中“状态是否变化”需要结合业务定义,比如:是否有新的工单得出了结论、计划中是否有步骤被标记完成、是否获取到了之前没有的数据。
运行系统本身也需要防止空转。Claude Code 就是一个例子:如果某个文件或工具输出大到每次压缩上下文后又立即填满,它会在尝试几次后停止自动压缩并报错,而不是无限循环下去。
停下来之后做什么
“停止”不等于“失败了就什么都不给”。不同的停止原因,应该对应不同的收尾方式:
几个设计原则:
- 停止原因要显式记录。 Claude Agent SDK 会在结果中区分
error_max_turns、error_max_budget_usd等不同的结束类型。你自己的系统也应该区分“正常完成”“撞上限”“无进展”“被中断”,这是做监控和改进的基础。 - 部分结果也是结果。 排查了 88 条工单后撞上预算上限,应该交付这 88 条的结论,而不是整体报错。
- 说清楚下一步。 告诉用户卡在哪里、需要什么帮助,是把“失败”变成“可接管”的关键。升级给人工的设计见「什么时候停下来找人」,失败的界面呈现见「失败的呈现」。
- 保存状态以便继续。 如果任务可能在补充信息或提高预算后继续执行,停止时要保存好进度。
常见误区
- “设一个最大步数就够了。” 最大步数只能防止无限运行,无法区分“正常地花了很多步”和“一直在空转”,更无法防止过早结束。四类停止条件各自解决不同的问题。
- “上限设得越宽松越好,免得任务做不完。” 宽松的上限意味着失控时损失更大。上限应该基于成功任务的实际分布来设定,并持续调整。
- “撞到上限就是失败,直接报错。” 撞上限时,已经完成的部分往往很有价值。交付部分结果和清晰的未完成说明,远比一条报错信息有用。
- “模型说做完了,再让它自己检查一遍就行。” 自我检查有帮助,但完成判定的最后一关,应尽可能交给程序或外部信号。
设计要点
- 每类任务都有书面的、尽可能可由程序检查的完成标准。
- 最大轮数、预算、耗时上限按任务类型分别设置,并基于数据确定。
- 有针对重复调用、重复错误、连续无进展的检测。
- 停止原因被显式记录,并进入监控看板。
- 每种停止原因都有对应的收尾方式:交付什么、告诉用户什么、状态是否保存。
- 模型宣布完成但程序检查不通过时,“打回重做”的次数也有上限。
练习
为「客服工单排查 Agent」设计停止条件(约 40 分钟):
- 写出单条工单排查的完成标准(至少 3 条,尽量可由程序检查)。
- 假设你收集到 50 次成功排查的数据(均为示例):轮数中位数为 6、P95 为 11;耗时中位数为 40 秒、P95 为 2 分钟。据此设定轮数和耗时上限,并说明理由。
- 列出 3 种可能的空转场景,以及对应的检测规则。
- 填写下表:
| 停止原因 | 交付给用户的内容 | 是否保存状态 | 是否通知人工 |
|---|---|---|---|
| 完成判定通过 | |||
| 撞上轮数上限 | |||
| 检测到重复错误 | |||
| 需要写操作权限 |
延伸阅读
- How the agent loop works(Claude Agent SDK 文档):循环结束条件、
max_turns与max_budget_usd,以及各类结果状态。 - Running agents(OpenAI Agents SDK 文档):最终输出的判定规则与
max_turns。 - Building effective agents:关于为 Agent 设置停止条件、控制成本与错误累积的建议。
下一步
到这里,Workflow 和 Agent 各自的机制都讲完了。模块最后一课「混合架构」讨论生产系统里最常见的形态:Workflow 里嵌 Agent,Agent 里调 Workflow。