1. 技能库膨胀这件事,为什么值得单独拿出来讲
做LLM智能体的人,几乎都会经历同一个阶段:一开始觉得技能库越丰富越好,恨不得把能想到的所有工具、所有流程、所有经验都塞进去。结果跑了一段时间之后发现,智能体的表现不但没有变好,反而开始出现各种莫名其妙的错误——调用了一个根本不该用的技能,或者在两个相似技能之间反复横跳,又或者因为技能描述太长把上下文窗口撑爆了。
这个现象在圈子里有个很形象的说法,叫技能库只增不减。你不断往里面加东西,但从来没有人去清理、合并、淘汰。时间一长,技能库就变成了一个堆满杂物的仓库,找什么都费劲,用什么都别扭。
EMNLP 2026 上有一篇工作叫SkillBrew,专门针对这个问题提出了一个多目标精炼的框架。它的核心思路很直接:技能库不应该只做加法,还要做减法。通过多目标的优化策略,把冗余的、低效的、互相冲突的技能进行合并、裁剪和重排序,让整个技能库变得更精炼、更好用。
这篇文章我想从工程实践的角度,把SkillBrew背后的逻辑拆开来讲清楚。不管你是正在搭建智能体系统的开发者,还是已经在维护一个跑了一段时间的技能库,这些内容应该都能给你一些可以直接落地的思路。我不会只讲论文里的公式,更多会讲为什么这个问题会出现、怎么判断你的技能库该精炼了、以及具体可以怎么操作。
2. 技能库为什么会越用越臃肿:三个真实的膨胀源头
2.1 增量式开发带来的“只加不删”惯性
大部分智能体项目的开发模式都是增量式的。今天发现智能体不会处理某个任务,就加一个技能;明天发现另一个场景覆盖不到,再加一个技能。这种开发方式本身没有问题,问题在于没有人会在加完新技能之后回头去看旧技能是不是还必要。
我见过一个典型的例子:一个客服智能体,最开始只有一个“查询订单”的技能。后来业务扩展,加了“查询物流”“查询退换货政策”“查询优惠券”“查询会员权益”等等。半年之后,技能库里躺着三十多个查询类技能,其中很多功能高度重叠,但因为没有统一的命名规范和功能边界定义,智能体经常在几个相似技能之间选错。
这种惯性的本质是:加技能是解决眼前问题的最快路径,而删技能需要额外的判断成本和验证成本。在项目进度压力下,几乎所有人都会选择先加再说。
2.2 技能描述粒度过细导致的碎片化
另一个常见的膨胀源头是技能粒度过细。比如“发送邮件”这个功能,有人会拆成“填写收件人”“填写主题”“填写正文”“点击发送”四个技能。从软件工程的角度看,这种拆分似乎很合理,每个技能职责单一。但对于LLM智能体来说,这反而增加了编排的复杂度。
智能体需要在每一步都做一次技能选择,而每次选择都有出错的可能。四个步骤意味着四次选择,每次选择的准确率如果是95%,四步下来整体成功率就降到了81%左右。更麻烦的是,碎片化的技能会让技能库的条目数量快速膨胀,而每一条技能描述都要占用上下文窗口的空间。
2.3 失败经验的错误固化
还有一种比较隐蔽的膨胀:把一次性的失败修复固化成了永久技能。比如某次智能体在处理一个特殊格式的日期时出错了,开发者就加了一个“处理特殊日期格式”的技能。但实际上这个特殊格式可能只出现过一次,后面再也不会遇到。这个技能就变成了一个永远不会被调用、但一直占着位置的僵尸技能。
更糟糕的是,如果这个僵尸技能的描述和某个常用技能的描述有重叠,它还可能在某些边缘情况下被误触发,导致原本正常的流程出错。
这三种膨胀源头往往同时存在,互相叠加。一个跑了三个月的智能体项目,技能库里可能有30%到50%的技能是冗余的、过细的或者僵尸状态的。
3. SkillBrew的多目标精炼到底在优化什么
3.1 三个优化目标:精简度、区分度、覆盖度
SkillBrew的核心是一个多目标优化框架,它同时考虑三个维度的目标:
精简度指的是技能库的整体规模要尽可能小。这不是单纯地追求数量少,而是要求每一条保留的技能都有明确的、不可替代的作用。精简度高的技能库,上下文占用少,智能体做选择时的候选空间小,出错的概率自然就低。
区分度指的是不同技能之间的功能边界要清晰。如果两个技能的描述高度相似,智能体就很难判断该用哪一个。SkillBrew会计算技能之间的语义相似度,对于相似度过高的技能对,要么合并,要么重新定义边界。
覆盖度指的是技能库整体能覆盖的任务范围不能因为精简而缩小。这是精简度的约束条件——你不能为了精简把有用的技能也删掉。覆盖度的评估通常需要一组代表性的任务样本,用来验证精简后的技能库是否还能完成原有的任务。
这三个目标之间存在天然的张力:精简度提高可能会降低覆盖度,区分度提高可能会增加技能数量。SkillBrew的做法不是找一个单一的最优解,而是找一组帕累托最优的解,让开发者可以根据自己的场景需求来选择。
3.2 技能合并的判定逻辑
技能合并是精炼过程中最关键也最需要谨慎的操作。SkillBrew的合并判定主要基于两个信号:
第一个信号是语义相似度。如果两个技能的名称和描述在向量空间中的距离非常近,它们大概率在功能上是重叠的。但语义相似度不能作为唯一依据,因为有些技能描述相似但实际处理的数据类型完全不同。
第二个信号是共现模式。SkillBrew会分析智能体在实际运行中的技能调用日志,看两个技能是否经常在同一个任务中被先后调用。如果两个技能几乎总是成对出现,那它们很可能应该被合并成一个更粗粒度的技能。
合并的决策规则可以简单概括为:语义相似度高且共现频率高的技能对,优先合并;语义相似度高但共现频率低的技能对,需要人工审核;语义相似度低但共现频率高的技能对,考虑是否可以组合成一个新的复合技能。
3.3 技能裁剪的风险控制
裁剪比合并更激进,因为它直接删除技能。SkillBrew在裁剪时采用了一种影子模式的策略:先标记候选裁剪技能,但在实际运行中仍然保留它们,只是不再主动推荐给智能体。观察一段时间后,如果确认这些技能从未被需要,再真正删除。
这种策略的好处是避免了“删错了才发现”的尴尬。我在自己的项目里也用过类似的方法,实测下来,影子模式可以把误删的风险降低到几乎为零,代价只是多占一段时间的存储空间。
4. 怎么判断你的技能库该做精炼了
4.1 四个可量化的预警信号
不是所有技能库都需要立刻精炼。但如果出现下面这几个信号,就说明问题已经比较严重了:
| 预警信号 | 判断标准 | 可能的影响 |
|---|---|---|
| 技能选择准确率下降 | 连续一周低于85% | 任务失败率上升 |
| 平均候选技能数过多 | 单次选择超过15个候选 | 上下文浪费,延迟增加 |
| 僵尸技能占比过高 | 超过20%的技能近30天零调用 | 维护成本浪费 |
| 相似技能对数量 | 相似度>0.85的技能对超过10对 | 选择混淆风险高 |
这些指标不需要很精确的计算,用简单的日志统计就能得到。关键是要有一个定期检查的机制,而不是等到问题爆发了才去处理。
4.2 用调用日志做一次技能库体检
如果你还没有系统的监控,可以先从调用日志入手做一次体检。具体做法是:
- 导出最近30天的技能调用记录,包含技能名称、调用时间、任务ID、调用结果(成功/失败)。
- 统计每个技能的调用次数,标记出零调用和低调用(少于5次)的技能。
- 计算技能之间的共现矩阵,找出高频共现的技能对。
- 对技能描述做一次向量化,计算两两之间的余弦相似度,找出高相似度的技能对。
这四步做完,你基本上就能看清楚技能库的健康状况了。零调用和低调用的技能是裁剪的候选,高频共现的技能对是合并的候选,高相似度的技能对是需要重新定义边界的候选。
4.3 精炼时机的选择:不要等到不得不做
我的经验是,技能库精炼最好在项目相对稳定的阶段做,而不是在功能快速迭代的时候。因为精炼本身需要验证,如果在迭代期做,你很难区分性能变化是精炼带来的还是新功能带来的。
一个比较合理的节奏是:每完成一个大版本的功能开发,留出一到两周的稳定期,在稳定期内做一次技能库精炼。这样既不会打断开发节奏,又能保证技能库不会无限膨胀。
5. 把SkillBrew的思路落地到自己的项目里
5.1 第一步:建立技能描述规范
在精炼之前,先确保你的技能描述是规范的。SkillBrew的很多判定逻辑依赖于技能描述的语义质量,如果描述本身写得含糊不清,后续的相似度计算和合并判定都会不准。
一个好的技能描述应该包含三个要素:功能动词+操作对象+边界条件。比如“查询订单”这个描述就太模糊了,改成“根据订单号查询订单状态和物流信息,仅支持已支付订单”就清晰很多。
我自己的做法是给团队定了一个模板:
技能名称:[动词]+[对象] 功能描述:[一句话说明这个技能做什么] 输入参数:[参数名: 类型, 说明] 输出结果:[返回什么] 边界条件:[什么情况下不应该用这个技能]这个模板看起来简单,但坚持用下来,技能库的可维护性会提升很多。
5.2 第二步:跑一次自动化的相似度分析
有了规范的描述之后,就可以做相似度分析了。具体操作上,我一般用下面这个流程:
from sentence_transformers import SentenceTransformer from sklearn.metrics.pairwise import cosine_similarity import numpy as np # 加载模型 model = SentenceTransformer('all-MiniLM-L6-v2') # 读取技能描述 skills = load_skills() # 返回 [{"name": ..., "description": ...}, ...] descriptions = [f"{s['name']}: {s['description']}" for s in skills] # 计算向量 embeddings = model.encode(descriptions) # 计算相似度矩阵 sim_matrix = cosine_similarity(embeddings) # 找出高相似度对 threshold = 0.85 high_sim_pairs = [] for i in range(len(skills)): for j in range(i+1, len(skills)): if sim_matrix[i][j] > threshold: high_sim_pairs.append({ "skill_a": skills[i]["name"], "skill_b": skills[j]["name"], "similarity": round(sim_matrix[i][j], 3) }) # 按相似度排序输出 high_sim_pairs.sort(key=lambda x: x["similarity"], reverse=True) for pair in high_sim_pairs: print(f"{pair['skill_a']} <-> {pair['skill_b']}: {pair['similarity']}")这段代码跑完,你会得到一份高相似度技能对的清单。接下来就是人工审核这些技能对,判断是合并、重定义还是保留。
5.3 第三步:设计合并策略时要考虑调用链路
合并技能不是简单地把两个技能的描述拼在一起。你需要考虑合并后的技能在调用链路上的影响。
举个例子:假设你有“查询订单状态”和“查询订单物流”两个技能,它们经常被先后调用。如果合并成“查询订单详情”,那么原来分两步的调用就变成了一步。这看起来是好事,但要注意:合并后的技能返回的信息更多,可能会占用更多的上下文空间。如果智能体后续只需要订单状态,不需要物流信息,那合并反而增加了不必要的上下文负担。
所以合并策略要根据实际调用模式来定。如果两个技能几乎总是同时被需要,合并是合理的;如果只是偶尔一起出现,保持独立可能更好。
5.4 第四步:用A/B测试验证精炼效果
精炼做完之后,一定要做验证。最直接的方法是用同一组测试任务,分别跑精炼前和精炼后的技能库,对比几个关键指标:
- 任务完成率:精炼后不能下降,最好能持平或略有提升。
- 平均调用步数:精炼后应该减少,因为技能更精炼了。
- 技能选择准确率:精炼后应该提升,因为候选空间变小了。
- 平均响应延迟:精炼后应该降低,因为上下文占用减少了。
如果精炼后任务完成率明显下降,说明裁剪过度了,需要回滚部分技能。如果各项指标都没有明显变化,说明精炼的力度不够,可以继续下一轮。
6. 精炼过程中容易踩的几个坑
6.1 过度合并导致技能粒度过粗
合并的诱惑很大,因为合并之后技能数量少了,看起来清爽。但合并过头会导致技能粒度过粗,智能体在执行任务时缺乏灵活性。
我踩过的一个坑是:把“发送邮件”“发送短信”“发送站内信”三个技能合并成了“发送通知”。结果智能体在执行时经常搞不清楚该用哪种通知方式,因为合并后的技能描述里虽然提到了三种方式,但没有明确的判断规则。后来不得不拆回去,加了一个“根据用户偏好选择通知方式”的前置判断技能。
教训是:合并的前提是两个技能在功能上确实可以统一,而不是仅仅因为它们看起来相似。如果两个技能的处理逻辑、输入输出格式、适用场景有明显差异,强行合并只会带来更多问题。
6.2 忽略技能之间的依赖关系
有些技能之间存在依赖关系,比如“取消订单”依赖于“查询订单状态”。如果你在精炼时删掉了被依赖的技能,依赖它的技能就会失效。
SkillBrew在处理这个问题时会构建一个技能依赖图,确保裁剪操作不会破坏依赖链。在实际操作中,你可以用简单的规则来检查:如果一个技能被其他技能的描述引用,或者在实际调用日志中经常作为前置步骤出现,那它就不应该被轻易裁剪。
6.3 精炼后忘记更新智能体的系统提示
技能库精炼之后,智能体的系统提示(System Prompt)里可能还残留着对旧技能的引用。这些残留的引用会误导智能体,让它尝试调用已经不存在的技能。
这个问题很隐蔽,因为系统提示通常很长,而且不会报错。智能体只是默默地选择一个替代技能,或者干脆放弃任务。我的做法是:每次精炼之后,用脚本扫描系统提示中提到的所有技能名称,和实际的技能库做一次比对,确保没有遗漏。
6.4 没有保留精炼历史导致无法回滚
精炼是一个迭代的过程,你可能需要多次调整才能找到合适的平衡点。如果没有保留每次精炼的历史记录,一旦发现问题就很难回滚到之前的状态。
建议每次精炼都做一次完整的技能库快照,包括技能列表、描述、依赖关系和系统提示。这样即使精炼出了问题,也能快速恢复到之前的状态。
7. 从SkillBrew延伸出来的几个工程实践思路
7.1 把技能库精炼做成定期任务
SkillBrew的思路不应该只在问题严重时才用,最好把它变成一个定期的维护任务。我的做法是每个月跑一次自动化的技能库分析,生成一份健康报告,包括技能数量变化、零调用技能列表、高相似度技能对、调用准确率趋势等。这份报告不需要很复杂,但能让你对技能库的状态心里有数。
7.2 用LLM来做技能描述的自动优化
SkillBrew的判定逻辑依赖技能描述的质量,而人工写描述既费时又容易不一致。一个可行的做法是用LLM来辅助优化技能描述:把原始描述输入LLM,让它按照统一的模板重写,同时检查描述中是否有模糊的表述。
这个做法我在自己的项目里试过,效果还不错。LLM重写后的描述在语义相似度计算上明显更稳定,因为表述风格统一了,减少了因为措辞差异导致的误判。
7.3 建立技能的生命周期管理机制
技能不应该一旦加入就永久存在。可以给每个技能定义一个生命周期状态:活跃(经常被调用)、观察(调用频率下降)、候选裁剪(长期零调用)、已裁剪。状态之间的转换可以基于调用日志自动触发,也可以人工干预。
这种机制的好处是让技能库的维护变得有章可循,而不是靠感觉来决定删不删。
7.4 精炼不是一次性的,而是持续的过程
最后想强调的是,技能库精炼不是做一次就完事了。随着业务变化和智能体能力提升,原来合理的技能划分可能变得不合理,原来必要的技能可能变得多余。精炼应该是一个持续的过程,而不是一次性的项目。
SkillBrew提供的多目标优化框架,本质上是一种系统化的精炼方法。你可以根据自己项目的规模和复杂度,选择完整落地这套框架,或者只借鉴其中的部分思路。关键是建立起“技能库需要定期做减法”的意识,并且在工程流程中留出精炼的空间。
我在实际项目中的体会是,一个经过精炼的技能库,不仅能让智能体跑得更稳,还能让团队的维护成本大幅降低。每次加新技能之前,先想一想是不是真的需要加,能不能通过调整现有技能来覆盖,这个习惯坚持下来,技能库的膨胀速度会慢很多。