Skill1:用一个奖励信号,把 Agent 的"选、用、攒"三件事一起练出来

核心摘要

做带技能库的 LLM Agent 这件事,一直有个让人头疼的工程现实——选技能、用技能、攒技能这三件事永远是分着练的。有人专心优化"用",选用现成的 retriever 选技能;有人在"攒"上玩花样,但选技能的部分干脆冻住。结果就是某一块练得再好,整条链路也跟着卡在最弱的那一环。这篇来自中科大、美团等机构的 Skill1 提出了一个挺利落的方案:让同一个策略同时负责"选、用、攒",并且所有的学习信号都来自同一个任务奖励 \(r(\tau)\)。它的关键技巧是把这个 0/1 信号拆成"低频趋势"和"高频偏离"——前者给选技能用,后者给攒技能用,中间执行的部分直接拿原始奖励。在 ALFWorld 上跑到 97.5% 平均成功率,比此前最强的 RetroAgent 高 2.6 个点,WebShop 上也是全榜第一。这是一个工程味很重、但思想干净的工作,值得做 Agent 训练的同行细看。


论文信息

  • 标题:Skill1: Unified Evolution of Skill-Augmented Agents via Reinforcement Learning
  • 作者:Yaorui Shi, Yuxin Chen(共同一作), Zhengxi Lu, Yuchun Miao, Shugui Liu, Qi Gu, Xunliang Cai, Xiang Wang, An Zhang(通讯)
  • 机构:中国科学技术大学、美团、新加坡国立大学、浙江大学、武汉大学
  • 链接https://arxiv.org/abs/2605.06130
  • 代码https://github.com/AlphaLab-USTC/Skill1

一、先说说让我皱眉的那个老问题

去年下半年开始,圈里关于"给 LLM Agent 配一个技能库"的工作突然多了起来。Voyager 那一脉的思路其实早就有了——Agent 在做任务的过程中,把成功经验沉淀成一段段可复用的策略,下次碰到类似任务先翻翻"经验本",而不是每次都从零开始硬磕。这件事的吸引力很直白:参数化的记忆(也就是直接把策略吸进权重里)有它的极限,而显式的技能库提供了一种"可读、可查、可复用"的额外记忆容器。

但真正把这套东西做成可训练的系统,会撞上一个让人很难受的工程现实。

技能库这个东西,至少有三件事要做:

  • :来了一个新任务,从一大堆历史技能里找出几个相关的,然后挑一个最合适的
  • :拿着选出来的技能去做任务,技能是文本形式的策略提示,模型要会用它
  • :任务做完后,从 trajectory 里反思总结出一条新的、值得入库的技能

听起来很清晰对吧?问题是,现有的工作几乎没有一个能把这三件事同时用 RL 在一个目标下训出来

我之前做过一段类似的事,大概能讲清楚卡点在哪。

  • SkillRL 这一类是冻住选择模块的——retriever 用一个固定的 embedding 模型做相似度匹配,选完之后用 RL 训"用"和"攒"。问题是 retriever 选回来的东西如果本身就不靠谱,下游再怎么练也救不回来。
  • RetroAgent 是反过来,"用"和"攒"都拿来训了,但选择那一步靠的是基于历史成功率的启发式打分,跟策略梯度无关——也就是说"模型怎么生成 query"这件事没法被任务结果倒过来纠错。
  • EvolveR、Mem0+GRPO 这些就更直接,把库当外置数据库,把训"用"那一段当主战场,其它的丢给外部模块。

更尴尬的是,即便有人勉强同时训了两个阶段,它们用的奖励信号往往不是同一个——选技能可能用一个自评分数,攒技能可能用一个启发式匹配分,用技能用任务结果。三个信号互相之间没有内在一致性,训起来很容易出现"这边在涨那边在掉"的撕扯。

这就是 Skill1 想解决的问题。它的诉求其实可以一句话总结:

能不能用一个统一的策略、一个统一的任务奖励,把"选、用、攒"三件事真的一起训出来?

听上去像个理想主义的口号,但他们给的解法挺干净。


二、Skill1 的核心 idea:一个奖励信号,拆成两个时间尺度

