从0到1搭建测试专用Skills库:自动断言+数据构造+多模态识别实战 1. 测试团队为什么需要一个私有 Skills 库测试对象变了这件事比很多人想象得更彻底。以前我们测的是确定性代码输入 A 必然得到 B断言写assert result expected就完事。现在要测的是大模型输出、AI 生成内容、多模态交互结果同一段提示词跑两次结果可能完全不同正则和等值断言直接失效。我所在的团队去年下半年开始接触 AI 类产品测试最初的做法是人工 review 加零散脚本。一个“判断这段 JSON 返回是否合理”的任务三个人写出三套逻辑谁也说不清标准是什么。更麻烦的是数据构造要造 100 条“深圳地区、月消费 5k-8k、偏好户外运动”的用户画像每次都得重写一遍随机脚本字段之间的关联规则还得手动维护。多模态识别更是重灾区。测一个拍照识物功能要验证截图里“立即购买”按钮是否可见可点击传统做法是写图像定位脚本换个 UI 版本就全废。这三类问题叠加起来测试用例的维护成本是普通产品的数倍核心原因就一个输出不确定断言写不了。Skills 库解决的正是这个问题。它把“测试经验”从一次性用例里抽出来封装成可复用、可组合的能力单元。一个“构造合法邮箱”的 Skill 加一个“生成随机密码”的 Skill不写新代码就能组合出“构造注册请求数据”的 Skill。资产从线性增长变成指数复用这是测试团队真正需要的底层能力。这篇内容面向的是想从零搭建测试专用 Skills 库的团队覆盖自动断言、数据构造、多模态识别三大能力。我会给出目录结构、Skill 注册配置、断言模板的可复制片段并演示用 TaoToken 统一 Key 和 API 通道接入多模态识别 Skill 的完整验证步骤目标是让你跑通一条端到端测试流水线。适合有一定 Python 基础、正在被 AI 测试折磨的测试开发和 QA 工程师。2. TaoToken 前置准备统一 Key 与 API 通道在搭建 Skills 库之前先把模型调用通道理顺。测试 Skill 会频繁调用大模型如果每个 Skill 各自管理 Key、各自处理重试和限流维护成本会失控。我的做法是用 TaoToken 作为统一的 API 通道一个 Key 覆盖对话、多模态、代码生成等场景Skill 层只关心业务逻辑。TaoToken 是一个模型 API 聚合服务官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。它的价值在于把不同模型的调用方式统一成 OpenAI 兼容格式测试 Skill 里不用为每个模型写适配层。对于测试团队来说这意味着断言 Skill、数据构造 Skill、多模态识别 Skill 可以共用同一套请求封装。前置准备分三步。第一步是注册账号并创建 API Key登录后进入控制台在 API Keys 页面生成一个 Key建议按环境区分比如test-skills-dev和test-skills-ci方便后续做用量归因。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite API Keys 管理页是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。第二步是确认模型 ID。测试 Skill 里会用到两类模型文本类用于断言和数据构造多模态类用于图像识别。你可以在模型对话页面先手动验证一下模型是否可用地址是 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。手动对话确认没问题后再写进 Skill 配置避免调试时把模型问题和代码问题混在一起。第三步是准备本地环境。我习惯用 Python 3.10 以上依赖openai、pydantic、pyyaml、jsonschema这几个库。openai库用来发请求pydantic做结构化输出校验pyyaml读 Skill 配置jsonschema校验数据构造结果。安装命令如下pip install openai pydantic pyyaml jsonschema pillow环境变量里配置 Key 和 Base URL不要硬编码到代码里export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/api这里有个容易踩的坑Base URL 末尾不要带/v1openai库会自动拼接路径。如果你手动加了/v1请求会变成/v1/v1/chat/completions直接 404。我试过在 CI 环境里因为这个问题排查了半小时最后发现是环境变量多写了一截。对于需要长期跑编码类 Skill、Agent 编排的团队可以考虑 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它在高频调用场景下更划算。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到参数问题先查文档。前置准备做完后你的 Skills 库就有了统一的模型出口。接下来所有 Skill 都通过这个通道调用模型Key 轮换、限流、用量统计都在一处管理这是从零搭建 Skills 库时最容易被忽略但最重要的一步。3. 可复制配置目录结构与 Skill 注册片段Skills 库的目录结构决定了后续的可维护性。我的建议是按“能力类型”分目录每个 Skill 一个文件夹里面放配置、提示词模板、测试样例。这样新增 Skill 时不用改核心代码只加目录和注册项。下面是我实际在用的结构test-skills/ ├── config/ │ ├── settings.yaml # 全局配置Base URL、默认模型、超时 │ └── registry.yaml # Skill 注册表 ├── skills/ │ ├── assertion/ │ │ ├── json_assert/ │ │ │ ├── skill.yaml # Skill 元信息 │ │ │ ├── prompt.md # 提示词模板 │ │ │ └── samples.jsonl # 回归测试样例 │ │ └── text_tone_assert/ │ ├── data_factory/ │ │ ├── user_profile/ │ │ └── boundary_value/ │ └── multimodal/ │ ├── ui_element/ │ └── object_detect/ ├── core/ │ ├── loader.py # 读取 registry 和 skill.yaml │ ├── executor.py # 统一调用模型、解析结构化输出 │ └── validator.py # 用 pydantic 校验输出 └── run_skill.py # 命令行入口全局配置config/settings.yaml长这样注意 Base URL 和模型 ID 都从这里读方便切换环境taotoken: base_url: https://taotoken.net/api api_key_env: TAOTOKEN_API_KEY timeout: 60 max_retries: 3 models: text: gpt-4o-mini multimodal: gpt-4o defaults: temperature: 0.2 max_tokens: 1024Skill 注册表config/registry.yaml是核心它把 Skill 名称映射到目录和类型skills: - name: json_assert type: assertion path: skills/assertion/json_assert model: text description: 校验 JSON 字段类型与约束返回结构化断言结果 - name: user_profile type: data_factory path: skills/data_factory/user_profile model: text description: 按规格构造用户画像数据支持字段关联约束 - name: ui_element type: multimodal path: skills/multimodal/ui_element model: multimodal description: 识别截图中的 UI 元素返回坐标与可见状态单个 Skill 的skill.yaml定义输入输出契约这是保证可组合性的关键name: json_assert version: 1.0.0 input_schema: type: object required: [payload, rules] properties: payload: type: string description: 待校验的 JSON 字符串 rules: type: array items: type: object properties: path: { type: string } expect_type: { type: string } not_null: { type: boolean } output_schema: type: object required: [passed, reason, evidence] properties: passed: { type: boolean } reason: { type: string } evidence: { type: string }断言模板prompt.md里最关键的是强制结构化输出不允许模型写小作文你是测试断言执行器。输入是一段 JSON 和一组断言规则。 要求 1. 逐条检查规则提取实际值后再比较禁止凭印象判断。 2. 只输出 JSON格式为 {passed: bool, reason: str, evidence: str}。 3. reason 用中文简述结论evidence 给出实际提取到的值。 4. 不允许输出 JSON 以外的任何内容不允许使用大概可能等模糊词。 输入 JSON {{payload}} 断言规则 {{rules}}这套配置的好处是新增一个断言 Skill只需要复制json_assert目录改skill.yaml和prompt.md在registry.yaml加一行核心代码零改动。数据构造和多模态 Skill 同理只是model字段指向不同的模型。如果你用 Claude Code 做 Skill 开发可以在项目根目录放一个.claude/settings.json把 Base URL、Key、Model ID 三件套写全避免每次手动配置{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的key, ANTHROPIC_MODEL: claude-3-5-sonnet } }注意这里的 Base URL 同样不带/v1。Claude Code 的接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 配置项以文档为准。三件套缺一不可Base URL 决定请求发往哪里Key 决定身份Model ID 决定用哪个模型少任何一个都会在启动时报错。4. 验证请求跑通多模态识别 Skill 端到端配置写完后必须验证一条完整链路。我选多模态识别 Skill 做验证因为它同时覆盖了模型调用、结构化输出、文件上传三个环节跑通了说明整条通道没问题。先写核心执行器core/executor.py它负责读配置、发请求、解析输出import os import json import base64 from openai import OpenAI import yaml def load_settings(pathconfig/settings.yaml): with open(path, r, encodingutf-8) as f: return yaml.safe_load(f) def build_client(settings): return OpenAI( api_keyos.environ[settings[taotoken][api_key_env]], base_urlsettings[taotoken][base_url], timeoutsettings[taotoken][timeout], ) def encode_image(image_path): with open(image_path, rb) as f: return base64.b64encode(f.read()).decode(utf-8) def run_multimodal_skill(image_path, target_desc, settings): client build_client(settings) model settings[models][multimodal] b64 encode_image(image_path) resp client.chat.completions.create( modelmodel, temperaturesettings[defaults][temperature], max_tokenssettings[defaults][max_tokens], messages[ { role: user, content: [ {type: text, text: f识别图中目标{target_desc}。只输出 JSON格式为 {{\found\: bool, \bbox\: [x1,y1,x2,y2], \visible\: bool, \reason\: str}}。}, {type: image_url, image_url: {url: fdata:image/png;base64,{b64}}}, ], } ], ) content resp.choices[0].message.content.strip() if content.startswith(): content content.strip().replace(json, , 1).strip() return json.loads(content)然后写命令行入口run_skill.py把参数传进去import sys from core.executor import load_settings, run_multimodal_skill if __name__ __main__: settings load_settings() image_path sys.argv[1] target sys.argv[2] result run_multimodal_skill(image_path, target, settings) print(json.dumps(result, ensure_asciiFalse, indent2))执行验证命令python run_skill.py ./samples/login_page.png 立即购买按钮预期返回结果类似{ found: true, bbox: [320, 680, 520, 740], visible: true, reason: 在页面底部检测到立即购买按钮区域完整无遮挡 }看到这个结果说明从 Key 读取、Base URL 拼接、图片编码、模型调用到结构化解析整条链路是通的。如果返回的bbox坐标明显超出图片尺寸说明模型在幻觉需要在 Skill 里加一层坐标范围校验这是多模态 Skill 的常见加固手段。验证完多模态再跑一个文本断言 Skill 确认文本通道也正常python run_skill.py --skill json_assert --payload {name:test,age:18} --rules [{path:$.age,expect_type:integer,not_null:true}]返回{passed: true, ...}就说明断言通道没问题。两条通道都验证通过后你的 Skills 库就具备了端到端跑测试流水线的基础能力。后续新增 Skill 时先单独验证再注册进registry.yaml最后接入 CI。5. 本篇常见错排查401、local proxy failed 与解析异常搭建过程中最容易卡住的不是业务逻辑而是通道和解析问题。我把实际遇到过的几类错误整理出来对照排查能省不少时间。第一类是 401 认证失败。报错通常是Error code: 401 - {error: {message: Invalid API key}}。原因有三个Key 没设置到环境变量、Key 复制时带了空格、Key 被禁用。排查顺序是先echo $TAOTOKEN_API_KEY确认变量存在且无空格再去 API Keys 页面确认 Key 状态正常。注意不要在代码里写api_keysk-xxx 这种带尾随空格的字符串openai库不会自动 trim。第二类是local proxy failed或连接超时。这类报错通常出现在 Base URL 写错的情况下比如写成了https://taotoken.net/api/v1导致路径重复或者网络环境有额外的转发配置。排查方法是先用 curl 直接打一次接口curl -X POST https://taotoken.net/api/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {model:gpt-4o-mini,messages:[{role:user,content:ping}]}如果 curl 通而 Python 不通问题在代码或环境变量如果 curl 也不通检查 Base URL 拼写和网络配置。注意 Base URL 就是https://taotoken.net/api不要自己加后缀。第三类是reading choices报错完整信息类似KeyError: choices或list index out of range。这通常是因为返回体不是标准结构可能是模型名写错导致返回了错误信息也可能是请求被限流返回了非预期内容。排查方法是把原始响应打出来print(resp.model_dump_json(indent2))看到实际返回结构后再定位。如果是模型名错误去模型对话页面确认可用模型 ID如果是限流加max_retries和退避重试。第四类是结构化输出解析失败报json.decoder.JSONDecodeError。原因是模型在 JSON 外面包了 markdown 代码块或解释文字。解决办法是在 Skill 提示词里强化约束同时在执行器里加清洗逻辑就是前面executor.py里那段strip()的处理。更稳妥的做法是用response_format{type: json_object} 参数让模型直接返回 JSON。第五类是 OAuth 或 Claude Code 配置报错。如果你用 Claude Code 接入报OAuth error或authentication failed检查.claude/settings.json里的三件套是否完整ANTHROPIC_BASE_URL、ANTHROPIC_API_KEY、ANTHROPIC_MODEL。Base URL 用https://taotoken.net/api不要带/v1。如果用了 CC Switch 或 Cline MCP同样要确认这三项配置一致任何一项缺失都会导致认证失败。第六类是数据构造 Skill 输出字段关联错误比如给 18 岁配了 50 万年薪。这不是通道问题是提示词约束不够。解决办法是在 Skill 里显式声明字段依赖规则比如“年龄小于 22 时月收入不超过 15k”并在输出后用jsonschema做二次校验不通过就重试。排查的核心思路是分层定位先确认通道通不通curl 测试再确认 Key 有没有问题401 排查最后确认输出解析结构化校验。大部分问题集中在通道和解析两层业务逻辑本身反而很少出错。6. 从 Skill 库到测试流水线持续演进跑通单条链路只是起点真正让 Skills 库产生价值的是接入 CI 和建立反馈闭环。我的做法是在run_skill.py基础上加一个批量执行模式读一个 YAML 描述的任务清单依次调用对应 Skill把结果汇总成报告。这样一条端到端测试流水线就成型了数据构造 Skill 生成测试数据断言 Skill 校验接口返回多模态 Skill 验证 UI 截图三个环节串起来就是完整的 AI 产品测试流程。Skill 是会退化的模型更新后之前好用的提示词可能变差。所以每个 Skill 目录下的samples.jsonl要维护一组回归样例每次修改提示词后跑一遍通过率低于阈值就回滚。同时记录每次执行的输入输出和人工反馈定期 review 低分记录来优化 Skill 描述。没有反馈闭环的 Skill 库用不了多久就会变成没人维护的数字垃圾堆。对于需要长期跑 Agent 编排、高频调用模型的团队Coding Plan 在成本和稳定性上更有优势地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。接入过程中遇到参数或配置问题先查接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 大部分报错都能在里面找到对应说明。需要新建 Key 或做环境隔离时去 API Keys 页面操作 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。最后给一个实用建议不要一上来就追求 Skill 数量。先把一个高频痛点场景做成 Skill连续用一周确认成功率稳定在 80% 以上再复制这套模式扩展。Skill 库的竞争力不在于封装了多少个而在于每个 Skill 被复用了多少次。当你的同事开始主动调用你写的 Skill 而不是自己重写时这个库才算真正活起来了。