跳转到主要内容
NiceEval 按 Assertion 评估,一条 Assertion 就是一项检查。调用 t.check(value, match)、t.judge(value, definition)、turn.succeeded() 或 calledTool(...) 时,NiceEval 立即登记一条 Assertion。 没有“先创建一个检查对象,再交给另一个 API 执行”这一步。调用返回的 handle 只用来配置刚登记的这一条,例如加标签、设门槛或计分。
每条 Assertion 的 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。
第一次停止后,这条 Assertion 不能再改配置。重复 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。

相关阅读