niceeval 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/docs-site/zh/concepts/adapter.mdx +11 -6
  2. package/docs-site/zh/concepts/experiment.mdx +8 -6
  3. package/docs-site/zh/concepts/overview.mdx +1 -1
  4. package/docs-site/zh/concepts/tier.mdx +6 -6
  5. package/docs-site/zh/guides/ci-integration.mdx +2 -0
  6. package/docs-site/zh/guides/connect-your-agent.mdx +7 -7
  7. package/docs-site/zh/guides/custom-reports.mdx +13 -13
  8. package/docs-site/zh/guides/experiments.mdx +4 -3
  9. package/docs-site/zh/guides/publish-report.mdx +91 -0
  10. package/docs-site/zh/guides/report-components.mdx +4 -4
  11. package/docs-site/zh/guides/reporters.mdx +1 -1
  12. package/docs-site/zh/guides/results-data.mdx +2 -2
  13. package/docs-site/zh/guides/runner.mdx +1 -1
  14. package/docs-site/zh/guides/sandbox-agent.mdx +11 -6
  15. package/docs-site/zh/guides/sandbox-backends.mdx +27 -0
  16. package/docs-site/zh/guides/viewing-results.mdx +5 -21
  17. package/docs-site/zh/guides/write-experiment.mdx +24 -10
  18. package/docs-site/zh/guides/write-send.mdx +6 -6
  19. package/docs-site/zh/index.mdx +1 -1
  20. package/docs-site/zh/introduction.mdx +1 -1
  21. package/docs-site/zh/reference/builtin-agents.mdx +4 -0
  22. package/docs-site/zh/reference/cli.mdx +9 -6
  23. package/docs-site/zh/reference/define-agent.mdx +19 -3
  24. package/docs-site/zh/reference/define-config.mdx +3 -1
  25. package/docs-site/zh/reference/define-eval.mdx +9 -3
  26. package/docs-site/zh/reference/events.mdx +4 -0
  27. package/docs-site/zh/reference/expect.mdx +4 -0
  28. package/package.json +3 -2
  29. package/src/agents/ai-sdk.test.ts +1 -1
  30. package/src/agents/ai-sdk.ts +2 -2
  31. package/src/agents/bub.ts +5 -1
  32. package/src/agents/claude-code.ts +1 -1
  33. package/src/agents/codex.ts +2 -2
  34. package/src/agents/index.ts +2 -0
  35. package/src/agents/streaming.test.ts +1 -1
  36. package/src/agents/types.ts +10 -2
  37. package/src/agents/ui-message-stream.test.ts +1 -1
  38. package/src/cli.ts +38 -14
  39. package/src/context/context.test.ts +1 -1
  40. package/src/context/context.ts +6 -3
  41. package/src/context/session.ts +5 -2
  42. package/src/context/types.ts +2 -2
  43. package/src/define.test.ts +82 -0
  44. package/src/define.ts +68 -7
  45. package/src/i18n/en.ts +4 -2
  46. package/src/i18n/zh-CN.ts +4 -2
  47. package/src/report/aggregate.ts +16 -16
  48. package/src/report/components.tsx +1 -1
  49. package/src/report/compute.ts +7 -7
  50. package/src/report/dual-face.test.tsx +1 -1
  51. package/src/report/{param.ts → flag.ts} +6 -6
  52. package/src/report/index.ts +3 -3
  53. package/src/report/react/MetricLine.tsx +1 -1
  54. package/src/report/react/fixtures.ts +1 -1
  55. package/src/report/report.test.ts +17 -17
  56. package/src/report/report.ts +1 -1
  57. package/src/report/types.ts +11 -11
  58. package/src/results/results.test.ts +2 -2
  59. package/src/runner/attempt.ts +53 -6
  60. package/src/runner/fingerprint.ts +1 -1
  61. package/src/runner/reporters/braintrust.test.ts +2 -2
  62. package/src/runner/reporters/braintrust.ts +3 -3
  63. package/src/runner/run.ts +1 -1
  64. package/src/runner/types.ts +8 -8
  65. package/src/sandbox/types.ts +45 -4
  66. package/src/show/compose.ts +1 -1
  67. package/src/view/app/App.tsx +1 -1
  68. package/src/view/app/css.d.ts +3 -0
  69. package/src/view/app/i18n.ts +3 -3
  70. package/src/view/app/lib/rows.ts +5 -5
  71. package/src/view/app/types.ts +1 -1
  72. package/src/view/client-dist/app.js +2 -2
  73. package/src/view/data.test.ts +26 -2
  74. package/src/view/data.ts +28 -4
  75. package/src/view/shared/types.ts +1 -1
@@ -60,7 +60,7 @@ niceeval show weather/brooklyn --history # 跨 run 时间轴:抖动与回
60
60
 
61
61
  同一个实验多次跑会留下多份结果,默认视图只回答一个问题——「现在什么水位」:每个 experiment × eval 取时间上最新的那份判决,跨 run 合成。按前缀只重跑了部分 eval 时,其余 eval 的判决从更早的 run 补齐,榜单永远是全局水位,不会因为一次局部重跑变残缺。合成是有标注的:每行带判决产生的时间,榜单头写明合成自几个 run。合成状态可能混着不同版本的被测代码——所以收工判决以 `--force` 全量重跑为准,迭代途中的 `show` 负责快、收尾的全量跑负责真。
62
62
 
