关于 create-skill-test
create-skill-test 指导开发者为 skill、Agent 和工作流包编写 Vally 评估规范,涵盖刺激项设计、夹具校验与评分规则,强调避免过拟合。
主要功能
- 01搭建 Vally eval.yaml 规范
- 02设计有区分度的刺激项
- 03按统计效力确定评估规模
- 04配置夹具与运行环境
- 05定义确定性及提示词评分器
- 06检查评分标准是否过拟合
适用场景
- 01为 skill 创建评估规范
- 02针对不同故障模式增加覆盖
- 03检查评估是否有机会通过
- 04设置并校验评估夹具
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dotnet/ skills --skill create-skill-test安装后在 Claude Code 中输入/create-skill-test确认已加载。
详细介绍
创建技能测试
为技能、Agent 或工作流包搭建评估规范(eval.yaml),使其符合 Vally 架构,通过 skill-validator check 和 check_eval_quality.py,足以给出判定,同时避免过拟合技能自身的措辞。
适用场景
- 为技能、Agent 或工作流包创建新的
eval.yaml - 为现有评估添加刺激项
- 调整评估规模,确保实际能够达到通过门槛
- 设置或修复与评估配套的 fixture 文件
- 检查评分标准项和评分器是否有过拟合风险
不适用场景
- 诊断评估失败或性能退化——请使用
improve-skill-quality - 修改 skill-validator 或评估工作流
- 创建或编辑
SKILL.md文件——请使用create-skill
输入
| 输入 | 必需 | 描述 |
|---|---|---|
| 技能或 Agent 名称 | 是 | 必须位于 plugins/<plugin>/skills/ 或 plugins/<plugin>/agents/ 下 |
| 插件名称 | 是 | 例如 dotnet-msbuild |
| 技能内容 | 是 | 请阅读;没有它就无法编写不过拟合的评分标准项 |
| 场景假设 | 是 | 对偏好案例说明预期改进,或说明防护机制所保护的不变量 |
| 要区分的失败模式 | 建议提供 | 每种失败模式对应一个独立刺激项 |
工作流
步骤 1:证明评估与目标相关
编写 YAML 之前,先说明刺激项要证明什么。如果目标应该改善答案、操作、克制程度或验证结果,使其优于不使用该目标的相同模型,则需要偏好刺激项。若某项有意义的不变量需要保护,即使正确行为与基线一致,也需要非投票式激活契约或空操作防护机制。
不要添加用于衡量以下内容的刺激项:
- 基础模型已具备的通用知识;
- 对仅指向参考文件的技能进行路径回忆;
- 输出量而非正确性;
- 现有案例的改名或轻微改写版本;或
- 单独测试
disable-model-invocation: true的参考技能。
为每个拟议案例添加 能力、风险 和 客户旅程 标签。偏好案例必须带来独特的投票价值。激活契约和空操作防护机制可以共享同一种能力,但必须保护不同的路由或保留不变量。如果两个案例的输入、预期结果、失败模式和评分器路径相同,则保留更强的案例。五个刺激项的最低数量要求绝不能成为凑数的理由。
然后定位目标和测试目录:
tests/<plugin>/<skill-name>/eval.yaml # skills
tests/<plugin>/agent.<agent-name>/eval.yaml # agents (the agent. prefix disambiguates)
tests/agentic-workflows/<package>/eval.yaml # redistributable gh-aw packages
确认目标位于 plugins/<plugin>/skills/<skill-name>/SKILL.md 或 plugins/<plugin>/agents/<agent-name>.agent.md,并阅读其内容。
Agent 评估使用原生 SDK Agent 通道。 Vally 0.14 无法注册自定义 Agent,因此 agent.* 规范不会通过技能实验运行。评估工作流会单独发现这些规范,通过 skill-validator evaluate 运行目标 Agent,并将证据转换为相同的带架构版本的结果和仪表板流水线。技能和 Agent 评估都必须满足独立刺激项的最低数量要求。
对于设置了 disable-model-invocation: true 的技能,请务必谨慎。 模型无法调用它,因此该技能不会出现在面向模型的启用技能组中,任何直接评估都会比较两个相同的组。答案内容评分器无法让这两个组产生差异。对此类技能,诚实的覆盖方式位于依赖层级——通过加载它的技能所做的结果评估,以及通过插件组覆盖。例如,filter-syntax 由 tests/dotnet-test/run-tests/eval.yaml 中的筛选命令场景覆盖。
步骤 2:编写规范骨架
规范采用 Vally 格式。此代码库中的每项评测都使用 stimuli: 和 graders:;scenarios: 和
assertions: 属于已不再加载的 Vally 旧格式。
name: <skill-name>
description: Evaluates the <plugin>/<skill-name> skill
type: capability
defaults:
timeout: 5m
runs: 1
stimuli:
- name: <what the agent must accomplish>
prompt: <natural developer request>
tags:
capability: <distinct-capability>
risk: <failure-being-prevented>
journey: <customer-task>
environment:
files:
- src: fixtures/<case>/Project.csproj
dest: Project.csproj
graders:
- type: output-matches
config:
pattern: (root cause|underlying issue)
- type: exit-success
- type: prompt
rubric:
- <outcome the agent should have reached>
仅使用
defaults:。config:是已弃用的别名,会被代码库门禁拒绝。单独使用该别名时,Vally 会发出警告;规范同时声明两个键时,则会抛出错误。将config:替换为一个defaults:块,并保留其中的设置。
步骤 3:编写内容前,先确定足以支持统计效力的评测规模
门禁会为每个不同的刺激分配一票。同一刺激的重复运行会合并为一张多数方向票,并继续作为可靠性证据保留。
- 不同刺激数 ≥ 5,否则判定为
underpowered——既不会通过,也不会被判定为回归。 - 对 不一致(非平局)刺激票进行精确的单侧符号检验,p ≤ 0.05。 平局不会被 丢弃;它们会减少不一致票的数量。
| 不一致刺激票数 | 通过所需记录数 | p |
|---|---|---|
| ≤ 4 | 无 | ≥ 0.0625 |
| 5–7 | 仅零负例(5W/0L) | 0.031 |
| 8 | 可容忍一次负例(7W/1L) | 0.035 |
恰好有 5 个刺激时,一次平局就会致命,因为这会使不一致票数降至 4。6 个刺激时可容忍一次平局;7 个刺激时最多可容忍两次。负例不可容忍。五个是进入评测的门槛,并非足够的统计效力。例如,若真实的条件胜率为 90%,达到 80% 的统计效力只需 8 张不一致票;若胜率为 80%、70% 和 60%,则分别需要 18、37 和 158 张。请根据需要检测的效果和所需的平局率来确定规模。
使用 runs 衡量可靠性,而非任务广度。Vally 建议 CI 中运行 3 次,夜间运行 5–10 次,以评估通过率、pass@k、pass^k 和波动性。增加运行次数无法满足五个刺激的最低门槛。
不要在 dotnet-skills.experiment.yaml 中设置 runs;实验覆盖项会覆盖每项评测自身的值,而不是为其设置默认值。
步骤 4:编写刺激
- 名称描述测试的内容,而非测试的方式。
- 提示词应是自然的开发者请求。绝不要提及技能、Agent 或其术语——提示词中的引导会抬高过拟合分数,并使基线产生偏差。
- 每个刺激都应检验技能的不同属性。用五个刺激覆盖同一属性,得到的只是算术结果,而非证据。
- 为每个能力刺激添加非空的
capability、risk和journey标签。使用稳定的小写 kebab-case 值。合理的测试组合应覆盖矩阵中的不同行或不同列,而不是用表述上的细微变化重复同一条旅程。 - 为每个刺激指定稳定且唯一的
name。Vally 按(stimulus name, trial index)配对比较轨迹;名称重复会导致槽位身份不明确。 - 对任何会迁移或重写代码的技能,都应包含一个边界/无操作刺激,以证明它会让已经正确的输入保持不变。
- 为每个真实的路由边界添加一个休眠刺激。无操作刺激用于证明技能面对目标范围内已正确的输入时会保持克制;休眠刺激用于证明面对目标范围外的请求时,目标技能会保持不活跃。
第 5 步:配置环境
environment:
files:
- src: fixtures/broken-build/App.csproj # path relative to eval.yaml
dest: App.csproj # path in the agent's working directory
- src: fixtures/broken-build # a directory
dest: .
commands:
- dotnet build -bl || exit 0 # guard intentional failures
不要在技能评测中设置 environment.skills。 实验声明了
vary: /environment/skills,并自行提供该值——基线组为 [],技能组为
plugins/<plugin>/skills/<skill>——因此,评测中声明的任何内容都会在
每个组中被替换。它无法只为某一个组添加技能。environment.skills 适用于
agent.* 评测;原生 Agent 通道只会在隔离的
目标运行中加载这些条目,而插件运行会加载生产插件的完整技能
集合。请参考现有 Agent 评测(例如
tests/dotnet-test/agent.test-quality-auditor/eval.yaml)的格式,而不要照搬凭记忆想出的形式——
本仓库中的规范对这些条目的写法并不一致。
测试样例规则——每一条都曾导致过真实结果:
- 所有引用的测试样例都必须纳入 git 跟踪。
.gitignore(例如coverage*.xml)曾悄悄忽略一个已提交的测试样例:评测在本地通过,却在 CI 中于设置阶段失败。请用git ls-files验证,而不要只查看工作区。 - 每个测试样例都必须符合其预期用途。 预期健康的测试样例必须能够构建;预期损坏的测试样例必须因刺激所针对的确切原因失败,且不能有其他原因。 测试样例作者引入的无关“预先存在的构建问题”会让评判器扣 Agent 的分。
- 每个测试样例都必须复现其刺激所命名的错误。 如果没有复现,基线就会得分很高,技能也无从发挥作用。
- 覆盖率测试样例必须内部一致。 Cobertura 报告中的
line-rate、汇总总数(lines-covered/lines-valid)和<line>元素如果互相矛盾,会导致两个组读到不同的事实,损失应归咎于测试样例。若评分标准条目或提示词中引用了某个数字,也要在同一次更改中更新。 - 不要接入重复的测试样例来提高
n;重命名遗留样例只会增加试验次数,并不能提供证据。 - 预期会失败、但仍需生成产物的设置命令必须添加保护(
|| exit 0),否则 vally 会丢弃该试验。 - 清理命令若会删除源文件,必须跳过包含
SKILL.md的目录——暂存的技能位于该目录中,删除它只会中止技能组。 - 保留完整的文件集合。 如果提示词限制了编辑范围或要求保留源文件/测试文件,应对范围内的每个文件进行快照或比较,而非只检查一个代表性文件。只检查主要输出的评分器可能会漏掉对同级文件的删除、截断或修改。
第 6 步:编写评估器
评估器是在每个实验组上运行的硬性通过/失败检查。
| 类型 | 必需配置 | 用途 |
|---|---|---|
output-matches / output-not-matches | pattern | 对 Agent 输出执行正则匹配 |
output-contains / output-not-contains | substring | 检查输出中的字面文本 |
file-exists / file-not-exists | path | 对工作目录执行 Glob 匹配 |
file-contains / file-not-contains | path、value | 检查生成文件的内容 |
run-command | command(以及可选的 expected_exit_code、timeout、stdout_matches) | 验证生成的代码能否实际构建/运行 |
exit-success | — | Agent 生成了非空输出 |
prompt | — | 使用 rubric 运行 LLM 评审器 |
规则:
- 如果评估器缺少
config,或缺少必需的键,解析仍会成功,但不会执行任何检查。 常见原因是编辑时缩进错误;check_eval_quality.py会拦截这种情况。 - 优先使用能适用于多种有效方案的宽泛模式:
(root cause|primary error|underlying issue)。 - 如果技能要求特定的输出格式,就对其进行断言。 如果技能要求输出一行明确的
Recommendation:,它可能会悄然不再这样做,而评估仍然通过。 - 使用
file-not-contains/file-not-exists证明 Agent 避免了错误操作。
编写提示词评估器之前,先定义确定性契约:
- 黄金验收: 生成 fixture 并应用
golden_patch(如果有)。黄金工作区和最终的golden_trajectory回复必须通过所有适用的确定性评估器。 - 变更拒绝: 构造一个评估旨在捕获的典型缺陷,例如文件缺失、发现的测试数为零、 编辑超出范围,或语义值发生变化。相关的确定性评估器必须失败。
- 完整状态检查: 覆盖请求中提到的所有文件和产物。不能因为存在一个肯定的子字符串就接受不完整的产物。
使用 golden_patch 保存可重放的工作区状态,使用 golden_trajectory 保存预期的最终回复。叙述中的编辑、构建或测试并不能作为证据:完成声明必须有补丁,或由 run-command 评估器重放证据。
第 7 步:编写评分项
评分项通过两两对比(基线组与熟练组)进行判断。过拟合评审器会将每个评分项分类:
| 分类 | 描述 | 目标 |
|---|---|---|
| 结果 | Agent 是否得出了正确结果——关注做了什么,而非如何做 | 以此为目标 |
| 技巧 | Agent 是否采用了特定于技能的流程 | 尽量减少 |
| 术语 | Agent 是否使用了该技能的术语 | 避免 |
- 评估结果,而非方法:“找出了构建失败的根本原因”,而不是“使用
dotnet build /flp重放 binlog”。 - 接受任何有效方案。
- 绝不按名称提及技能,也绝不复用
SKILL.md中的措辞。 - 绝不奖励使用技能——运行框架会单独报告技能激活情况,因此这类评分项毫无意义,只会抬高过拟合分数。
- 不要测试模型已有的知识;这不会带来任何增量。
- 每个评分项都应能独立评估。
- 不要按原始数量奖励(测试数量、报告长度);当两个实验组都采取行动时,评审器会进行比较。
良好:
rubric:
- Correctly identified the missing NuGet package as the root cause of the build failure
- Recognized that downstream failures cascaded from that root cause
- Suggested a concrete fix that resolves it
过拟合:
rubric:
- Replayed the binary log using 'dotnet build /flp:v=diag' # technique
- Measured cold, warm, and no-op build scenarios # vocabulary
- Used the template-comparison skill # rewards activation
第 8 步:谨慎添加约束
constraints:
expect_tools: [bash]
reject_tools: [edit, create]
reject_skills: [some-skill]
- 在建议性问题上设置
expect_tools: [bash]会强制执行 restore 或 build,并将回答变成超时,却没有任何质量收益。只有当任务确实需要工具时,才要求使用工具。 - 要让只读刺激保持只读,正确做法是使用
reject_tools。
第 9 步:添加休眠防护
休眠防护用于证明:对于表面上符合技能条件、实际却不适用的请求,该技能会保持休眠状态。针对每个实际存在的“何时不应使用”边界,各添加一项:输入格式错误、请求超出范围、项目类型不兼容、框架版本错误或缺少前置条件。
- name: Decline dump analysis request
prompt: |
I already have a .dmp crash dump from my .NET app. Can you help me
analyze it to find the root cause of the crash?
expect_activation: false
graders:
- type: output-matches
config:
pattern: (out of scope|not cover|does not|cannot|only.*collect)
- type: prompt
rubric:
- Stated that dump analysis is out of scope
- Did not open or analyze the dump file
- Did not install analysis tools such as dotnet-dump analyze, lldb, or windbg
- Suggested the correct alternative
切勿将
expect_activation: false与constraints.reject_skills结合使用。 这会强制无技能运行技能组,因此测试框架无法观察目标技能是否劫持请求。比较结果仍会作为仅供报告的证据显示,但不会参与偏好判定;意外的单独激活会阻止测试通过。expect_activation: false单独使用才是此仓库的惯例。
工作流包场景
对于包目标,请检查 agentic-workflows/<package>/aw.yml,然后阅读其入口工作流、本地导入项和随附 Agent。原生 SDK 通道会使用离线 fixture 评估它们的实际提示词正文和已安装资源。
将收集器输出、修订/跟踪证据和服务响应指定为 fixture 输入;在 result.json 中提出终端操作,而不要假装通过实时 GitHub 或安全输出工具发布内容。使用确定性评分器断言结构化结果。不要将预期答案放入 Agent 可读取的 fixture 或暂存的评分器脚本中;应通过评分器 argv 传递预期值。
提示词表达式根据扁平的 workflow-context.json fixture 渲染,其中的键是经过精确去除首尾空白的表达式,值为字符串。缺少上下文会导致设置失败。正确选择 noop 的工作流仍属于预期激活的决策证据,而不是 expect_activation: false 路由证据。请酌情涵盖常规、部分、过时、不兼容、缺少证据和多模块场景。将编译、辅助程序执行和实际消费者发布测试分开:此通道标记为 workflow-prompt-sdk,而非端到端 Actions 执行。
dotnet run --project eng/skill-validator/src/SkillValidator.csproj -- evaluate `
agentic-workflows/<package>/aw.yml `
--tests-dir tests/agentic-workflows --runs 1 --verdict-warn-only
防护评分标准会验证三点:识别(说明为何不适用)、克制(不运行工作流、不更改文件、不安装内容)、引导(给出正确的下一步)。
步骤 10:验证
dotnet run --project eng/skill-validator/src/SkillValidator.csproj -- check --plugin ./plugins/<plugin>
python eng/eval-quality/check_eval_quality.py
./eng/run-skill-evals.sh <plugin> <skill-name>
对于 Agent 评估,直接运行原生通道:
dotnet run --project eng/skill-validator/src/SkillValidator.csproj -- evaluate \
plugins/<plugin>/agents/<agent>.agent.md \
--tests-dir tests/<plugin> \
--runs 1 \
--verdict-warn-only
CI 会通过 eng/vally-adapter/adapt-agent-results.mjs 处理此结果,应用与技能结果相同的独立刺激符号检验策略。
验证必须覆盖以下四个层面:
- **确定性结构:**运行
check_eval_quality.py;如果检查器发生变更,还要运行相关的检查器自测。 - **生产解析与黄金回放:**通过仓库的 Vally 入口运行技能评估。对于 Agent 评估,运行
skill-validator evaluate,以证明原生 SDK 通道接受可执行的场景字段。该解析器不会读取golden_trajectory或golden_patch,因此请单独验证这些参考:运行check_eval_quality.py,然后生成测试夹具、应用黄金补丁,并针对黄金工作区运行所有适用的确定性文件和命令评分器。确认最终的黄金回复通过其输出评分器。 - **正常执行:**使用正常的工作线程并发数和声明的
defaults.timeout。不得仅通过单个工作线程或临时增加时间预算来认证评估。如果正常并发暴露出竞态或超时,应将其归类为可靠性证据。 - **跨模型系列敏感性:**对于广泛的路由或行为变更,至少使用一个 GPT 系列执行器和一个 Claude 系列执行器进行评估。分别报告各项结果。不同的执行器或裁判模型系列不会增加独立刺激的数量。
check_eval_quality.py 会拦截 22 类可能导致结果失真的结构缺陷:缺失或未跟踪的测试夹具、相互矛盾的覆盖测试夹具、空评分器配置、带有 reject_skills 的休眠保护、刺激数量低于下限、重复的 YAML 键或刺激名称,以及无效的默认值、标签、黄金证据、测试命令或 ATIF 轨迹。完整列表请参阅 eng/eval-quality/README.md。不要将新的评估添加到 eng/eval-quality/underpowered-allowlist.txt 中——门禁会拒绝相对于基础分支新增的允许列表条目。
对于正式运行,请提交包含 /evaluate 的 PR 审查,以便将其绑定到已审查的提交。
验证清单
- 目录为
tests/<plugin>/<skill-name>/或tests/<plugin>/agent.<agent-name>/ - 规范使用
stimuli:/graders:,并包含当前的defaults:设置块 - 至少有 5 个符合偏好评估条件的独立刺激;休眠契约不计入此下限
- 每个刺激都不可或缺且符合目标;每个偏好案例都提供独立的投票价值
- 每个能力刺激都有稳定的
capability、risk和journey标签,以及唯一名称 - 提示词绝不提及技能、Agent 或其词汇
- 每个引用的测试夹具都存在,并由
git ls-files跟踪 - 每个测试夹具的行为都符合其刺激中的假设:正常的测试夹具能够构建,有意损坏的测试夹具仅因指定原因失败
- 保留和范围评分器覆盖完整的范围内文件集
- 每个评分器都包含必需的
config键 - 技能规定的任何输出格式都有对应的评分器
- 黄金证据通过确定性评分器,且一个真实的变更会导致其失败
- 评分标准条目以结果为导向,绝不奖励使用技能
- 改写技能包含无操作案例;路由边界仅使用
expect_activation: false - 生产运行器接受可执行规范,并在正常并发和时间限制内完成
- 黄金参考通过独立检查器和确定性回放
- 广泛的路由或行为变更分别提供 GPT 系列和 Claude 系列的证据
-
skill-validator check和check_eval_quality.py均通过
常见陷阱
| 陷阱 | 解决方案 |
|---|---|
编写 scenarios: / assertions: | 这种格式已无法加载;请使用 stimuli: / graders: |
使用已弃用的顶层 config: 别名 | 将其重命名为 defaults: 并保留其设置 |
| 评测恰好包含 5 个刺激项 | 一次平局就会让通过变得不可能;请根据效果和预期平局率确定规模 |
增加 runs 以达到最低要求 | 重复运行用于衡量单项任务的可靠性;请增加刺激项 |
| 提示词按名称提及技能或 Agent | 改写为自然的开发者请求 |
| 评分标准奖励使用技能 | 删除该项——测试框架会单独报告技能激活情况;评分标准衡量结果 |
| 存在夹具,但被 git 忽略 | 使用 git ls-files 验证;否则 CI 设置会失败 |
| 夹具无法构建,或因错误的原因而失败 | 先修复夹具,再判断是否是技能的问题 |
使用 reject_skills 的休眠保护 | 单独使用 expect_activation: false |
在咨询类问题上设置 expect_tools: [bash] | 删除它;这会导致超时,而非提升质量 |
| 代码生成的超时时间过短 | 使用约 360 秒;空输出会导致所有评分器都判定失败 |
| 编辑后遗留重复的 YAML 键 | 它会逐字段覆盖下一个刺激项;请删除多余的代码块 |
| 刺激项名称重复 | Vally 将名称用作比较标识;请为每个刺激项设置稳定且唯一的名称 |
对 disable-model-invocation: true 的技能直接进行评测 | 删除该评测,并通过使用方的结果覆盖相关引用 |
| Agent 评测低于刺激项最低数量 | 原生 Agent 适配器使用相同的符号检验门槛;请增加符合偏好比较条件的独立刺激项 |
使用 ./eng/run-skill-evals.sh“运行”Agent 评测 | 该辅助脚本仍仅适用于技能;请使用 skill-validator evaluate |
Agent 评测缺少 environment.skills | 声明 Agent 会调用的技能,否则无法调用它们 |
在技能评测中设置 environment.skills | 实验会改变该键,并在每个实验组中替换它;该声明不会生效 |