如果你最近在折腾智能体,八成会撞上agent-skills这个说法。我第一次听到这个词的时候,第一反应是不就是给Agent挂几个工具嘛。但等我真把一个混杂了数据查询、报表生成、通知推送的业务塞给一个Agent去跑,才发现事情的复杂程度远远超过“挂工具”。这篇文章不打算罗列某个平台的功能清单,我想从一个实际重构项目的角度,聊聊技能(Skill)这一层应该怎么设计、怎么编排、怎么评测,以及怎么在团队里长期维护。适合正在搭Agent、被乱成一锅粥的prompt搞到头大的人。
1. 为什么agent-skills突然成了刚需:单体Agent的痛点与技能化改造
1.1 单体Agent的常见翻车现场
先说我那个经营数据问答Agent吧。最初版本非常天真,把所有东西塞在一个system prompt里:SQL模板、统计口径、周报月报规则、环比同比公式、甚至还有语气指南。上线第一天还行,第三天运营同事问“各渠道的续费率为什么和昨天的邮件对不上”,它就把周口径和月口径混在一起,给了个完全失真的答案。我查日志时发现,模型在同一轮回答里既执行了“优先使用最新日活数据”,又执行了“统计周期按自然周对齐”,两条规则互相打架,没有任何机制告诉它谁先谁后。
这种戏码我后来看了太多次。单体Agent的问题在于把“知道什么”和“按什么顺序做事”混在一起。prompt越长,模型越容易顾此失彼;一旦某一段指令语焉不详,它就会用猜的。更麻烦的是,业务方改一个报表口径,你要在几千词的prompt里海底捞针,改完还可能影响其他规则。这就像把所有员工手册装进一本厚书,让新员工自己找章节,出问题根本不奇怪。
1.2 技能(Skill)不是工具(Tool):两者的边界与取舍
很多人会把Skill和Tool当成一回事,这是agent-skills里最容易犯的概念错误。Tool是模型可以直接调用的函数或接口,比如“执行SQL”“发一封邮件”“调用搜索API”,它回答的是“能做什么”。Skill则是围绕某个领域问题打包好的完整能力,里面可以包含提示词说明、脚本、校验规则、示例数据,甚至内嵌对多个Tool的调用顺序。Skill回答的是“在什么场景下、按什么步骤做、做完怎么校验”。
我在项目里用过一张对比表,也许对你有用:
| 对比项 | Tool | Skill |
|---|---|---|
| 抽象层次 | 单次操作 | 完整任务流程 |
| 核心内容 | 输入输出接口 | 说明+脚本+规则+示例 |
| 触发方式 | 模型按需调用 | 模型判断后整体加载 |
| 失败处理 | 调用方处理 | 技能内部自带兜底 |
| 复用范围 | 代码级复用 | 连同领域知识一起复用 |
没有Tool的Skill是空壳,没有Skill的Tool是散沙。举个例子,一个“PDF报表解析”技能,内部可能调用文件读取Tool、OCR Tool、表格提取Tool;技能要做的是决定先用谁、出错后是重试换方案还是直接报错,并且把最终结果翻译成结构化JSON。单纯把Tool抛给Agent,等于给一个新人发了一堆零件,却没给装配图纸。最近agent-skills这个说法频繁出现,我觉得本质上是大家意识到:堆工具最大的瓶颈不是模型会不会调用,而是缺少一层组织知识的结构。
1.3 技能化改造后的运行逻辑
改造之后,整个Agent的运行逻辑会变成这样:用户请求进来,主流程先做意图识别,把请求分流到对应的技能域;每个技能被激活时再读取自己的SKILL.md和相关脚本;技能执行完,只回传结构化结果和高亮提示,主流程负责汇总和答复。比起过去一次加载全部知识,现在主流程只需要维护一份非常精简的调度说明,大部分领域知识被放到技能包内部按需加载。
我之前那个报表Agent,改造前system prompt大概有接近一万字,改造后主提示词只保留了意图分流和边界规则,大概缩到原来的十分之一。执行同样的查询,响应不仅更稳,而且每条规则坏了只改对应技能,不会再牵连其他地方。如果你正在做一个垂直场景Agent,我建议先把所有塞在prompt里的“怎么做”剥离出来,逐个变成技能包,运行逻辑会一下子清爽很多。
2. 技能包的最小结构:SKILL.md写得好,Agent才叫得动
2.1 SKILL.md里到底该写什么
围绕agent-skills的工程实践,大家逐渐形成了一种共识:每个技能包的核心是一份Markdown说明文件,主流命名是SKILL.md。它既不是给人看的产品文档,也不是给模型看的聊天话术,而是用来指导模型按特定方式完成任务的执行手册。很多人的误区是在里面写“本技能是一个强大的、先进的……”,这类形容词对模型毫无帮助,模型真正需要的是四个信息:这个技能解决什么问题、什么时候该用它、按什么顺序做、有哪些红线绝对不能碰。
我会把SKILL.md分成四段:用途、触发条件、执行步骤、注意事项。其中触发条件是最容易被忽略的。你要把“当用户请求中出现XX关键词且需要YY结果时使用”写清楚,而不是“当用户需要数据分析时使用”。指示越模糊,模型越容易跨技能乱选。
2.2 一个可以抄的技能模板
下面是一个我目前还在用的模板,你可以直接抄。我自己习惯在文件最前面加一段YAML front matter,用来登记技能名、版本、依赖,以及给模型看的description。
--- name: report-comparison description: 当用户要求对比两个周期的经营数据时使用,例如环比、同比、日报对比。 version: 1.2.0 dependencies: - pandas>=2.0 - matplotlib --- # 报表对比技能 ## 输入 - 基准日期或周期 - 对比日期或周期 - 指标名称列表 ## 执行步骤 1. 先解析出两个周期,确认统计口径。 2. 调用 scripts/compare.py 计算差异。 3. 将输出 JSON 展示成表格或文字结论。 4. 如果任一周期缺数据,返回缺数据原因,不要继续算。 ## 注意事项 - 周期必须对齐,不允许把7天环比率和30天环比率直接对比。 - 所有中间结果写入临时目录,不要写进最终回复。这套模板看起来简单,但每一节都有讲究。比如“执行步骤”必须写成模型可操作的序列,“步骤2调用scripts/compare.py”这种写法比“计算差异”可靠得多,因为它把模型从怎么实现里解放出来,只负责传参和判断结果。再比如“注意事项”要写成否定式规则,模型对“不要做X”的遵循程度往往高于“请谨慎处理X”。
2.3 代码资产与依赖管理
SKILL.md只是皮,真正干活的是目录里的脚本和资源。我见过很多技能包只有一个Markdown文件,执行起来还是要靠模型现场写代码,这样的技能非常不稳定。建议按这个结构组织:
skills/report-comparison/ ├── SKILL.md ├── scripts/ │ └── compare.py ├── assets/ │ └── 口径说明.json └── requirements.txt几个经验:第一,脚本优先写成命令行工具,参数从命令行传入,而不是做成函数库。因为Agent最擅长的是“运行一个命令然后把输出读出来”,让它在代码里找函数签名,出错概率高得多。第二,参数校验要做全。模型生成的日期格式五花八门,脚本里不校验,后面全盘崩溃。第三,依赖版本一定要锁定,别用pandas>=2.0这种范围依赖,部署环境一变就跑不起来。第四,不要在技能包内写死服务器路径或账号口令,一律通过环境变量注入。这一条既是安全问题,也是复用问题。
3. 多技能场景下的编排与上下文管理:技能多了才是考验
3.1 技能触发与选择:让模型自己判断还是由流程控制
技能少的时候,让模型自己选基本没问题。技能一多,尤其是有两三个技能描述相近的时候,选择就成了重灾区。我在项目里试过三种方案,说下实测感受。
- 纯模型自主选择。把每个技能的description暴露给调度模型,让它自己挑。优点是实现简单、扩展方便,缺点是当用户需求一句话里包含多个意图时,模型经常只挑一个技能,或者选错。
- 固定路由表。先用文本分类模型或规则把请求分到业务域,再在域内加载技能。优点是不靠猜,缺点是规则要维护,遇到新场景得先定新分类,灵活性差一点。
- 混合模式(目前推荐)。先做一层轻量意图判定:高置信度走固定路由,低置信度交给调度模型自主选择。相当于把简单场景自动化,把模糊场景留给模型去兜底。
我的体会是,技能触发的稳定性,百分之八十要靠description写得好。把description当作搜索引擎的查询词来写:要包含该技能独有的关键词,也要写明排除场景。比如“仅当用户明确提到环比或同比时才使用,单纯问汇总时不要使用”。这句话看着废话,实际能挡掉大量误触发。
3.2 上下文隔离与传递:避免技能互相污染
多技能Agent最常见的问题是上下文污染。技能A算完的中间表被当成事实传给了技能B,技能B又基于脏数据输出结论,最后排查起来简直要命。我的策略是给每个技能一个独立工作目录,中间文件只放在这个目录里;技能结束时只向主流程回传一份结构化结果摘要,其他日志和临时数据一律留在原处。
这里可以打个比方:主流程的上下文像公司的会议室,各技能是不同项目组。项目组在自己工位讨论(独立临时目录),开会时只带结论PPT进会议室(结构化摘要)。如果所有人把草稿纸都摊在会议桌上,这个会开到后面基本没法看清主线。
具体操作上,我要求所有技能返回结果统一成JSON:{"data": ..., "warnings": [...], "debug": {...}},主流程只读data和warnings,debug只在需要排查时单独拉取。这样即便技能内部再乱,也不会把噪声带到上层。另外,如果技能A的输出要喂给技能B,不要直接引用A的原始输出对象,通过明确路径传递文件或通过主流程重新加载结果,能避免很多隐性关联。
3.3 冲突处理与优先级:实测中的坑
两个技能描述相似时,模型会发挥不稳定。我遇到过“订单异常检测”和“流失预警”两个技能,某些数据特征同时命中双方触发词,模型一会儿选A一会儿选B。后来我在各自SKILL.md里加了“互斥声明”:A写“本技能仅处理支付失败或物流异常,不做用户流失判断”,B写“本技能不要替代订单异常检测”。即便如此,偶尔还是会串,这时只能在主流程加优先级。
我的做法是维护一个优先级列表,低优先级技能只有在高优先级技能未命中时才允许执行。举个粗糙的例子:
SKILL_PRIORITY = ["holiday-calendar", "report-comparison", "anomaly-detection"] def route(request): for skill_id in SKILL_PRIORITY: if matches(request, skill_ids[skill_id]): return skill_id return None这段代码没有炫技成分,但它强制把“谁先谁后”从模型自由发挥变成显式规则,能解决大部分冲突问题。再补一个循环依赖检测:两个技能互相等待对方结果时,设定最多两轮重试,超过就中止并给出错误提示。不要指望模型自己发现自己在死循环。
4. 技能评测:光靠感觉调prompt,迟早要吃大亏
4.1 评测集怎么建
技能包改起来很容易,但怎么证明改完更好了?我在这个项目上吃过亏,所以现在坚持先建评测集再动手改。评测集不需要很大,二十到五十条足够,但要覆盖三类场景。第一条典型成功路径:正常业务问题,预期能选对技能并给出格式正确的答案。第二条边界输入:缺日期、日期倒置、单位不统一、请求同时涉及多个技能,这类用例才是技能质量的照妖镜。第三条失败恢复:脚本异常、数据缺失,模型应该主动说明而不是硬编一个结论。
每条用例至少要配两个检查点:技能选择是否正确,最终输出是否满足预期结构/关键数值。我建议把评测用例用一个简单的表格管理:
| 编号 | 输入 | 预期技能 | 预期结果检查点 |
|---|---|---|---|
| rc-01 | 对比上周和这周的营收 | report-comparison | 包含两个周期的数值与环比 |
| rc-02 | 本周一数据缺失,对比周一和周二 | report-comparison | 提示缺失,不输出周二对比结论 |
| rc-03 | 用户只说“最近怎么样” | 不触发report-comparison | 走通用问答或总览技能 |
配表时别只看答案,还要看模型有没有“多走一步”。比如明明只要求对比,模型却自己算了个预测,这属于越权,在我的评测里照样判失败。
4.2 三个关键指标
我自己常用三个指标。
第一个是技能选择准确率。定义:评测集里模型实际选择/命中的技能与预期技能一致的比例。它衡量的是description写得好不好,以及路由规则是否清晰。
第二个是任务成功率。定义:最终输出通过全部检查点的比例。它衡量整个技能包的质量,包括脚本健壮性和步骤描述准确度。
第三个是上下文污染率。定义:在最终回复里检测到底层中间变量、临时文件名或无关日志的次数占总请求数的比例。这个指标很多人不看,但对Agent很要命,因为它直接反映技能隔离做得好不好。
怎么算不复杂。跑一遍评测集,把每次结果按三个维度打标,然后数数占比。我自己的及格线是:选择准确率90%以上、任务成功率85%以上、污染率低于2%。没到线的,先别上线。
4.3 失败样本分析与回归
比指标更重要的是失败样本。我会给每个技能维护一份失败记录,格式就四列:模型版本、技能版本、输入、失败原因。运行一段时间后拉出来看,原因通常集中在几类:意图误判、技能描述歧义、脚本异常、口径更新没同步。每一类对应不同的修法:
- 意图误判:说明路由层或description的边界不够清晰,加排除词。
- 描述歧义:说明两个技能触发词重叠,梳理优先级或加互斥声明。
- 脚本异常:说明技能包自身质量问题,补参数校验和异常分支。
- 口径更新没同步:说明知识维护流程缺失,要改SKILL.md里的口径说明。
另外,每次改动技能描述或脚本,都要把历史失败用例重新跑一遍,防止修一个坑又挖一个坑。我是把评测脚本做成命令行工具,比如:
python -m tests.run_regression --skills report-comparison跑完后自动输出上一版对比。没有这个回归习惯,技能版本越叠越多,最后谁也不敢动。
5. 工程化落地:目录规范、版本管理与团队协作
5.1 先把目录结构定下来,后面少吵架
当技能从一两个变成十几个,目录结构就会成为团队协作的第一道坎。我建议在仓库里专门开辟一个skills目录,每个技能一个子目录,子目录名和SKILL.md里的name保持一致。整体结构大概长这样:
agent-skills/ ├── skills/ │ ├── report-comparison/ │ │ ├── SKILL.md │ │ ├── CHANGELOG.md │ │ ├── scripts/ │ │ └── assets/ │ └── order-anomaly/ │ └── SKILL.md ├── tests/ │ ├── cases/ │ └── run_regression.py └── shared/ └── agent_utils.py这里有两个细节容易被忽略。一是skills目录下不要放公用的工具代码,要抽到shared里;技能之间如果需要共享函数,通过shared导入,否则改一个技能会影响另一个。二是测试用例按技能划分目录,而不是按时间或人员划分,这样每次回归才能精准框定范围。
5.2 版本管理与变更记录
技能描述和脚本都处于高频迭代状态,没有版本管理,两周后你根本说不清线上跑的是哪一版。我会给每个技能维护一个CHANGELOG.md,同时用Git标签标版本。版本号建议遵循语义化:大版本表示技能行为或口径有Breaking Change;小版本表示新增了触发条件或脚本功能,向后兼容;补丁版本表示改错别字、补充示例这类小调整。
SKILL.md front matter里的version要跟Git标签保持一致,不要出现仓库打了v2.0.0,SKILL.md里还写着1.2.0的情况。变更记录只需要记三件事:改动原因、改动内容、影响范围。举个例子:
| 版本 | 日期 | 改动 | 影响范围 |
|---|---|---|---|
| 1.3.0 | 2025-11-02 | 支持自然周和自然月对比 | 触发条件、输入解析 |
| 1.2.1 | 2025-10-20 | 修正缺失数据的提示文案 | 无行为变化 |
很多团队的Agent技能没有版本概念,线上表现异常时根本没法回滚。加一个版本字段的成本很低,收益却很大。
5.3 团队协作:技能评审要当成Code Review一样审
技能包本质上是一份会被模型反复阅读和执行的代码,它必须走代码评审流程。我列了一份PR检查单,供你拷贝:
- 描述是否使用了具体触发词,有没有模糊的形容词。
- 执行步骤是否可操作,模型能否不猜就能跑。
- 脚本是否有参数校验和异常处理。
- 依赖版本是否锁定。
- 是否补充了对应的评测用例。
- 是否更新了CHANGELOG和版本号。
另外,我自己有一个团队约定:先写SKILL.md,再写脚本。理由很简单,如果一个人连技能的目标、触发条件和步骤都写不清楚,直接写脚本大概率也是拍脑袋。让新同学先从写SKILL.md开始,技能评审时也先评审说明文档,脚本只是对说明的实现。这个方法帮我们把很多问题挡在编码之前。
关于多语言,如果你的业务方或模型会在中英文混杂的请求下运行,建议SKILL.md里的触发条件同时写明中英文关键词,避免模型因为语言切换找不到技能。
最后分享一个我现在的习惯:每次新建技能,先在tests/cases里写三条用例,再写SKILL.md,最后写脚本。这个顺序看起来很反直觉,但我试下来确实能挡掉很多无效开发。三条用例就像锚一样,让技能目标一直清晰。如果你也在做agent-skills,不妨从一个小技能开始,先把结构跑顺,再往里面堆复杂度。技能库这东西,前期多花点心思,后期能省掉大把和模型斗智斗勇的时间。