ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Rollback课程学习机制:让多轮Agent在错误级联时学会主动回滚

Rollback课程学习机制:让多轮Agent在错误级联时学会主动回滚

我们团队半年前在一次版本演示上翻过车。一个看似普通的多轮交互 Agentic 任务——用户连续提了几个需求,中间夹了一次工具调用,Agent 在第二个请求上理解错了意图,然后带着这个错误一路执行到底,输出了一份完全不可用的结果。更郁闷的是,模型全程显得特别“自信”,每一步都逻辑自洽,就是地基从第二步开始就歪了。这类场景做 Agent 的朋友应该都不陌生。当时我们把问题归到工程层面,试了各种兜底规则,效果都不理想,后来转向训练层面,折腾出了一个叫“Rollback 课程学习机制”的方案——简单说,就是在训练阶段显式教会模型在中间步骤出错时主动回退、重新规划。这篇文章就把这套机制从问题拆解、方案设计、数据构造到训练排查完整梳理一遍,给正在做多轮 Agent 训练与落地的朋友做个参考。

1. 问题拆解:多轮 Agent 为何总是“一步错步步错”

1.1 一个典型的“错误级联”现场

先还原一下当时我们内部构建的一个演示场景。任务本身不复杂:某电商客服系统接到用户提问,用户先问“我的订单到哪了”,Agent 调用物流查询接口拿到正常结果;随后用户追问“那这个订单能改地址吗”,Agent 需要先校验订单状态、再判断是否允许修改,最后调用修改接口。问题就出在第二步:模型把“改地址”理解成了“退换货”,于是后续所有工具调用、话术生成全都围绕退换货展开。用户每追问一次,错误就被放大一次,到最后模型甚至“编”出了一套根本不存在的退换货流程。

这在多轮 Agent 场景里有个专门的说法:错误级联(error cascade)。它不是单个步骤的错误,而是错误沿着对话轮次和工具调用链不断累积、放大。我们在复盘时统计了内部测试集里的失败轨迹,发现有将近四成的失败案例不是“模型不会做”,而是“中间某一步做错了,但模型坚持把后面的路走完”。

1.2 为什么模型不会主动“回头”

从训练目标层面看,语言模型被训练成“在给定上文的前提下续写最可能的下文”。一旦上文里包含了错误的中间状态,模型的下文生成就会沿着错误上下文继续前进。它没有机制去质疑“我前面是不是错了”,因为没有哪一种训练目标会显式地奖励“回头”这个行为。

从数据层面看,常规的指令微调和偏好对齐数据里,几乎不会包含“先做错、再纠正、继续完成任务”的正样本。我们翻了内部积累的 SFT 数据,干净轨迹占了绝大多数,少量失败轨迹在训练时还会被当作负例过滤掉。换句话说,模型在训练阶段根本没有见过“犯错之后还能补救”的示范,自然也就学不会这个动作。

从评测层面看,常见的端到端成功率评测只关心最终结果对不对,中间步骤的错误被平均值稀释了。模型拿到的反馈是“最终成功”或“最终失败”,它无法从这种粗粒度信号里定位到具体哪一步需要修正。三个因素叠加在一起,就成了我们熟悉的画面:Agent 每一步都自信满满,最后交出一份离谱的答案。

1.3 常规补救方案为什么不够

最初我们想走工程路线,写一堆兜底规则:检测工具调用失败、检测参数异常、检测用户情绪变化等等。试了一个月,规则越写越多,覆盖面却始终跟不上。多轮交互的语义错误(比如理解错意图、选错分支)根本没法靠规则穷举,硬写只会让系统变成一个不断打补丁的“屎山”。

后来也试过让模型做“自我反思”,就是在生成之后追加一轮批判性提示,让模型自己检查有没有问题。结果在部分单轮问答场景有效,但放到多轮工具调用链上就不太靠谱了。模型往往嘴上承认“我错了”,实际行为还是沿着原来的错误路径走,属于典型的“语言上认错、行动上不改”。

还有人可能会想到推理时搜索,比如在多个候选分支里做 beam search 或者树搜索。这种方法效果确实好,但代价是推理计算量成倍上涨,在线场景根本扛不住。我们当时的判断是:既然工程规则和推理时方案都有天花板,那就回到训练阶段,让“回滚”本身成为一个模型内建的能力。

