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
@@ -4,7 +4,7 @@ sidebarTitle: "沙箱 provider"
4
4
  description: "NiceEval 在 Docker 或 Vercel sandbox 中运行 coding agents。了解如何选择 provider、配置权限,并通过 warm pools 改善性能。"
5
5
  ---
6
6
 
7
- Sandbox backend 是创建和管理隔离运行环境的基础设施。[NiceEval](https://niceeval.com/) 把它们包装成同一个 `Sandbox` 接口,所以 adapter 不需要知道当前用的是本地 Docker、Vercel micro-VM 还是第三方云服务。
7
+ Sandbox Provider 是创建和管理隔离运行环境的基础设施。[NiceEval](https://niceeval.com/) 把它们包装成同一个 `Sandbox` 接口,所以 Adapter 不需要知道当前用的是本地 Docker、Vercel micro-VM、E2B 还是第三方云服务。
8
8
 
9
9
  ## `Sandbox` 接口
10
10
 
@@ -51,13 +51,21 @@ export default defineExperiment({
51
51
 
52
52
  ## 环境钩子
53
53
 
54
- `dockerSandbox()` / `vercelSandbox()` / `e2bSandbox()` 返回的 spec 上有两个链式方法:`.setup(fn)` 和 `.teardown(fn)`。它们解决一类具体问题:**有些东西你本想直接做进 Docker 镜像或 E2B 模板,但它要按实验变化**——比如某个实验要装一个额外的二进制、预热一次模型、写一份 hook 文件,或者在多次 attempt 之间载入和回存状态。静态镜像做不到"每个实验装不同的东西",这两个钩子就是运行时补上的那一层。
54
+ `dockerSandbox()` / `vercelSandbox()` / `e2bSandbox()` 返回的 spec 上有两个链式方法:`.setup(fn)` 和 `.teardown(fn)`。它们处理只有运行时才知道的环境内容,例如按实验写小配置、检查预制工具是否可用、安装 hook,或在多次 Attempt 之间载入和回存状态。
55
+
56
+ 共享 helper 需要显式标注回调类型时,从公开入口导入,不要从某个 provider 的 spec 反推:
57
+
58
+ ```ts
59
+ import type { SandboxHook, SandboxHookContext } from "niceeval/sandbox";
60
+ ```
61
+
62
+ 稳定且体积大的依赖不要在这里重复安装。系统包、Agent CLI、编译好的二进制和大模型缓存应该先做进 Docker image、Vercel 沙箱快照或 E2B template。每个 Attempt 从预制环境启动,`.setup()` 只做薄薄的一层动态配置和 fail-fast 检查。
55
63
 
56
64
  ```ts
57
65
  export default defineExperiment({
58
66
  agent: codexAgent({ mcpServers: [mempalMcp] }),
59
- sandbox: e2bSandbox({ template: "fasteval-agents" })
60
- .setup(mempalSetup("codex")) // 装二进制、预热、写 hook、载入状态
67
+ sandbox: e2bSandbox({ template: "fasteval-agents-mempal" }) // 已包含二进制和模型缓存
68
+ .setup(mempalSetup("codex")) // 预检、写 hook、载入状态
61
69
  .teardown(mempalTeardown("codex")), // 回存状态
62
70
  maxConcurrency: 1, // 载入和回存之间不能并发,声明串行
63
71
  });
@@ -65,17 +73,230 @@ export default defineExperiment({
65
73
 
66
74
  规则一览:
67
75
 
68
- - **签名**:钩子函数和 Adapter 的 `setup` / `teardown` 一样是 `(sandbox, ctx)`;`setup` 可以返回一个清理函数。
76
+ - **签名**:钩子函数是 `(sandbox, ctx)`;`setup` 可以返回一个清理函数。这里的 `ctx` 是窄的 Sandbox Hook Context,只有 experiment 身份、取消信号和反馈方法,不带 Agent 会话或 telemetry。
69
77
  - **不可变**:每次 `.setup()` / `.teardown()` 都返回一个新 spec,原对象不变,可以继续链。
70
78
  - **多个钩子**:多个 `.setup()` 按追加顺序执行;多个 `.teardown()` 按追加的逆序执行。
71
79
  - **执行时机**:`setup` 钩子在 sandbox 创建后、git 基线之前最先跑——它写下的文件会进基线,不会被算进 agent 产出的 diff;`teardown` 钩子在 Adapter 的 `teardown` 之后、sandbox 销毁之前最后跑——把状态回存到外部正好用这个时机。
72
- - **失败语义**:`setup` 钩子抛错,这次 attempt 记为 `errored`(环境问题,不是 agent 做错题);`teardown` 钩子报错只记日志,不改变已产出的结果。
80
+ - **失败语义**:`setup` 钩子抛错,这次 Attempt 记为 `errored`(环境问题,不是 Agent 做错题);`teardown` 钩子可以报告 diagnostic,默认不改变已经得到的判定。某个收尾动作是结果成立的必要条件时应抛错,由 runner 明确记录为致命错误。
73
81
  - **不带 sandbox 的 Agent**:`defineAgent` 构造的 Agent 没有 sandbox,`sandbox` 字段对它不生效,钩子自然不会跑。
74
82
 
75
83
  钩子里可以用 `ctx.experimentId`(路径推导的实验 id)当状态隔离的键,比如不同实验各自维护一份跨 attempt 的缓存。跨 attempt 状态的载入和回存是你自己在钩子里写的普通代码——[NiceEval](https://niceeval.com/) 不提供状态存储;要保证同一实验的 attempt 不并发读写同一份状态,在 experiment 上声明 `maxConcurrency: 1`。
76
84
 
85
+ ### 从环境钩子报告进度和问题
86
+
87
+ 长时间安装、预热和状态恢复可以调用 `ctx.progress(...)`。它只更新当前 Attempt 的短期状态,不会把每一步都写进结果。需要运行结束后仍能看到的问题使用 `ctx.diagnostic(...)`:
88
+
89
+ ```ts
90
+ const sandbox = e2bSandbox({ template: "niceeval-agents" })
91
+ .setup(async (sandbox, ctx) => {
92
+ ctx.progress({ message: "安装 memory helper", current: 1, total: 2 });
93
+ await sandbox.runCommand("npm", ["install", "-g", "memory-helper"]);
94
+
95
+ ctx.progress({ message: "预热 memory index", current: 2, total: 2 });
96
+ try {
97
+ await warmIndex(sandbox);
98
+ } catch (error) {
99
+ ctx.diagnostic({
100
+ code: "memory-warmup-degraded",
101
+ level: "warning",
102
+ message: "预热失败,本次使用冷索引继续运行",
103
+ data: { reason: String(error) },
104
+ dedupeKey: "memory-warmup-degraded",
105
+ });
106
+ }
107
+ });
108
+ ```
109
+
110
+ `progress` 和 `diagnostic` 都不能指定全局阶段、颜色或输出流。Runner 知道当前回调属于 `sandbox.setup`,会把 Human 终端中的阶段显示为 sandbox setup。`diagnostic` 会随 Attempt 写入 `result.json`,之后可用 `niceeval show @<locator>` 回顾;它本身不会改变判定。环境无法继续时直接抛出异常。
111
+
77
112
  和另外两处 setup 的分工:Adapter 的 `setup` 管"怎么连被测 agent"(装 CLI、写鉴权配置);eval 里 `test(t)` 开头的代码管"这道题需要哪些起始文件";sandbox 的 `.setup()` 管"这次实验的环境里要多装什么"。三层各写各的,互相不知道对方的内容。MCP server、Skill、model 这些被测 agent 的配置仍然只从 Adapter 工厂参数进——环境钩子不做 Adapter 的事。
78
113
 
114
+ ## 预制环境与运行时 checkpoint
115
+
116
+ NiceEval 用 typed spec 统一引用预制环境,但不提供一个假的通用构建命令:
117
+
118
+ ```ts
119
+ dockerSandbox({ image: "my-evals:node24" })
120
+ vercelSandbox({ snapshotId: "snap_abc123" })
121
+ e2bSandbox({ template: "my-evals" })
122
+ ```
123
+
124
+ Docker image、Vercel 沙箱快照和 E2B template 的凭据、构建上下文、发布和过期方式不同。项目应使用 provider 的官方工具维护构建脚本,把最终 ID 或名字放进 experiment。适合预制的判断很简单:如果所有 Attempt 都要下载或安装同一份内容,而且它稳定、昂贵或体积大,就把它移到预制环境。
125
+
126
+ ### 从官方基线继续构建以提速
127
+
128
+ 稳定、体积大、每个 Attempt 都相同的内容——系统包、Agent CLI、编译好的二进制、大模型缓存——应在跑 eval 之前烘焙进 provider 的可发布制品,让每个 Attempt 从预制环境启动、跳过运行时安装。三个内置 provider 都能从官方基线继续派生,不必从空白环境装 Agent;但它们的构建工具、凭据和发布语义不同,NiceEval 只统一**消费**产物 ID(`image` / `snapshotId` / `template`),不伪造跨 provider 的构建 DSL。
129
+
130
+ Adapter 与 Sandbox 不互相猜配置:Adapter 负责检查所需 CLI,Sandbox spec 负责选择 provider 和制品。Claude Code 与 Codex 缺少 CLI 时会回退到运行时安装,所以烘焙纯粹是提速;Bub 还会核对版本、OTel 插件和 Python 插件集合的安装指纹,不能仅凭 `command -v bub` 跳过安装,必须用预制环境。三个 provider 的构建都只在环境依赖变化时跑一次,产物换一个版本化名字,不要塞进每个 Attempt 的 `.setup()`。
131
+
132
+ #### E2B:从官方与公共模板派生
133
+
134
+ E2B 已提供 Claude Code 的 `claude` template 和 Codex 的 `codex` template。NiceEval 提供一个 E2B 专属的薄封装,让你从这两个官方起点继续链原生 E2B API。E2B 暂无 Bub 官方 template,因此 Bub 分支使用 NiceEval 固定到不可变 commit 的安装配方:
135
+
136
+ NiceEval 同时发布了三份任何 E2B Team 都能引用的公共模板。完整 namespace 与经过验证的 release
137
+ tag 由 NiceEval 自己维护,下游直接取完整引用:
138
+
139
+ ```ts
140
+ import {
141
+ NICEEVAL_CLAUDE_CODE_E2B_TEMPLATE,
142
+ NICEEVAL_CODEX_E2B_TEMPLATE,
143
+ NICEEVAL_BUB_E2B_TEMPLATE,
144
+ } from "niceeval/sandbox/e2b-template";
145
+
146
+ e2bSandbox({ template: NICEEVAL_CLAUDE_CODE_E2B_TEMPLATE })
147
+ e2bSandbox({ template: NICEEVAL_CODEX_E2B_TEMPLATE })
148
+ e2bSandbox({ template: NICEEVAL_BUB_E2B_TEMPLATE })
149
+ ```
150
+
151
+ 这些基线已实际启动验证:Claude Code `2.1.207`、Codex `0.144.1`,Bub 安装指纹 `83770925b77a`。每个值都是带 tag 的完整跨 Team 引用;业务仓库不应复制这些字符串,也不应维护或读取另一份 NiceEval release 常量。派生模板需要记录 base 身份时,直接使用所选的完整 template ref。
152
+
153
+ ```ts title="scripts/build-e2b-template.ts"
154
+ import { Template } from "e2b";
155
+ import { e2bCodingAgentTemplate } from "niceeval/sandbox/e2b-template";
156
+
157
+ const template = e2bCodingAgentTemplate("codex")
158
+ .aptInstall(["ripgrep", "jq"])
159
+ .runCmd("corepack enable && pnpm --version")
160
+ .copy("fixtures/toolchain.lock", "/opt/evals/toolchain.lock");
161
+
162
+ await Template.build(template, "acme-codex-evals:2026-07-13", {
163
+ cpuCount: 2,
164
+ memoryMB: 4096,
165
+ });
166
+ ```
167
+
168
+ ```bash
169
+ e2b auth login
170
+ pnpm tsx scripts/build-e2b-template.ts
171
+ ```
172
+
173
+ 然后在 Experiment 里只引用构建结果:
174
+
175
+ ```ts
176
+ import { e2bSandbox } from "niceeval/sandbox";
177
+
178
+ sandbox: e2bSandbox({ template: "acme-codex-evals:2026-07-13" })
179
+ ```
180
+
181
+ 也可以直接从 NiceEval 公共模板继续派生,只支付项目依赖的构建成本:
182
+
183
+ ```ts
184
+ import { NICEEVAL_CODEX_E2B_TEMPLATE } from "niceeval/sandbox/e2b-template";
185
+
186
+ const template = Template()
187
+ .fromTemplate(NICEEVAL_CODEX_E2B_TEMPLATE)
188
+ .aptInstall(["ripgrep", "jq"])
189
+ .runCmd("corepack enable");
190
+ ```
191
+
192
+ `e2bCodingAgentTemplate("claude-code" | "codex" | "bub")` 返回原生 `TemplateBuilder`,不是 NiceEval 私有构建 DSL。你可以继续使用 `.aptInstall()`、`.runCmd()`、`.copy()` 等 E2B 能力。构建自己的 alias 会把官方起点和项目依赖冻结在同一个可复现制品里;依赖变更时重建并换一个版本化 alias。
193
+
194
+ 若 Bub Adapter 配了 `pythonPlugins`,构建模板时把同一组 package 传给 factory,插件集合才会进入兼容性指纹并真正命中预装环境:
195
+
196
+ ```ts
197
+ e2bCodingAgentTemplate("bub", {
198
+ bubPythonPackages: ["bub-plugin-memory==1.3.0"],
199
+ })
200
+ ```
201
+
202
+ #### Docker:使用 NiceEval 维护的镜像,或从官方 node 基础镜像派生
203
+
204
+ 要直接运行 NiceEval 内置的 `claude-code`、`codex` 或 `bub` Adapter,可以使用对应的公开镜像:
205
+ [`niceeval/claude-code`](https://hub.docker.com/r/niceeval/claude-code)、
206
+ [`niceeval/codex`](https://hub.docker.com/r/niceeval/codex) 或
207
+ [`niceeval/bub`](https://hub.docker.com/r/niceeval/bub)。每个镜像只包含自己的 Agent CLI,并为
208
+ `linux/amd64` 和 `linux/arm64` 发布 manifest;每个 NiceEval release 都有同名 tag。稳定 CI 要固定
209
+ release tag 或 digest,不要依赖会移动的 `latest`:
210
+
211
+ ```ts
212
+ import { dockerSandbox } from "niceeval/sandbox";
213
+
214
+ sandbox: dockerSandbox({ image: "niceeval/codex:v0.6.1" })
215
+ ```
216
+
217
+ 这个镜像是 NiceEval 维护的公开镜像,不是 Docker 的 `library/*` Official Image。它随 NiceEval
218
+ release 更新,里面的 Agent CLI 版本由该 release 的构建配方固定。
219
+
220
+ 如果只需要一个 Agent,或还要加入项目专有依赖,写 Dockerfile 从 Docker 的官方基线
221
+ `node:24-slim` 派生。省略 `image` 时,NiceEval 也会按 runtime 使用该默认镜像:
222
+
223
+ ```dockerfile title="Dockerfile"
224
+ FROM node:24-slim
225
+ # slim 镜像不带 ca-certificates / git,Agent 和 npm 都要用
226
+ RUN apt-get update \
227
+ && apt-get install -y --no-install-recommends ca-certificates git \
228
+ && rm -rf /var/lib/apt/lists/*
229
+ # npm 全局装进 /usr/local/bin,正好落在沙箱注入的 PATH 上
230
+ RUN npm install -g @openai/codex@0.144.1
231
+ ```
232
+
233
+ ```bash
234
+ docker build -t acme-codex-evals:2026-07-13 .
235
+ ```
236
+
237
+ 然后在 Experiment 里只引用构建结果:
238
+
239
+ ```ts
240
+ import { dockerSandbox } from "niceeval/sandbox";
241
+
242
+ sandbox: dockerSandbox({ image: "acme-codex-evals:2026-07-13" })
243
+ ```
244
+
245
+ Docker 沙箱默认以非 root 的 `node` 用户(UID 1000)跑命令,并把 `/usr/local/bin` 放进 PATH,所以 `npm install -g` 装的全局二进制天然可见;要装到别处的 Agent(如落在 `~/.local/bin`)记得让它进 PATH。本地快速迭代可以直接省略 `image` 用默认 `node:*-slim`,稳定 CI 引用不可变 tag。
246
+
247
+ #### Vercel:从官方 runtime 拍快照
248
+
249
+ Vercel 没有 E2B 式的 template registry,也没有 Dockerfile;沙箱快照是从一台跑起来的 microVM 拍出来的。用 Vercel SDK 从官方 runtime(`node24`)起一台沙箱,装好 Agent CLI,调 `.snapshot()` 拿到 `snap_...`,再把它交给 `vercelSandbox({ snapshotId })`:
250
+
251
+ ```ts title="scripts/build-vercel-snapshot.ts"
252
+ import { Sandbox } from "@vercel/sandbox";
253
+
254
+ const sandbox = await Sandbox.create({ runtime: "node24" }); // 官方 runtime 起 microVM
255
+ await sandbox.runCommand({
256
+ cmd: "npm",
257
+ args: ["install", "-g", "@openai/codex@0.144.1"],
258
+ sudo: true, // 全局装进 /usr/local/bin,落在沙箱 PATH 上
259
+ });
260
+ const { snapshotId } = await sandbox.snapshot();
261
+ console.log(snapshotId); // snap_...
262
+ await sandbox.stop();
263
+ ```
264
+
265
+ ```bash
266
+ pnpm tsx scripts/build-vercel-snapshot.ts
267
+ ```
268
+
269
+ 然后在 Experiment 里引用打印出的 ID:
270
+
271
+ ```ts
272
+ import { vercelSandbox } from "niceeval/sandbox";
273
+
274
+ sandbox: vercelSandbox({ snapshotId: "snap_xxx" })
275
+ ```
276
+
277
+ Vercel snapshot 不支持 E2B 式公共发布:snapshot ID 受创建它的 Team/Project 权限控制,同项目成员可以复用,外部用户必须在自己的 Vercel Project 重拍一份。NiceEval 维护项目当前验证过的永不过期 snapshot 是 `snap_7sIjfs71xfmVly0WEUTGhTBoMGeL`,但它不是跨账号公共 ID。
278
+
279
+ ### 运行时 checkpoint
280
+
281
+ `createCheckpoint()` / `restoreCheckpoint()` 是另一件事。它们把指定的 Linux 路径打包成 `Buffer`,可以在已创建的 Sandbox 之间恢复文件系统片段:
282
+
283
+ ```ts
284
+ import { createCheckpoint, restoreCheckpoint } from "niceeval/sandbox";
285
+
286
+ const data = await createCheckpoint(sandbox, ["/home/user/.cache/tool"]);
287
+ await restoreCheckpoint(nextSandbox, data);
288
+ ```
289
+
290
+ 这适合运行时缓存,不会创建可发布的 image/template/snapshot,也不管理共享、版本和过期。归档或恢复失败会直接抛错。
291
+
292
+ ## 瞬时错误重试
293
+
294
+ 内置 Provider 创建沙箱时,限流、`fetch failed`、连接重置、5xx、临时网络不可达这类瞬时失败会自动做指数退避重试;模板不存在、凭据缺失这类配置错误第一次就报错。重试用尽后该 Attempt 记为 errored。`defineSandbox` 自定义 Provider 的 `create` 是你自己的函数,NiceEval 不替它重试。
295
+
296
+ `readFile`、`downloadFile`、`uploadFile`、批量写入和目录上传会对 429、5xx、`fetch failed`、连接重置等瞬时传输错误自动做有限重试。文件不存在、权限错误、取消和 Sandbox terminated 不重试。
297
+
298
+ `runCommand` 与 `runShell` 不自动重试。命令可能已经产生副作用,只有你能确认它可安全重复时,才在 hook 或 eval 中显式重试。
299
+
79
300
  ## Docker
80
301
 
81
302
  Docker 适合本地开发和标准 CI。优点是简单、可控、无云端依赖;缺点是机器资源有限,冷启动和安装依赖可能较慢。
@@ -84,9 +305,35 @@ Docker 适合本地开发和标准 CI。优点是简单、可控、无云端依
84
305
 
85
306
  Vercel Sandbox 适合需要云端隔离、更多资源或更稳定环境的任务。需要相应 token 或 OIDC 配置。
86
307
 
87
- ## 第三方 provider
308
+ ## 自定义 Provider
309
+
310
+ 用 `defineSandbox` 接入其它服务。`create` 的 `feedback` 已绑定到 `sandbox.create` 阶段,可以报告分配实例、拉镜像或恢复沙箱快照的状态:
311
+
312
+ ```ts
313
+ import { defineSandbox } from "niceeval/sandbox";
314
+
315
+ export default defineSandbox({
316
+ name: "modal",
317
+ recommendedConcurrency: 8,
318
+ async create({ timeout, runtime, feedback }) {
319
+ feedback.progress({ message: "分配 Modal Sandbox" });
320
+ const instance = await allocateModal({ timeout, runtime });
321
+
322
+ if (instance.usedFallbackRegion) {
323
+ feedback.diagnostic({
324
+ code: "modal-fallback-region",
325
+ level: "warning",
326
+ message: `主区域不可用,使用 ${instance.region}`,
327
+ data: { region: instance.region },
328
+ });
329
+ }
330
+
331
+ return new ModalSandbox(instance);
332
+ },
333
+ });
334
+ ```
88
335
 
89
- 只要实现 `Sandbox` 接口,就可以接入其他 sandbox provider。adapter 仍然只依赖接口,不依赖 provider 私有 API。
336
+ 返回值实现 `Sandbox` 接口即可。Provider SDK 的原始日志不要直接写宿主进程的 `stdout` / `stderr`;短期状态走 `feedback.progress`,需要保留的问题走 `feedback.diagnostic`,无法创建环境时抛错。这样 Human dashboard 不会被日志打散,CI 也能保持单一有序输出。
90
337
 
91
338
  ## 权限和 root
92
339
 
@@ -94,9 +341,10 @@ Vercel Sandbox 适合需要云端隔离、更多资源或更稳定环境的任
94
341
 
95
342
  ## 性能建议
96
343
 
344
+ - 把稳定重依赖做进 image/template/snapshot,不在每个 Attempt 的 `.setup()` 重装。
97
345
  - 减少 fixture 依赖体积。
98
- - 使用缓存或预热机制。
99
- - 控制 `sandboxConcurrency`,避免本地 Docker 资源耗尽。
346
+ - 对动态内容使用小而明确的缓存或预检。
347
+ - 控制 `maxConcurrency`(experiment 字段或 `--max-concurrency`),避免本地 Docker 资源耗尽。
100
348
  - 把慢测试拆成必要的 gate 和可选的 soft 检查。
101
349
 
102
350
  Warm pools 和复用属于 runner / scheduler 层面的能力,详见 [Runner](/zh/guides/runner)。
@@ -10,7 +10,7 @@ description: "用 NiceEval 的五种评分机制评估任意 eval:值断言、
10
10
 
11
11
  | 目标 | 推荐机制 |
12
12
  |---|---|
13
- | 回复包含固定字段 | `includes` / `matches` |
13
+ | 回复包含固定子串或命中正则 | `includes` |
14
14
  | 结构化 JSON 完全匹配 | `equals` |
15
15
  | 工具调用是否发生 | `t.calledTool` / `t.notCalledTool` |
16
16
  | 输出是否语义正确 | `t.judge.*` |
@@ -20,14 +20,14 @@ description: "用 NiceEval 的五种评分机制评估任意 eval:值断言、
20
20
  ## 值断言
21
21
 
22
22
  ```ts
23
- import { includes, equals, matches } from "niceeval/expect";
23
+ import { includes, equals } from "niceeval/expect";
24
24
 
25
25
  t.check(t.reply, includes("refund"));
26
26
  t.check(turn.data, equals({ intent: "refund" }));
27
- t.check(t.reply, matches(/order #\d+/));
27
+ t.check(t.reply, includes(/order #\d+/));
28
28
  ```
29
29
 
30
- 值断言适合精确、稳定、低歧义的结果。
30
+ `includes` 同时接受子串和正则;`matches` 只接受 Standard Schema / Zod schema,用于结构校验,不做正则匹配。值断言适合精确、稳定、低歧义的结果。
31
31
 
32
32
  ## 作用域断言
33
33
 
@@ -4,29 +4,26 @@ sidebarTitle: "查看结果"
4
4
  description: "每次运行写入 .niceeval/ 的结构化 artifact 有两扇门:niceeval show 在终端用 @<locator> 精确定位一次 Attempt,逐级下钻 Eval 源码、执行记录与 diff;niceeval view 在网页看同一份证据。"
5
5
  ---
6
6
 
7
- 每次运行后,[NiceEval](https://niceeval.com/) 都会把结构化结果写入该实验的**结果快照**目录 `.niceeval/<experiment>/<快照>/`。看结果有两扇门,读的是同一批 artifact:`niceeval show` 是终端读法,人和 coding agent 都能用;`niceeval view` 是网页读法,适合人浏览证据。两扇门共用判定公式与选结果规则:对每个 experiment 的每道 eval,取时间上最新的那份判定,同一个 experiment 跨多次运行拼出来。默认首页针对媒介不同:`show` 先给可下钻的 Attempt 表,`view` 先给图表分析;传入同一个 `--report` 文件时则共用报告组件与数据口径。
7
+ 每次运行后,[NiceEval](https://niceeval.com/) 都会把结构化结果写入该实验的**结果快照**目录 `.niceeval/<experiment>/<快照>/`。看结果有两扇门,读的是同一批 artifact:`niceeval show` 是终端读法,人和 coding agent 都能用;`niceeval view` 是网页读法,适合人浏览证据。两扇门共用判定公式、选结果规则和默认报告:对每个 experiment 的每道 eval,取时间上最新的那份判定,同一个 experiment 跨多次运行拼出来;再按 experiment id 的父目录分组,只在同组内比较。`show` 输出文本面,`view` 输出网页面。
8
8
 
9
9
  ## 控制台输出
10
10
 
11
11
  ```text
12
- Discovered 3 evals
13
-
14
- classify (12ms)
15
- weather/brooklyn (456ms)
16
- ✗ api-validation (38s)
17
- - gate: expected src/routes/users.ts to include .safeParse() [FAILED]
18
-
19
- Failing:
20
- ✗ api-validation
21
- gate fileIncludes("src/routes/users.ts", ".safeParse()")
22
- niceeval show api-validation
23
-
24
- Results: 2 passed, 1 failed, 0 errored, 0 skipped
25
- Structured results: .niceeval/compare_web-agent/2026-07-09T10-00-00-000Z-x1f2/
26
- (snapshot.json + 每 attempt 的 result.json / events.json / trace.json / diff.json)
12
+ Plan: 45 attempts · 9 evals × 5 configs · concurrency 19
13
+ ✗ @12h8m4k1 fixtures/button [compare/claude-e2b] errored · sandbox.create
14
+ sandbox-rate-limit: E2B sandbox allocation failed after 5 attempts
15
+ Inspect: niceeval show @12h8m4k1
16
+
17
+ niceeval exp compare 2m 14s
18
+ 45 total · 6 reused · 19 running · 12 queued · 8 completed $0.84
19
+
20
+ ACTIVE
21
+ memory/agent-029-use-cache compare/bub-e2b 1m 42s running tests
22
+ memory/agent-030-app-route compare/codex 1m 18s editing src/app.ts
23
+ … 17 more active
27
24
  ```
28
25
 
29
- 运行中每个 eval 一条流式行,失败断言内联展开;运行结束的收尾块把失败集中重放一遍,每条自带下钻命令,末尾是本次实验的快照目录。这段输出怎么进「跑→读→修→再跑」的循环,见 [Agent 反馈闭环](/zh/guides/agent-feedback-loop)。
26
+ Human profile 只原位更新当前总数和 active slots,不把历史帧推入 scrollback。失败、错误和去重后的 diagnostic 永久追加,并带 locator。结束块只保留摘要、失败 locator、下钻命令和结果路径。这段输出怎么进「跑→读→修→再跑」的循环,见 [Agent 反馈闭环](/zh/guides/agent-feedback-loop)。
30
27
 
31
28
  ## `.niceeval/<experiment>/<快照>/`
32
29
 
@@ -38,7 +35,7 @@ Structured results: .niceeval/compare_web-agent/2026-07-09T10-00-00-000Z-x1f2/
38
35
  └─ 2026-07-09T10-00-00-000Z-x1f2/ # 快照目录:时间戳 + 随机后缀
39
36
  ├─ snapshot.json # 快照元数据(开始时写,收尾补 completedAt)
40
37
  └─ weather-tool/a0/ # 单个 eval attempt 的目录
41
- ├─ result.json # 判决、断言、用量 —— attempt 完成时一次写成
38
+ ├─ result.json # 判定、断言、结构化错误/diagnostics、用量
42
39
  ├─ events.json
43
40
  ├─ sources.json
44
41
  ├─ trace.json
@@ -51,39 +48,44 @@ Structured results: .niceeval/compare_web-agent/2026-07-09T10-00-00-000Z-x1f2/
51
48
  `niceeval show` 的位置参数有两种形态:eval id 前缀选「看哪些 eval」,`@<locator>` 精确指名「看哪一次 Attempt」。flag 选「看哪个切面」:
52
49
 
53
50
  ```bash
54
- niceeval show # 榜单:每个 Attempt 的判定、eval、locator、短原因、耗时与成本
51
+ niceeval show # 默认报告:按实验组分区,组内比较配置并下钻到 Attempt
55
52
  niceeval show weather # 前缀过滤:weather/* 下每个 eval 的判定
56
53
  niceeval show weather/brooklyn # 单个 eval:各 experiment 的 attempt 行(含 @<locator>)、默认 attempt 的断言明细
57
54
  niceeval show @1k2m9qrs # 精确到一次 Attempt:紧凑全景(断言/执行/diff 摘要 + 可用证据一览)
58
55
  niceeval show @1k2m9qrs --eval # 该 Attempt 运行时保存的 Eval 源码,断言标回源码行
59
56
  niceeval show @1k2m9qrs --execution # 该 Attempt 的消息、thinking、Skill 加载、工具调用,有 OTel 时补时间
57
+ niceeval show @1c3h6twx --timing # 有界诊断时间树:phase、hook、operation、shell、turn、OTel 与收尾
58
+ niceeval show @1c3h6twx --timing=full # 同一棵时间树逐节点完整展开
60
59
  niceeval show @1c3h6twx --diff # sandbox 里的文件改动
61
60
  niceeval show weather/brooklyn --history # 跨 run 时间轴:抖动与回归拐点
62
61
  ```
63
62
 
64
- `@<locator>` 是一次 Attempt 的持久、不透明身份,形如 `@1k2m9qrs`:由 `{experimentId, 快照时刻, evalId, attempt 序号}` 这个身份元组确定性派生,同一份落盘结果反复解析恒得到同一个值。它出现在 `niceeval show` 的紧凑索引里,直接复制粘贴就能定位到那一次运行。裸 `show` 的 locator 列只打印 `@<id>`,不追加判定或证据能力缩写;打开 Attempt 首页后,`available` 会列出实际可用的证据命令。
63
+ `@<locator>` 是一次 Attempt 的持久、不透明身份,形如 `@1k2m9qrs`:由 `{experimentId, 快照时刻, evalId, attempt 序号}` 这个身份元组确定性派生,同一份落盘结果反复解析恒得到同一个值。它出现在 `niceeval show` 的分组明细里,直接复制粘贴就能定位到那一次运行。列表里的 locator 只打印 `@<id>`,不追加判定或证据能力缩写;打开 Attempt 首页后,`available` 会列出实际可用的证据命令。
64
+
65
+ Sandbox 创建、setup 或 teardown 错误不依赖 trace。`result.json` 保存错误的 `code`、`message`、生命周期阶段(`phase`)、有限 cause/stack 和 diagnostics;trace 只在存在时补调用关系与耗时。Attempt 在 cleanup、teardown 和 sandbox stop 结束后才封口写入,因此收尾 diagnostic 也能回顾。
65
66
 
66
67
  同一个实验多次跑会留下多份结果,不带 `@<locator>` 的默认视图只回答一个问题——「现在整体怎样」:每个 experiment × eval 取时间上最新的那份判定,同一个 experiment 跨多次运行拼出来。按前缀只重跑了部分 eval 时,其余 eval 的判定从更早的运行补齐,报告永远是全局最新,不会因为一次局部重跑变残缺。合成是有标注的:每份判定都带上它产生的时间,能看出报告是从哪几次运行拼出来的。合成结果可能混着不同版本的被测代码——所以收工判定以 `--force` 全量重跑为准,迭代途中的 `show` 负责快、收尾的全量跑负责真。
67
68
 
68
- 单个 eval 视图里,多 experiment、多 Attempt 时的断言明细块默认挑最新一次失败的 Attempt 展开;没有失败就挑最新一次。这只是一个默认展开的启发式,不是精确选择——需要精确看某一次 Attempt,复制那一行的 `@<locator>` 直接 `show` 它即可。`--experiment` Selection 收窄到一个实验,`--run <目录>` 钉死看某一次历史 run,`--history` 看跨 run 趋势。不带 `--report` `show` 渲染专用 Attempt 索引;`--report <文件>` 换成你自己的报告,同一个文件也能喂给 `view`。位置前缀、`--run`、`--experiment` 对 `--report` 同样生效;`--history` 与 `--report` 互斥。
69
+ 单个 eval 视图里,多 experiment、多 Attempt 时的断言明细块默认挑最新一次失败的 Attempt 展开;没有失败就挑最新一次。这只是一个默认展开的启发式,不是精确选择——需要精确看某一次 Attempt,复制那一行的 `@<locator>` 直接 `show` 它即可。`--experiment compare` 按路径段前缀把 Selection 收窄到整个 `compare` 组,`--experiment compare/bub` 只留一个 experiment;`--run <目录>` 钉死看某一次历史 run,`--history` 看跨 run 趋势。`--report <文件>` 同时替换 `show` / `view` 的默认报告。位置前缀、`--run`、`--experiment` 对自定义报告同样生效;`--history` 与 `--report` 互斥。
69
70
 
70
71
  `niceeval view` 的每个视图都有 CLI 对应物:
71
72
 
72
73
  | `niceeval view` 里的视图 | 终端对应 |
73
74
  | --- | --- |
74
- | 当前结果的同构 Attempt 表 | `niceeval show` |
75
+ | 当前结果的分组比较报告 | `niceeval show` |
75
76
  | 该 eval 各 experiment 的判定行 + 默认 attempt 的断言明细 | `niceeval show <eval id>` |
76
77
  | 单次 Attempt 的紧凑全景(断言、执行、diff 摘要与证据可用性) | `niceeval show @<locator>` |
77
78
  | Eval 源码标注(断言标回源码行) | `niceeval show @<locator> --eval` |
78
79
  | AI 对话、thinking、Skill 加载与工具调用(有 OTel 时补时间) | `niceeval show @<locator> --execution` |
80
+ | 单次 Attempt 的阶段耗时分解 | `niceeval show @<locator> --timing`;逐节点审计用 `--timing=full` |
79
81
  | 文件改动 | `niceeval show @<locator> --diff` |
80
82
  | 历史 run 列表 | `niceeval show <eval id> --history`;钉死某一次用 `--run <目录>` |
81
83
 
82
- ### 默认 Attempt 索引
84
+ ### 默认分组比较报告
83
85
 
84
- 不带 flag 的 `niceeval show` 先按 experiment 打印摘要,再用同构表列出每个 Attempt。列固定为 `STATUS / EVAL / ATTEMPT / RESULT / DURATION / COST`:四态判定同时给图标与完整单词,locator 只保留 `@<id>`,失败原因优先显示期望值/实际值或命令退出码。完整断言和 evidence 留在 `niceeval show @<locator>`。
86
+ 不带 flag 的 `niceeval show` 如果命中多个可比组,只列组索引和可直接执行的 `niceeval show --experiment <group>` 命令;Selection 只剩一个组时才输出该组的成本 × 端到端成功率图和实验列表。组的边界来自 experiment id 的完整父目录:`compare/*` `dev-e2b/*` 不共享坐标系、连线、排序或汇总数字;顶层 experiment 各自形成单例组。
85
87
 
86
- 只有一个 experiment 时直接显示它的表,不出现“至少两个实验才能比较”的无关空态。有两个及以上 experiment 时,前面增加紧凑比较表;成本 × 通过率散点留给 `niceeval view`。快照未完成、过旧或覆盖不全的 warning 位于索引前;自定义报告摆了 `RunOverview` 时,同一条 warning 也只显示一次。
88
+ 组内实验列表先给固定列汇总,再按 experiment Eval Attempt 展开。locator 只保留 `@<id>`,失败原因优先显示期望值/实际值或命令退出码;完整断言和 evidence 留在 `niceeval show @<locator>`。某组只有一个 experiment 时散点仍显示单点,不出现“至少两个实验才能比较”的空态。快照未完成、过旧或覆盖不全的 warning 位于组索引前,同一条 warning 只显示一次。
87
89
 
88
90
  ### 单个 eval
89
91
 
@@ -91,8 +93,8 @@ niceeval show weather/brooklyn --history # 跨 run 时间轴:抖动与回
91
93
  $ niceeval show weather/brooklyn
92
94
  weather/brooklyn — 布鲁克林天气查询
93
95
 
94
- compare/bub-gpt-5.4 ✓ passed 1 attempt 38.0s $0.03 (2h ago) @1xqur9kx[E,X,⏱]
95
- compare/codex-gpt-5.4 ✗ failed 3 attempts 41.2s $0.12 (40s ago) @1k2m9qrs[E,X,⏱] gate calledTool("get_weather")
96
+ compare/bub-gpt-5.4 ✓ passed 1 attempt 38.0s $0.03 (2h ago) @1xqur9kx
97
+ compare/codex-gpt-5.4 ✗ failed 3 attempts 41.2s $0.12 (40s ago) @1k2m9qrs gate calledTool("get_weather")
96
98
 
97
99
  attempt 3 · compare/codex-gpt-5.4 · failed · 41.2s · 12.3k tokens · $0.04
98
100
  ✗ gate calledTool("get_weather") — tool was never called
@@ -101,7 +103,7 @@ attempt 3 · compare/codex-gpt-5.4 · failed · 41.2s · 12.3k tokens · $0.04
101
103
 
102
104
  artifacts: .niceeval/compare_codex-gpt-5.4/2026-07-09T10-00-00-000Z-x1f2/weather/brooklyn/a2/
103
105
  attempt locator: @1k2m9qrs
104
- next: niceeval show @1k2m9qrs [--eval|--execution|--diff]
106
+ next: niceeval show @1k2m9qrs [--eval|--execution]
105
107
  ```
106
108
 
107
109
  每个 experiment 一行的紧凑索引末尾就带着代表 attempt 的 `@<locator>`(`compare/bub-gpt-5.4` 只有一次 attempt 就代表它自己,`compare/codex-gpt-5.4` 代表的是默认展开的那一次失败);展开的明细块结尾再重复一次精确的 `attempt locator:`,两处给的是同一个值。想看别的 attempt(比如 `compare/codex-gpt-5.4` 更早失败的那次),去 `--history` 或 artifact 目录里找到它的 locator,`niceeval show @<locator>` 直接跳过去,不需要先回到这张列表。
@@ -126,20 +128,22 @@ failures:
126
128
  source: evals/memory/swelancer-manager-proposals.eval.ts:40:11
127
129
 
128
130
  execution: 2 events · 0 skill loads · 0 tool calls · 1 AI messages
129
- timing: OTel spans recorded for this attempt — see --execution for per-step timing.
131
+ timing: eval.run 40.8s · scoring.evaluate 0.3s
130
132
 
131
133
  changes: diff unavailable · no workspace diff was recorded for this attempt
132
134
 
133
- evidence: eval source [E] · execution [X] · OTel timing [⏱]
134
135
  artifacts: .niceeval/compare_codex-gpt-5.4/2026-07-09T10-00-00-000Z-x1f2/weather/brooklyn/a2/
135
- next: niceeval show @1k2m9qrs [--eval|--execution|--diff]
136
+ available:
137
+ niceeval show @1k2m9qrs --eval
138
+ niceeval show @1k2m9qrs --execution
139
+ niceeval show @1k2m9qrs --timing
136
140
  ```
137
141
 
138
142
  这份全景只给摘要,不复现某个切面的完整内容——完整源码、完整事件流或完整文件列表分别要对应的证据 flag 才给。`changes` 一行永远说明 diff 不可用的具体原因:这里是「这次 Attempt 从未收集过 diff」(`weather/brooklyn` 不是 sandbox eval,压根不会有工作区改动可收集);如果是 sandbox eval 但 agent 确实没碰任何文件,会是另一句「没有产生工作区文件改动」;两种「不可用」原因不同,不共用一句含糊的提示。
139
143
 
140
- ### `--eval`、`--execution`、`--diff`:三个证据切面
144
+ ### `--eval`、`--execution`、`--timing`、`--diff`:四个证据切面
141
145
 
142
- 三个证据 flag 既可以加在 `@<locator>` 后面(精确到一次 Attempt),也可以加在能唯一收窄到一个 eval 的前缀后面(挑同一套默认启发式选中的 attempt——最新一次失败,没有失败就挑最新一次,与单 eval 详情块展开的是同一次);前缀撞到不止一个 eval 时会报错,报错正文直接给出每个候选 eval 的 `@<locator>`,照抄一个继续即可。可以同时传多个证据 flag,一次输出全部要看的切面。
146
+ 四个证据 flag 既可以加在 `@<locator>` 后面(精确到一次 Attempt),也可以加在能唯一收窄到一个 eval 的前缀后面(挑同一套默认启发式选中的 attempt——最新一次失败,没有失败就挑最新一次,与单 eval 详情块展开的是同一次);前缀撞到不止一个 eval 时会报错,报错正文直接给出每个候选 eval 的 `@<locator>`,照抄一个继续即可。可以同时传多个证据 flag,一次输出全部要看的切面。
143
147
 
144
148
  `--eval` 是运行时保存的 Eval 源码,按行标注每条断言、gate 失败与 soft 分数直接排在对应源码行下面,源码里没能对应到具体行的断言(没有位置信息,或位置指向另一个文件)单独成一段,永不丢弃:
145
149
 
@@ -230,6 +234,45 @@ timing unavailable · OTel trace was not collected
230
234
  full events: .niceeval/compare_codex-gpt-5.4/2026-07-09T10-00-00-000Z-x1f2/weather/brooklyn/a2/events.json
231
235
  ```
232
236
 
237
+ `--timing` 回答「整个 Attempt 的时间花在哪里」。runner 把 lifecycle、setup/teardown hook、批量工作的 operation、所有经 Sandbox API 发出的 shell 命令,以及每个 session/turn 的 send 墙钟包络记进 `result.json`;某轮有 OTel 时,再按 `traceId` 把 agent/model/tool span 挂到该轮下面。没有 OTel 时,phase、hook、operation、shell 与 turn 时间仍完整,只缺轮内细分。出错或超时的 Attempt 在已知的最深节点用 `✗` 标出死在哪里:
238
+
239
+ ```text
240
+ $ niceeval show @1c3h6twx --timing
241
+ @1c3h6twx · fixtures/button · compare/bub-gpt-5.4 · errored
242
+ total 2m 4s
243
+
244
+ sandbox.queue 0.3s
245
+ sandbox.create 8.2s
246
+ sandbox.setup 21.6s
247
+ ├─ restoreCache 18.9s
248
+ │ └─ shell · tar xzf … 18.8s
249
+ └─ setup#2 2.7s
250
+ └─ shell · pnpm config set … 2.7s
251
+ workspace.baseline 0.2s
252
+ └─ shell · git init && git commit … 0.2s
253
+ agent.setup 41.5s
254
+ ├─ shell · npm install -g @openai/codex… 39.8s
255
+ └─ shell · write ~/.codex/config.toml 1.7s
256
+ eval.run 50.9s
257
+ └─ turn s1/t1 50.9s ✗ agent-runtime-error
258
+ └─ shell · codex exec … 50.7s
259
+ ├─ agent · codex.exec 50.5s OTel
260
+ └─ model · chat 44.2s OTel
261
+
262
+ teardown (not counted in total):
263
+ agent.teardown 0.4s
264
+ sandbox.teardown 3.1s
265
+ └─ persistCache 3.1s
266
+ └─ shell · tar czf … 3.0s
267
+ sandbox.stop 1.2s
268
+ ```
269
+
270
+ 收尾阶段不计入 Attempt 总耗时,单独分组列出——「判定早就出了、进程还在等收尾」这类问题看这一组。缩进表示包含关系,不表示子项可以相加:命令、turn 和 OTel span 都可能嵌套或并发。
271
+
272
+ 裸 `--timing` 会完整列出 phase,并把 phase 下的细节控制在 80 个节点内。超过上限时,它优先保留失败路径、最慢节点与首尾时序,在省略位置显示节点数、未展示的失败数,并给出 `--timing=full`。小树中两种模式输出相同;`--timing=full` 不设节点上限,适合审计旧结果或把完整树重定向到文件。NiceEval 不自动启动 pager,因此管道、CI 和 coding agent 不会等待键盘输入。命令可能并发,省略行不会把子节点耗时相加。
273
+
274
+ operation 的名称由执行这段工作的组件在采集时写入。例如一次 workspace diff 导出可以显示成 `export workspace diff · 1 window · 3,302 files`,下面只有一条真实的批量 shell。展示层不会解析 `git show` 等命令文本、猜出一个 `git show ×N` 分组;如果旧 artifact 确实记录了几千次调用,默认模式有界摘要,`--timing=full` 仍能逐条核对。一次超时到底是 Sandbox 创建慢、某条安装命令慢,还是一轮里的模型/工具慢,`--timing` 一眼可判。旧结果或第三方写入的结果没有阶段数据时,两种模式都如实提示 `phase timing unavailable`。
275
+
233
276
  `--diff` 默认给文件级摘要(落盘的 diff 只有改动后的全文,没有基线可比对增删行数,所以每个改动过的文件都标 `M`、后面跟它现在的行数,不区分新建与修改);看单个文件的完整内容用 `--diff=<文件路径>`——路径必须用 `=` 连写,位置参数永远留给 eval id 前缀 / `@<locator>`:
234
277
 
235
278
  ```text
@@ -254,12 +297,12 @@ $ niceeval show @1c3h6twx --diff=src/components/Button.tsx
254
297
  2. 运行 niceeval show <eval-id>,读取该题各 experiment 的判定、默认 attempt 的断言明细,
255
298
  以及每行末尾的 @<locator>。
256
299
  3. 运行 niceeval show @<locator>,看紧凑全景确认这次 Attempt 实际捕获到了哪些证据。
257
- 4. 按问题选择 --eval、--execution 或 --diff;可以同时传多个证据 flag。
300
+ 4. 按问题选择 --eval、--execution、--timing 或 --diff;可以同时传多个证据 flag。
258
301
  5. 输出被截断时,读取末尾标出的 sources.json、events.json、trace.json 或 diff.json 原始路径。
259
302
  6. 修复后重跑该 eval,再用 niceeval show <eval-id> 验证新的 @<locator>;收工前用 --force 全量重跑确认整体结果。
260
303
  ```
261
304
 
262
- `--eval` 回答“这道题实际检查了什么、哪条断言为什么过或没过”,`--execution` 回答“agent 做了什么、调用了什么、时间花在哪里”,`--diff` 回答“沙箱里的文件实际改成了什么”。先按问题选证据,不需要每次把三份都读完。
305
+ `--eval` 回答“这道题实际检查了什么、哪条断言为什么过或没过”,`--execution` 回答“agent 做了什么、调用了什么”(可关联时附 OTel 时间),`--timing` 回答“整个 Attempt 的时间花在哪里”,`--diff` 回答“沙箱里的文件实际改成了什么”。先按问题选证据,不需要每次把四份都读完。
263
306
 
264
307
  ### 历史与抖动:`--history`
265
308
 
@@ -292,9 +335,9 @@ npx niceeval view
292
335
  失败后立刻运行 `npx niceeval view`,可以直接打开刚刚那次运行的 artifacts。
293
336
  </Tip>
294
337
 
295
- `view` 的首页是一份报告:不传 `--report` 时是成本 × 通过率散点图与逐实验表,适合人在网页比较;传了 `--report` 就换成你自己的报告(与 `show --report` 吃同一个文件),见[自定义报告](/zh/guides/custom-reports)。裸 `show` 则使用更适合终端 agent 循环的 Attempt 索引。证据部分(Attempt 弹窗、transcript、trace 瀑布、run 列表)始终保留。
338
+ `view` 的首页是一份报告:不传 `--report` 时完整加载当前结果并显示全部可比组索引,选中一组后只显示该组的成本 × 端到端成功率散点图与实验表。切组只改变页面状态,不重新读取或计算;不同组不会混进同一张图或榜单。浏览器禁用 JS 时,每组作为独立的 `<details>` 完整可读;启用 JS 后一次聚焦一组。传了 `--report` 就换成你自己的报告(与 `show --report` 吃同一个文件),见[自定义报告](/zh/guides/custom-reports)。证据部分(Attempt 弹窗、transcript、trace 瀑布、run 列表)始终保留。
296
339
 
297
- 网页版多几样浏览操作:点表头就地排序、在榜单的过滤框里筛行、点开一个 experiment 行看它每道题的判定与原因、悬停散点看数值——这些只影响眼前的视图,不改判定口径,刷新即恢复。报告上方有 **Copy fix prompt** 按钮,把全部失败打包成可直接粘给 coding agent 的修复 prompt(attempt 弹窗里有单条版)。报告文案有中英两份,随界面语言切换。
340
+ 网页版多几样浏览操作:切换可比组、点表头就地排序、在当前组的过滤框里筛行、点开一个 experiment 行看它每道题的判定与原因、悬停散点看数值——这些只影响眼前的视图,不改判定口径,刷新即恢复。Attempt 弹窗里有与 `show --timing` 同源的统一时间树:Sandbox 启动、setup hook 及其 shell、agent 安装命令、每轮 send 与可关联的 OTel model/tool、评分与收尾都能逐层展开。报告上方有 **Copy fix prompt** 按钮,把全部失败打包成可直接粘给 coding agent 的修复 prompt(attempt 弹窗里有单条版)。报告文案有中英两份,随界面语言切换。
298
341
 
299
342
  ## 导出与静态托管
300
343
 
@@ -317,7 +360,7 @@ NiceEval 写入 `<dir>/index.html`,并把查看器要读取的 artifact(`sou
317
360
  - `diff.json` 和 `o11y.json` 不会被复制。查看器不读取它们,且 diff 可能达到上百 MB。
318
361
  - 用 `file://` 直接打开 `index.html` 时浏览器不允许 fetch artifact,代码视图会提示源码不可用。本地预览用 http 服务打开。
319
362
 
320
- 最简单的流程是在跑过 eval 的机器上导出,把产物目录直接部署,不需要额外步骤。要让站点随 push 自动更新,把 `.niceeval/` 提交进仓库、CI 上一行 `view --out` 导出——workflow 与平台接线见[通过 CI 发布报告](/zh/guides/publish-report)。
363
+ 最简单的流程是在跑过 eval 的机器上导出,把产物目录直接部署,不需要额外步骤。要让站点随 push 自动更新,先用 `copySnapshots` 生成经过单文件预算检查的发布结果根,把它提交进仓库,再让 CI 对这个目录运行 `view --run <目录> --out`——workflow 与平台接线见[通过 CI 发布报告](/zh/guides/publish-report)。
321
364
 
322
365
  ## Artifact 说明
323
366
 
@@ -327,7 +370,7 @@ NiceEval 写入 `<dir>/index.html`,并把查看器要读取的 artifact(`sou
327
370
 
328
371
  ### `result.json`
329
372
 
330
- 单个 attempt 的权威记录:判定、断言、耗时、用量、成本。attempt 完成时一次写成,之后不再改写。
373
+ 单个 Attempt 的权威记录:判定、断言、正式阶段耗时、结构化 error、去重后的 diagnostics、用量和成本。cleanup、teardown 与 Sandbox stop 完成后一次写成,之后不再改写。`progress` 的短期 message/current/total 不落盘。
331
374
 
332
375
  ### `events.json`
333
376
 
@@ -343,7 +386,7 @@ eval 源码位置和断言位置,用于查看器把失败断言对应回代码
343
386
 
344
387
  ### `trace.json`
345
388
 
346
- OTLP trace 或标准化后的 span 数据。
389
+ OTLP trace 或标准化后的 span 数据。它是可选的执行关系和耗时证据,不是错误存储:沙箱创建(`sandbox.create`)可能早于 telemetry,teardown 可能晚于 trace collect。
347
390
 
348
391
  ### `o11y.json`
349
392
 
@@ -364,6 +407,7 @@ Soft 断言的分数记录在 `assertions[].score` 里;非 `--strict` 模式
364
407
 
365
408
  - 榜单里失败/错误的题每条自带下钻命令:`niceeval show <eval id>` 先看断言明细,行尾的 `@<locator>` 可以直接精确下钻到某一次 Attempt。
366
409
  - 想知道 eval 实际检查了什么、断言为什么过或没过,看 `--eval`(标回源码行的断言标注)。
367
- - 想知道 agent 做了什么、调用了什么、时间花在哪里,看 `--execution`(或 view 的证据面板)。
410
+ - 想知道 agent 做了什么、调用了什么,看 `--execution`;关联成功的 OTel 时间会贴在同一事件旁。
368
411
  - coding-agent 失败时看 `--diff` 和 `--execution` 里的工具调用;两者结合能看出「改错了文件」还是「压根没调用该调用的工具」。
412
+ - Sandbox eval 跑得慢或超时,先看 `--timing`:排队、Sandbox 启动、setup hook 里的每条 shell、agent 安装命令、每轮 send 与可关联的 OTel model/tool 各花多久,一眼可判;收尾卡住也在这里单独列出。
369
413
  - 判断缺陷在被测程序还是 eval 本身、修完怎么重跑,流程见 [Agent 反馈闭环](/zh/guides/agent-feedback-loop)。