跳转到主要内容
断言把 agent 在一次评估用例里做的一切——每条消息、每次工具调用、每处文件改动、每一分 token——折叠成一个可解释的结果。NiceEval 提供四种互补的断言机制:有的立即检查一个值,有的在整轮跑完后评估整次运行,有的在 Sandbox 里跑测试,有的衡量效率。四种都产出同一种 Assertion 类型,都进同一套判定规则。第五种机制——让语言模型评判开放式质量——见 Judge

四种断言机制

1. 值断言

t.check(value, matcher)t.require(value, matcher) 立即针对 niceeval/expect 的匹配器评估一个具体值,适合能就地验证的事实。

2. 作用域断言

t.succeeded()t.calledTool()t.messageIncludes() 等在 test(t) 里注册,但在函数返回之后才对完整轮次数据统一评估,适合整次运行的事实。

3. Test-as-scoring

在 Sandbox 型评估用例里,从 test(t) 跑项目测试、构建脚本或临时探针命令——适合代码任务,文件内容和构建结果就是事实标准。

4. 效率断言

t.maxTokens()t.maxCost() 把 token 用量和估算成本变成可评分的维度。答对了但烧掉十倍 token 的 agent,不该跟省着用的拿一样的分。

gate 与 soft 严重度

每条断言都带一个严重度,决定它如何影响最终判定,只有两档:
gate 是硬性要求。一旦失败,整个评估用例立刻判为 failed——不管其它断言表现如何。适合必须为真的事实:“调用了正确的工具”“输出解析为合法 JSON”“没有 shell 命令报错”。niceeval/expect 里大多数匹配器(includesequalsmatchessatisfies)默认 gate;t.succeeded()t.calledTool() 这类作用域断言也默认 gate。
在任意匹配器或断言上用链式方法覆盖默认严重度:

判定规则

所有断言收齐后,运行器按固定优先级取第一个成立项,折叠成一个结果:
errored 压过一切,因为执行证据已经不可信;failed 压过 skipped,避免 t.skip() 掩盖此前记录的硬失败。

passed

没有错误,所有 gate 断言通过,所有 soft 断言都达到阈值(或未开 --strict)。

failed

至少一个 gate 断言没通过,或 --strict 下有 soft 断言低于阈值。硬失败。

errored

执行异常、超时或作者错误,本次执行无法形成可信结论——不伪装成断言失败。

skipped

调用了 t.skip("reason")。完全排除在通过率统计之外。
多次运行(runs > 1)时,单个 eval 的汇总变成通过率(产出 passed 的运行占比)和平均耗时,而不是单一 verdict。

1. 值断言 —— niceeval/expect 匹配器

t.check(value, assertion) 立即评估断言并记录结果。t.require(value, assertion) 做同样的事,但断言失败时立即抛出,中止 test 函数剩余部分——适合前置条件:一个必要事实不成立就没必要继续跑。 niceeval/expect 提供的匹配器:
用法示例:
匹配器是纯函数——(value) => number——所以你可以自己写一个,不需要任何特殊注册就能传给 t.check

2. 作用域断言

作用域断言在 test(t) 里注册,但在函数返回之后才对累积完的完整轮次数据评估。它们读的是 t.send() 产出的标准事件流(见 Drive)及其派生事实——所以只要你的 adapter 产出正确的事件,这些断言对任何 agent 都一样好用。
作用域断言只有在 agent 声明了对应能力时才出现在 t 上。agent 没声明 toolObservability: true 时调用 t.calledTool() 是编译期报错。

运行 / session 维度

工具 / action 维度

calledTool / notCalledToolinput 参数支持一套小型匹配语言:普通对象做深度部分匹配,RegExp 匹配序列化后的输入,谓词函数拿到原始 input 值。

事件流维度(低层逃生舱)

以上所有作用域断言都是这几条低层事件流查询的语法糖。找不到合适的高层断言时,可以降到 eventsSatisfy,对原始 StreamEvent[] 写任意谓词。

结构化输出(挂在 turn 上,不是 t

工作区维度(仅 sandbox agent)

t.sandbox.diff 是可查询对象:t.sandbox.diff.get("src/Button.tsx") 返回文件改动后的内容;t.sandbox.diff.isEmpty() 检查有没有文件变化;t.sandbox.diff.matches(re)t.sandbox.notInDiff(re) 对完整 diff 文本跑正则。 作用域断言到处遵守同一条规则:接收者决定作用域,不是断言名字决定作用域。 t.* 聚合这次 eval run 的全部轮次(含 t.newSession() 开的额外 session);session.*t.newSession() 的返回值)只看这一条 session;turn.*t.send() 的返回值)只看这一轮自己。同一套词汇,不同接收者——各接收者是什么见 Drive

3. Test-as-scoring(Sandbox 型评估用例)

Sandbox 型代码评估用例里,在 test(t) 内跑验证命令,把结果记成断言:
也可以通过标准事件流断言行为:t.calledTool(...)t.sandbox.noFailedShellCommands()t.eventsSatisfy(...),以及 t.sandbox.fileChanged(...) 这类 diff 断言。

4. 效率 / 成本断言

token 用量是一等评分维度。答对了但花费远超预期的 agent,不该跟省着用的拿一样的分。
t.usagetest(t) 里随处可用,暴露 { inputTokens, outputTokens, cacheReadTokens?, … }。Sandbox 型 agent 的 token 数由 adapter 从 transcript 里抠出;远程 agent 直接在 Turn.usage 里返回。

自定义评分器

值断言就是一个函数 (value) => number | Promise<number>,用 makeAssertion 自己写:
自定义匹配器和内置的一样支持链式方法:.gate().atLeast(0.7)

相关阅读

  • Drivet.send()t.newSession() 和 HITL:这些断言读的 Turn 数据是怎么产出的。
  • Judge — 第五种评分机制,评无法写成固定规则的开放式质量。
  • 写 send — 标准事件流如何产出,作用域断言依赖它什么。
  • 评估 — 断言如何折进评估用例生命周期和 verdict 类型。