niceeval 0.6.0 → 0.6.2

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 (314) hide show
  1. package/dist/agents/types.d.ts +72 -6
  2. package/dist/context/types.d.ts +32 -12
  3. package/dist/i18n/en.d.ts +54 -0
  4. package/dist/i18n/zh-CN.d.ts +55 -1
  5. package/dist/o11y/types.d.ts +16 -2
  6. package/dist/report/aggregate.d.ts +5 -3
  7. package/dist/report/aggregate.js +32 -5
  8. package/dist/report/built-ins/experiment-comparison.d.ts +39 -0
  9. package/dist/report/built-ins/experiment-comparison.js +119 -0
  10. package/dist/report/built-ins/index.d.ts +2 -1
  11. package/dist/report/built-ins/index.js +2 -2
  12. package/dist/report/components.d.ts +10 -2
  13. package/dist/report/components.js +3 -3
  14. package/dist/report/compute.d.ts +11 -18
  15. package/dist/report/compute.js +68 -66
  16. package/dist/report/flag.d.ts +16 -1
  17. package/dist/report/flag.js +19 -1
  18. package/dist/report/format.d.ts +16 -14
  19. package/dist/report/format.js +28 -30
  20. package/dist/report/index.d.ts +5 -4
  21. package/dist/report/index.js +6 -5
  22. package/dist/report/locale.d.ts +23 -3
  23. package/dist/report/locale.js +47 -6
  24. package/dist/report/metrics.d.ts +13 -1
  25. package/dist/report/metrics.js +66 -15
  26. package/dist/report/primitives.d.ts +6 -0
  27. package/dist/report/react/AttemptList.d.ts +4 -4
  28. package/dist/report/react/AttemptList.js +8 -10
  29. package/dist/report/react/EvalList.d.ts +1 -1
  30. package/dist/report/react/EvalList.js +0 -0
  31. package/dist/report/react/ExperimentComparison.d.ts +8 -0
  32. package/dist/report/react/ExperimentComparison.js +11 -0
  33. package/dist/report/react/ExperimentList.d.ts +4 -2
  34. package/dist/report/react/ExperimentList.js +57 -7
  35. package/dist/report/react/MetricScatter.js +6 -16
  36. package/dist/report/react/chart-math.d.ts +23 -6
  37. package/dist/report/react/chart-math.js +71 -19
  38. package/dist/report/react/fixtures.d.ts +3 -3
  39. package/dist/report/react/fixtures.js +30 -18
  40. package/dist/report/react/format.d.ts +1 -1
  41. package/dist/report/react/format.js +1 -1
  42. package/dist/report/react/index.d.ts +1 -1
  43. package/dist/report/report.d.ts +5 -1
  44. package/dist/report/report.js +6 -2
  45. package/dist/report/text/faces.d.ts +1 -1
  46. package/dist/report/text/faces.js +100 -61
  47. package/dist/report/text/table.js +36 -5
  48. package/dist/report/types.d.ts +40 -34
  49. package/dist/results/types.d.ts +11 -0
  50. package/dist/runner/feedback/sink.d.ts +110 -0
  51. package/dist/runner/types.d.ts +513 -22
  52. package/dist/sandbox/docker.d.ts +23 -2
  53. package/dist/sandbox/e2b.d.ts +15 -1
  54. package/dist/sandbox/errors.d.ts +30 -3
  55. package/dist/sandbox/io-retry.d.ts +17 -0
  56. package/dist/sandbox/registry.d.ts +2 -0
  57. package/dist/sandbox/resolve.d.ts +18 -5
  58. package/dist/sandbox/retry.d.ts +11 -1
  59. package/dist/sandbox/types.d.ts +39 -5
  60. package/dist/sandbox/vercel.d.ts +7 -1
  61. package/dist/scoring/coverage.d.ts +30 -0
  62. package/dist/scoring/display.d.ts +21 -0
  63. package/dist/scoring/display.js +120 -0
  64. package/dist/scoring/types.d.ts +103 -20
  65. package/dist/shared/aggregate.d.ts +1 -0
  66. package/dist/shared/aggregate.js +3 -3
  67. package/dist/shared/types.d.ts +28 -0
  68. package/dist/tty-line.d.ts +0 -4
  69. package/dist/util.d.ts +23 -0
  70. package/docs-site/zh/concepts/adapter.mdx +24 -6
  71. package/docs-site/zh/concepts/assert.mdx +11 -10
  72. package/docs-site/zh/concepts/evals.mdx +7 -6
  73. package/docs-site/zh/concepts/experiment.mdx +1 -1
  74. package/docs-site/zh/concepts/overview.mdx +7 -7
  75. package/docs-site/zh/guides/agent-feedback-loop.mdx +35 -31
  76. package/docs-site/zh/guides/authoring.mdx +33 -0
  77. package/docs-site/zh/guides/ci-integration.mdx +23 -12
  78. package/docs-site/zh/guides/connect-your-agent.mdx +29 -3
  79. package/docs-site/zh/guides/custom-reports.mdx +29 -34
  80. package/docs-site/zh/guides/dataset-fanout.mdx +25 -3
  81. package/docs-site/zh/guides/debug-sandbox.mdx +57 -0
  82. package/docs-site/zh/guides/debugging.mdx +210 -0
  83. package/docs-site/zh/guides/experiments.mdx +10 -3
  84. package/docs-site/zh/guides/fixtures.mdx +3 -1
  85. package/docs-site/zh/guides/official-adapters.mdx +27 -3
  86. package/docs-site/zh/guides/publish-report.mdx +30 -16
  87. package/docs-site/zh/guides/report-components.mdx +49 -37
  88. package/docs-site/zh/guides/reporters.mdx +2 -2
  89. package/docs-site/zh/guides/results-data.mdx +42 -8
  90. package/docs-site/zh/guides/runner.mdx +17 -7
  91. package/docs-site/zh/guides/sandbox-agent.mdx +57 -7
  92. package/docs-site/zh/guides/sandbox-providers.mdx +258 -10
  93. package/docs-site/zh/guides/scoring-guide.mdx +4 -4
  94. package/docs-site/zh/guides/viewing-results.mdx +85 -41
  95. package/docs-site/zh/guides/write-experiment.mdx +5 -3
  96. package/docs-site/zh/guides/write-send.mdx +19 -2
  97. package/docs-site/zh/index.mdx +1 -1
  98. package/docs-site/zh/reference/builtin-agents.mdx +27 -0
  99. package/docs-site/zh/reference/capabilities.mdx +2 -2
  100. package/docs-site/zh/reference/cli.mdx +35 -9
  101. package/docs-site/zh/reference/define-agent.mdx +60 -5
  102. package/docs-site/zh/reference/define-config.mdx +1 -1
  103. package/docs-site/zh/reference/define-eval.mdx +42 -9
  104. package/docs-site/zh/reference/events.mdx +2 -2
  105. package/docs-site/zh/reference/expect.mdx +36 -6
  106. package/package.json +5 -1
  107. package/src/agents/ai-sdk-otel.test.ts +1 -0
  108. package/src/agents/ai-sdk.test.ts +3 -0
  109. package/src/agents/ai-sdk.ts +3 -0
  110. package/src/agents/bub-install-spec.test.ts +34 -0
  111. package/src/agents/bub-install-spec.ts +32 -0
  112. package/src/agents/bub.ts +31 -32
  113. package/src/agents/claude-code.test.ts +130 -9
  114. package/src/agents/claude-code.ts +76 -4
  115. package/src/agents/codex.test.ts +189 -40
  116. package/src/agents/codex.ts +155 -14
  117. package/src/agents/coding-cli-versions.test.ts +15 -0
  118. package/src/agents/coding-cli-versions.ts +3 -0
  119. package/src/agents/index.ts +11 -0
  120. package/src/agents/langgraph.test.ts +204 -0
  121. package/src/agents/langgraph.ts +495 -0
  122. package/src/agents/marketplace.ts +85 -0
  123. package/src/agents/native-config.test.ts +179 -0
  124. package/src/agents/native-config.ts +267 -0
  125. package/src/agents/openai-compat.test.ts +1 -0
  126. package/src/agents/openclaw.test.ts +31 -0
  127. package/src/agents/openclaw.ts +171 -0
  128. package/src/agents/plugin-config.test.ts +1 -0
  129. package/src/agents/sdk-streams.test.ts +79 -0
  130. package/src/agents/sdk-streams.ts +55 -10
  131. package/src/agents/skills.test.ts +1 -0
  132. package/src/agents/streaming.test.ts +3 -9
  133. package/src/agents/types.ts +73 -6
  134. package/src/agents/ui-message-stream.test.ts +3 -0
  135. package/src/cli.ts +411 -108
  136. package/src/context/context.test.ts +51 -12
  137. package/src/context/context.ts +161 -29
  138. package/src/context/session.test.ts +1 -0
  139. package/src/context/session.ts +114 -6
  140. package/src/context/types.ts +30 -12
  141. package/src/define.test.ts +13 -8
  142. package/src/define.ts +25 -4
  143. package/src/expect/index.ts +53 -23
  144. package/src/i18n/en.ts +65 -4
  145. package/src/i18n/zh-CN.ts +66 -4
  146. package/src/o11y/cost.test.ts +1 -0
  147. package/src/o11y/execution-tree.test.ts +1 -20
  148. package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
  149. package/src/o11y/otlp/parse.test.ts +1 -0
  150. package/src/o11y/otlp/turn-otel.test.ts +1 -0
  151. package/src/o11y/parsers/bub.test.ts +1 -0
  152. package/src/o11y/parsers/claude-code.test.ts +1 -34
  153. package/src/o11y/parsers/openclaw.test.ts +154 -0
  154. package/src/o11y/parsers/openclaw.ts +310 -0
  155. package/src/o11y/prices.json +746 -311
  156. package/src/o11y/tool-names.test.ts +1 -0
  157. package/src/o11y/types.ts +16 -2
  158. package/src/report/aggregate.ts +34 -5
  159. package/src/report/built-in-user-parity.test.tsx +127 -173
  160. package/src/report/built-ins/experiment-comparison.tsx +179 -0
  161. package/src/report/built-ins/index.ts +7 -2
  162. package/src/report/components.tsx +11 -3
  163. package/src/report/compute.ts +80 -74
  164. package/src/report/dual-render.test.tsx +222 -91
  165. package/src/report/flag.ts +30 -2
  166. package/src/report/format.ts +36 -27
  167. package/src/report/index.ts +23 -6
  168. package/src/report/locale.ts +49 -6
  169. package/src/report/metrics.ts +68 -15
  170. package/src/report/primitives.tsx +6 -0
  171. package/src/report/react/AttemptList.tsx +9 -36
  172. package/src/report/react/EvalList.tsx +0 -0
  173. package/src/report/react/ExperimentComparison.tsx +68 -0
  174. package/src/report/react/ExperimentList.tsx +173 -55
  175. package/src/report/react/MetricScatter.tsx +13 -25
  176. package/src/report/react/chart-math.test.ts +85 -0
  177. package/src/report/react/chart-math.ts +101 -22
  178. package/src/report/react/enhance.js +72 -1
  179. package/src/report/react/fixtures.ts +34 -21
  180. package/src/report/react/format.ts +1 -1
  181. package/src/report/react/index.tsx +0 -1
  182. package/src/report/react/render.test.tsx +30 -69
  183. package/src/report/react/styles.css +112 -14
  184. package/src/report/report.test.ts +308 -105
  185. package/src/report/report.ts +6 -2
  186. package/src/report/text/faces.ts +111 -67
  187. package/src/report/text/table.ts +42 -5
  188. package/src/report/types.ts +42 -34
  189. package/src/results/annotated-source.test.ts +62 -9
  190. package/src/results/annotated-source.ts +64 -6
  191. package/src/results/attempt-evidence.test.ts +9 -7
  192. package/src/results/attempt-evidence.ts +15 -8
  193. package/src/results/attempt-source.ts +6 -3
  194. package/src/results/copy.ts +145 -55
  195. package/src/results/host-equivalence.test.ts +11 -9
  196. package/src/results/index.ts +2 -0
  197. package/src/results/locator.test.ts +1 -22
  198. package/src/results/open.ts +7 -1
  199. package/src/results/publish.ts +149 -0
  200. package/src/results/results.test.ts +85 -51
  201. package/src/results/truncate.ts +90 -0
  202. package/src/results/types.ts +7 -0
  203. package/src/results/writer.ts +31 -13
  204. package/src/runner/attempt.test.ts +138 -7
  205. package/src/runner/attempt.ts +603 -104
  206. package/src/runner/discover.test.ts +47 -0
  207. package/src/runner/discover.ts +36 -2
  208. package/src/runner/eval-source.test.ts +1 -27
  209. package/src/runner/feedback/agent.test.ts +504 -0
  210. package/src/runner/feedback/agent.ts +409 -0
  211. package/src/runner/feedback/ci.test.ts +562 -0
  212. package/src/runner/feedback/ci.ts +401 -0
  213. package/src/runner/feedback/coordinator.test.ts +317 -0
  214. package/src/runner/feedback/coordinator.ts +397 -0
  215. package/src/runner/feedback/failure.ts +40 -0
  216. package/src/runner/feedback/human.test.ts +616 -0
  217. package/src/runner/feedback/human.ts +535 -0
  218. package/src/runner/feedback/index.ts +66 -0
  219. package/src/runner/feedback/io.ts +78 -0
  220. package/src/runner/feedback/profile.test.ts +50 -0
  221. package/src/runner/feedback/profile.ts +58 -0
  222. package/src/runner/feedback/reducer.test.ts +395 -0
  223. package/src/runner/feedback/reducer.ts +260 -0
  224. package/src/runner/feedback/renderer.ts +82 -0
  225. package/src/runner/feedback/sink.ts +203 -0
  226. package/src/runner/feedback/testing.ts +106 -0
  227. package/src/runner/ledger.test.ts +230 -0
  228. package/src/runner/ledger.ts +329 -0
  229. package/src/runner/report.test.ts +128 -3
  230. package/src/runner/report.ts +33 -9
  231. package/src/runner/reporters/artifacts.ts +8 -2
  232. package/src/runner/reporters/braintrust.test.ts +8 -7
  233. package/src/runner/reporters/braintrust.ts +9 -2
  234. package/src/runner/reporters/index.ts +2 -2
  235. package/src/runner/reporters/json.test.ts +162 -0
  236. package/src/runner/reporters/json.ts +35 -8
  237. package/src/runner/reporters/shared.ts +1 -5
  238. package/src/runner/run.test.ts +760 -3
  239. package/src/runner/run.ts +242 -36
  240. package/src/runner/sandbox-prep.ts +3 -42
  241. package/src/runner/timing.ts +158 -0
  242. package/src/runner/types.ts +518 -22
  243. package/src/sandbox/checkpoint.test.ts +55 -0
  244. package/src/sandbox/checkpoint.ts +29 -8
  245. package/src/sandbox/cli-commands.ts +407 -0
  246. package/src/sandbox/docker.ts +115 -16
  247. package/src/sandbox/e2b-agent-template.test.ts +56 -0
  248. package/src/sandbox/e2b-agent-template.ts +94 -0
  249. package/src/sandbox/e2b.ts +74 -9
  250. package/src/sandbox/errors.ts +111 -4
  251. package/src/sandbox/index.ts +2 -0
  252. package/src/sandbox/io-retry.test.ts +58 -0
  253. package/src/sandbox/io-retry.ts +45 -0
  254. package/src/sandbox/keep-registry.test.ts +86 -0
  255. package/src/sandbox/keep-registry.ts +142 -0
  256. package/src/sandbox/keep.ts +178 -0
  257. package/src/sandbox/paths.test.ts +1 -0
  258. package/src/sandbox/paths.ts +19 -8
  259. package/src/sandbox/registry.ts +20 -3
  260. package/src/sandbox/resolve.ts +76 -11
  261. package/src/sandbox/retry.test.ts +70 -0
  262. package/src/sandbox/retry.ts +46 -4
  263. package/src/sandbox/types.ts +44 -6
  264. package/src/sandbox/vercel.ts +43 -20
  265. package/src/scoring/collector.ts +60 -17
  266. package/src/scoring/coverage.ts +95 -0
  267. package/src/scoring/diff.ts +81 -0
  268. package/src/scoring/display.test.ts +121 -0
  269. package/src/scoring/display.ts +133 -0
  270. package/src/scoring/evidence.test.ts +189 -0
  271. package/src/scoring/judge.test.ts +142 -0
  272. package/src/scoring/judge.ts +15 -18
  273. package/src/scoring/scoped.ts +217 -50
  274. package/src/scoring/types.ts +117 -20
  275. package/src/scoring/verdict.ts +16 -4
  276. package/src/shared/aggregate.ts +3 -2
  277. package/src/shared/types.ts +31 -0
  278. package/src/show/compose.ts +2 -2
  279. package/src/show/index.ts +29 -16
  280. package/src/show/render.ts +626 -308
  281. package/src/show/show.test.ts +251 -36
  282. package/src/tty-line.ts +8 -26
  283. package/src/util.test.ts +1 -0
  284. package/src/util.ts +41 -0
  285. package/src/view/app/components/AttemptModal.tsx +153 -2
  286. package/src/view/app/components/CodeView.tsx +32 -11
  287. package/src/view/app/components/CopyControls.tsx +2 -2
  288. package/src/view/app/i18n.ts +6 -0
  289. package/src/view/app/lib/attempt-route.test.ts +1 -0
  290. package/src/view/app/lib/verdict.ts +7 -9
  291. package/src/view/artifact-serving.test.ts +2 -1
  292. package/src/view/client-dist/app.css +1 -1
  293. package/src/view/client-dist/app.js +17 -17
  294. package/src/view/data.test.ts +2 -1
  295. package/src/view/data.ts +17 -7
  296. package/src/view/index.ts +12 -1
  297. package/src/view/server.ts +2 -0
  298. package/src/view/shared/types.ts +1 -1
  299. package/src/view/styles.css +3 -0
  300. package/src/view/view-report.test.ts +11 -10
  301. package/dist/o11y/execution-tree.d.ts +0 -103
  302. package/dist/o11y/otlp/select.d.ts +0 -22
  303. package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +0 -1
  304. package/dist/report/built-ins/cost-pass-rate-comparison.js +0 -17
  305. package/dist/results/annotated-source.d.ts +0 -61
  306. package/dist/results/attempt-evidence.d.ts +0 -69
  307. package/dist/results/attempt-source.d.ts +0 -15
  308. package/src/report/built-ins/cost-pass-rate-comparison.tsx +0 -23
  309. package/src/runner/reporters/console.ts +0 -70
  310. package/src/runner/reporters/live.test.ts +0 -56
  311. package/src/runner/reporters/live.ts +0 -247
  312. package/src/runner/reporters/quiet.test.ts +0 -66
  313. package/src/runner/reporters/quiet.ts +0 -49
  314. package/src/runner/reporters/table.ts +0 -277
