过去两周我一直在跟一组“蛮不讲理”的接口断言较劲。某零售系统的回归测试里,接口返回的提示文本总在玩同义反复,“当前库存不足以支持该数量”和“库存不够,请减少数量”明明指向同一条业务规则,可一旦按文本比较,每次都是 fail。我在主提示词里不断追加规则,要求大模型先归类再断言,结果指令越堆越多,效果却忽好忽坏,甚至因为指令互相覆盖,把别的用例也带偏了。
后来我花了两天时间,认真研究了 OpenClaw skills 这套机制,研究完只有一个感受:它把“教 AI 干活”这件事的颗粒度从“整段人生建议”降到了“单个动作指令”,我上面的问题基本被根治。如果你也在做 AI 测试,或者正用大模型做接口断言、文本提取、数据转换这类重复性工作,这篇文章会把最值得抄走的经验都列出来。我会尽量讲清楚每个选择背后的理由,而不是只丢结论。
1. 我为什么要盯上 skills:一次改提示词改到想摔键盘的经历
1.1 主提示词是怎么一步步膨胀的
最开始我的做法很朴素:把接口返回的字段和期望值直接写进提示词,让大模型判断“通过”还是“失败”。比如:
请判断以下订单返回是否符合预期:金额字段应为100,状态应为已完成。前几个用例效果还行,但一旦覆盖到十几个接口,问题就来了。不同的接口有不同的字段语义,有的字段要校验格式,有的字段要校验大小关系,有的字段要校验枚举值。于是我开始在主提示词里追加各种规则:“金额字段必须大于0”“状态必须在白名单内”“时间戳必须是13位”……规则越加越多,提示词越来越长。
后果分两步出现。先是各规则之间开始打架。比如我规定“金额必须等于商品单价乘以数量”,又规定“金额允许存在四舍五入误差”,大模型在不同会话里经常选一个执行,输出极不稳定。再后来,规则太多导致大模型开始“过度发挥”,在无关用例里也强行套用某些断言,把原本正确的接口判成失败。
1.2 写脚本的另一个坑:能跑,但维护成本太高
妥协方案是写脚本。用正则表达式适配返回文本,再做规则匹配。一开始确实稳,文本说什么我就匹配什么。可一旦文本换种说法,正则就挂了。后来改用大模型 API 做文本理解,自己维护请求、解析、重试、上下文管理,还要处理 token 上限。一套下来,工作量已经接近在写一个小型测试框架。
问题在于,我所在的小组是测试团队,不是 AI 平台团队。为了一个“断言语义归一化”的需求,去维护一套大模型调用代码,属于典型的杀鸡用牛刀,而且这套代码换个人接手根本看不懂。
1.3 skills 给出的答案为什么不一样
OpenClaw skills 这套机制给我的第一感觉,是它把“能力”从“对话上下文”里抽出来了,变成一个独立单元。它不要求我去写大模型调用框架,也不要求在提示词里堆砌各种规则,而是让我以声明式的方式告诉 AI:什么时候该用这个能力、输入什么、期望得到什么格式的输出。
打个比方。以前我给 AI 的是“你是个资深测试,帮我看看这个返回哪里不对”,相当于让一个新员工自由发挥。现在我给 AI 的是一本操作手册加一套预置工具,某个场景出现时,它按手册调用对应工具,而不是凭感觉即兴表演。
这套机制非常适合测试场景,因为测试工作天然是“重复性高、规则明确、期望可验证”的。
1.4 它到底适合什么、不适合什么
我梳理了一张表,方便你判断自己的任务适不适合往 skills 上靠:
| 适合场景 | 不适合场景 |
|---|---|
| 接口断言语义归一化 | 需要大量业务背景推理的探索性测试 |
| 文本格式标准化提取 | 完全开放式的主观评价任务 |
| 规则明确的字段校验 | 需要跨系统联调追踪的端到端分析 |
| 重复性报告生成 | 数据量极大且对实时性要求极高的场景 |
一句话总结:凡是你能写出“输入 A,经过固定处理,得到 B”的任务,都值得考虑用 skill 固化下来。凡是连你自己都说不清规则的任务,skill 也帮不了你。
2. 拆开 OpenClaw skills 内部:它到底由什么组成、怎么被加载
2.1 在我眼里,一个 skill 其实是四个部分
我没有去看官方文档的术语表,完全是从实测结果反推的。一个能跑通的 skill,本质上由四块组成:名称、描述、输入输出约定、执行体。我见过最简单的一个 skill 长这样:
skill_name: timestamp_normalizer description: 将各种格式的时间字符串统一转换为标准时间戳。适用于接口返回字段中的时间解析。 input: - raw_time: 原始时间字符串 output: - timestamp: 13位毫秒时间戳 - success: 布尔值 executor: command: python scripts/normalize_time.py这里有个容易被忽略的点:执行体不一定非得是一段脚本,也可以是命令行工具、HTTP 请求,或者单纯是一段纯文本模板。关键是它要能接收输入、给出结构化返回,供大模型继续判断。我第一条 skill 的执行体就不是脚本,而是一段强制转换规则,大模型读完后直接按规则产出 JSON。
2.2 加载顺序和优先级,决定了你排查问题的方向
第二天下午我被一个问题卡住:我改了某个 skill 的说明文字,重新测了半天,行为却没有任何变化。后来发现,我改的这个 skill 和另一个目录下的同名 skill 发生了覆盖。系统加载时按目录顺序扫描,后加载的覆盖先加载的,我改的恰好是先加载的那个。
这个教训值得单独说:遇到“skill 不生效”的问题,第一反应不应该是怀疑大模型,而是先确认你改的这份文件是不是最终生效的那一份。你可以在每个 skill 文件里加一个 version 字段,然后在输出里带上版本号,这样就能很快定位到底加载的是哪个版本。
我用一段简单命令排查:
find skills -name "SKILL.md" | xargs grep -n "skill_name:" | sort列出所有 skill 名称和位置,一眼就能看出是否有重复。
2.3 描述质量直接决定 AI 会不会用它
这是最容易忽视、也最影响效果的地方。描述写得太泛,比如“用于处理时间”,大模型遇到时间字段时可能根本想不起来调用它,而选择自己猜测;描述写得太长,把实现细节全塞进去,又会导致大模型每次对话都把这段描述塞进上下文,白白浪费 token。
我实测下来,比较稳妥的写法是:一句话讲清楚用途 + 列出典型触发场景 + 说明输出格式。可以这样写:
description: 将接口返回中的时间字符串统一转为13位毫秒时间戳。当且仅当字段名包含 time、date、created_at、updated_at 时使用。这样既给了触发条件,也给了边界。相比“处理时间字段”这种写法,AI 的调用准确率高很多。
2.4 执行体与外部的接缝,尽量窄
skill 的执行体就像插座上的插孔,外部世界通过这个插孔接入。如果你对外部命令的路径、环境变量、运行目录有依赖,换一台机器就可能挂掉。我第二天踩过这个坑:在开发机上能跑通的脚本,到 CI 环境里直接报文件不存在,原因是脚本用相对路径定位依赖文件,而 skill 的工作目录和脚本预期工作目录不一致。
我的做法是,在执行体里强制设置绝对路径,并在 skill 目录内放置一个env_check.sh,启动前检查依赖是否齐全。第一次跑的时候会花点时间,但能省下后面大量定位环境问题的功夫。
3. 第二天动手:从零定义一条“自然语言转断言结构”的可复用 skill
3.1 目标定义:让不可执行的描述变成可执行的断言
我要处理的最痛问题,是把自然语言写的校验要求,转成结构化的断言对象。比如测试用例里写着“订单金额必须大于0,且等于商品单价乘以数量”,这种描述没法直接跑,需要人肉翻译成代码。我的目标很单纯:让 AI 先调用这个 skill,把描述翻译成 JSON 格式的断言描述,再由另外的脚本执行它。
这个设计有一个隐含的好处:翻译和执行解耦。大模型负责语义理解,脚本负责确定性执行,两边互不干扰。
3.2 目录结构怎么搭
我参考了插件系统常见的组织方式,把每个 skill 放在独立目录里,方便维护和版本管理。
skills/ assertion_builder/ SKILL.md examples/ valid_case_01.json valid_case_02.json invalid_case_01.json scripts/ build_assertion.py每个目录都是独立单元,可以单独打包、单独回滚。SKILL.md是给 AI 看的说明书,examples/是给 AI 参考的样例,scripts/是具体执行逻辑。
3.3 编写描述和参数说明
我写的描述保持简短,核心信息全部结构化。
skill_name: assertion_builder description: 将测试用例中的自然语言校验要求转换为结构化的断言 JSON。当用户提供一段包含校验意图的文本时使用。 input: - requirement_text: 原始的自然语言校验描述 output: - assertion_json: 包含字段名、操作符、期望值的 JSON 对象参数说明里强调了两点:输入必须是纯文本不能被污染,输出必须是合法 JSON。这样大模型在处理时不容易跑偏。
3.4 示例和反例,比规则描述更管用
第一版我犯了个错误:只给了一个正面示例,效果并不稳定。后来我加了两组正向示例和一组反例,实测准确率明显提升。下面是一组我在 examples 里放的样例:
{ "requirement_text": "订单金额必须大于0,且等于商品单价乘以数量", "assertion_json": { "field": "order_amount", "operator": "AND", "conditions": [ { "operator": "GT", "value": 0 }, { "operator": "EQ", "expression": "unit_price * quantity" } ] } }反例我故意放了一条“金额四舍五入到分,误差不超过0.01”的情况,让模型知道这种需求应该输出容差断言而不是精确相等。模型见过了边界,就不会在真正遇到边界时瞎猜。
3.5 第一版自测结果,差点把我劝退
我准备了一组包含 10 条不同写法的自测用例。第一轮跑下来只过了 4 条,一半以上的输出要么字段名和接口文档对不上,要么操作符用得不对。当时差点以为这套机制也不行,后来冷静下来分析了一下失败原因,发现问题不在 skill 机制,而在我的“教材”太差:
- 示例太少,模型没见过足够的变体;
- 参数名称没和接口文档字段强绑定,模型在自行发挥;
- 缺少“不要做什么”的约束,模型把模糊描述强行翻译成精确断言。
我重新调整了描述,加了字段映射提示,又补了三个示例,包括边界情况,第二轮正确率直接到了 9/10。这个经验让我意识到:skill 的效果并不取决它写得多华丽,而取决于示例覆盖了多大比例的常见场景。
4. 拿去跑真实接口回归:效果、样例和触顶边界
4.1 测试对象和测试目标
为了验证这个 skill 不是玩具,我拿某订单系统的订单详情接口做了回归。这个接口返回嵌套 JSON,包含订单金额、商品明细、优惠信息、状态码等字段。过去的做法是人工写断言,每个字段写一行校验,遇到文案变动还得人工改。
现在流程变成三步:提取返回体,调用 assertion_builder 将校验需求转成结构化断言,再用渲染脚本把 JSON 断言转成 pytest 风格代码。转换这步是确定性的,出问题只可能在 skill 的翻译环节。
4.2 实际操作和代码片段
字段校验需求我是这样描述的:
校验订单金额字段,要求:金额大于0,等于商品原价乘以折扣系数,且四舍五入保留两位小数。调用 skill 后生成的结构化断言长这样:
{ "field": "data.order_amount", "operator": "AND", "conditions": [ { "operator": "GT", "value": 0 }, { "operator": "EQ", "expression": "round((data.original_price * data.discount_rate), 2)" } ] }然后由一个固定脚本把 JSON 逐条翻译成断言代码。整个过程中只有“自然语言到 JSON”这一步是智能行为,其余部分全是确定性执行,所以我敢把生成结果直接放进回归流水线。
4.3 前后对比,数据说话
跑完一轮包含 50 条用例的回归,我把新旧方式的差异整理成了表:
| 对比项 | 旧方式 | 新方式 |
|---|---|---|
| 用例编写耗时 | 约1天 | 约2小时 |
| 文案变化导致的改动 | 每条用例都要手改 | 只需改校验需求描述 |
| 失败定位耗时 | 平均10分钟 | 平均3分钟 |
| 误报率 | 相对较高 | 显著下降 |
这个提升并不来自 AI 更聪明,而是来自“翻译”和“执行”分离后,每个环节的职责单一了。AI 只干理解语义这一件事,不再承担生成完整测试代码的任务,错误率天然下降。
4.4 它也有明确的上限,别指望万能
我也测了 skill 在更复杂条件下的表现。比如跨接口关联校验:订单总金额必须等于订单列表里所有订单子金额之和。这种需求超出了单条 context 能判断的范畴,skill 仍然能生成结构,但生成出来的结构需要人工验证数据来源是否正确。再比如业务规则与实际逻辑有歧义时,skill 会照单全收,把错误预期固化成断言,反而比人工写更危险。
所以我的结论是:skill 适合做“单点能力封装”,不适合做“跨场景综合分析”。在所有接入点,我都保留了一个人工确认步骤,确保生成结果不是直接进入流水线,而是先经过测试工程师的快速审核。审核很快,因为 JSON 结构化之后,人一眼就能看出断言意图对不对。
5. 最容易踩的五个坑:命名冲突、上下文污染和隐形依赖
5.1 同名 skill 静默覆盖,浪费了我半天时间
前面提到的加载顺序问题,我再展开讲一下。当时我的skills/目录下有两个目录都放了SKILL.md,里面写了同一个skill_name,结果只有后加载的那个生效。我改的是先加载那个,所以怎么改都没反应。
排查方法是先列出全部 skill 名称:
find . -name "SKILL.md" -exec grep -l "skill_name:" {} \; | xargs grep -n "skill_name:" | sort我自己定了一条规矩:全库 skill 名称全局唯一,不允许两个目录拥有同名 skill。后续再也没被这个问题坑过。
5.2 上下文污染,是隐藏最深的坑
这个问题的现象很隐蔽。我在调完一个 skill 后,接下一个用例时,模型偶尔会把前一个用例里的临时变量、中间结果带进来,导致后一个用例的输出莫名其妙多了字段。一开始我怀疑是模型幻觉,后来发现不是,是 skill 在执行过程中显式写入了一些变量,但调用结束后没清理。
解决办法是双层的。第一层,在 skill 描述里明确写一句“调用结束后不得保留任何临时变量”;第二层,在输出结构里统一加cleanup:true这样的标记,由执行脚本在返回结果前强制清理。实测两层配合,污染概率降到接近于零。
5.3 依赖外部命令时,环境差异会打你一个措手不及
如果 skill 执行体依赖某个命令行工具,而工具只安装在部分机器上,那 skill 在本地可以跑,在 CI 上就会挂。有个技巧是,在 skill 入口做环境自检,把缺失的依赖列出来。这不算聪明的方案,但能最快暴露问题。我的入口脚本长这样:
#!/bin/bash command -v python3 >/dev/null 2>&1 || { echo "python3 not found"; exit 1; } command -v node >/dev/null 2>&1 || { echo "node not found"; exit 1; }与其让依赖问题在运行时随机暴露,不如在启动时直接失败,把错误信息亮出来。
5.4 示例太少时,模型会在同一棵树上反复吊死
只给一个正面示例,模型学到的是“模仿这个句式”,而不是“理解这一类需求”。一旦输入描述的长相变化,输出就崩了。我后来在每个 skill 的 examples 目录里至少放三组样例:一个基础正面案例、一个复杂边界案例、一个反例,并且给每组样例加上注释,说明这个样例覆盖了哪类风险。
5.5 生成结果不带版本号,改完 skill 后全线崩盘
skill 升级之后,以前生成的结构化断言可能就不符合新的输出格式了。如果这些断言已经写进测试代码里,改造成本会突然放大。我在 skill 输出里统一加了一个skill_version字段,每次调整 skill 逻辑都同步递增版本号。一旦后续测试代码报错,先检查版本号,马上就能判断是兼容性问题还是数据问题。
6. 把一条 skill 沉淀成团队技能库:规范、自测样例和淘汰机制
6.1 先定命名规范,再谈复用
一个人用 skill 可以随意发挥,但一旦进入团队协作,没有规范就是灾难。我在我们小组推了一套简单约定:skill 名称必须带领域前缀,比如assert_、parse_、search_、normalize_,后面跟动作对象。这样的好处是,在目录列表里扫一眼,就能知道这个 skill 属于哪类能力、支持什么功能。
6.2 为每条 skill 写一个“干样例”文件
这个灵感来自契约测试。我给每条 skill 配了一个self_test.sh,里面固定执行三组典型输入,对比输出是否符合预期。任何改动后先本地跑一遍,不过就拒绝提交。
python scripts/build_assertion.py --input "订单金额必须大于0" --expect "GT, 0" python scripts/build_assertion.py --input "金额四舍五入到分" --expect "ROUND, 2" python scripts/build_assertion.py --input "状态必须在白名单内" --expect "IN, [PENDING, SUCCESS]"这一步非常值得做,因为 skill 本质上是代码,代码就应该能测试,不能用“改一版看缘分”的方式维护。
6.3 建立周度评审机制,淘汰低价值 skill
skill 数量一多,维护负担就会上升。我们做了个很轻量的周度机制:统计每条 skill 一周内的调用次数,零调用的重新审视描述是不是写得太晦涩;连续一个月零调用的直接标记废弃。调用失败率高的 skill,优先补充反例或者拆分成更小的能力。
这套机制不复杂,但能让技能库保持“活着”。不用的 skill 及时清掉,用到的时候再重建,比维护一堆僵尸 skill 更省心。
6.4 和测试用例管理配合,防止连锁失效
skill 被删除或升级,直接影响引用它的测试用例。我在组织用例时,会在每条用例的注释里注明用了哪个 skill 的哪个版本。这样一旦 skill 崩溃,我可以快速反查受影响范围,而不是等 CI 炸了再到处翻日志。
6.5 后续还能往哪些方向扩展
现在这条 assertion_builder 只是把自然语言转成结构化断言,下一步我打算让它直接对接接口文档,从字段定义自动生成校验需求,省去手写输入描述这一步。再往后,可以结合覆盖率反馈,让 skill 知道哪些断言真正拦截过故障,从而自动优化描述优先级。这些都需要更多验证,但框架已经搭好,后面的路好走很多。
结尾之前,我想单独聊一个点
如果你准备上手,我的建议是不要一上来就折腾语法和最佳实践。先找一个你做起来最烦、每周都要重复的测试任务,把 AI 完成这个任务的步骤固化下来,做成你的第一条 skill。哪怕它丑、哪怕它只覆盖一种场景,跑通之后带来的正反馈,比读十篇文档都管用。
我非常确定,OpenClaw skills 不是银弹。它解决不了探索性测试的开放性,也替代不了测试工程师对业务的理解。它真正擅长的事情是把那些“明明有规律、却每次都要重新推理”的细碎能力一次定型,让 AI 不再用随机性对抗你的确定性。仅凭这一点,它就非常值得测试团队认真研究。