2. 方案设计:Rollback 课程学习机制是怎么工作的

2.1 核心思路:把“回退”变成可学习的动作

Rollback 课程学习机制首先要回答一个问题:到底怎么让模型学会“回头”?

我们的做法是在训练阶段扩展 Agent 的动作空间。原本模型在每一轮可以输出的动作包括:调用某个工具、生成回复文本、结束任务。在这套机制里,我们额外增加了一个显式动作:回滚(rollback)。当模型生成这个动作时,它需要同时输出一个“回退目标”,指明要回到之前的哪一个状态点——可以是某一次对话轮次,也可以是某一次工具调用之前,然后从那个状态重新开始规划。

为什么要用“显式动作”而不是靠自然语言让模型自我纠正?这里有个关键区别:自然语言纠正只是改变了表达,不改变模型内部的信念状态;而显式回滚动作会真正触发状态重置,把对话历史与工具执行记录恢复到指定位置。打个比方,前者是司机嘴里念叨“我开错路了”,后者是司机真的打方向盘掉头。

为了让“回滚”能被训练出来,我们借鉴了课程学习(Curriculum Learning)的思路。课程学习的核心思想很简单:给模型安排一条由易到难的学习路径。我们把这个思想用在了回滚训练上:先教模型在最简单场景下学会回滚,再逐步增加回滚的复杂度,最后让模型在真实的多轮交互环境中自主判断何时回滚、回滚到哪里。

2.2 三级课程的内容安排

我们把课程拆成了三个级别,每一级对应一种回滚难度。

级别训练内容核心目标典型示例
Level 1单步回滚学会“错一步退一步”第 2 步调错工具,在第 3 步检测到错误,回滚到第 2 步之前,重新调用正确工具
Level 2多步连续回滚学会“连续错两步都退回去”第 2 步理解错意图,第 3 步调用了错误接口,第 4 步回滚到第 1 步重新规划
Level 3分支重选学会在多个可能路径中识别错误分支并切换第 2 步选了 A 分支,执行到第 4 步发现 A 分支走不通,回滚并切换 B 分支完成目标

Level 1 的关键是“错误位置明确、回滚距离固定”。数据里会显式标注错误的步骤,模型只需要学会“检测到错误信号就回退一步”。这个级别的任务是建立回滚动作的基本语义。

Level 2 的关键是“连续错误”。模型不能只回退一步就完事,它需要判断错误的起点在更早的位置,一口气退到正确状态再重来。这个级别考验的是模型对整条轨迹的“因果归因”能力。

Level 3 的关键是“分支切换”。此时错误不再是简单的“某一步做错”,而是整条分支方向就错了。模型需要对比当前分支和备选分支的结果,判断是否要放弃当前分支,并且知道切到哪一条分支上去。

2.3 为什么选课程学习而不是一次性混合训练

一开始也有人提过:为什么不把三个级别的数据混在一起,让模型一起学?我们后来意识到,混合训练最大的问题是会把“回滚”变成一把乱挥的锤子。

如果训练集里既有 Level 1 的简单回滚,又有 Level 3 的复杂分支重选,模型很容易把“频繁回滚”当成一种万能策略。因为回滚动作在训练早期就很容易拿到正奖励——它总是比“继续沿错误路径走”要好。模型会很快发现这个捷径,然后不管三七二十一,见到任何不确定信号就先回滚再说。这不是我们要的行为。

课程学习的好处在于,它给了模型一条清晰的能力发展路径:先在低难度课程里建立“回滚动作是有效手段”的认知,再在中等难度里学会“回滚需要准确选择目标位置”,最后在高难度里掌握“什么时候该回滚、什么时候不该回滚”的判断力。这个顺序很像人学开车:先学直线,再学倒车,最后才学复杂路况下的灵活变道。跳级学习也许能快,但稳定性差,后面需要花更多时间去修正模型学到的坏习惯。

3. 实操落地:训练数据构造与奖励设计的关键细节

3.1 轨迹数据的拆分与重构