63
- 多 experiment、多 attempt 时,证据切面默认挑最新一次失败的 attempt;用 `--experiment` 与 `--attempt` 指定其它,用 `--run <目录>` 钉死看某一次历史 run,用 `--history` 看跨 run 趋势(见下文)。默认榜单只是报告槽的出厂填充:`--report <文件>` 可以把它整槽换成你自己的口径,同一个文件也能喂给 `view`,见[自定义报告](/zh/guides/custom-reports)。位置前缀、`--run`、`--experiment` 对 `--report` 同样生效(收窄报告拿到的选集);`--history` 与 `--report` 互斥,证据切面 flag 出现时走证据室、不渲染报告槽。
63
+ 多 experiment、多 attempt 时,证据切面默认挑最新一次失败的 attempt;用 `--experiment` 与 `--attempt` 指定其它,用 `--run <目录>` 钉死看某一次历史 run,用 `--history` 看跨 run 趋势(见下文)。不带 `--report` 时 `show` 渲染的是默认报告;`--report <文件>` 换成你自己的报告,同一个文件也能喂给 `view`,见[自定义报告](/zh/guides/custom-reports)。位置前缀、`--run`、`--experiment` 对 `--report` 同样生效(收窄报告拿到的选集);`--history` 与 `--report` 互斥,证据切面 flag 出现时只看证据、不渲染报告。
64
64
 
65
65
  `niceeval view` 的每个视图都有 CLI 对应物:
66
66
 
@@ -179,7 +179,7 @@ npx niceeval view
179
179
  失败后立刻运行 `npx niceeval view`,可以直接打开刚刚那次运行的 artifacts。
180
180
  </Tip>
181
181
 
182
- view 的结构是「报告槽 + 证据室」:报告槽默认装官方榜单,`--report <文件>` 整槽换成你自己的报告(与 `show --report` 吃同一个文件),见[自定义报告](/zh/guides/custom-reports);证据室(attempt 弹窗、transcript、trace 瀑布)始终保留,报告里的数字点进去就是证据。
182
+ view 的首页是一份报告:不传 `--report` 时是默认报告,传了就换成你自己的报告(与 `show --report` 吃同一个文件),见[自定义报告](/zh/guides/custom-reports);证据部分(attempt 弹窗、transcript、trace 瀑布)始终保留,报告里的数字点进去就是证据。
183
183
 
184
184
  ## 导出与静态托管
185
185
 
