1. Agent为什需要“Skills”,而不是一堆零散的工具函数
这两年“Agent”这个词快被说烂了,但真正跑过生产环境的人心里都清楚:一个Agent能不能干活,很多时候不取决于模型有多聪明,而取决于它手里有没有一套沉淀好的方法。我在agent-skills项目上折腾了差不多半年,最大的体会是——把Agent变强的最短路径,不是给它更多API权限,而是把“老手做这件事的完整过程”封装成它能直接照做的技能库。
1.1 没有Skills的Agent,为什么总在低级错误上反复打转
先看一个我们几乎都经历过的场景:你让Agent帮你调研某个行业,你煞费苦心地在提示词里写了“先找权威来源”“再交叉验证”“最后输出结论”,第一次它做得不错。第二次换了个话题,你又得重新写一遍。第三次你忘了写“输出中要标注信息来源”,它就真的开始满嘴跑火车。
这不是模型变笨了,而是你每次都在把工作经验临时灌输给它。Agent本身没有任何记忆,你的Prompt写得再细,关掉会话就归零了。更麻烦的是,一次对话里塞入的规则一旦超过某个量,模型会开始忽略细节,只挑它觉得重要的部分执行。
Skills解决的就是这个问题。它本质上是在Agent的工作目录里放一套“操作手册”:一个技能对应一类任务,手册里写清楚什么场景使用、按什么步骤执行、需要调用哪些脚本、输出长什么样。上次调好的流程,这次直接复用,不需要再教一遍。
我当时在agent-skills仓库里写的第一个技能是report_generator,作用很简单:把一堆杂乱的项目记录整理成结构化周报。这个技能放在很多Agent框架里看就是个Prompt模板,但我故意把它做得更厚——里面包含了数据清洗规则、周报格式模板、以及异常数据怎么处理的示例。跑了一个月之后,它生成的周报几乎不需要人改。
1.2 Skill、Function Calling、Plugin到底怎么区分
不少朋友问我,Skills和Function Calling有什么区别,和Plugin又有什么关系。我一般用这张表来回答:
| 维度 | Skill | Function Calling | Plugin |
|---|---|---|---|
| 核心载体 | 流程化的过程知识 | 单个函数接口 | 外部系统集成包 |
| 解决的问题 | 让Agent知道“怎么做” | 让Agent知道“能调什么” | 让Agent具备对接“外部服务”的能力 |
| 是否包含逻辑 | 包含多步推理和执行规则 | 通常只有一个原子操作 | 包含API对接、鉴权、数据转换 |
| 典型表现 | 一份SKILL.md加若干脚本 | 函数名加参数schema | 独立模块,接入后成为Agent的能力扩展 |
它们不是替代关系。一个成熟的技能库里,一个Skill通常会调用好几个Function,也可能依赖某个Plugin去拉外部数据。区别在于粒度:Function是“手”,Skill是“操作说明书”,Plugin是“工具箱里的专用设备”。
1.3 agent-skills项目的核心思路
我做agent-skills的时候给自己定了一个调:与其做一个全能的Agent,不如做一批足够好用的Skills。项目的目录大概是这样的:
agent-skills/ ├── README.md ├── skills/ │ ├── report_generator/ │ │ ├── SKILL.md │ │ ├── scripts/ │ │ └── templates/ │ ├── research_task/ │ │ ├── SKILL.md │ │ ├── scripts/ │ │ └── examples/ │ └── ...核心思路只有一句话:把个人经验转成团队可复用的资产。每个技能目录都自包含,可以单独测试、单独发布、单独回滚。别人拿过去不需要理解你的原始想法,只需要读一遍SKILL.md就能用起来。
2. Skills的目录结构与内部组织:把一项能力拆成最小可复用单元
很多开源项目里的Skill就是一片Markdown,看起来方便,真跑起来会发现缺东西。我建议一个可用的Skill至少包含三部分:能力声明(SKILL.md)、执行工具(scripts)、参考素材(templates/examples)。
2.1 SKILL.md是给Agent看的说明书,不是给人看的文档
我踩过最大的坑,就是按照“写给人看的技术文档”标准去写SKILL.md,结果Agent读起来效率极低。后来我总结了一套更适合Agent解析的结构,大概是这样的:
--- name: research_task description: 当用户需要调研某个主题、行业、竞品或技术方向时使用此技能。 - 输入: topic(主题)、depth(深度)、lang(输出语言) - 输出: 结构化调研报告(Markdown格式) --- ## When to Use 用户提出“调研、研究、分析、了解一下、对比一下”等关键词时,通常需要本技能。 ## Process 1. 信息收集:利用search_fetch工具获取至少10个不同来源的页面。 2. 信息筛选:按权威性、时效性、相关性三档打分,保留分数不低于7分的资料。 3. 交叉验证:同一事实必须在至少两个独立来源中同时出现,否则标记为“单一来源信息”。 4. 结果输出:按模板生成报告,包含摘要、核心发现、数据表格、来源列表。 ## Dependencies - search_fetch: 必需 - fetch_webpage: 必需 - extract_pdf: 可选 ## Constraints - 不得将任何单一来源信息表述为“事实”。 - 输出语言必须与lang参数一致。这种写法Agent读起来效率很高,因为每个段落的边界非常清晰。关键是description字段要写“触发场景”,而不是“功能定义”。举个例子:“当用户需要调研某个主题时使用”比“执行调研任务”更容易被Agent命中。
2.2 配属脚本与模板:让技能不止是“嘴上说说”
纯文本的Skill能教会Agent流程,但教不了它“动手”。我会给大部分技能配上至少一个脚本,哪怕脚本很小。比如research_task技能里放了一个filter_sources.py,作用是对收集到的来源列表做初筛,剔除明显不相关的URL。这样Agent就不用每跑一次都让模型自己判断一遍来源质量。
脚本放在技能目录里有几个实在的好处:
- 依赖可以声明在技能内部,避免全局安装一堆库。
- 单个技能可以被单独单元测试,不污染其他技能。
- 给脚本加注释就是最低成本的技能文档。
templates目录我一般放输出模板。report_generator技能里有一个weekly_report_template.md,里面预置了表格和段落的骨架,Agent只需要往里面填内容。比起让模型自由发挥,模板能显著提升输出的一致性。
2.3 命名与描述的艺术
技能命名要面向“能力”,不要面向“接口”。我见过有人把技能命名为github_api_wrapper,这就是典型的面向接口命名。换成release_packager或者project_syncer,Agent在应对“帮我整理发布包”这类请求时,命中率会明显更高。
描述里的触发词要覆盖自然语言的多种表达方式。比如调研技能,我会把“调研、研究、分析、了解一下、对比一下、查一查”全部写进描述里。Agent做行动决策时,很多时候就是靠description和用户请求的语义匹配来选技能。这段描述写不好,再好的技能也会被晾在一边。
3. 我如何从零封装一个可用Skill:以调研型任务为例
空谈理论没意思,我直接拿research_task这个技能来走一遍完整封装流程。这也是agent-skills里被复用次数最多的一个技能。
3.1 选定场景,先定义输入输出
没有明确输入输出的技能,就是耍流氓。我当时是先写了一份内部约定,把技能边界画清楚了:
| 输入字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| topic | string | 是 | 调研主题,一句话说清楚 |
| depth | string | 否 | 可选值:summary/detail/deep,默认detail |
| lang | string | 否 | 输出语言,默认zh |
| max_sources | int | 否 | 最大来源数量,默认10 |
| context | text | 否 | 附加背景信息,例如调研目的 |
输出则固定为Markdown报告,分五个段落:执行摘要、核心发现、数据与证据、风险与局限性、参考来源列表。每段都有明确要求,比如“风险与局限性”必须有,哪怕内容是“本次调研未发现重大风险”。
把输入输出定成这样,最大的价值是Agent知道自己“干完活”的标准是什么。很多Agent跑偏,就是因为任务完成的标准没定义清楚。
3.2 把“老手怎么做”写进过程框架
定义完输入输出,接下来是最难的一步:把老手调研时脑子里走的流程,显式地写进技能里。
我拆解了自己做调研的动作,总结出四步:
信息收集。这里有个关键规则:先用尽量宽的搜索词拿回大量候选,宁多勿缺。很多Agent调研质量差,是因为第一步就只搜索了用户给的那个关键词,漏掉了同义词、相关概念和上下游信息。
信息筛选。不是所有搜索结果的得分都一样。我给每条来源做三档评分:权威性(是否来自机构官网、学术数据库、行业头部媒体)、时效性(是否近三年内)、相关性(是否直接命中主题)。三项评分相乘,低于阈值进不去待用池。
交叉验证。同一个数据点至少要找到两个独立来源才能写进“核心发现”。只出现一次的信息,单独放进“单一来源信息”一节。
结果组织。按模板输出,不要自己发明结构。
这套流程看起来平平无奇,但它解决了Agent最让人头疼的“一本正经编数据”问题。交叉验证那一步,直接砍掉了大部分幻觉输出。
3.3 本地验证的三板斧
技能写完之后不能直接上生产,我在本地会做三轮验证:
第一轮:最小样例。用一个很小的topic跑一遍完整流程,比如“什么是Agent Skills”。重点看过程是否卡住、输出是否完整、有没有越界动作。
第二轮:边界输入。把topic设成一个极其模糊的词,比如“效率”,看看Agent会不会不知所措;再把topic设成一个极其具体的词,比如“React 19 服务器组件在Next.js 15中的水合错误率”,看看技能能否兜住深度需求。
第三轮:资源消耗记录。每次跑完,统计token消耗和耗时。我给自己定了一条线:单个调研任务全流程的token消耗不能超过一定范围,如果超了,说明检索步骤可能循环太多次,需要给脚本加阈值。
这个验证流程完全可以照抄,不管你是用现成框架还是自己写调度器,跑一遍花不了多少时间,但能省下后面调试的无数个小时。
4. 装载与调度:Agent运行时如何发现并调用Skill
技能封装好了,下一个问题是怎么让Agent“知道”有这些技能,并在合适的时机把它们调用起来。
4.1 显式调用和自动调度怎么选
我在项目里同时支持两种调用模式。
显式调用适合任务边界清晰的场景。用户输入“运行周报技能”,Agent直接加载report_generator,不需要做任何决策。这种模式可靠性最高,适合企业内部的固定流程。
自动调度适合开放式场景。Agent收到“帮我分析一下上周的数据情况”这种模糊请求后,自己从技能库里匹配最合适的技能,然后加载执行。这种模式灵活,但决策错误的风险也高。
两手准备的原因是:完全依赖自动调度,在技能数量多了之后一定会出现误选;完全依赖显式调用,又等于让用户背技能清单。最终我采取的策略是:关键任务优先显式调用,辅助任务允许自动调度。
4.2 技能描述、优先级与冲突消解
当技能库里的技能超过十个,自动调度一定会碰到“多个技能看起来都合适”的情况。我靠三个机制解决:
- 触发分数。我给每个技能的描述里隐式标定了触发场景。agent-skills里的调度器会计算技能描述和用户请求的语义相似度,超过阈值才进入候选池,并且会输出一个置信度分数。
- 技能优先级。候选池里的技能按优先级排序。比如report_generator和data_analyzer都可能处理周报任务,但report_generator优先级更高,因为它是专门做格式化的。
- 仲裁规则。两个技能置信度都很高时,采用“更具体的技能获胜”原则。data_analyzer是泛指,report_generator是特指,那么特指的胜出。
这套机制不复杂,但确实把误选率降了不少。
4.3 多技能协同时的上下文管理
实际业务里,很少有一个技能从头干到尾的情况。更多时候是:调研技能先产出资料,分析师技能再对资料做解读,最后周报技能把解读结构化成报告。
技能一旦串联,上下文管理就成了大问题。每个技能都会向对话里注入自己的指令文本、脚本输出和中间结论,累积起来会撑爆上下文窗口。
我的做法是三个原则:
- 技能输出必须带摘要。每个技能结束时,生成一个不超过300字的“结果摘要”,后续技能只读摘要和必要数据文件,不读完整日志。
- 技能上下文尽量自包含。技能A如果依赖技能B的输出,那么技能B应该把结果落成一个中间文件,技能A去读文件,而不是从对话历史里找。
- 中间结果隔离。Agent的工作目录里按任务ID建子目录,每个技能产生的临时文件都存在对应子目录里,避免互相覆盖。
这三条原则让多技能协同从“一团乱麻”变成了“流水线作业”。
5. 实测中最容易翻车的几个问题
即使结构和调度都搭建好了,真正跑起来还是会遇到各种奇怪问题。我把最典型的三个放在这里,这些坑在官方文档里基本找不到。
5.1 上下文污染:技能“好心办坏事”
我在2.1节提到SKILL.md要写得边界清晰,是因为我吃过一次很大的亏。一开始我的skills包内容写得特别丰富,把各种背景知识、最佳实践、示例都写了进去,一个SKILL.md能有一两千行。
结果就是,Agent每次加载这个技能,都要把这一两千行塞进上下文。看起来是懂了很多,实际上模型被大量指令干扰,反而执行不好主任务。有一回,调研技能加载后,模型居然花了不少精力去遵循文档里“保持好奇心”这种泛泛的要求,却把“交叉验证信息”这个核心步骤给省略了。
解决方案是“分层压缩”:SKILL.md只保留必须的规则和流程,背景知识挪到docs/目录,只有在Agent需要时才会读取。主说明文件控制在150行以内,让模型在一屏之内能把握全貌。
5.2 技能之间互相打架,Agent陷入死循环
技能多了之后,会出现一种诡异的失败模式:Agent在调研技能里发现需要整理信息来源,于是调用了格式化技能;格式化技能又认为自己需要先分析数据,于是调用了分析技能;分析技能又觉得应该先搜索更多材料,于是又调回了调研技能。
整个Agent陷入了一个循环,白白消耗大量token,最后输出一个没有结论的报告。
我给的解法是给每个技能声明“允许调用边界”。SKILL.md里的Constraints段落不仅写“不能做什么”,还要写“不能调用哪些技能”。同时,我在调度器里加了一个硬性规定:技能嵌套深度不能超过一层。也就是说,技能A可以调用技能B,但技能B内部不允许再调用技能C,需要更复杂的能力时,必须回到主循环里重新规划。
这个限制一开始觉得很死板,但实际跑下来反而稳定很多。Agent又不是分布式系统,没必要让技能无限递归下去。
5.3 过度设计:技能库从助力变成负担
做agent-skills的第三个月,我的技能数量膨胀到了30多个。看起来是好事,实际上每个技能都要维护、测试、更新描述。更糟的是,技能一多,自动调度器开始频繁误选。
有一次用户说“帮我整理一下PDF里的合同条款”,调度器居然匹配到了表单提取技能,因为那个技能描述里写了“抽取文本”。这种错误说实话有点蠢,但责任在我——我不该给还没用熟的能力建立那么多入口。
后来我定了一条铁律:一个技能如果在四周时间内没有被任何Agent成功调用过,就摘掉它的自动调度入口,降级为普通文档存档。技能数量从30多个压回15个左右,误选率立刻下降了一大截。
6. 面向团队与项目落地的进阶思考
如果你只是自己玩,前面五章的内容已经够用了。但如果要把技能库放进团队项目里,还有几件容易被忽略的事。
6.1 技能版本的治理:像管理代码一样管理技能
技能也是代码的一种,只不过它的“运行环境”是Agent。我在agent-skills项目里把每个技能都纳入Git管理,并且规定:任何改动必须走MR流程,同时更新CHANGELOG.md。
每个技能目录下有一个轻量的metadata.json:
{ "name": "research_task", "version": "1.4.2", "last_updated": "2025-06-18", "changes": [ "增加单一来源信息标记规则", "修复了深度调研时搜索循环次数过多的问题" ] }靠这个文件,我能快速定位“上周还能用,这周突然表现变差”是不是某个技能版本更新导致的。
6.2 从使用日志中反推技能改进
技能好不好用,不能只靠感觉。我给调度器加了一个很基础的日志模块,每回Agent调用技能都会记录一组信息:调用时间、触发的用户请求、命中的技能名、是否完成、是否中途失败、如果失败是哪一步失败。
| 字段 | 示例 |
|---|---|
| timestamp | 2025-06-18 14:32:11 |
| request | 帮我分析最近30天的销售数据异常 |
| skill | data_analyzer |
| status | completed |
| fail_step | null |
每两周我会拉一遍日志,把失败率最高的技能捞出来看原因。绝大多数失败都集中在三个原因:技能描述与用户请求不匹配、技能流程中某一步需要的外部资源不可用、输出模板和实际场景对不上。这些信息比任何抽象讨论都有用。
6.3 让技能库保持“脏乱但可用”的实践经验
最后一个建议可能跟很多人的直觉相反:不要太早追求技能库的规范化。我见过有人花了三周时间设计技能Schema、写单元测试、做自动化校验,结果一个实际能跑的技能都没做出来。
对我来说,先让技能库里有一批“能用但丑”的技能,比有一套漂亮但空转的框架重要得多。前期的脏乱是可以接受的,因为只有在真实使用中,你才知道哪些字段是必要的,哪些规则是多余的。等到技能数量超过15个、开始影响调度准确率时,再花时间做规范和演进也不迟。
我个人现在维护agent-skills的方式其实挺朴素:保持每个技能能独立工作,保证SKILL.md是最新的,然后让真实的调用数据告诉我下一步该优化什么。这个方向并不性感,但它就是让Agent真正“好用”的那条路。