图1:训练范式对比——左侧是 Agent 与技能库交互的"选-用-攒"三阶段循环;中间展示了之前的方法只对部分阶段做策略梯度(比如冻结选择、或用外部 teacher 做蒸馏);右侧则是 Skill1,用单一策略 + 单一任务奖励信号,把三个阶段一起优化。

图 1:训练范式对比。这张图把这篇论文的卖点讲得很直白——之前的方法是"局部最优",Skill1 主张"全局协同"。

2.1 工作流:一个策略串起三件事

来一个任务 \(x\),Skill1 是这么走的:

  1. 策略 \(\pi_\theta\) 看到任务,生成一个自然语言的 检索 query \(q\)
  2. 用一个冻结的 encoder(all-MiniLM-L6-v2)把 query 和库里每条技能的描述算个相似度,召回 top-\(K\) 个候选(论文里 \(K=5\)
  3. 同一个策略 \(\pi_\theta\) 拿到这 \(K\) 个候选,生成一个 permutation(重排序),排第一的那个技能 \(z\) 就被选出来
  4. 策略以 \(z\) 的 strategy 作为提示,进入多轮交互执行任务,最多 \(T\) 步,拿到 0/1 的终端奖励 \(r(\tau)\)
  5. 策略对整段 trajectory 做反思,输出一条新技能(含 strategy 描述和适用场景描述),只有当 \(r(\tau)=1\) 时才入库

整个过程里,query、permutation、action 序列、新技能这四段都是同一个 \(\pi_\theta\) 生成的,所以理论上都可以一起拿策略梯度更新。这就是"统一"两个字的来源。

技能库容量上限 5000 条,满了之后用一个简单的启发式做淘汰:\(U(s) \cdot \log(n(s))\),其中 \(U(s)\) 是技能的历史 utility 分,\(n(s)\) 是被选中的次数。两个都小的优先淘汰——既没人用、用了也没效果的,先走。

这一套工作流其实并不算"创新"。Voyager、ExpeL、RetroAgent 都有类似形态。Skill1 真正值钱的地方在后面那个 reward 拆解。

2.2 关键 trick:把任务奖励拆成低频趋势 + 高频偏离

这是我读到的时候第一次"嗯,这个想法挺漂亮"的地方。

问题摆在那:你只有一个 0/1 的任务结果 \(r(\tau)\),怎么把它合理地分配给"选、用、攒"三个完全不同时间尺度的能力?

  • "用" 关心的是当前这个 episode 做没做成——尺度就是单次
  • "选" 关心的是这个技能"长期来看"靠不靠谱——尺度是跨 episode 的统计
  • "攒" 关心的是这条新攒出来的技能"相对于库里已有的"是不是真有增量——尺度是和库的整体分布比较

作者的思路是:

\(r(\tau)\) 看成一个信号序列,做一个低通和高通滤波。低频部分(趋势)给"选",高频部分(偏离)给"攒","用"直接用原始信号。

具体怎么做?

选技能的奖励:对每条被检索回的技能 \(s\),维护一个 EMA(指数移动平均)的 utility 分:

\[U(s) \leftarrow (1-\alpha)\, U(s) + \alpha\, r(\tau_i),\quad \forall s \in \mathcal{B}_K\]

这个 EMA 就是任务奖励的低频分量——它把"这个技能跨多个 episode 平均效果如何"这件事提取出来。然后用它作为重排序的 ground truth,让策略给出的 permutation \(\sigma_i\) 尽量和 utility 排序对齐,奖励就是 NDCG:

\[R^{\text{rerank}}_i = \mathrm{NDCG}\bigl(\sigma_i,\; \operatorname{argsort}(-U(\mathcal{B}_K^i))\bigr)\]

注意一个小细节:EMA 更新时不只更新被选中的那条技能,而是更新所有被召回的 top-\(K\) 都更新。作者的理由是——同一批被召回,说明它们都和当前任务相关,可以一起拿当前的任务结果作为信号。这个工程决定我挺认可,单纯只更新被选中那条会让没被选的技能永远拿不到反馈。

攒技能的奖励:用当前任务结果减去"库里已有的最强技能的 utility":

\[R^{\text{distill}}_i = r(\tau_i) - \hat{U}_i,\quad \hat{U}_i = \max_{s \in \mathcal{B}_K^i} U(s)\]

这就是高频分量——它衡量的是"这次结果相对于库的现状是惊喜还是平庸"。如果一个新攒出来的技能能让任务结果超过库里最好的那条,那这条新技能就值得入库,给正奖励;反过来如果还不如现有的,给负奖励,劝退冗余蒸馏。

用技能的奖励:直接用原始任务结果 \(R^{\text{util}}_i = r(\tau_i)\),配合 GRPO 的 group-relative advantage 做策略梯度。

三个目标加权求和:

\[\mathcal{J}(\theta) = \mathcal{J}^{\text{util}}(\theta) + \lambda_1 \mathcal{J}^{\text{rerank}}(\theta) + \lambda_2 \mathcal{J}^{\text{distill}}(\theta)\]

论文里 \(\lambda_1 = \lambda_2 = 0.3\)

图2:Skill1 框架总览。(a) 策略生成 query 并对召回候选做重排序,完成"选";(b) 以选中的技能为条件做多轮交互,完成"用";(c) 反思 trajectory 蒸馏出新技能,完成"攒"。三段的奖励都从同一个 \(r(\tau)\) 派生。

图 2:完整流程图,把上面三步用图说清楚。注意 EMA-utility \(U(s)\) 这个东西是非参数化更新的,它本身不参与梯度,但它派生出来的 NDCG 和 variation 信号要回传梯度。

说到这,我想多聊一句——这种"用 EMA 提取长期趋势,用偏离提取短期变化"的思路其实在传统强化学习里有先例,比如 dueling network 把 V 值和 advantage 拆开、PPO 的 GAE 做时序差分。但把这套思想搬到"技能库管理"这个看似不相关的场景,让一个完全相同的任务奖励信号承担三种角色,这个 translation 是漂亮的。

2.3 工程上的几个细节

有几个点值得专门提一下,做过类似事情的同行会有感:

(a) Re-ranking 为什么用 REINFORCE 而不是 GRPO?

GRPO 是用同一个任务下的 \(G\) 个 rollout 做 group-relative advantage 标准化。但 Skill1 里每个 rollout 生成的 query 不一样,召回回来的 \(\mathcal{B}_K^i\) 也不一样,候选集都不同,做 group 比较就没意义了。所以 re-rank 部分退回到原始的 REINFORCE,单条独立优化。这个细节看上去不起眼,但其实是被实验逼出来的——如果直接套 GRPO,rerank 信号会被均值方差归一化扭曲。

(b) Distill 的 advantage 单独归一化。

虽然 distill 和 util 都用 GRPO 框架,但它们的 advantage 分别独立标准化。原因也很自然:util 衡量"任务做没做成",distill 衡量"和库比有没有惊喜",两者均值方差完全不同,混在一起算 group 统计量会互相干扰。

(c) 技能淘汰策略。

\(U(s) \cdot \log(n(s))\) 这个分数。乘 log 是为了保护"虽然效用高但还没被广泛验证"的新技能不被秒杀,同时也避免"虽然用得多但效果不好"的老技能赖着不走。挺工程的一个细节。


三、实验结果:好看,但要看清"和谁比"

3.1 主实验:97.5% 的 ALFWorld 平均成功率

ALFWorld 任务类型 Pick Look Clean Heat Cool Pick2 Avg WebShop Score WebShop Succ
ReAct 48.5 35.4 34.3 13.2 18.2 17.6 31.2 46.2 19.5
Reflexion 62.0 41.6 44.9 30.9 36.3 23.8 42.7 58.1 28.8
ExpeL 21.0 67.0 55.0 52.0 71.0 6.0 46.3 30.9 11.2
PPO 92.3 64.0 92.5 89.5 80.3 68.8 80.4 81.4 68.7
GRPO 90.8 66.1 89.3 74.7 72.5 64.7 77.6 79.3 66.1
GiGPO 97.7 82.7 98.8 83.7 89.3 79.2 90.8 84.4 72.8
SkillRL 97.9 71.4 90.0 90.0 95.5 87.5 89.9 85.2 72.7
RetroAgent 97.9 90.9 99.2 92.9 85.3 91.0 94.9 88.9 82.3
Skill1 100.0 98.6 97.3 99.2 96.1 96.0 97.5 89.7 82.9

几个观察。

和最强 RL-only 方法 GiGPO 比,Skill1 高了 6.7 个点。 这个差距体现的是"显式技能库"相对于"参数内隐式记忆"的额外价值。最有意思的是涨幅最大的两类任务——Look(+15.9 点)和 Pick2(+16.8 点)——恰好是需要组合多个子流程的任务类型。这一点和直觉吻合:参数化记忆擅长把一个完整流程"固化",但要在新任务里"重组合"已学过的子流程,显式技能库的可读、可检索特性就有优势了。

和最强带技能库的方法 RetroAgent 比,Skill1 高 2.6 个点。 RetroAgent 的"选"是启发式的(基于历史成功率),"用"和"攒"都用了 RL 但奖励信号是分开的。Skill1 把三件事统一在一个信号下,赚回这 2.6 个点。但说实话——

这里我必须给一个不太热情的评论。 Skill1 主表中的"Clean"任务上比 RetroAgent 低了 1.9 个点,作者在附录 D 里强调这个差距 \(p=0.147\) 不显著。但反过来 Skill1 比 RetroAgent 高的那些 task,确实 \(p\) 值很小(Heat \(p=0.004\),Cool \(p=0.005\))。这个 statistical analysis 是论文最让我信服的部分之一——他们没有躲,明确给了 3 seed 的方差和 t-test。这种坦诚比那种把方差藏起来只给 mean 的做法好得多。

3.2 消融:拆掉哪一块都不行

配置 Pick Look Clean Heat Cool Pick2 Avg
Skill1 完整版 100.0 98.6 97.3 99.2 96.1 96.0 97.5
w/o Select.(去掉选择阶段) 96.9 90.3 98.0 90.4 86.5 85.3 91.8
w/o Distill.(去掉蒸馏阶段) 97.4 88.5 98.1 96.1 87.6 89.5 92.4
w/o Library(完全不要库) 96.7 71.5 94.9 70.7 71.5 65.5 80.9
\(\lambda_1=0\)(去掉重排序奖励) 99.5 80.5 98.8 100.0 90.6 84.9 94.0
\(\lambda_2=0\)(去掉蒸馏奖励) 100.0 85.4 95.5 96.4 91.0 96.2 94.9
\(\lambda_1=\lambda_2=0\) 98.1 74.9 95.6 95.6 79.5 87.2 90.2

我看完这张表第一反应是:"w/o Library 才掉到 80.9%,比我想象的还顽强。" 然后又仔细看了一眼——掉得最狠的是 Heat(-28.5 点)和 Pick2(-30.5 点),这两个都是多步组合任务。说明库的价值真的高度集中在"需要复用历史子流程"的场景,而对单步任务(如 Pick)影响不大。

更值得琢磨的是 \(\lambda_1\)\(\lambda_2\) 同时为 0 的那一行——掉到 90.2%,比单独去掉任何一个 stage("用、攒"二选一)还低。作者的解读是:这两个辅助信号不仅对它们自己的目标有用,对"用"也有帮助。你想想看,当 selection 和 distillation 同时缺少信号的时候,policy 退化成了一个有库但不会管理库的状态,这个状态比"完全没有库"还糟糕——因为库里塞满了不靠谱的东西,反而干扰了 utilization。

3.3 训练动力学:三个能力是怎么一起涨起来的

图3:训练动态。三条曲线分别是:选择精度(U(s) 平均值)、任务完成率、蒸馏正向率。橙色是完整 Skill1,绿色是去掉选择信号的版本,浅色是去掉两个辅助信号的版本。

图 3:训练动力学。最值得看的是左图——selection precision 在 step 20 左右就先冲到 0.95,然后 step 60 时 utilization 和 distillation 才跟着冲到 0.8。这个"先选后用"的次序很自然——只有先有靠谱的检索,下游才有得练。

这张图是我觉得整篇论文最有说服力的一张。它直接展示了三个能力在训练过程中的相互推动——选择先收敛,提供高质量的技能供给;然后利用和蒸馏跟上,因为它们手里拿到的素材变好了;最后这两者的提升又反过来让库的整体质量上升,selection 的信号也跟着变更可靠。这是一个真实的正反馈循环。

如果你把绿色或浅色曲线放在一起看,会发现去掉选择信号之后,所有三条曲线都被拖慢了——不仅是 selection 自己,连 utilization 都跟着掉。这就是 co-evolution 的核心论据:每一个 stage 的信号都不只服务于自己,它通过改变下游 stage 的输入分布间接影响整条链。

3.4 技能库的多样性:值得单独说

图6:t-SNE 可视化收敛后的技能库。左图是完整 Skill1,右图是去掉选择和蒸馏信号的版本。被红色和紫色高亮的是使用频率前 10% 的技能。Skill1 激活的高频技能数量几乎是 baseline 的两倍,且分布在更广的策略空间。

图 6:技能库的多样性对比。这张图给的洞察很直观——Skill1 的高频技能像撒胡椒粉一样分散在策略空间,而消融版本的高频技能挤在角落里抱团。

这是一个我没预期到但很喜欢的发现。当你只看主表的成功率数字时,你看不出技能库内部发生了什么——是高度集中在几条"明星技能"上反复用,还是真的形成了一个多样化的能力集合?

Figure 6 给出的答案是后者。Skill1 训出来的库里,被高频使用的技能(前 10%)数量几乎是消融版本的两倍,而且这些高频技能在 t-SNE 空间里铺得很开。作者的解释是:variation 信号(也就是 \(R^{\text{distill}} = r - \hat{U}\))会主动惩罚"和现有技能高度相似但效果不见得更好"的新蒸馏,所以策略被迫去探索那些没被现有技能覆盖的角落。

这点对工程很有启发——如果你做技能库管理,单纯的"成功才入库"这种过滤是不够的,你需要一个"和现有库的差异性"的额外信号来避免技能塌缩

3.5 计算开销:1.3 - 1.7 倍于 GRPO

方法 Step 20 (s) Step 60 (s) Step 100 (s) 库大小 @ Step 100
GRPO(无库) 301 274 297
SkillRL 368 319 327 83
Skill1 387 444 494 5000
w/o Distill. 509 750 738 5000(提前满)

Skill1 比纯 GRPO 慢 1.3 - 1.7 倍,开销主要来自库变大之后 selection 时上下文变长。但说实话,对于一个能涨 17 点的方法(GRPO 77.6 → Skill1 97.5),这点开销在我看来挺值的

更有意思的对比是 w/o Distill.——去掉蒸馏之后,库会被原始 trajectory 塞爆(5000 上限快速打满),导致 selection 上下文极长,反而慢了 69%。这说明蒸馏不仅是为了质量,也是为了把经验压缩成短的、可检索的形态,控制库的"信息熵"在一个合理范围。


四、几个让我皱眉的地方

读完之后我必须承认这是一篇做得相当扎实的工作。但有几个地方我会想多问一句。

(1) 任务奖励都是 0/1,信号过粗的问题怎么办?

ALFWorld 和 WebShop 的 success rate 是二值的(成功/失败),整个 EMA-utility 也就在 [0,1] 之间。这种情况下,"低频趋势"和"高频偏离"的拆分确实清爽。但如果任务奖励本身是稠密的(比如 process reward 或者多维评分),这套拆解还能不能 work?作者没回答这个问题。我直觉上这套机制在稀疏 0/1 信号下最干净,一旦换成稠密信号,"低频"和"高频"的边界会变模糊。

(2) Encoder 冻结,但 query 是学出来的,这个 mismatch 怎么处理?

retriever 用的 all-MiniLM-L6-v2 是预训练好的句嵌入模型,它对 query 和 skill description 的语义匹配方式是固定的。但 Skill1 同时在训"生成什么样的 query 才能召回好技能"——其实是让 policy 去学一个 encoder 的隐式 prior。如果 encoder 本身的语义空间和任务空间不对齐,policy 就要学一种"奇怪的"措辞方式来骗 retriever。这种"为 encoder 量身定制 query"的策略可能不会泛化。

(3) 库容量 5000 是不是太小?

ALFWorld 训练数据规模有限,5000 条够用。但论文自己也在 Limitation 里承认了——任务多样性一上来,这个 fixed-size 库就会成为瓶颈。我自己更想看到的是 hierarchical skill organization 或者基于 retrieval frequency 的分层淘汰机制,而不是简单的 EMA-based eviction。

(4) 和同期工业界方案的关系。

论文里对比的 baseline 主要是学界工作(RetroAgent、SkillRL、Mem0),但同期工业界其实有一些类似的"经验沉淀 + 检索复用"的 production 系统在跑。这套学术框架真的能直接照搬到工业部署吗?我持保留态度——production 里的技能往往不是 free-form 文本,而是结构化的工具调用模板、有版本号、有 ownership。Skill1 的 strategy 字段是自由文本,自然语言泛化好但工程可维护性差。


五、对工程的启发:哪些可以拿来用

抛开论文本身的研究价值,对做 Agent 系统的工程同行,我觉得有几个 takeaway 是可以直接吸收的。

(a) "选、用、攒"必须联合训练,不能分块。 这是最大的工程启发。如果你的 Agent 系统已经有了一个固定 retriever + 训过的 policy,那升级路径不是各练各的,而是想办法让 policy 也参与"生成 query"这件事,让 query 也能拿到 task feedback 的梯度。

(b) 用 EMA 跟踪每条技能的长期 utility。 这个开销极小,但能给你一个"哪些技能值得保留、哪些该淘汰"的可量化指标。即便不做 RL 训练,这个 utility 也可以作为重排序的特征喂给一个轻量的 ranker。

(c) 不要只用"任务成功"作为入库门槛。 Skill1 的 variation 信号给了一个很好的工程范式——"超过库的最强水平"才入库。这个比简单的"成功即入库"门槛高,能避免库快速膨胀和质量稀释。

(d) Distillation 不只是为了质量,也是为了压缩。 这个 insight 容易被忽略。Skill1 的 ablation 里去掉 distillation 的版本反而比完整版慢 50%+,因为库被原始 trajectory 撑爆了。压缩本身就是一种性能优化。

(e) 用 t-SNE 看你的库长什么样。 这是一个非常便宜的诊断工具。如果你的高频技能在嵌入空间里抱团,说明你的库已经塌缩到几个套路上了,需要从机制层面加压力让它探索新策略空间。


六、一个我没太想明白的开放问题

最后聊一个我自己还没完全想通的事情,作者也没解决——

当 Agent 主要靠技能库做事的时候,base policy 的能力上限在哪里?

Skill1 用的是 Qwen2.5-7B-Instruct。这个模型本身的 reasoning 能力其实有上限。当技能库变得越来越丰富,base policy 越来越依赖"检索 + 套用"模式时,会不会出现一个现象——policy 变成了一个"高级模板拼接器",而失去了从头思考的能力

这不是危言耸听。Case Study 1 里那个例子,Skill1 之所以知道"stove burner 烧不了 plate,要用微波炉",是因为库里恰好有一条 utility=0.951 的技能明确写了这一点。如果碰到一个库里没有的新约束,policy 还能不能像 zero-shot 那样从环境观测里推理出来?还是说它已经被训练得"先查库再说",忘了怎么 zero-shot 推理?

这其实是参数化记忆和外部记忆之争的一个老问题,Skill1 没有给出答案,但它提供了一个很好的 testbed 来研究这个问题。

总的来说,这是一篇我会推荐给做 Agent 训练同行细读的论文。它不是那种"突破性的新算法"——说到底还是 GRPO 的拓展。但它在"如何用一个干净的奖励拆解机制把多个能力一起练"这件事上做得很漂亮,并且 ablation 和 statistical analysis 都给得相当扎实。这种"思想干净 + 实验严谨"的组合,在当下 Agent 领域已经不算多见了。


觉得有启发的话,欢迎点赞、在看、转发。跟进最新 AI 前沿,关注我