09.06概念更新于 2026年9月17日

停止条件

最大步数、预算上限、完成判定与死循环检测。

09.06 · 课时讲解

停止条件

做成了怎么停,做不成怎么停,没有进展怎么停。

1 / 11

一句话理解:每个 Agent 循环都需要三道闸门:做成了怎么停,做不成怎么停,没有进展怎么停。不能只靠模型自己说“我做完了”。

Workflow 的终点写在代码里,走完最后一个节点就结束。Agent 不同:它的循环默认在“模型不再调用工具”时结束,这意味着什么时候停,默认由模型自己决定。

于是会出现两种代价很高的失败。一种是停得太早:证据还没查全,模型就给出一个听起来很合理的结论。另一种是停不下来:同一个接口反复重试、在两个方案之间来回切换、一遍遍压缩上下文却毫无进展,直到烧完预算。一个生产级 Agent 必须把“停下来”当成一项正式设计,而不是留给模型临场发挥。

循环默认怎么结束

回顾「Agent Loop 详解」中的伪代码:模型的回复里没有工具调用时,循环就结束。主流框架都是这样设计的:

  • Claude Agent SDK:模型给出不含工具调用的回复时结束;同时提供 max_turns(最大工具调用轮数)和 max_budget_usd(按客户端成本估算的预算上限)两个限制,默认都不限制。文档明确建议:对于开放式任务,设置预算是生产环境 Agent 的良好默认做法。
  • OpenAI Agents SDK:模型输出符合预期类型的文本、并且没有工具调用时,视为最终输出;超过 max_turns 时抛出 MaxTurnsExceeded 异常。

也就是说,框架给了你“模型觉得完成了”这一个出口,再加上一个硬上限。中间的完成判定、无进展检测、异常升级,都需要你自己设计。

四类停止条件

类型 回答的问题 典型做法 客服工单排查中的例子
完成判定 真的做成了吗? 程序检查可验证的完成标准 每条工单都有原因和工具证据,或明确标记“证据不足”
硬上限 最多允许花多少? 最大轮数、预算、总耗时、单个工具的调用次数 单条工单最多 15 轮、总耗时 5 分钟(示例值)
无进展检测 是不是在原地打转? 重复调用检测、重复错误检测、状态无变化检测 同一接口、同样参数调用 3 次仍失败
外部中断 是否需要人或规则介入? 用户中断、审批被拒、触发风险规则 发现需要修改订单状态,超出只读权限,必须转人工

四类停止条件像围在循环四周的闸门,任何一道被触发,循环都要停下来:

围绕 Agent Loop 的四类停止条件:完成判定、硬上限、无进展检测、外部中断 Agent Loop 模型逐轮决策 完成判定 真的做成了吗? 硬上限 轮数 · 预算 · 耗时 无进展检测 重复调用 · 重复错误 外部中断 用户 · 审批 · 风险规则 模型宣布完成 做成了怎么停 做不成怎么停 没有进展怎么停 需要人介入时停
图 1 · 四道闸门:不能只靠模型自己说“我做完了”

完成判定:把“做完”写成可检查的条件

“完成”要尽可能由程序判断,而不是只听模型的自述。不同场景的完成标准示例:

场景 模型容易说的“完成” 可检查的完成标准
Coding Agent 修 Bug “已修复该问题” 相关测试通过,构建通过,没有新增失败
SEO 诊断 “已完成全站检查” 报告中的页面数等于站点地图中的页面数;每个问题都引用了脚本输出
销售下降分析 “原因是缺货” 结论中引用的数字能与原始数据核对,各因素解释的下降之和与总下降幅度大致吻合
客服工单排查 “原因已查明” 每个结论都关联至少一次工具调用的结果

当模型宣布完成、而程序检查不通过时,常见的处理方式是:把未通过的具体项反馈给模型,让它继续工作,但这个“打回”的次数本身也要设上限。

这个“宣布完成 → 程序检查 → 打回”的过程如下:

完成判定流程:模型宣布完成后由程序检查,不通过则打回,打回次数超限转人工 模型宣布完成 程序检查 可验证的完成标准 交付结果 附证据 反馈未通过项 模型继续工作 转人工 交付部分结果 通过 不通过 打回次数超过上限
图 2 · 完成判定的最后一关交给程序,打回重做的次数也要有上限

硬上限:不是拍脑袋定的

