niceeval 0.6.1 → 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 (296) hide show
  1. package/dist/agents/types.d.ts +67 -5
  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 -1
  9. package/dist/report/built-ins/experiment-comparison.js +116 -10
  10. package/dist/report/built-ins/index.d.ts +1 -0
  11. package/dist/report/built-ins/index.js +1 -1
  12. package/dist/report/components.d.ts +8 -2
  13. package/dist/report/components.js +3 -3
  14. package/dist/report/compute.d.ts +11 -18
  15. package/dist/report/compute.js +54 -34
  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 -8
  19. package/dist/report/format.js +27 -12
  20. package/dist/report/index.d.ts +4 -3
  21. package/dist/report/index.js +5 -4
  22. package/dist/report/locale.d.ts +11 -2
  23. package/dist/report/locale.js +23 -5
  24. package/dist/report/metrics.d.ts +13 -1
  25. package/dist/report/metrics.js +65 -14
  26. package/dist/report/primitives.d.ts +6 -0
  27. package/dist/report/react/AttemptList.d.ts +2 -2
  28. package/dist/report/react/AttemptList.js +5 -6
  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 +2 -1
  34. package/dist/report/react/ExperimentList.js +8 -10
  35. package/dist/report/react/MetricScatter.js +5 -11
  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 +21 -14
  40. package/dist/report/report.d.ts +5 -1
  41. package/dist/report/report.js +6 -2
  42. package/dist/report/text/faces.d.ts +1 -1
  43. package/dist/report/text/faces.js +42 -41
  44. package/dist/report/text/table.js +36 -5
  45. package/dist/report/types.d.ts +39 -21
  46. package/dist/results/types.d.ts +11 -0
  47. package/dist/runner/feedback/sink.d.ts +110 -0
  48. package/dist/runner/types.d.ts +513 -22
  49. package/dist/sandbox/docker.d.ts +23 -2
  50. package/dist/sandbox/e2b.d.ts +15 -1
  51. package/dist/sandbox/errors.d.ts +30 -3
  52. package/dist/sandbox/io-retry.d.ts +17 -0
  53. package/dist/sandbox/registry.d.ts +2 -0
  54. package/dist/sandbox/resolve.d.ts +18 -5
  55. package/dist/sandbox/retry.d.ts +11 -1
  56. package/dist/sandbox/types.d.ts +39 -5
  57. package/dist/sandbox/vercel.d.ts +7 -1
  58. package/dist/scoring/coverage.d.ts +30 -0
  59. package/dist/scoring/display.d.ts +21 -0
  60. package/dist/scoring/display.js +120 -0
  61. package/dist/scoring/types.d.ts +103 -20
  62. package/dist/shared/aggregate.d.ts +1 -0
  63. package/dist/shared/aggregate.js +3 -3
  64. package/dist/shared/types.d.ts +28 -0
  65. package/dist/tty-line.d.ts +0 -4
  66. package/dist/util.d.ts +23 -0
  67. package/docs-site/zh/concepts/adapter.mdx +22 -4
  68. package/docs-site/zh/concepts/experiment.mdx +1 -1
  69. package/docs-site/zh/concepts/overview.mdx +6 -6
  70. package/docs-site/zh/guides/agent-feedback-loop.mdx +28 -26
  71. package/docs-site/zh/guides/authoring.mdx +33 -0
  72. package/docs-site/zh/guides/ci-integration.mdx +23 -12
  73. package/docs-site/zh/guides/connect-your-agent.mdx +29 -3
  74. package/docs-site/zh/guides/custom-reports.mdx +29 -34
  75. package/docs-site/zh/guides/dataset-fanout.mdx +25 -3
  76. package/docs-site/zh/guides/debug-sandbox.mdx +57 -0
  77. package/docs-site/zh/guides/debugging.mdx +210 -0
  78. package/docs-site/zh/guides/experiments.mdx +10 -3
  79. package/docs-site/zh/guides/official-adapters.mdx +26 -2
  80. package/docs-site/zh/guides/publish-report.mdx +30 -16
  81. package/docs-site/zh/guides/report-components.mdx +42 -30
  82. package/docs-site/zh/guides/reporters.mdx +2 -2
  83. package/docs-site/zh/guides/results-data.mdx +17 -9
  84. package/docs-site/zh/guides/runner.mdx +17 -7
  85. package/docs-site/zh/guides/sandbox-agent.mdx +56 -7
  86. package/docs-site/zh/guides/sandbox-providers.mdx +257 -9
  87. package/docs-site/zh/guides/scoring-guide.mdx +4 -4
  88. package/docs-site/zh/guides/viewing-results.mdx +79 -36
  89. package/docs-site/zh/guides/write-experiment.mdx +5 -3
  90. package/docs-site/zh/guides/write-send.mdx +17 -1
  91. package/docs-site/zh/index.mdx +1 -1
  92. package/docs-site/zh/reference/builtin-agents.mdx +27 -0
  93. package/docs-site/zh/reference/capabilities.mdx +2 -2
  94. package/docs-site/zh/reference/cli.mdx +33 -7
  95. package/docs-site/zh/reference/define-agent.mdx +57 -4
  96. package/docs-site/zh/reference/define-config.mdx +1 -1
  97. package/docs-site/zh/reference/define-eval.mdx +42 -9
  98. package/docs-site/zh/reference/expect.mdx +26 -1
  99. package/package.json +5 -1
  100. package/src/agents/ai-sdk-otel.test.ts +1 -0
  101. package/src/agents/ai-sdk.test.ts +3 -0
  102. package/src/agents/ai-sdk.ts +3 -0
  103. package/src/agents/bub-install-spec.test.ts +34 -0
  104. package/src/agents/bub-install-spec.ts +32 -0
  105. package/src/agents/bub.ts +31 -32
  106. package/src/agents/claude-code.test.ts +130 -9
  107. package/src/agents/claude-code.ts +76 -4
  108. package/src/agents/codex.test.ts +189 -40
  109. package/src/agents/codex.ts +155 -14
  110. package/src/agents/coding-cli-versions.test.ts +15 -0
  111. package/src/agents/coding-cli-versions.ts +3 -0
  112. package/src/agents/index.ts +11 -0
  113. package/src/agents/langgraph.test.ts +204 -0
  114. package/src/agents/langgraph.ts +495 -0
  115. package/src/agents/marketplace.ts +85 -0
  116. package/src/agents/native-config.test.ts +179 -0
  117. package/src/agents/native-config.ts +267 -0
  118. package/src/agents/openai-compat.test.ts +1 -0
  119. package/src/agents/openclaw.test.ts +31 -0
  120. package/src/agents/openclaw.ts +171 -0
  121. package/src/agents/plugin-config.test.ts +1 -0
  122. package/src/agents/sdk-streams.test.ts +79 -0
  123. package/src/agents/sdk-streams.ts +55 -10
  124. package/src/agents/skills.test.ts +1 -0
  125. package/src/agents/streaming.test.ts +3 -9
  126. package/src/agents/types.ts +68 -5
  127. package/src/agents/ui-message-stream.test.ts +3 -0
  128. package/src/cli.ts +411 -108
  129. package/src/context/context.test.ts +51 -12
  130. package/src/context/context.ts +161 -29
  131. package/src/context/session.test.ts +1 -0
  132. package/src/context/session.ts +114 -6
  133. package/src/context/types.ts +30 -12
  134. package/src/define.test.ts +13 -8
  135. package/src/define.ts +25 -4
  136. package/src/expect/index.ts +53 -23
  137. package/src/i18n/en.ts +64 -2
  138. package/src/i18n/zh-CN.ts +65 -3
  139. package/src/o11y/cost.test.ts +1 -0
  140. package/src/o11y/execution-tree.test.ts +1 -20
  141. package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
  142. package/src/o11y/otlp/parse.test.ts +1 -0
  143. package/src/o11y/otlp/turn-otel.test.ts +1 -0
  144. package/src/o11y/parsers/bub.test.ts +1 -0
  145. package/src/o11y/parsers/claude-code.test.ts +1 -34
  146. package/src/o11y/parsers/openclaw.test.ts +154 -0
  147. package/src/o11y/parsers/openclaw.ts +310 -0
  148. package/src/o11y/prices.json +746 -311
  149. package/src/o11y/tool-names.test.ts +1 -0
  150. package/src/o11y/types.ts +16 -2
  151. package/src/report/aggregate.ts +34 -5
  152. package/src/report/built-in-user-parity.test.tsx +110 -153
  153. package/src/report/built-ins/experiment-comparison.tsx +173 -13
  154. package/src/report/built-ins/index.ts +6 -1
  155. package/src/report/components.tsx +9 -3
  156. package/src/report/compute.ts +70 -40
  157. package/src/report/dual-render.test.tsx +194 -67
  158. package/src/report/flag.ts +30 -2
  159. package/src/report/format.ts +35 -11
  160. package/src/report/index.ts +22 -4
  161. package/src/report/locale.ts +25 -5
  162. package/src/report/metrics.ts +67 -14
  163. package/src/report/primitives.tsx +6 -0
  164. package/src/report/react/AttemptList.tsx +6 -31
  165. package/src/report/react/EvalList.tsx +0 -0
  166. package/src/report/react/ExperimentComparison.tsx +68 -0
  167. package/src/report/react/ExperimentList.tsx +15 -9
  168. package/src/report/react/MetricScatter.tsx +12 -14
  169. package/src/report/react/chart-math.test.ts +85 -0
  170. package/src/report/react/chart-math.ts +101 -22
  171. package/src/report/react/enhance.js +33 -1
  172. package/src/report/react/fixtures.ts +24 -17
  173. package/src/report/react/render.test.tsx +9 -64
  174. package/src/report/react/styles.css +73 -2
  175. package/src/report/report.test.ts +306 -98
  176. package/src/report/report.ts +6 -2
  177. package/src/report/text/faces.ts +47 -43
  178. package/src/report/text/table.ts +42 -5
  179. package/src/report/types.ts +41 -21
  180. package/src/results/annotated-source.test.ts +62 -9
  181. package/src/results/annotated-source.ts +64 -6
  182. package/src/results/attempt-evidence.test.ts +9 -7
  183. package/src/results/attempt-evidence.ts +15 -8
  184. package/src/results/attempt-source.ts +6 -3
  185. package/src/results/copy.ts +145 -55
  186. package/src/results/host-equivalence.test.ts +8 -6
  187. package/src/results/index.ts +2 -0
  188. package/src/results/locator.test.ts +1 -22
  189. package/src/results/open.ts +7 -1
  190. package/src/results/publish.ts +149 -0
  191. package/src/results/results.test.ts +85 -51
  192. package/src/results/truncate.ts +90 -0
  193. package/src/results/types.ts +7 -0
  194. package/src/results/writer.ts +31 -13
  195. package/src/runner/attempt.test.ts +138 -7
  196. package/src/runner/attempt.ts +603 -104
  197. package/src/runner/discover.test.ts +47 -0
  198. package/src/runner/discover.ts +36 -2
  199. package/src/runner/eval-source.test.ts +1 -27
  200. package/src/runner/feedback/agent.test.ts +504 -0
  201. package/src/runner/feedback/agent.ts +409 -0
  202. package/src/runner/feedback/ci.test.ts +562 -0
  203. package/src/runner/feedback/ci.ts +401 -0
  204. package/src/runner/feedback/coordinator.test.ts +317 -0
  205. package/src/runner/feedback/coordinator.ts +397 -0
  206. package/src/runner/feedback/failure.ts +40 -0
  207. package/src/runner/feedback/human.test.ts +616 -0
  208. package/src/runner/feedback/human.ts +535 -0
  209. package/src/runner/feedback/index.ts +66 -0
  210. package/src/runner/feedback/io.ts +78 -0
  211. package/src/runner/feedback/profile.test.ts +50 -0
  212. package/src/runner/feedback/profile.ts +58 -0
  213. package/src/runner/feedback/reducer.test.ts +395 -0
  214. package/src/runner/feedback/reducer.ts +260 -0
  215. package/src/runner/feedback/renderer.ts +82 -0
  216. package/src/runner/feedback/sink.ts +203 -0
  217. package/src/runner/feedback/testing.ts +106 -0
  218. package/src/runner/ledger.test.ts +230 -0
  219. package/src/runner/ledger.ts +329 -0
  220. package/src/runner/report.test.ts +128 -3
  221. package/src/runner/report.ts +33 -9
  222. package/src/runner/reporters/artifacts.ts +8 -2
  223. package/src/runner/reporters/braintrust.test.ts +8 -7
  224. package/src/runner/reporters/braintrust.ts +9 -2
  225. package/src/runner/reporters/index.ts +2 -2
  226. package/src/runner/reporters/json.test.ts +162 -0
  227. package/src/runner/reporters/json.ts +35 -8
  228. package/src/runner/reporters/shared.ts +1 -5
  229. package/src/runner/run.test.ts +760 -3
  230. package/src/runner/run.ts +242 -36
  231. package/src/runner/sandbox-prep.ts +3 -42
  232. package/src/runner/timing.ts +158 -0
  233. package/src/runner/types.ts +518 -22
  234. package/src/sandbox/checkpoint.test.ts +55 -0
  235. package/src/sandbox/checkpoint.ts +29 -8
  236. package/src/sandbox/cli-commands.ts +407 -0
  237. package/src/sandbox/docker.ts +115 -16
  238. package/src/sandbox/e2b-agent-template.test.ts +56 -0
  239. package/src/sandbox/e2b-agent-template.ts +94 -0
  240. package/src/sandbox/e2b.ts +74 -9
  241. package/src/sandbox/errors.ts +111 -4
  242. package/src/sandbox/index.ts +2 -0
  243. package/src/sandbox/io-retry.test.ts +58 -0
  244. package/src/sandbox/io-retry.ts +45 -0
  245. package/src/sandbox/keep-registry.test.ts +86 -0
  246. package/src/sandbox/keep-registry.ts +142 -0
  247. package/src/sandbox/keep.ts +178 -0
  248. package/src/sandbox/paths.test.ts +1 -0
  249. package/src/sandbox/paths.ts +19 -8
  250. package/src/sandbox/registry.ts +20 -3
  251. package/src/sandbox/resolve.ts +76 -11
  252. package/src/sandbox/retry.test.ts +70 -0
  253. package/src/sandbox/retry.ts +46 -4
  254. package/src/sandbox/types.ts +44 -6
  255. package/src/sandbox/vercel.ts +43 -20
  256. package/src/scoring/collector.ts +60 -17
  257. package/src/scoring/coverage.ts +95 -0
  258. package/src/scoring/diff.ts +81 -0
  259. package/src/scoring/display.test.ts +121 -0
  260. package/src/scoring/display.ts +133 -0
  261. package/src/scoring/evidence.test.ts +189 -0
  262. package/src/scoring/judge.test.ts +142 -0
  263. package/src/scoring/judge.ts +15 -18
  264. package/src/scoring/scoped.ts +217 -50
  265. package/src/scoring/types.ts +117 -20
  266. package/src/scoring/verdict.ts +16 -4
  267. package/src/shared/aggregate.ts +3 -2
  268. package/src/shared/types.ts +31 -0
  269. package/src/show/compose.ts +2 -2
  270. package/src/show/index.ts +21 -1
  271. package/src/show/render.ts +619 -104
  272. package/src/show/show.test.ts +235 -19
  273. package/src/tty-line.ts +8 -26
  274. package/src/util.test.ts +1 -0
  275. package/src/util.ts +41 -0
  276. package/src/view/app/components/AttemptModal.tsx +153 -2
  277. package/src/view/app/components/CodeView.tsx +32 -11
  278. package/src/view/app/components/CopyControls.tsx +2 -2
  279. package/src/view/app/i18n.ts +6 -0
  280. package/src/view/app/lib/attempt-route.test.ts +1 -0
  281. package/src/view/app/lib/verdict.ts +7 -9
  282. package/src/view/artifact-serving.test.ts +2 -1
  283. package/src/view/client-dist/app.css +1 -1
  284. package/src/view/client-dist/app.js +17 -17
  285. package/src/view/data.test.ts +1 -0
  286. package/src/view/data.ts +11 -1
  287. package/src/view/index.ts +11 -0
  288. package/src/view/server.ts +2 -0
  289. package/src/view/styles.css +3 -0
  290. package/src/view/view-report.test.ts +6 -5
  291. package/src/runner/reporters/console.ts +0 -70
  292. package/src/runner/reporters/live.test.ts +0 -56
  293. package/src/runner/reporters/live.ts +0 -247
  294. package/src/runner/reporters/quiet.test.ts +0 -66
  295. package/src/runner/reporters/quiet.ts +0 -49
  296. package/src/runner/reporters/table.ts +0 -277