数据是我们踩坑最多的地方。光有想法没有数据,一切白搭。我们用的数据来源主要有两个:一是从真实交互日志里筛出的完整轨迹,二是基于模拟环境人工合成的轨迹。两类数据都要经过同一个处理流程:拆分、注入干扰、生成回滚标签。

具体做法是:先从日志里抽出成功的完整轨迹,然后随机选择轨迹中的某个位置,注入不同类型的错误,再让标注人员(或者另一个更强的模型)生成一段修复轨迹。修复轨迹的特征是:先走到某个位置时产生回滚动作,回滚到错误起点之前,再重新执行剩余步骤完成目标。

在数据格式上,我们定义了一套结构化的回滚标注,方便训练时直接使用。

{ "task_id": "099x", "trace": [ {"turn": 1, "action": "user_query", "content": "查询订单状态"}, {"turn": 2, "action": "call_tool", "content": "get_order_status"}, {"turn": 3, "action": "call_tool", "content": "update_address_wrong"}, {"turn": 4, "action": "rollback", "target": 2, "reason": "wrong_intent"} ], "rollback_type": "wrong_intent", "error_step": 3, "rollback_step": 4, "rollback_target": 2, "restored_success": true }

这个字段设计是我们反复调过的。一开始只记录了“有没有回滚”,训练出来的模型知道要回滚,但不清楚要退到哪一步,经常退不到点上。后来加了rollback_target字段,模型的任务变得更具体:不仅要输出回滚动作,还要输出回退位置的索引。效果立刻有了明显提升。

数据构造上还有几个注意点。错误类型要覆盖常见的几类:意图误解(把 A 需求理解成 B 需求)、工具调用失败(接口报错、超时)、参数错误(传错订单号、传错日期范围)、上下文遗忘(忽略了前几轮已经确认过的信息)。只覆盖单一错误类型会导致模型的回滚能力迁移性很差,换个新场景就不工作了。

3.2 奖励塑造:怎么让模型“愿意”回滚

训练这套机制我们用了策略梯度类的强化学习框架(内部实验用的是 PPO),奖励设计是整个方案的心脏。奖励如果给不对,模型学到的东西会完全跑偏。

我们的奖励函数由三部分构成:

r_total = 恒定向导奖励 + 回滚行为奖励 + 任务终止奖励

基础导向奖励(r_step):每完成一个有效步骤给 0.1 分。这个奖励让模型保持“继续推进任务”的倾向,而不是呆在原地不动。

回滚行为奖励(r_rollback):当模型正确识别出当前轨迹需要回滚时,给 1.0 分;当回滚到的目标位置恰好是错误起点之前时,额外给 0.5 分。这个部分是最关键的,它直接定义了我们“想要”的行为。注意这里有两个维度:一是“要不要回滚”的判断,二是“回滚到哪”的准确性。

任务终止奖励(r_terminal):最终任务成功给 5.0 分,失败给 -2.0 分。任务成功奖励要足够大,否则模型会觉得“反正回滚也能拿分,最终目标完不完成无所谓”。

我拿一个具体例子算一下奖励。假设一条轨迹一共 5 步:第 1 步用户提问,第 2 步模型意图理解错误,第 3 步走了一步错误路径,第 4 步模型检测到问题并回滚到第 1 步,第 5 步重新执行正确路径并最终完成任务。

奖励计算过程:

  • 基础导向奖励:有效步骤是 1、3、5 这 3 步,但第 3 步是错误步骤,我们只给第 1 步和第 5 步计分,共 0.2;如果设计得更严格,错误步骤还可以倒扣。
  • 回滚行为奖励:第 4 步正确触发回滚给 1.0,回滚目标位置(第 1 步)正确,额外给 0.5。
  • 任务终止奖励:最终成功给 5.0。
  • 合计:0.2 + 1.0 + 0.5 + 5.0 = 6.7 分。

这个数字不是随便定的。我们后来为了压“过度回滚”,专门把回滚行为奖励从 1.0 降到了 0.3,再把“没有错误时不要回滚”做成隐式惩罚,这部分会在后面的踩坑章节详说。

3.3 回滚粒度的选择:退到“哪一轮”比“要不要退”更难

