循环工程:为什么你的 AI Agent 循环会"刷分",以及怎么让它真正收敛 #1979
Replies: 3 comments
|
隐藏断言这条修得挺干净,尤其是把 error 尾巴那 2000 字符也一起过滤掉,多数人只堵正向那条路就收工了。不过 hint builder 本身还是模型写的,它从工具轨迹里重建提示,重建得太具体一样会把答案漏回去,这块可能还得给断言字符串加一道出口检查。另外验收从 worker 手里拿走之后,判分那一侧就成了单点,checker 和 worker 同一个基座模型的话,独立性其实没那么强,换一份上下文只解决了一半。 agent loop 和验证循环这块我用 Python 拆成了教程,GitHub 上 500+ 星,仓库有中文 README:https://github.com/hardness1020/awesome-agent-architecture/tree/main/sections/21-loop-engineering |
|
谢谢,这条读得很细,2000 字符那个尾巴确实是我们补的第二刀( 1. hint builder:机制不同,但你的结论成立hint builder 不是模型写的。 但你说的"会把答案漏回去"是真的,只是入口更机械:断言是被 harness 自己的 verify_command 重新打印出来的,而脱敏是逐字的。 verify_command 的输出是任意程序输出,下面这些日常变形都能让逐字匹配失效,而字符串对模型仍然完全可读:
举个具体的: 第二道 所以你说的"给断言字符串加一道出口检查"就是对的方向,而且不能靠继续加编码 —— 变形空间是开放的。已经开了 #2020,方向是归一化后按 span 脱敏(先剥 ANSI、折叠空白、去行前缀,再把偏移映射回原文),上面再叠一个相似度闸:任意窗口对断言超过阈值就整段抹掉。对输出尾巴这一段 fail closed,因为它风险最高、对重试质量的贡献最小。里面也写了你这条的机制归属,免得后来人去改错模块。 2. 独立性:分两层,一层不是模型,一层我们只做到厂商级AC 级验收门根本没有模型。 它跑 if spec.output_assertion and spec.output_assertion not in combined:( 模型进来的是 evaluate 阶段的语义层和 Stage 3 共识层,你这条打的就是这里。我们做了一个 诚实说它的边界在哪:
所以你那句"换一份上下文只解决了一半"我认。我们没打算用换上下文解决它,我们的选择是:能做成确定性的那一层就别放模型进去,放了模型的那一层就把独立性标出来而不是假装有。单厂商用户拿到的标签就是 真正没做的是基座级识别 —— 厂商映射是一张 3. 你那个仓库看了 |
|
补一句,上面说的第四列没做成,改了形状:hardness1020/awesome-agent-architecture#61。 原因是硬约束,不是偷懒。你 CONTRIBUTING 里那条 改成在
第四列你要是想要,我另开一个 PR 来压缩 cell,你说一声就行。 |
Uh oh!
There was an error while loading. Please reload this page.
给 AI coding agent 套一个循环,是这两年几乎所有人都会想到的一步:跑一次不行就再跑一次,加个评估器打分,分数不够就重试。
但真正把这套东西跑到生产里的人,大概都撞过同两堵墙:
这两个都是环境设计的问题,不是模型的问题,也不会随着模型变强而消失。模型越强,找到抄近路的速度反而越快。
下面用一个开源项目 Ouroboros 最近合并的 RFC(#1917,实现在 #1916)当具体例子,因为它把这两个问题的修法都写在了代码里,可以直接对着读。
一、第一堵墙:你把答案抄给了考生
大多数 agent 框架在给 worker 下达任务时,会把验收标准原样贴进 prompt 里,包括用来判分的那条命令和那个断言。动机很朴素:让 agent 知道自己会被怎么检查,它才好对齐。
Ouroboros 早期版本也是这么干的,在
_build_success_contract_block里把verify_command和Expected output: <assertion>一起渲染给了 worker。还有第二条泄露路径更隐蔽:重试的时候,失败原因里带着断言的repr(),顺着result.error的尾巴又流回了下一轮 prompt。问题在于,一旦 agent 看得见断言字符串,满足断言就永远比满足需求便宜。
RFC 里直接点了名:一个卡住的 worker 最省力的路径,就是去糊弄那个断言字符串,而不是去实现验收标准本身(项目里留了一份
seed_2be2907edc07的复盘)。这就是典型的 reward hacking。你以为你在测能力,其实你在测抄答案的能力。修法:无条件隐藏,而不是"可配置隐藏"
第一,两条泄露路径都得堵。 只堵正向那条没用:
_build_success_contract_block现在只渲染验收标准的描述和expected_artifacts,验证由 harness 独立执行,worker 看不到判分逻辑。repr();重试提示改由一个专门的、断言安全的 hint builder(orchestrator/retry_hints.py)生成,它会从每一个片段里把断言字符串过滤掉,包括那段 2000 字符的命令输出尾巴。那个 2000 字符的尾巴值得单独抄走。堵住了主路径、忘了日志尾巴,泄露口还是开着的。
第二,没有开关。 RFC 明确写了:不提供任何"披露级别"的配置项,这个提案被显式否决了。
做框架的人本能反应是"给个 flag 让用户自己选"。但信息屏障一旦可以关掉,它就会在某个赶进度的下午被关掉,而且没人会发现,因为分数看起来更好了。
那 agent 失败了怎么办?给提示,不给答案
隐藏答案之后,worker 卡住了什么信息都没有,确实会原地打转。所以 RFC 用提示循环补上这一环:下一轮的指令不是从断言里抄的,而是从这次会话实际做了什么里重建的,包括工具调用轨迹、证据清单(只读复用
deliver_gate.load_ac_evidence_manifest)、验证器的结论。X"X这就是信息不对称在起作用,和人类考试的安排一样:考官知道答案,考生只知道自己错在哪。
二、第二堵墙:失败是死路
RFC 里的原话是:每一个零件都存在,包括 verify gate、run→eval 链、
evolve_step、Ralph driver、focus.select_evolution_focus,但没有任何东西把它们连起来。结果就是:
于是循环名义上存在,实际上是三段互不相连的直线。失败没有被消化,只是被上报了。
修法:run → eval → evolve 接成一条链
改动本身不复杂,但有几个约束很关键:
1. 失败的 run 也要进评估。
_run_succeeded的门槛放宽,只要产生了 session,就把它接进正式评估。同时保持 fail-open:入队失败绝不反过来改写 run 的结论。2. 被拒绝的评估触发有预算的演化循环。 这里没有重新实现一个收敛循环,而是委派给已有的演化机制:评估的终态路径在
final_approved is False时入队ouroboros_start_ralph。真正新写的那块叫 Gen1 bridge:把这次 run 的 seed 和评估产出的多 AC 清单投影成谱系事件,让
evolve_step把这次普通的 run 当作第 1 代回放,然后从第 2 代开始带着焦点继续。3. 只有失败的验收标准进入下一代,通过的冻结。 这条的性价比最高。
为了让
focus.select_evolution_focus能正确冻结已通过的 AC,新写的 checklist→ACResult转换器(mcp/tools/evaluate_ralph_chain.py)必须满足相当严格的要求:完整的索引覆盖、逐字一致的ac_content、semantic_ac_key同一性。严到这个程度是有原因的:不冻结通过项的循环,会反复把已经做对的东西重做一遍,不但烧钱,还会把已经正确的实现改坏,最后表现为分数在两代之间来回震荡。
三、循环必须能停下来
套循环最危险的地方在于,它不会自己停。
Ouroboros 这里没有发明新的停止条件,直接复用了 Ralph 已有的那套:QA 通过、收敛、震荡检测、等级回退、墙钟超时。默认
execution.auto_evolve_max_generations = 3,取值范围钳制在 1..10。只有在预算耗尽之后才会真正报 BLOCKED。其中震荡检测和等级回退是专门用来防假收敛的:分数在 A→B→A→B 之间来回跳,或者新一代比上一代更差,都要能被识别出来并叫停,而不是继续烧 token。
还有一个小修复。RFC 里提到
evolution/loop.py有一个裸except,三条路径都会静默地把evaluation_summary变成None。修法不是加个日志,而是记录一份带失败原因的 rejected summary,保持 fail-closed 的焦点语义,同时让这次失败变成持久化的事实。单次执行里吞掉一个异常,最多错一次;循环里吞掉一个异常,错误会被放大 N 代,而你在日志里什么都看不到。
四、便宜的关卡放在贵的前面
同样的思路贯穿了设计的其他部分。
Ouroboros 的评估是分层的:Mechanical(免费、确定性检查)→ Semantic → Multi-Model Consensus。能在第一层否掉的,绝不送去烧 LLM 判分。
访谈阶段用的是一个数,而不是分层。模糊度被量化为加权清晰度的反值:
README 里那个 greenfield 的例子:
演化那头则要等本体相似度 ≥ 0.95 才算收敛。两道数学关卡,README 里把背后的想法写成一句话:没想清楚之前不要写,没稳定之前不要停。
五、可以拿去检查自己项目的三条
RFC 全文在 Q00/ouroboros#1917,实现在 #1916,设计文档在仓库的
docs/hidden-checklist-convergence/下面(requirements / architecture / implementation 三份)。项目地址:github.com/Q00/ouroboros,中文 README 是 README.zh-CN.md。有在生产里跑多代 agent 循环的朋友,你们是怎么处理假收敛的?这部分我见到的好答案最少。
All reactions