> ## Documentation Index
> Fetch the complete documentation index at: https://niceeval.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# NiceEval Taste：写出一眼能看懂判据的评估

> 让同事只读评估用例正文，就能说出它测什么、什么算通过、证据为什么够。用已有事实、简单条件和普通算术写判据，少造新概念。

写完一条评估，让同事只读正文就能说明：测什么、什么算通过、为什么这些证据足够、怎样计分。先写业务判据，再选择最短且忠实的表达。不要先遍历日志，再给统计结果起一个业务名称。

能用已有事实、简单条件和普通计算表达，就不要命名新业务概念。Match 用于选择材料和组合小条件；几行算术直接写在正文，再使用官方计分入口加权。少概念比少行代码更重要。

本页帮助你审阅和改写已有评估。声明与运行步骤见[编写评估用例](/docs/zh/tutorials/authoring)，签名见 [defineEval 参考](/docs/zh/reference/define-eval)。先按被测对象选择写法：Agent 类使用消息、工具调用和会话历史；自定义应用类由 Adapter 提供正式业务事实。两类共用 Match、Judge 与计分规则。

* [Agent 类评估](#agent-类评估)：从单轮工具检查到多轮回答质量。
* [自定义应用类评估](#自定义应用类评估)：从上下文事实到领域原子与业务计算。

## Agent 类评估

被测对象通过 `t.send()` 接收任务，并返回消息、工具调用和 Turn 状态时，直接使用 Agent 的作用域断言。先检查可精确读取的事实，再让 Judge 判断回答含义；无需为工具名称、参数或回复再建一套应用 reader。

### 用两轮交互分别检查事实与回答质量

下面的 `.eval.ts` 要求 Agent 查询台北天气，再根据查询结果给出出门建议。实验使用的 Agent 必须提供 `get_weather` 工具，输入包含 `city` 与 `unit`，并配置 Judge。工具名称与输入属于被测 Agent 的协议，`calledTool`、`toolMatch` 和 `closeQA` 是框架入口。

```ts theme={null}
import { defineScoreEval } from "niceeval";
import { atMost, jsonMatch, toolMatch } from "niceeval/expect";

export default defineScoreEval({
  description: "查天气后给出有依据的出门建议",
  async test(t) {
    const weather = await t.send("查询台北当前天气，温度使用摄氏度。");
    await weather.succeeded().gate().orStop();

    // 本轮必须完成正确查询，不能借用其它轮次的工具调用。
    await weather.calledTool(toolMatch("get_weather", {
      input: jsonMatch({ city: "Taipei", unit: "celsius" }),
      status: "completed",
    }).exactly(1)).gate().score(20).label("正确查询").orStop();

    const advice = await t.send("根据刚才查到的天气，说明是否需要带伞及理由。");
    await advice.succeeded().gate().orStop();

    // 验收需要查询结果和后续建议，交给 Judge 的是完整交互历史。
    t.closeQA("最后的带伞建议是否符合工具返回的天气，且理由没有编造天气事实？", {
      maxMaterialBytes: 256 * 1024,
      maxAuditBytes: 1024 * 1024,
    }).score(80).label("建议有依据");

    t.check(t.usage.totalTokens, atMost(10_000)).gate().label("用量上限");
    t.check(t.elapsedMs, atMost(30_000)).gate().label("运行耗时上限");
  },
});
```

正确查询贡献 20 分，建议满足验收问题贡献 80 分；用量与运行耗时单独设门槛。例如工具返回下雨而 Agent 建议带伞，还需 Judge 判断理由是否忠实于实际天气事实。出现“雨”或“伞”字样本身不能证明回答正确。

查询成功是第二轮的前提，所以同时使用 `.gate()` 与 `.orStop()`。若任务允许查询失败后说明限制，就应为该业务分支另写判据，不能沿用这里“查询成功才继续”的前提。

### 先选作用域，再选材料

| 检查目标 | 选择方式 | 容易误判的写法 |
| - | - | - |
| 本轮是否完成正确工具调用 | 在该 `turn` 上调用 `calledTool`，同时声明输入、状态与数量 | 在整个 Attempt 找到一次同名调用就判本轮通过 |
| 本轮回答本身是否满足要求 | `turn.closeQA(问题)` 使用该轮完整历史 | 只按关键词判断解释或合理拒绝 |
| 后续回答是否遵守前文和工具结果 | 在涵盖这些轮次的 Session 或 root 上调用 `closeQA(问题)` | 只传最后一句回复，让 Judge 猜前文 |
| 某类工具调用的整体表现 | `closeQA(toolMatch(...), 问题)` 选择完整命中工具材料 | 只取第一条或成功调用代表所有调用 |

材料范围必须足够回答问题。显式 ToolMatch 只选择工具事实；若问题需要用户要求和回复，应选包含它们的完整历史。质量检查不先排除失败调用、拒绝或澄清；Judge 材料或审计超限时保留不可判定，不截断后继续评分。

多轮任务先明确每项要求属于哪个 Turn 或 Session。需要检查工具顺序时，在受管工具集合上使用 `inOrder`，具体入口见 [Match 参考](/docs/zh/reference/expect)；先后发生仍不等于后者使用了前者的结果，数据依赖还需输入与输出证据。

### 把确定性条件留给 Match

工具名称、参数、状态和次数用确定性检查，语义忠实度与建议质量才交给 Judge。`judge("是否调用了天气工具？")` 让模型重复判断已有事实；`reply.includes("抱歉")` 又无法判断拒绝是否合理。两种写法都没有选对证据与判断方式。

`t.usage` 与 `t.elapsedMs` 直接提供框架用量和运行墙钟。不要在评估正文重新累计工具日志、维护模型价格表或把缺失 token 补成 0。观察不到的调用仍需 Adapter 正式上报，用量字段存在不代表所有外部调用都已被观测。

## 自定义应用类评估

被测对象提供客服记录、游戏状态或其它业务事实时，先由 Adapter 提供当前 Attempt 的正式上下文。再用领域原子组合条件，用普通 TypeScript 计算已有数值；正文继续使用同一套 `check`、`closeQA`、gate 与 score。

### 先写场景、判据和充分证据

把场景写成一句业务任务，再列出可独立失败的要求。每项注明观察对象、量词、时间范围和证据来源，最后分配权重。短注释解释为什么检查这一项，不复述调用名称。

正文依次呈现“做了什么、检查什么、阈值与权重是什么”。按原因或约束写简短 `//` 注释，用空行分开观察前提、业务门槛和计分。少量可读的组合优先于节省遍历次数；不要为性能把判据压进难以解释的通用包装。

例如，“确认收件人收到邮件”需要发送回执与收件记录的关联。生成了邮件正文只能证明有输出；发送请求被接受只能证明进入处理流程。两者都不能独自证明送达。

| 责任 | 应当承担的工作 |
| - | - |
| 评估正文 | 声明场景、观察步骤、检查点、独立业务判据与权重；说明后续步骤依赖的前提 |
| Adapter | 适配应用的正式事实，提供当前 Attempt 的强类型上下文，管理连接与清理、记录关联、用量归一化与上报 |
| 领域 Match | 提供身份、状态、操作类型等可复用的小判据，说明充分证据并保留诊断；整条验收条件由正文组合 |
| NiceEval | 提供通用检查、组合求值、gate、score、停止控制、受管 Judge、统计、取消和证据审计 |

Adapter 读取应用已有的协议、执行与状态事实，不复制一套权威。供检查消费的事实应只读并按 Attempt 隔离。日志格式转换、记录关联与计费不要挤进评估正文。

领域抽象应让读者更容易解释判据。把三十行过滤移到另一个文件，再命名为 `journalMatches`，并没有解决问题。反过来，`runEverything()` 隐藏所有检查点与权重，也让读者无法审阅评估。NPC、游戏规则和 journal 属于应用；普通 Agent 的消息和工具调用使用相同的通用 Judge 能力。

#### reader 取事实，判断 Match 比较事实

上下文 Match 中的 `read(ctx)` 读取当前 Attempt 有权访问的事实或完整材料，内层 `match` 决定怎样判断。框架在断言接收者处理 `check` 或裁判调用时注入 ctx，作者不把 ctx 或整个 journal 作为普通判分参数传入。签名见 [Match 参考](/docs/zh/reference/expect)。

应用词汇应贴近事实：人物、发言、抽象操作、原子操作、System One/Two、请求与返回选择。Adapter 负责把这些事实正式关联，声明完整性与测量窗口。不要让日志文件名或存储格式成为作者的主要语言。

应用可通过 Adapter 的 `assertions` 提供 `t.systemOne(match)`、`t.said(match)` 或 `t.hp(selector, collectionMatch)`，读取事实后转交同一个 `check`。这些都是应用自定义方法，不是 NiceEval 内置；调用即检查，不要求作者先拿到“范围”再判断。无需另建 `defineMaterialSource`、`bindMaterialSource` 注册流程，也不增加 `t.observation` 或 `usageSnapshot`。

### 正文组合条件，函数名不代替判据

`胜者有自主战斗行动()` 也不是合格的简化。读者仍需打开函数实现，才能知道它检查了身份、模型成功、决策采用，还是只找到一条包含战斗文字的日志。把整句业务结论写成函数名，不会让判据变得可见。

领域原子可以有领域名，例如人物、生命值、决策和操作。每个原子只负责一个可解释的条件；相等、计数、组合与失败诊断由通用能力承担。当前场景选择谁、要求几项、哪些状态必须同时满足，都写在评估正文。

物体遵守相同规则。“货箱完好送达”应展示同一物体的身份、耐久条件和目标位置，不能把整句结论藏进函数。比如 `id === crateId`、`durability >= minimum`、`positionId === destinationId` 是三个可见条件；若还要求曾被搬运，需要独立的操作事实，当前位置不能代替过程证据。

下面是应用接入后的正文片段。导入的组合器来自 `niceeval/expect`；`t.hp`、`t.systemOne`、`t.objects` 与人物、模型、决策、操作、物体、耐久和位置 Match 均由应用定义，必须先按该应用的 Adapter 契约接入。

```ts theme={null}
import { and, countWhere, equals, greaterThan } from "niceeval/expect";

// 指定人物中，生命值恰为 0 的有 3 个。
t.hp(npcMatch({ ids }), countWhere(equals(0), equals(3))).gate();

// 同一行动的身份、模型结果、决策采用与操作类型必须同时满足。
t.systemOne(and(
  npcMatch({ id }),
  modelMatch({ outcome: "succeeded" }),
  decisionMatch({ outcome: "adopted" }),
  operationMatch({ ids: operationIds }),
)).gate();

// 选择指定货箱，要求它仍有耐久且位于目标位置。
t.objects(
  objectMatch({ id: 剧本.carry.objectId }),
  and(
    durabilityMatch(greaterThan(0)),
    positionMatch({ map: 剧本.map.name, ...剧本.destination }),
  ),
).score(50);
```

这里的 `ids` 是本场景检查的人物集合，`id` 是被检查的人物，`operationIds` 是允许的操作类型。它们应在场景声明处可见。生命值与数量条件不隐藏在“全部败退”中，模型成功与决策采用也不隐藏在“自主行动”中。

物体示例中的 `剧本` 提供本场景的物体 ID、地图与目标位置。`t.objects` 的第一个参数选择物体，第二个参数判断同一物体的耐久和位置，50 分权重直接可见。该应用方法在 Adapter 内使用官方 `filterWhere`、`countWhere` 组合并交给 `check`，不调用私有求值函数；它不是 NiceEval 内置的物体 API。

Adapter 从 ctx 提供可信且已关联的事实，让组合条件判断同一个候选。不要为了表达这些条件，在正文创建临时数据结构或展开 journal 过滤；也不要让 Adapter 直接返回某个 case 的综合通过结论。已采用的决策仍不等于操作执行成功，任务需要执行结果时另写对应检查。

模型请求成功、决策被采用、操作执行成功和目标达成是四个事实。不能用甲的成功请求、乙的采用决定与丙的操作拼出一次通过；也不能将一次模型成功解释为整场任务成功。

#### 集合组合保留数量与完整性

| 框架组合器 | 作用 |
| - | - |
| `mapValue(label, project, match)` | 从单个事实投影一个值，再交给内层判断；适合定义人物身份、模型结果等原子 |
| `filterWhere(selector, aggregate)` | 对完整集合选择确定命中项，保序交给集合判断 |
| `mapEach(project, aggregate)` | 从每项投影同一种值，保留材料身份与顺序，再交给集合判断 |
| `countWhere(item, count)` | 判断每项，将精确命中数量交给数量 Match |

人数齐全应另验。三个生命值为 0，不能证明所有指定人物都已出现；完整空集合的计数为 0，也不能证明“全员满足”。Adapter 必须保留缺人物或缺测量的未知状态，不能删除未知项后宣称集合完整。

这些集合组合器对 `partial` 或 `unavailable` 返回不可判定，不把已知小计当精确数量。存在性检查中一个确定见证足够成立的规则，不能推广到精确计数。

### 明确量词与完整性前提

“有一次”“每一次”和“一次也没有”需要不同的证据。先限定同一 Attempt、对象和观察区间，再决定缺失记录是否影响结论。

| 业务要求 | 足以支持结论的证据 | 完整空集合 |
| - | - | - |
| 存在一次符合条件的行动 | 一个真实、可追溯且满足全部条件的见证 | 不成立 |
| 每次行动都符合条件 | 覆盖全部候选，并逐项判定；若业务要求实际参与，另加存在性检查 | 是否接受取决于业务是否要求实际参与 |
| 没有违规行动 | 完整覆盖范围内没有违规；一个反例足以否定 | 可以成立 |
| 对整组材料提出正向验收问题 | 完整、有序、非空的相关材料足以回答问题 | `closeQA` 得 0，且不调用 Judge |

`closeQA` 的空集规则只属于该材料验收入口，不是所有检查的空集规则。例如 `notCalledTool` 可以在完整空集合上成立。`partial` 或 `unavailable` 不能证明没有发生；未知集合也不能替代完整空集合。

存在性在已找到确定见证时可以成立，但这个见证不能证明“全部合格”。同样，已知违规能否定“没有违规”，即使其余材料尚不完整。不要把所有证据不足都压成业务失败。

组合条件必须落在同一候选上。`存在甲的行动，并且存在一次送达乙的发言`，不能证明 `存在甲向乙送达的发言`。前者可能分别由甲移动和丙说话满足。

### 用上下文 Match 编写客服任务评估

下面三段按顺序组成一个 `support-review.ts` 模块。它导出项目侧工厂 `createSupportReview`，接收应用已有的观测函数，返回同一个 Adapter 与评估定义。这个工厂不是 SDK 的新入口；它让接入代码与评估正文分别可读。

调用方把返回的 `evaluation` 默认导出为 `.eval.ts`，并在 Experiment 中使用返回的同一个 `adapter`。运行前配置 Judge，见[验证 Judge](/docs/zh/tutorials/verify-judge)。

#### 应用交付事实，保留未知状态

`observe(ctx)` 负责执行或等待本次客服任务，返回当前 Attempt 的只读事实。它使用 `ctx.signal` 响应取消，按连接需要登记释放回调，并通过 `ctx.recordUsage` 上报正式用量。记录关联、协议解码与时间校验在应用适配中完成，不放进评估正文。

`businessSeconds` 必须是已核验的非负有限业务时间。已完成需要完成事件；确定未完成需要完整观察结束证据。两者都无法确认时，返回 `unavailable`。发言集合保留每项证据 ID、原文、送达与模型来源信息；未知状态不能伪装成完整记录。

```ts theme={null}
import { defineAdapter, type AdapterCreateContext } from "niceeval";
import {
  atLeast, atMost, countWhere, defineContextMatch, defineMaterialMatch,
  equals, filterWhere, mapEach, mapValue,
  type BooleanMatch, type CollectionValue, type MaterialCollection, type MatchFact,
  type NumericMaterial,
} from "niceeval/expect";

export type Speech = {
  readonly actorId: string;
  readonly text: string;
  readonly delivered: boolean;
  readonly modelOrigin: "verified" | "unknown";
};
export type Completion =
  | { readonly completed: true; readonly eventId: string; readonly businessSeconds: number }
  | { readonly completed: false; readonly observationEndId: string };
export type SupportFacts = {
  readonly speeches: MaterialCollection<Speech>;
  readonly completion: MatchFact<Completion>;
};
```

#### 领域 Match 只选择材料与组合条件

人物发言只按人物选择，送达失败或模型来源未知的发言仍进入表达质量判断。`read` 从框架注入的 ctx 读取材料，不需要 source 或 bind 注册。读取集合的预算包含未命中项。

完成状态和业务耗时已经是应用事实，直接读取并计算即可。不要为了取出一个字段或包装算术，另造“完成耗时效率 Match”。公式、时间尺度和权重写在评估正文。

```ts theme={null}
export const 人物 = (actorId: string) =>
  mapValue("人物", (speech: Speech) => speech.actorId, equals(actorId));

export const 发言材料 = (match: BooleanMatch<Speech, Speech>) =>
  defineMaterialMatch<SupportFacts, Speech>({
    name: "actor-speeches",
    read: ctx => ctx.speeches,
    match,
    capture: { maxItems: 1024, maxBytes: 512 * 1024,
      maxNodes: 65536, maxDepth: 32 },
  });
```

#### 正文保留独立判据与权重

参与计 5 分，表达质量占 45 分，完成效率占 50 分。本场景还要求至少一条客服发言送达；token 与运行耗时只设通过门槛。`closeQA` 的显式预算用于全部命中材料和裁判审计，不替代 Match 的读取预算。这里的预算是例子选择，不代表任何长度的任务都能装下。

`said` 与 `speechDelivery` 是本例 Adapter 自定义的直接检查方法。前者检查存在性，后者把同一份正式集合交给框架筛选、投影与计数；它们都不预先决定场景的通过条件。`speechDelivery` 把原集合交给组合器，保留其中的 partial 或 unavailable。

```ts theme={null}
export function createSupportReview(
  observe: (ctx: AdapterCreateContext) => Promise<SupportFacts>,
) {
  const adapter = defineAdapter({
    name: "support-review",
    create: observe,
    assertions({ check }) {
      return {
        said(match: BooleanMatch<Speech, Speech>) {
          return check(发言材料(match));
        },
        speechDelivery(
          selector: BooleanMatch<Speech, Speech>,
          aggregate: BooleanMatch<CollectionValue<boolean>, CollectionValue<boolean>>,
        ) {
          return check(defineContextMatch<SupportFacts, MaterialCollection<Speech>>({
            name: "speech-delivery",
            read: ctx => ({ state: "available", value: ctx.speeches }),
            match: filterWhere(selector, mapEach(
              (speech: Speech) => speech.delivered, aggregate,
            )),
          }));
        },
      };
    },
  });
  const evaluation = adapter.defineScoreEval({
    description: "客服正式发言质量与任务完成效率",
    test(t) {
      // 参与是加分；至少一次送达是这道题独立的业务门槛。
      t.said(人物("agent")).score(5).label("参与交流");
      t.speechDelivery(人物("agent"), countWhere(equals(true), atLeast(1)))
        .gate().label("至少一次送达");

      // 全部发言参与质量判断；不按送达或模型来源筛选。
      t.closeQA(发言材料(人物("agent")), "每句发言是否自然完整、意思清楚？", {
        maxMaterialBytes: 256 * 1024,
        maxAuditBytes: 1024 * 1024,
      }).score(45).label("表达质量");

      // 普通公式就在正文；未知仍占固定权重，不能当作未完成的 0。
      const completion = t.completion;
      const ratio: number | NumericMaterial = completion.state === "unavailable"
        ? { state: "unavailable", reason: completion.reason }
        : completion.value.completed
          ? Math.max(0, 1 - completion.value.businessSeconds / 180)
          : 0;
      t.score(ratio, { weight: 50 }).label("完成耗时");

      const usage = t.usage;
      t.check(usage.totalTokens, atMost(10_000)).gate().label("用量上限");
      t.check(t.elapsedMs, atMost(30_000)).gate().label("运行耗时上限");
    },
  });
  return { adapter, evaluation };
}
```

`usage` 与 `elapsedMs` 是属性，不调用快照方法。读取的用量保留当时已知的值与未知状态；未上报 token 不能使预算检查通过。`elapsedMs` 是本次评估运行墙钟，完成效率使用应用的业务时间，两者不互换。

若任务在第 90 个业务秒完成，效率贡献 25 分；表达质量需由 Judge 另行判定。Judge 不可用或材料超限时，不能声称取得完整总分。送达检查不证明模型自主性；业务要求模型自主表达时，另登记确定性检查，不能靠表达得分推断。

### 案例一：问路后实际会面

任务是让甲询问乙如何到达会面地点，并由甲自主行动完成会面。评估对象是甲的选择、执行和结果，不是玩家是否帮它完成任务。

表达质量选择人物在观察区间内的全部正式发言，包括无人听见、内容错误和模型来源尚未核验的发言。保留提问、回答、拒绝和澄清，不先筛出送达或来源已核验的发言再问质量。

表达质量、信息有效性、实际送达和模型来源分别判断。评价表达时提供必要的双方对话上下文；评价“对方实际获得的信息是否有效”时，使用完整双方送达对话与正式地图。材料范围随问题确定，送达条件不能变成表达质量的筛选条件。

下面是一条完整的小评估设计，分值仅为此任务的业务选择。它是伪代码，不是可直接运行的 TypeScript；应用动作与材料 API 应按安装版本的参考页实现。

```text theme={null}
场景：甲不知道路线；乙知道路线；两者处于可交谈范围。
目标：甲问路后自主前往指定地点，与乙会面。

观察：运行应用直到任务结束或业务观察期限到达。

前提：确认当前 Attempt 的对话、行动和结果记录完整可关联。
      无法确认则报告证据不可用，停止依赖它们的检查。

检查点一：存在甲向乙送达的问路发言。
          通过门槛；同一发言同时满足人物、接收者与送达事实。

检查点二：分别选择与两个验收问题对应的完整材料。
          表达质量：甲全部正式发言是否自然、完整，可结合上下文理解？权重 2。
          不排除无人听见、内容错误或模型来源尚未核验的发言。
          信息有效：实际送达对话是否给出足以找到目标地点的信息？权重 3。
          同时提供该场景的正式地图与目标信息作为判断依据。
          模型来源另行检查；来源不明不能当作模型已自主表达。

检查点三：甲的模型选择了移动；应用实际执行；最终到达并会面。
          三项分别记录；最终会面必须关联到甲的自主行动。
          自主会面设为通过门槛，并计 5 分；玩家代走不得满足。

结果：逐项显示判断与理由。完整评分范围为 0 至 10 分。
      未通过门槛仍保留已取得分数；证据不可用不伪造完整零分。
```

“先收到路线，后发生移动”只证明时间顺序。若要求“根据乙的指路行动”，还需要应用提供选择与输入的关联证据。无法取得时，应收窄判据并说明限制，不能用时间接近代替因果证明。

同一份材料可以支撑不同问题，但不能强求不同问题共用同一个筛选范围。问题也不能暗示答案。例如“甲是否表现优秀并成功理解乙”没有明确验收标准。拆开表达与信息两个问题后，读者能分别解释为什么得分。

### 案例二：协商搬运与实际交货

任务是让甲与乙协作搬运物品。先明确双方是否已知目标、是否必须协商，以及谁有权拒绝。判据来自这次业务条件，不能沿用问路任务的全部要求。

| 场景与事实 | 交流判断 | 完成判断 |
| - | - | - |
| 目标尚未对齐；乙解释无法搬运并提出合理替代 | 可以是有效交流，材料保留拒绝与后续回应 | 没有实际交货，不能记为完成 |
| 双方已知目标，按既有约定默契搬运 | 业务未要求发言，不登记依赖发言的质量项 | 用搬运与交货记录检查结果 |
| 操作已被接受，但物品仍在原地 | 接受回执不能证明沟通质量 | 尚未交货 |
| 玩家完成交货，甲没有自主搬运 | 不据此推断甲理解任务 | 不满足甲自主完成的要求 |

若评估交流质量，保留双方完整对话并提出可据材料回答的问题。若评估交货，检查物品、目标位置、执行者和完成回执的关联。交流得分与交货得分独立，权重在正文可见。

同样的方法适用于工具调用、操作请求和返回结果：操作记录保留失败与调整，requests 保留实际发出的正文，choices 保留未采用的模型输出。选择、执行、结果分开观察，不能拿其中一层代替另一层。

`closeQA(材料Match, 问题)` 可以评价一个人物的全部发言、操作、匹配请求或返回选择。框架保序把完整命中材料交给同一个受管 Judge，不在正文逐条调用模型。只传第一条命中、摘要或成功样本，都不能代表全量表现。

只用于当前评估的问题直接写在裁判调用处，让读者同时看到材料和验收标准。多处共用的纯问题文本可以提取复用；问题文本不是判断函数。不要把材料选择、验收问题与权重一起藏进某个场景专用函数。

保存下来的末态位置只证明结束时在哪里。即使应用把末态投影到每个操作上，它也不是每次操作当时的位置；要评价路线或逐次轨迹，必须读取对应时点的正式事实。

### 改写容易误导的表达

以下是业务伪代码片段；变量表示应用事实，不是 NiceEval 提供的字段。

| 原写法 | 为什么误导 | 改写方向 |
| - | - | - |
| `journal.filter(isSuccess).slice(0, 12)` 交给 Judge | 成功筛选与抽样掩盖拒绝、错误和调整，不能代表整个表现 | 按业务对象和观察区间选全量材料，保序保留原文与证据 ID |
| `speeches.filter(deliveredAndModelVerified)` 评价表达 | 无人听见或来源待核验的差表现被排除 | 表达质量选择人物全部正式发言；送达和模型来源另行检查 |
| `journal.map(...).reduce(...)` 后命名为 `cooperation` | 统计没有说明什么算协作 | 先声明协作判据，再由领域 Match 检查相关主体、目标与行动的关联 |
| `t.observation(...)`、`t.efficiency(...)`、`runEverything()` | 名称看不出证据和判据，或把全部评分藏起来 | 正文分别展示观察前提、业务检查、Judge 问题与分值 |
| `胜者有自主战斗行动()` | 函数名遮住判断过程，正文看不到采用了哪些条件 | 正文组合人物身份、模型成功、决策采用与允许的操作类型 |
| `三名敌人全部败退()` | “败退”可能混合生命值、离场与任务结果，数量也藏在实现里 | 明确人物范围，分别展示生命值条件与所需数量 |
| `货箱完好送达()` | 身份、耐久和位置条件被函数名遮住 | 在同一物体上组合身份、耐久阈值与目标位置 |
| `首次决胜游戏秒数()`、`决胜前调用效率()`、`反比得分Match()` | 为取数或几行公式增加概念，读者还得查定义 | 从正式 ctx 读取同一窗口的耗时与实际调用数，在正文计算 0–1 并加权 |
| `has(actorA) && has(deliveredToB)` | 两条不同记录也能满足 | 在同一候选上组合人物、动作和送达条件 |
| `output.includes("移动")` | 文本计划不等于实际移动 | 分别检查模型选择、执行回执与世界位置变化 |
| `accepted === true` 就计任务完成 | 请求受理不等于最终结果 | 检查对应请求的完成回执和目标状态 |
| `tokens ?? 0`、`cost ?? 0` | 缺数据变成免费或低用量 | 保留未知或已知下限，依据覆盖状态判断预算 |
| `Date.now() - started` 当作业务完成时间 | 运行墙钟包含等待和评估开销 | 业务时限读取应用时间；运行耗时单独观察 |
| `judge("是否调用了工具？")` | 让模型猜可直接读取的事实 | 从正式工具调用记录确定性检查 |
| `reply.includes("抱歉")` 判断合理拒绝 | 关键词不能解释对话意义 | 给 Judge 完整上下文，询问拒绝理由是否符合业务限制 |

调用次数、token 与价格是不同指标。Adapter 上报正式用量，框架聚合；评估正文不重复维护模型计价表。未知费用不补零，已知下限也不能冒充完整总量。

上报发生在正式物理调用边界，保留失败和重试。`t.usage`、`t.elapsedMs` 用于框架的通用用量与墙钟测量；业务上的“完成前调用数”“到完成用了多少游戏时间”由应用 reader 明确观察窗口。不要把一次读取的累计用量改名为“完成前用量”，也不要用游戏时间替代运行耗时。

### 保住证据与失败解释

材料按原顺序保存，并能追溯到同一 Attempt 的正式记录。完整是相对于声明的业务范围而言，不意味着把所有无关日志都发给 Judge。选择范围按对象、任务与观察区间确定，不能按表现好坏确定。

需要整段对话判断时，不截断原文、不移除拒绝或澄清。超出容量就报告不可判定，不能为取得分数而缩短观察区间、抽样或只保留成功项。若业务问题确实改变，应明确形成另一项评估，不能把它的结果当作原任务的全量结论。

#### 分开考虑读取集合与裁判材料的预算

读取预算限制扫描和捕获的整个候选集合，包含未命中的记录。Judge 材料预算限制验收问题、全部命中材料及其身份；审计还要容纳实际请求、响应与引用信息。候选少、命中少和字节少是不同条件。

例如，读取一千条操作后只有二十条属于甲，不能只按二十条规划读取预算。若甲的二十条操作包含长正文，条数足够也不代表裁判材料或审计预算足够。预算不足时保留 unknown，不把前十二条当作全部。

存在性需要一个已确认的见证；全材料 QA 则必须确认相关集合完整，并处理全部命中项。发现一个合格行动不能证明其余行动优秀，也不能让全材料 QA 忽略尚未读取的记录。预算停止后的存在性结论同样必须依据正式证据收据，不能自行从被截断的数组推定成功。

#### 普通计算保留业务边界

完成耗时是一个业务事实，不要求评估正文传入整个 journal。应用提供可追溯的完成状态与业务时间；正文直接计算分数，不为一次取数或算术定义新的业务函数、Match 或计分语言。

例如，180 个业务秒内越早完成得分越高，可以定义为 `max(0, 1 - 完成秒数 / 180)`，权重为 50。完整观察已证明未完成时得 0；完成边界或观察结束证据缺失时不可判定。不要把缺少完成事件直接解释为耗时 0，也不要把运行墙钟代入业务时间。

已知完成于第 90 秒只证明这项规则得到 25 分。它不证明送达、模型自主选择或表达质量通过；这些判据仍在正文分别检查。单个样例或类型检查通过，也不等于整个业务评估已经验证。

例如战斗场景可以明确等待“选定人物的存活数为 1，或观察期限到达”。到期不是决胜，最终观察必须区分唯一幸存、仍有多人和无人存活。停止后读取同一正式测量窗口的游戏耗时和实际调用数，再按业务规则作普通计算；不从日志重新追算一个“首次决胜”概念。

这段只规定观察意图，不声明实时状态或截止 API。具体应用必须先提供正式只读状态与停止协议，才能实现实时条件；不要私调 evaluator、读取还没保存完的内部存储，或假设取消就等于全部在途操作已结束。状态入口与 cleanup 的签名分别以应用契约和 [Adapter 参考](/docs/zh/reference/define-agent)为准。

Judge 问题必须能根据给定材料回答。外部地图、规则或参考答案确为验收依据时，明确提供并标注身份，不把期望结论写成问题的前提。材料里的指令作为被评估内容处理，不授权裁判执行操作。

复用领域 Match 时，同时审阅其名称、充分证据与失败解释。保留业务检查点与独立权重，避免为相同能力增加多套别名或注册步骤。接口具体形状以安装版本参考为准，不把设计草案当作可运行 API。

## 交付前的共同检查

两类评估都需要检查计分用途、证据完整性与真实运行结果。下面的规则适用于 Agent 和自定义应用。

### 分别声明停止、通过门槛与计分

`check` 登记判断，handle 再配置该判断的用途。在 Score Eval 中，普通断言默认只保存结果；`.score(weight)` 才贡献分数，`.gate()` 才建立 Boolean 通过门槛。Pass Eval 的 Boolean 默认影响通过判定，不要把两种题型的默认规则混用。

普通计算得到有限的 0–1 数值后，使用 `t.score(ratio, { weight: 50 })`，贡献为 ratio × 50。单参数 `t.score(points)` 表示直接贡献分数，含义不同。公式不用先包装成 `ScoreMatch`；加权入口也接受带完整性状态的 `NumericMaterial`，未知时保留固定权重而不补零。

后续步骤依赖完整性或配置前提时，使用 `await handle.orStop()`；它负责停止后续执行，不自动替代业务 gate。measurement 用 `.orStop(minimum)`，或在 `.gate(minimum)` 后无参复用条件。证据不可用应保持 unavailable，不先改写成 `false` 或 `0` 再停止。

参与加分和所有人必须行动不是同一要求。不要把每个人的 `.score(5)` 改成 `.gate()` 来缩短代码；这会改变业务标准。低分与正式未完成的零分可以是有效结果，不能为了绿色报告降低阈值或删除失败项。具体题型规则见[通过制与计分制](/docs/zh/tutorials/evaluation-kinds)。

### 从公开入口验证真实证据链

先确认安装版本的导出与类型，再用 `pnpm exec niceeval exp <experiment>` 真实运行评估，让 `check` 拿到真实 Adapter 提供的 ctx。不要私调 Match 的 evaluator，也不要伪造核心断言上下文。构造和类型检查能发现写法错误，但不能证明材料读取、取消、审计或业务判断正确。

针对集合检查完整非空、完整空集、缺项和 partial 分支；组合检查不同人物的条件不能混合成立。需要判断胜负时，唯一幸存、尚未决胜和同归于尽分别列为业务情形，不能把所有非胜利分支都当成技术错误。

有外部 HTTP 或持续运行应用时，验证真实取消、等待在途操作结束、保存证据的顺序。检查取消后的迟到事件是否被正确处理，保存后是否仍有写入，以及不完整观察是否被如实标记。局部 Fixture 通过不能证明真实应用的整个生命周期通过。

跑完后用 `pnpm exec niceeval show @<attempt-locator>` 打开同一个 Attempt，区分三类问题：调用或解析异常属于技术错误；缺失、partial 和 unavailable 表示证据不足；证据完整但门槛不满足属于业务失败。低分本身不是框架错误，零分也不自动等于证据缺失。修正对应层的问题，不通过削弱断言追求绿色。

交付时分别报告 `--dry` 计划、类型与构造检查、局部运行和真实端到端运行各覆盖了什么。文档构建通过不等于 Judge 验收；少量样本通过不等于长材料或整组任务通过。运行与读回使用[反馈闭环](/docs/zh/tutorials/agent-feedback-loop)中的公开命令。

### 写作自检

提交评估前，用这些问题检查正文，并用同一 Attempt 的结果核对答案：

* 读者能否说出目标、观察范围、每项通过条件与分值？
* 每个要求是“存在”“全部”还是“没有”？完整性前提在哪里检查？
* 组合条件是否判断同一事实？不同 Attempt 的材料是否可能混入？
* 模型选择、实际执行和最终结果是否分开？成功是否属于被测主体？
* Judge 是否得到完整相关材料，且问题能根据这些材料回答？
* 表达质量是否保留人物全部正式发言，把送达与模型来源留给独立检查？
* 读取集合、全部命中材料和审计是否分别满足预算？超限是否仍被如实报告？
* 缺记录、缺 token、未知费用和超容量是否保留为不可判定，而非零值？
* 不打开函数实现，能否读出人物、状态、数量、阈值与组合关系？
* Match 是否只选择材料和组合小条件？已有字段或几行算术是否被多余的业务命名包住？
* 合理拒绝、无需发言的协作、失败后的调整是否按本场景规则处理？
* 得分与通过门槛是否独立可见？前提失败是否停止依赖它的后续检查？
* 保存下来的末态是否被误当成逐次轨迹？业务测量窗口是否与通用用量混淆？
* 验证是否经过真实 ctx 与公开结果，且明确区分局部检查和真实运行？

运行与读回步骤见 [Coding Agent 反馈闭环](/docs/zh/tutorials/agent-feedback-loop)。计分与停止控制的具体用法见[通过制与计分制](/docs/zh/tutorials/evaluation-kinds)，Judge 的配置与验证见[验证 Judge](/docs/zh/tutorials/verify-judge)。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.