回滚机制里最有意思的问题,是回滚的粒度。所谓“回滚”,到底是回到哪一层?是回到之前的对话轮次,还是回到某一次工具调用之前,还是干脆把整个对话历史重置?

我们实验了三种粒度。最粗糙的是“整段回滚”,即回到任务起点重新开始。这种方案实现简单,但代价是丢掉所有已完成的有效信息。比如说用户已经确认了收货地址,模型因为一次工具调用失败就整段重来,用户肯定要疯。

中间粒度是“按轮次回滚”,回到某一轮对话之前的全部状态。这种粒度比较好训练,因为对话轮次是天然的分隔边界。但它处理不了“同一轮内,意图理解就错了”的情况。

我们最终采用的是“按动作索引回滚”:把对话历史和工具调用记录统一编号,模型输出回滚时指定action_index,状态管理模块把数据恢复到该动作执行之前。这个粒度最灵活,也最难学——模型需要知道“错误发生在哪一步”,这本质上是在训练一种定位能力。

实际训练中,头几个 epoch 模型经常把回滚目标定位到错误步骤本身而不是错误之前的位置。我们加了一个小技巧:在标注数据里,把rollback_target统一转换成“错误动作的前一个动作索引”,让目标注释口径一致。如果错误发生在 action 3,回滚目标就是 action 2 完成后的状态。这个细节看似很小,但统一口径之后,模型的学习难度明显下降,定位准确率提升很快。

4. 实验配置:训练流程、超参数与课程调度策略

4.1 三阶段训练流程

整套训练流程我们分成了三个阶段。

第一阶段是监督微调(SFT)。在拼接了回滚轨迹标注的数据集上,让模型先学会基本的行为模式:看到错误痕迹就输出回滚动作、指定回退目标、重新规划。这个阶段不追求模型有多强的泛化能力,只求它“知行合一”,能够在分布内的样本里稳定复现回滚动作。

第二阶段是强化学习微调(RL)。我们用策略梯度方法在回滚数据上继续训练,重点是优化奖励信号。这个阶段模型开始学会权衡:什么情况下回滚是值得的,什么情况下继续推进更有利。

第三阶段是课程调度。把三级课程的数据按顺序送进训练管道,每一级课程内部单独设置一个评估集,只有当当前级别的成功率稳定超过某个阈值时,才把下一级别的数据加进来。

整个流程看起来不复杂,但每一步之间都有紧耦合。比如 SFT 阶段如果做得不够扎实,模型没有学会输出结构化的回滚动作,RL 阶段就无从谈起。我们第一次训练时 SFT 只跑了 2 个 epoch 就急着进入 RL,结果模型在动作格式上频频出错,RL 阶段的大部分时间都在纠正格式问题,效率惨不忍睹。

4.2 模型选型与关键超参数

实验用的基座模型是一个 7B 参数规模的开源基座模型。选它的原因很现实:我们当时的 GPU 资源有限,7B 在单机多卡环境下可以做较快的迭代验证。一开始也有人建议直接用更大的模型,但我们判断这套机制的核心价值在训练方法而非模型规模,用小模型跑通方法论、验证数据构造和奖励设计的合理性,再考虑放大,这样迭代成本低得多。

超参数取值说明
SFT 学习率2e-5偏保守,避免破坏基座模型原有能力
SFT epoch3在小型回滚数据集上不宜过量训练
RL 学习率1e-6强化学习阶段更小,防止灾难性遗忘
PPO KL 系数0.05控制策略偏移幅度
rollout 数量4096 条/训练步保证优势函数估计的方差可控
GAE λ0.95平衡偏差与方差
折扣因子 γ0.99多轮任务偏长,需要较远的视野
训练 batch size128按显存上限调整

几个参数的取值逻辑说一下。RL 学习率定在 1e-6 是因为我们担心策略变化太大会把 SFT 阶段学到的回滚格式丢掉。KL 系数 0.05 是一个安全范围内的值:太小则策略偏离参考策略过远容易不稳定,太大会限制策略探索空间。rollout 数量 4096 是我们试了 1024 和 2048 之后调到 4096 的,数量太小时优势估计的噪声太大,训练曲线一直在抖动。

