选技能不是选 Top-k:这篇论文把 Agent 技能路由改造成了"组队问题"

你有没有想过一个场景:你的 Agent 背后挂着一个八万多个技能的技能库,用户来了一句"帮我把这个 Excel 里的数据清洗一下,然后画个图"。路由模块吭哧吭哧算了一圈相关度,返回的前 10 个技能里,有 6 个都是"数据清洗"的近亲——描述长得像、实现也像,个个相关度分数都很高。而真正需要的"画图表"技能,被挤到了第 15 名开外。

上下文窗口就那么大,塞进去一堆重复技能,真正的短板反而没补上。

arXiv 2609.05824 这篇论文盯的就是这个问题。说实话我第一反应是:这不就是检索里的多样性重排嘛,MMR 二十多年前就干了。但往下看才发现,直接把多样性惩罚搬过来在技能路由场景里会翻车,而且翻得挺惨——论文消融实验里, naive 的余弦相似度 DPP 把多技能 Full Coverage@10 从 0.424 干到了 0.254,越加多样性越差。这篇论文值钱的地方,就是讲清楚了为什么会翻车,以及怎么修。

核心摘要

大规模技能库里,很多技能功能上是冗余的,而复杂任务往往需要的是一组互补技能。现有技能路由器把每个候选技能独立打分取 Top-k,容易浪费宝贵的上下文预算在重复技能上。作者提出 DSR(Diverse Skill Routing),用行列式点过程(DPP)做多样性感知的子集选择,核心创新是一个"查询残差多样性核"——先把每个技能 embedding 里和查询对齐的分量扣掉,再算技能之间的冗余。在 SkillRouter 基准(约 8 万技能、75 条专家验证查询)上,DSR 在多技能查询上把 Full Coverage@50 从 0.458 提到 0.551,涨了 9.3 个点。我的判断:这不是底层突破,是一个想法干净、实现便宜、定位精准的工程改进,但它指出的"技能路由本质是互补集合选择"这个视角,值得每个在做 Agent 技能系统的人认真想一想。

论文信息

  • 标题:Beyond Top-k Skill Retrieval: Diversity-Aware Skill Routing for LLM Agents
  • 作者:Wang Wei、Tiankai Yang、Samyadeep Basu、Hongjie Chen、Yue Zhao、Zhengzhong Tu、Xiyang Hu、Franck Dernoncourt、Ryan A. Rossi、Hoda Eldardiry(通讯作者)
  • 机构:Virginia Tech、University of Southern California、Adobe Research、Dolby Labs、Texas A&M University、Arizona State University
  • 发表:2026 年 9 月 5 日提交,arXiv:2609.05824(cs.AI)
  • 链接:https://arxiv.org/abs/2609.05824

🎯 问题动机:技能多了,路由反而成了瓶颈

先交代一下背景。这两年 Agent 系统的一个明显趋势是"技能化"——不再靠重训模型加能力,而是把程序性知识打包成可复用的技能模块(指令、脚本、示例、参考文档),推理时按需加载进上下文。Voyager 在具身场景里让 Agent 自己攒技能库,SkillRouter 则直接研究了在约 8 万个技能的大规模池子上做路由有多难,还发现完整的技能实现文本(不只是名字和描述)里藏着重要的路由信号。

问题来了。当技能池膨胀到几万、几十万的量级,你不可能把所有技能都塞给 Agent——上下文窗口装不下,无关技能还会干扰执行。所以需要一个路由器,从大海里捞出相关的几个。

现有路由器(包括 SkillRouter)的做法是典型的两段式:先用双塔 encoder 检索候选,再用逐点(pointwise)重排模型给每个候选独立打一个相关度分数,取 Top-k 完事。

对单技能任务,这没毛病——找到那一个对的就行。但真实 Agent 任务往往是组合式的:文档解析、信息抽取、数据变换、可视化,一条流水线要好几环。这时候独立打分的毛病就暴露了:好几个功能雷同的技能因为描述相似,相关度分数都高,抱团挤进 Top-k;而任务需要的另一环技能,分数稍低就被挤掉了。

结果就是:上下文预算被冗余技能吃掉,多步工作流的覆盖面反而变差。