最大轮数、预算上限设多少,应该来自数据,而不是凭感觉:

  1. 收集一批成功完成的任务轨迹,统计轮数、Token 消耗和耗时的分布。
  2. 以成功任务的较高分位数(比如 P95)为基础,留出一定余量来设上限。
  3. 上线后持续观察“撞上限”的比例:比例过高,说明上限太紧或任务太难;几乎为零,说明可以收紧以控制失控风险。

不同任务类型应该有不同的上限。“查一条工单”和“排查一百条工单”用同一个上限,必然有一个不合理。成本测算方法见「成本模型」。

无进展检测:识别原地打转

常见的“空转”信号,以及一个简单的检测思路:

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 就是一个例子:如果某个文件或工具输出大到每次压缩上下文后又立即填满,它会在尝试几次后停止自动压缩并报错,而不是无限循环下去。

停下来之后做什么

“停止”不等于“失败了就什么都不给”。不同的停止原因,应该对应不同的收尾方式:

不同停止原因对应的收尾方式 停止原因 完成判定通过 交付结果 + 证据 撞上硬上限 交付已完成部分 + 未完成清单 如“剩余 12 单建议人工处理” 检测到无进展 说明卡在哪里 已尝试的方法 + 继续需要什么 外部中断 / 风险规则 保存当前状态 说明需要谁做什么决定
图 3 · 停止不等于什么都不给:每种停止原因都有对应的交付物

几个设计原则:

  • 停止原因要显式记录。 Claude Agent SDK 会在结果中区分 error_max_turns、error_max_budget_usd 等不同的结束类型。你自己的系统也应该区分“正常完成”“撞上限”“无进展”“被中断”,这是做监控和改进的基础。
  • 部分结果也是结果。 排查了 88 条工单后撞上预算上限,应该交付这 88 条的结论,而不是整体报错。
  • 说清楚下一步。 告诉用户卡在哪里、需要什么帮助,是把“失败”变成“可接管”的关键。升级给人工的设计见「什么时候停下来找人」,失败的界面呈现见「失败的呈现」。
  • 保存状态以便继续。 如果任务可能在补充信息或提高预算后继续执行,停止时要保存好进度。

常见误区

  • “设一个最大步数就够了。” 最大步数只能防止无限运行,无法区分“正常地花了很多步”和“一直在空转”,更无法防止过早结束。四类停止条件各自解决不同的问题。
  • “上限设得越宽松越好,免得任务做不完。” 宽松的上限意味着失控时损失更大。上限应该基于成功任务的实际分布来设定,并持续调整。
  • “撞到上限就是失败,直接报错。” 撞上限时,已经完成的部分往往很有价值。交付部分结果和清晰的未完成说明,远比一条报错信息有用。
  • “模型说做完了,再让它自己检查一遍就行。” 自我检查有帮助,但完成判定的最后一关,应尽可能交给程序或外部信号。

设计要点

  • 每类任务都有书面的、尽可能可由程序检查的完成标准。
  • 最大轮数、预算、耗时上限按任务类型分别设置,并基于数据确定。
  • 有针对重复调用、重复错误、连续无进展的检测。
  • 停止原因被显式记录,并进入监控看板。
  • 每种停止原因都有对应的收尾方式:交付什么、告诉用户什么、状态是否保存。
  • 模型宣布完成但程序检查不通过时,“打回重做”的次数也有上限。

练习

为「客服工单排查 Agent」设计停止条件(约 40 分钟):

  1. 写出单条工单排查的完成标准(至少 3 条,尽量可由程序检查)。
  2. 假设你收集到 50 次成功排查的数据(均为示例):轮数中位数为 6、P95 为 11;耗时中位数为 40 秒、P95 为 2 分钟。据此设定轮数和耗时上限,并说明理由。
  3. 列出 3 种可能的空转场景,以及对应的检测规则。
  4. 填写下表:
停止原因 交付给用户的内容 是否保存状态 是否通知人工
完成判定通过
撞上轮数上限
检测到重复错误
需要写操作权限

延伸阅读

下一步

到这里,Workflow 和 Agent 各自的机制都讲完了。模块最后一课「混合架构」讨论生产系统里最常见的形态:Workflow 里嵌 Agent,Agent 里调 Workflow。

试试:上下文、MCP、沙箱、Prompt 注入、外链