4.3 课程难度切换的调度策略

课程学习的关键在“什么时候把更难的数据加进来”。我们一开始用的是固定步数切换:Level 1 训练 500 步,不管效果如何直接切到 Level 2。结果不出意外地翻车了——Level 1 的成功率只有 60% 出头就升级,Level 2 数据一进来,模型直接招架不住,整体指标反倒比单一级别还要差。

后来改成了自适应调度:每个级别维护一个 EMA 评估窗口,只有当当前级别的成功率超过 80%,并且连续三个评估批次没有出现明显下降,才把下一级别的数据混合进来。而且不是一次性替换,是逐步混合——新级别的数据占比每 50 步提升 10%,给模型一个缓冲期。

这个调度逻辑用伪代码写出来大概是这样:

if ema_success_rate(level) > 0.8 and not declining(level, 3): if level < 3: level += 1 mixing_ratio = 0.1 # 新级别数据从 10% 起步 else: wait_or_recover() if level > 1: mixing_ratio = min(0.5, mixing_ratio + 0.1) # 逐步提升到 50% 封顶

自适应调度的好处是让模型在每个级别上都“站稳”了再往前走。代价是训练时间变长,有时候一个级别要卡很久。我们那会儿平均每个级别要卡一两周才能达到切换条件,中间还有好几次成功率不升反降,不得不回滚到之前的 checkpoint 重新爬坡。

5. 踩坑记录:训练过程中遇到的典型问题与排查方法

5.1 问题一:模型学会了“疯狂回滚”

第一个大坑是模型学会了回滚技能之后,开始过度使用它。具体表现是:在完全正常的轨迹里,模型也频繁触发回滚。我们统计过,有一段时间训练出来的策略在简单任务上的回滚频率超过了 30%——也就是说,每三条轨迹里就有一条含有多余的回滚动作。

诊断下来,根因在奖励设计。回滚行为奖励给得太高,模型发现“回滚一下就能拿分”,比“继续努力完成任务再拿分”来得轻松得多。这就好比一个学生学会了举手问问题,结果每道题不管会不会都先举手再说。

解决办法分两步:一是把回滚行为奖励从 1.0 降到 0.3,降低它对模型的诱惑力;二是在奖励函数里加了一条隐式惩罚——如果模型在无错误轨迹中触发回滚,在终止奖励里额外扣 0.5 分。两步做完,回滚频率从 30% 降到了 8% 以下,同时任务成功率重新回到上升轨道。

5.2 问题二:低难度课程过拟合

第二个坑是 Level 1 上训练得很好,一上 Level 2 就拉胯。前期训练时,Level 1 数据集上的准确率很快就刷到了 95% 以上,我们当时还挺高兴,觉得模型已经学会了单步回滚。结果 Level 2 数据一混合进来,Level 1 的准确率立刻掉到 85%,Level 2 的成功率更是只有 30% 出头,整个训练曲线呈“过山车”形状。

问题的本质还是数据分布太单一。Level 1 数据里错误模式相对简单固定,模型学到的是“看到类似的错误信号就回滚”的浅层匹配,而不是“真正理解错误在哪儿”的深层能力。一旦错误模式变了样,模型就完全不会玩。

我们的解决方法是给 Level 1 数据注入干扰样本。具体做法是从 Level 2 和 Level 3 的数据里挑一些相对“简单”的样本,打折混进 Level 1 的训练集,比例大概控制在二成左右。同时在 Level 1 的评估环节,除了原有的单步回滚场景,还额外加了少量需要两步回滚的“彩蛋”样本,用来监控模型有没有形成对回滚机制的真正理解。调整之后,模型在 Level 2 上的表现明显更稳了。

5.3 问题三:训练过程不稳定,loss 震荡、reward 尖峰

第三个坑是训练稳定性的问题,这个坑一度卡了我们将近两周。现象是:loss 曲线在正常下降过程中突然出现周期性尖峰,reward 也时不时冲到一个异常高的值然后迅速回落,训练直接失去收敛趋势。