@@ -8,10 +8,11 @@ description: "报告文件里能摆的全部官方双面组件:每个组件回
8
8
 
9
9
  ## 名称与用途
10
10
 
11
- 英文术语描述组件形态,API 名是代码里的导出名。组件分三类:实体列表逐项展示 experiment、Eval 或 Attempt;汇总组件概括整批 Selection;指标图形把指定维度聚合成值。三个实体列表的 `.data(selection)` 都返回普通数组,报告作者先用 JavaScript `.filter()` 收窄,再把 `items` 交给组件。过滤条件不藏在组件里。中文正文首次提到时用“中文名(`API 名`)”,后续可以只写中文名或 `API 名`。
11
+ 英文术语描述组件形态,API 名是代码里的导出名。组件分四类:默认组合件按可比组组织完整比较;实体列表逐项展示 experiment、Eval 或 Attempt;汇总组件概括整批 Selection;指标图形把指定维度聚合成值。三个实体列表的 `.data(selection)` 都返回普通数组,报告作者先用 JavaScript `.filter()` 收窄,再把 `items` 交给组件。过滤条件不藏在组件里。中文正文首次提到时用“中文名(`API 名`)”,后续可以只写中文名或 `API 名`。
12
12
 
13
13
  | 分类 | 中文名 | English | API | 主展示单位 |