@@ -101,12 +101,12 @@ npx niceeval exp my-bot refund # 只跑 ID 以 refund 开头的
101
101
  npx niceeval view # 本地查看器里看结果
102
102
  ```
103
103
 
104
- **跑通的样子**:终端里每条 eval 一行判定,末尾汇总 `N passed, N failed`;`npx niceeval view` 里能看到每条 eval 逐轮的输入、事件和评分明细。
104
+ **跑通的样子**:人在终端运行时会看到一个动态 dashboard,完成和排队数量只在原位更新;失败、错误和 warning 会永久留在上方。结束时只打印摘要、失败 locator 和结果路径。`npx niceeval view` 里能看到每条 Eval 逐轮的输入、事件和评分明细。
105
105
 
106
106
  没跑通时,按报错的位置分三类排查:
107
107
 
108
108
  - **`fetch` 直接抛错**(连接被拒等):应用没起来,或 `send` 里的 URL 不对——先用 `curl` 对那个接口发一次同样的请求确认。
109
- - **`t.succeeded()` 没过、本轮判定是 failed**:请求发出去了但应用回了非 2xx。在 `send` 里用 `ctx.log()` 打印响应体,`niceeval view` 的本轮日志里能看到。
109
+ - **`t.succeeded()` 没过、本轮判定是 failed**:请求发出去了,但应用返回的 Turn `failed`。把协议中的失败映射到 `Turn.status` 或标准 `error` event;需要额外保留的有限上下文用 `ctx.diagnostic(...)`,不要打印完整响应体。
110
110
  - **只有内容断言没过**:接入本身已经通了——在 `view` 里对照 `t.reply` 的实际值,调断言或调应用。
111
111
 
112
112
  到这里接入已经完成:文本断言和 judge 评分都能用了。想要更多断言(工具、多轮、审批流),见文末的增量地图。
@@ -173,7 +173,33 @@ export default defineExperiment({
173
173
  | `flags` | experiment 的 `flags` | 原样转发(请求体、header 都行),应用按参数切换变体 |
174
174
  | `telemetry` | 配置了 OTel 接入时出现 | 只碰 `headers`:每轮新的 W3C `traceparent`,spread 进请求头。接收端点每次运行都一样,在 `defineConfig` 里固定、应用启动时指向它,不从 send 传——见 [OTel 接入](/zh/guides/connect-otel) |
175
175
  | `session` | 运行器(每条会话线一份) | 会话续接与 HITL 停轮现场的存取器都在它上面:`history()`、`id` / `capture()`、`hold()` / `take()`,见[写 send](/zh/guides/write-send) |
176
- | `log(msg)` | 运行器 | 写进本轮日志,`niceeval view` 里可见 |
176
+ | `progress(update)` | 运行器(绑定当前 `agent.run`) | 报告 Turn/tool 的短期状态;Human dashboard 可显示,结果不保存 |
177
+ | `diagnostic(input)` | 运行器(绑定当前 `agent.run`) | 保存协议退化、响应不完整等 warning/error;可由 locator 下钻回顾 |
178
+
179
+ ### Adapter 里的进度、诊断和致命错误
180
+
181
+ ```ts
182
+ async send(input, ctx) {
183
+ ctx.progress({ message: "等待上游模型" });
184
+ const response = await callAgent(input, { signal: ctx.signal });
185
+
186
+ if (response.eventsIncomplete) {
187
+ ctx.diagnostic({
188
+ code: "incomplete-event-stream",
189
+ level: "warning",
190
+ message: "上游响应缺少工具结果事件",
191
+ data: { requestId: response.requestId },
192
+ dedupeKey: `incomplete-event-stream:${response.requestId}`,
193
+ });
194
+ }
195
+
196
+ return toTurn(response);
197
+ }
198
+ ```
199
+
200
+ `progress` 是可覆盖的短期状态;`diagnostic` 是运行结束后仍能回顾的有界记录。两者都不能指定 phase 或输出流,也不会自动改变 `Turn.status` 或 Attempt 判定。连接失败、解析无法继续等基础设施错误应抛出异常;正常收到的被测 Agent 失败通过 `Turn.status: "failed"` 表达。
201
+
202
+ 终端只显示错误的一层摘要和 locator。完整 code、message、cause、stack 与 diagnostics 在 `result.json` 中,使用 `niceeval show @<locator>` 查看。OTel trace 只补充调用关系和耗时,不是错误记录的前提。
177
203
 
178
204
  同一个 agent 对着本地和生产分别跑,就是两个 experiment 文件、两份工厂参数:
179
205
 
@@ -8,19 +8,19 @@ description: "一份报告就是一个报告文件:官方宿主打开结果、
8
8
 
9
9
  一份报告就是一个报告文件。你不用打开结果目录、不用写渲染代码、不用起自己的应用:`niceeval show` / `niceeval view` 本体就是宿主——替你打开结果、把数据注入进来,你用官方组件和 `Row` / `Col` 摆版面,写完把文件路径递给 `--report`,终端和网页两扇门就都认它——官方的证据室深链、`--run` 历史切换、静态导出,自定义报告全部原样享有。
10
10
 
11
- ## view 的默认报告也是一份报告定义
11
+ ## show / view 的默认报告也是一份报告定义
12
12
 
13
- `niceeval view` 不传 `--report` 时渲染的默认报告不是私有实现,而是 `niceeval/report` 公开导出的一个值,也可显式传给 `niceeval show --report`:
13
+ `niceeval show` / `view` 不传 `--report` 时渲染的默认报告不是私有实现,而是 `niceeval/report` 公开导出的一个值:
14
14
 
15
15
  ```ts
