ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

从提示词模板到SKILL.md:Agent技能封装的结构化实践

2026/9/11 5:27:49 拓冰建站 浏览量
从提示词模板到SKILL.md:Agent技能封装的结构化实践 写这篇内容之前我先说个真实感受我接触过太多做Agent的同学一开始都是把技能写成一大段提示词塞到系统提示词里然后不停地调语气、加约束、补few-shot。结果呢上下文越来越长、维护越来越难、换个场景就废。后来我在自己的项目里尝试了SKILL.md这套目录化、结构化的技能封装方式才真正体会到技能不是一段话而是一个有结构的模块是什么意思。这篇文章我就把这段时间的实践、踩坑和设计思路完整梳理一遍希望能帮到正在做Agent开发、又对技能编排感到头疼的人。1. 为什么要把技能从提示词模板升级成SKILL.md1.1 提示词模板的两大硬伤提示词模板到现在依然是很多Agent项目的标配因为它上手最快写一段角色设定、加几条规则、贴两个例子看起来就能用了。但只要你做过稍微复杂一点的Agent就会发现它有两大硬伤。第一个硬伤是上下文爆炸。一个Agent往往不止一个能力你要它写代码、做代码审查、生成发布说明、整理会议纪要。如果每个技能都写成一段长提示词全部塞进system prompt里几轮对话下来再叠加历史消息、工具返回结果很快就顶到上下文窗口的天花板。更麻烦的是模型注意力被分散技能之间的指令互相干扰最后表现反而不如精简。第二个硬伤是难以测试和迭代。提示词模板是线性的文本你很难单独验证某一段指令是否生效、某一个步骤是否被执行。改一句措辞可能影响全局。回滚的时候更痛苦没有结构性只能整个版本换。做过大模型应用开发的人都知道没有评测就没有迭代而提示词模板恰恰很难做细粒度的评测。这两个问题不是靠写得更清楚能解决的而是结构性问题。你要把技能从一段话变成一个模块让它在需要的时候才被加载、有自己的元信息、有可选的脚本和参考资源。这就是SKILL.md出现的原因。1.2 一句话说不清什么是SKILL.mdSKILL.md本质上是一个约定每个技能是一个目录目录里至少有一个SKILL.md文件这个文件用YAML frontmatter加上Markdown正文来完整描述这个技能是什么、什么时候用、怎么用、注意事项是什么。看起来很简单对吧但它的设计意图很关键Agent在运行时先通过frontmatter里的description判断这个技能和当前任务是否匹配只有匹配了才会进一步加载SKILL.md正文。也就是说它天然就是按需加载的。你不需要把二十个技能的完整说明都塞进上下文只需要让Agent知道我这里有哪些技能各是干什么的然后按需读取。这里要强调一下SKILL.md和Agent本身不是一个层次的东西。Agent是调度主体SKILL.md是可被调度的技能资源。你可以理解成Agent是员工SKILL.md是员工手里的操作手册员工不会每次上班把全部操作手册背下来而是接单的时候才翻对应的那本。这个关系是你理解后续所有设计的前提。2. SKILL.md的目录结构与元信息设计2.1 一个标准目录该长什么样我在项目里用到的技能目录结构大概是这样的skills/ └── code_review/ ├── SKILL.md ├── scripts/ │ ├── run_review.py │ └── requirements.txt ├── references/ │ ├── security_checklist.md │ └── style_rules.md └── examples/ ├── good_review.md └── bad_review.md这套结构参考了Anthropic那套开放规范的推荐实践但我在实际过程中做了一点调整把examples单独拿出来不直接贴在SKILL.md正文里。原因是示例文本通常很长全部塞进正文会导致加载成本高放进examples目录后正文只需要给路径和用法Agent要参考的时候再去读灵活性更高。scripts目录是很多模板教程没讲透的地方。它的作用是承接需要精确计算、批量处理或环境依赖的部分。比如code_review技能里你可能需要读取大量文件、做静态检查、统计改动行数这些用自然语言让模型一个个做既慢又容易出错。写成Python脚本后Agent只需要调用一次拿回结构化结果再基于结果写审查意见效率和准确率完全不在一个量级。references目录放的是标准化参考。比如安全审查清单、编码风格规则、项目历史约定。这些内容不是每次都要读但在特定审查维度上必须查。把这类信息以独立文件形式放在SKILL.md旁边比写进正文更合理正文负责怎么做的流程references负责按什么标准做的知识两者解耦改起来互不影响。2.2 frontmatter里到底该写什么frontmatter是整个SKILL.md的门面Agent调用技能的判断依据大部分在这里。我见过不少初学者把frontmatter写得很随意就一个name加一句description拉倒结果技能上线后几乎没被触发过。这说明你对门面的理解还不够。我目前比较稳定的字段结构是这样的字段是否必填作用与建议name必填技能的唯一标识短且无歧义建议用snake_casedescription必填描述技能适用场景、输入和输出说明什么情况下该调用license可选若技能涉及开源代码明确许可证allowed-tools可选限定技能运行时允许使用的工具列表metadata可选额外信息比如作者、版本号、模型要求、依赖description是重中之重它不是给读者看的是给模型看的。写description时不要用模糊词汇比如可以辅助代码相关工作这种描述会让模型在需要具体技能时无法确定该不该选它。要写成当用户请求对指定代码或PR进行逐行审查重点关注安全漏洞、性能瓶颈和风格问题时使用本技能。输入应为代码路径或patch内容输出为Markdown格式审查报告。越具体、越带触发词召唤率越高。allowed-tools这个字段很多人忽略。实际运维中你一定会遇到这种情况技能内部需要调用某个工具但用户环境里没有或者出于安全考虑不应该让技能随便执行工具。在frontmatter里限制工具范围等于给技能加了一道闸门避免它干本不该干的事。我习惯给所有需要执行脚本或shell命令的技能都加上这个字段宁可少给不要多给。3. 手把手设计一个可复用的SKILL.md3.1 先想清楚边界再动手很多人在拿到SKILL.md规范后第一反应就是赶紧写正文。但我强烈建议你先花30分钟想清楚技能的边界否则后面会反复改。什么叫做想清楚边界第一技能要解决什么任务任务输入输出是什么。第二技能不做什么哪些情况应该明确拒绝或转给别人。第三技能需要哪些外部资源代码仓库、API、知识库。第四技能的复杂度和可维护性是否值得用目录结构还是用一个简短的模块就够了。我常用的方法是先把要封装的技能拆成核心流程和知识参考两部分。核心流程是Agent每一次都必须执行的步骤知识参考是不一定每次用、但用到时必须查的内容。核心流程放进SKILL.md正文知识参考放进references目录。这样划分有一个好处正文保持精简避免把不常用但体积巨大的规则文本塞进上下文同时让技能执行路径更稳定。我举个实际例子就拿代码审查技能来说。它的核心流程是读取变更范围、获取相关文件、执行静态检查脚本、按检查清单逐项核对、输出结构化审查意见。这些内容会写进SKILL.md正文。而项目里哪些目录不允许出现调试代码依赖库版本升级的注意事项这类项目级知识应该放进references/project_rules.mdAgent需要时再读取。3.2 SKILL.md正文用操作手册替代人设台词进入正文写作阶段我最大的体会是不要写成人设台词要写成操作手册。人设台词长什么样比如你是一位资深代码审查专家拥有多年开发经验对代码质量和安全有深刻理解你的目标是帮助团队提升代码质量。这些话看起来气势很足但对模型行为几乎没有约束力。操作手册长什么样直接写步骤当你调用本技能时请严格按照以下步骤执行 1. 确认输入检查用户提供的代码路径、PR编号或diff内容若缺失引导用户补齐。 2. 读取变更范围使用 git diff --stat 查看改动文件列表识别高风险文件。 3. 调用审查脚本运行 scripts/run_review.py 目标路径获取静态检查结果。 4. 逐维度审查依次按安全风险、性能瓶颈、可读性、测试覆盖四个维度输出问题。 5. 输出报告以 Markdown 格式输出表格列出文件-行号-问题等级-建议。这种写法直接决定了模型执行任务的路径。它能照着做执行结果可复现复盘时也能看出哪一步出了问题。在正文中我还会增加三个小节前置条件、依赖说明、退出条件。前置条件用来约束何时才能启动这个技能依赖说明告诉Agent需要哪些工具或文件存在才能执行退出条件则用来明确任务做完的标志是什么。这三个小节内容不长但能让技能变成一个闭环任务而不是写完报告就结束的开放式输出。还有一点要特别提醒不要通篇使用必须强烈不建议这类情绪化词汇。过度使用命令式语气会挤压模型在合理范围内的自主性反而让它在遇到边界情况时不知道如何处理。我现在的原则是流程步骤用祈使句标准说明用陈述句风险点用注意前缀而不是反复喊必须。3.3 辅助资源脚本、参考与示例怎么放脚本是SKILL.md体系里非常关键的一环。很多技能的瓶颈不在模型不会写而在模型拿不到精确数据。举个例子让模型自己判断一个函数的时间复杂度它能猜但让它统计整个项目里所有函数的时间复杂度如果没有脚本它只能逐个文件读效率低且容易漏。我在code_review技能里放了一个run_review.py核心逻辑是# 简化版逻辑 import sys, json from pathlib import Path def main(target_path): files list(Path(target_path).rglob(*.py)) problems [] for f in files: # 这里做基础的语法树检查、可疑模式扫描 problems.append({file: str(f), level: warning, message: ...}) print(json.dumps(problems, ensure_asciiFalse, indent2)) if __name__ __main__: main(sys.argv[1])脚本输出必须是结构化、可解析的最好一次输出完整结果让Agent直接基于结果加工。不要写成交互式脚本不要让Agent一行行去问否则Agent很容易在等待时编造一个结果。这是我踩过的坑脚本改成批量输出JSON后审查报告质量稳定了很多。references目录适合放两类内容一类是标准化清单比如安全规则、风格指南、常见反模式另一类是上次怎么做的历史案例比如团队之前处理过的典型问题复盘。这两类信息通常很长不适合放在正文里放references后可以让Agent按需读取正文还能保持精简。examples目录我一般放好/坏对照示例。这比在正文里写few-shot有一点点区别examples文件是给Agent做对照学习的正文里的示例是给Agent提供输出格式的。实操中我发现把完整示例文件放到独立目录后再在正文里只给一个用法说明比直接把示例贴在正文中更省token且模型执行的格式稳定度明显更高。4. 接入Agent与调试的关键细节4.1 description写得不好技能永远不会被调用这个坑出现的频率高到我不厌其烦地重复SKILL.md写好了但Agent就是不用。最常见的原因就是description写得跟猜谜一样模糊。我调试过的项目里最典型的一个例子是有人写用于生成各种文档结果Agent在用户真的要生成周报时宁可自己随便写也不调用这个技能。后来我们把description改成当用户要求生成项目周报、月报或迭代总结时使用本技能。输入是日期范围和git提交记录输出是一份包含进展、风险、下一步计划的周报文档命中率立刻上来了。description还应该负起排除无关调用的职责。写清楚不要做什么同样能提升命中精度。比如一个专门做前端代码审查的技能description里最好明确仅适用于前端代码审查不适合后端逻辑审查。这样可以避免Agent在用户请求后端审查时错误调用前端技能。再补充一点不同Agent框架对description的读取逻辑不一样有的会把它当作工具描述注入工具列表有的会当作技能库索引统一读取。但不管哪种都遵循同一个原则description要能独立表达什么时候用、输入是什么、输出是什么。不要依赖正文去弥补description的缺失模型多数情况下是先看description再决定要不要读正文。4.2 上下文管理该被加载的是说明不是示例代码SKILL.md本身解决的是按需加载问题但你要是以为有了它就不用管上下文了那就错了。实际运行中你依然要面对加载了多少、什么时候加载、加载完是否卸载的问题。我自己的策略是把SKILL.md正文控制在800到1200个中文字符左右这已经足够包含一个中等复杂度技能的完整流程。把凡是在300字以上的示例、模板、报告样例全部放到examples或者references里正文只放路径和一句话说明。这样Agent在匹配技能阶段只需要读frontmatter和精简正文成本低、速度快。另外一个实践是不要让Agent把SKILL.md读进来后一直保留到最后而是在任务执行完毕后让技能自己收尾即使技能用例结束后清理中间状态。这个操作在不同框架里叫法不同有的叫context reset有的叫tool lifecycle核心思想都一样技能生命周期和对话生命周期分离技能用完后它的上下文占用就释放避免影响后续任务。我还观察到脚本输出如果过大也会挤占上下文。比如静态检查脚本一次性输出几百条JSON模型读起来很费力。优化办法是脚本内置过滤和摘要能力默认只输出高优先级问题低频问题写入文件Agent按需再查。这相当于在技能资源和上下文之间加了一个中间层效果很明显。4.3 测试与回滚没有评测等于白写做Agent开发的人都知道没有评测你所有的优化都只是感觉。我早期写SKILL.md时从来没想过给它建测试集觉得写清楚一点就行了。后来发现同一个SKILL.md在不同模型版本下的表现差异大得惊人不测试根本不敢发布。现在我的做法是给每个SKILL.md维护一个小型测试集大概十条左右的真实任务请求覆盖正常场景、边界场景和拒绝场景。每次修改SKILL.md后跑一遍测试集记录技能是否被调用输出格式是否合规关键步骤是否执行结果是否满足要求四个维度最后汇总成一个评分。评分不过阈值就不发布。测试集文件可以放在skills/code_review/ └── tests/ └── test_cases.mdtest_cases.md里每条用例都包含输入、期望行为、通过标准。有了这些你改description、调正文顺序、改脚本输出格式之后能立刻知道哪些改动是正向的哪些是负向的不再靠猜。版本管理上我习惯用git tag给技能备注版本号比如code_reviewv1.2.0。必要的时候checkout上一个版本对比效果回滚成本极低。5. 关于SKILL.md的几个常见误区5.1 SKILL.md不是MCPMCP也不是SKILL.md这个问题我经常在社区里看到很多人把SKILL.md和MCP放在一起比甚至有人以为SKILL.md要取代MCP。它们解决的是完全不同层次的问题MCP是模型和外部工具之间的一种标准化协议解决怎么调用外部能力SKILL.md是一种技能文件约定解决怎么把可复用的任务流程沉淀下来。你可以把MCP理解成外设接口SKILL.md理解成操作手册。操作手册里当然可以写着调用外设接口做某事但两者不是竞争关系而是配合关系。我在设计技能时通常这么划分需要读写外部系统、数据库、API的时候走MCP工具需要固定流程、知识参考、脚本编排的时候用SKILL.md封装。两者互相补充不要混淆。5.2 技能粒度多大才合适技能粒度是另一个高频问题。技能太大比如负责所有文档工作里面涵盖周报、PRD、接口文档、操作手册你会发现SKILL.md正文很难写流程相互冲突description也无法准确描述Agent调用时会频繁误选。技能太小比如生成会议纪要标题又显得没必要几个token的提示词就能解决非要建目录反而增加管理成本。我用一个简单标准来判断如果这个技能需要至少三个以上的固定执行步骤、或者需要引用独立的参考资料、或者需要脚本辅助那么适合做成SKILL.md。如果只是一句话指令能搞定的事就不要硬套格式。实操中我宁可让技能数量多一些、粒度细一点也不要做一个大而全的技能。细粒度技能便于单独测试和迭代而且多个细粒度技能之间可以通过Agent的编排能力组合使用灵活性远高于一个复杂技能。5.3 写了SKILL.md之后Agent就一定能调用不一定。SKILL.md只是把技能表达好Agent框架是否支持技能索引、运行时是否真的按需读取、用户的Agent是否配了足够的工具权限这些环节任何一个出了问题技能都不会被正确调用。我在接一个新框架时第一步不是直接写技能而是先翻它的文档确认三个问题技能目录应该放在哪里、frontmatter解析哪些字段、description是如何被注入到模型上下文里的。这三个问题搞清楚了再动手写技能事半功倍。否则你按A框架写的SKILL.md放到B框架里可能完全不认这不是格式错而是框架的加载约定不同。后面我还会继续实验更多用法比如给技能加版本依赖、在脚本里做多模型适配、用技能组合的方式处理更复杂的任务流。现阶段我认为SKILL.md最大的价值不是它的格式有多好而是它逼着你把直觉式的提示词写作转变成工程化的技能设计这种转变带来的收益会随着技能数量增长越来越明显。