14
14
  | --- | --- | --- | --- | --- |
15
+ | 组合 | 实验组比较 | Experiment comparison | `ExperimentComparison` | 按 experiment 父目录分组;每组独立的摘要、散点与实验列表 |
15
16
  | 汇总 | 运行总览 | Run overview | `RunOverview` | 一批 Selection;汇总其中的 experiment、Eval 和 Attempt |
16
17
  | 汇总 | 组摘要 | Group summary | `GroupSummary` | 收窄后的一批 Selection;汇总一组 experiment 和 Eval |
17
18
  | 实体列表 | 实验列表 | Experiment list | `ExperimentList` | 每项一个 experiment;展开到该 experiment 的 Eval |
@@ -32,7 +33,7 @@ description: "报告文件里能摆的全部官方双面组件:每个组件回
32
33
  - **诚实渲染。** 缺数据渲染 `—` 不补 0;覆盖不全的格子带 `12/15` 角标;截断如实标注剩余数量与原始 artifact 路径。
33
34
  - **排序与方向随指标的 `better`。** higher 的指标降序、lower 的升序,「好」的一头恒在上、在右上。
34
35
  - **证据室深链。** 网页面的格子、点和条目深链到 Attempt 详情;终端面用同一 Attempt locator 交给 `niceeval show @<id>`。
35
- - **终端输出形成反馈闭环。** 每个 Attempt 有一个以 `@` 开头的短 locator,例如 `@1k2m9qrs`。它唯一指向 experiment、结果快照、Eval 和 Attempt。`✓` / `✗` / `!` / `–` 是 passed / failed / errored / skipped;`[E]` 表示运行时 Eval 源码可用,`[X]` 表示执行步骤可用,`[⏱]` 表示 OTel 时间可用,`[D]` 表示工作区 diff 可用。执行步骤统一包含消息、thinking、tool call/result 和 Skill load;OTel 只给这些步骤补时间,不另开一份输出。
36
+ - **终端输出形成反馈闭环。** 每个 Attempt 有一个以 `@` 开头的短 locator,例如 `@1k2m9qrs`。它唯一指向 experiment、结果快照、Eval 和 Attempt。`✓` / `✗` / `!` / `–` 是 passed / failed / errored / skipped。列表不用字母缩写编码证据可用性——locator 本身就是证据入口;打开 attempt(`niceeval show @<locator>`)后再列出实际可用的证据命令。执行步骤统一包含消息、thinking、tool call/result 和 Skill load;OTel 只给这些步骤补时间,不另开一份输出。
36
37
  - **两面同口径,网页面不依赖 JS 也完整。** 排序在计算时由 `sort` 定死,终端和网页看到同一份基准顺序;下钻是普通链接、展开折叠用 `<details>`。网页面另有一层浏览操作:点表头就地重排、榜单行过滤、图表点位悬停看数值——只影响眼前的视图,不改数据口径,刷新即回基准顺序;浏览器禁用 JS 时这些操作消失,内容一样不少(悬停信息退化为图内提示)。
37
38
 
38
39
  ## 排版原语
@@ -95,6 +96,18 @@ description: "报告文件里能摆的全部官方双面组件:每个组件回
95
96
 
96
97
  指标表、指标矩阵、成绩单和成对差异表的终端面就建在 `Table` 上,所以你的表和官方的表用的是同一把尺子。表格之外的形态要自己排字符时,用[自定义报告](/zh/guides/custom-reports)「换形态」一节里那套文本排版函数。
97
98
 
99
+ ## 实验组比较(`ExperimentComparison`)
100
+
101
+ `niceeval show` / `view` 不传 `--report` 时使用的默认组合件。它先按 experiment id 的完整父目录分组,再为每组分别计算组摘要、成本 × 端到端成功率散点和实验列表:
102
+
103
+ ```tsx
104
+ <ExperimentComparison data={await ExperimentComparison.data(selection)} />
105
+ ```
106
+
107
+ `compare/bub` 与 `compare/codex` 属于 `compare`,可以横向比较;`dev-e2b/bub` 属于另一个组,不会进入同一张图、同一条 series 或同一张表。顶层 experiment 没有父目录时,以自己的完整 id 形成单例组。网页面持有完整组索引并一次聚焦一组,切组不重新读取 Selection;终端面命中多组时只显示组索引与单组查看命令,命中单组时才展开详情。浏览器禁用 JS 时,每组仍以独立 `<details>` 保留完整内容。
108
+
109
+ 这个分区只属于默认组合件。下面的 `MetricScatter`、`MetricTable` 和 `ExperimentList` 都忠实消费调用方传入的数据;自定义报告把跨组 Selection 传给它们,就表示明确选择跨组分析。
110
+
98
111
  ## 运行总览(`RunOverview`)
99
112
 
100
113
  每张报告开头「这批数据是什么」:几个配置、几道题、通过分布、总成本、何时跑的;Selection 里的挑选警告随数据一起进来、直接显示在条内,诚实不用你另外传一遍。
@@ -126,13 +139,15 @@ Pass rate 73.3% · 2 experiments · 15 evals · failed 3 · errored 1 · $0.93
126
139
  latest 2026-07-09T10:00:00Z
