“agent-skills”这个标题最近在我的技术雷达上反复出现。起因很简单:我们团队做了快一年的智能体应用,越做到后面越发现一个问题——光是给Agent配工具、写提示词,解决不了“它每次都像个新手”的毛病。你让它整理代码仓库,它第一步问你要权限,第二步开始瞎猜规则,第三步输出格式又和你上次要求的不一样。后来我们把手头一批高频任务改造成技能化(skills)组织,相当于给每个任务场景配了一份“岗位说明书+操作手册+质量门槛”,整个稳定度立刻上了一个台阶。这篇文章不是概念科普,是我自己从零搭技能库的完整过程,包括目录怎么设计、描述怎么写、实测踩了哪些坑,以及技能库怎么从一个人用到一个团队用。
我一开始接触“agent-skills”这个概念时,下意识觉得这无非是“给智能体打包一堆提示词片段”。真正实践下来才发现,这个判断错得很离谱。技能化的核心不是把提示词整理得更好看,而是把“模型自由发挥”的部分和“可脚本化执行”的部分彻底分层,让模型只做它擅长的事,把确定性交给代码。下面我会用我们团队实际做的“周报生成技能”作为贯穿全文的案例,把整个设计过程和踩坑经历完整摊开讲。
1. agent-skills到底在解决什么问题:Agent不是没有能力,是没有“岗位说明书”
1.1 一个让我决定重构的典型场景
上个月我们有个项目进入密集迭代期,每周一上午都要产出上一周的进展汇总。原来的做法很简单——在系统提示词里写一大段“请根据git提交记录和任务面板生成周报”,然后每次都由模型自己决定怎么读数据、按什么结构整理。结果连续三周出问题:
第一周,模型把提交记录一条条罗列出来,整份周报像流水账;第二周,它开始自己发挥,把代码注释里的口吻都写进了“团队复盘”部分,看得项目负责人一头雾水;第三周更离谱,它居然跳过周五晚上的合并提交,理由是“周五的提交与本周目标无关”,可那恰好是当周最关键的一个功能集成。
问题不在于模型能力不够,而在于我们从来没有告诉它“一份合格的周报应该长什么样”“数据来源有哪些”“哪些环节必须程序化执行而不是靠模型判断”。这就是典型的“有能力但没有岗位说明书”状态。后来我把这个任务整体封装成一个技能,把数据采集写成脚本,把周报结构固定成模板,把质量校验写成硬性规则,模型只负责“阅读数据、归纳要点、填写模板”这一层推理工作。从那周开始,周报生成的稳定度从六成左右直接拉到九成以上。
1.2 技能(Skill)和工具(Tool)的根本差异
很多团队卡在“我已经给Agent接了一堆工具了,为什么还要技能化”这个问题上。我自己的理解是:工具解决的是“能做什么”,技能解决的是“应该怎么做、按什么顺序做、做到什么标准才算完成”。
举一个生活化的例子:工具相当于你给一个新员工开了一堆系统权限——他能登录代码仓库、能访问任务面板、能发邮件。但权限再全,他还是不知道“每周一早上的正确动作”是:先拉取仓库日志,再同步任务状态,把两者对齐后按固定模板输出周报,最后抄送给指定的人。这套“动作序列+执行标准”就是技能。
从实现层面来看,两者也不同。一个工具通常对应一个函数或一个API端点,输入输出边界清晰;而一个技能可能内部要串联多个工具调用、嵌入领域知识、引用模板文件、设定中间校验点。换句话说,工具是技能的一个组成部分,技能更像一个“带流程控制的任务包”。这也是为什么用“附加一堆工具+长提示词”的方式做Agent总是不稳定——工具定义得再清楚,模型仍然需要每次现场决定调用顺序、判断中间结果、选择输出格式,这些不确定性叠加起来,出错率自然高。
1.3 按需加载带来的上下文与成本红利
技能化还有一个经常被低估的收益:上下文按需加载。把一份完整的任务说明写成技能后,平时它只是一堆安静的文件夹,只有用户请求触发了相关场景,系统才会把技能描述加载进上下文。
我算过一笔很实在的账。以前把周报规则写进系统提示词,大约要占2500个token;如果每天有50个会话都在跑这个提示词,那就是12.5万token的固定开销,其中绝大多数会话根本与周报无关。技能化之后,这段内容从全局提示词里移出去,只有真正需要生成周报的会话才加载。按一周跑4次计算,上下文消耗几乎是原来的零头。这不只是省钱的问题——上下文窗口是有限资源,把不相关的长文本永久占着,等于压缩了模型处理当前任务的有效空间,任务越复杂,这种浪费越致命。
实践中还有一个更微妙的收益:技能内的指令文本是“在需要时才出现”的,模型读到它时正好进入对应任务上下文,指令的服从度往往比“永久挂在system prompt里”更好。因为模型不会被一段与当前对话无关的长文本干扰注意力,触发技能时反而更聚焦。
2. 拆开一个技能库:目录、描述文件、脚本和资源的组织逻辑
2.1 SKILL.md:决定“模型什么时候想起这个技能”
一个技能最核心的文件是描述文档(通常命名为SKILL.md),它承担两个任务:一是告诉编排层“这个技能是干嘛的”,二是告诉模型“触发后该怎么一步步执行”。这里最容易犯的错误是把描述文档写成一份“给人类看的项目说明”,满是背景介绍和设计愿景,却没有给模型可执行的指令。
我实践下来的经验是,描述文档必须包含四个关键段落:触发条件(when_to_use)、前置条件、执行步骤、输出要求。触发条件尤其重要,它决定了模型在什么情况下应该主动调用这个技能。比如我们周报技能的触发条件写得非常明确——“当用户要求整理本周进展、生成周报、汇总迭代结果时使用”,而不是笼统的“用于项目管理”。前置条件用来避免模型在缺少数据时硬着头皮瞎编,输出要求则用来卡住最终格式。
一个比较合适的描述文档开头是这样:
name: generate-weekly-report description: 根据仓库提交记录和任务面板状态,生成按周分组的项目周报 when_to_use: 当用户要求整理本周进展、输出周报、汇总迭代结果时 version: 1.2.0 tags: [report, weekly, project-management]YAML头之外的正文字数不必追求多,但要精准。一份理想的SKILL.md最好控制在1000到2000个token以内,太长会挤压上下文,太短则无法约束模型行为。写完之后要做一件事:把描述文档当“产品说明书”来读,删掉所有模型读了也不会改变行为的废话。我最早的版本写了两千多字,后来砍到八百多字,效果反而更好,因为关键约束更醒目了。
2.2 scripts:把不可控的“模型自由发挥”变成可控的“脚本执行”
技能里最容易被忽略但最值得投入的部分,是可执行脚本目录。很多人在设计技能时把所有逻辑都交给模型推理,这其实是把最大不确定性留在了最关键的位置。我的原则是:凡是能脚本化的环节,一律脚本化;模型只做脚本结果之上的归纳、解释和决策。
拿周报技能来说,采集git提交记录、拉取任务面板状态、按日期统计变更量,这些操作完全可以通过Python脚本完成,输出为结构化的JSON。模型拿到JSON后从中提取要点、组织语言、生成总结,而不是自己一步步去翻仓库、翻面板。这样至少带来三方面的好处:数据源准确稳定,文件不会被模型“选择性遗漏”;采集耗时从分钟级压到秒级;脚本的边界条件(比如仓库路径不存在、权限不足)可以提前拦截,避免模型在错误数据上继续发挥。
脚本与描述文档之间的接口要严格约定。脚本的入参和输出格式必须写清楚,且输出最好是严格的结构化格式。我通常在SKILL.md里直接写明“执行python scripts/collect_data.py --weeks 1,输出为JSON对象,包含status、commits、tasks三个字段”,这样模型即使不看脚本源码也知道从哪里读取结果。
2.3 assets与templates:给输出稳定性加一道锚
除了描述文档和脚本,技能通常还要带资源目录,里面主要放两类内容:静态参考资料和输出模板。静态参考资料可以是领域术语表、项目背景说明、历史优秀案例等;输出模板则是每次生成结果必须套用的框架。
输出模板的价值在于锁定结构。模型在自由模式下写出来的周报,结构上每次都会漂移——这次有“风险与阻塞”,下次可能就漏了;这次用表格,下次用列表。但如果让它严格填充一个预定义的Markdown模板,结构漂移问题就基本消失,后续就算要做自动化解析、自动归档,也有稳定的格式可以依赖。
有朋友问我,模板不会限制模型的发挥吗?我的回答是:对于生产性任务,限制发挥恰恰是目标。我们不需要“周报生成得很有创意”,我们需要它稳定、完整、格式可预测。创意发挥应该发生在内容归纳的层面,而不是结构层面。模板就是把这层“结构自由度”关掉,只保留“内容自由度”。
2.4 一个最小技能目录的完整样例
把上面几块拼起来,一个最小可用的技能目录大概是这样的:
generate-weekly-report/ ├── SKILL.md ├── scripts/ │ ├── collect_data.py │ └── validate_report.py ├── assets/ │ ├── project_glossary.md │ └── example_report.md └── templates/ └── weekly_report_template.md这个结构看起来简单,但它体现了一个重要原则:职责分离。SKILL.md负责“指挥”,scripts负责“执行”,assets负责“参考”,templates负责“约束输出”。团队里新来的工程师拿到任何一个已有技能,只要扫一眼目录,就能在几分钟内搞清楚这个技能是怎么工作的。这比让大家从一团乱麻的对话历史里反推“当初是怎么设计的”高效太多。
3. 从需求到落地:手写一个“周报生成技能”的完整过程
3.1 选定任务边界:什么该让模型推理,什么该让脚本做
做技能的第一步不是写代码,而是画一条边界线:这个任务里,哪些环节是确定性的、可脚本化的,哪些环节依赖语言理解、归纳判断、需要模型发挥。
以周报为例,我画出来的边界是:
- 脚本负责:读取git日志、统计提交数、拉取任务面板状态、按日期/分类聚合数据。
- 模型负责:阅读聚合后的数据,识别本周关键进展,归纳主题,判断哪些内容需要重点突出,把要点填入模板。
- 模型不负责:计算、排序、过滤、格式验证——这些统统交给脚本或者模板结构。
这条边界线的判断标准很简单:如果某个环节可以用20行代码稳定实现,就不要让模型去“思考”;如果某个环节需要理解语义、归纳意图、生成自然语言,那就明确交给模型,不要强行脚本化。很多技能做出来效果差,不是脚本太少,而是脚本和模型的职责纠缠在一起,导致模型不知道该何时依赖数据、何时自行发挥。
3.2 把执行流写成可遵循的分步清单
边界画清楚后,下一步是把整个任务流程拆成分步清单,写进SKILL.md。这一步的关键不是“写步骤”,而是“让每一步都有明确的结束标志”。
我最初写执行流时是这样写的:
- 收集本周的提交记录。
- 分析提交记录并生成总结。
- 输出周报。
这种写法等于没写——模型根本不知道第一步什么时候算完成、第二步要分析到什么颗粒度、第三步输出给谁。后来我改成这样:
- 运行
python scripts/collect_data.py --weeks 1,获得本周提交与任务变更的JSON数据;若返回status为“error”,立即停止并向用户说明原因。 - 读取
templates/weekly_report_template.md,确认模板结构。 - 从JSON数据的commits字段中识别与项目目标直接相关的提交,按功能模块聚类;从tasks字段中提取状态变化,标注完成与阻塞项。
- 每一条进展描述必须包含可验证的提交标识或任务标识,禁止出现无依据的通话记录式描述。
- 将内容填入模板对应章节,生成最终周报。
每一步的结束标志都非常清楚——要么拿到JSON数据,要么读到了模板,要么每条描述都带上了标识。模型在这种清单下几乎没有“自由发挥”的空间,但它又确实参与了核心的语义归纳工作。这就是好的执行流设计:约束行为,但保留智能。
3.3 写入校验和质量门槛
分步清单能保证任务“跑起来”,但保证不了“跑出来的东西能用”。所以技能里必须加一道质量校验环节。我们团队的做法是再写一个校验脚本validate_report.py,它会检查生成结果是否满足几个硬性条件:是否包含指定章节、是否每条进展都有标识、是否有明显超长的段落、是否在模板结构上出现偏移。
质量门槛要写成“可机器检查”的硬规则,不要写成“请确保结果准确”这种模糊要求。比如:
- 硬规则1:最终输出必须是Markdown格式,一级标题为“第X周项目进展”。
- 硬规则2:每个功能模块小节下至少包含一条进展、一个阻塞项(如有)。
- 硬规则3:任何进展描述中不得出现类似“可能是”“大概”这类不确定表达。
一开始我担心规则太严格会让模型束手束脚,实际测试后发现,模型反而更愿意在规则明确的框架下展开内容——因为它不用花时间猜测“用户到底想要什么格式”,可以把全部注意力放在内容质量上。校验脚本虽然只做简单的文本检查,但它在“生成结束”和“交付结果”之间加了一道闸门,避免了低级错误流入下游。
3.4 在真实会话里反复测试、修正描述
技能写完不经过真实对话测试就直接上生产,那就是给自己埋雷。我的做法是分别用正常场景、边界场景和错误场景各测试三轮。
正常场景就是普通的“帮我生成上周周报”,看模型是否会主动触发技能、是否按步骤执行、输出是否完整。边界场景包括:用户要求“只要周五之后的提交”或者“帮我写个周报但是不包含测试模块的改动”——这些考验模型在偏离标准流程时能否正确处理。错误场景则是数据获取失败、权限不足、仓库路径不存在,看模型是否会停下来求助,而不是硬编一份假周报。
三轮测试跑下来,几乎每次都会发现SKILL.md描述里的问题。最常见的是触发条件写得太窄,导致模型在某些变体问法下想不起这个技能;其次是步骤之间缺少过渡说明,模型执行完第二步后不知道该不该继续做第三步。修正描述文档是一个反复迭代的过程,直到连续十次测试都能拿到一致且合格的结果,我才会把这个技能正式纳入技能库。
4. 实测阶段最值得记的四个坑
4.1 描述写得太泛,Agent在“用与不用”之间反复横跳
我们做过一个“代码仓库巡检技能”,最初的描述是“对代码仓库进行质量检查”。听起来没问题,但实测中模型经常在用户随口问“这个仓库怎么样”时触发它,然后又因为缺少明确的检查项清单而只做了表面功夫:数了一下文件数量、看了一眼提交频率,就输出一份不痛不痒的结论。
后来我们分析了触发记录,问题就出在描述太泛。改成“当用户要求审查代码规范、识别潜在缺陷、评估仓库维护状态时使用;日常闲聊和概览性问题不要触发”之后,触发准确率明显提高。这里的关键是描述文档不仅要写“何时用”,更要用负向条件写明“何时不用”。模型对“什么时候不该用”的判断,往往比对“什么时候该用”更敏感。
4.2 一个技能塞了太多内容,上下文直接被撑爆
团队里A同学一度把“项目分析技能”做得非常“全面”,SKILL.md写了四千多token,还塞进了五个参考文档和三个模板。他的想法是让模型“一次学全”,结果实测时模型在对话中段开始丢失关键指令——不是模型不行,而是上下文中相关信息太多,互相干扰。
解决办法是拆分:把“项目分析技能”拆成“结构分析”“依赖分析”“变更风险评估”三个独立技能,让每个技能保持单一职责。这就像给新人培训时,与其一次塞一本五百页手册,不如分模块教学、按需调阅。拆分之后每个技能都变轻了,模型反而学得更准。我现在给自己定了一个硬指标:单个技能的SKILL.md不超过1500 token,参考文档不超过两份。
4.3 两个技能职责重叠,模型开始选择困难
技能库建到十几个之后,出现了新问题:两个技能的描述有重叠,模型不知道选哪个。比如“生成周报技能”和“生成迭代复盘技能”,在实际触发时,模型有时候用周报技能生成复盘内容,有时候又用复盘技能反推周报,两边结构对不上,下游归档乱成一团。
排查后发现,两个技能的when_to_use条件都写了“用户要求总结项目进展”,边界没有划清。后来我们在描述里做了明确区分:周报强调“按时间周期的例行汇总”、复盘强调“针对某次迭代或事件的结构化回顾”,并在各自的when_to_use里加上了互斥条件。这个坑给我的教训是:技能库不是简单的文件夹堆砌,每个技能的边界本身也需要“治理”。当两个技能都声称自己负责同一类任务时,结果是模型的选择越来越随机。
4.4 脚本输出格式是自由文本,后续流程全线崩盘
这个问题最隐蔽,也最致命。我们的“变更风险评估技能”里,负责采集代码变更的Python脚本最初返回的是自然语言文本,比如“检测到3个高风险文件,主要集中在登录模块”。模型拿这段文本去生成最终报告时,每次都用自己的理解重新解释一遍,导致同一份数据在不同会话里被解读成完全不同的风险结论。
修复方式很简单:把脚本输出改成严格JSON,并约定字段结构。比如:
{ "status": "ok", "risk_files": [ {"path": "src/auth/login.py", "risk_level": "high", "reason": "contains hardcoded credentials"} ], "summary": "3 high-risk files found" }模型读取这种输出后,只需要做转述和归纳,不再需要对自由文本二次理解。这件事给我的启发是:脚本与模型之间的接口,必须像前后端接口一样严格定义。自由文本永远只应该出现在最终交付给用户的输出里,内部数据交换一律结构化。
我把这四个坑整理成了一张速查表,供团队内部分享:
| 问题表现 | 根因 | 解决方向 |
|---|---|---|
| 模型触发不准,时用时不用 | SKILL.md描述泛化,缺少负向条件 | 写明when_to_use和when_not_to_use |
| 对话中段指令丢失 | 技能内容过多,上下文拥挤 | 拆分技能,控制描述长度 |
| 多个技能互相抢任务 | 边界重叠,触发条件不互斥 | 明确各自边界,加互斥条件 |
| 模型对同一数据解读漂移 | 脚本输出自由文本 | 脚本输出严格JSON,约定字段结构 |
5. 从个人技能包到团队技能库的进阶路线
5.1 命名规范化与版本管理:让技能可以像依赖包一样演进
技能数量一多,命名就成了大问题。团队早期几个技能叫“周报神器”“代码检查一下”,靠人肉记忆还能应付,到三十多个技能时完全失控。后来我们规定了一套统一命名格式:动词-对象-场景,例如generate-weekly-report、analyze-changes-risk、format-commit-message。这套格式有两大好处:一看名称就知道技能干什么;在描述文档里引用其他技能时,名称本身就是可读的调用指示。
版本管理我们直接用了Git仓库,每个技能目录单独演进。SKILL.md的YAML头里写入version字段,行为变化时必须升版本或者至少更新变更记录。这套操作看着基础,但它让技能库从“个人随手整理的文件夹”变成了“可追溯、可回滚、可多人协作的资产”。有一次新版校验脚本误杀了一类合法提交,我们就是靠回滚到上个版本迅速恢复的。
5.2 技能编排:原子技能与复合技能的分层设计
技能多了之后,我开始意识到不是所有技能都要“从零执行到最后”。有些任务天然是多个原子技能的组合。比如“发布前检查”这个技能,内部其实要先调用“构建技能”确认编译通过,再调用“测试技能”跑关键用例,最后调用“变更风险评估技能”判断是否引入兼容性问题。
这种复合技能的设计思路是分层:原子技能只做一件确定的事,复合技能只做编排,不在自身内部重复实现任何原子逻辑。这样做的最大好处是复用——新增一个发布流程时,不需要重写构建和测试逻辑,只需要组合已有技能并处理它们之间的数据传递。同样,当某个原子技能被优化时,所有依赖它的复合技能自动受益,不需要逐个人工维护。
5.3 触发元信息设计:为什么“何时不该用”比“何时该用”更重要
最后想单独聊一下触发元信息的设计。很多开源技能库里的描述文档只会写这个技能能做什么,很少写什么时候不要用。但我的实测经验恰恰相反:模型对负向条件的敏感度极高,明确写了“当用户只是闲聊时不要触发”的技能,误触发率比不写的低一个量级。
在设计触发描述时,我会先列出过去两周所有“不该触发却触发了”的case,把这些case的特征提炼成负向条件写进描述里。比如周报技能后来就加了这样的句子:“当用户只是询问某个提交的具体内容时,不要触发本技能——那是‘代码变更解释技能’的职责。”这相当于给模型画出了一个决策树,让它明白技能之间的边界在哪里。到这一步,技能库才真正从“一堆脚本和文档”变成“一个有组织的智能体工作体系”。
在我自己这段时间的实践里,最大的体会是:技能化不是在替模型做决策,也不是在压缩模型的智能空间,而是在给团队的智能体应用“立规矩、建制度”。一个没有制度的团队,再优秀的成员也会各行其是;一个没有技能的Agent,再强的模型也会每次都从零开始瞎试。把流程、边界、校验、模板这些“制度”固化进可复用的技能包之后,Agent才真正从一个“聪明的实习生”变成了一个“靠谱的老员工”。你现在可以把你们团队最头疼的那个重复性任务翻出来,试着按这个思路拆成技能——先从一条最窄的业务线开始,跑通流程,再慢慢扩散到整个技能库。