实验三:编写一个确定性 Scorer¶
本篇要解决什么问题¶
如果 Scorer(评分器)只把 Target 给出的「success」字段读出来再照抄,就等于让被测系统给自己判分,判分者和被测对象之间也就没有边界了。这个实验从 FieldMatchesExpectedScorer 开始,让 Scorer 根据已经冻结的 Observation(观测)和 Reference 自己下判断,然后再补齐集合匹配、缺字段、无效血缘和多值 Reference 这几种情况。判分过程必须独立。
核心机制¶
Scorer 拿到 ObservationBundle 之后,只能去合同指定的事件或 Artifact 里找证据,再根据这些证据生成 ScoreRecord。每条 Score 都要带上 trial_id、canonical_attempt_id、bundle digest 和 scorer_id,你才能根据记录查回当时用的观测与规则,也能在评分规则升级后,交给另一版 Scorer 重算同一份 Bundle。结果有效却不符合规则时记为 failed,没有可用观测时记为 unscorable,如果血缘出错则记为 invalid。
完整流程¶
- 写 scoring unit、所需字段、Reference、值域和状态表;
- 从 Bundle 倒序找
target_completed,只读取允许字段; - 构造稳定 score_id,覆盖 Bundle、Scorer 与规则配置;
- 缺 output/expected/field 返回 unscorable,不伪造 0;
- 合法值满足规则返回 passed/value=1,否则 failed/value=0;
- 为 passed、failed、missing 和 identity 写测试。
关键数据与不变量¶
只要 Scorer identity 变了,score_id 就得跟着变。如果只是调整 threshold,则必须留住原始 value,不能把实际测到的值和发布政策塞进同一个字段。Score 只能指向 canonical Attempt,而多值 Reference 会列出允许的答案和它们各自适用的条件,你不能随手拿第一个答案来判分,reason 也只写能够核对的解释,不采集隐藏思维链。
动手实验¶
先设计 FieldInAllowedSetScorer(field="risk_band", allowed={"medium","high"}),再依次喂给它准备好的 high、low 和缺字段结果,然后写出各自的 status/value/reason。随后把 allowed 改成 {low},看看规则已经变了以后,旧 Score 还能不能代表同一次测量,并说明你的依据。
预期输出与答案¶
high 应该得到 passed/1,low 应该得到 failed/0,缺字段时则是 unscorable/None。结论很直接:allowed 集合是 Scorer 合同的一部分,你一旦改了它,旧 Score 就不再代表同一次测量,必须换用新的 scorer_id/score_id 重新评分。只有 Gate threshold 变了时,原 Score 才可能继续复用。
如何核对¶
阅读 scorers/rules.py 和 test_scoring.py,手工确认每个 Score 字段能回到 Bundle。
本篇不能证明什么¶
确定性规则每次都能稳定复现,只能说同样的输入会得到同样的判断,却不能证明它真的测到了你关心的能力,也不能保证它覆盖了业务里最难的边界。关键词和集合规则都可能漏掉语义,所以你还要拿真实样本反复验证它是否符合领域需求,并随着业务变化持续更新,必要时再用校准过的 Judge(裁判模型)补上它测不到的部分。