127
140
  ```
128
141
 
129
- 通过率是 eval 级折叠计票口径:同一 eval 的多轮 attempt 先折成一个判定(任一轮通过则算通过,否则取最严重的),`passed / (passed + failed + errored)`,`skipped` 不进分母——与页头 `RunOverview` 那个两级聚合、带 partial credit `passRate` 是两码事,两者服务不同问题:`GroupSummary` 回答「这组题过了几道」,`RunOverview` 回答「整体质量几分」,不要互相替代。`evals` 按 `experimentId + eval id` 的完整身份键去重——组里两个 experiment 各自的同名 eval 算两道题,不会被误合并成一道。`errored` 为 0 时这一段省略,但 `verdicts.errored` 这个数据字段本身永远在,省略只发生在渲染层。`totalCostUSD` 是组内可测 attempt 成本求和,一个 attempt 都没报成本时是 `null`,两面都渲染缺数据而不是 `$0`。
142
+ 通过率是 eval 级折叠计票口径:同一 eval 的多轮 attempt 先折成一个判定(任一轮通过则算通过,否则取最严重的),`passed / (passed + failed + errored)`,`skipped` 不进分母。页头 `RunOverview` 使用 `endToEndPassRate` 的两级聚合:先算同一道题各 Attempt 的端到端成功率,再跨题平均。两者的聚合粒度不同,但都会让 errored 降低成功率。`GroupSummary` 回答「这组题最终过了几道」,`RunOverview` 回答「每次实际运行交付成功结果的比例」,不要互相替代。`evals` 按 `experimentId + eval id` 的完整身份键去重——组里两个 experiment 各自的同名 eval 算两道题,不会被误合并成一道。`errored` 为 0 时这一段省略,但 `verdicts.errored` 这个数据字段本身永远在,省略只发生在渲染层。`totalCostUSD` 是组内可测 attempt 成本求和,一个 attempt 都没报成本时是 `null`,两面都渲染缺数据而不是 `$0`。
130
143
 
131
144
  组摘要是普通的公开组件,`GroupSummary.data` 是普通的公开计算函数——想在自己的报告里按目录前缀分组、每组摆一块,按上面的写法收窄 Selection 再调它就行。
132
145
 
133
146
  ## 实验列表(`ExperimentList`)
134
147
 
135
- 每项固定代表一个 experiment。主行显示 experiment id、agent、model、flags、Eval 判定构成、通过率、Tokens、成本和耗时;展开后显示这个 experiment 的 Eval 列表。它不接受列配置——这是 experiment 的诊断视图,不是通用指标表。
148
+ 每项固定代表一个 experiment。主行显示 experiment id、agent、model、flags、Eval 判定构成、通过率、Tokens、成本和耗时;展开后显示这个 experiment 的 Eval 列表。Eval 父行显示折叠判定、Attempt 数、平均耗时和平均成本;下面每个 Attempt 再显示该轮自己的失败摘要。失败内容只出现一次,不会在 Eval 与唯一 Attempt 上重复。它不接受列配置——这是 experiment 的诊断视图,不是通用指标表。默认 `ExperimentComparison` 每次只把一个可比组的 items 交给它;组件本身不猜组边界。
149
+
150
+ 默认比较已经用组名作面板 / 段落标题,所以组内每行的 experiment 标签会去掉这层文件夹前缀,只显示 id 末段(和同组散点的点标签一致),不在每行重复文件夹名;完整 id 仍是排序、着色和身份的依据。独立使用 `ExperimentList`、不告诉它相对哪个组时,显示完整 id。
136
151
 
137
152
  ```tsx
138
153
  const experiments = await ExperimentList.data(selection);
@@ -142,22 +157,19 @@ const experiments = await ExperimentList.data(selection);
142
157
  />
143
158
  ```
144
159
 
145
- `niceeval show` 每个 experiment 输出一行汇总,每个 Eval 只占一行。Attempt ID 后的证据位告诉 AI 能读什么,不展开重复命令:
160
+ `niceeval show` 先输出 experiment 比较表,再按 experiment 展开 Eval / Attempt 父子表。Eval 父行给题级平均值,Attempt 子行给这一轮的失败摘要和 locator:
146
161
 
147
162
  ```text
148
- compare/bub-gpt-5.4 · bub · gpt-5.4
149
- pass 87% · 13 passed / 1 failed / 1 errored · 42 attempts · 4m 12s · $0.42
150
- ✓ algebra/quadratic @12f9k3aq✓[E,X,⏱] @141bm7cx✓[E,X,⏱] 18s avg · $0.02 avg
151
- ✗ weather/brooklyn @1k2m9qrs✗[E,X,⏱] @1nx4dpqr✗[E,X] @19vq2jex✗[E,X] gate calledTool("get_weather")
152
- ! fixtures/button @1c3h6twx![E,D] command timed out after 120s
153
-
154
- compare/codex-gpt-5.4 · codex · gpt-5.4
155
- pass 80% · 12 passed / 3 failed · 45 attempts · 5m 03s · $0.51
156
- algebra/quadratic @1d8p4kwz✓[E,X,⏱] 16s · $0.02
157
- algebra/matrix @1e2j7mxa✗[E,X] @1f5r9nab✗[E,X,⏱] @1g4w8pcd✗[E,X,⏱] gate equals("42")
158
- ✗ weather/brooklyn @1h6t3vbe✗[E,X,⏱] @1j9m2qfg✗[E,X,⏱] @1k7c5rxh✗[E,X,⏱] gate calledTool("get_weather")
159
-
160
- inspect: niceeval show @<id> [--eval|--execution|--diff]
163
+ Experiment Model Agent Avg duration E2E pass rate Result Tokens Est. cost
164
+ bub-gpt-5.4 gpt-5.4 bub 41.0s 50% 1 passed / 1 failed 42k $0.08
165
+
166
+ bub-gpt-5.4
167
+ Status Eval / Attempt Result Duration Cost
168
+ ✓ passed algebra/quadratic 18.0s avg $0.02 avg
169
+ ✓ └─ @12f9k3aq — 18.0s $0.02
170
+ failed weather/brooklyn 42.0s avg $0.04 avg
171
+ ✗ ├─ @1k2m9qrs calledTool("get_weather") · no calls 41.0s $0.04
172
+ └─ @1nx4dpqr calledTool("get_weather") · no calls 43.0s $0.04
161
173
  ```
162
174
 
163
175
  locator 由 `experimentId + snapshot.startedAt + evalId + attempt index` 的不可变身份确定,复制或发布结果后保持不变。宿主在当前结果根解析 locator;不存在或发生冲突时直接报错,不回退到“最新一次”。`@` 前缀让它与 Eval ID 前缀选择器无歧义。
@@ -166,7 +178,7 @@ locator 由 `experimentId + snapshot.startedAt + evalId + attempt index` 的不
166
178
 
167
179
  ## Eval 列表(`EvalList`)
168
180
 
169
- 每项固定代表一个 `experimentId + evalId`,因为同一个 Eval 跑在两个 experiment 上是两条不同结果。主行显示判定、Attempt 数、聚合分数、成本、耗时和失败原因;展开后显示这个 Eval 的 Attempt 列表。
181
+ 每项固定代表一个 `experimentId + evalId`,因为同一个 Eval 跑在两个 experiment 上是两条不同结果。主行显示判定、Attempt 数、聚合分数、平均成本和平均耗时;展开后显示这个 Eval 的 Attempt 列表,由每个 Attempt 行显示该轮自己的失败原因。Eval 主行不挑某一轮的失败原因冒充题级结论。
170
182
 
171
183
  ```tsx
172
184
  const evals = await EvalList.data(selection);
@@ -181,13 +193,13 @@ const evals = await EvalList.data(selection);
181
193
  ```text
182
194
  weather/brooklyn · compare/bub-gpt-5.4 · failed
183
195
  score 0.20 · 3 attempts · 41s avg · $0.04 avg
184
- @1k2m9qrs✗[E,X,⏱] · gate calledTool("get_weather") — tool was never called
185
- @1nx4dpqr✗[E,X] · gate calledTool("get_weather") — tool was never called
186
- @19vq2jex✗[E,X] · gate calledTool("get_weather") — tool was never called
196
+ @1k2m9qrs✗ · gate calledTool("get_weather") — tool was never called
197
+ @1nx4dpqr✗ · gate calledTool("get_weather") — tool was never called
198
+ @19vq2jex✗ · gate calledTool("get_weather") — tool was never called
187
199
 
188
200
  fixtures/button · compare/bub-gpt-5.4 · errored
189
201
  score — · 1 attempt · 2m 00s · $0.09
190
- @1c3h6twx![E,D] · command timed out after 120s
202
+ @1c3h6twx! · command timed out after 120s
191
203
 
192
204
  inspect: niceeval show @<id> [--eval|--execution|--diff]
193
205
  ```
@@ -196,7 +208,7 @@ inspect: niceeval show @<id> [--eval|--execution|--diff]
196
208
 
197
209
  ## Attempt 列表(`AttemptList`)
198
210
 
199
- 每项固定代表一个 Attempt,显示 experiment、Eval、Attempt 序号、判定、耗时、成本、失败断言、errorJudge 评语和证据链接。它既能列失败证据,也能列通过样本,不把 verdict 过滤写死在组件名里。
211
+ 每项固定代表一个 Attempt,显示 experiment、Eval、Attempt 序号、判定、耗时、成本、失败断言、结构化 error 的一层摘要、Judge 评语和证据链接。diagnostics、cause 和 stack 留给 locator 下钻详情,避免比较列表被基础设施日志撑开。它既能列失败证据,也能列通过样本,不把 verdict 过滤写死在组件名里。
200
212
 
201
213
  ```tsx
202
214
  const attempts = await AttemptList.data(selection, { redact });
@@ -206,23 +218,23 @@ const attempts = await AttemptList.data(selection, { redact });
206
218
  />
207
219
  ```
208
220
 
209
- `niceeval show` 每项完整输出一个 Attempt,不再折叠到 Eval 汇总。这里已经是叶子层,只在末尾给一条与该 Attempt 可用证据对应的模板:
221
+ `niceeval show` 每项完整输出一个 Attempt,不折叠到 Eval 汇总。这里已经是叶子层,只在末尾给一条与该 Attempt 可用证据对应的模板:
210
222
 
