Agent Skill开发实战:SKILL.md、scripts、references三件套全解析

Agent Skill开发实战:SKILL.md、scripts、references三件套全解析 最近后台收到不少朋友问同一个问题Agent Skill到底怎么开发网上聊概念的文章很多但真正能照着做、能落地的不多。我自己前前后后写出了十几个Skill踩过不少坑有被模型气到摔键盘的时候也有做出来之后自己都惊讶“原来可以这么顺”的时候。今天想把我目前觉得最实用的一套方法整理出来核心就三个词SKILL.md、scripts、references。这篇文章不是什么“从零入门”而是直接面向“你已经知道Skill是个什么东西现在要动手写一个能用、好用的Skill”的人。我会用一个贯穿全文的真实案例——接口测试用例生成Skill带你把三步走完顺便把联调测试、环境排错这些文档里不写的东西也讲透。如果你正准备开发自己的第一个Skill或者已经写了一个但发现模型总是“不听话”这篇应该能帮上忙。1. Skill到底是个什么东西SKILL.md、scripts、references分别解决什么问题很多人第一次接触Skill最先困惑的不是“怎么写”而是“Skill和Agent、和普通的Prompt到底有什么区别”。这个想不明白后面怎么写都觉得别扭。1.1 Skill不是Agent也不是普通的PromptAgent是一个完整的执行流程它负责理解目标、拆解任务、调用工具、组织结果是一个“总调度”的角色。而Skill更像是Agent手边的一个“专业工具包”里面装好了对应的说明书、工具脚本和参考资料。Agent可以按需打开这个工具包调用里面的能力但Agent本身不等于这个工具包。普通Prompt是一次性的你跟模型说一段话它按照这段话执行说完就完了。但Skill是“可复用、粒度高、面向特定任务”的能力单元。它的价值在于把一类经常要做的任务沉淀成标准化的文件结构下次Agent遇到类似需求直接读取这个Skill不需要你重新组织一大段提示词。热词里经常有“skill插件”“skill脚本”的说法其实指的就是这个工具包的形态。你可以把它理解为给Agent安装了一个“领域专家模块”这个模块不是凭空让模型变聪明而是给它配上了专用的说明书、脚本和参考资料。1.2 三件套的分工逻辑一个标准的Skill目录结构长这样my-skill/ ├── SKILL.md # 说明书定义什么时候用、怎么用 ├── scripts/ # 可执行脚本负责确定性计算和文件处理 └── references/ # 参考资料补模型的知识盲区这三者的分工我习惯用一个“新员工入职”的例子来解释。SKILL.md 就是岗位职责手册。它告诉模型你是干什么的在什么情况下你要出手具体按什么步骤干活哪些事情绝对不能做。模型每加载一个Skill第一时间读的就是这个文件。scripts 就是你的工具箱里的专用工具。有些活是模型不擅长的比如解析一个巨大的JSON文件、做精确的数学计算、批量转换文件格式、调用外部API。这些事情让模型硬想它容易出错也容易慢但你给它一把顺手的好工具它就能又快又稳地完成任务。references 就是参考资料架。模型训练时不可能背下你们公司内部的所有规范、所有字段含义、所有历史模板。你把这些东西整理成文件放在references里模型干活的时候随时能翻。所以一句话总结这三步SKILL.md负责让模型“知道怎么干”scripts负责让模型“干得动”references负责让模型“干得对”。很多人的Skill不好用问题就出在这三者失衡上——要么SKILL.md写得太含糊要么scripts压根没有要么references放了一堆模型不会去读的废文件。2. 第一步把SKILL.md写成模型一看就会执行的说明书SKILL.md是Skill的入口也是决定整个Skill成败的一半。写不好它后面scripts和references做得再漂亮模型也不知道该怎么用。2.1 frontmatter决定模型“什么时候想起你”SKILL.md开头的YAML frontmatter最重要的字段是name和description。name是Skill的名字这个随意但建议用能体现任务类型的英文短横线命名比如api-test-case-generator、log-analyzer。真正值得花心思的是description。我的经验是description不是写给读者看的是写给“模型的检索系统”看的。模型在收到用户请求之后会扫描所有可用Skill的description判断哪个Skill与当前任务匹配。所以描述里必须包含下面几类信息这个Skill是做什么的核心能力什么情况下该用它触发场景用户的什么话术可能意味着需要你典型触发词我见过太多人把description写成“A skill for generating API test cases.”这太笼统了。模型看到这句话根本判断不出来“用户说帮我查一下这个接口的参数边界”到底算不算你的触发场景。我建议的写法是--- name: api-test-case-generator description: 根据OpenAPI/Swagger文档或接口描述生成覆盖正常、异常、边界场景的接口测试用例。当用户上传接口定义文件、粘贴接口参数、或要求补充/审查API测试用例时使用。 license: MIT metadata: version: 1.0.0 ---注意这里把“上传接口定义文件”“粘贴接口参数”“要求补充/审查测试用例”都写进去了。这些都是用户可能会用到的表达模型读到这些关键词会更准确地把你“召唤”出来。2.2 正文用“给实习生写SOP”的方式描述行为frontmatter决定模型“什么时候用你”正文决定模型“怎么用你”。正文的写作标准我强烈建议你想象成你招了一个靠谱但缺少业务经验的实习生你要写一份SOP让他照着执行。他会死抠字眼他会逐条执行但他不会“领会精神”。所以正文里最好包含以下章节何时使用再次强调触发条件防止模型误用。使用步骤用最明确的祈使句一步一步拆解第一步做什么、第二步做什么。脚本调用说明告诉模型scripts里有哪个脚本、怎么调用、传什么参数、输出什么格式。输出要求明确输出的组织方式比如必须包含哪些字段、用什么模板。注意事项把容易踩的坑提前写死比如“不要修改用户原始文件”“期望状态码不要一律写成200”。拿我们案例里的接口测试用例生成Skill正文可以这样写# API测试用例生成 负责将用户提供的接口定义转化为结构化、可直接执行的测试用例表。 ## 何时使用 - 用户上传 OpenAPI 3.0 / Swagger 2.0 的 JSON/YAML 文件或粘贴接口定义片段。 - 用户要求“给这个接口补几个测试用例”“检查一下用例覆盖是否完整”。 - 测试报告评审时需要反向列出缺失的边界用例。 ## 使用步骤 1. 解析接口定义提取 method、path、参数列表、参数约束required、enum、minimum、maximum、format。 2. 调用 scripts/generate_cases.py传入接口定义文件路径拿到候选用例 JSON。 3. 结合 references/boundary_rules.md 中的边界值规则对脚本输出进行补充和修正。 4. 按 references/use_case_template.md 的模板整理最终测试用例表。 5. 输出时确保每条用例包含编号、标题、前置条件、请求体、期望状态码、断言点、优先级。 ## 脚本调用方式 bash python scripts/generate_cases.py openapi.yaml -m get脚本输出是 JSON 数组每个元素包含 path、method、param、case_type、value 字段。注意事项只读用户的接口定义文件不做修改。参数格式不合法如 date、email、uuid时必须生成对应的“格式异常”用例。期望状态码必须符合 HTTP 语义GET 成功是 200POST 成功可以是 201校验失败是 400 或 422。禁止输出只覆盖 happy path 的用例集至少包含正常、异常、边界三类。你可能会觉得这里面的要求太多了模型会不会被束缚住我的经验正相反。模型是不怕约束的它怕的是模糊。你把规则写清楚它执行起来又快又稳定你写“输出要尽量全面”它反而不知道“全面”到底意味着几条用例。 ### 2.3 SKILL.md写作的常见错误与纠偏 写SKILL.md最常犯的几个错误我一个个说 **第一把SKILL.md写成教程。** 这个文件不是教模型“接口测试是什么”的而是告诉模型“现在具体怎么操作”的。凡是模型本来就会的知识不要在SKILL.md里科普凡是模型容易犯的错才值得写进去。 **第二步骤太粗。** 只写“生成测试用例”不叫步骤那叫目标。步骤要细到“解析接口定义提取…然后用脚本生成…再结合规则修正…最后按模板输出”每一步都配有可验证的输出物。 **第三没有写“别做什么”。** 我一开始写SKILL.md也总是只写正面要求后来发现模型特别擅长在边界处自由发挥。比如你只写“生成测试用例”它可能真的把用户上传的接口文件给改了。所以SKILL.md里一定要有“注意禁忌”这块。我后来养成的习惯是每一次测试时只要发现模型做了不该做的事就立刻把这条写进SKILL.md的注意事项里。这个文件是活的不是写一遍就完事。 ## 3. 第二步scripts是把“确定性工作”从模型手里交给代码 SKILL.md写得再好也改变不了一个事实模型在处理确定性任务时可靠性远不如代码。什么是确定性任务就是输入相同、输出必定相同不靠“理解”就能完成的工作比如解析JSON、遍历目录、批量替换字符串、做数值校验。这些任务交给模型做它可能这次对下次错交给代码做它100次都是对的。 ### 3.1 脚本选型与目录约定 scripts目录里放什么语言主要看你的使用环境和Skill的目标用户。我自己用得最多的是Python因为它在数据处理、接口调用、文本解析上生态最全而且大多数做Agent开发的人机器上都装了Python。如果你的场景里Shell更顺手比如要批量处理文件、调用系统命令那用Shell也完全没问题。还有一些Skill会用Node.js比如依赖前端生态的代码生成类Skill。 目录约定上我建议所有可执行脚本统一放在scripts下不要让脚本散落在Skill根目录或者references里。原因很简单SKILL.md里要给模型写明“调用scripts/generate_cases.py”路径越规整模型越不容易找错。 ### 3.2 面向模型的脚本接口设计 给模型用的脚本和你自己平时写的脚本有一个很大的不同调用方不是“人”而是“会读文档的大语言模型”。模型通过SKILL.md里的说明来调用脚本它不会像人一样灵活应对意外情况。所以脚本接口设计要遵循几个原则 **参数越少越好。** 最好只接收一两个位置参数比如输入文件路径可选的过滤条件。参数一旦超过三个模型就可能传错、漏传。 **不要做交互式输入。** 脚本里不要出现input()不要等待用户确认。模型没有“交互”这回事一旦脚本卡在等待输入整个任务就挂死了。 **输出用JSON等结构化格式。** 模型的强项是理解自然语言弱项是解析非结构化的纯文本。你把结果用JSON打出来模型解析起来非常轻松。如果输出的是大段日志也要尽量用“KEY VALUE”或JSON lines这种稳定可拆解的格式。 **明确退出码。** 脚本失败时除了打印报错还要用sys.exit(1)返回非0退出码。模型看到退出码才能判断“脚本出错了我应该换一种方式”。如果脚本出错还是返回0模型会误以为执行成功然后把错误结果当成正常结果输出。 ### 3.3 一个可直接改来用的generate_cases.py示例 下面这个脚本是给接口测试用例生成Skill配套的。它读一个OpenAPI文件提取关键参数约束生成一组候选边界用例。逻辑不算复杂但足够说明“什么样的脚本适合放进Skill”。 python #!/usr/bin/env python3 根据OpenAPI文件生成候选接口测试用例。 import argparse import json import sys from pathlib import Path try: import yaml except ImportError: yaml None def load_openapi(path: Path) - dict: if path.suffix.lower() in (.yaml, .yml): if yaml is None: raise SystemExit(缺少依赖请先 pip install pyyaml) return yaml.safe_load(path.read_text(encodingutf-8)) return json.loads(path.read_text(encodingutf-8)) def base_cases_for_param(param: dict) - list: 根据参数约束生成候选用例值。 cases [] ptype param.get(schema, {}).get(type, string) if ptype string: enum param.get(schema, {}).get(enum) if enum: cases.append((valid_enum, enum[0])) cases.append((empty_string, )) cases.append((too_long, a * 1024)) elif ptype in (integer, number): schema param.get(schema, {}) minimum schema.get(minimum) maximum schema.get(maximum) if minimum is not None: cases.append((min_value, minimum)) if maximum is not None: cases.append((below_min, minimum - 1)) if maximum is not None: cases.append((max_value, maximum)) cases.append((above_max, maximum 1)) return cases def main(): parser argparse.ArgumentParser(description从OpenAPI生成候选测试用例) parser.add_argument(spec, typePath, helpOpenAPI文件路径) parser.add_argument(-m, --method, help只处理某个HTTP方法如get) args parser.parse_args() spec load_openapi(args.spec) result [] for path, item in spec.get(paths, {}).items(): for method, op in item.items(): if method not in (get, post, put, delete, patch): continue if args.method and method.lower() ! args.method.lower(): continue for param in op.get(parameters, []): for kind, value in base_cases_for_param(param): result.append({ path: path, method: method.upper(), param: param.get(name), case_type: kind, value: value, }) print(json.dumps(result, ensure_asciiFalse, indent2)) sys.exit(0) if __name__ __main__: main()注意几个细节。第一脚本用argparse接收参数这样模型在SKILL.md里看到的调用说明就很清晰你还可以直接写“python scripts/generate_cases.py spec文件 -m get”这种示例。第二YAML解析不是Python标准库所以脚本开头做了try/except并提示缺少依赖时怎么解决。第三所有输出最后通过json.dumps打印到标准输出模型能直接拿到结构化数据。模型拿到这个JSON之后再结合references里的规则做补充和裁剪整个流程就很顺畅了。这就是“确定性工作交给脚本判断和补全交给模型”的典型例子。4. 第三步references是模型的外接硬盘不是摆设三件套里references是最容易被忽略的一个。很多人要么不放参考资料要么一股脑塞进去一堆文件然后指望模型自己翻。这两种都不对。references做得好能让Skill的效果上一个台阶做得不好反而会拖慢模型、误导输出。4.1 references里该放什么一句话放那些模型训练时大概率没见过或者见过但记不准而当前任务又必须依赖的信息。放在接口测试用例生成Skill里典型的内容有边界值规则不同数据类型数值、字符串、日期、邮箱、UUID的边界取值约定。模型可能知道“字符串要测超长”但它不一定知道你们团队约定“超长1024字符以上”。状态码语义参考不同方法、不同错误类型对应的HTTP状态码。模型知道200是成功但201、400、404、422、409怎么选它经常搞混。用例输出模板规定最终输出长什么样的Markdown模板模型直接照着填格式统一。历史样例一到两个完整的、质量高的用例样例模型可以模仿风格。注意是“样例”不是“模板”样例的价值是让模型看到成品形态。团队内部术语表如果你们团队对某些字段有特殊叫法也放进去。这些内容的核心价值在于把模型“可能知道但不够准确”的知识替换成“我们验证过的、标准化的知识”。模型不需要去记忆这些细节它只需要在需要时来references里查。4.2 用INDEX.md告诉模型“先读谁再读谁”只把资料堆在references里还不够你还要告诉模型“先翻哪个、后翻哪个”。我强烈建议每个Skill的references目录下都放一个INDEX.md相当于这个资料架的索引卡。以接口测试用例生成Skill为例INDEX.md可以写成这样# references 使用指南 - boundary_rules.md必读。生成数值、字符串、日期、特殊格式字段用例前先查对应规则。 - use_case_template.md必读。所有最终输出的用例表必须按此模板组织。 - http_status_guide.md生成期望状态码时参考。 - samples/仅供模仿风格不要复制其中的具体接口字段到用户输出中。为什么这个索引很重要因为模型不会被动的“通读所有参考资料”它更倾向于按需检索。你给它一个“先读谁”的顺序它就不会在6个文件里纠结该看哪个。而且你可以在SKILL.md的使用步骤里直接引用这里的文件名让模型每次执行任务时都“先看INDEX.md”形成固定路径。4.3 目录规模与文件格式的实战建议references不是越大越好。我见过有人把一个几百页的内部文档直接塞进references结果是模型每次处理任务时都要消耗大量上下文去扫描这些资料有时还会被里面不相关的细节带偏。我的经验是每个参考资料文件尽量控制在几十到一百行以内。如果需要放的信息量很大就拆成多个专题文件而不是一个大杂烩文件。总文件数控制在十个以内。太多的话模型选择成本高INDEX.md也压不住。优先放Markdown/TXT/JSON这类文本格式。PDF、Word这类二进制格式模型读取能力受限也不是所有客户端都能解析。必要情况下可以先转成Markdown再放进来。每个文件的开头用一两句话说清“这个文件是干什么的”。这样模型打开文件后能立刻判断是否需要深入阅读。另外提醒一句references不是只建一次就不管了。随着你测试Skill、发现模型频繁在某个细节上犯错你应该把“正确答案”写进references里把这条错误从模型的行为中矫正过来。用这个方式迭代比你改十版SKILL.md都快。5. 联调测试与问题排查从能跑到好用差的都是细节Skill写完了只是第一步。真正让Skill变得可靠靠的是反复测试和排错。这块是我花时间最多的地方也是网上教程基本不讲的。5.1 本地调试的完整流程我自己调试一个Skill基本按照下面的流程走第一步准备一组稳定的测试素材。不要每次测试都临时找输入。接口测试Skill就准备一份小型的、覆盖多类参数约束的OpenAPI文件比如包含字符串、数值、枚举、可选参数各一种。日志分析Skill就准备一份包含INFO、WARN、ERROR、堆栈的样例日志。让每轮测试都在同一组输入上跑你才能对比出改动前后的行为差异。第二步从“最简链路”跑起。第一次测试只验证三件套里的核心链路模型能不能因为一个标准输入触发Skill触发后能不能按SKILL.md里的步骤调用scripts里的脚本脚本能不能正常输出。这三步任何一个卡住先解决掉再往下进行。第三步扩大边角输入。核心链路跑通后开始给模型“挑刺”。比如给接口测试Skill一个不含paths的OpenAPI文件或者一个参数约束极少的接口看模型会不会懵会不会拒绝执行或者胡编用例。第四步回归所有历史问题。我自己有一个“问题清单”每发现一次模型错误就记下来修复后在下一次回归中重点验证这个点。不这么做的话经常会出现“修好了A又引入了B”的尴尬。5.2 高频问题排查速查表以下是我开发Skill过程中遇到频率最高的几类问题整理成一张速查表现象可能原因排查方向模型完全不解触发SkillSKILL.md的description写得过窄或关键词缺失检查description是否覆盖了用户可能的口语化表达模型频繁误触发Skilldescription写得过宽什么内容都能沾边收紧description明确写出“不做什么”模型不按SKILL.md的步骤走正文步骤太粗、没有顺序编号拆成可验证的小步骤每一步配输出要求模型调用脚本时传参错误SKILL.md里没有给示例命令在SKILL.md中明确写一行可复制的调用示例脚本执行报错ModuleNotFoundError脚本依赖没有安装在SKILL.md的注意事项中写明依赖安装命令模型不读references缺少INDEX.md入口或SKILL.md没有强调在SKILL.md步骤里显式写“先读references/INDEX.md”模型输出的用例只有正常路径SKILL.md缺少对异常、边界用例的强制要求在“注意事项”里明确写“禁止只覆盖happy path”这七类问题我几乎在每前几个Skill里全踩过。后来养成的习惯是每写完一个Skill先把它扔给最笨的场景去测一边。我所谓的“最笨”就是故意用模糊、极端、缺参数的输入去测。这样测出来的问题比正常输入多得多。5.3 几个容易踩的环境坑除了Skill本身逻辑的问题开发环境里也有几个高频坑我单独拿出来说。一个坑是pnpm安装依赖时出现[err_pnpm_ignored_builds] ignored build scripts。这个报错通常出现在你用pnpm安装Node.js依赖时pnpm从10.x开始默认忽略依赖包的postinstall脚本包括core-js、esbuild、parcel/watcher这些常见包。后果是某些依赖安装不完整运行时缺少二进制文件。解决方法是在package.json里显式声明允许哪些包执行构建脚本{ pnpm: { onlyBuiltDependencies: [esbuild, core-js] } }或者直接在项目目录运行pnpm approve-builds交互式选择允许的包。这个问题在开发基于Node.js生态的Skill时特别容易遇到我的经验是不要直接忽略它因为后面总会在某个奇怪的地方冒出来。还有一个坑是PyCharm执行脚本时终端提示类似e:\ip_location_tool\.venv\scripts\python.exe的路径然后报错。这类问题最常见的原因有三个一是你没有在PyCharm里正确配置Project Interpreter导致它用了默认解释器二是虚拟环境里的依赖没装齐脚本import环节就挂了三是有些脚本依赖当前工作目录下的相对路径文件但PyCharm默认的工作目录可能跟你预期的不一致导致FileNotFoundError。排查思路也很简单先确认PyCharm的Settings里Python Interpreter指向的是项目里的.venv解释器然后在Terminal里手动执行.\\.venv\\Scripts\\activate激活环境再手动跑一次脚本看报错信息是哪一个环节。绝大多数问题手动跑一次就能定位。6. 完整实战从一个需求到一个可测试的Skill最后我把整个流程串起来完整走一遍接口测试用例生成Skill的开发过程。你可以把这部分当作一份“抄作业模板”。6.1 需求整理与目录初始化先明确需求希望有一个Skill能根据OpenAPI接口定义自动生成覆盖正常、异常、边界的测试用例。目标用户是测试工程师输入是接口文档输出是结构化测试用例表。需求明确后建目录结构api-test-case-generator/ ├── SKILL.md ├── scripts/ │ └── generate_cases.py └── references/ ├── INDEX.md ├── boundary_rules.md ├── use_case_template.md └── http_status_guide.md这里我特意把references里要放哪几个文件先想清楚而不是写到一半再补。6.2 编写SKILL.md与references把前文示例里的SKILL.md内容填进去。这里补充一下references里部分内容的写法。boundary_rules.md的开头可以这样写# 边界值规则 本文件定义接口测试用例中不同数据类型的边界取值规则。 生成数值、字符串、日期、格式类字段用例时遵循以下约定 - 数值型取最小值、最大值、最小值-1、最大值1至少4条用例。 - 字符串型空字符串、超长字符串长度1024、包含特殊字符%[]等。 - 日期时间格式错误、时区缺失、闰年2月29日。 - 邮箱/UUID格式非法、大小写混用、含空格。use_case_template.md给出最终输出的模板# 测试用例模板 每条用例必须包含以下字段按表格输出 | 编号 | 标题 | 前置条件 | 请求体/参数 | 期望状态码 | 断言点 | 优先级 | | --- | --- | --- | --- | --- | --- | --- | - 编号规则TC_模块_序号 - 优先级P0为阻塞级P1为高优先P2为常规。http_status_guide.md列一个简表GET成功200POST创建成功201参数校验失败400或422资源不存在404未认证401无权限403服务端异常500这些内容模型本来“差不多知道”但通过references把规则固定下来后输出质量就稳定了。6.3 联调验证与回归测试写完后我准备了一份测试用的OpenAPI文件包含一个/users接口和一个/orders接口前者有必填的name字符串和可选的age数值后者有枚举类型status。然后跑了几轮测试。第一轮我直接上传这个OpenAPI文件并要求“帮我生成测试用例”。模型成功触发Skill调用脚本拿到了候选值列表然后按照references里的模板组织输出。但我在检查输出时发现一个Bug脚本生成的“below_min”和“above_max”用例模型直接照抄进最终用例没有把“期望状态码”改成400。也就是说模型知道这条用例是异常场景但状态码写成了200。这个问题在没加references之前反复出现加了http_status_guide.md之后模型明显学会了把越界值用例的状态码改成400。我又试了一轮“不传参数类型”的极端输入只给一个基本的接口描述没有完整OpenAPI文件。这时候模型依然能工作但依赖的是它自己对接口测试的理解不再走脚本。这个行为是合理的但输出格式偶尔会跑偏。后来我在SKILL.md里加了一句“当没有接口定义文件时要求用户提供字段清单或基于用户描述自行推断参数并在输出中标注‘推断参数’”。这轮改进之后极端场景下输出格式也稳定了。整个联调过程我一句话总结每次测试都要带着“找茬”的心态去跑不要验证一次能跑通就觉得完事了。写Skill这件事做得越多越有一个体会出来一个Skill好不好用80%取决于你愿不愿意在SKILL.md和references里把话说绝、说透。别怕约束太多模型不会嫌你啰嗦它只会感谢你让它少犯错。如果你现在正被某个Skill搞得一头火不妨试着把“使用步骤”拆成更小的动作给每个动作配上一条输出要求再去跑一轮测试十有八九会有改善。你说到底Skill的开发就是一个“你替模型把所有边界都想好”的过程。