t.check(value, match)、t.judge(value, definition)、turn.succeeded() 或 calledTool(...) 时,NiceEval 立即登记一条 Assertion。
没有“先创建一个检查对象,再交给另一个 API 执行”这一步。调用返回的 handle 只用来配置刚登记的这一条,例如加标签、设门槛或计分。
key 和 label 各自最多设置一次。重复设置会报错,即使两次写的是同一个值。
Match 只负责比较
niceeval/expect 导出的都是 Match,一个 Match 就是一条比较规则。它可以复用,不会变化,同样的输入总是得到同样的结果,也没有副作用。Match 本身不带门槛、分值或控制流,这些都在 handle 上配置。
t.check(value, match) 只接受这两个参数,在调用时读取 value 并登记 Assertion。
matched 还是 mismatched,只说明这个值是否满足这条规则,不说明规则本身写得对不对。任务允许多种正确结果时,规则要覆盖所有这些结果。
equals(expected) 做深相等比较;比较字符串时,要求一字不差。如果任务接受多种等价写法,先把产物解析或归一化成任务关心的值,再比较。只有字面写法本身就是任务要求时,才直接比较字符串。
在 Turn、Session 和整个评估上检查
succeeded、calledTool 这类方法调用即登记一条 Boolean Assertion。调用在谁身上,就决定看哪些证据:
turn.*只看这一个 Turn。session.*看这个 Session 到调用时为止的全部内容。t.*看调用时所有已经开始的 Session。
calledTool 不接受次数参数,次数写在 toolMatch 上,例如 .exactly(2)。input、output 和 status 条件也写在 toolMatch 里:JSON 结构用 jsonMatch,命令参数用 commandMatch。notCalledTool 接受同样的 matcher,但不带次数。
通过制和计分制
defineEval 是通过制。Boolean 检查直接影响这个 Attempt 的判定:有一条 mismatched,最终判定就是 failed,但后面的检查仍会继续执行和登记。
连续值检查和 Judge 给出 0 到 1 之间的测量值。测量值默认不影响判定,在 handle 上调用 .gate(minimum) 后,低于最低分才判 failed。
defineScoreEval 是计分制。它既有判定,也有累计分数 score。分数就是各项贡献相加,没有隐含的总分或百分比。
Assertion 默认只记录结果,不计分;加上 .score(n) 才贡献分数。Boolean 满足时加 n,不满足时加 0;测量值 m 贡献 m * n。t.score(n) 直接加分,它返回的 handle 只能设置 key 和 label。
.gate(minimum),它不影响这一项的分数,所以同一条可以既设门槛又计分。
低分本身不算失败;只有设了门槛且没达到时,判定才是 failed,已经得到的分数仍然保留。没有 .score() 的检查不会显示 +0。一项计分都没有的计分制评估,正常得到 score: 0。
停止后续步骤
.orStop() 在检查没满足时停止后续步骤,它返回 Promise,必须 await:
- Boolean 检查直接用自己的条件。
- 还没设门槛的测量值,用
.orStop(minimum)指定停止的最低分。 - 已经写了
.gate(minimum)的,用不带参数的.orStop()沿用同一个最低分。
await 之后还没执行的代码,这部分代码不会产生结果。已经发生的普通 JavaScript 副作用不会撤销,之前启动的并发任务也不会被取消。
停止后的判定取决于原因:
- 因门槛没满足而停止,判定是
failed。 - 只用
.orStop(minimum)而没设门槛的测量值低于最低分,只停止后续步骤,不会因此判failed。 - 因
unavailable或errored而停止,判定是errored。
await 不带参数的 .orStop() 会得到同一个 Promise。
Judge
defineJudge 定义一个 Judge。t.judge(material, definition) 只接受 Judge 定义,是 t.check(material, definition) 的简写,两者走同一条执行路径。通过制和计分制都能用 .gate(minimum),计分制还能用 .score(n);同一条 Assertion 只调用一次 Judge。材料、配置和失败时的行为见 Judge。
证据缺失和结果保存
缺少证据不能当成普通的不满足。结果是unavailable 或 errored 时,会保留原因和脱敏后的证据。
每条 Assertion 的结果也不只是“通过”或“没通过”。它保存三类内容:
- 被检查的值:安全的结构化内容,或指向它的稳定引用。
- 用了哪条规则:Match 或评估函数的身份和完整配置。
- 评估结果,以及门槛、计分和显示需要的信息。
t.check(a, b) 和 t.judge(material, definition) 由你提供被检查的值和规则。calledTool、succeeded 这类方法替你取得被检查的值、构造 Match,保存规则相同。
举两个例子:
t.check(await t.sandbox.runCommand(...), commandSucceeded())保存这次命令的命令名、参数、退出状态、运行时间,以及脱敏后的 stdout 和 stderr(内容或引用)。calledTool(...)保存它看到的每次工具调用:调用身份、脱敏后的输入、状态、输出或错误的引用、记录是否完整,以及命中了哪些事件。没有命中时,也保存看过的范围和候选调用,而不是只存一个false。
query、view 和源码视图都能直接用保存下来的结果组织文案和界面,不需要重新运行 Match 或再调用一次 Judge。界面上的 expected 和 received 是读取时根据这些内容生成的,不是只存了两个字符串。
Assertion 只规定必须保存什么,怎样落盘由 Record 决定。判断所需的数据没法安全保存时,结果必须是 unavailable,不能只留下 matched 或 mismatched。