niceeval 依赖已经装进项目、niceeval init 已经运行,你是从随包 INDEX.md 进入本页的。用用户的语言与用户交流。所有 API、字段和 CLI 行为以随包文档为准,不要凭训练记忆现编。
编写评估前,按 NiceEval Taste 确定业务判据、充分证据与权重。交付时让读者只读评估正文就能解释通过条件与计分。
第 1 步:探索项目,再和用户确认
这一步决定后面整条路径。先自己读代码探明,把探到的结论列给用户核对,探不到的再提问——不要一上来就抛一串问题,也不要没探就假设。要探明的信息:-
这是个什么 Agent:读 README、
package.json依赖、路由和 agent loop 代码,判断它是用什么写的(AI SDK、LangGraph、OpenAI Agents SDK、Claude Agent SDK、自研 loop……),核心用例是什么(客服?SQL?编码任务?)。 -
前端和 Agent 怎么通信:HTTP 还是 gRPC / WebSocket?协议是标准的还是自己实现的——AI SDK UI Message Stream、OpenAI Responses / Chat Completions 这类标准协议,还是 SDK 原生事件流透传,还是用户自定义的 JSON/SSE 帧?这直接决定 Adapter 是用内置的(零映射)还是手写
send(要自己写事件映射)。 -
真实目标怎样运行:确认构建或安装命令、启动命令、就绪检查、端口、依赖服务、凭证来源和停止方式。Adapter 只连接目标,不在
send或评估用例里临时启动一套替代实现。凭证只提供给需要它的运行边界,不写入评估源码、工作区或运行交接。 - 后端有没有接 OTel:搜有没有 OTel SDK 初始化、AI SDK telemetry、LangSmith / OpenLLMetry / OpenInference 这类埋点。已经有的话 Tier 2 几乎零成本。
-
用户自己有没有做 A/B Test / feature flag:应用里已有变体开关的话,Experiment 的
flags可以直接透传给它(Tier 3 的现成入口)。 -
Judge 用什么:语义评估(
t.judge(material, definition))要一个与被测 Agent 分离的 Judge 模型,走 OpenAI 兼容协议。问用户手上有哪个服务的 Key、想用什么 Judge 模型。缺 Key 不会静默通过:Judge 断言会记为unavailable。没有 Judge 配置时,先只写精确 Match。 -
是不是 Agent 本体要进 Sandbox:被测对象是 coding agent CLI、或给 coding agent 写的 Skill/Plugin/Hook/MCP server(要在隔离 workspace 里改文件/跑命令)的话,必须走 Sandbox,不能像 HTTP 服务那样直接
send。 默认建议dockerSandbox({ source: { type: "image", image: "node:24-slim" } })。任务或现有配置已经指定 Provider 时沿用;只有 Provider、Docker 可用性或远程执行需求仍会影响方案时才问。Provider 声明在评估用例或 Experiment 上,没有 CLI flag、项目级默认值或自动探测——见选择 Sandbox Provider。
- Tier 1(只接 send):应用一行不改,全套断言(文本、Judge、多轮、工具、HITL)都在这一档。
- Tier 2(send + OTel):应用把 OTel span 也发 NiceEval 一份,在
niceeval view中查看调用瀑布图。已有埋点(第 3 点探到的)就零改动。 - Tier 3(侵入改造 + flags):把应用内部变体暴露成
flags做 feature A/B。已有 A/B 开关(第 4 点探到的)就是现成入口。
第 2 步:配置 Judge
把第 1 步问到的 Judge 服务配上。在niceeval.config.ts 里用具名 Provider 明确选择协议:
baseUrl 必须显式写。 只配 key 不配 baseUrl,NiceEval 打的是官方端点,网关凭据会被发到 OpenAI 并回一句「Incorrect API key provided」——看起来像 key 过期,实际是端点选错了。
三个要提醒用户的点:
- 配错不会假装通过。 读不到模型或 key、请求超时时,Judge 断言记为
unavailable并带上原因;网关拒绝请求、返回格式不对时记为errored。结果里不会把拿不到的分数显示成不通过或零分。 - 配完先验一次。 用
defineJudge声明 rubric 和 anchors,再调用t.judge(material, definition).gate(0.8)。跑完后用pnpm exec niceeval show --run <run-id>查看分数,步骤见配置并验证 Judge。 - 没有用户在场(自治接入)时不要直接跳过 Judge:先看环境里是否已有与所选 Provider 对应的 key。有就验证;没有时先写精确 Match,并在收尾说明 Judge 没有配置。
- Judge 模型要与被测 Agent 分离,避免同一个模型给自己的输出打分。执行配置从 Experiment 和项目配置的
judgeRuntime求值;定义与材料见 Judge。
第 3 步:写 Adapter、Experiment 和评估用例
按第 1 步选中的方向读完对应文档后,依次写:- Adapter(
agents/*.ts或用户项目里约定的目录)——用defineAgent实现send,配置走工厂参数,不写死、不读process.env。契约见 Adapter,API 签名见 Adapter 参考,事件映射见事件参考。两个容易踩的点:端点/模式要选被测系统核心能力的入口,不是最容易跑通的入口——比如被测平台既有「纯 LLM 聊天」又有「连库执行」两种模式,接前者等于评了个底层模型代理,没评到产品本身。evidenceCoverage必须按实际映射如实声明——只把最终文本映射出来就不要用completeEvidenceCoverage,声明会影响断言完整性,虚报比保守更糟。 - Experiment(
experiments/*.ts)——引用上面的 Adapter,声明model、flags、attempts等。模型对比写两个实验文件,各自写一个model。evals: (eval) => boolean决定每个 Experiment 运行哪些评估用例。文件路径决定 Experiment ID,也决定一条命令能批量运行哪些 Experiment。 - 评估用例(
evals/*.eval.ts)——先探明这个应用是干嘛的,再写一条贴着它真实功能的评估用例。读它的 README、路由、工具定义或系统提示,找出它的核心用例(客服机器人就问一条真实的客服问题、SQL agent 就给一个真实的查询任务),拿这个用例做第一条评估用例的输入和断言。两类输入都不合格:「你好」这种和应用无关的占位输入,以及「你是什么/你能做什么」这种问被测系统它自己的元问题——那不是用户拿它干活的用例。形式上仍从最小写起:一句输入,t.succeeded()+ 一个针对预期回答的内容断言——但最小形式只是调通的脚手架,不是交付标准,收尾前还要满足两条:- 断言在被测系统胡编时要会变红。不要断言输入里本来就有的词(问「X 是什么」再断言回答含「X」,被测方复读题目就能通过)。断言预期回答独有的实质内容——具体事实、结构(
hasSections())、真实链接(includesUrl()),或用t.judge(material, definition)做语义判定。 - 至少一条负例。喂一个被测系统应该答不了的输入(不存在的表、检索不到的主题),断言它明确说查不到/做不到,而不是编造一个看似合理的结果——对连着真实数据/检索源的 agent,这是最值得先测的失败形态。 写法见编写评估用例,断言与题型见题型与断言,签名见 defineEval 参考。
- 断言在被测系统胡编时要会变红。不要断言输入里本来就有的词(问「X 是什么」再断言回答含「X」,被测方复读题目就能通过)。断言预期回答独有的实质内容——具体事实、结构(
ctx),见接入自己的 Agent。
架构上有两条硬规则,写 Adapter 时不要违反:
- 不做进程内直调。就算 agent runtime 和评估用例在同一个代码库里,Adapter 也要走 HTTP(或对应传输层),不要把
fetch换成直接import被测函数——原因见接入自己的 Agent 里「为什么不直调」。 - 评估用例侧不代管被测进程。不 spawn 应用、不另开端口。应用由用户自己按平时的方式启动(
pnpm dev之类),Adapter 连不上时报「先起应用」这类明确的错误,不要自己起服务。
第 4 步:跑通并验证
先用--dry 确认会跑哪些评估用例、每个跑几次,再正式运行。下面以 pnpm 为例,用户项目用其它包管理器时换成对应的 exec:
pnpm exec niceeval view。
按 Coding Agent 反馈闭环完成运行、查看、修改和重跑。需要机器读取时,先执行 pnpm --silent exec niceeval query discover,再发送完整 request。查看方式见查看运行结果。
先按终态分流,再决定是否修改:
errored表示运行本身没跑成,没有得出可以评分的结果。先检查目标服务、端点、凭证、依赖、超时和 Adapter 的错误映射,修好后重新运行。failed表示运行跑完了,但业务断言没有满足。先查看同一个 Attempt 的断言和证据;被测系统确实答错时,不需要把它改成绿色。- 计分制评估跑完后,仍要查看得分和每条断言。总览里显示跑完或通过,不代表拿到了满分。
errored 的问题,并只复验受影响的范围。只读请求不修改或重跑。目标服务、端口、凭证、数据库或模型服务等真实外部条件缺失时,明确报告阻塞;不要用临时替代物冒充真实目标已经跑通。
需要在终端查看某个 Attempt 的细节时,先从当前总览复制它的 locator,再用同一个 locator 查看各项内容:
partial、unavailable 和 not-recorded 表示信息不全,不等于零分或失败。
第 5 步:收尾,告诉用户做了什么
总结之前先过一遍收尾自检——任何一条不满足就回第 3 步补,不要在总结里含糊带过:- 评估用例的输入是被测系统的核心用例,不是问它自己的元问题或占位寒暄
- 每条内容断言在被测系统复读题目/胡编时会变红(断言的词不是输入里本来就有的)
- 至少有一条负例(应该答不了的输入,断言它明确说做不到)
- 有 key 时 Judge 已配置,且在
niceeval show或niceeval view里看到过 Judge 分数。没 key 时总结里说明了 - Experiment 里声明的
model/flags确实被 Adapter 消费(没有写了没人读的死配置,也没有编造被测系统不存在的 model 值) - 真实目标按项目自己的方式启动并确认就绪,凭证只提供给需要它的运行边界,没有写进评估源码、工作区或交接
- 至少有一个跑完且不是
errored的 Attempt;业务失败如实保留,并用同一个 locator 核对分数、断言和证据是否完整
niceeval exp <实验路径>、niceeval show 和 niceeval view 怎么跑、第一次运行的结果是什么样。不要在没被要求的情况下顺手重构用户已有代码,也不要在这几个文件之外新增抽象。
第 6 步:问用户要不要往深了接
总结完之后,把还能往深接的选项列给用户——每一项都说清能做什么、大概改多少代码、买到什么好处,让用户自己选,不要自作主张多做:
共同点也要讲给用户:这些全是给 Adapter 或应用加增量,已写的评估用例一行不用改。三档投入分别买到什么、什么时候值得升级,见接入等级。第 1 步探到应用已有 OTel 埋点的话,瀑布图那条要主动推荐——成本接近零。