@@ -195,30 +195,14 @@ NiceEval 写入 `<dir>/index.html`,并把查看器要读取的工件(`source
195
195
 
196
196
  `--out` 只接受目录。没有单文件导出:代码视图、transcript 和 trace 依赖工件文件,单个 HTML 装不下完整证据。要把结果发给别人,托管整站发链接即可。
197
197
 
198
+ 零可读结果时 `view` 直接报错、非零退出——本地服务起不来,`--out` 不导出空页面。错误逐条列出被跳过的 run 目录与原因;落盘 schemaVersion 与当前版本不兼容时,还给出能直接查看旧落盘的 `npx niceeval@<版本> view` 命令。
199
+
198
200
  两点限制:
199
201
 
200
202
  - `diff.json` 和 `o11y.json` 不会被复制。查看器不读取它们,且 diff 可能达到上百 MB。
201
203
  - 用 `file://` 直接打开 `index.html` 时浏览器不允许 fetch 工件,代码视图会提示源码不可用。本地预览用 http 服务打开。
202
204
 
203
- 最简单的流程是在跑过 eval 的机器上导出,把产物目录直接部署,不需要额外步骤。`.niceeval/` 通常不进版本库,如果构建发生在别的机器(CI、托管平台),先用 `copySnapshots` 把要发布的快照瘦身复制进仓库,再对着快照目录导出:
204
-
205
- ```typescript
206
- // scripts/snapshot.ts —— 跑完 eval 后执行,产物提交进仓库
207
- import { openResults, copySnapshots } from "niceeval/results";
208
-
209
- const results = await openResults(".niceeval");
210
- await copySnapshots(results.latest(), "site-data/run", {
211
- artifacts: ["sources", "events", "trace"],
212
- });
213
- ```
214
-
215
- ```bash
216
- npx niceeval view --run site-data/run --out site # CI 上对着快照构建
217
- ```
218
-
219
- 结果目录经 `--run` 递入,与 `show --run` 同一语义——位置参数永远留给 eval id 前缀。
220
-
221
- `results.latest()` 按实验挑最新快照,而不是挑最新的 run——某次运行只跑了部分实验时,其它实验仍保留上一轮的结果。
205
+ 最简单的流程是在跑过 eval 的机器上导出,把产物目录直接部署,不需要额外步骤。要让站点随 push 自动更新,把 `.niceeval/` 提交进仓库、CI 上一行 `view --out` 导出——workflow 与平台接线见[通过 CI 发布报告](/zh/guides/publish-report)。
222
206
 
223
207
  ## Artifact 说明
224
208
 
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  title: "写实验:用 defineExperiment 固定运行配置"
3
3
  sidebarTitle: "写实验"
4
- description: "学习如何在 experiments/ 里用 defineExperiment 选择 agent、传 model 和 params、设置 runs、预算、并发与 sandbox。"
4
+ description: "学习如何在 experiments/ 里用 defineExperiment 选择 agent、传 model 和 flags、设置 runs、预算、并发与 sandbox。"
5
5
  ---
6
6
 
7
- Experiment 是可签入的运行配置:同一批 eval 对着哪个 adapter、哪个模型、哪些参数、跑几次、预算多少,都写在 `experiments/` 里。CLI 的位置参数只负责筛 eval,不负责临时改 agent 或运行配置。
7
+ Experiment 是可签入的运行配置:同一批 eval 对着哪个 adapter、哪个模型、开哪些 flags、跑几次、预算多少,都写在 `experiments/` 里。CLI 的位置参数只负责筛 eval,不负责临时改 agent 或运行配置。
8
8
 
9
9
  ## 最小实验
10
10
 
@@ -20,7 +20,7 @@ export default defineExperiment({
20
20
 
21
21
  `agent` 放的是已经配置好的 agent 实例。被测系统的 URL、鉴权、协议细节通常传给 adapter 工厂;运行器不会单独保存一个 `agentConfig` 字段。
22
22
 
23
- ## 传 model 和 params
23
+ ## 传 model 和 flags
24
24
 
25
25
  ```ts
26
26
  // experiments/concise.ts
@@ -34,7 +34,7 @@ export default defineExperiment({
34
34
  apiKey: process.env.STAGING_AGENT_API_KEY,
35
35
  }),
36
36
  model: "gpt-5.4",
37
- params: {
37
+ flags: {
38
38
  promptVariant: "concise",
39
39
  webResearch: false,
40
40
  },
@@ -46,7 +46,7 @@ export default defineExperiment({
46
46
 
47
47
  `model` 会作为 `ctx.model` 传给 adapter;应用接口支持模型选择时,adapter 把它放进请求体、header 或 CLI 参数即可。
48
48
 
49
- `params` 会作为 `ctx.params` 传给 adapter,也会作为 `t.params` 出现在 eval 里。它适合表达 experiment 维度的 feature 开关,例如 prompt 版本、工具集、检索开关。真正按参数切换行为的是你的应用或 adapter,[NiceEval](https://niceeval.com/) 只负责原样透传。
49
+ `flags` 会作为 `ctx.flags` 传给 adapter,也会作为 `t.flags` 出现在 eval 里。语义就是产品 A/B 测试里的 feature flag:一个 experiment 就是一组 flag 取值,例如 prompt 版本、工具集、检索开关;值可以是任意 JSON(变体名、数字、配置对象),不限于布尔。真正按 flag 切换行为的是你的应用或 adapter,[NiceEval](https://niceeval.com/) 只负责原样透传。它与命令行开关是两回事——后者在文档里一律写作 CLI flag 或字面的 `--xxx`。
50
50
 
51
51
  ```ts
52
52
  // agents/web-agent.ts
@@ -57,7 +57,7 @@ async send(input, ctx) {
57
57
  body: JSON.stringify({
58
58
  message: input.text,
59
59
  model: ctx.model,
60
- params: ctx.params,
60
+ flags: ctx.flags,
61
61
  }),
62
62
  signal: ctx.signal,
63
63
  });
@@ -66,7 +66,7 @@ async send(input, ctx) {
66
66
 
67
67
  ## 一组实验怎么写
68
68
 
69
- 一个实验文件是一格配置。要比较多个模型、agent 或 params,就把多个文件放进同一个文件夹:
69
+ 一个实验文件是一格配置。要比较多个模型、agent 或 flag 取值,就把多个文件放进同一个文件夹:
70
70
 
71
71
  ```text
72
72
  experiments/
@@ -89,13 +89,27 @@ npx niceeval exp prompt-variants
89
89
  | `agent` | 选择并配置 adapter;必填 |
90
90
  | `model` | 单个模型名,经 `ctx.model` 透传 |
91
91
  | `reasoningEffort` | 单个推理努力程度(如 `"high"`),经 `ctx.reasoningEffort` / `t.reasoningEffort` 透传,归属与 `model` 一致 |
92
- | `params` | 任意 JSON 对象,经 `ctx.params` / `t.params` 透传 |
92
+ | `flags` | 实验条件(A/B 里的 feature flag),任意 JSON 对象,经 `ctx.flags` / `t.flags` 透传 |
93
93
  | `runs` | 每个 eval × 配置最多跑几次 |
94
94
  | `earlyExit` | 多次运行里通过一次就提前停止 |
95
95
  | `evals` | 限定这一格配置跑哪些 eval |
96
96
  | `timeoutMs` | 单个 attempt 的超时 |
97
97
  | `budget` | 这一格配置的预算上限 |
98
98
  | `maxConcurrency` | 这一格配置的并发上限 |
99
- | `sandbox` | sandbox agent 使用的后端,如 `dockerSandbox()` / `e2bSandbox()` |
99
+ | `sandbox` | sandbox agent 使用的后端,如 `dockerSandbox()` / `e2bSandbox()`;spec 上可以链 `.setup()` / `.teardown()` 挂按实验变化的环境钩子 |
100
100
 
101
- 跨配置比较的设计建议见[实验矩阵](/zh/guides/experiments)。adapter 如何消费 `ctx.model` `ctx.params` 见[Adapter](/zh/concepts/adapter)。
101
+ Experiment 本身是纯配置数据,没有 `setup` / `teardown` 这类字段。要在跑 agent 前按实验准备环境(装二进制、预热、跨 attempt 载入和回存状态),挂在 `sandbox` 字段的 spec 上:
102
+
103
+ ```ts
104
+ export default defineExperiment({
105
+ agent: codexAgent({ mcpServers: [mempalMcp] }),
106
+ sandbox: e2bSandbox({ template: "fasteval-agents" })
107
+ .setup(mempalSetup("codex")) // 装二进制、预热、写 hook、载入状态
108
+ .teardown(mempalTeardown("codex")), // 回存状态
109
+ maxConcurrency: 1, // 载入和回存之间不能并发,声明串行
110
+ });
111
+ ```
112
+
113
+ 钩子的执行时机、多钩子顺序和失败语义见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-backends#环境钩子)。
114
+
115
+ 跨配置比较的设计建议见[实验矩阵](/zh/guides/experiments)。adapter 如何消费 `ctx.model` 和 `ctx.flags` 见[Adapter](/zh/concepts/adapter)。
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "如何写好 Send"
3
3
  sidebarTitle: "如何写好 Send"
4
- description: "手写 Adapter 的 send 函数,一条主线七步走:发消息拿回复、接上之前的消息、记录消费、把工具解析成事件、人工介入(HITL)、接上 OTel trace、透传 experiment 的 params。每一步只在上一步的代码上多几行,每一步解锁一组断言。"
4
+ description: "手写 Adapter 的 send 函数,一条主线七步走:发消息拿回复、接上之前的消息、记录消费、把工具解析成事件、人工介入(HITL)、接上 OTel trace、透传 experiment 的 flags。每一步只在上一步的代码上多几行,每一步解锁一组断言。"
5
5
  ---
6
6
 
7
7
  [Adapter](/zh/concepts/adapter) 讲了契约:`send` 传入 `TurnInput` 和 `AgentContext`,返回 `Turn`。这一页讲怎么写它——从"发一条消息拿到回复"的最小形态起步,一步一步加到完整接入。每一步只在上一步的代码上多几行,前面已有的部分在代码里用 `// ……省略` 标出;每一步末尾说明这几行解锁了哪些断言。改完任何一步都能立刻验证:把新解锁的断言写进一条 eval 重跑 `npx niceeval exp`,`niceeval view` 里能看到这一步新增的数据——多轮轨迹、用量、工具事件、待回答请求。
@@ -303,16 +303,16 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces node server.js
303
303
 
304
304
  **这一步解锁**:`niceeval view` 里的调用瀑布图——应用内部每次模型调用、每次工具执行的耗时与 token,按轮铺成时间线。断言不变:判决依据在前几步的事件映射里已经齐了,span 只进瀑布图、不喂断言。接收器怎么起、span 怎么归属到轮,见 [OTel 接入](/zh/guides/connect-otel)。
305
305
 
306
- ## 第七步:透传 experiment 的 params(A/B 对比)
306
+ ## 第七步:透传 experiment 的 flags(A/B 对比)
307
307
 
308
- 应用把变体暴露成可切换的配置后,experiment 声明 `params`,运行器每轮经 `ctx.params` 原样递给 `send`;Adapter 不解释它们的含义,只随请求转发——应用按参数切换变体:
308
+ 应用把变体暴露成可切换的配置后,experiment 声明 `flags`,运行器每轮经 `ctx.flags` 原样递给 `send`;Adapter 不解释它们的含义,只随请求转发——应用按参数切换变体:
309
309
 
310
310
  ```ts
311
311
  // ……send 其余部分同前,省略
312
312
  body: JSON.stringify({
313
313
  messages,
314
314
  model: ctx.model, // ← 第一步就转发的 experiment.model;这里一起列出
315
- params: ctx.params, // ← experiment.params 原样透传;没配时是 {}
315
+ flags: ctx.flags, // ← experiment.flags 原样透传;没配时是 {}
316
316
  }),
317
317
  ```
318
318
 
@@ -324,11 +324,11 @@ import chatApp from "../agents/chat-app.ts";
324
324
  export default defineExperiment({
325
325
  agent: chatApp,
326
326
  model: "gpt-5.4", // ← 经 ctx.model 到 send
327
- params: { promptVariant: "concise" }, // ← 每轮经 ctx.params 到 send
327
+ flags: { promptVariant: "concise" }, // ← 每轮经 ctx.flags 到 send
328
328
  });
329
329
  ```
330
330
 
331
- 两个 experiment 文件各声明一份 `params`,`npx niceeval exp` 分别跑同一批 eval,就是一组 A/B 对比。这是三档接入里的 Tier 3(要求应用配合暴露开关),投入与回报见 [Tier](/zh/concepts/tier);`params` 与 `model`、`runs` 等其余 experiment 字段见[写实验](/zh/guides/write-experiment)。
331
+ 两个 experiment 文件各声明一份 `flags`,`npx niceeval exp` 分别跑同一批 eval,就是一组 A/B 对比。这是三档接入里的 Tier 3(要求应用配合暴露开关),投入与回报见 [Tier](/zh/concepts/tier);`flags` 与 `model`、`runs` 等其余 experiment 字段见[写实验](/zh/guides/write-experiment)。
332
332
 
333
333
  **这一步解锁**:同一批 eval 跨变体的成绩对比。
334
334
 
@@ -87,7 +87,7 @@ eval 运行完成后,[NiceEval](https://niceeval.com/) 会生成易读报告
87
87
  | 概念 | 一句话 |
88
88
  |---|---|
89
89
  | [Eval](/zh/concepts/evals) | 一个测试用例:写在 `evals/*.eval.ts` 里,描述测什么。 |
90
- | [Experiment](/zh/concepts/experiment) | 可签入的运行配置:决定连哪个 Adapter、什么 model、什么 params。 |
90
+ | [Experiment](/zh/concepts/experiment) | 可签入的运行配置:决定连哪个 Adapter、什么 model、什么 flags。 |
91
91
  | [Adapter](/zh/concepts/adapter) | 连接被测系统的适配层:实现一个 `send`,把返回翻译成标准事件流。 |
92
92
  | [Sandbox](/zh/guides/sandbox-backends) | 需要隔离工作区的 coding agent 才用得到;直连 Web Agent 不需要。 |
93
93
  | [Tier](/zh/concepts/tier) | 接入 Adapter 的三档投入:Tier 1 只接 send,Tier 2 加 OTel 换调用瀑布图,Tier 3 侵入改造做 feature A/B。 |
@@ -114,7 +114,7 @@ NiceEval 是一个 Agent-Native 的评估工具。Dataset / golden 那一套「
114
114
  | 概念 | 一句话 |
115
115
  |---|---|
116
116
  | Eval | 一个测试用例:写在 `evals/*.eval.ts` 里,描述测什么。 |
117
- | Experiment | 可签入的运行配置:决定连哪个 Adapter、什么 model、什么 params。 |
117
+ | Experiment | 可签入的运行配置:决定连哪个 Adapter、什么 model、什么 flags。 |
118
118
  | Adapter | 连接被测系统的适配层:实现一个 `send`,把返回翻译成标准事件流。 |
119
119
  | Sandbox | 需要隔离工作区的 coding agent 才用得到;直连 Web Agent 不需要。 |
120
120
  | Tier | 接入 Adapter 的三档投入:Tier 1 只接 send,Tier 2 加 OTel 换调用瀑布图,Tier 3 侵入改造做 feature A/B。 |
@@ -56,6 +56,8 @@ description: "NiceEval 内置的 claude-code、codex、bub 适配器分别做到
56
56
 
57
57
  {/* GENERATED:BEGIN builtin-agent-config */}
58
58
 
59
+ {/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
60
+
59
61
  ### `ClaudeCodeConfig`
60
62
 
61
63
  #### `apiKey`
@@ -196,6 +198,8 @@ export default uiMessageStreamAgent({
196
198
 
197
199
  {/* GENERATED:BEGIN ui-message-stream-options */}
198
200
 
201
+ {/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
202
+
199
203
  #### `name`
200
204
 
201
205
  ```ts
@@ -4,7 +4,7 @@ sidebarTitle: "命令行"
4
4
  description: "NiceEval CLI 参考:exp、init、list、clean 和 view 命令,以及 experiment、eval 过滤、sandbox、并发、预算和 JUnit CI 输出。"
5
5
  ---
6
6
 
7
- [NiceEval](https://niceeval.com/) CLI 是发现、运行和查看 eval 的入口。实际执行 eval 时采用 experiment-first 模型:**`exp` 选择可签入的运行配置**,experiment 后的位置参数才按 eval ID 前缀过滤。agent、model 和 params 写在 `experiments/`,不靠临时 CLI 参数。
7
+ [NiceEval](https://niceeval.com/) CLI 是发现、运行和查看 eval 的入口。实际执行 eval 时采用 experiment-first 模型:**`exp` 选择可签入的运行配置**,experiment 后的位置参数才按 eval ID 前缀过滤。agent、model 和 flags 写在 `experiments/`,不靠临时 CLI 参数。
8
8
 
9
9
  ## 命令
10
10
 
@@ -56,10 +56,12 @@ npx niceeval exp compare-models weather
56
56
 
57
57
  ## 常用 flags
58
58
 
59
- 下表由 CLI 的 flag 解析表生成。**没有**用于选择 sandbox 后端的 CLI flag:sandbox 后端只能写在代码里——在 experiment(`defineExperiment`)里设置 `sandbox`,或者在 `niceeval.config.ts`(`defineConfig`)里设置作为项目级兜底,值来自 `niceeval/sandbox` 的 `dockerSandbox()` / `vercelSandbox()` / `e2bSandbox()`;在 CLI 上尝试传入 `--sandbox` 会直接报错并以非零状态退出。`--watch` / `--json` 是文档曾接受、尚未实现的 flag,容忍解析(no-op)但不接任何行为,同样不在下表出现。
59
+ 下表由 CLI 的 flag 解析表生成,表外的 flag 一律按未知 flag 报错并以非零状态退出。**没有**用于选择 sandbox 后端的 CLI flag:sandbox 后端只能写在代码里——在 experiment(`defineExperiment`)里设置 `sandbox`,或者在 `niceeval.config.ts`(`defineConfig`)里设置作为项目级兜底,值来自 `niceeval/sandbox` 的 `dockerSandbox()` / `vercelSandbox()` / `e2bSandbox()`。
60
60
 
61
61
  {/* GENERATED:BEGIN cli-flags */}
62
62
 
63
+ {/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
64
+
63
65
  | Flag | 类型 | 说明 |
64
66
  |---|---|---|
65
67
  | `--agent` | string | experiment 运行不支持该 flag。要换 agent,请在 `experiments/` 下新增或复制一个配置文件。 |
@@ -70,16 +72,17 @@ npx niceeval exp compare-models weather
70
72
  | `--budget` | number | 整次运行的预算上限(美元)。 |
71
73
  | `--tag` | string | 只运行带有该 tag 的 eval(见 `defineEval` 的 `tags`)。 |
72
74
  | `--junit` | string | 额外写一份 JUnit XML 报告到指定路径,供 CI 消费。 |
75
+ | `--json` | string | 额外写一份 JSON 结果(`RunSummary` 原样序列化)到指定路径,供 CI 或下游脚本消费。 |
73
76
  | `--out` | string | `view` 命令专用:把结果查看器静态导出到指定目录。 |
74
77
  | `--port` | number | `view` 命令专用:指定本地服务器监听端口。 |
75
78
  | `--transcript` | boolean | `show` 命令专用:渲染单个 eval 的完整对话与工具调用(证据切面)。 |
76
79
  | `--trace` | boolean | `show` 命令专用:渲染单个 eval 的 trace 瀑布文本版(证据切面)。 |
77
80
  | `--diff` | boolean | `show` 命令专用:sandbox 里的文件改动摘要;`--diff=<文件路径>` 看单个文件的完整改动(路径必须 `=` 连写)。 |
78
81
  | `--history` | boolean | `show` 命令专用:跨 run 时间轴,只列真实执行;与 `--report` 互斥。 |
79
- | `--experiment` | string | `show` 命令专用:选集只留该实验。 |
82
+ | `--experiment` | string | `show` / `view` 命令专用:选集只留该实验。 |
80
83
  | `--attempt` | number | `show` 命令专用:指定详情 / 证据切面看第几次 attempt(与展示一致的 1 计序号)。 |
81
- | `--run` | string | `show` 命令专用:钉死看某一个结果目录(历史 run 或 `copySnapshots` 产物)。 |
82
- | `--report` | string | `show` 命令专用:把默认榜单整槽换成你的报告文件(默认导出 `defineReport(...)`)。 |
84
+ | `--run` | string | `show` / `view` 命令专用:钉死看某一个结果目录(历史 run 或 `copySnapshots` 产物)。 |
85
+ | `--report` | string | `show` / `view` 命令专用:用你的报告文件替换默认报告(文件默认导出 `defineReport(...)`)。 |
83
86
  | `--dry` | boolean | 只打印本次会匹配到的 eval × 运行配置,不实际执行。 |
84
87
  | `--quiet` | boolean | 关闭控制台 / live 进度输出(reporter 仍会写 artifacts)。 |
85
88
  | `--force` | boolean | 忽略上次运行结果,不跳过已通过的 (experiment, eval) 组合,强制全部重跑。 |
@@ -106,7 +109,7 @@ npx niceeval exp compare-models
106
109
  npx niceeval exp compare-models weather-tool
107
110
  ```
108
111
 
109
- 运行命名 experiment,用矩阵比较 agents、models 或 params。第二个参数开始是 eval ID 前缀过滤。
112
+ 运行命名 experiment,用矩阵比较 agents、models 或 flags。第二个参数开始是 eval ID 前缀过滤。
110
113
 
111
114
  ## `view`
112
115
 
@@ -80,6 +80,8 @@ export default defineExperiment({
80
80
 
81
81
  {/* GENERATED:BEGIN agent-def */}
82
82
 
83
+ {/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
84
+
83
85
  ### `RemoteAgentDef`
84
86
 
85
87
  #### `name`
@@ -214,17 +216,29 @@ readonly reasoningEffort?: string;
214
216
 
215
217
  模型推理努力程度;归属同 model——实验决定,省略时不覆盖 agent 原生默认。
216
218
 
217
- #### `params`
219
+ #### `flags`
218
220
 
219
221
  ```ts
220
- readonly params: Readonly<Record<string, unknown>>;
222
+ readonly flags: Readonly<Record<string, unknown>>;
221
223
  ```
222
224
 
223
- experiment 的 `params` 字段原样透传,内容和结构完全由 experiment 作者自定义
225
+ experiment 的 `flags` 字段原样透传,内容和结构完全由 experiment 作者自定义
224
226
  (如 `{ webResearch: true }`、`{ systemPrompt: "..." }`)。adapter 按自己的约定
225
227
  读取其中的字段;框架本身不解释、不校验它的内容。命名特意避开 CLI 解析出的
226
228
  `flag`(跑法层面的 --timeout/--budget 等),两者是不相关的概念。
227
229
 
230
+ #### `experimentId`
231
+
232
+ ```ts
233
+ readonly experimentId?: string;
234
+ ```
235
+
236
+ 路径推导出的实验 id(与结果归属 `runWho` / `AgentRun.experimentId` 同源);不经
237
+ experiment 跑(如脱离 CLI、直接构造 `AgentRun` 的场景)时为 undefined。典型用途:
238
+ `SandboxSpec.setup` 钩子按实验隔离跨 attempt 的状态(缓存目录名、快照 tag 等按
239
+ `ctx.experimentId` 分区),或 adapter 按实验切换鉴权 / 路由。与 `flags`(实验条件的
240
+ 具体取值)是两个维度——这里只是「跑的是哪个实验」的稳定标识,不携带条件内容。
241
+
228
242
  #### `sandbox`
229
243
 
230
244
  ```ts
@@ -280,6 +294,8 @@ log(msg: string): void;
280
294
 
281
295
  {/* GENERATED:BEGIN sandbox-methods */}
282
296
 
297
+ {/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
298
+
283
299
  ### `Sandbox`
284
300
 
285
301
  #### `workdir`
@@ -4,7 +4,7 @@ sidebarTitle: "defineConfig"
4
4
  description: "defineConfig 参考:judge、reporters、并发、超时和 sandbox 默认值。"
5
5
  ---
6
6
 
7
- `defineConfig` 从根目录的 `niceeval.config.ts` 默认导出,只放项目级默认值。agent、model、params、runs 和实验预算写在 `experiments/` 下的 `defineExperiment` 文件里。
7
+ `defineConfig` 从根目录的 `niceeval.config.ts` 默认导出,只放项目级默认值。agent、model、flags、runs 和实验预算写在 `experiments/` 下的 `defineExperiment` 文件里。
8
8
 
9
9
  ```ts
10
10
  import { defineConfig } from "niceeval";
@@ -20,6 +20,8 @@ export default defineConfig({
20
20
 
21
21
  {/* GENERATED:BEGIN config-fields */}
22
22
 
23
+ {/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
24
+
23
25
  #### `name`
24
26
 
25
27
  ```ts
@@ -26,6 +26,8 @@ export default defineEval({
26
26
 
27
27
  {/* GENERATED:BEGIN defineeval-options */}
28
28
 
29
+ {/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
30
+
29
31
  #### `id`
30
32
 
31
33
  ```ts
@@ -108,6 +110,8 @@ eval 主体:拿到 TestContext,驱动对话 / 沙箱操作并就地断言。
108
110
 
109
111
  {/* GENERATED:BEGIN test-context */}
110
112
 
113
+ {/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
114
+
111
115
  #### `send`
112
116
 
113
117
  ```ts
@@ -207,13 +211,13 @@ readonly reasoningEffort?: string;
207
211
 
208
212
  本次 attempt 的推理努力程度(如 "low"/"medium"/"high",取值由 adapter/模型决定)。
209
213
 
210
- #### `params`
214
+ #### `flags`
211
215
 
212
216
  ```ts
213
- readonly params: Readonly<Record<string, unknown>>;
217
+ readonly flags: Readonly<Record<string, unknown>>;
214
218
  ```
215
219
 
216
- 本次 attempt 生效的 params(experiment.params 与 CLI flag 合并后的只读视图)。
220
+ 本次 attempt 生效的实验 flags(experiment.flags 的只读视图;实验条件,非命令行开关)。
217
221
 
218
222
  #### `log`
219
223
 
@@ -440,6 +444,8 @@ t.judge.autoevals.summarizes(sourceText, { on: t.reply }).atLeast(0.7);
440
444
 
441
445
  {/* GENERATED:BEGIN turn-handle */}
442
446
 
447
+ {/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
448
+
443
449
  #### `events`
444
450
 
445
451
  ```ts
@@ -23,6 +23,8 @@ interface Turn {
23
23
 
24
24
  {/* GENERATED:BEGIN usage-fields */}
25
25
 
26
+ {/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
27
+
26
28
  #### `inputTokens`
27
29
 
28
30
  ```ts
@@ -81,6 +83,8 @@ token 用量或 OTel span 反推得到)。存在时优先于按价格表(`define
81
83
 
82
84
  {/* GENERATED:BEGIN stream-events */}
83
85
 
86
+ {/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
87
+
84
88
  #### `message`
85
89
 
86
90
  ```ts
@@ -29,6 +29,8 @@ t.require(turn.status, equals("completed"));
29
29
 
30
30
  {/* GENERATED:BEGIN expect-matchers */}
31
31
 
32
+ {/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
33
+
32
34
  #### `includes`
33
35
 
34
36
  ```ts
@@ -150,6 +152,8 @@ t.check(t.reply, includes("nice to have").atLeast(0.7));
150
152
 
151
153
  {/* GENERATED:BEGIN value-assertion */}
152
154
 
155
+ {/* 本区块由 pnpm docs:reference 从源码注释生成,勿手改;要改文案,改对应源码的 TSDoc/JSDoc(映射见 scripts/generate-reference.ts) */}
156
+
153
157
  #### `name`
154
158
 
155
159
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niceeval",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Agent-native eval tool — eval agents, services, functions, and coding-agent fixtures",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -100,7 +100,8 @@
100
100
  "shiki": "^4.3.0",
101
101
  "tailwind-merge": "^3.6.0",
102
102
  "tailwindcss": "^4.3.1",
103
- "typescript": "^5.6.0",
103
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
104
+ "@typescript/native": "npm:typescript@^7.0.2",
104
105
  "vite": "^8.1.0",
105
106
  "vitest": "^4.1.9"
106
107
  },
@@ -454,7 +454,7 @@ function fakeCtx(opts: { id?: string } = {}): AgentContext {
454
454
  return {
455
455
  signal: new AbortController().signal,
456
456
  model: undefined,
457
- params: {},
457
+ flags: {},
458
458
  sandbox: undefined as unknown as AgentContext["sandbox"],
459
459
  session,
460
460
  log: () => {},
@@ -367,7 +367,7 @@ export interface AiSdkGenerateContext<M = unknown> {
367
367
  /** 实验钉的推理努力程度(ctx.reasoningEffort);省略 → 用应用自己的默认。应用自己决定怎么塞进 providerOptions(如 OpenAI 的 reasoningEffort)。 */
368
368
  readonly reasoningEffort?: string;
369
369
  readonly signal: AbortSignal;
370
- readonly params: Readonly<Record<string, unknown>>;
370
+ readonly flags: Readonly<Record<string, unknown>>;
371
371
  /**
372
372
  * 配了 `tracing`(如 `aiSdkOtel()`)才有:直接放进 generateText / streamText 的
373
373
  * `telemetry` 选项。OTel provider、per-attempt 端点绑定和轮末 flush 都由工厂做,
@@ -505,7 +505,7 @@ export function aiSdkAgent<M = unknown>(options: AiSdkAgentOptions<M>): Agent {
505
505
  model: ctx.model,
506
506
  reasoningEffort: ctx.reasoningEffort,
507
507
  signal: ctx.signal,
508
- params: ctx.params,
508
+ flags: ctx.flags,
509
509
  telemetry: otel?.settings,
510
510
  });
511
511
  } catch (error) {
package/src/agents/bub.ts CHANGED
@@ -89,7 +89,8 @@ async function ensureBub(sb: Sandbox, home: string): Promise<void> {
89
89
 
90
90
  const inflight = installsInProgress.get(home);
91
91
  if (inflight) {
92
- await inflight;
92
+ // leader 失败(多为沙箱瞬态网络错)不级联杀 waiter:兜掉后走下面自己的安装路径重试。
93
+ await inflight.catch(() => {});
93
94
  const after = memCheckpoints.get(home);
94
95
  if (after) { await restoreCheckpoint(sb, after); return; }
95
96
  }
@@ -97,6 +98,9 @@ async function ensureBub(sb: Sandbox, home: string): Promise<void> {
97
98
  let resolveInstall!: () => void;
98
99
  let rejectInstall!: (e: unknown) => void;
99
100
  const installPromise = new Promise<void>((res, rej) => { resolveInstall = res; rejectInstall = rej; });
101
+ // 失败经由下方 throw e 传播给本 attempt;这把锁可能自始至终没有 waiter,
102
+ // 不兜住 rejection 会变 unhandledRejection,把整个 runner 进程连同全矩阵杀掉。
103
+ installPromise.catch(() => {});
100
104
  installsInProgress.set(home, installPromise);
101
105
 
102
106
  try {
@@ -94,7 +94,7 @@ export function claudeCodeAgent(config?: ClaudeCodeConfig): Agent {
94
94
  const args = ["--print", "--dangerously-skip-permissions"];
95
95
  if (ctx.model) args.push("--model", ctx.model);
96
96
  if (config?.maxTurns != null) args.push("--max-turns", String(config.maxTurns));
97
- if (ctx.params.webResearch) args.push("--allowedTools", "WebSearch,WebFetch");
97
+ if (ctx.flags.webResearch) args.push("--allowedTools", "WebSearch,WebFetch");
98
98
  if (ctx.session.id) args.push("--resume", ctx.session.id);
99
99
  args.push(input.text);
100
100
 
@@ -9,7 +9,7 @@ import type { Agent, McpServer } from "../types.ts";
9
9
  //
10
10
  // 连接方式:在沙箱里 spawn `codex exec --json`,stdout JSONL → parseCodex → 标准事件流。
11
11
  // 配置:鉴权本地(config / env),模型交给实验(ctx.model),推理努力程度经 ctx.reasoningEffort
12
- // (兼容旧的 ctx.params.effort),其余参数经 ctx.params
12
+ // (兼容旧的 ctx.flags.effort),其余参数经 ctx.flags
13
13
  // ───────────────────────────────────────────────────────────────────────────
14
14
 
15
15
  export interface CodexConfig {
@@ -44,7 +44,7 @@ export function codexAgent(config?: CodexConfig): Agent {
44
44
  // model 归属:实验决定(ctx.model);省略时不写 model 行,交给 codex CLI 原生默认,
45
45
  // 不在 adapter 里硬编码一个会过期的模型名。
46
46
  const modelLine = ctx.model ? `model = "${ctx.model}"\n` : "";
47
- const effort = ctx.reasoningEffort ?? (ctx.params.effort as string | undefined) ?? "medium";
47
+ const effort = ctx.reasoningEffort ?? (ctx.flags.effort as string | undefined) ?? "medium";
48
48
  const base = getBaseUrl();
49
49
 
50
50
  if (base) {
@@ -79,6 +79,8 @@ export type {
79
79
  Agent,
80
80
  AgentContext,
81
81
  AgentSession,
82
+ AgentSetup,
83
+ AgentTeardown,
82
84
  AgentTracing,
83
85
  SpanMapper,
84
86
  Telemetry,
@@ -9,7 +9,7 @@ import { createAgentSession } from "../context/session.ts";
9
9
  function lineCtx(): AgentContext {
10
10
  return {
11
11
  signal: new AbortController().signal,
12
- params: {},
12
+ flags: {},
13
13
  session: createAgentSession(),
14
14
  sandbox: undefined as never,
15
15
  log() {},
@@ -155,12 +155,20 @@ export interface AgentContext {
155
155
  /** 模型推理努力程度;归属同 model——实验决定,省略时不覆盖 agent 原生默认。 */
156
156
  readonly reasoningEffort?: string;
157
157
  /**
158
- * experiment 的 `params` 字段原样透传,内容和结构完全由 experiment 作者自定义
158
+ * experiment 的 `flags` 字段原样透传,内容和结构完全由 experiment 作者自定义
159
159
  * (如 `{ webResearch: true }`、`{ systemPrompt: "..." }`)。adapter 按自己的约定
160
160
  * 读取其中的字段;框架本身不解释、不校验它的内容。命名特意避开 CLI 解析出的
161
161
  * `flag`(跑法层面的 --timeout/--budget 等),两者是不相关的概念。
162
162
  */
163
- readonly params: Readonly<Record<string, unknown>>;
163
+ readonly flags: Readonly<Record<string, unknown>>;
164
+ /**
165
+ * 路径推导出的实验 id(与结果归属 `runWho` / `AgentRun.experimentId` 同源);不经
166
+ * experiment 跑(如脱离 CLI、直接构造 `AgentRun` 的场景)时为 undefined。典型用途:
167
+ * `SandboxSpec.setup` 钩子按实验隔离跨 attempt 的状态(缓存目录名、快照 tag 等按
168
+ * `ctx.experimentId` 分区),或 adapter 按实验切换鉴权 / 路由。与 `flags`(实验条件的
169
+ * 具体取值)是两个维度——这里只是「跑的是哪个实验」的稳定标识,不携带条件内容。
170
+ */
171
+ readonly experimentId?: string;
164
172
  /** 仅沙箱型 agent 有(运行器按 --sandbox 备好)。 */
165
173
  readonly sandbox: Sandbox;
166
174
  readonly session: AgentSession;
@@ -73,7 +73,7 @@ function ctx(overrides: Partial<{ model: string }> = {}): AgentContext {
73
73
  return {
74
74
  signal: new AbortController().signal,
75
75
  model: overrides.model,
76
- params: {},
76
+ flags: {},
77
77
  sandbox: undefined as never,
78
78
  // 同一个 ctx 重复用 = 同一条会话线(续接,同一个 ctx.session);新造 = 新线。
79
79
  session: createAgentSession(),