Skip to content

评测用例格式

eval-samples 文件是 omk eval / omk doctor 使用的版本化用例集文档。它的 samples 数组包含具体用例,每条一个 prompt,外加可选的 rubricassertions 和元数据。JSON 与 YAML 都是一等格式:生成结果默认使用 eval-samples.json,手写时可使用 eval-samples.yaml

想知道怎么设计一套严谨用例(测什么、测几条、元数据字段),见用例设计;本页是逐字段的格式参考。

存放位置

推荐把项目共享用例和 skill 私有用例分开:

  • 项目共享用例:放在项目根目录的 eval-samples.jsoneval-samples.yaml。适合多个 variant 做 A/B 对比,保证它们跑同一套测试集。
  • skill 私有用例:放在 <skill>/.omk/eval-samples.json<skill>/.omk/eval-samples.yaml。因此,私有用例要求使用目录 skill(<skill>/SKILL.md)。

omk eval 只会在单 treatment 且能明确定位到某个 skill 时自动发现 skill 私有用例。多版本对比建议使用项目共享用例,或显式传 --samples

自动发现只识别以上两个 canonical 文件名。同一作用域同时存在 JSON 与 YAML 时,omk 会报告歧义并停止,不会静默选择其中一个。.ymlsamples.*<name>.eval-samples.* 扁平 sidecar 和分片目录均不参与自动发现;仍可通过 --samples 显式读取自定义 JSON / YAML 文件或分片目录。

每个文件都必须声明 schemaVersion: omk.eval-sample-set/v2,历史顶层数组格式会被拒绝。根文档、sample、assertion、mock 及其嵌套契约都采用严格校验;未知字段会在执行前报错,不会被静默忽略。发布的 JSON Schema 位于 schemas/eval-samples/v2/eval-sample-set.schema.json

json
{
  "schemaVersion": "omk.eval-sample-set/v2",
  "samples": [
    {
      "sample_id": "s001",
      "prompt": "审查这段代码的安全性",
      "context": "function auth(u, p) { db.query('SELECT * FROM users WHERE name=' + u); }",
      "rubric": {
        "security": {
          "criterion": "准确识别注入漏洞并说明影响",
          "weight": 0.6
        },
        "actionability": {
          "criterion": "给出可直接采用的参数化查询修复",
          "weight": 0.4
        }
      },
      "assertions": [
        { "type": "contains", "value": "SQL", "weight": 1 },
        { "type": "contains", "value": "parameterized", "weight": 1 },
        { "type": "not_contains", "value": "safe", "weight": 0.5 }
      ]
    }
  ]
}

根文档还可以声明 requires,其中包含 toolsfilesenvpreflight 字符串数组;除此之外不接受其它根字段。

字段说明

