2336 字
约 7 分钟
14
S21-A22 · 终止条件:Agent 什么时候该停

一个研究型 Agent 被要求"调研某个技术方案",它跑了 47 轮工具调用、花了 12 分钟,最后输出了一份"资料清单"而不是"结论"——因为它没有"停"的条件,只会在"预算耗尽"时被迫停下。

Agent 的"什么时候停"通常只被实现成"最大轮数"或"token 上限",而那个是"强制熔断",不是"完成判断"。这两种失败"看起来相反",成因却相同:没有"够不够"的判断——于是"停"只由"外部预算"决定,而不是"任务状态"。
缺了后者,Agent 要么"过早停在半成品",要么"跑到预算用光"。


1. 三种终止条件

类型 例子
强制上限(硬熔断) 最大轮数、最大 token、超时
目标达成(软判断) "问题已回答"、"信息足够"
无效进展(自适应) 连续 N 轮没有新信息

第二项是"质量"的关键:它让 Agent"在还够用的时候停",于是"剩下的预算"不必花完。

第三项是"效率"的关键:它让 Agent"在明显没戏的时候停"第三项的"判据"要"看增量"而不是"看总数":"这一轮新增了什么"比"现在有多少"更能说明"还要不要继续"——因为"总数在涨"可能只是"重复收集"。
——而"没戏"往往表现为"重复搜到同样的东西"。

2. "目标达成"为什么难判断

难点 说明
目标本身模糊 "调研清楚"没有判据
自评不可靠 模型倾向"说完成了"
判据需要外部信号 要"检索到 X"而不是"感觉够了"

第二项的"机制"是"它被训练成"给出答案",而不是"承认不足""——于是"自评完成"的默认倾向是"乐观",而乐观在"研究型任务"上最贵。
第二项最危险:如果"停不停"由模型自己说"我完成了",它会"过早宣布完成"——因为"完成"在训练里被奖励。

所以"目标达成"应当写成"可检查的条件",而不是"让模型自评"。

// 三种"可检查"的终止条件(不依赖自评)
1 覆盖类:至少命中 3 个不同来源,且覆盖"定义/优缺点/替代方案"三类
2 字段类:输出必须填满 5 个字段(缺一个就继续)
3 校验类:回答必须能通过"引用回查"(每条结论有来源)
// 判据:条件能否用"程序"判,而不是"感觉"

3. "无效进展"的三种信号

信号 判据
结果重复 新结果的 URL/ID 与已有重叠 > 70%
查询重复 连续两轮的查询"语义相同"
无新字段 输出草稿的字段数没有增加

第二项需要"语义判重"(否则"换个词"就骗过判据)第二项的"近似实现"是"归一化后比对"(去停用词、去标点、词序无关)——它能挡住"换个语序"的重复,而挡不住"同义改写",通常够用。
,而它可以用"同一个检索词归一化后相同"来近似。

// 三档"无效进展"的处理(避免"一刀切")
第 1 次无进展:改写查询(换角度)
连续 2 次:减少并行、聚焦缺口字段
连续 3 次:停止,输出"当前已知 + 缺口清单"
// 关键:停的时候要"交付现状",而不是"报错退出"

把"完成"写成"状态 + 判据",于是"停"变成可测的:

// 一个可执行的"是否完成"(状态 + 判据 + 缺口)
def is_done(state) -> tuple[bool, list[str]]:
    gaps = []
    if len({s.source_id for s in state.evidence}) < 3: gaps.append("来源不足 3 个")
    for f in ("definition", "pros_cons", "alternatives"):
        if not state.fields.get(f): gaps.append(f"缺 {f}")
    if any(not c.has_citation for c in state.claims): gaps.append("结论缺引用")
    return (not gaps), gaps
// 用法:每轮结束调用一次;返回缺口 → 继续(按缺口改写查询)

4. "停得早"与"停得晚"的代价

情况 代价
停得早 输出不全(用户要重跑)
停得晚 成本与延迟(用户等 12 分钟)
停得乱(报错退出) 用户拿不到任何东西