211
223
  ```text
212
- ✗ @1k2m9qrs · weather/brooklyn · compare/bub-gpt-5.4 · 41s · $0.04 · [E,X,⏱]
224
+ ✗ @1k2m9qrs · weather/brooklyn · compare/bub-gpt-5.4 · 41s · $0.04
213
225
  gate calledTool("get_weather") · failed
214
226
  tool was never called
215
227
  soft judge("回答基于实时数据") · 0.2/1
216
228
  reply invents a temperature
217
229
 
218
- ! @1c3h6twx · fixtures/button · compare/bub-gpt-5.4 · 2m 00s · $0.09 · [E,D]
230
+ ! @1c3h6twx · fixtures/button · compare/bub-gpt-5.4 · 2m 00s · $0.09
219
231
  command timed out after 120s
220
232
  inspect: niceeval show @<id> [--eval|--execution|--diff]
221
233
 
222
234
  (3 more not shown · showing 20 of 23)
223
235
  ```
224
236
 
225
- 发布前要消毒 error、断言 detail 或 Judge 评语时,把 `redact` 交给 `.data()`;要展示哪些 Attempt,过滤返回的 `AttemptListItem[]`。`limit` 也由报告作者在数组上用 `.slice(0, 20)` 表达,截断时把原始数量交给组件的 `total`,组件据此显示“还有 n 项未展示”,不静默截断。
237
+ 要在页面显示前遮蔽 error message/cause/stack、diagnostic message/data、断言 detail 或 Judge 评语,把 `redact` 交给 `.data()`;稳定 code、lifecycle operation、experiment、Eval 和 locator 不改。它只影响这份组件数据,管不到发布目录里的 artifact 文件——发布数据集的消毒用 [`copySnapshots` 的 `redact` 选项](/zh/guides/results-data)。要展示哪些 Attempt,过滤返回的 `AttemptListItem[]`。`limit` 也由报告作者在数组上用 `.slice(0, 20)` 表达,截断时把原始数量交给组件的 `total`,组件据此显示“还有 n 项未展示”,不静默截断。
226
238
 
227
239
  ## 指标表(`MetricTable`)
228
240
 
@@ -231,8 +243,8 @@ inspect: niceeval show @<id> [--eval|--execution|--diff]
231
243
  ```tsx
232
244
  <MetricTable data={await MetricTable.data(selection, {
233
245
  rows: "agent",
234
- columns: [passRate, codeLines, costUSD],
235
- sort: passRate,
246
+ columns: [endToEndPassRate, codeLines, costUSD],
247
+ sort: endToEndPassRate,
236
248
  })} />
237
249
  ```
238
250
 
@@ -251,7 +263,7 @@ codex 80% 12/15 355 lines $0.51
251
263
  行 × 列两个维度、格子里一个指标,回答「哪道题谁挂了」。稀疏渲染:没有样本的格子空着,不编数。
252
264
 
253
265
  ```tsx
254
- <MetricMatrix data={await MetricMatrix.data(selection, { rows: "eval", columns: "agent", cell: passRate })} />
266
+ <MetricMatrix data={await MetricMatrix.data(selection, { rows: "eval", columns: "agent", cell: endToEndPassRate })} />
255
267
  ```
256
268
 
257
269
  ```text
@@ -273,7 +285,7 @@ next: niceeval show geometry/area
273
285
  <MetricBars data={await MetricBars.data(selection, {
274
286
  rows: "evalGroup", // 一组条 = 一个科目/benchmark
275
287
  columns: "agent", // 一根条 = 一个 agent
276
- cell: passRate,
288
+ cell: endToEndPassRate,
277
289
  })} />
278
290
  ```
279
291
 
@@ -317,11 +329,11 @@ codex 71.0/100 40/50 31/50 (1 missing)
317
329
  points="experiment" // 每个点 = 一个配置的聚合
318
330
  series="agent" // 同 agent 的档位连成线
319
331
  x={costUSD}
320
- y={passRate}
332
+ y={endToEndPassRate}
321
333
  />
322
334
  ```
323
335
 
324
- 直接把 `selection` 传给它,宿主渲染前替你算好数据——默认报告就是这么写的。要在自己已经跑起来的 React 应用里嵌这张图、或数据是预先算好的,改传 `data`:`<MetricScatter data={await MetricScatter.data(selection, { points: "experiment", series: "agent", x: costUSD, y: passRate })} />`;同时传 `data` 和 `selection`、或两者都不传,类型检查都会报错。
336
+ 直接把 `selection` 传给它,宿主渲染前替你算好数据。`MetricScatter` 不根据 experiment id 自动分组;默认 `ExperimentComparison` 会先收窄到一个可比组,再逐组调用它。要在自己已经跑起来的 React 应用里嵌这张图、或数据是预先算好的,改传 `data`:`<MetricScatter data={await MetricScatter.data(selection, { points: "experiment", series: "agent", x: costUSD, y: endToEndPassRate })} />`;同时传 `data` 和 `selection`、或两者都不传,类型检查都会报错。
325
337
 
326
338
  ```text
327
339
  pass ↑ (好 → 右上)
@@ -335,7 +347,7 @@ pass ↑ (好 → 右上)
335
347
  A bub-high B bub-medium C codex-high D codex-low
336
348
  ```
337
349
 
338
- 网页面点带悬停提示(值与 `samples/total`,禁用 JS 时退化为图内提示)、同系列连线、点击深链下钻。终端面用字母标点、图例列在图下;x 或 y 缺数据的点两个面都不画,注脚如实报「n 个点缺数据」;点太密排不下时降级为坐标表,不硬挤。画得出来的点是 0 个(x 或 y 全缺数据)时,两个面都明说这两个指标没有可用数据,不留一片空白;只有 1 个点时,明说「至少要两个实验才能比较」;2 个及以上才正常出图——组件从不因为点不够就整块消失,让你不知道图为什么没了。维度槽也收自定义维度(`{ name, of }`,从 attempt 已有数据算组名)和 `flag()`(experiment 声明的变量),怎么选见[自定义报告](/zh/guides/custom-reports)的「换分组」一节。
350
+ 网页面点带悬停提示(值与 `samples/total`,禁用 JS 时退化为图内提示)、同系列连线、点击深链下钻。终端面用字母标点、图例列在图下;x 或 y 缺数据的点两个面都不画,注脚如实报「n 个点缺数据」;点太密排不下时降级为坐标表,不硬挤。画得出来的点是 0 个(x 或 y 全缺数据)时,两个面都明说这两个指标没有可用数据,不留一片空白;1 个点也照常出图——组件从不因为点不够就整块消失,让你不知道图为什么没了。维度槽也收自定义维度(`{ name, of }`,从 attempt 已有数据算组名)和 `flag()`(experiment 声明的变量),怎么选见[自定义报告](/zh/guides/custom-reports)的「换分组」一节。
339
351
 
340
352
  ## 指标趋势图(`MetricLine`)
341
353
 
@@ -345,7 +357,7 @@ x 是有序变量、每个系列一条线,回答「变量拧大,分数怎么
345
357
  <MetricLine data={await MetricLine.data(selection, {
346
358
  x: flag("latencyMs", { label: "Simulated latency", unit: "ms" }),
347
359
  series: flag("agents", { label: (v) => `${v} agents` }),
348
- y: passRate,
360
+ y: endToEndPassRate,
349
361
  })} />
350
362
  ```
351
363
 
@@ -376,7 +388,7 @@ A 1 agents B 4 agents C 16 agents
376
388
  { a: "compare/bub", b: "compare/bub--agents-md", label: "bub" },
377
389
  { a: "compare/codex", b: "compare/codex--agents-md", label: "codex" },
378
390
  ],
379
- metrics: [passRate, costUSD],
391
+ metrics: [endToEndPassRate, costUSD],
380
392
  })} />