字段类型必填说明
sample_idstring用例唯一标识
promptstring发送给模型的用户提示词
contextstring附加上下文(代码片段等),会被包裹在代码块中拼接到 prompt 后。也支持 URL,运行时自动抓取内容
cwdstring单用例工作目录覆盖(这一条的 runtime context)
rubricobject具名且独立判定的评分维度;每个值包含 criterionweight
rubric.<name>.criterionstring当前维度的一条非空评分准则
rubric.<name>.weightnumber(0, 1] 内的正权重;单条 sample 的所有 rubric 权重之和必须为 1
assertionsarray断言检查列表,详见断言类型
assertions[].typestring断言类型
assertions[].valuestring|number视类型检查值(containsmin_lengthcost_max 等必填)
assertions[].valuesarray视类型字符串数组(contains_allcontains_any 必填)
assertions[].patternstring视类型正则表达式(regex 必填)
assertions[].flagsstring正则标志(默认 "i"
assertions[].schemaobject视类型JSON Schema 对象(json_schema 必填,基于 ajv
assertions[].referencestring视类型参考文本(semantic_similarity 必填)
assertions[].thresholdnumber通过阈值;默认值随类型而定 —— LLM 打分类为 3rouge_n_min / bleu_min0.5mock_hit1
assertions[].fnstring视类型自定义断言 JS 文件路径(custom 必填)
assertions[].weightnumber权重(默认 1)
assertions[].notboolean反转有效的通过/失败读数,适用于任意类型
assertions[].nnumberrouge_n_min 的 n-gram 阶数(默认 1)

loader 会在任何模型调用前校验完整契约。rubric 至少包含一个维度;维度名与 criterion 不能是空白文本,weight 必须是有限正数,单条 sample 内的权重和须在 1e-9 容差内等于 1。发布的 JSON Schema 表达局部结构与数值范围,runtime validator 额外执行跨属性的权重和校验。非法输入会作为配置错误失败,绝不会计入模型失败。

元数据与沙箱字段

用例还能带元数据(纯文档 / 诊断用,不参与 grading / judge / verdict)和沙箱字段(用于脱离真实环境评测)。完整指引见用例设计,这里给字段索引:

字段类型用途
capabilitystring[]该用例覆盖的能力维度(驱动 coverage 诊断)
difficulty'easy' | 'medium' | 'hard'难度分桶(强枚举)
constructstring测什么:necessity / quality / capability(允许自定义)
provenance'human' | 'llm-generated' | 'production-trace'数据来源
covers{ targetKind, ref }[]可选声明的 skill 结构锚点,建议先用于关键用例;仅用于 Skill Map
mocksobject[]工具调用拦截列表 —— 要求执行器支持 mock 拦截
mocksStrictboolean未命中任何 mock 的工具调用直接 deny(默认 true;只有显式允许透传时才设为 false
tripwireboolean诱错样本:LLM 应当 fail(默认 false
environmentobject仅作 prompt 上下文的前置:cli_available / files_available / notes;不会物化文件或环境变量

loader 还会校验跨字段引用。每条 mock_hit: "Tool:N" 必须指向该工具声明的第 N 条 mock;mock 不存在或序号越界都属于配置错误。执行器兼容性会在评测前单独检查,支持矩阵见执行器

mocks[].tool 与 trace 断言使用同一套 source-neutral 工具身份(如 BashReadEdit)。executor adapter 会先把 exec_commandcommand_executionapply_patch 等 runtime-native 名称归一化再匹配;为兼容旧用例和自定义工具,原生名称的精确匹配仍然保留。

covers 是可选的显式声明字段,不从 prompt 文本里推断。建议先给关键用例、关键 reference / workflow / hard rule 声明它,让 Studio 能画出已声明的结构边,而不是要求每条用例都变成维护负担。不写只表示 Skill Map 暂无这条声明边,不代表该结构一定没被测到:

Studio 的 Skill Map 节点详情也会读取这个声明:选中图中的节点时,会显示该结构关系是否由 sample.covers 显式声明。

yaml
schemaVersion: omk.eval-sample-set/v2
samples:
  - sample_id: release-risk-summary
    prompt: "总结发布风险和回滚方案。"
    covers:
      - targetKind: reference
        ref: references/release-policy.md
      - targetKind: workflow
        ref: release
      - targetKind: workflow_node
        ref: release.check

targetKind 支持 skillskill_filefrontmatterreferencescripthard_ruleworkflowworkflow_nodereference / scriptref 是相对 skill 根目录的路径;hard_rule / workflow 使用规则或 workflow id;workflow_node 使用 workflowId.nodeId。这个字段不进入 grading、评委 prompt、verdict,也不进入 sample 指纹。

URL 自动抓取

promptcontext 中的 URL 会在评测前自动抓取内容并内联到文本中。适用于引用在线文档、API 文档等场景:

json
{
  "schemaVersion": "omk.eval-sample-set/v2",
  "samples": [{
    "sample_id": "s001",
    "prompt": "请根据以下 PRD 文档生成评测用例:https://wiki.example.com/prd/feature-x"
  }]
}

宿主会在 Resolve 阶段解析 URL,并在编译 Evaluation Core Dataset 之前替换为实际内容。每个规范 URL 只解析一次;规范化后的 UTF-8 字节会封存为按摘要寻址的 content 资源,同一份字节同时进入 Dataset input 与 Definition digest。HTTP/MCP 等传输细节只保留在非规范 lineage 中,因此仅切换传输方式不会改变测量身份。

解析顺序是:匹配 URL 优先使用 MCP(例如受 SSO 保护的私有文档),其余 URL 或 MCP 失败的 URL 再使用安全 HTTP。解析采用失败关闭:任何非占位 URL 无法解析时,本次评测直接停止,不会静默退回原始 URL 继续测量,避免临时网络状态改变实际测量 construct。

私有文档 URL:在项目目录放一个 .mcp.json 配置文件,或通过 --mcp-config 指定路径:

json
{
  "mcpServers": {
    "docs": {
      "command": "npx",
      "args": ["@example/docs-mcp-server"],
      "env": { "DOCS_API_TOKEN": "xxx" },
      "urlPatterns": ["docs.example.com"],
      "fetchTool": {
        "name": "fetch_doc",
        "urlTransform": {
          "regex": "docs\\.example\\.com/([^/]+/[^/]+)/([^/?#]+)",
          "params": { "namespace": "$1", "slug": "$2" }
        },
        "contentExtract": "data.body"
      }
    }
  }
}

公网 URL:通过有界 HTTP resolver 获取。每次重定向都会重新校验目标;loopback/私网/link-local 地址会被拒绝;只接受文本型 UTF-8 响应,并限制响应大小。私网或需要认证的文档必须显式配置 MCP resolver;URL authority 中携带凭证会被拒绝。example.com 等 RFC 占位域名保持字面量,永不发起请求。

项目根目录的 .mcp.json 会被自动发现,--mcp-configeval.yaml.mcpConfig 可显式覆盖。resolver 启动的 MCP 客户端只存活于单次 Resolve 会话,并且会在继续编译 Core 之前全部关闭。 urlPatterns 是 hostname allowlist:使用 docs.example.com 这样的精确 host,或 *.example.com 这样的显式子域通配;不允许按 path/query 子串匹配。

评分策略

1. 断言评分

基于规则的本地检查,每个断言产生通过/失败结果。

计算方式:

  • 通过率 = 通过断言的权重之和 / 总权重(0~1)
  • 分数 = 1 + 通过率 × 4(映射到 1~5 分)
  • 示例:3 个断言(权重各 1),2 个通过 → 通过率 = 2/3 → 分数 = 1 + 0.67 × 4 = 3.67

算综合分时,断言会拆成两个独立层 —— factScore(事实类检查)和 behaviorScore(行为类检查),各自用上面的公式在自己那批断言上打分。

2. Rubric 评分

每个 rubric 维度会被编译成一次独立的评委调用,避免同一 prompt 内多个准则因排列位置产生优先级偏差。评委对每个适用维度打 1-5 分;只有全部计划维度都有观测值时,OMK 才按密封权重计算加权平均,任一维度缺失都会让 rubric 聚合结果缺失。所有适用维度也都会进入发布阶段的评委分歧与不确定性门禁。

3. 综合分数

综合分是所有存在的层分数的平均 —— 共三层:

来源
factScore事实类断言(contains / regex / json_* / equals / semantic_similarity / tool_*_contains …)
behaviorScore行为类断言(长度 / 词数 / cost_max / latency_max / turns_* / tools_* / custom …)
judgeScore独立判定的 rubric 维度加权聚合

composite = mean(存在的层)。某层没有断言(或没配评委)时从平均里剔除,不当作 0 分;没有任何已观测层的 sample 不产生数值型综合分。

完整推导、等权重 caveat、以及多层 verdict gate 与综合分的关系见评分公式

断言类型

30+ 种,分两类。确定性断言本地校验(不调模型);LLM 打分断言会调评委、返回 1-5 分,按 threshold 判通过。

确定性(本地,不调 LLM):

类型说明
contains / not_contains包含/不包含子串
regex正则匹配
min_length / max_length长度范围
json_valid / json_schemaJSON 校验
starts_with / ends_with前缀/后缀匹配
equals / not_equals精确匹配
word_count_min / word_count_max词数范围
contains_all / contains_any多值匹配
cost_max / latency_max成本/延迟限制
tools_called / tools_not_called / tools_count_min / tools_count_maxAgent 工具调用断言
tool_output_contains / tool_input_contains / tool_input_not_contains工具输入/输出内容匹配(_not_ 为「不得包含」)
mock_hit声明的沙箱 mock 实际被某次工具调用命中(见用例设计
turns_min / turns_max多轮对话轮数限制
rouge_n_minROUGE-N recall ≥ threshold(reference 填参考答案,n 默认 1,threshold 默认 0.5)
levenshtein_max编辑距离 ≤ value(用于「输出跟参考几乎一致」场景)
bleu_minBLEU-4 ≥ threshold(unsmoothed,短文本会塌陷到 0)
custom自定义 JS 函数(30s 超时)

LLM 打分(调评委,1-5 分,threshold 默认 3):

类型说明
faithfulness输出是否被 sample.context 支持(反幻觉)
answer_relevancy输出是否切题回答 sample.prompt;能抓住跑题、回避、冗余
context_recallsample.context 关键事实在输出中的覆盖率(reference 可显式列 gold facts)
semantic_similarityreference 的整体语义相似度

通用修饰:

任何断言加 not: true 即反向(替代 not_contains / not_equals 等成对类型;老类型保留作 alias):

yaml
- type: regex
  pattern: "TODO|FIXME"
  not: true              # 必须不含 TODO/FIXME

对于异步评委断言和 custom 断言,仅在得到有效的原始通过/失败读数后执行反转。Provider 失败、超时、取消、预算截断、缺少输入和无效输出仍是失败或缺失证据;not: true 绝不会把基础设施或协议失败变成通过。

断言组合(assert-set):

assert-set 类型让多个断言以 any(OR)或 all(AND)逻辑组合,可嵌套:

yaml
- type: assert-set
  mode: any              # 任一通过即过 (mode: 'all' 则需全部通过)
  children:
    - { type: contains, value: "参数化" }
    - { type: contains, value: "prepared statement" }
    - { type: regex, pattern: "bind\\(.*\\?" }

子断言可独立带 not: true;嵌套 assert-set 可在确定性断言上表达任意布尔逻辑。异步评委断言(semantic_similarityfaithfulnessanswer_relevancycontext_recallcustom)必须保留在顶层,因为 assert-set 走同步求值;嵌套这类断言会在执行前被拒绝。

分层评分提示。 assert-set 只在叶子子断言同层(全是事实层 / 全是行为层)时才计入 fact / behavior 分层 composite。混层 assert-set(如一条 contains + 一条 max_length)没有单一可诚实归属的层,故不计入分层 composite(仍计入扁平的断言通过 / 失败)。若想让某条用例的信号落进分层 composite,优先用叶子断言,或让每个 assert-set 保持在同一层内。

自定义断言

js
// my-assertion.mjs
export default function(output, { sample, assertion }) {
  return { pass: output.includes('SQL'), message: '检查了 SQL 关键字' };
}