第三项的"修法"很轻:把"当前已知 + 缺口清单"作为"停止时的标准输出"——于是"停下"变成"交付一个更小的结果",而不是"失败"。
第三项最"伤":"跑到一半报错"让用户连"半成品"都拿不到——而"带着缺口清单停下"至少可用。

5. 三种"终止判断"的实现位置

位置 效果
提示里写"够了就停" 弱(依赖自评)
控制器里判(代码) 强(可检查条件)
外部评估(另一个模型/规则) 中(成本高)

第二项是"工程上的正解":把"是否完成"写成代码里的断言第二项的"形态"是"一个 is_done(state) 函数":输入是"当前收集到的结构化状态",输出是"布尔 + 缺口列表"——于是"停"与"继续"都有依据。
——于是"完成"是"可验证的",不是"模型说的"。

第三项的"成本"要算:每次判断多一次模型调用——于是在"轮数多"的任务上,"每轮都判"可能比"多跑两轮"更贵,通常"每 2–3 轮判一次"。
第三项在"条件难写"时用:用一个小模型判"是否已覆盖",而它只做"是/否"判断——比"主模型自评"更可靠。

"重复检测"要能"按内容"而不是"按查询串",否则会被"改写"绕过:

// 三种重复信号(从粗到细)
1 查询串归一化后相同(去停用词、排序)→ 最便宜
2 结果 ID 集合的 Jaccard > 0.7          → 中等(说明"搜到同一批")
3 新增"结论条数"为 0                    → 最可靠(说明"没有新知识")
// 组合:1 命中 → 改写查询;2 命中 → 收窄范围;3 命中断 → 停

6. 排错:五种"终止条件问题"

现象 成因 处理方向
跑满预算才停 只有硬熔断 加"可检查条件"
早停但输出不全 依赖自评 改成字段/覆盖检查
重复搜同一件事 无"进展"判断 加重复检测
停止时没输出 直接报错退出 交付现状 + 缺口
有时停有时不停 条件不一致 固定判据

第五项是"调试"难点:同一任务两次运行结果不同第五项的"修法"是"把判据固定成"可打印的中间状态"":每次判断都记一行"字段数/来源数/重复率"——于是"为什么这次停了"可从日志回答。
——因为"停不停"依赖模型的随机自评。

"停止"的输出模板值得固定,因为它决定"用户能不能接着用":

// 停止时的标准输出(四段)
1 结论:已确认的 3–5 条(带引用)
2 覆盖:已覆盖的定义/优缺点/替代方案
3 缺口:还没查清的点(各一句)
4 建议:下一步可以查什么(可执行)
// 判据:用户能否"只读第 3、4 段"就决定"要不要再跑"

7. 边界:什么情况一个上限就够

场景 为什么 做法
任务很窄(一次查询) 无"要跑多久"问题 只设上限
输出不要求完整(找灵感) 半成品可接受 只设上限
人工全程盯着 人会喊停 只设上限
成本极低(本地小模型) 跑久点无所谓 只设上限

最后一条与"体验"有关的边界:"停"要"可解释":输出里带一句"我覆盖了 A/B/C,还缺 D",用户才知道"要不要再跑一次"——而"无声地停"会让人以为"这就是全部"。
判断口径一句话:"停早了,用户会不会重跑"。会(研究、报告、多步任务),就必须有"可检查的完成条件";不会(单次查询、找灵感),一个上限足够。


8. 自测

  1. 三种终止条件里,哪一种是"质量"的关键、哪一种是"效率"的关键?
  2. 为什么"让模型自评是否完成"不可靠?"可检查条件"该怎么写?
  3. "无效进展"的三种信号里,哪一种需要语义判重?为什么"换个词"能骗过简单判据?
  4. "停得乱(报错退出)"为什么比"停得早"更伤?
  5. 为什么"把完成条件写进代码"比"写进提示"更强?
  6. 判断"是否只需一个上限"的那个问题是什么?
S21-A22 · 终止条件:Agent 什么时候该停
https://www.clxhxhhr.top/posts/6622/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。