作者的论点一句话就能说清:大规模技能路由不该只是相关度排序,它本质上是互补集合选择——你要选的不是"k 个最相关的技能",而是"k 个合起来能覆盖任务的技能"。

🧠 方法核心:DPP 组队,但先把"查询成分"扣掉

直觉先行

如果让我用一句话讲 DSR 的核心 idea:选技能就像组队打篮球,不是选五个得分最强的,而是选五个位置互补的——但注意,"都和这场比赛相关"不算重复,"干的活一样"才算重复。

这后半句就是全篇的技术灵魂。

整体流程

DSR 站在标准 retrieve-and-rerank 流水线的肩膀上,只动最后一步:

  1. 候选检索:encoder 把查询 \(x\) 和每个技能 \(s_i\) 各自编码成 L2 归一化的 embedding \(\mathbf{e}_x\)\(\mathbf{e}_i\),用余弦相似度 \(a_i(x)=\mathbf{e}_x^\top\mathbf{e}_i\) 从全库里捞出 Top-\(M\) 候选集 \(\mathcal{C}_x\)\(M \ll N\),实验里 \(M=50\))。
  2. 质量打分:用逐点重排模型(reranker)给每个候选算相关度 logit \(h_i(x)\),过 sigmoid 得到非负质量分 \(q_i(x)=\sigma(h_i(x))\)
  3. DPP 子集选择:在候选集上构造 DPP 核矩阵,贪心地选出一个既高质量又不冗余的 \(k\) 元子集,最后按质量分排序输出。

前两步和 SkillRouter 完全一样——这是刻意的,作者要隔离出"多样性选择"这一个变量的效果。

DPP 是什么,为什么用它

行列式点过程(Determinantal Point Process)是子集选择问题的一个经典概率框架,天生偏好"质量高且彼此不同"的集合。DSR 在候选集上构造核矩阵:

\[L^{(x)}_{ij} = q_i(x)\,\phi_x(s_i,s_j)\,q_j(x)\]

其中 \(\phi_x(s_i,s_j)\) 是查询条件下的技能间相似度。对任意子集 \(A\),DPP 给的分数是行列式 \(F(A;x)=\det(\mathbf{L}^{(x)}_A)\)。行列式有个很妙的几何直觉:它正比于这些向量张成的平行多面体体积——质量分高让边更长,彼此不相似让边之间更"撑得开",体积自然大。冗余技能向量方向接近,体积塌缩,行列式就小。

精确求最优子集是难的,DSR 用标准的贪心 MAP:从空集出发,每步加入使对数行列式边际增益最大的候选,直到凑够 \(k\) 个,用增量 Cholesky 分解避免反复重算行列式。

有个细节挺优雅:贪心第一步时 \(A=\emptyset\),边际增益是 \(\log L^{(x)}_{ii}=\log q_i(x)^2\),也就是第一个被选中的永远是质量分最高的技能——reranker 的头名预测被完整保留,多样性只在后续位置发力。这个设计很稳。

查询残差核:全篇真正的关键

如果直接用技能 embedding 的余弦相似度当 \(\phi_x\),就是标准 DPP。但作者指出,这在技能路由里有个隐蔽的坑:

两个技能 embedding 接近,可能有两个完全不同的原因——

  • 它们真的冗余(功能重叠,该罚);
  • 它们都和同一个查询相关(比如"清洗数据"和"画图表"都因为和"分析这个表格"相关而共享了查询方向的 embedding 分量,但它们干活的部分并不一样,不该罚)。

不分青红皂白地惩罚所有相似度,就会把"同任务但互补"的技能当成冗余踢掉。这就是消融实验里 naive 核翻车的原因。

DSR 的解法干净得像教科书:先把每个技能 embedding 里沿查询方向的分量投影掉,得到残差

\[\mathbf{r}_i = \mathbf{e}_i - (\mathbf{e}_i^\top\mathbf{e}_x)\,\mathbf{e}_x\]

再把残差和原 embedding 按比例混合并归一化:

\[\tilde{\mathbf{z}}_i = \lambda\,\mathbf{r}_i + (1-\lambda)\,\mathbf{e}_i, \qquad \mathbf{z}_i = \frac{\tilde{\mathbf{z}}_i}{\|\tilde{\mathbf{z}}_i\|_2}\]

