
这两年只要聊到 Agent 开发就绕不开“技能”这个词。但我在翻开源项目和带团队的时候发现一个很普遍的问题很多人把技能直接写进系统提示词里本质就是一段 Prompt 模板模型怎么用、什么时候用、用得好不好全靠试和赌。等技能多了以后改一个功能就要去动那坨越来越长的提示词Agent 行为完全失控。我踩过这个坑之后现在所有 Agent 项目都改成用 SKILL.md 这种结构化技能文件来定义能力。这篇文章我把我为什么推荐 SKILL.md、它和提示词模板的本质区别、实际怎么写、以及运行中最容易踩的坑一次性讲明白。1. 从“提示词模板”到“SKILL.md”先理清一次认知升级1.1 提示词模板的天花板为什么光有提示词不够先聊一个基础问题提示词模板到底缺什么很多人以为技能 给模型的指令于是把所有内容都揉进 system prompt比如“你是一个代码审查专家请检查代码质量、安全性和可维护性……”然后塞一堆规则。这种做法在单技能、单场景的 Demo 里没问题一旦技能多起来问题立刻暴露。第一是没有任何触发边界。你写了一段“数据分析技能”的提示词模型可能在用户问天气的时候也强行套用那套规则因为所有技能都堆在上下文里模型根本分不清边界。第二是无法绑定工具和脚本。提示词只能约束模型“怎么想”约束不了“怎么调用外部能力”。技能里如果需要跑一个 SQL、解析一份日志、调一个 API提示词就显得苍白无力。第三是完全没法做版本管理和测试。提示词改一个字都可能改变行为但你又不敢随便改因为没有结构化的测试手段回归验证基本靠人肉。说白了提示词模板是把“能力定义”和“对话文本”混在了一起。而技能应该是可以被识别、被调度、被独立演进的一个单元。这就是 SKILL.md 这类结构化技能文件出现的原因它把技能从“一段话”变成了“一个文件/一个目录”让 Agent 能发现技能、判断何时使用、按约定执行甚至挂载脚本和工具。1.2 理解 SKILL.md 的定位技能文件到底长什么样SKILL.md 的核心约定其实不复杂每个技能都是一个独立目录目录里有一个 SKILL.md 作为入口描述文件可选的还有配套脚本、模板、参考资料。SKILL.md 通过 YAML frontmatter 声明技能的元信息用正文写具体指令。这个约定在 Anthropic 的 Claude Skills 生态里被发扬光大后来很多开源 Agent 框架比如 OpenHands、各类基于 codex 的 Agent 项目也沿用了类似结构逐渐成了社区里不成文的通用标准。在完整形态里一个技能目录大概是这个结构my-skill/ ├── SKILL.md ├── scripts/ │ └── run_check.py └── references/ └── style_guide.md这套结构的价值在于Agent 在运行时会扫描可用的技能目录读取每个 SKILL.md 的元信息把“技能名称 用途描述”注入上下文形成技能清单。当用户请求触发某个描述时模型才会加载对应的完整指令再决定要不要执行脚本。这个过程实现了“轻量路由 按需加载”避免了把所有技能塞进上下文造成的混乱和 token 浪费。1.3 提示词、技能、工具和 Agent 到底是什么关系这四个概念是很多人最迷糊的地方。简单说可以把 Agent 比作一个“部门”工具是“办公设备”提示词是“临时口头指令”而技能是“标准作业程序SOP”。SOP 里会写清楚什么情况下启用、按什么步骤执行、需要用到什么设备这正是 SKILL.md 在做的事。从开发视角看四者的边界也很清晰概念本质生命周期是否可复用提示词模板一段对话/指令文本随会话存在弱难以结构化复用工具可调用的函数/API由代码管理强纯粹复用技能元信息 指令 可选脚本独立文件可版本化强目录即封装Agent调度技能的运行载体整个应用组合层编排为主理解了这个分层你就明白为什么别再拿提示词模板当技能了技能是 Agent 能力的“封装单元”提示词只是封装内部的一部分内容。2. SKILL.md 的结构拆解每一部分到底在解决什么问题2.1 名称域命名规范直接决定技能能否被发现先看一个实际的 SKILL.md 结构。通常开头是 YAML frontmatter--- name: code_review description: 在代码提交、MR/PR 评审或故障复盘时对代码进行质量、安全性、可维护性逐项检查并输出结构化评审报告。适合用户提供代码片段、仓库 diff 或文件路径的场景。 ---name是技能的唯一标识看起来只是一个字符串但其实它承担了两个功能一是作为 Agent 路由时的匹配锚点二是作为日志/调试时定位问题的 ID。因此命名必须遵守几个硬规则用英文字母、数字、下划线不用空格和特殊符号保持全局唯一避免两个技能同名导致路由冲突名字要短一眼能看出职责比如sql_expert、data_cleaner不要写skill_for_checking_data_quality_and_cleaning。我在实际项目里见过最典型的命名问题是“两套名字”代码里叫fetch_weatherSKILL.md 里叫天气查询技能frontmatter 写的又是WeatherSkill。这种不一致会直接导致 Agent 路由时出现混乱排查起来非常费劲。建议把 name 当作代码变量来约束大小写风格统一我习惯全部小写 下划线。2.2 描述域决定 Agent“什么时候想起用你”如果说 name 是技能 ID那么 description 就是技能的“广告词”和“触发条件”。模型在运行时会拿用户的请求和所有技能的 description 做语义匹配所以 description 写得好不好直接决定了技能会不会被正确调用。很多人在这里犯的错是写得太抽象比如“提供代码审查服务”模型遇到“帮我看看这段代码有没有问题”时就可能对不上技能就永远不触发。好的 description 至少包含三个要素触发场景、输入类型、输出结果。我的习惯是写成一个“当……时用于……最终输出……”的句式。举个例子description: 当用户要求审查代码质量、检查安全隐患、评审 MR/PR、或复盘故障代码时使用。输入可以是代码文本、diff 或文件路径。输出包括问题清单、严重级别、修复建议和修改示例。这句话把“什么情况下用”“喂进去什么”“还给什么”都讲清楚了。模型在路由阶段不需要看技能全部内容只需要这个描述能和用户意图对得上。所以描述里要放贴近用户口语的关键词比如“看看代码”“评审一下”“这代码行不行”甚至可以直接给出几个典型说法让匹配率更高。2.3 指令域给模型一套可执行的步骤而不是一篇散文frontmatter 下面是正文部分这部分是技能执行时模型会完整读取的指令。很多人的正文是在写作文长篇大论描述“你应该如何成为一个优秀的评审者”但模型读完还是不知道第一步该干什么。正确的写法是给一套带顺序的操作流程可用步骤编号、条件分支、检查表来组织。# 代码评审执行流程 1. 先把输入代码拆解为“功能实现”“错误处理”“安全性”“性能”四个维度。 2. 逐维度检查每发现一个问题记录问题位置文件行号、问题类型、严重级别高/中/低、修复建议。 3. 如果安全维度没有发现任何问题也要输出一句“未发现安全风险”的确认。 4. 最终按“总体评价 / 问题清单 / 修复建议摘要”三段结构输出 Markdown 报告。 ## 严重级别判定规则 - 高可能导致数据泄漏、崩溃、核心逻辑错误。 - 中存在明显性能隐患或异常处理缺失。 - 低风格、命名、可读性问题。 ## 禁止事项 - 不要逐行复述代码只列问题。 - 不要跳过安全维度即便没有发现问题。你能看出这段指令和提示词模板的差别在哪它定义了执行路径、输出结构、判定标准、禁止项。模型照着走就能稳定产出同格式的结果。正文还有一个重要原则尽量少让模型“自由发挥”。有节奏的、可判定的规则越多输出方差越小这是技能工程和提示词工程最大的不同。3. 实操手写一个可用的 SKILL.md 技能完整流程3.1 第一步选场景、定边界、画流程动手写之前先不要碰文件先把场景想清楚。我以一个“代码评审”技能为例讲讲我的思考过程。首先是场景什么情况下用户会用到这个技能可能是临时贴一段代码可能是审查一个提交 diff也可能是查看某个文件后直接要求评审。这三个场景输入不同、上下文不同都要在描述里体现。然后是边界这个技能不做修复只输出建议不接收整个仓库只处理用户明确提供的内容。边界画得越清楚模型越不会越权。最后是流程接收代码 → 确定评审维度 → 逐项检查 → 输出报告。我在纸上把这个流程画出来注意这里不要画什么花哨的图直接写步骤和分支条件就够了。流程里我会特别标出两个容易失控的点第一模型容易沉迷于“逐行讲解代码”所以必须写明“不要逐行复述”第二模型容易在没发现问题时硬凑问题所以必须要求写“未发现安全风险”这类明确确认。3.2 第二步搭建目录、写 SKILL.md 正文我把上面的设计落实到文件。先创建目录skills/code_review/然后写SKILL.md。完整的初始化版本大概是这样--- name: code_review description: 当用户要求审查代码质量、检查安全隐患、评审 MR/PR diff 或复盘故障代码时使用。输入支持代码文本、diff 文本、文件路径。输出结构化评审报告包含问题清单、严重级别、修复建议。 --- # 代码评审技能 你是一个严谨的资深代码评审专家。用户可能直接贴代码、贴 diff或者给文件路径。无论哪种输入都按下面流程执行。 ## 执行步骤 1. 明确输入范围如果是文件路径先读取文件如果是 diff以改动行为主如果是完整代码全量检查。 2. 按四个维度逐项检查功能实现、错误处理、安全性、性能。每个维度至少确认一次不能跳过。 3. 记录问题每条问题包含位置、类型、严重级别、修复建议。修复建议要给出可落地的改法优先给出代码示例。 4. 输出报告结构固定为 ### 总体评价 用 3-5 句话概括代码质量说明主要风险和整体可维护性。 ### 问题清单 | 位置 | 类型 | 严重级别 | 问题描述 | 修复建议 | | ---- | ---- | ---- | ---- | ---- | ### 修复建议摘要 按严重级别排序给出最重要的三条修复动作。 ## 约束 - 不要逐行复述代码。 - 不修改代码只输出建议。 - 安全维度无问题时必须显式输出“未发现安全风险”的确认语句。 - 如果输入信息不足比如缺少文件内容先向用户索要不要猜测。这段正文有几个细节值得注意。第一我用“固定结构”约束输出格式表格的列都规定好了模型产出的结果天然适合人读或程序解析。第二我在约束里写了“输入信息不足时先索要”这是很多技能漏掉的一点模型在信心不足时倾向于瞎猜给它一条官方“退路”可以显著减少幻觉。第三没有在正文里堆砌关于“你是一个优秀评审者”的废话因为那部分对执行结果的改善非常有限。3.3 第三步挂载工具让技能真正“干得了活”纯文本指令可以让模型输出好但很多任务必须执行代码才能完成。就拿代码评审来说如果用户给的是一个仓库目录模型不可能把几百个文件全读一遍。这时候技能需要绑定脚本。我在scripts/下放了一个轻量脚本collect_diff.py用来把 git 工作区的改动收集成统一格式供模型分析。#!/usr/bin/env python3 收集 git 工作区改动输出为便于模型分析的 compact diff。 import subprocess import sys def get_diff(baseHEAD): result subprocess.run( [git, diff, base], capture_outputTrue, textTrue, encodingutf-8, ) if result.returncode ! 0: print(result.stderr) sys.exit(1) return result.stdout def main(): diff_text get_diff() if not diff_text.strip(): print(没有检测到改动。) return # 限制单次输出长度避免超长 diff 撑爆上下文 max_chars 8000 if len(diff_text) max_chars: print(fdiff 较大截取前 {max_chars} 字符。完整内容请分批评审。) diff_text diff_text[:max_chars] print(diff_text) if __name__ __main__: main()为什么脚本要限制输出长度因为我踩过坑一个大仓库的 diff 可能有几十万字符直接塞进上下文token 消耗爆炸模型还会在长内容里丢失重点。所以脚本主动截断并且告诉用户“分批评审”。这里你也能看出来脚本的价值是替模型处理“笨重”的事让模型专注于判断和产出。脚本写好后我还做了一个小动作在 SKILL.md 正文的“执行步骤”第 1 步补充说明“如果需要查看 diff运行python3 scripts/collect_diff.py获取改动内容”然后让脚本文件放在技能目录的scripts子目录下。这样模型在读完 SKILL.md 后就知道有这个工具、何时调用、怎样调用。3.4 第四步测试、观察、迭代这个步骤很多人图省事直接跳过但恰恰是它决定了技能上线后好不好用。我的测试方法不复杂准备三组输入覆盖技能的核心场景、边缘场景、反向场景。第一组核心场景贴一段明显有 bug 和安全隐患的代码看看模型有没有按四个维度检查、输出的表格格式对不对。第二组边缘场景给一个空文件、或一个非常短的代码片段看模型会不会犯傻硬凑问题。第三组反向场景只问“帮我写个快速排序”看模型会不会错误触发代码评审技能。如果反向场景触发了说明 description 里的触发条件没写清楚需要加上“仅当用户要求审查、评审已有代码时使用”这类限定语。测试完我发现一个很有意思的问题模型在输出“修复建议”时有时会给出只改一半的伪代码。我的对策是在约束里加了一条“修复建议必须是可以直接粘回原文件的完整片断禁止省略号”。这是很细节的地方但对实际使用体验影响极大。整个迭代过程我跑了三轮每次只改 SKILL.md 的一个点然后重新验证直到核心、边缘、反向三个场景都通过。4. 实战中的坑排查技巧与避坑指南4.1 技能没被调用先检查 description而不是正文我见过最多的反馈就是“技能写好了但 Agent 从来不调用”。很多人第一反应是去改正文把指令写得更细但问题往往不出在正文而出在 description。模型路由时只读 frontmatter 里的描述正文是触发之后才加载的。所以如果描述和用户请求匹配不上正文写得再完美也没用。排查方法很简单把用户的原始问题和技能 description 放在一起用肉眼判断语义重合度。重合度低就重写 description用更接近用户口语的表达。比如把“进行代码质量与安全审查”改成“当用户说‘帮我看看代码’‘评审一下’‘这个 PR 有问题吗’时使用”。有时候再加几个示例询问方式触发率会立刻上来。还有一个隐蔽坑某些框架把 description 的长度截断太长的时候模型只能看到开头。所以一定要把最重要的触发场景写在 description 的前半句不要把关键信息放到第二句以后。我自己的习惯是控制在 150 字以内一句场景、一句输入、一句输出。4.2 技能多了以后互相干扰怎么办技能数量超过十个以后干扰是必然出现的。表现是请求明明该走技能 A结果模型调用了技能 B或者一个请求同时被两个技能的 description 匹配中输出混乱。这个问题我在一个运维技能库里踩得很深后来总结了几个行之有效的策略。第一是“命名空间隔离”同类技能统一前缀比如日志类叫log_analyze、log_rotate、log_compress让 name 本身带上分类信息路由时定位更快。第二是“描述负例化”在 description 末尾明确写“不要用于……”把容易混淆的场景排除掉。比如日志压缩技能的描述里可以加“如果是分析日志内容请使用 log_analyze”。第三是“控制技能加载数量”框架如果不支持按需加载而是一次性把所有技能描述都塞给模型那技能越多路由准确率越低。这种时候要考虑把相似技能合并为一个或者给 Agent 设计两级路由先按领域选技能包再在包内选具体技能。4.3 技能文件越写越大上下文和成本都失控技能正文太长是另一个高频问题。模型触发技能后会把整个 SKILL.md 加载进上下文如果你的技能写了上万字每一次触发都在烧 token而且长文本会稀释指令权重导致关键约束反而被忽略。解决思路是“分层存放按需加载”。SKILL.md 正文只保留核心执行流程和判定规则像风格指南、详细示例这类参考资料放到references/子目录并在正文里注明“如果涉及具体命名规范先读取 references/style_guide.md”。这样不相关的场景就不用加载参考资料上下文开销自然降下来。其次要控制正文里的“装饰内容”比如对“你是一个优秀专家”的人格化描述、大段背景介绍。这些内容对任务执行几乎没有帮助删掉之后模型照样能干活但 token 成本会明显下降。我经手的一个技能从 4000 字压缩到 1200 字后输出质量没有下降单次调用成本却降了接近一半。4.4 版本管理和团队协作技能也是代码既然技能是文件它就该按代码的规格来管理。我刚用 SKILL.md 时吃过一个亏团队成员改了技能正文但没留任何说明结果线上行为变了没人知道是哪次改动引起的。后来我立了几条规矩效果很好。第一技能目录进 Git和主代码一起走 MR/PR 流程。改动必须描述清楚“改了什么行为、为什么要改”。第二每个技能目录里维护一个CHANGELOG.md按日期记录每次变更。第三把第三小节里的三组测试场景固化成测试用例存在一个tests/目录里任何技能改动后都要跑一遍回归。我不追求自动化测试完美覆盖所有场景但至少核心、边缘、反向三类用例能挡住 80% 的回归问题。版本管理还有一个容易被忽略的好处它可以倒逼团队养成“先想清楚再动手”的习惯。没有版本控制时改技能文本是很随意的事有了提交记录和评审过程每个人都会更谨慎这比任何规范文档都管用。4.5 最后分享几个我常用的调试小技巧写到这里再送几个实战中帮我省了不少时间的小技巧都是常规文档里不会写的。一是开调试日志。把框架里技能路由的部分打开看模型最终匹配到哪个技能以及匹配的置信度。大部分 Agent 框架都把这类日志藏在 debug 模式下找到它你就不用靠猜来定位问题了。二是“独立跑技能组件”。不要每次都启动完整 Agent 来测试技能尤其调试脚本时。我经常直接用命令行跑python3 scripts/collect_diff.py先确认脚本本身没问题再回到 Agent 环境测试问题定位范围会一下子缩小很多。三是“给 description 留一条测试路”。我习惯在 description 最后写一句“如果用户要求测试本技能请输出 TRIGGER_TEST ”这样我可以直接在对话里输入“测试技能”来验证技能的触发链路比造一堆复杂用户请求快得多。四是“先让用户看到你”。这句话是说给模型听的约束我在很多技能正文里都会加一条执行完成后先给用户一个简短摘要再给详细内容。因为有的时候模型输出太长用户翻不到关键结论加了这个约束后整个交互体验会好很多。我在实际项目中还有一个体会SKILL.md 的迭代其实没有终点。刚写出来能跑不代表它一直好用框架升级、模型换版本、用户使用习惯变化都会影响技能的触发准确率和输出质量。我的做法是每个月抽查一次线上日志看看哪些技能命中率低、哪些技能输出被用户反复修改然后针对性调整 description 和正文。真正好用的技能库不是一次写出来的是长期打磨出来的这个心态比任何技术细节都重要。