381
393
  ```
382
394
 
@@ -4,7 +4,7 @@ sidebarTitle: "Reporter 上报"
4
4
  description: "用内置 reporters 把 eval 结果送到 Braintrust 实验、JUnit XML 或自定义目的地。"
5
5
  ---
6
6
 
7
- [NiceEval](https://niceeval.com/) 自己跑、自己判分;reporter 负责把结果送出去。控制台输出和 `.niceeval/` artifacts 本身就是内置 reporter,始终开启。其余 reporter 从 `niceeval/reporters` 导入,按需挂载。
7
+ [NiceEval](https://niceeval.com/) 自己跑、自己判分;Reporter 负责把完成结果送到其它目的地。运行中的 Human/Agent/CI 反馈由 `niceeval exp --output ...` 选择,不是用户配置的 Reporter;`.niceeval/` results artifacts 始终开启。其余 Reporter 从 `niceeval/reporters` 导入,按需挂载。
8
8
 
9
9
  挂载位置有两个:
10
10
 
@@ -110,4 +110,4 @@ const notify: Reporter = {
110
110
  - `onRunComplete(summary)`:运行结束,收到聚合汇总。
111
111
  - `onEvent(event)`:更细粒度的事件流(`eval:start`、`run:budgetExceeded` 等)。
112
112
 
113
- reporter 抛错只会打一条诊断信息,不会让运行中断。只有目的地没被内置覆盖时才需要自定义——`.niceeval/` 的 artifacts 已经记录了全部结果,事后分析直接读它(见[查看结果](./viewing-results))。
113
+ 用户在 config/eval 中挂载的 Reporter 默认是 best-effort:抛错会形成永久 diagnostic,但不会中断在飞 Attempt。CLI 显式要求的 `--json` / `--junit` 与默认 results artifacts 是 required 输出,写失败会让最终运行判红。只有目的地没被内置覆盖时才需要自定义——`.niceeval/` 的 artifacts 已经记录了完整结果,事后分析直接读它(见[查看结果](./viewing-results))。
@@ -68,7 +68,7 @@ const attempt = snap.evals[0].attempts[0];
68
68
 
69
69
  attempt.evalId; // "algebra/quadratic" —— 属于哪道题,不用绕 result
70
70
  attempt.experimentId; // 属于哪个实验
71
- attempt.result; // EvalResult:判定、断言、用量、成本、experiment 元数据
71
+ attempt.result; // EvalResult:判定、断言、结构化 error/diagnostics、用量、成本
72
72
  attempt.ref; // { snapshot, attempt }:证据引用,与 view 深链、报告格子的 refs 同一身份
73
73
  await attempt.events(); // StreamEvent[] | null
74
74
  await attempt.trace(); // TraceSpan[] | null
@@ -83,6 +83,35 @@ await attempt.sources(); // SourceArtifact[] | null
83
83
  - **读不了的落盘不静默。** 版本不兼容、目录损坏的快照进 `skipped` 并带原因;要不要展示由你定,但缺口永远被算出来。
84
84
  - **同进程内按句柄记忆化。** 两处都读同一个 `diff()` 不会把上百 MB 读两遍。
85
85
 
86
+ `attempt.result.error` 是让 Attempt 进入 `errored` 的唯一致命执行错误,包含稳定 `code`、人可读 `message`、发生错误的 lifecycle operation,以及可选的有限 cause/stack。`attempt.result.diagnostics` 可以与任意判定共存,保存运行仍可继续或收尾时发现的问题。瞬时 `progress` 不落盘;OTel trace 也不是错误存储的前提。
87
+
88
+ ## 超大输出会被截断
89
+
90
+ Agent 跑一条命令,输出可以大得离谱——一次递归 `grep` 扫进 `node_modules`,撞上压缩过的 JS 文件,单行就有几 MB。这种输出会同时进 `events.json` 和 `trace.json`,不管的话一个 attempt 就能占上百 MB。
91
+
92
+ 所以 NiceEval 落盘时会削:`events.json` 和 `trace.json` 里任何超过 256 KiB 的字符串,只保留前 256 KiB,末尾留一行说明:
93
+
94
+ ```text
95
+ [niceeval] truncated 51467156 → 262144 bytes
96
+ ```
97
+
98
+ **这不影响判定。** 断言读的是运行时的完整输出,截断只发生在写文件的那一刻——落盘的 artifact 是证据,不是评分的输入。断言该过还是过,该挂还是挂。
99
+
100
+ 要在自己的报告里如实标出「这里少了东西」,别去匹配上面那行文本,读结构化字段:被截断的事件和 span 都带 `truncated`,里面是被截断的位置和原始字节数。
101
+
102
+ ```typescript
103
+ const events = await attempt.events();
104
+ for (const e of events ?? []) {
105
+ for (const t of e.truncated ?? []) {
106
+ console.log(`${t.path} 原始 ${t.originalBytes} 字节,已截断`);
107
+ }
108
+ }
109
+ ```
110
+
111
+ 这条上限管的是**单个字符串值**,不是整个 JSON 文件。一个文件可以有很多正常值;`diff.json` 和源码也不能截断,因为它们要保持完整语义。所以 `.niceeval/` 适合做本地事实根,不默认适合直接提交进 Git。发布前用下面的 `copySnapshots` 做 artifact 选择和整文件大小检查。
112
+
113
+ 截断发生在持久化边界,不能替 agent runtime 限制发给模型的工具输出。如果 runtime 先把 50 MB 工具结果完整塞进模型请求并收到 413,NiceEval 仍会把 Attempt 记为 `errored`;这里只保证失败后的 events / trace 不再被同一段输出撑爆。
114
+
86
115
  ## 版本:谁写的、读不读得了
87
116
 
88
117
  每个快照都带自己的出身:`producer` 是写这份结果的工具与版本(niceeval 自己,或经写入面转换的第三方 harness),`schemaVersion` 是磁盘格式版本。格式只在破坏兼容时递增版本,读取器只认相同版本——**版本不兼容的落盘不解析、不迁移、不猜**,整个 run 进 `skipped`:
@@ -157,7 +186,7 @@ for (const exp of results.experiments) {
157
186
 
158
187
  即使在这条最深的路径上也不碰磁盘布局:路径拼接、存在性、版本过滤、快照切分全被库消化。要把这份分布摆进报告页,用 [`defineComponent`](/zh/guides/custom-reports) 包一个双面组件即可。
159
188
 
160
- 一条跨快照累计时的义务:`--resume` 会把上一轮已通过、fingerprint 匹配的结果携带合入新快照,同一个 attempt 因此可能存在于多份落盘。携带条目不是空壳:它带着原快照的 `startedAt`(身份锚)与 `artifactBase`(指向原快照 attempt 目录的相对路径),懒加载按候选顺序回退——先本快照的 attempt 目录,再 `artifactBase` 指向的原快照目录(原快照被清理后如实返回 `null`);`ref` 指向条目所在的落盘,即携带入的那份新快照。身份键 `(experimentId, evalId, attempt, startedAt)` 的四个字段都在数据上——前两个是 attempt 的直达字段,序号与 `startedAt` 在 `attempt.result` 上。reader 忠实反映这份重复;跨快照聚合前用 `dedupeAttempts` 按身份键去重,重复保留最新快照里的那份——报告积木的计算函数内置这条,自己写脚本时记得过一遍:
189
+ 一条跨快照累计时的义务:NiceEval 默认把上一轮已有确定判定(passed / failed)、且 eval 代码和配置没变的结果携带合入新快照(`--force` 全部重跑),同一个 attempt 因此可能存在于多份落盘。携带条目不是空壳:它带着原快照的 `startedAt`(身份锚)与 `artifactBase`(指向原快照 attempt 目录的相对路径),懒加载按候选顺序回退——先本快照的 attempt 目录,再 `artifactBase` 指向的原快照目录(原快照被清理后如实返回 `null`);`ref` 指向条目所在的落盘,即携带入的那份新快照。身份键 `(experimentId, evalId, attempt, startedAt)` 的四个字段都在数据上——前两个是 attempt 的直达字段,序号与 `startedAt` 在 `attempt.result` 上。reader 忠实反映这份重复;跨快照聚合前用 `dedupeAttempts` 按身份键去重,重复保留最新快照里的那份——报告积木的计算函数内置这条,自己写脚本时记得过一遍:
161
190
 
162
191
  ```typescript
163
192
  import { dedupeAttempts } from "niceeval/results";
@@ -185,7 +214,7 @@ const snap = await writer.snapshot({ // 建快照目录(独占创建,撞名换
185
214
  });
186
215
 