16
- import { CostPassRateComparison } from "niceeval/report";
16
+ import { ExperimentComparison } from "niceeval/report";
17
17
  ```
18
18
 
19
- `niceeval view` 缺省选择 `CostPassRateComparison`:一张成本 × 通过率散点图加一张实验明细表,没有私有数据通道。裸 `niceeval show` 使用更适合终端的 Attempt 索引;显式自定义报告(包括这份 definition)仍可由两个宿主按同一双面协议渲染。
19
+ `ExperimentComparison` 先按 experiment id 的父目录切成可比组,再为每组分别计算成本 × 端到端成功率散点图和实验明细表。`compare/bub` `compare/codex` 可以同图,`dev-e2b/bub` 必须在另一个组;顶层 experiment 各自成为单例组。网页持有全部组并一次聚焦一组;终端命中多组时只列索引和单组查看命令,命中单组时才展开详情。两面都不会生成跨组总榜。
20
20
 
21
21
  | | 网页(人看) | 终端(agent 和你看) |
22
22
  | --- | --- | --- |
23
- | 官方默认 | 分析报告 | Attempt 索引 |
23
+ | 官方默认 | 分组比较报告(网页面) | 同一分组比较报告(文本面) |
24
24
  | 自定义摆法 | `niceeval view --report reports/exam.tsx` | `niceeval show --report reports/exam.tsx` |
25
25
 
26
26
  四个格子是同一个宿主。自定义只有三个层次,逐层深入:
@@ -39,16 +39,13 @@ import { CostPassRateComparison } from "niceeval/report";
39
39
  // reports/exam.tsx —— 一份定义,两扇门共用
40
40
  import {
41
41
  defineReport, Col, Section,
42
- MetricScatter, ExperimentList, EvalList, AttemptList, Scoreboard,
43
- costUSD, passRate,
42
+ ExperimentComparison, Scoreboard,
44
43
  } from "niceeval/report";
45
44
 
46
45
  export default defineReport(async ({ selection }) => {
47
- const experiments = await ExperimentList.data(selection);
48
46
  return (
49
47
  <Col>
50
- <MetricScatter selection={selection} points="experiment" series="agent" x={costUSD} y={passRate} />
51
- <ExperimentList items={experiments} />
48
+ <ExperimentComparison data={await ExperimentComparison.data(selection)} />
52
49
  <Section title="考试成绩单">
53
50
  <Scoreboard data={await Scoreboard.data(selection, { rows: "agent", subjects: "evalGroup" })} />
54
51
  </Section>
@@ -72,7 +69,7 @@ niceeval view --report reports/exam.tsx # 网页:同一棵树走网页面
72
69
 
73
70
  页面里的每个组件都是**双面**的:网页面是 React 渲染,终端面是字符渲染,两面吃同一份算好的数据。实体列表按 experiment → Eval → Attempt 展示事实;指标表、矩阵、条形图、成绩单、散点图、趋势图和差异表展示聚合值。完整清单见[报告组件](/zh/guides/report-components)。网页面的实体、格子和点深链到 Attempt 详情,终端面印出对应的 `niceeval show <eval id>` 下钻命令。
74
71
 
75
- 默认报告没有特权:它就是上面导出的那个值 `CostPassRateComparison`,正文是一张 `MetricScatter` 加一份 `ExperimentList`。你的报告和它同级;需要相同摆法时 import 同样的公开组件,不需要就不摆。
72
+ 默认报告没有特权:它就是上面导出的 `ExperimentComparison`。你的报告和它同级;需要同样的“按目录分组、组内比较”摆法就直接引用它,不需要就不摆。自己直接组合 `MetricScatter` / `ExperimentList` 时,通用组件只消费你传入的数据,不会自动分组;把跨组 Selection 传进去就表示你明确要做跨组分析。
76
73
 
77
74
  ## 排版:Row 与 Col 在两个面都成立
78
75
 
@@ -105,17 +102,17 @@ codex 78.0/100 40/50 38/50 │ codex 80% $0.51
105
102
 
106
103
  想把实验按目录前缀分组、每组顶部摆一块紧凑统计(通过率、experiment/eval 数、failed/errored、总成本、最后运行时间),用官方组件 `GroupSummary`。它的计算函数 `GroupSummary.data` 只吃一份已经收窄好的 Selection——不管你怎么分组,把对应组的 Selection 传进去,就是同一套折叠口径(eval 级折叠计票、null-safe 总成本、组内最后运行时间的最大值),不是另一套近似公式。
107
104
 
108
- 常见的分法是按 experiment id 的目录前缀(`compare/bub-low` 属于组 `compare`,顶层实验不属于任何组);用 `Selection.filter` 收窄出每组的 Selection,就能一组摆一块:
105
+ 常见的分法是按 experiment id 的目录前缀(`compare/bub-low` 属于组 `compare`,顶层实验以自己的完整 id 形成单例组);用 `Selection.filter` 收窄出每组的 Selection,就能一组摆一块:
109
106
 
110
107
  ```tsx