\(\lambda\in[0,1]\) 控制残差投影的强度(实验取 0.85),保留一小部分原始表示是为了稳定性。最后相似度算在残差空间里:

\[\phi_x(s_i,s_j) = \frac{1+\mathbf{z}_i^\top\mathbf{z}_j}{2}\]

映射到 \([0,1]\),且保证 \(\phi_x(s_i,s_i)=1\)

这样,"因为都相关所以相似"的部分被扣掉了,多样性惩罚只落在"扣掉查询成分之后还剩下的重叠"上——那才是真正的冗余。这个思路让我想到残差网络和投影矩阵的老把戏,不新鲜,但用在这里恰到好处,而且只需要一行投影运算,几乎零成本。

🧪 实验:多技能查询上的差距藏不住了

实验设置

基准是 SkillRouter:从 Claude Skill Registry 衍生的约 8 万候选技能池,75 条专家验证查询,覆盖 55 个领域、8 个大类。其中单技能查询 24 条,多技能查询 51 条(每条需要 2 到 5 个目标技能)。还分两个难度档:Easy 档 78,361 个技能,Hard 档 79,141 个(混入了 780 个 LLM 生成的干扰技能),论文报告两档平均。

对比基线是完整 SkillRouter 流水线:SR-Emb-0.6B 检索 + SR-Rank-0.6B 逐点重排。DSR 用一模一样的检索器和重排器,只把最后的"取 Top-k"换成"查询残差 DPP 选择"。指标是 Recall@k(目标技能召回了多少)和 Full Coverage@k(该查询的所有目标技能是否全部进了前 k——对多技能任务这个指标更严苛也更实际,缺一环整条流水线就跑不起来)。\(k\in\{10,20,50\}\),实验在 A100 80GB 上跑。

一个读表时要知道的坑:SkillRouter 官方发布的输出只有 20 条排序结果,所以它的 Recall@50 和 Full Coverage@50 就等于 @20 的值。@50 的对比其实是看"DSR 能不能构造出更长、更不冗余的短名单"。

主结果

方法 R@10 R@20 R@50 FC@10 FC@20 FC@50
全部查询(75 条)
SkillRouter .705 .754 .754 .520 .560 .560
DSR .712 .768 .808 .527 .573 .633
多技能查询(51 条)
SkillRouter .659 .704 .704 .424 .458 .458
DSR .668 .739 .773 .432 .492 .551

坦率地讲,共享截断点 @20 上的提升不算炸裂:全量查询 Recall@20 从 0.754 到 0.768,Full Coverage@20 从 0.560 到 0.573。考虑到没换检索器也没换重排器,只动了选择策略,这个幅度合理但谈不上惊艳。

真正的差距在更长名单和多技能查询上。多技能子集,Full Coverage@50 从 0.458 到 0.551——9.3 个点。这符合理论预期:逐点重排容易让相似技能抱团占据前列,而 DPP 会主动把名额让给覆盖任务不同侧面的技能。任务越组合化,名单越长,多样性选择的红利越大。

附录还给了 MRR 作为早精度诊断:DSR 的 MRR@10 是 0.784,SkillRouter 是 0.788,几乎持平。也就是说覆盖率的提升不是靠牺牲首个正确技能的排名换来的——这和"贪心第一步必选质量分最高者"的设计对上了。

消融:残差核是灵魂,naive 多样性反而有毒

这张表我觉得是全篇最有信息量的:

核函数 质量分来源 R@10 多技能 FC@10
无(逐点基线) Reranker .705 .424
标准余弦核 Embedding .540 .178
标准余弦核 Reranker .534 .254
查询残差核 Embedding .618 .237
查询残差核 Reranker .711 .441

两个发现。

其一,naive 多样性是有害的。同样用 reranker 质量分,标准余弦核 DPP 的多技能 FC@10 只有 0.254,连不做任何多样性处理的逐点基线(0.424)都远远不如。换成残差核,同样条件下飙到 0.441。这就是前面说的那个坑的实锤:余弦核把"共享任务上下文"误判成冗余,把该一起选的互补技能拆散了。0.254 对 0.441,这个差距比主实验的任何一组对比都大,说明整个方法的成败几乎全押在残差投影这一步上。