我们按顺序排查了好几个因素。先怀疑是 reward scale 太大,把终止奖励从 5.0 调到 2.0,尖峰频率有所下降,但没有完全解决。再怀疑 KL 系数不合适,试着从 0.05 调到 0.02,结果策略偏离参考模型太远,又冒出新的不稳定。最后发现真正的问题出在 rollout 数量上——之前为了加速训练,把 rollout 数量从 4096 降到了 2048,导致优势估计的方差变大,训练波动随之加剧。把 rollout 数量恢复回去之后,尖峰基本消失,训练曲线重新变得平滑。

这个排查过程给我的教训是:当训练出问题时,先别急着调这个调那个,按“奖励尺度 → KL 约束 → 数据采样量”的顺序一个个试,每次只动一个变量,才有可能定位到真正的病根。我们一开始同时动了三个参数,结果根本分不清是哪个起的作用,白白浪费了好几天时间。

6. 效果观察:三类任务上的提升与一个意外发现

6.1 三类内部评测任务的结果

机制跑通之后,我们在内部评测集上对三个任务场景做了评估。评测集是从真实交互日志里抽出来、经过人工标注的轨迹,每个任务各抽了 500 条,覆盖了多种错误类型。

任务场景基线成功率加入回滚机制后提升幅度
多轮客服系统56.8%74.0%+17.2 个百分点
数据分析助手61.2%74.0%+12.8 个百分点
网页导航 Agent58.6%74.0%+15.4 个百分点

三个任务场景都有提升,但提升的方式不太一样。多轮客服场景的提升主要来自“错误意图的及时发现”——模型在用户第二次追问时就能察觉理解偏了,主动回滚到之前的意图确认轮次。数据分析助手的提升则更多来自“工具调用链上的回滚修复”——查询参数传错了,模型会回滚到参数设置那一步重新构造查询。网页导航 Agent 的提升来自“分支路径的重新选择”——发现当前页面路径越走越偏时,回退到较早的导航分支点。

表格里的数据是在某个内部模拟项目 X 的评测集上测出来的,绝对数值不一定有普适性,但趋势上能说明套机制的可行性:它不是在某个特定场景里偶然奏效,而是跨场景稳定带来了正向收益。

6.2 一个意外发现:模型学会了“预测性回滚”

评测过程中我们发现了一个有意思的现象:部分成功轨迹里,模型的回滚动作并不是发生在“错误已经被工具执行结果证实”之后,而是发生在“错误刚刚露出苗头、还没有造成实际影响”的时候。

举个例子,在数据分析助手场景里,用户要求统计“上个月”的销售数据,模型在构造查询时把日期范围设成了上上个月。按常理,这个错误要到工具返回结果后才能被发现。但训练后的模型在构造查询参数的那一刻就触发了回滚,重新读取了一遍用户原话中的日期表述,然后修正了参数再继续执行——整个过程看起来就像是模型产生了“预测性回滚”。

这个现象背后的机制我们还没有完全搞清楚,一个比较合理的推测是:课程学习的多级难度训练让模型形成了对“哪一步容易出错”的敏感性。在 Level 2 和 Level 3 的训练里,模型见过太多“错误前缀 + 回滚修复”的样例,学会了在错误发生更早的阶段就识别出风险信号。

这个发现让我们的思路打开了新的方向:回滚机制不只能作为“错误修复工具”,还可能作为“不确定性管理手段”——当模型对某个决策的置信度偏低时,主动回滚到更早的确定性状态重新规划,而不是等错误坐实之后再补救。现在团队正在沿着这个方向做一些初步实验,目前看结果还挺有潜力的。

如果让我总结这套机制最核心的价值,我不会说它只是让 Agent 学会了“认错”。更准确的说法是,它把人的直觉——走错路要及时折返——转化成了可训练、可度量的技能。这条路我们走了好几个月,踩的坑比预想多,但收获也超出预期。如果你们也在做多轮 Agent 产品,我建议别一开始就在太大的模型上动手,先用一个小模型把回滚的数据构造和奖励逻辑跑通,再考虑规模放大。这套机制目前能处理的还主要是单智能体场景,多智能体协作下的回滚问题涉及多个 Agent 之间的状态同步和信念修正,会是一个更复杂的挑战。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进