111
108
  // reports/groups.tsx —— 按 experiment id 前缀分组,每组顶部摆一块统计
112
109
  import { defineReport, Col, Section, GroupSummary } from "niceeval/report";
113
110
  import type { Snapshot } from "niceeval/report";
114
111
 
115
- // experiment id 的目录前缀当组名:顶层实验(id "/")不属于任何组。
116
- function groupOf(snapshot: Snapshot): string | undefined {
112
+ // experiment id 的完整父路径当组名;顶层 experiment 各自成为单例组。
113
+ function groupOf(snapshot: Snapshot): string {
117
114
  const parts = snapshot.experimentId.split("/");
118
- return parts.length > 1 ? parts.slice(0, -1).join("/") : undefined;
115
+ return parts.length > 1 ? parts.slice(0, -1).join("/") : snapshot.experimentId;
119
116
  }
120
117
 
121
118
  export default defineReport(async ({ selection }) => {
@@ -126,9 +123,7 @@ export default defineReport(async ({ selection }) => {
126
123
  groups.map(async (key) => {
127
124
  const scoped = selection.filter((s) => groupOf(s) === key);
128
125
  const summary = <GroupSummary data={await GroupSummary.data(scoped)} />;
129
- return key === undefined ? summary : (
130
- <Section key={key} title={key}>{summary}</Section>
131
- );
126
+ return <Section key={key} title={key}>{summary}</Section>;
132
127
  }),
133
128
  )}
134
129
  </Col>
@@ -153,7 +148,7 @@ latest 2026-07-01T11:30:00Z
153
148
  // reports/golf.tsx —— code-golf:谁写出能用的代码,谁写得短
154
149
  import {
155
150
  defineReport, Col, MetricTable,
156
- defineMetric, passRate, costUSD,
151
+ defineMetric, endToEndPassRate, costUSD,
157
152
  } from "niceeval/report";
158
153
 
159
154
  // 项目自己的口径:只比能用的代码的行数
@@ -166,8 +161,8 @@ const codeLines = defineMetric({
166
161
  async value(attempt) {
167
162
  const diff = await attempt.diff();
168
163
  if (!diff) return null;
169
- return Object.values(diff.generatedFiles)
170
- .reduce((n, src) => n + src.split("\n").length, 0);
164
+ return Object.keys(diff.files)
165
+ .reduce((sum, path) => sum + (diff.get(path) ?? "").split("\n").length, 0);
171
166
  },
172
167
  });
173
168
 
@@ -175,8 +170,8 @@ export default defineReport(async ({ selection }) => (
175
170
  <Col>
176
171
  <MetricTable data={await MetricTable.data(selection, {
177
172
  rows: "agent",
178
- columns: [passRate, codeLines, costUSD],
179
- sort: passRate,
173
+ columns: [endToEndPassRate, codeLines, costUSD],
174
+ sort: endToEndPassRate,
180
175
  })} />
181
176
  </Col>
182
177
  ));
@@ -195,7 +190,7 @@ codex 80% 355 lines $0.51
195
190
 
196
191
  `label` 可以是一份文案,也可以按语言给:`label: { en: "Code lines", "zh-CN": "代码行数" }`——查看器界面切语言时,按语言给的 label 跟着切;只给一份就两种语言都用它。指标算出来的数字本身不分语言。
197
192
 
198
- 内置指标里 `passRate` / `costUSD` / `durationMs` / `tokens` 只读 attempt 自带的判定、用量这些字段,任何一份结果目录都算得出。`turns`(agent 的总轮次)不一样,它读 `attempt.o11y()`——这份数据发布时不一定随行,`copySnapshots` 要显式把 `"o11y"` 加进 `artifacts` 才带得上(见[结果数据 API](/zh/guides/results-data)的「发布」一节),漏了就渲染成 `—`,不是 0。自己写的指标只要读了 `o11y()` / `diff()` 这类 artifact(就像上面 `codeLines` 读 `attempt.diff()`),发布前都要过一遍同样的检查。
193
+ 内置指标里 `endToEndPassRate` / `taskPassRate` / `executionReliability` / `costUSD` / `durationMs` / `tokens` 只读 Attempt 自带的判定、用量这些字段,任何一份结果目录都算得出。没有限定词的“成功率”使用 `endToEndPassRate`:passed 记 1,failed 和 errored 都记 0。`taskPassRate` 只在形成可信判定的样本上衡量答题质量,errored 不参与;展示它时应明确写“可判定任务通过率”,不能简称成功率。要区分答题质量和执行问题,把 `endToEndPassRate`、`taskPassRate`、`executionReliability` 三列并排。`turns`(Agent 的总轮次)不一样,它读 `attempt.o11y()`——这份数据 `copySnapshots` 缺省会随行,但如果发布脚本显式给了 `artifacts` 列表又没把 `"o11y"` 写进去,它就不在发布根里(见[结果数据 API](/zh/guides/results-data)的「发布」一节),指标渲染成 `—`,不是 0。自己写的指标只要读了 `o11y()` / `diff()` 这类 artifact(就像上面 `codeLines` 读 `attempt.diff()`),发布前都要过一遍同样的检查。
199
194
 
200
195
  ## 换分组:三种维度
201
196
 
@@ -221,7 +216,7 @@ const vendor: Dimension = {
221
216
 
222
217
  <MetricTable data={await MetricTable.data(selection, {
223
218
  rows: vendor,
224
- columns: [passRate, costUSD],
219
+ columns: [endToEndPassRate, costUSD],
225
220
  })} />
226
221
  ```
227
222
 
@@ -248,7 +243,7 @@ export default defineExperiment({
248
243
  <MetricLine data={await MetricLine.data(selection, {
249
244
  x: flag("latencyMs", { label: "Simulated latency", unit: "ms" }),
250
245
  series: flag("agents", { label: (v) => `${v} agents` }),
251
- y: passRate,
246
+ y: endToEndPassRate,
252
247
  })} />
253
248
  ```
254
249
 
@@ -264,12 +259,12 @@ export default defineExperiment({
264
259
 
265
260
  ```tsx
266
261
  // reports/cost-board.tsx
267
- import { defineReport, Col, Table, MetricTable, costUSD, passRate } from "niceeval/report";
262
+ import { defineReport, Col, Table, MetricTable, costUSD, endToEndPassRate } from "niceeval/report";
268
263
 
269
264
  export default defineReport(async ({ selection }) => {
270
265
  const board = await MetricTable.data(selection, {
271
266
  rows: "agent",
272
- columns: [passRate, costUSD],
267
+ columns: [endToEndPassRate, costUSD],
273
268
  });
274
269
  return (
275
270
  <Col>
@@ -283,7 +278,7 @@ export default defineReport(async ({ selection }) => {
283
278
  key: r.key,
284
279
  cells: {
285
280
  agent: r.key,
286
- pass: r.cells[passRate.name].display,
281
+ pass: r.cells[endToEndPassRate.name].display,
287
282
  // 缺数据交 null,组件渲染成 —;不要自己填 0
288
283
  cost: r.cells[costUSD.name].value === null ? null : r.cells[costUSD.name].display,
289
284
  },
@@ -324,7 +319,7 @@ codex 80% $0.51
324
319
  // reports/passbars.tsx
325
320
  import {
326
321
  defineReport, defineComponent, Col, Style, MetricTable,
327
- bar, padEnd, stringWidth, passRate,
322
+ bar, padEnd, stringWidth, endToEndPassRate,
328
323
  } from "niceeval/report";
329
324
 
330
325
  interface BarRow { key: string; ratio: number | null; display: string }
@@ -356,11 +351,11 @@ const PassBars = defineComponent<{ rows: BarRow[] }>({
356
351
  });
357
352
 
358
353
  export default defineReport(async ({ selection }) => {
359
- const board = await MetricTable.data(selection, { rows: "agent", columns: [passRate] });
354
+ const board = await MetricTable.data(selection, { rows: "agent", columns: [endToEndPassRate] });
360
355
  const rows = board.rows.map((r) => ({
361
356
  key: r.key,
362
- ratio: r.cells[passRate.name].value, // 格子键锚在指标对象上,不裸写字符串
363
- display: r.cells[passRate.name].display,
357
+ ratio: r.cells[endToEndPassRate.name].value, // 格子键锚在指标对象上,不裸写字符串
358
+ display: r.cells[endToEndPassRate.name].display,
364
359
  }));
365
360
  return (
366
361
  <Col>
@@ -400,4 +395,4 @@ niceeval view --report reports/exam.tsx --out site
400
395
 
401
396
  ## 界线:内置命令不长配置
402
397
 
403
- 一次只渲染一份报告,`--report` 收显式文件路径——没有 `reports/` 目录自动发现、没有插件注册表、没有配置文件。自定义指标和自定义组件都住在你的报告文件里,随文件一起递入,宿主不为它们长任何注册面。不传 `--report` 时渲染的就是内置的 `CostPassRateComparison`,你的报告和它是同级实现。
398
+ 一次只渲染一份报告,`--report` 收显式文件路径——没有 `reports/` 目录自动发现、没有插件注册表、没有配置文件。自定义指标和自定义组件都住在你的报告文件里,随文件一起递入,宿主不为它们长任何注册面。不传 `--report` 时渲染的就是内置的 `ExperimentComparison`,你的报告和它是同级实现。
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  title: "数据驱动测试(dataset fan-out):用多份数据运行同一套 eval"
3
3
  sidebarTitle: "数据驱动测试"
4
- description: "从 .eval.ts 文件导出数组,将一套 eval 逻辑展开为多个 case。用 loadYaml 或 loadJson 读取外部数据集,并获得稳定 ID。"
4
+ description: "从 .eval.ts 文件导出数组或 keyed record,将一套 eval 逻辑展开为多个 case。用 loadYaml 或 loadJson 读取外部数据集,并获得稳定 ID。"
5
5
  ---
6
6
 
7
7
  数据驱动测试(dataset fan-out)适合大量结构相同、输入不同的测试。例如 SQL 生成、意图分类、检索问答和工具选择。
8
8
 
9
9
  ## 工作原理
10
10
 
11
- 一个 `.eval.ts` 文件默认导出数组:
11
+ 没有外部业务 ID 时,一个 `.eval.ts` 文件默认导出数组:
12
12
 
13
13
  ```ts
14
14
  import { defineEval } from "niceeval";
@@ -41,6 +41,25 @@ sql/0001
41
41
 
42
42
  序号零填充,便于稳定引用和过滤。
43
43
 
44
+ 数据源已经带稳定 case、issue 或 benchmark ID 时,默认导出 keyed record:
45
+
46
+ ```ts
47
+ export default Object.fromEntries(
48
+ rows.map((row) => [
49
+ row.issueId,
50
+ defineEval({
51
+ description: row.title,
52
+ async test(t) {
53
+ await t.send(row.prompt);
54
+ t.succeeded();
55
+ },
56
+ }),
57
+ ]),
58
+ );
59
+ ```
60
+
61
+ 文件是 `evals/swelancer.eval.ts`、key 是 `15193` 时,ID 就是 `swelancer/15193`。key 必须是一个非空路径片段:不能是 `.` / `..`,不能含 `/`、`\\` 或控制字符。NiceEval 按 key 字典序发现,数据源返回顺序变化不会改变运行顺序。
62
+
44
63
  ## 从 YAML / JSON 加载
45
64
 
46
65
  ```ts
@@ -63,8 +82,11 @@ cases:
63
82
  # 运行整个数据集
64
83
  npx niceeval exp local sql
65
84
 
66
- # 只运行第一个 case
85
+ # 只运行第一个数组 case
67
86
  npx niceeval exp local sql/0000
87
+
88
+ # 运行一个 keyed case
89
+ npx niceeval exp local swelancer/15193
68
90
  ```
69
91
 
70
92
  ## 数据集 vs 独立文件
@@ -0,0 +1,57 @@
1
+ ---
2
+ title: "保留沙箱现场排查问题"
3
+ sidebarTitle: "保留沙箱现场"
4
+ description: "用 --keep-sandbox 把失败 Attempt 的沙箱保留成可随时唤醒的现场,用 niceeval sandbox enter 进去手动排查,用 sandbox list / stop 查看和清理。"
5
+ ---
6
+
7
+ 沙箱默认在每个 Attempt 结束后销毁,排查依据是落盘的 artifact:`niceeval show` 能看到判定、断言、diff 和事件流。大多数问题到这里就够了——完整的排查路线见 [Debug 手册](/zh/guides/debugging)。
8
+
9
+ 但有些问题只能进活的环境里看:
10
+
11
+ - **环境起不来**——setup 阶段装依赖失败、agent CLI 启动不了。这时 agent 还没开始跑,事件流是空的,最快的办法是进沙箱手动重跑一遍安装命令。
12
+ - **改动落在 `git diff` 之外**——全局装了什么包、`$HOME` 下写了什么配置、`PATH` 实际是什么,artifact 里没有。
13
+ - **重跑太慢**——冷启动加安装要几分钟,想逐条验证猜测时,留着现场比每次重跑快得多。
14
+
15
+ ## 跑的时候保留现场
16
+
17
+ ```bash
18
+ npx niceeval exp local onboarding/tool-first --keep-sandbox # 等价 --keep-sandbox=failed
19
+ npx niceeval exp local onboarding/tool-first --keep-sandbox=all # 通过的也保留
20
+ ```
21
+
22
+ `--keep-sandbox` 是 `niceeval exp` 的运行参数,两档:`failed`(缺省值)保留判定为 `failed` 或 `errored` 的 Attempt(包括超时打断的);`all` 连通过的也保留——调 setup 钩子、核对通过环境的真实状态时用它,不用故意弄挂一条 eval。不带这个参数时全部销毁。
23
+
24
+ 运行结束后,摘要里会列出保留了哪些沙箱、怎么进去:
25
+
26
+ ```text
27
+ Kept sandboxes (1)
28
+ @1x7f3q9k onboarding/tool-first #1 errored docker · a3f9c2d1
29
+ enter: niceeval sandbox enter a3f9c2d1
30
+ Stop them with: niceeval sandbox stop --all
31
+ ```
32
+
33
+ 每行给三样东西:Attempt 定位符(用 `niceeval show @1x7f3q9k` 看落盘证据)、沙箱实例 id、进入现场的命令。保留下来的沙箱不会一直跑着烧资源——Docker 容器停在磁盘上,E2B 微 VM 暂停计费,Vercel 保存文件系统。`niceeval sandbox enter` 会先唤醒再进入,在 workdir 打开 shell;退出 shell 后现场自动回到休眠(想让它保持运行,加 `--leave-running`)。进去之后就是这次 Attempt 跑完时的环境,可以手动执行命令、翻文件、复现失败。
34
+
35
+ ## 查看和清理
36
+
37
+ 保留下来的沙箱逐条记录在 `.niceeval/sandboxes/` 里,用 `niceeval sandbox` 管理:
38
+
39
+ ```bash
40
+ niceeval sandbox list # 列出保留的沙箱和现场状态
41
+ niceeval sandbox enter a3f9c2d1 # 唤醒并进入;退出后自动回到休眠
42
+ niceeval sandbox stop a3f9c2d1 # 销毁指定沙箱(id 可以只写唯一前缀)
43
+ niceeval sandbox stop --all # 全部销毁
44
+ ```
45
+
46
+ `stop` 是幂等的:沙箱已经不在了(手动删过、云端过期)不算错误,只会把记录移掉并说明。如果 provider 销毁失败,命令会保留记录并返回错误,方便稍后重试,不会把仍活着的资源从列表里藏掉。忘了清也有提醒——下次运行开始时,如果还有上次保留的沙箱,会打一行提示。
47
+
48
+ ## 各 Provider 的差别
49
+
50
+ - **Docker**:保留 = 容器停在磁盘上(不占内存,重启 Docker 也还在),进入时自动启动。容器不会自己消失,是唯一需要主动清理的 provider。除了 `niceeval sandbox stop`,也可以用 `docker ps -a -f label=niceeval.keep-candidate=true` 直接核对。
51
+ - **E2B**:保留 = 暂停微 VM——文件和内存整体保存,暂停期间停止计费、无限期保留,进入时自动恢复。
52
+ - **Vercel Sandbox**:保留 = 停止微 VM——文件系统保存、之后可恢复,但内存状态不保留,唤醒后进程要重新启动;超过 provider 的保留期限后 `niceeval sandbox list` 标成 `expired`。
53
+ - **自定义 Provider**:`defineSandbox` 产出的 provider 不支持留存,因为事后的 `sandbox stop` 不加载用户配置,无法在新进程里安全找回自定义销毁函数。
54
+
55
+ ## 边界
56
+
57
+ 保留的沙箱只用来排查,不能续跑或重新评分;判定、断言、diff 这些结论仍以 artifact 为准。查看 artifact 的方法见[查看结果](/zh/guides/viewing-results)。
@@ -0,0 +1,210 @@
1
+ ---
2
+ title: "排查失败与复盘历史运行"
3
+ sidebarTitle: "Debug 手册"
4
+ description: "一份按场景组织的排查手册:断言失败怎么定位、环境错误怎么进沙箱、agent 改了什么怎么看、旧的运行怎么翻出来复盘——每一步都有命令顺序和输出示例。"
5
+ ---
6
+
7
+ 跑完一次 `niceeval exp`,失败的 Attempt 都带一个 `@` 开头的定位符(如 `@1qrdcfq8`)。它出现在运行摘要、CI 日志和报告里,定位符本身不会过期——只要 `.niceeval/` 里对应的结果快照还在,今天的定位符下周还能用同一条命令打开同一次 Attempt。所有排查都从它开始。
8
+
9
+ ## 第一步永远是 `niceeval show @<定位符>`
10
+
11
+ 不带任何参数打开 Attempt,第一页就是为排查设计的:判定、失败的断言、耗时分布、改动概览,以及下一步可用的命令。
12
+
13
+ ```text
14
+ $ niceeval show @1qrdcfq8
15
+ @1qrdcfq8 · memory/swelancer-manager-proposals · dev-e2b/codex-e2b · failed
16
+ snapshot 2026-07-12T10:08:29.361Z · attempt 1 · 50.0s · 58.5k tokens · $0.05
17
+
18
+ assertions: 3 passed · 1 gate failed
19
+ eval source: evals/memory/swelancer-manager-proposals.eval.ts · sha256:ee33b9c4…
20
+
21
+ failures:
22
+ gate · Issue 15193: selected proposal matches the one maintainers accepted
23
+ assertion: equals(4)
24
+ expected: 4
25
+ received: 1
26
+ source: evals/memory/swelancer-manager-proposals.eval.ts:40:11
27
+
28
+ execution: 12 events · 0 skill loads · 7 tool calls · 4 AI messages
29
+ timing: sandbox.queue 0.2s · sandbox.create 5.6s · sandbox.setup 3.5s · agent.setup 12.1s ·
30
+ eval.run 26.3s · workspace.diff 0.3s · scoring.evaluate 1.4s · teardown +0.8s
31
+
32
+ changes: 2 files changed by agent · M manager_decisions.json · A notes/decision-log.md
33
+
34
+ available:
35
+ niceeval show @1qrdcfq8 --eval
36
+ niceeval show @1qrdcfq8 --execution
37
+ niceeval show @1qrdcfq8 --timing
38
+ niceeval show @1qrdcfq8 --diff
39
+ ```
40
+
41
+ 看这一页先回答一个问题:**是 agent 答错了(`failed`),还是环境根本没跑起来(`errored`)?** 两种情况的排查路线完全不同。
42
+
43
+ ## 场景一:断言失败(failed)——agent 跑完了,但结果不对
44
+
45
+ 排查顺序是「哪条断言挂了 → agent 当时做了什么 → 它到底改了什么」。
46
+
47
+ **1. 把断言放回源码。** `--eval` 显示运行时保存的那份 eval 源码(不是你工作区里可能已经改过的版本),失败的断言直接标在对应行上;`t.send(...)` 的调用行标出它产生的那一轮——轮标签(`s1/t1`,与 `--execution` / `--timing` 用同一套)、这轮成没成、花了多久:
48
+
49
+ ```text
50
+ $ niceeval show @1qrdcfq8 --eval
51
+ 21✓ await t.send("Review the proposals and record your decision…");
52
+ s1/t1 · completed · 22.4s
53
+ 38 for (const [issue, label] of Object.entries(expected)) {
54
+ 39 await t.group(`Issue ${issue}: selected proposal matches…`, async () => {
55
+ 40✗ t.check(Number(decisions[issue]?.selected_proposal_id), equals(label.selected_proposal_id));
56
+ gate · Issue 15193 · equals(4) · expected 4 · received 1
57
+ 41 });
58
+ 42 }
59
+ ```
60
+
61
+ **2. 看 agent 当时做了什么。** `--execution` 把这次 Attempt 的对话按时间线展开——用户消息、assistant 回复、每次工具调用的入参和结果:
62
+
63
+ ```text
64
+ $ niceeval show @1qrdcfq8 --execution
65
+ TURN s1/t1 · completed · 22.4s · 12.4k tok · $0.02
66
+ USER
67
+ Review the proposals and record your decision for each issue…
68
+
69
+ ASSISTANT
70
+ I'll inspect the task layout and the decision format first…
71
+
72
+ TOOL · command_execution +12.8s · 1.3s
73
+ input
74
+ /bin/bash -lc 'cat tasks/15193/proposals.md'
75
+ result · completed · exit 0
76
+ Proposal 1: …
77
+ ```
78
+
79
+ 对话按轮分段,每轮头行给出编号(`s1/t1`)、状态、耗时和用量——这个编号和 `--diff`、`--timing` 里的轮次标签是同一套,能互相对照。
80
+
81
+ **3. 看它到底改了什么。** `--diff` 只显示 **agent 自己改动的文件**——你上传的起始文件、跑完后写入的验证材料不会混在里面,所以列表里的每一行都真的是 agent 干的:
82
+
83
+ ```text
84
+ $ niceeval show @1qrdcfq8 --diff
85
+ 2 files changed by agent
86
+ M manager_decisions.json +6 -2 s1/t1, s1/t2
87
+ A notes/decision-log.md +18 s1/t2
88
+
89
+ single file: niceeval show @1qrdcfq8 --diff=manager_decisions.json
90
+ ```
91
+
92
+ 行尾的 `s1/t1` 表示这个文件是在第几轮对话里被改的,能和 `--execution` 的轮次对上。要看单个文件的逐行改动,用 `=` 连写文件路径:
93
+
94
+ ```text
95
+ $ niceeval show @1qrdcfq8 --diff=manager_decisions.json
96
+ M manager_decisions.json · changed in s1/t1, s1/t2
97
+ @@ -1,5 +1,7 @@
98
+ {
99
+ - "15193": { "selected_proposal_id": 1 },
100
+ + "15193": { "selected_proposal_id": 4 },
101
+ ```
102
+
103
+ 到这里通常能下结论:是任务描述有歧义、agent 理解错了,还是断言本身写得太死。
104
+
105
+ **要看文件本身,而不只是改动?** 落盘的证据刻意不保存整个工作区——`--diff` 只有 agent 改过的文件,agent 该写没写的文件、你上传的起始材料、setup 装出来的东西都不在里面。想看它们的实际内容,进活现场:重跑这一条 eval 加 `--keep-sandbox`(`failed` 的 Attempt 同样会保留,不只是环境错误),用下面场景二的方式进沙箱,workdir 里就是这次跑完时的完整文件树。
106
+
107
+ ## 场景二:环境错误(errored)——agent 根本没跑起来
108
+
109
+ `errored` 的第一页不列断言,而是列出错误发生在哪个阶段、什么原因:
110
+
111
+ ```text
112
+ $ niceeval show @12h8m4k1
113
+ @12h8m4k1 · memory/agent-029-use-cache · compare/claude-e2b · errored
114
+
115
+ error:
116
+ phase: sandbox.create
117
+ code: sandbox-rate-limit
118
+ message: E2B sandbox allocation failed after 5 attempts
119
+ cause: RateLimitError · too many concurrent sandboxes
120
+
121
+ execution: unavailable (attempt failed before telemetry was configured)
122
+ timing: sandbox.queue 1.2s · sandbox.create 2m 6s ✗ failed here
123
+ ```
124
+
125
+ `phase` 直接告诉你死在哪一步,而且决定了下一步走哪条路:
126
+
127
+ **`sandbox.create` 失败——沙箱根本没创建出来,没有现场可留。** 这类错误(配额、限流、凭据、镜像 / 模板不存在)在你自己的机器和账号侧排查:核对 API key 和配额、降低 `--max-concurrency`、确认镜像 / 模板名。示例里的 rate-limit 就属于这类,重跑加 `--keep-sandbox` 只会原地再死一次。
128
+
129
+ **`sandbox.setup` / `agent.setup` / `eval.run` 失败——沙箱活过,值得留现场。** 装依赖失败、agent CLI 起不来、跑到一半超时,这类问题事件流往往是空的,落盘证据帮不上忙,最快的办法是留住现场进去手动重跑一遍出错的命令:
130
+
131
+ ```bash
132
+ # 只重跑这一条 eval,失败时保留沙箱
133
+ npx niceeval exp compare memory/agent-029 --keep-sandbox
134
+ ```
135
+
136
+ ```text
137
+ Kept sandboxes (1)
138
+ @18c1m2qx memory/agent-029-use-cache #1 errored docker · a3f9c2d1
139
+ enter: niceeval sandbox enter a3f9c2d1
140
+ Stop them with: niceeval sandbox stop --all
141
+ ```
142
+
143
+ `niceeval sandbox enter a3f9c2d1` 会唤醒现场并在 workdir 打开 shell——手动执行安装命令看真实报错、翻 `$HOME` 下的配置、检查 `PATH`,这些都在 artifact 之外,只有活现场能回答;退出 shell 后现场自动回到休眠,不白烧资源。保留策略、各 provider 的差别见[保留沙箱现场](/zh/guides/debug-sandbox)。
144
+
145
+ ## 查看和清理留下的沙箱
146
+
147
+ 保留下来的沙箱不会一直烧资源:Docker 容器停驻在磁盘上,E2B 微 VM 暂停计费,进入时自动唤醒。用 `niceeval sandbox` 管理:
148
+
149
+ ```text
150
+ $ niceeval sandbox list
151
+ ID PROVIDER STATE FROM
152
+ a3f9c2d1 docker dormant memory/agent-029-use-cache #1 · errored · @18c1m2qx · 2026-07-14 15:02
153
+ enter: niceeval sandbox enter a3f9c2d1
154
+ 9f21c07b vercel expired onboarding/tool-first #2 · failed · @1x7f3q8a · 2026-07-14 14:31
155
+ expired 2026-07-14 14:36 — remove with: niceeval sandbox stop 9f21c07b
156
+ ```
157
+
158
+ `dormant` 是「睡着但随时能进」,`expired` 是「现场已经没了,只剩记录」。排查完记得清理:
159
+
160
+ ```bash
161
+ niceeval sandbox stop a3f9c2d1 # id 可以只写唯一前缀
162
+ niceeval sandbox stop --all
163
+ ```
164
+
165
+ ## 场景三:复盘旧的运行
166
+
167
+ 每次运行都会在 `.niceeval/<实验>/<时间戳>/` 下留一份完整的结果快照,判定、断言、事件流、diff 都在里面,**不会被下一次运行覆盖**。复盘有三个入口:
168
+
169
+ **用旧定位符直接打开。** 从上周的终端记录、CI 日志或报告里复制 `@` 定位符,`niceeval show @<定位符>` 照常工作,上面的 `--eval` / `--execution` / `--diff` 全部可用——包括那份运行时的 eval 源码,哪怕你后来把 eval 改了。
170
+
171
+ **按实验回看当前水位。** 不记得定位符时,从实验入手:
172
+
173
+ ```bash
174
+ niceeval show --experiment compare/bub # 这个实验每道题现在的判定
175
+ niceeval show memory/swelancer --experiment compare/bub # 收窄到某道题
176
+ ```
177
+
178
+ 列表里每道题、每次 Attempt 都带定位符,接着往深处钻就回到上面的场景一 / 场景二。
179
+
180
+ **在浏览器里翻。** 复盘一批失败、对比多次 Attempt 时,网页比终端顺手:
181
+
182
+ ```bash
183
+ niceeval view
184
+ ```
185
+
186
+ 首页是成本 × 通过率总览和实验对比表;每个 Attempt 的详情页有判定、断言、完整时间树、对话、trace 和 diff,还有「Copy fix prompt」按钮——把失败整理成一段可以直接交给 coding agent 的修复提示词。报告里的 Attempt 深链和 `show` 用同一套定位符。
187
+
188
+ **打开归档或别人发来的结果。** 结果目录是自包含的——从 CI 下载的、同事拷给你的、发布到静态站前生成的目录,都能直接指过去:
189
+
190
+ ```bash
191
+ niceeval show --run tmp/ci-artifacts/results
192
+ niceeval view --run site-data/run
193
+ ```
194
+
195
+ 一个注意点:如果本地清理过旧快照目录,之后的运行里「沿用上次结果」的条目会找不到原始证据(显示为缺失)。要长期归档某次运行,先用 [`copySnapshots`](/zh/guides/results-data) 复制出一份再删。
196
+
197
+ ## 速查:症状 → 命令
198
+
199
+ | 症状 | 命令顺序 |
200
+ |---|---|
201
+ | 断言挂了,不知道为什么 | `show @loc` → `show @loc --eval` |
202
+ | 想知道 agent 当时做了什么 | `show @loc --execution` |
203
+ | 想确认 agent 改了哪些文件 | `show @loc --diff` → `--diff=<path>` |
204
+ | 哪一步慢 / 超时死在哪 | `show @loc`(看 `timing:` 行)→ `show @loc --timing` |
205
+ | 沙箱创建就失败(配额 / 凭据 / 镜像) | `show @loc` 看 error 的 code 与 cause → 查账号配额、核对凭据、降 `--max-concurrency`(没有现场可留) |
206
+ | 装依赖失败、CLI 起不来、跑一半超时 | 重跑该 eval 加 `--keep-sandbox` → `niceeval sandbox enter <id>` |
207
+ | 想看文件实际内容(agent 没改的、起始材料、`$HOME`) | 重跑加 `--keep-sandbox`(failed 也留)→ `sandbox enter` 进 workdir 看 |
208
+ | 留了哪些沙箱、清理 | `sandbox list` → `sandbox stop <id>` / `--all` |
209
+ | 复盘上周那次失败 | 翻出旧定位符 → `show @loc`;不记得就 `show --experiment <实验>` |
210
+ | 一批失败一起看 | `niceeval view` → Attempt 详情 → Copy fix prompt |
@@ -35,7 +35,7 @@ export default defineExperiment({
35
35
  npx niceeval exp compare-models # 把同组各 model 并排出报告
36
36
  ```
37
37
 
38
- 这样每个配置独立成文件:可命名、可 diff、可单独 review,"这一组就是对照"在文件结构上就讲清楚了。
38
+ 这样每个配置独立成文件:可命名、可 diff、可单独 review,"这一组就是对照"在文件结构上就讲清楚了。默认 `niceeval show` / `view` 也沿用这条边界:只把同一文件夹下的 experiment 放进同一张成本 × 端到端成功率图和实验表,不同文件夹分别显示。
39
39
 
40
40
  某一格结果反常、需要单独复现时,用该配置的完整 id(`组/文件名`)精确只跑这一格,不用先跑完整组:
41
41
 
@@ -50,8 +50,8 @@ npx niceeval exp compare-models/gpt-5.4
50
50
  - 不同 Adapters。
51
51
  - 不同模型(Tier 1 接入即可:应用接口暴露模型选择,`model` 经 `ctx.model` 透传)。
52
52
  - 不同 prompts 或 feature flags(要求 Tier 3 接入:变体在应用内部,需要应用把它暴露成 experiment 可选的 flag,经 `flags` → `ctx.flags` 透传)。
53
- - 不同 sandbox 后端。
54
- - 不同运行环境条件(比如装不装某个记忆工具的二进制、有没有预置状态):环境差异写在 `sandbox` spec 的 `.setup()` / `.teardown()` 钩子里,一个变体一个 experiment 文件,见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-providers#环境钩子)。
53
+ - 不同 sandbox provider。
54
+ - 不同运行环境条件(比如装不装某个记忆工具的二进制、有没有预置状态):环境差异写在 `sandbox` spec 的 `.setup()` / `.teardown()` 钩子里,一个变体一个 experiment 文件,见 [沙箱 provider · 环境钩子](/zh/guides/sandbox-providers#环境钩子)。
55
55
  - 同一任务的 pass@N。
56
56
 
57
57
  Tier 1 / Tier 2 / Tier 3 的定义见 [Tier](/zh/concepts/tier)。
@@ -67,6 +67,13 @@ api-validation claude-code pass@3 = 1/3 (33%) mean 41s
67
67
 
68
68
  除了 pass rate,还应该看平均耗时、token、成本和失败类型。
69
69
 
70
+ ```bash
71
+ npx niceeval show --experiment compare-models
72
+ npx niceeval view
73
+ ```
74
+
75
+ `show --experiment` 按路径段匹配 id 前缀,所以组名会选中组内所有配置,但不会误中名字只是在字符串上相似的另一个组。`view` 默认加载完整结果,直接在页面里选组;`--experiment` 对它只是可选的启动时收窄。
76
+
70
77
  ## 设计 experiment 的建议
71
78
 
72
79
  - 保持 eval 集合稳定,避免比较时混入新变量。