
做 AI Agent 开发这段时间我踩过最多的坑不是模型不会答而是同一个专业动作每次都要让模型“临场发挥”。比如让 Agent 按公司规范生成接口测试用例一会儿漏了边界值一会儿格式不合预期你越是把要求写进系统提示词越容易和别的任务互相干扰。后来我把这类高频、标准化的动作拆成一个独立的 Skill用 SKILL.md 做入口说明scripts 放可执行逻辑references 放参考资料整个流程立刻可控了。这篇文章就是我这套“三步走”的完整复盘场景我选的是软件测试领域所以你会看到生成测试用例、解析测试结果这类具体例子但方法本身完全可以迁移到任何专业场景。适合正在做 AI 应用落地、Agent 自动化或者想给团队沉淀标准技能的开发者。1. Skill 到底是什么为什么测试场景最先受益1.1 Skill 是给 Agent 安装的“可插拔技能包”很多人在做 Agent 的时候习惯把所有要求都塞进 system prompt结果 prompt 越来越长模型越来越“糊涂”。Skill 的思路完全不同它不是让 Agent 记住所有技能而是像给 Agent 装了一套可插拔的技能包。平时这些技能是休眠的一旦任务匹配Agent 才会加载对应的 SKILL.md按照里面的指引调用脚本、查阅资料完成任务后再卸下不干扰其他对话。一个标准 Skill 目录通常长这样test-case-generator/ ├── SKILL.md ├── scripts/ │ ├── generate_test_cases.py │ └── run_tests.py └── references/ ├── test_case_template.md └── boundary_value_checklist.mdSKILL.md 是技能的“说明书”负责告诉模型这个技能是干什么的、怎么用scripts 是“执行器”把能确定化的逻辑写成代码references 是“参考书”存放不经常变动但必须遵循的规范、数据、模板。三者各管一摊比单纯堆 prompt 清晰得多。1.2 测试场景为什么适合用 Skill 固化我用测试场景来讲这个方法不是随便选的。测试领域有一个很典型的特点规则明确、重复度高、判定标准清晰。比如“接口测试用例必须包含正常场景、异常场景、边界值”“金额字段要测 0、负数、超长数字、非数字字符”这些规则一旦沉淀下来几乎每次生成用例都要用。这正好是 Skill 的舒适区。另一个原因是测试任务的可验证性很强。一个 Skill 写得好不好跑一遍就知道用例生成全不全脚本能不能解析结果边界值有没有覆盖。这种“有明确验收标准”的场景特别适合用来培养团队对 Skill 开发的直觉。相比之下那些偏创意、开放式要求的任务反而不容易快速判断 Skill 的质量。1.3 三步走的整体设计思路所谓“三步走”其实是按照“描述、执行、参考”三个维度来拆解技能先写 SKILL.md明确技能的触发条件、执行步骤和输出要求。再写 scripts把能通过代码确定化的环节从“让模型想”变成“让代码算”。最后补 references把那些长期有效、不需要模型重新推理的规范和数据放进去。我比较推荐这个顺序因为 SKILL.md 是骨架先把骨架立起来才知道 scripts 要承担什么职责、references 要补哪些内容。很多新手习惯先写代码写到一半发现不知道要让模型怎么调用结果代码和说明各说各话Agent 根本跑不通。2. 第一步SKILL.md 是技能的说明书2.1 frontmatter 决定 Agent 什么时候启用这个技能SKILL.md 顶部是 YAML frontmatter最关键的是name和description两个字段。name好办起一个简洁、不与其他 Skill 冲突的名字就行。真正决定命运的是description。我见过太多人把 description 写成功能列表比如“用于生成测试用例”这在多 Skill 场景下非常容易出问题。Agent 判断是否加载某个 Skill靠的就是 description 和当前任务的语义匹配度。如果描述太宽泛Agent 会在不相关的场景下乱加载如果太狭窄该触发时又不触发。我常用的方法是在 description 里写清楚“触发条件 能做什么 典型输入输出”。比如--- name: test-case-generator description: 当用户要求为接口、函数或业务模块生成测试用例、补充边界值测试、或按团队规范整理测试场景时使用。输入参数包括接口定义或需求描述输出为结构化测试用例清单。 ---这样写Agent 匹配的准确率明显提升。核心是让模型做一次“是否需要启用”的二分类判断而不是阅读理解。description 本质上就是分类器的特征越具体分类越准。2.2 正文指令要让模型“照着执行”SKILL.md 的正文部分是我们直接写给 Agent 看的它不等同于产品文档不需要铺垫背景也不需要礼貌用语。我的经验是用祈使句按步骤写明确必须做什么、不能做什么、最终要给出什么格式的结果。一份合格的 SKILL.md 正文至少覆盖这几个模块目标一句话说清楚这个 Skill 存在的目的。输入要求调用这个 Skill 需要哪些信息缺失时如何向用户提问。执行步骤列出从拿到输入到产出结果的完整流程最好带序号。输出格式明确最终输出的组织方式必要时给出示例。边界与兜底遇到不支持的输入、程序报错、信息不足时应该怎么处理。正文不要太长。Agent 的上下文窗口有限SKILL.md 只是“技能提示”核心计算最好交给 scripts 完成。那些“应该先测正常场景再测异常场景”的流程性要求可以写进来但“每种场景具体要覆盖哪 20 个边界点”这种知识更适合放进 references。2.3 一份测试用例生成 Skill 的 SKILL.md 示例下面是我实际在用的一个精简版本你可以直接照着改--- name: test-case-generator description: 当用户要求为接口、函数或业务模块生成测试用例、补充边界值测试、或按团队规范整理测试场景时使用。输入参数包括接口定义或需求描述输出为结构化测试用例清单。 --- # 测试用例生成技能 你的目标是根据用户提供的接口定义或需求描述生成一份符合团队规范的测试用例清单。 ## 输入要求 1. 用户需要提供被测对象的名称、输入参数含类型与约束如果有接口文档或需求描述优先读取。 2. 如果信息不足以生成用例先列出缺少的关键字段向用户确认后再继续禁止凭空假设。 ## 执行步骤 1. 解析输入从用户消息或 references 中的接口文档模板里提取被测对象信息。 2. 调用脚本运行 scripts/generate_test_cases.py将提取到的参数定义通过 JSON 传给脚本。 3. 接收输出脚本会返回结构化测试用例 JSON如果脚本报错把错误信息原样返回给用户。 4. 补充描述对脚本生成的每条用例用一句自然语言解释测试意图。 5. 输出结果按“功能测试 / 异常测试 / 边界测试”三组组织用例清单。 ## 输出格式 输出 Markdown 表格包含以下字段 | 用例编号 | 场景分类 | 输入数据 | 预期结果 | 测试意图 | 其中“输入数据”必须是可以直接复制执行的参数值禁止使用“待定”这类模糊占位。 ## 边界与兜底 - 如果用户给的是 URL 或接口地址而不是参数定义提示用户先提供接口文档。 - 如果脚本生成结果为空说明该对象缺少可测字段直接反馈不要强行编造用例。 - 不要在一次回复中生成超过 30 条用例超出部分提示用户分批处理。这份 SKILL.md 的关键点是把“怎么生成用例”的算法逻辑踢给了脚本SKILL.md 只保留流程控制和输出格式要求。Agent 要做的不是发明测试方法而是判断输入、调用脚本、整理输出这些事情它做起来既快又不容易出错。3. 第二步scripts 是技能的执行器3.1 为什么需要独立脚本而不是让模型现场写代码把逻辑写进 scripts是我认为 Skill 开发里最值得的一笔投入。模型虽然能写代码但每次生成的结果不稳定而且浪费 token。同样的边界值枚举逻辑模型第一次可能列 30 个第二次可能只列 18 个你没法控制。脚本是确定性的。同一个输入进去出来的一定是同一个结果。这一点在测试场景里极其重要因为测试用例本身就是“可重复、可回归”的。把枚举、格式化、规则校验这类算法逻辑交给脚本Agent 只负责调度和表达整个 Skill 的质量下限就被抬起来了。另外脚本是可以单独测试的。你不需要通过 Agent 去验证“边界值枚举对不对”直接在命令行跑一次就知道了。这也让 Skill 的维护成本大幅下降。3.2 脚本的输入输出协议Skill 里的脚本要给 Agent 调用所以输入输出协议必须极其明确。我的建议是输入用命令行参数加 JSON输出用 JSON并且通过退出码表达执行状态。一个简单的测试用例生成脚本大概长这样#!/usr/bin/env python3 根据参数定义生成测试用例 JSON。 import json import sys def parse_arg_spec(raw): spec json.loads(raw) # spec 示例: {name: amount, type: number, min: 0, max: 10000} return spec def generate_cases(spec): name spec[name] typ spec.get(type, string) cases [] # 正常场景 if typ number: default_val spec.get(default, 0) cases.append({field: name, category: 功能, input: default_val, expect: 处理成功, why: 默认值}) optional spec.get(optional, False) if not optional: cases.append({field: name, category: 异常, input: NULL, expect: 参数缺失报错, why: 必填字段缺失}) # 边界场景 if min in spec: cases.append({field: name, category: 边界, input: spec[min], expect: 处理成功, why: 最小值边界}) cases.append({field: name, category: 边界, input: spec[min] - 1, expect: 校验失败, why: 小于最小值}) if max in spec: cases.append({field: name, category: 边界, input: spec[max], expect: 处理成功, why: 最大值边界}) cases.append({field: name, category: 边界, input: spec[max] 1, expect: 校验失败, why: 大于最大值}) return cases def main(): try: raw sys.argv[1] spec parse_arg_spec(raw) result generate_cases(spec) print(json.dumps({ok: True, cases: result}, ensure_asciiFalse, indent2)) except Exception as e: print(json.dumps({ok: False, error: str(e)}, ensure_asciiFalse)) sys.exit(1) if __name__ __main__: main()注意几个细节输入必须通过sys.argv[1]获取 JSON 字符串输出固定是{ok: true/false, ...}结构。Agent 只要判断ok字段就能知道脚本运行是否成功不需要去解析一堆乱七八糟的日志。错误信息用error字段返回这样 Agent 可以直接把错误内容转述给用户不用猜。3.3 依赖管理与运行环境陷阱Skill 的 scripts 本质上是一个普通程序该做好的工程化规范一个都不能少。Python 项目要固定requirements.txtNode 项目要固定package.json最好把版本锁死不要用“”。否则今天能用明天依赖升级脚本就崩了。这里有一个我在实际运行中踩过的坑Node 项目用 pnpm 安装依赖时经常看到类似下面的提示[err_pnpm_ignored_builds] ignored build scripts: core-js3.45.1, esbuild0.2 [err_pnpm_ignored_builds] ignored build scripts: parcel/watcher2.5.6pnpm 出于安全考虑默认不会执行依赖包的postinstall和build脚本而core-js、esbuild这类包恰恰需要构建脚本才能正常工作。结果就是安装时不报错一旦 Agent 运行脚本立刻出现“moduled not found”或者空对象调用排查起来非常恼火。解决办法有两种。一是在package.json里显式声明允许构建{ pnpm: { onlyBuiltDependencies: [core-js, esbuild] } }二是干脆在 Skill 目录里写一个prepare.sh安装完依赖后跑一次pnpm approve-builds把需要构建的包加进白名单。我的经验是如果 Skill 里的脚本只是做 JSON 解析、文件处理这类纯逻辑任务优先选 Node 标准库或者 Python 标准库尽量少引入带原生模块的第三方包能从根上避开这类问题。4. 第三步references 是技能的参考知识库4.1 references 该放哪些内容references 目录存放的是那些“基本不变、但 Skill 执行时经常要查”的内容。在测试类 Skill 里我通常会放这几样测试规范摘要团队约定的用例命名规则、字段要求。边界值检查清单比如数字类型要测0、负数、极大值、小数、非数字字符串类型要测空串、超长串、特殊字符、Unicode。接口文档模板让 Agent 在缺少输入时按模板向用户提问。历史缺陷分类常见的线上问题类型帮助用例生成更有针对性。这些内容的共同点是长期稳定。你不需要模型每次都从零推理“什么是边界值”直接把清单给它让它对照执行准确率立刻不一样。4.2 references 和 scripts 的边界怎么划这个问题经常有人问这段内容到底该放脚本里还是放 references 里我的判定标准很简单需要计算和判断的进 scripts需要查找和对照的进 references。比如“金额字段是否允许为负数”这个判断逻辑很简单放脚本但“团队对金额字段还有哪些历史踩坑经验”这种带上下文的知识放 references。我整理了一个常用的分工表内容类型存放位置原因边界值枚举规则scripts逻辑固定需要确定性的输出参数格式校验scripts需要计算适合用代码完成团队测试规范references长期有效供 Agent 查阅接口文档模板references结构化知识不适合写成代码历史缺陷案例references用于启发测试思路不直接参与计算输出格式整理scripts SKILL.md脚本生成原始数据SKILL.md 定义展示方式这个边界不是绝对的但按这个思路分基本不会出大错。核心还是那句话能用代码保证的别让模型“临场发挥”需要模型利用知识做判断的才交给它。4.3 组织方式决定检索效率Agent 访问 references 不是靠“读”而是靠“检索”。它进入 Skill 目录后会根据 SKILL.md 的指引决定要不要打开某个文件。如果 references 里堆了几十个命名混乱的文件Agent 大概率会选择不读或者读错。我的做法是文件命名做到“一看就知道写什么”并且在 SKILL.md 里对每个 references 文件做一句话说明。比如references/ ├── test_case_template.md # 接口测试用例的标准 Markdown 模板 ├── boundary_checklist.md # 各类型字段的边界值检查清单 └── common_defects.md # 历史线上缺陷的分类与对应测试建议SKILL.md 里对应的写法## 参考资料索引 - references/test_case_template.md当用户提供接口文档时按此模板提取被测字段。 - references/boundary_checklist.md生成异常和边界用例前先对照此清单。 - references/common_defects.md遇到不确定要测什么时参考历史缺陷类型补充场景。这样做的好处是Agent 不用自己猜某个文件有没有用它只要顺着索引去找就行。索引本身也在缩小检索范围降低误读概率。5. 怎么测试你开发出来的 Skill5.1 先测脚本再测组合我见过太多人把 Skill 扔给 Agent 一跑发现不行但完全不知道是 SKILL.md 写错了还是脚本写错了。正确的做法是分层测试先单独测脚本再测 Agent 和脚本的组合。单独测脚本很简单直接在命令行构造输入python3 scripts/generate_test_cases.py {name: amount, type: number, min: 0, max: 10000}检查输出的 JSON 是否符合预期。这个阶段不需要 Agent也不需要 API Key纯粹是普通的程序测试。脚本测稳了再进 Agent 环境做联调问题范围一下就缩小了。5.2 用 Prompt 矩阵做 Agent 级联调Agent 级联调不能只测一两个 Prompt 就宣布完成。我的习惯是提前设计一个测试 Prompt 矩阵覆盖 Skill 的典型场景、边界场景和异常场景。测试类型输入示例预期行为正常场景“为以下接口生成测试用例POST /api/payment参数 amount 为 number 类型范围 0-10000”调用 Skill返回分组用例信息缺失“帮我生成测试用例”不调用脚本先追问被测对象输入超范围一次性给出 50 个接口参数按 SKILL.md 规则分批处理或拒绝脚本出错传入无法解析的 JSON返回脚本 error 字段内容无关场景“帮我写一首诗”不加载测试 Skill这个矩阵表最好沉淀到 Skill 仓库里每次改完 SKILL.md 或者 scripts都要把矩阵跑一遍。我把这个动作叫“Skill 回归测试”跟传统软件测试里的回归测试一个思路。改代码之前先记录基准输出改完对比差异很多隐蔽问题都是这么暴露出来的。5.3 失败注入与边界验证除了正常路径我还要对 Skill 做“失败注入”。测试类 Skill 本身就是干这个的但它自己也要扛得住异常输入否则 Agent 一遇到特殊情况就崩盘根本没法在生产环境用。我会刻意测试这些场景用户传入的参数定义是残缺的比如只有字段名没有类型。脚本运行时依赖缺失比如 pnpm 忽略了构建脚本。agent 加载了 Skill但 references 文件被误删。输出结果超过模型 token 限制。每一种情况SKILL.md 里都应该有兜底方案。我在编写 SKILL.md 时特别强调了一句话“当信息不足时先提问不要凭空假设。”这句话在异常场景下能救回很多次对话。6. 常见问题与排查实录6.1 Skill 没有被调用这是最普遍的问题。查下来十有八九是 description 写得不对。要么太宽泛Agent 在无关场景也尝试加载要么太狭窄Agent 识别不到触发条件。我遇到过最典型的案例description 写的是“生成测试用例”结果用户说“给我测测这个接口有没有问题”Agent 完全没有联想到要加载 Skill。处理办法是回看 description把“在什么场景下使用”和“输入长什么样”写清楚。改完之后用之前的 Prompt 矩阵重新验证看触发率有没有变化。一般来说description 里带上 3 个以上和任务强相关的动作词触发率会明显提高。6.2 脚本跑起来就报错尤其查不到依赖脚本在命令行单测能过在 Agent 环境里报错多半是环境不一致。最常见的就是 pnpm 忽略构建脚本导致原生依赖缺失报错信息五花八门但核心是“某个模块不存在”。我现在的习惯是Skill 目录提交之前先在一个全新的空环境里按 README 执行一次安装和测试确认没有隐式依赖。另外尽量把脚本做得“环境无关”。能用标准库解决的不引第三方包必须引入的就把版本号写死并且把安装命令写进 SKILL.md 或者仓库说明里。这样 Agent 换一个机器运行也能顺利复现。6.3 Agent 输出的 JSON 总是解析失败SKILL.md 里要求输出 JSON但模型偶尔会带 Markdown 代码块标记或者多出注释导致下游解析失败。这个问题不能靠“反复强调”解决我现在的做法是把解析逻辑做容错。在 scripts 里增加一个parse_agent_output.py专门处理和清洗模型输出去掉代码块标记、提取第一个 JSON 对象、处理尾逗号。把“AI 的不稳定性”挡在协议层之外。6.4 相对路径找不到文件Skill 被 Agent 加载时当前工作目录不一定是 Skill 目录。脚本里用相对路径引用 references 文件经常“文件不存在”。我的经验是在脚本开头直接用Path(__file__).resolve().parent.parent之类的方式定位 Skill 根目录所有资源路径都基于它来计算。只要做到这一点无论 Agent 从哪里发起调用都能正确找到 references。6.5 回归测试Skill 也要有测试套件我把 Skill 仓库当成普通代码仓库来管理。根目录放一个tests/文件夹里面存三样东西固定输入样例、期望输出、Prompt 矩阵。每次改动 SKILL.md、scripts 或 references就运行一遍python3 -m pytest tests/虽然测试的内容是 Agent 技能但流程和传统软件测试完全一样。我甚至会把“Agent 加载 Skill 后的输出结果是否包含必填字段”写成校验函数自动化判断格式正确性。这么做的好处是当 Skill 越来越多、团队协作越来越频繁时不会出现“改 A Skill 弄坏 B Skill”的情况。我个人在实际项目里的体会是Skill 开发最忌讳“一步到位”的心态。不要试图第一次就把 SKILL.md、scripts、references 全部写得完美而是先跑通一个最小闭环哪怕脚本只处理一种输入类型。跑通之后再逐步把更多规则从 SKILL.md 下沉到 scripts把更多知识从模型脑子里沉淀到 references。这个不断沉淀的过程才是 Skill 真正值钱的地方。最后再分享一个小技巧每当你发现模型在某个任务上反复犯同样的错误别急着加提示词先想想这个错误能不能用一个脚本、一条规则、一份清单来解决。能就把它写进 Skill 里。这样你的 Agent 不只是“聪明”而是“稳定地聪明”。