187
216
  for (const r of convertedResults) {
188
- await snap.writeAttempt(r.result, { // 写 result.json(判决权威落点,一次写成)+ 拆 artifact 文件
217
+ await snap.writeAttempt(r.result, { // 写 result.json(判定权威落点,一次写成)+ 拆 artifact 文件
189
218
  events: r.events, // 第二参 = artifact,都可选;缺哪样读取面就懒加载出 null
190
219
  diff: r.diff,
191
220
  }); // 拆 artifact 文件、算目录、回填引用,全在库内发生
@@ -194,7 +223,7 @@ for (const r of convertedResults) {
194
223
  await writer.finish(); // 给每个快照补 completedAt,没有任何收尾聚合
195
224
  ```
196
225
 
197
- `writer.snapshot()` 就是读取面「实验 → 快照」层次的镜像:转多个 experiment 就开多个快照目录,experimentId / agent / model / startedAt 这些快照级元数据在这里声明一次,不用塞进每条 attempt;可选的 `knownEvalIds`(该实验已知的 eval 并集)也在这里声明——它是残缺检测的分母,转换只覆盖部分题目时如实交代全集,下游的覆盖警告就能算出来(`copySnapshots` 发布时会自动补记这个字段,见下文)。转完的目录就是标准结果目录:`niceeval show` / `niceeval view` 直接能看,报告积木直接能算,不用抄格式文档;`producer` 会原样出现在读取面的 `snap.producer` 上。**每个文件恰好写入一次**是写入面的核心承诺:`snapshot.json` 开跑即写、收尾只补 `completedAt`;`result.json` 与 artifact 随 attempt 完成落盘。进程中断只丢未完成的 attempt,已完成的判决与 artifact 已经在盘上——真正「有 attempt 落盘却没有 `snapshot.json`」的极端情况才归 `skipped("incomplete")`,未收尾但元数据齐全的快照能正常读,只带一条警告。
226
+ `writer.snapshot()` 就是读取面「实验 → 快照」层次的镜像:转多个 experiment 就开多个快照目录,experimentId / agent / model / startedAt 这些快照级元数据在这里声明一次,不用塞进每条 attempt;可选的 `knownEvalIds`(该实验已知的 eval 并集)也在这里声明——它是残缺检测的分母,转换只覆盖部分题目时如实交代全集,下游的覆盖警告就能算出来(`copySnapshots` 发布时会自动补记这个字段,见下文)。转完的目录就是标准结果目录:`niceeval show` / `niceeval view` 直接能看,报告积木直接能算,不用抄格式文档;`producer` 会原样出现在读取面的 `snap.producer` 上。**每个文件恰好写入一次**是写入面的核心承诺:`snapshot.json` 开跑即写、收尾只补 `completedAt`;`result.json` 与 artifact 随 attempt 完成落盘。进程中断只丢未完成的 attempt,已完成的判定与 artifact 已经在盘上——真正「有 attempt 落盘却没有 `snapshot.json`」的极端情况才归 `skipped("incomplete")`,未收尾但元数据齐全的快照能正常读,只带一条警告。
198
227
 
199
228
  ## 发布:`copySnapshots`
200
229
 
@@ -205,14 +234,19 @@ import { openResults, copySnapshots } from "niceeval/results";
205
234
 
206
235
  const results = await openResults(".niceeval");
207
236
  await copySnapshots(results.latest(), "site-data/run", {
208
- artifacts: ["sources", "events", "trace", "o11y"], // diff 可达百 MB,发布时常见地不带;
209
- }); // o11y 只有几 KB,报告用到 turns 这类
237
+ artifacts: ["sources", "events", "trace", "o11y"], // diff 不截断,缺省也不带;
238
+ redact: (text) => text.replaceAll(/sk-[A-Za-z0-9]+/g, "[redacted]"),
239
+ }); // redact 必填:函数消毒,或 false 显式声明原文发布;
240
+ // 每个待发布文件还会经过 50 MiB 预检;
241
+ // o11y 只有几 KB,报告用到 turns 这类
210
242
  // 读 o11y 的指标就把它带上,不然渲染成「—」
211
243
  ```
212
244
 
213
- 第一个参数收 `Selection` 或手工挑的 `Snapshot[]`——和报告积木同一个输入约定。`artifacts` 的合法值是 `"events" | "trace" | "o11y" | "diff" | "sources"`,缺省全带。目标目录已存在且非空时报错,不静默覆盖——发布脚本要幂等就自己先清目标目录。
245
+ 第一个参数收 `Selection` 或手工挑的 `Snapshot[]`——和报告积木同一个输入约定。`artifacts` 的合法值是 `"events" | "trace" | "o11y" | "agentSetup" | "diff" | "sources"`;缺省带除 `diff` 外的五类。目标目录已存在且非空时报错,不静默覆盖——发布脚本要幂等就自己先清目标目录。
246
+
247
+ 复制开始前,NiceEval 会规划全部目标文件并检查序列化后的大小。任一文件超过固定的 50 MiB,整次复制在创建目标目录前失败,错误会列出路径、实际大小和处理建议。你可以从 `artifacts` 排除那类证据;如果是旧版本留下的超大 events / trace,用当前版本重跑后再发布。这个检查既覆盖没有逐值截断的源码 / diff,也覆盖单值都正常但累计过大的 JSON,避免直到 `git push` 才撞上 Git host 的单文件限制。
214
248
 
215
- 复制不改 artifact 内容、不消毒;发布前给自由文本消毒用报告积木 `AttemptList.data` 的 `redact` 钩子。唯一随行补记的是挑选时的**覆盖事实**:`partial-coverage` 警告的分母是实验的历史并集,而发布目录没有历史——所以每个复制出的快照带上 `knownEvalIds`(复制时刻该实验已知的 eval 并集),reader 端把它并进 `exp.evalIds` 的计算(取本地历史与快照携带值的并集)。发布目录上重新 `openResults().latest()`,残缺警告被同一套机制重新算出来,不靠发布者转述。复制出的目录就是标准结果目录,`niceeval view --run <目录>` 直接能看;要让报告站随 push 自动更新,workflow 见[通过 CI 发布报告](/zh/guides/publish-report)。
249
+ 大小预检只决定整次复制成功或失败,不会从一个超大文件中间删内容。消毒不是可选项——`copySnapshots` 要求显式传 `redact`:给一个函数就改写复制出来的所有文件里的自由文本(events、trace、源码、diff、运行摘要都在内;id、事件类型这类标识字段不动),确定这批数据可以原文公开就传 `redact: false`,两个都不传会直接报错。注意报告积木 `AttemptList.data` 的 `redact` 只影响页面上显示的数据,管不到发布目录里的 artifact 文件;发布场景一律在 `copySnapshots` 这一步消毒。唯一随行补记的是挑选时的**覆盖事实**:`partial-coverage` 警告的分母是实验的历史并集,而发布目录没有历史——所以每个复制出的快照带上 `knownEvalIds`(复制时刻该实验已知的 eval 并集),reader 端把它并进 `exp.evalIds` 的计算(取本地历史与快照携带值的并集)。发布目录上重新 `openResults().latest()`,残缺警告被同一套机制重新算出来,不靠发布者转述。复制出的目录就是标准结果目录,`niceeval view --run <目录>` 直接能看;要让报告站随 push 自动更新,workflow 见[通过 CI 发布报告](/zh/guides/publish-report)。
216
250
 
217
251
  ## 分层速览
218
252
 
@@ -42,10 +42,11 @@ npx niceeval exp local --max-concurrency 8
42
42
  ## runs 与 early-exit
43
43
 
44
44
  ```bash
45
- npx niceeval exp local fixtures/button --runs 5 --early-exit
45
+ npx niceeval exp local fixtures/button --runs 5
46
+ npx niceeval exp local fixtures/button --runs 5 --no-early-exit
46
47
  ```
47
48
 
48
- `runs` 用于测 pass rate。`early-exit` 会在某个 attempt 通过后停止同一 eval 的剩余尝试。
49
+ `runs` 用于测 pass rate。首过即停默认开启:某个 Attempt 通过后,同一 eval 的剩余 Attempt 会被停止。想拿完整的通过率分布时,用 `--no-early-exit` 关闭,让每个 eval 跑满 `runs` 次。
49
50
 
50
51
  ## 缓存
51
52
 
@@ -57,13 +58,22 @@ npx niceeval exp local fixtures/button --runs 5 --early-exit
57
58
  npx niceeval exp local --timeout 300000 --budget 5
58
59
  ```
59
60
 
60
- 超时保护单个 eval,预算保护整次运行成本。
61
+ 超时保护单个 eval,预算保护整次运行成本。NiceEval 只有在 Attempt 已经发起 Agent Turn、却连续拿不到成本数据时,才提示预算无法执行。如果 Attempt 在 `sandbox.create` 或 setup 阶段就失败,Agent 尚未运行,CLI 只显示对应的结构化执行错误,不再追加容易误导排查方向的预算警告。
61
62
 
62
63
  ## Reporter
63
64
 
64
- runner eval 完成后把结果交给 reporters:
65
+ 运行中的反馈由 `--output` 选择消费者模型;Reporter 负责把完成后的结果写到其它目的地。两者不是同一层:
66
+
67
+ ```bash
68
+ npx niceeval exp local --output human # 人:TTY dashboard + 永久错误/诊断
69
+ npx niceeval exp local --output agent # AI:稳定 envelope + locator handoff
70
+ npx niceeval exp local --output ci # CI:单一有序 stdout 事件流
71
+ ```
72
+
73
+ 省略时使用 `auto`:TTY 选择 Human,CI 环境选择 CI,其它非 TTY 选择 Agent。输出模型只改变反馈,不改变调度、判定或 artifact。
74
+
75
+ Runner 在 Eval 完成后把结果交给 Reporters:
65
76
 
66
- - console reporter 提供实时反馈。
67
77
  - JSON artifacts 用于后续分析。
68
78
  - JUnit reporter 适合 CI。
69
79
  - Braintrust reporter 把一次运行作为实验上报,跨提交比较。
@@ -72,11 +82,11 @@ runner 在 eval 完成后把结果交给 reporters:
72
82
 
73
83
  ## 输出目录
74
84
 
75
- 每次运行会写入该实验的结果快照目录 `.niceeval/<experiment>/<快照>/`,包括快照级 `snapshot.json`,以及每个 attempt 的 `result.json`(判决与断言)和按需生成的 `events.json`、`sources.json`、`trace.json`、`o11y.json`、`diff.json` 等拆分 artifact
85
+ 每次运行会写入该实验的结果快照目录 `.niceeval/<experiment>/<快照>/`,包括快照级 `snapshot.json`,以及每个 Attempt 的 `result.json`(判定、断言、结构化错误、diagnostics)和按需生成的 `events.json`、`sources.json`、`trace.json`、`o11y.json`、`diff.json` 等拆分 artifact。瞬时 progress 不落盘。
76
86
 
77
87
  ## 推荐调试流程
78
88
 
79
89
  1. 先跑 `npx niceeval list` 确认发现结果。
80
90
  2. 用 `npx niceeval exp <实验> <ID 前缀>` 缩小到一个 eval。
81
- 3. 失败后运行 `npx niceeval view` 查看 transcript diff
91
+ 3. 失败后复制终端里的 locator,先运行 `npx niceeval show @<locator>` 看错误或断言摘要;需要 Agent 行为时再加 `--execution`,需要文件变化时加 `--diff`。
82
92
  4. 再扩大到完整 suite 或 experiment。
@@ -41,14 +41,31 @@ export default defineExperiment({
41
41
  export ANTHROPIC_API_KEY=sk-ant-...
42
42
  npx niceeval exp local fixtures/button
43
43
 
44
- npx niceeval exp local fixtures/button --runs 10 --early-exit
44
+ npx niceeval exp local fixtures/button --runs 10
45
45
  ```
46
46
 
47
47
  <Note>
48
- 没有对应的 CLI flag——后端选择完全写在代码里。如果 experiment 和 `niceeval.config.ts` 都没设置 `sandbox`,[NiceEval](https://niceeval.com/) 在创建 sandbox 时会直接报错,不会自动探测。
48
+ 没有对应的 CLI flag——provider 选择完全写在代码里。如果 experiment 和 `niceeval.config.ts` 都没设置 `sandbox`,[NiceEval](https://niceeval.com/) 在创建 sandbox 时会直接报错,不会自动探测。
49
49
  </Note>
50
50
 
51
- 内置 agent `niceeval/adapter` 导出的是工厂函数。需要配置鉴权、代理、MCP GitHub skill 时,把这些写进工厂参数;模型仍然写在 experiment 的 `model` 字段,sandbox 后端仍然写在 `sandbox` 字段:
51
+ 云端跑 coding agent 时,可以直接使用 NiceEval 已发布的 E2B 公共模板,避免每个 Attempt 安装 CLI:
52
+
53
+ ```ts
54
+ import { codexAgent } from "niceeval/adapter";
55
+ import { e2bSandbox } from "niceeval/sandbox";
56
+
57
+ export default defineExperiment({
58
+ agent: codexAgent(),
59
+ model: "gpt-5.4",
60
+ sandbox: e2bSandbox({
61
+ template: "correctroads-default-team/niceeval-codex:v0.6.1",
62
+ }),
63
+ });
64
+ ```
65
+
66
+ Claude Code 使用 `correctroads-default-team/niceeval-claude-code:v0.6.1`,Bub 使用 `correctroads-default-team/niceeval-bub:v0.6.1`。版本 tag 适合 CI;省略 tag 会跟随当前稳定构建。如何继续增加系统包、二进制或模型缓存,见 [沙箱 provider · 从官方基线继续构建以提速](/zh/guides/sandbox-providers#从官方基线继续构建以提速)。
67
+
68
+ 内置 agent 从 `niceeval/adapter` 导出的是工厂函数。需要配置鉴权、代理、MCP 或 GitHub skill 时,把这些写进工厂参数;模型仍然写在 experiment 的 `model` 字段,sandbox provider 仍然写在 `sandbox` 字段:
52
69
 
53
70
  ```ts
54
71
  import { defineExperiment } from "niceeval";
@@ -87,12 +104,13 @@ export default defineExperiment({
87
104
  ```text
88
105
  createSandbox
89
106
  → sandbox spec 的 .setup() 钩子? # 环境准备(按实验装东西);没挂就跳过
90
- git init && git commit
107
+ eval setup? # 这条 eval 的任务夹具(如果定义了)
91
108
  → adapter.setup? # 装 CLI / 写 agent 配置
92
109
  → test(t): uploadDirectory(...) # 写入这条 eval 的起始文件
93
- → adapter.send(input, ctx)
110
+ → adapter.send(input, ctx) # agent 在这一步改动的文件才进 diff
94
111
  → test(t): runCommand(...) # 手工运行验证命令
95
- collectGeneratedFiles() # git diff HEAD
112
+ 汇总 agent 改动的文件 # t.sandbox.diff / fileChanged 使用
113
+ → 评分与判定
96
114
  → adapter.teardown? # agent 收尾
97
115
  → sandbox spec 的 .teardown() 钩子? # 环境收尾(如回存状态),销毁前最后跑
98
116
  → sandbox.stop()
@@ -100,7 +118,9 @@ createSandbox
100
118
 
101
119
  起始文件和验证命令都写在 `test(t)` 中。agent 执行阶段只能看到你已经写进 sandbox 的文件。
102
120
 
103
- 环境钩子(`.setup()` / `.teardown()`)挂在 experiment `sandbox` 字段的 spec 上,用来做"按实验变化的环境准备"——装某个实验专属的二进制、预热、跨 attempt 载入和回存状态。它排在 git 基线之前,所以写下的文件不会被算进 agent 产出的 diff。写法和规则见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-providers#环境钩子)。
121
+ `t.sandbox.diff` `t.sandbox.fileChanged()` 只包含 **agent 在 `t.send()` 期间改动的文件**:NiceEval 在每次 `t.send()` 前后记录一次工作区状态,把中间的变化记在 agent 名下。你上传的起始文件、`t.send()` 之后写入的验证材料都不会混进来,所以 `fileChanged("src/app.ts")` 只在 agent 真的动过这个文件时通过。
122
+
123
+ 环境钩子(`.setup()` / `.teardown()`)挂在 experiment `sandbox` 字段的 spec 上,用来做"按实验变化的环境准备"——装某个实验专属的二进制、预热、跨 attempt 载入和回存状态。它写下的文件属于环境,不会被算进 agent 产出的 diff。写法和规则见 [沙箱 provider · 环境钩子](/zh/guides/sandbox-providers#环境钩子)。
104
124
 
105
125
  ## 自定义 sandbox agent
106
126
 
@@ -109,9 +129,21 @@ import { defineSandboxAgent } from "niceeval/adapter";
109
129
 
110
130
  export default defineSandboxAgent({
111
131
  name: "my-agent",
132
+ async setup(sandbox, ctx) {
133
+ ctx.progress({ message: "检查 my-agent 安装" });
134
+ await sandbox.runCommand("npm", ["install", "-g", "my-agent"]);
135
+ },
112
136
  async send(input, ctx) {
137
+ ctx.progress({ message: "运行 my-agent CLI" });
113
138
  await ctx.sandbox.runCommand("my-agent", ["run", input.text, "--json-out", "agent-events.json"]);
114
139
  const transcript = await ctx.sandbox.readFile("agent-events.json");
140
+ if (transcript.trim() === "") {
141
+ ctx.diagnostic({
142
+ code: "empty-transcript",
143
+ level: "warning",
144
+ message: "my-agent 没有写出 transcript,工具断言可能缺少证据",
145
+ });
146
+ }
115
147
  return {
116
148
  status: "completed",
117
149
  events: parseTranscript(transcript),
@@ -120,6 +152,24 @@ export default defineSandboxAgent({
120
152
  });
121
153
  ```
122
154
 
155
+ `setup`、每次 `send` 和 `teardown` 都拿到各自作用域的反馈方法:
156
+
157
+ - `ctx.progress({ message, current?, total? })` 更新当前短期状态,适合安装 CLI、运行 Turn、读取 transcript;不要逐 token 或逐 JSONL frame 调用。
158
+ - `ctx.diagnostic({ code, level, message, data?, dedupeKey? })` 保存协议退化、transcript 缺失和清理问题。它会进入终端永久事件和 `result.json`。
159
+ - 无法继续运行时抛出异常,runner 会把 Attempt 标为 `errored`,并保存发生阶段、错误码、message、cause 和 stack。
160
+
161
+ 不要从 Adapter 直接调用 `console.log/error` 或写 `process.stdout/stderr`。它们会打散 Human dashboard,也会破坏 CI 日志顺序。
162
+
163
+ 运行中看到 Sandbox 或 Adapter 错误时,终端会给出 Attempt locator:
164
+
165
+ ```text
166
+ ✗ @12h8m4k1 fixtures/button [local] errored · agent setup
167
+ agent-install-failed: npm install my-agent exited with code 1
168
+ Inspect: niceeval show @12h8m4k1
169
+ ```
170
+
171
+ 运行 `niceeval show @12h8m4k1` 可查看结构化错误、diagnostics 和已完成的生命周期阶段;`--timing` 给有界诊断时间树(排队、Sandbox 启动、setup/teardown hook 里的 shell、Agent CLI 安装与启动命令、每轮 send、可关联的 OTel model/tool、收尾),可直接看出错误或超时发生在哪一层、之前的时间花在哪里。树超过 80 个细节节点时会保留失败、慢点和首尾样本并提示省略数量;需要逐节点审计时使用 `--timing=full`。`--execution` 则以事件为骨架查看 agent 做了什么,有 OTel 时只把时间贴到能唯一关联的事件旁。Sandbox 创建失败可能发生在 telemetry 建立前,所以错误回顾不依赖 trace。
172
+
123
173
  ## `ctx.model` 与 `ctx.flags`
124
174
 
125
175
  experiment 声明的 model 和 flags 会出现在 adapter context 中。adapter 可以决定如何把它们转成 CLI 参数或 HTTP payload。