其二,质量信号仍然重要。固定残差核,把 reranker 质量换成 embedding 相似度质量,R@10 从 0.711 掉到 0.618,多技能 FC@10 从 0.441 掉到 0.237。DPP 只是选择器,原料不行,选得再巧也没用。最好的组合是"学出来的相关度模型 + 查询残差多样性",两者缺一不可。

和其他排序策略的横评

方法 单技能 R@10 单技能 FC@10 多技能 R@10 多技能 FC@10
Top-k 检索(纯 embedding) .875 .875 .630 .381
LLM ranker(Qwen3-8B 零样本) .719 .719 .616 .331
SkillRouter(SR-Rank-0.6B) .875 .875 .659 .424
DSR .875 .875 .668 .432

单技能查询上四家打平——只要目标技能只有一个,强逐点信号就够用了,多样性没处发力。多技能查询上才是分水岭。还有个值得一瞟的数字:零样本 LLM 重排(Qwen3-8B)全面垫底,多技能 FC@10 只有 0.331,连纯 embedding 检索都不如。在上万个相似技能里做细粒度区分,prompt 一把梭看来是真不行,这倒也侧面支持了"专门训练的小重排器仍有存在价值"。

🤔 我的判断

亮点在哪。 这篇论文最大的贡献其实不是方法本身——DPP 是老工具,残差投影是老技巧——而是把问题重新定义清楚了:技能路由 = 互补集合选择,而不是相关度排序。这个视角的切换带来了一个可直接落地的改进,而且工程成本极低:冻结检索器和重排器,只在最后加一步 50×50 矩阵上的贪心 DPP,增量 Cholesky 实现,开销可以忽略。消融实验做得诚实,把"naive 多样性会翻车"这个负结果明明白白摆出来,这比主实验涨的那几个点更有说服力。

问题在哪。 也有几个让我皱眉的地方。

第一,评估规模偏小。75 条专家查询,多技能子集只有 51 条——Full Coverage@50 上 9.3 个点的差距,换算下来大约对应 5 条查询的翻转。方向是可信的(消融和横评的模式都一致),但统计噪声不小。作者自己在 Limitations 里也承认了这一点。

第二,@50 的对比对基线有点不公平。SkillRouter 官方输出只到 20 条,它的 @50 指标天然被截断,DSR 在 @50 上的"大胜"有一部分是名单长度带来的。真正干净的对比是 @20,而 @20 上的提升是温和的。作者对此倒是坦率,明确说了 @20 才是共享截断对比。

第三,指标停在检索层。Recall 和 Full Coverage 度量的是"技能捞没捞全",不是"Agent 最终任务做没做成"。技能全捞到了,Agent 照样可能因为规划失误或技能指令冲突而翻车。检索覆盖到端到端成功率之间还隔着一层,这篇论文没碰。

第四,DSR 完全受制于候选生成。如果检索器压根没把必需技能捞进 Top-50,后面 DPP 再精巧也无力回天。多样性选择治不了召回的漏。

对工程的启发。 如果你在做技能库、工具库或者 MCP server 池子比较大的 Agent 系统,这个思路几乎可以直接抄:保留现有检索+重排,在输出前加一层查询残差 DPP 选择,几十行代码的事。但抄之前先想清楚一件事——你的场景里多技能查询占比多大?如果绝大多数请求单技能就能解决,这层多样性基本白加(单技能上四个方法打平已经说明了)。另外,千万别图省事直接用 embedding 余弦相似度做多样性惩罚,这篇论文用 0.254 vs 0.441 的数字告诉你,那是负优化。

顺着这个方向再追问一步:残差核扣掉的是"和查询线性对齐"的分量,但技能之间的互补性未必活在 embedding 的线性结构里——"清洗数据"和"画图表"在工作流里是上下游关系,这种功能性互补靠 embedding 投影能不能完全捕捉,我是存疑的。也许下一步是把工作流结构(技能之间的调用依赖)显式建模进选择目标里。那才是"组队问题"的完整形态。


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