说实话,第一次在调试记录里看到agent-skills这个命名的时候,我愣了一下。等我把手头那个频繁"精神分裂"的Agent项目拆开重做之后,才反应过来:真正卡住我们大半年的,根本不是模型能力不够,而是我们从来没给Agent建立过一套正经的"技能管理体系"。
这篇内容不聊大模型原理,也不搬论文。纯粹从一个写代码的人视角,聊聊我围绕agent-skills这个思路,把原来一堆散装Prompt整理成结构化技能文件之后,系统行为发生了哪些肉眼可见的变化,踩了哪些坑,以及现在回看哪些设计是最关键的。
1. 为什么我把注意力从"堆Prompt"转向了Skills体系
1.1 散装Prompt最大的问题是"不可预期"
早先做Agent,我的习惯是这样的:把任务背景、角色设定、工具清单、注意事项全部塞进一个超长的System Prompt里。需求一多就接着往后面追加,最后那个Prompt文件比我毕业论文还长,改一个业务细节要全局搜一遍关键词,生怕动了哪句话导致别的场景崩掉。
但真正让我破防的不是维护成本,而是行为不可控。同样一个Agent,上午跑流程还很规矩,下午换了模型版本之后开始自作主张跳过步骤;明明只让它查天气,它把上下文里所有历史对话当成背景信息,写了一大段"综合研判报告"。这类问题的根源是什么?是我们的指令全部混在一坨文本里,模型根本分不清"什么时候该做什么"。
agent-skills这种设计思路解决的就是这个问题:不是给Agent写一篇"全能说明书",而是把它的能力拆成一个一个独立封装的技能文件,每个技能只负责一件具体的事,包含触发条件、执行步骤、输出格式、边界限制。Agent在某个节点该调用哪个技能,由路由逻辑决定,而不是让模型在一团乱麻里自己猜。
1.2 技能文件和Prompt的本质区别
很多人觉得这不就是把Prompt拆成几个文件嘛,本质上没区别。最开始我也这么想,等实际做完对比实验,才发现差得远。
一张表格就能说明白:
| 维度 | 传统长Prompt | 技能文件(Skills) |
|---|---|---|
| 触发机制 | 全靠模型在上下文里自行判断 | 显式的技能描述+路由逻辑 |
| 内容组织 | 所有能力平铺混排 | 按职责拆封,每个文件独立 |
| 可测试性 | 改一句影响全局,难以定位 | 单技能独立验证,回归方便 |
| 复用性 | 换个项目基本重写 | 跨项目复制,改参数即用 |
| 上下文占用 | 超长Prompt持续占据token | 按需加载,不用的技能不进上下文 |
| 行为一致性 | 依赖模型当时的"心情" | 尽量把不确定性约束在技能内部 |
关键差异在最后一条:agent-skills的核心逻辑是"把能力外置",Agent本身只做规划和路由,具体怎么执行一件事,交给确定的技能文件来约束。这相当于你把一个实习生的工作手册从一段模糊的口头交代,变成了一套标准作业程序。模型漂移的影响被限制在"理解任务并选对技能"这个层面,而不是"在3000字指令里找出对的那一句"。
2. Skills的物理形态:从SKILL.md到技能目录的整体设计
2.1 一个技能文件该长什么样
这个体系落地的时候,我参考了社区里比较常见的做法,按"一个目录代表一个技能"的规范来组织。每个技能目录里必须有一个技能定义文件(一般习惯命名为SKILL.md),其余根据需求放示例脚本、参考文档、模板文件。整体结构长这样:
skills/ ├── meeting-minutes/ # 会议纪要技能 │ ├── SKILL.md # 技能定义(必选) │ ├── templates/ │ │ └── minutes.md # 输出模板 │ └── examples/ │ └── example_input.md ├── code-review/ # 代码审查技能 │ ├── SKILL.md │ └── checklists/ │ └── python.md └──>--- name: meeting-minutes description: 用于将会议录音转写文本整理为结构化会议纪要,输出包含结论、分歧点、待办事项。 when_to_use: 当输入包含会议参与人对话记录,且需要产出会议纪要文档时使用。 ---注意when_to_use这个字段。我一开始没写它,结果Agent在用户单纯描述"今天开会讨论了XX"的时候也会触发会议纪要技能,生成了大量无用输出。后来我把"什么场景下该用"和"什么场景下不该用"写清楚,误触发率立刻降下来了。
2.2 技能内容的分段设计逻辑
SKILL.md元信息下面就是正文。这一部分不要写成长篇大论,而是写清楚"做这件事的步骤和标准"。我现在固定用三段式:
- 执行步骤:用有序列表写明从输入到输出的处理阶段,每个阶段一句话即可,避免让模型自由发挥。
- 输出规范:明确最终产物的格式,比如"使用Markdown表格列出待办事项""每条结论必须注明对应发言人"。
- 边界与禁区:写清楚哪些动作不允许做,比如"不得修改用户原始输入""不生成会议中未讨论内容的决策"。
以前写Prompt总想着把话说全,生怕模型听不懂。实践下来恰恰相反,技能文件写得越克制,命中率越高。原因也简单:模型对结构化短指令的遵循度,远高于长篇大论的温言劝导。你把步骤压缩到 5 条以内,它照着做的概率极高;你把每步都展开成 300 字,它反而开始"理解性发挥"。
2.3 技能目录统一放在项目根的skills/下
没有硬性规范说技能必须集中放,但我的习惯是所有技能统一塞进项目根目录的skills/文件夹。这样做的直接好处有三个:
第一,路径清晰,任何人接手项目,打开根目录就知道Agent有哪些能力;第二,加载逻辑简单,初始化Agent时遍历目录即可,不需要维护复杂的索引表;第三,方便后续做版本管理,整个skills/目录可以单独打包共享,也可以挂在远端仓库作为公共技能库。
跨项目迁移的时候,直接把整个skills/目录拷贝过去,顺手把技能依赖的脚本路径改一下,一个Agent就拥有了另一套完整能力集。这件事在传统Prompt模式下基本没法想象:每一个新项目都要重新调Prompt,跟调教新员工似的。
3. 手写一个可复用的技能文件:结构、参数与流程
3.1 从零定义一个技能:以"代码审查"为例
拿我这个项目里每天都在用的"代码审查"技能举例。以前我让Agent做代码审查,直接在System Prompt里写"请审查提交的代码,关注bug、性能和可维护性,并给出修改建议",结果就是每次输出的审查报告风格完全不同,有时候给表格,有时候给长文,甚至有一次把代码直接重写了一遍,搞得人很头大。
改成技能文件之后,我先定义清楚了"代码审查"这个技能的边界:
--- name: code-review description: 针对使用者提交的代码变更做静态审查,输出问题列表与修改建议。 when_to_use: 当输入包含代码片段或diff内容,且用户明确要求进行代码审查时。 when_not_to_use: 用户未要求审查时,不得主动评论代码质量;用户要求重构或改写代码时,不应调用本技能,应直接修改。 --- ## 执行步骤 1. 识别输入中的代码片段或diff,确定涉及的编程语言。 2. 按优先级依次检查:正确性缺陷(空指针、越界、逻辑反转)、安全隐患(SQL注入、硬编码密钥)、性能问题(N+1查询、重复计算)、可维护性(命名、魔法数字、重复代码)。 3. 将问题按严重程度分级:P0必须修复、P1建议修复、P2可选优化。 ## 输出规范 - 使用Markdown表格输出,列为:严重级别、文件行号、问题描述、修改建议。 - 每个问题必须附带可读性良好的代码示例,示例放在表格下方。 - 不得直接重写用户代码,除非用户明确要求。 ## 边界 - 只审查输入范围内的代码,不推测未提供文件中的问题。 - 不执行自动修复,只提供建议。这个文件写完之后,Agent的审查行为稳定了很多。输出格式不同的问题彻底消失了,审查深度也基本稳定在一个可接受的水平。后面我又在这个技能目录里加了一个checklists/文件夹,放各种语言的检查清单,让模型按需参考。比如Python项目的审查,可以额外读取checklists/python.md,里面列明常见反模式和对应的排查方式。这一步把领域经验也沉淀进了技能里,而不是只依赖模型的通用知识。
3.2 参数传递:让技能和你的业务数据解耦
技能不能是封闭的。它必须接收外部的输入,否则每个场景都要复制一份技能文件,那就谈不上复用了。我的做法是:技能文件的说明中明确约定输入字段,由Agent从对话上下文中提取并填充。
以"会议纪要"技能为例,我约定的输入格式是:
## 输入约定 调用本技能时,请从对话中提取以下字段: - 参会人员: 通过上下文或用户明确指定获取 - 会议主题: 通常为用户第一句话点明的内容 - 对话记录: 用户提供的全部文本这样设计之后,技能本身只依赖"字段=值"的输入结构,不关心这些值来自哪个渠道。用户可能直接粘贴记录,也可能让Agent从某个附件里读取,字段层面完全复用。遇到字段缺失,技能文件中还要写明兜底策略:参会人员缺失时默认写"未知",会议主题缺失时根据第一条消息推断并加注"(推断)"。
3.3 技能内嵌脚本:把算法逻辑从模型手里拿走
纯文本技能文件能做很多事情,但遇到需要精确计算或数据处理的场景,让模型"心算"就不靠谱了。比如数据分析技能,我让模型自己算数据集的四分位数和异常值,它每次算出来的结果都不一样,而且从不告诉用户它用的是哪种方法。
后来我改了方案:把这种确定性逻辑写成Python脚本,放进技能目录的scripts/文件夹,技能文件里写清楚"当需要计算异常值时,执行scripts/outliers.py并将输出作为依据"。模型负责判断什么时候该跑脚本、解释脚本输出,但纯粹的数值计算交给真正的代码。
这套设计思路后来被我推广到好多技能里:能交给代码的,绝不留给模型自由发挥。模型擅长的是理解需求和组织语言,不擅长的是精确计算和稳定复现。一个技能文件里动的操作越多,执行结果就越稳定。
4. 技能质量的核心:边界感与渐进式披露
4.1 边界感是单技能稳定的基石
我在很多技能文件里反复强调"什么时候不该用它",刚开始身边同事不理解,觉得写这些限制是画蛇添足,技能本来就是干这个的,还写什么不让干。
到项目跑起来之后,大家才意识到边界描述能救多少场。以"会议纪要"技能为例,我在when_not_to_use里写了"用户仅询问如何组织会议时,不调用本技能"。如果不写这条,Agent会在用户问"开会应该注意什么"的时候直接生成一份随机会议的纪要,答非所问等于无效回答,还浪费大量token。边界写得具体,Agent的选择就是一路坦途。
我建议每个技能文件里都加一个"当用户表达内容与技能目的相关但并非要求执行时"的处理条目。这个边界成本极低,收益是肉眼可见的稳定性提升。我自己在后来的技能模板里把它列为必填项了。
4.2 渐进式披露:不要让模型一口气读完所有细节
技能文件最忌讳写成百科全书。一个技能的完整执行细节可能有两三千字,如果全部塞进模型上下文,不仅浪费token,还会稀释核心指令的权重。
渐进式披露(Progressive Disclosure)的做法是:SKILL.md主文件只写核心步骤、输出规范和不能做的事,控制在500字以内;那些"进阶技巧""特殊场景处理"之类的内容,放在同目录下的参考文档里,主文件中写一句"如处理××场景,参考references/xxx.md"。模型只有在遇到对应场景时,才会按需读取那个文档。
实际效果差异非常大。两个技能,一个全量塞入,一个按需加载,前者在长任务中目标丢失的周期明显更短,后者在连续执行多轮任务时准确率几乎不衰减。这和人的工作记忆一个道理:不管能力多强,你在某一时刻能全神贯注处理的信息量是有限的。让Agent先记住核心步骤,是保障长链路稳定的关键。
4.3 技能描述会被模型"阅读理解":meta信息要下功夫
既然技能是按描述被路由的,那description字段才是模型决定"到底用不用你"的第一道关卡。描述写得不准确,后面所有细节都白搭。
我踩过的坑是:把描述写得太宽泛。比如"用于处理数据分析相关问题",结果分析需求来了,模型看了半天,觉得这个描述太笼统,宁可自己发挥也不用技能,我又回到之前的混乱状态。后来我把描述改成"输入包含表格或结构化数据集合,且需要探索数据分布、检测异常值或提取统计指标时使用"。指向性瞬间清晰,命中率从六成左右稳定提升到九成以上,在线上的对话日志里测了好几轮,效果非常显著。
写作时的技巧是把自己放在Agent视角:它的上下文里有一堆技能描述,每个描述只有一两行,它必须判断当前用户需求由哪个技能来承接。如果你自己是模型,看了这段描述,能秒懂它的使用场景吗?按这个标准去写,基本不会差到哪去。
5. 调优与实测:技能冲突、误触发和收敛策略
5.1 技能多了就会互相打架:层级与优先级设计
技能数量一多,新的问题冒出来了:多个技能描述看起来都沾边,模型选了错误的那一个。最典型的例子,我同时建了"内容总结"和"会议纪要"两个技能。有次用户发了一段会议对话说"帮我总结一下",模型去调了"内容总结",输出的是摘要,而用户其实想要的是带待办事项的纪要。
这类问题的本质是技能之间没有明确的优先级关系。我的方案分两种:
- 触发条件收窄:在"会议纪要"的
description里明确要求"当输入内容属于多人同步交流记录,且输出需要包含待办事项时优先使用",把场景区分度拉大。 - 引入路由前置判断:在Agent的规划提示里写一句"涉及多个技能时,优先选择执行步骤更具体、更贴近用户原始意图的那个;拿不准时向用户确认,不要自行猜测。"
其实这个问题还可以做得再彻底一点:给技能元信息加一个priority场,数字精确匹配,路由时按最高优先级的技能优先加载。不过跑了几周,我发现收敛触发条件之后,基本用不上这么重的机制。先把描述写清楚,大多数冲突自己就消失了。
5.2 单技能回归测试:一个人的质量保障
agent-skills体系最大的隐性红利是每个技能都可以独立测试,不用再开一个完整Agent跑端到端流程。我日常调优的手法是:写一个test_inputs/目录,每个技能文件夹下放5到10条标记过的输入用例,有一部分是该触发的,有一部分是故意为难它不该触发的。跑完看两个指标:召回率(该触发的有没有漏)和误报率(不该触发的有没有乱出)。
有一次我调整了"会议纪要"技能的描述,把"多人交流记录"改成"会议或多人对话的转写文本",结果误报率立刻上升了两个百分点。回滚之后,我又把描述改成"包含发言人与对话轮次的文本",误报率才降回去。没有这套测试集,这类细微的语义漂移根本不可能是能隔夜发现的。技能仓库里跑测试,成了我后面发布技能之前的固定动作。
5.3 上下文污染:技能一旦加载就会留在上下文里
还有一个细节容易忽略:技能文件被加载之后,它的内容会残留在上下文中。就算这次任务已经结束,后续几轮对话里模型仍受它影响。最典型的场景是:
用户调用"会议纪要"技能生成了一份纪要。然后接着问"帮我把这封邮件翻译成英文"。按理说这跟会议纪要没有半点关系,但因为技能文件还在上下文,模型可能把"邮件翻译"整成"把会议纪要用英文重新排版输出"。
解决思路有两个方向。一是系统层面做上下文清理,在每个任务的结尾标记技能上下文结束,强迫模型把后续内容当成新任务对待;二是技能文件本身加提示"完成输出后,如用户新提出的要求与当前技能无关,则忽略技能中的格式与步骤约束"。第二个方案实施起来更简单,实测也确实有效。我通常是两个一起做,一个从外部压制,一个从内部提醒,双管齐下。
6. 多Agent协作下的技能编排与共享
6.1 技能作为Agent的"可复制能力单元"
项目做到后期,已经不是一个Agent在干活了。我拆了三个角色:一个负责日常问答,一个负责代码相关任务,一个负责数据分析。它们的能力有一部分是重叠的,比如都要求能读文件、能写摘要。
如果每个Agent各自维护一套Prompt,改一处要同步三遍,迟早出不一致。技能的引入让这件事变得很轻松:公共技能放进共享目录,各Agent按需加载。负责代码审查的Agent不会去读"会议纪要"技能,但"代码审查"技能内部又会引用"文件读取"这种公共技能。能力成了模块,Agent变成了组装模块的壳子,比起单一Agent包揽所有任务,可维护性提升得不止一个量级。
6.2 技能共享仓库:改一处,处处生效
技能共享之后,版本更新就成了大家关心的头等大事。我目前用的是最原始但最可靠的方案:技能目录用Git管理,所有变更走提交记录,技能文件头部备注最后修改人和修改原因。Agent加载技能时,读的是当前工作分支的内容,改完直接同步到所有用到该技能的Agent上,无缝衔接新逻辑。
当然,这是一把双刃剑。如果某个Agent依赖旧版技能的行为,共享技能的更新就可能让它行为突变。我的折中方案是:一个技能目录下可以放SKILL.md(当前版本)和SKILL.v1.md(旧版本),需要锁定行为的Agent在配置里指明"使用SKILL.v1.md"。这样既享受了共享的便利,又不用怕公共技能改挂了自己的流程。
6.3 跨项目克隆:一套技能体系的隐性收益
最后说一个隐性收益:技能的跨项目复用能力比想象中大得多。我在A项目里写的"会议纪要"技能,原封不动拷到B项目,几乎不用改就能正常工作。因为技能文件描述的是"如何把输入转为输出",它与具体业务解耦得足够彻底。
沿着这个思路,我已经在内部攒了几十个通用技能,内容覆盖代码审查、文档生成、数据分析、信息抽取、报告规范化等高频场景。新项目启动时,不再是从零开始调教Agent,而是从技能库里挑合适的,直接组装一个新Agent上场。原来一个Agent从搭建到稳定需要两周,现在选好技能、跑一遍单技能测试,两天就能用在真实业务里。
这也是agent-skills让我最受触动的一点:Agent的能力不再是一锤子买卖,它变成了一笔可以跨项目积累的资产。每打磨一个技能,等于给未来的所有项目都做了一次升级。
说回项目本身,我回头看这轮重构,印象最深的反而不是某个具体技能写得多好,而是"把Agent的能力当作工程资产来管理"这件事。技能文件让能力有了边界,有了版本,有了测试,也有了跨项目复用的可能。那些曾经让我头疼的"模型不听话"问题,从根源上被改变了:我不再期待模型自己变聪明,而是给它一套足够清晰的规则,让它没有机会乱来。
如果你现在也在跟Agent的行为反复作斗争,我建议别急着加Prompt了。花点时间,把能力拆成一个个独立的技能文件,配上清晰的,边界描述和触发条件,先跑一周看看。也许你会发现,问题比我说的还顽固,但方向大概率是对的:Agent的进化,下一步不是模型再变大,而是技能体系变得更好用。