niceeval 0.9.0 → 0.10.0

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 (428) hide show
  1. package/INDEX.md +31 -33
  2. package/dist/agents/types.d.ts +20 -17
  3. package/dist/context/types.d.ts +21 -21
  4. package/dist/i18n/en.d.ts +10 -2
  5. package/dist/i18n/zh-CN.d.ts +15 -7
  6. package/dist/o11y/execution-tree.d.ts +103 -0
  7. package/dist/o11y/otlp/select.d.ts +22 -0
  8. package/dist/report/{react → assets}/colors.d.ts +8 -0
  9. package/dist/report/{react → assets}/colors.js +23 -0
  10. package/dist/report/built-in/index.d.ts +2 -2
  11. package/dist/report/built-in/index.js +2 -2
  12. package/dist/report/built-in/standard.d.ts +8 -1
  13. package/dist/report/built-in/standard.js +17 -7
  14. package/dist/report/components/attempt-detail/AttemptAssertions.d.ts +6 -0
  15. package/dist/report/components/attempt-detail/AttemptAssertions.js +17 -0
  16. package/dist/report/components/attempt-detail/AttemptConversation.d.ts +6 -0
  17. package/dist/report/components/attempt-detail/AttemptConversation.js +34 -0
  18. package/dist/report/components/attempt-detail/AttemptDiagnostics.d.ts +6 -0
  19. package/dist/report/components/attempt-detail/AttemptDiagnostics.js +7 -0
  20. package/dist/report/components/attempt-detail/AttemptDiff.d.ts +6 -0
  21. package/dist/report/components/attempt-detail/AttemptDiff.js +11 -0
  22. package/dist/report/components/attempt-detail/AttemptError.d.ts +6 -0
  23. package/dist/report/components/attempt-detail/AttemptError.js +16 -0
  24. package/dist/report/components/attempt-detail/AttemptFixPrompt.d.ts +6 -0
  25. package/dist/report/components/attempt-detail/AttemptFixPrompt.js +7 -0
  26. package/dist/report/components/attempt-detail/AttemptSource.d.ts +6 -0
  27. package/dist/report/components/attempt-detail/AttemptSource.js +27 -0
  28. package/dist/report/components/attempt-detail/AttemptSummary.d.ts +8 -0
  29. package/dist/report/components/attempt-detail/AttemptSummary.js +20 -0
  30. package/dist/report/components/attempt-detail/AttemptTimeline.d.ts +6 -0
  31. package/dist/report/components/attempt-detail/AttemptTimeline.js +28 -0
  32. package/dist/report/components/attempt-detail/AttemptTrace.d.ts +6 -0
  33. package/dist/report/components/attempt-detail/AttemptTrace.js +29 -0
  34. package/dist/report/components/attempt-detail/AttemptUsage.d.ts +6 -0
  35. package/dist/report/components/attempt-detail/AttemptUsage.js +20 -0
  36. package/dist/report/components/attempt-detail/compute.d.ts +24 -0
  37. package/dist/report/components/attempt-detail/compute.js +254 -0
  38. package/dist/report/components/attempt-detail/faces.d.ts +14 -0
  39. package/dist/report/components/attempt-detail/faces.js +235 -0
  40. package/dist/report/components/attempt-detail/index.d.ts +38 -0
  41. package/dist/report/components/attempt-detail/index.js +527 -0
  42. package/dist/report/{react → components}/cell.d.ts +5 -3
  43. package/dist/report/{react → components}/cell.js +3 -3
  44. package/dist/report/{react → components/entity-lists}/AttemptList.d.ts +8 -4
  45. package/dist/report/{react → components/entity-lists}/AttemptList.js +16 -10
  46. package/dist/report/{react → components/entity-lists}/EvalList.d.ts +3 -3
  47. package/dist/report/{react → components/entity-lists}/EvalList.js +0 -0
  48. package/dist/report/{react → components/entity-lists}/ExperimentList.d.ts +3 -3
  49. package/dist/report/{react → components/entity-lists}/ExperimentList.js +10 -10
  50. package/dist/report/components/entity-lists/compute.d.ts +23 -0
  51. package/dist/report/components/entity-lists/compute.js +171 -0
  52. package/dist/report/components/entity-lists/faces.d.ts +5 -0
  53. package/dist/report/components/entity-lists/faces.js +174 -0
  54. package/dist/report/components/entity-lists/index.d.ts +51 -0
  55. package/dist/report/components/entity-lists/index.js +165 -0
  56. package/dist/report/{react → components}/fixtures.d.ts +2 -2
  57. package/dist/report/{react → components}/fixtures.js +1 -1
  58. package/dist/report/{react → components/metric-views}/DeltaTable.d.ts +2 -2
  59. package/dist/report/{react → components/metric-views}/DeltaTable.js +4 -4
  60. package/dist/report/{react → components/metric-views}/MetricBars.d.ts +3 -3
  61. package/dist/report/{react → components/metric-views}/MetricBars.js +3 -3
  62. package/dist/report/{react → components/metric-views}/MetricLine.d.ts +2 -2
  63. package/dist/report/{react → components/metric-views}/MetricLine.js +8 -5
  64. package/dist/report/{react → components/metric-views}/MetricMatrix.d.ts +3 -3
  65. package/dist/report/{react → components/metric-views}/MetricMatrix.js +4 -4
  66. package/dist/report/components/metric-views/MetricScatter.d.ts +11 -0
  67. package/dist/report/{react → components/metric-views}/MetricScatter.js +26 -23
  68. package/dist/report/{react → components/metric-views}/MetricTable.d.ts +3 -3
  69. package/dist/report/{react → components/metric-views}/MetricTable.js +4 -4
  70. package/dist/report/{react → components/metric-views}/Scoreboard.d.ts +2 -2
  71. package/dist/report/{react → components/metric-views}/Scoreboard.js +3 -3
  72. package/dist/report/components/metric-views/compute.d.ts +89 -0
  73. package/dist/report/{compute.js → components/metric-views/compute.js} +32 -415
  74. package/dist/report/components/metric-views/faces.d.ts +13 -0
  75. package/dist/report/components/metric-views/faces.js +381 -0
  76. package/dist/report/components/metric-views/index.d.ts +50 -0
  77. package/dist/report/components/metric-views/index.js +273 -0
  78. package/dist/report/{text → components/metric-views}/plot.js +1 -1
  79. package/dist/report/components/shared-compute.d.ts +22 -0
  80. package/dist/report/components/shared-compute.js +47 -0
  81. package/dist/report/components/shared-faces.d.ts +9 -0
  82. package/dist/report/components/shared-faces.js +26 -0
  83. package/dist/report/components/shared.d.ts +68 -0
  84. package/dist/report/components/shared.js +125 -0
  85. package/dist/report/{react → components/site-components}/CopyFixPrompt.d.ts +2 -2
  86. package/dist/report/{react → components/site-components}/CopyFixPrompt.js +2 -2
  87. package/dist/report/{react → components/site-components}/HeroCard.d.ts +2 -2
  88. package/dist/report/{react → components/site-components}/HeroCard.js +2 -2
  89. package/dist/report/{react → components/site-components}/ScopeWarnings.d.ts +2 -2
  90. package/dist/report/{react → components/site-components}/ScopeWarnings.js +3 -3
  91. package/dist/report/{react → components/site-components}/TraceWaterfall.d.ts +3 -3
  92. package/dist/report/{react → components/site-components}/TraceWaterfall.js +5 -5
  93. package/dist/report/components/site-components/compute.d.ts +28 -0
  94. package/dist/report/components/site-components/compute.js +132 -0
  95. package/dist/report/components/site-components/faces.d.ts +21 -0
  96. package/dist/report/components/site-components/faces.js +75 -0
  97. package/dist/report/components/site-components/index.d.ts +74 -0
  98. package/dist/report/components/site-components/index.js +220 -0
  99. package/dist/report/{scope-warnings.d.ts → components/site-components/scope-warnings.d.ts} +2 -2
  100. package/dist/report/{scope-warnings.js → components/site-components/scope-warnings.js} +2 -2
  101. package/dist/report/{react → components/summaries}/ScopeSummary.d.ts +2 -2
  102. package/dist/report/{react → components/summaries}/ScopeSummary.js +17 -8
  103. package/dist/report/components/summaries/compute.d.ts +7 -0
  104. package/dist/report/components/summaries/compute.js +51 -0
  105. package/dist/report/components/summaries/faces.d.ts +7 -0
  106. package/dist/report/components/summaries/faces.js +38 -0
  107. package/dist/report/components/summaries/index.d.ts +27 -0
  108. package/dist/report/components/summaries/index.js +78 -0
  109. package/dist/report/definition/grid-layout.d.ts +50 -0
  110. package/dist/report/definition/grid-layout.js +89 -0
  111. package/dist/report/{primitives.d.ts → definition/primitives.d.ts} +34 -4
  112. package/dist/report/{primitives.js → definition/primitives.js} +101 -10
  113. package/dist/report/{report.d.ts → definition/report.d.ts} +36 -65
  114. package/dist/report/{report.js → definition/report.js} +58 -90
  115. package/dist/report/{text/table.d.ts → definition/table-text.d.ts} +2 -2
  116. package/dist/report/{text/table.js → definition/table-text.js} +2 -2
  117. package/dist/report/{tree.d.ts → definition/tree.d.ts} +44 -12
  118. package/dist/report/{tree.js → definition/tree.js} +10 -12
  119. package/dist/report/index.d.ts +33 -18
  120. package/dist/report/index.js +23 -14
  121. package/dist/report/{aggregate.d.ts → model/aggregate.d.ts} +12 -4
  122. package/dist/report/{aggregate.js → model/aggregate.js} +28 -8
  123. package/dist/report/{flag.d.ts → model/flag.d.ts} +14 -1
  124. package/dist/report/{flag.js → model/flag.js} +35 -0
  125. package/dist/report/{format.d.ts → model/format.d.ts} +11 -5
  126. package/dist/report/{format.js → model/format.js} +48 -4
  127. package/dist/report/{locale.d.ts → model/locale.d.ts} +17 -17
  128. package/dist/report/{locale.js → model/locale.js} +31 -33
  129. package/dist/report/{metrics.d.ts → model/metrics.d.ts} +2 -2
  130. package/dist/report/{metrics.js → model/metrics.js} +2 -2
  131. package/dist/report/{types.d.ts → model/types.d.ts} +140 -20
  132. package/dist/report/react/index.d.ts +32 -21
  133. package/dist/report/react/index.js +33 -21
  134. package/dist/report/{load.d.ts → runtime/load.d.ts} +1 -1
  135. package/dist/report/{load.js → runtime/load.js} +1 -1
  136. package/dist/report/runtime/text.d.ts +67 -0
  137. package/dist/report/runtime/text.js +114 -0
  138. package/dist/report/runtime/web.d.ts +35 -0
  139. package/dist/report/runtime/web.js +70 -0
  140. package/dist/results/annotated-source.d.ts +87 -0
  141. package/dist/results/attempt-evidence.d.ts +74 -0
  142. package/dist/results/attempt-source.d.ts +15 -0
  143. package/dist/results/select.d.ts +12 -2
  144. package/dist/results/select.js +27 -5
  145. package/dist/runner/eval-selection.d.ts +26 -0
  146. package/dist/runner/feedback/sink.d.ts +26 -1
  147. package/dist/runner/types.d.ts +149 -23
  148. package/dist/sandbox/docker.d.ts +6 -0
  149. package/dist/sandbox/types.d.ts +23 -21
  150. package/dist/scoring/display.d.ts +7 -2
  151. package/dist/scoring/display.js +42 -4
  152. package/dist/scoring/types.d.ts +3 -3
  153. package/dist/shared/aggregate.d.ts +15 -1
  154. package/dist/shared/aggregate.js +32 -1
  155. package/dist/shared/types.d.ts +6 -1
  156. package/docs-site/images/logo-dark.svg +7 -0
  157. package/docs-site/images/logo.svg +6 -5
  158. package/docs-site/zh/README.md +8 -7
  159. package/docs-site/zh/examples/ai-agent-application.mdx +5 -5
  160. package/docs-site/zh/examples/coding-agent-extensions.mdx +4 -4
  161. package/docs-site/zh/examples/index.mdx +3 -3
  162. package/docs-site/zh/examples/integrations/ai-sdk-v7.mdx +1 -1
  163. package/docs-site/zh/examples/integrations/claude-sdk.mdx +3 -3
  164. package/docs-site/zh/examples/integrations/codex-sdk.mdx +3 -3
  165. package/docs-site/zh/examples/integrations/langgraph.mdx +3 -3
  166. package/docs-site/zh/examples/integrations/pi-sdk.mdx +2 -2
  167. package/docs-site/zh/explanation/adapter.mdx +19 -19
  168. package/docs-site/zh/explanation/assert.mdx +9 -9
  169. package/docs-site/zh/explanation/drive.mdx +4 -4
  170. package/docs-site/zh/explanation/evals.mdx +8 -8
  171. package/docs-site/zh/explanation/experiment.mdx +8 -8
  172. package/docs-site/zh/explanation/hitl.mdx +13 -13
  173. package/docs-site/zh/explanation/judge.mdx +3 -3
  174. package/docs-site/zh/explanation/overview.mdx +7 -7
  175. package/docs-site/zh/explanation/runner.mdx +11 -11
  176. package/docs-site/zh/explanation/tier.mdx +6 -6
  177. package/docs-site/zh/index.mdx +14 -14
  178. package/docs-site/zh/introduction.mdx +13 -16
  179. package/docs-site/zh/reference/builtin-agents.mdx +61 -28
  180. package/docs-site/zh/reference/capabilities.mdx +7 -27
  181. package/docs-site/zh/reference/cli.mdx +35 -32
  182. package/docs-site/zh/reference/define-agent.mdx +32 -31
  183. package/docs-site/zh/reference/define-config.mdx +6 -6
  184. package/docs-site/zh/reference/define-eval.mdx +33 -20
  185. package/docs-site/zh/reference/events.mdx +4 -4
  186. package/docs-site/zh/reference/expect.mdx +3 -3
  187. package/docs-site/zh/reference/official-adapters.mdx +17 -17
  188. package/docs-site/zh/reference/report-components.mdx +193 -121
  189. package/docs-site/zh/reference/results-data.mdx +13 -13
  190. package/docs-site/zh/troubleshooting/debug-sandbox.mdx +11 -11
  191. package/docs-site/zh/troubleshooting/debugging.mdx +43 -19
  192. package/docs-site/zh/{how-to → tutorials}/agent-feedback-loop.mdx +38 -38
  193. package/docs-site/zh/tutorials/agent-onboarding.mdx +100 -0
  194. package/docs-site/zh/tutorials/authoring.mdx +211 -0
  195. package/docs-site/zh/{how-to → tutorials}/ci-integration.mdx +8 -8
  196. package/docs-site/zh/{how-to → tutorials}/connect-otel.mdx +22 -22
  197. package/docs-site/zh/{how-to → tutorials}/connect-your-agent.mdx +58 -63
  198. package/docs-site/zh/tutorials/custom-reports.mdx +453 -0
  199. package/docs-site/zh/{how-to → tutorials}/dataset-fanout.mdx +4 -4
  200. package/docs-site/zh/tutorials/experiments.mdx +103 -0
  201. package/docs-site/zh/{how-to → tutorials}/fixtures.mdx +9 -9
  202. package/docs-site/zh/{how-to → tutorials}/publish-report.mdx +11 -5
  203. package/docs-site/zh/tutorials/quickstart.mdx +18 -18
  204. package/docs-site/zh/{how-to → tutorials}/reporters.mdx +6 -6
  205. package/docs-site/zh/{how-to → tutorials}/sandbox-agent.mdx +6 -6
  206. package/docs-site/zh/{how-to → tutorials}/sandbox-providers.mdx +41 -19
  207. package/docs-site/zh/{how-to → tutorials}/scoring-guide.mdx +4 -4
  208. package/docs-site/zh/{how-to → tutorials}/viewing-results.mdx +59 -50
  209. package/docs-site/zh/tutorials/write-experiment.mdx +355 -0
  210. package/docs-site/zh/{how-to → tutorials}/write-send.mdx +40 -40
  211. package/package.json +8 -4
  212. package/src/agents/bub.ts +21 -8
  213. package/src/agents/claude-code.ts +23 -10
  214. package/src/agents/codex.test.ts +22 -7
  215. package/src/agents/codex.ts +22 -9
  216. package/src/agents/index.ts +2 -2
  217. package/src/agents/openai-compat.ts +1 -1
  218. package/src/agents/post-setup.ts +45 -22
  219. package/src/agents/streaming.ts +1 -1
  220. package/src/agents/types.ts +20 -17
  221. package/src/agents/ui-message-stream.test.ts +10 -0
  222. package/src/agents/ui-message-stream.ts +12 -1
  223. package/src/cli.ts +77 -64
  224. package/src/context/types.ts +21 -21
  225. package/src/define.ts +13 -0
  226. package/src/i18n/en.ts +30 -15
  227. package/src/i18n/zh-CN.ts +30 -15
  228. package/src/index.ts +2 -0
  229. package/src/report/{react → assets}/colors.ts +22 -0
  230. package/src/report/{react → assets}/enhance.js +1 -33
  231. package/src/report/{react → assets}/styles.css +136 -112
  232. package/src/report/built-in/index.tsx +2 -2
  233. package/src/report/built-in/standard.tsx +18 -6
  234. package/src/report/components/attempt-detail/AttemptAssertions.tsx +67 -0
  235. package/src/report/components/attempt-detail/AttemptConversation.tsx +110 -0
  236. package/src/report/components/attempt-detail/AttemptDiagnostics.tsx +38 -0
  237. package/src/report/components/attempt-detail/AttemptDiff.tsx +35 -0
  238. package/src/report/components/attempt-detail/AttemptError.tsx +31 -0
  239. package/src/report/components/attempt-detail/AttemptFixPrompt.tsx +26 -0
  240. package/src/report/components/attempt-detail/AttemptSource.tsx +80 -0
  241. package/src/report/components/attempt-detail/AttemptSummary.tsx +74 -0
  242. package/src/report/components/attempt-detail/AttemptTimeline.tsx +108 -0
  243. package/src/report/components/attempt-detail/AttemptTrace.tsx +63 -0
  244. package/src/report/components/attempt-detail/AttemptUsage.tsx +29 -0
  245. package/src/report/components/attempt-detail/attempt-components.test.tsx +680 -0
  246. package/src/report/components/attempt-detail/compute.ts +287 -0
  247. package/src/report/components/attempt-detail/faces.ts +257 -0
  248. package/src/report/components/attempt-detail/index.tsx +583 -0
  249. package/src/report/components/attempt-detail/validate.test.ts +235 -0
  250. package/src/report/{react → components}/cell.tsx +6 -3
  251. package/src/report/{report.test.ts → components/compute.test.ts} +316 -46
  252. package/src/report/{react → components/entity-lists}/AttemptList.tsx +24 -16
  253. package/src/report/{react → components/entity-lists}/EvalList.tsx +0 -0
  254. package/src/report/{react → components/entity-lists}/ExperimentList.tsx +23 -20
  255. package/src/report/components/entity-lists/compute.ts +196 -0
  256. package/src/report/components/entity-lists/faces.ts +207 -0
  257. package/src/report/components/entity-lists/index.tsx +241 -0
  258. package/src/report/components/entity-lists/validate.test.ts +114 -0
  259. package/src/report/{react → components}/fixtures.ts +2 -2
  260. package/src/report/{react → components/metric-views}/DeltaTable.tsx +5 -5
  261. package/src/report/{react → components/metric-views}/MetricBars.tsx +5 -5
  262. package/src/report/{react → components/metric-views}/MetricLine.tsx +9 -6
  263. package/src/report/{react → components/metric-views}/MetricMatrix.tsx +6 -6
  264. package/src/report/{react → components/metric-views}/MetricScatter.tsx +41 -33
  265. package/src/report/{react → components/metric-views}/MetricTable.tsx +6 -6
  266. package/src/report/{react → components/metric-views}/Scoreboard.tsx +4 -4
  267. package/src/report/{compute.ts → components/metric-views/compute.ts} +51 -477
  268. package/src/report/components/metric-views/faces.ts +423 -0
  269. package/src/report/components/metric-views/index.tsx +352 -0
  270. package/src/report/{text → components/metric-views}/plot.ts +1 -1
  271. package/src/report/components/metric-views/validate.test.ts +211 -0
  272. package/src/report/{react → components}/render.test.tsx +47 -17
  273. package/src/report/components/shared-compute.ts +59 -0
  274. package/src/report/components/shared-faces.ts +30 -0
  275. package/src/report/components/shared.ts +192 -0
  276. package/src/report/{react → components/site-components}/CopyFixPrompt.tsx +3 -3
  277. package/src/report/{react → components/site-components}/HeroCard.tsx +3 -3
  278. package/src/report/{react → components/site-components}/ScopeWarnings.tsx +4 -4
  279. package/src/report/{react → components/site-components}/TraceWaterfall.tsx +13 -10
  280. package/src/report/components/site-components/compute.ts +144 -0
  281. package/src/report/components/site-components/faces.ts +80 -0
  282. package/src/report/components/site-components/index.tsx +265 -0
  283. package/src/report/{scope-warnings.ts → components/site-components/scope-warnings.ts} +3 -3
  284. package/src/report/{site-components.test.tsx → components/site-components/site-components.test.tsx} +23 -21
  285. package/src/report/components/site-components/validate.test.ts +89 -0
  286. package/src/report/{react → components/summaries}/ScopeSummary.tsx +26 -11
  287. package/src/report/components/summaries/compute.ts +56 -0
  288. package/src/report/components/summaries/faces.ts +48 -0
  289. package/src/report/components/summaries/index.tsx +127 -0
  290. package/src/report/components/summaries/validate.test.ts +49 -0
  291. package/src/report/definition/grid-layout.test.ts +124 -0
  292. package/src/report/definition/grid-layout.ts +146 -0
  293. package/src/report/{primitives.tsx → definition/primitives.tsx} +168 -18
  294. package/src/report/{report.ts → definition/report.ts} +98 -165
  295. package/src/report/{shell-head.test.ts → definition/shell-head.test.ts} +2 -2
  296. package/src/report/{text/table.ts → definition/table-text.ts} +4 -4
  297. package/src/report/{tree.ts → definition/tree.ts} +58 -22
  298. package/src/report/index.ts +126 -66
  299. package/src/report/{aggregate.ts → model/aggregate.ts} +31 -9
  300. package/src/report/{flag.ts → model/flag.ts} +39 -1
  301. package/src/report/{format.ts → model/format.ts} +59 -5
  302. package/src/report/{locale.ts → model/locale.ts} +34 -36
  303. package/src/report/{metrics.ts → model/metrics.ts} +3 -3
  304. package/src/report/{types.ts → model/types.ts} +143 -22
  305. package/src/report/react/index.tsx +52 -26
  306. package/src/report/{dual-render.test.tsx → runtime/dual-render.test.tsx} +564 -49
  307. package/src/report/runtime/host.test.ts +44 -0
  308. package/src/report/runtime/host.ts +138 -0
  309. package/src/report/{load.ts → runtime/load.ts} +1 -1
  310. package/src/report/runtime/text.ts +192 -0
  311. package/src/report/runtime/web.ts +106 -0
  312. package/src/results/attempt-evidence.ts +5 -1
  313. package/src/results/host-equivalence.test.ts +54 -3
  314. package/src/results/select.ts +30 -6
  315. package/src/runner/attempt.test.ts +48 -1
  316. package/src/runner/attempt.ts +62 -42
  317. package/src/runner/cleanup-timeout.test.ts +16 -0
  318. package/src/runner/cleanup-timeout.ts +23 -0
  319. package/src/runner/discover.ts +1 -2
  320. package/src/runner/eval-selection.test.ts +187 -0
  321. package/src/runner/eval-selection.ts +109 -0
  322. package/src/runner/experiment-cleanup-registry.test.ts +89 -0
  323. package/src/runner/experiment-cleanup-registry.ts +39 -0
  324. package/src/runner/experiment-labels.test.ts +71 -0
  325. package/src/runner/feedback/agent.test.ts +33 -1
  326. package/src/runner/feedback/agent.ts +12 -16
  327. package/src/runner/feedback/ci.test.ts +35 -1
  328. package/src/runner/feedback/ci.ts +16 -18
  329. package/src/runner/feedback/coordinator.ts +28 -0
  330. package/src/runner/feedback/human.test.ts +108 -1
  331. package/src/runner/feedback/human.ts +71 -21
  332. package/src/runner/feedback/reducer.test.ts +64 -0
  333. package/src/runner/feedback/reducer.ts +26 -0
  334. package/src/runner/feedback/sink.ts +40 -1
  335. package/src/runner/fingerprint.ts +2 -1
  336. package/src/runner/report.test.ts +14 -0
  337. package/src/runner/report.ts +3 -1
  338. package/src/runner/run.test.ts +390 -12
  339. package/src/runner/run.ts +259 -26
  340. package/src/runner/sandbox-selection.test.ts +13 -3
  341. package/src/runner/sandbox-selection.ts +4 -2
  342. package/src/runner/types.ts +158 -23
  343. package/src/sandbox/docker.ts +7 -0
  344. package/src/sandbox/e2b.ts +12 -13
  345. package/src/sandbox/types.ts +23 -21
  346. package/src/scoring/display.test.ts +28 -2
  347. package/src/scoring/display.ts +38 -5
  348. package/src/scoring/types.ts +3 -3
  349. package/src/shared/aggregate.test.ts +52 -0
  350. package/src/shared/aggregate.ts +30 -1
  351. package/src/shared/types.ts +6 -1
  352. package/src/show/command.test.ts +34 -0
  353. package/src/show/command.ts +16 -0
  354. package/src/show/compose.ts +1 -1
  355. package/src/show/index.ts +58 -22
  356. package/src/show/render.ts +16 -240
  357. package/src/show/show.test.ts +128 -35
  358. package/src/view/app/App.test.tsx +8 -7
  359. package/src/view/app/App.tsx +135 -51
  360. package/src/view/app/i18n.ts +7 -147
  361. package/src/view/app/lib/attempt-dialog.test.ts +65 -0
  362. package/src/view/app/lib/attempt-dialog.ts +69 -0
  363. package/src/view/app/main.tsx +0 -1
  364. package/src/view/app/types.ts +3 -86
  365. package/src/view/artifact-serving.test.ts +22 -38
  366. package/src/view/client-dist/app.css +1 -1
  367. package/src/view/client-dist/app.js +15 -22
  368. package/src/view/data.test.ts +48 -36
  369. package/src/view/data.ts +122 -89
  370. package/src/view/index.ts +3 -13
  371. package/src/view/server.ts +36 -6
  372. package/src/view/shared/types.ts +6 -31
  373. package/src/view/site-parity.test.ts +24 -1
  374. package/src/view/site.ts +144 -17
  375. package/src/view/styles.css +18 -806
  376. package/src/view/view-report.test.ts +182 -34
  377. package/dist/report/components.d.ts +0 -179
  378. package/dist/report/components.js +0 -544
  379. package/dist/report/compute.d.ts +0 -139
  380. package/dist/report/react/ExperimentComparison.d.ts +0 -10
  381. package/dist/report/react/ExperimentComparison.js +0 -12
  382. package/dist/report/react/MetricScatter.d.ts +0 -9
  383. package/dist/report/react/format.d.ts +0 -3
  384. package/dist/report/react/format.js +0 -7
  385. package/dist/report/text/faces.d.ts +0 -45
  386. package/dist/report/text/faces.js +0 -671
  387. package/dist/report/web.d.ts +0 -32
  388. package/dist/report/web.js +0 -48
  389. package/docs-site/zh/how-to/authoring.mdx +0 -162
  390. package/docs-site/zh/how-to/custom-reports.mdx +0 -414
  391. package/docs-site/zh/how-to/experiments.mdx +0 -86
  392. package/docs-site/zh/how-to/write-experiment.mdx +0 -164
  393. package/src/report/components.tsx +0 -925
  394. package/src/report/react/ExperimentComparison.tsx +0 -73
  395. package/src/report/react/format.ts +0 -9
  396. package/src/report/text/faces.ts +0 -767
  397. package/src/report/web.ts +0 -77
  398. package/src/show/report-host.test.ts +0 -205
  399. package/src/show/report-host.ts +0 -389
  400. package/src/view/app/components/AttemptModal.tsx +0 -496
  401. package/src/view/app/components/CodeView.test.tsx +0 -142
  402. package/src/view/app/components/CodeView.tsx +0 -310
  403. package/src/view/app/components/CopyControls.tsx +0 -106
  404. package/src/view/app/components/Trace.tsx +0 -100
  405. package/src/view/app/components/Transcript.tsx +0 -157
  406. package/src/view/app/components/ui/badge.tsx +0 -21
  407. package/src/view/app/lib/artifact-url.ts +0 -17
  408. package/src/view/app/lib/attempt-route.test.ts +0 -80
  409. package/src/view/app/lib/attempt-route.ts +0 -52
  410. package/src/view/app/lib/format.ts +0 -70
  411. package/src/view/app/lib/guards.test.ts +0 -108
  412. package/src/view/app/lib/guards.ts +0 -71
  413. package/src/view/app/lib/rows.ts +0 -22
  414. package/src/view/app/lib/transcript-data.tsx +0 -151
  415. package/src/view/app/lib/verdict.ts +0 -23
  416. package/src/view/app/shared.ts +0 -8
  417. /package/dist/report/{react → components/metric-views}/chart-math.d.ts +0 -0
  418. /package/dist/report/{react → components/metric-views}/chart-math.js +0 -0
  419. /package/dist/report/{text → components/metric-views}/plot.d.ts +0 -0
  420. /package/dist/report/{react → components/site-components}/PoweredBy.d.ts +0 -0
  421. /package/dist/report/{react → components/site-components}/PoweredBy.js +0 -0
  422. /package/dist/report/{text/layout.d.ts → model/text-layout.d.ts} +0 -0
  423. /package/dist/report/{text/layout.js → model/text-layout.js} +0 -0
  424. /package/dist/report/{types.js → model/types.js} +0 -0
  425. /package/src/report/{react → components/metric-views}/chart-math.test.ts +0 -0
  426. /package/src/report/{react → components/metric-views}/chart-math.ts +0 -0
  427. /package/src/report/{react → components/site-components}/PoweredBy.tsx +0 -0
  428. /package/src/report/{text/layout.ts → model/text-layout.ts} +0 -0
@@ -1,32 +0,0 @@
1
- import type { AttemptLocator } from "../results/locator.ts";
2
- import type { Scope } from "../results/types.ts";
3
- import { type ReportLocale } from "./locale.ts";
4
- import { type ReportDefinition, type ReportHostContext } from "./report.ts";
5
- export interface StaticHtmlOptions {
6
- /** 渲染哪一页;缺省第一页。未命中抛 ReportPageNotFoundError。 */
7
- pageId?: string;
8
- /** 证据室深链;缺省用 view 的 attempt 路由 `#/attempt/@<locator>`(单段、不透明)。 */
9
- attemptHref?: (locator: AttemptLocator) => string;
10
- /** 官方组件 chrome 文案的 locale;默认 "en"。 */
11
- locale?: ReportLocale;
12
- }
13
- /**
14
- * web 宿主的装载语义:选页 → resolve(组合展开 + spec 取数,唯一的 await 边界)→
15
- * 树校验(与 text 宿主同一遍)→ 静态渲染 web 面。宿主不在报告树外另设警告通道——
16
- * 挑选警告的呈现件是 `ScopeWarnings` 组件,内建报告每页都放它,自定义报告放不放是
17
- * 作者义务(docs/feature/reports/architecture.md「Scope 是计算入口」)。
18
- */
19
- export declare function renderReportToStaticHtml(definition: ReportDefinition, ctx: ReportHostContext, options?: StaticHtmlOptions): Promise<string>;
20
- /**
21
- * 渲染一页报告树的 web 面(宿主逐页调用;页选择归宿主):resolve → validate → 静态渲染。
22
- * 挑选警告由页内的 `ScopeWarnings` 组件呈现,宿主不前置任何树外块。
23
- * ctx.report 是宿主规范化后的声明。
24
- */
25
- export declare function renderReportTreeToStaticHtml(tree: import("./tree.ts").ReportNode, ctx: {
26
- scope: Scope;
27
- results: import("../results/types.ts").Results;
28
- report: import("./report.ts").ReportMeta;
29
- }, options?: {
30
- attemptHref?: (locator: AttemptLocator) => string;
31
- locale?: ReportLocale;
32
- }): Promise<string>;
@@ -1,48 +0,0 @@
1
- // web 宿主(view --report)的装载入口:同一棵树走 web 面,renderToStaticMarkup 吐静态
2
- // HTML 烘进查看器的报告槽。只有这一侧真正 import react-dom(import 边界即运行时边界),
3
- // 所以本文件不从 niceeval/report 的入口 re-export —— 宿主与测试按源路径 import。
4
- import { renderToStaticMarkup } from "react-dom/server";
5
- import { resolveReportTree, runWithWebContext, validateReportTree, ResolveMemo, } from "./tree.js";
6
- import { DEFAULT_REPORT_LOCALE } from "./locale.js";
7
- import { buildReportMeta, pickReportPage } from "./report.js";
8
- /**
9
- * web 宿主的装载语义:选页 → resolve(组合展开 + spec 取数,唯一的 await 边界)→
10
- * 树校验(与 text 宿主同一遍)→ 静态渲染 web 面。宿主不在报告树外另设警告通道——
11
- * 挑选警告的呈现件是 `ScopeWarnings` 组件,内建报告每页都放它,自定义报告放不放是
12
- * 作者义务(docs/feature/reports/architecture.md「Scope 是计算入口」)。
13
- */
14
- export async function renderReportToStaticHtml(definition, ctx, options) {
15
- const page = pickReportPage(definition, options?.pageId);
16
- const meta = buildReportMeta(definition, ctx.scope, page.id);
17
- const resolved = await resolveReportTree(page.content, {
18
- scope: ctx.scope,
19
- results: ctx.results,
20
- report: meta,
21
- memo: new ResolveMemo(),
22
- });
23
- validateReportTree(resolved);
24
- const webCtx = {
25
- attemptHref: options?.attemptHref ?? ((locator) => `#/attempt/${locator}`),
26
- locale: options?.locale ?? DEFAULT_REPORT_LOCALE,
27
- };
28
- return runWithWebContext(webCtx, () => renderToStaticMarkup(resolved));
29
- }
30
- /**
31
- * 渲染一页报告树的 web 面(宿主逐页调用;页选择归宿主):resolve → validate → 静态渲染。
32
- * 挑选警告由页内的 `ScopeWarnings` 组件呈现,宿主不前置任何树外块。
33
- * ctx.report 是宿主规范化后的声明。
34
- */
35
- export async function renderReportTreeToStaticHtml(tree, ctx, options) {
36
- const resolved = await resolveReportTree(tree, {
37
- scope: ctx.scope,
38
- results: ctx.results,
39
- report: ctx.report,
40
- memo: new ResolveMemo(),
41
- });
42
- validateReportTree(resolved);
43
- const webCtx = {
44
- attemptHref: options?.attemptHref ?? ((locator) => `#/attempt/${locator}`),
45
- locale: options?.locale ?? DEFAULT_REPORT_LOCALE,
46
- };
47
- return runWithWebContext(webCtx, () => renderToStaticMarkup(resolved));
48
- }
@@ -1,162 +0,0 @@
1
- ---
2
- title: "编写 eval: 单轮、多轮和数据集模式"
3
- sidebarTitle: "编写 eval"
4
- description: "学习如何用 defineEval 编写 NiceEval eval,包括单轮断言、多轮对话、数据驱动测试和 sandbox workspace。"
5
- ---
6
-
7
- 每个 [NiceEval](https://niceeval.com/) eval 都是一个 TypeScript 文件,默认导出 `defineEval(...)`。核心原则是:**路径即身份**、**一个文件一个 eval**、**线性书写并就地断言**。
8
-
9
- ## `defineEval` 的结构
10
-
11
- ```ts
12
- import { defineEval } from "niceeval";
13
-
14
- export default defineEval({
15
- description?: string;
16
- tags?: string[];
17
- judge?: JudgeConfig;
18
- reporters?: Reporter[];
19
- timeoutMs?: number;
20
- metadata?: Record<string, unknown>;
21
- async setup(sandbox, ctx) { /* task fixture + progress/diagnostic */ },
22
- async test(t) { /* interactions + assertions */ },
23
- });
24
- ```
25
-
26
- <Warning>
27
- 不要设置 `id` 或 `name`。`evals/weather/brooklyn.eval.ts` 会自动变成 ID `weather/brooklyn`。
28
- </Warning>
29
-
30
- ## 单轮 eval
31
-
32
- ```ts
33
- import { defineEval } from "niceeval";
34
- import { includes } from "niceeval/expect";
35
-
36
- export default defineEval({
37
- description: "Brooklyn weather query",
38
- async test(t) {
39
- await t.send("What's the weather like in Brooklyn today?");
40
- t.succeeded();
41
- t.calledTool("get_weather", { input: { city: "Brooklyn" }, count: 1 });
42
- t.check(t.reply, includes("sunny"));
43
- },
44
- });
45
- ```
46
-
47
- `t.send()` 驱动一次交互,`t.succeeded()` 和 `t.calledTool()` 是作用域断言,`t.check()` 是立即记录的值断言。
48
-
49
- ### `Turn` 对象
50
-
51
- | 属性 | 说明 |
52
- |---|---|
53
- | `turn.events` | 标准事件流,主要事实来源 |
54
- | `turn.data` | 结构化输出 |
55
- | `turn.status` | `"completed"`、`"failed"` 或 `"waiting"` |
56
- | `turn.usage` | token / cost 等 usage |
57
- | `turn.message` | assistant 文本回复 |
58
- | `turn.toolCalls` | 本轮工具调用 |
59
-
60
- ## 多轮 eval
61
-
62
- ```ts
63
- export default defineEval({
64
- description: "Draft an email, then send it on confirmation",
65
- async test(t) {
66
- const draft = await t.send("Draft a follow-up email.");
67
- draft.succeeded();
68
- t.check(draft.message, includes("Best"));
69
-
70
- await t.send("Looks good, send it.");
71
- t.calledTool("send_email");
72
- },
73
- });
74
- ```
75
-
76
- 需要并行独立会话时,用 `t.newSession()`。
77
-
78
- ## 数据驱动测试(dataset fan-out)
79
-
80
- 一个文件可以导出 eval 数组:
81
-
82
- ```ts
83
- export default rows.map((row) =>
84
- defineEval({
85
- description: row.task,
86
- async test(t) {
87
- await t.send(row.prompt);
88
- t.check(t.reply, equals(row.expected));
89
- },
90
- }),
91
- );
92
- ```
93
-
94
- 生成 ID 为 `sql/0000`、`sql/0001` 等。详见 [数据驱动测试](/zh/how-to/dataset-fanout)。
95
-
96
- ## Sandbox workspace
97
-
98
- Coding-agent eval 仍然是普通 `.eval.ts` 文件,只是 test 里会准备 sandbox workspace、发送任务并检查文件结果:
99
-
100
- ```ts
101
- import { defineEval } from "niceeval";
102
-
103
- export default defineEval({
104
- description: "Create a Button component",
105
- async test(t) {
106
- await t.sandbox.uploadDirectory("../workspaces/ts-starter");
107
- await t.send("Create src/components/Button.tsx with label and onClick props.").then((turn) => turn.succeeded());
108
- t.sandbox.fileChanged("src/components/Button.tsx");
109
- },
110
- });
111
- ```
112
-
113
- 详见 [Fixtures](/zh/how-to/fixtures)。
114
-
115
- ## 从 Eval 报告长步骤和诊断
116
-
117
- `setup` 用于这条 Eval 的任务夹具。第二个参数绑定到 eval setup 阶段;`test(t)` 里的反馈绑定到 eval run 阶段:
118
-
119
- ```ts
120
- export default defineEval({
121
- async setup(sandbox, ctx) {
122
- ctx.progress({ message: "安装 fixture 依赖" });
123
- await sandbox.runCommand("npm", ["install"]);
124
- },
125
-
126
- async test(t) {
127
- t.progress({ message: "上传隐藏测试", current: 1, total: 2 });
128
- await t.sandbox.uploadDirectory("../fixtures/project");
129
-
130
- const preflight = await inspectFixture();
131
- if (preflight.usedFallback) {
132
- t.diagnostic({
133
- code: "fixture-check-degraded",
134
- level: "warning",
135
- message: "Fixture 预检使用了备用检查器",
136
- data: { checker: preflight.checker },
137
- });
138
- }
139
-
140
- await t.send("完成任务");
141
- },
142
- });
143
- ```
144
-
145
- `progress` 只更新运行中的短期状态,不进入结果。`diagnostic` 会写进当前 Attempt 的 `result.json`,但不会代替断言或自动改变判定:业务结论仍用 `t.check` / `t.require` / gate;基础设施无法继续时抛出异常。
146
-
147
- ## 命名约定
148
-
149
- <CardGroup cols={2}>
150
- <Card title="文件名" icon="file">
151
- 只有 `.eval.ts` 会被 runner 发现。
152
- </Card>
153
- <Card title="目录分组" icon="folder">
154
- `evals/billing/refund.eval.ts` 的 ID 是 `billing/refund`。
155
- </Card>
156
- <Card title="数据集" icon="database">
157
- 适合大量结构相同、输入不同的 case。
158
- </Card>
159
- <Card title="Sandbox workspace" icon="box">
160
- 适合 coding agent,需要真实文件系统、命令和 diff。
161
- </Card>
162
- </CardGroup>
@@ -1,414 +0,0 @@
1
- ---
2
- title: "用报告积木搭自己的口径"
3
- sidebarTitle: "自定义报告"
4
- description: "一份报告就是一个报告文件:官方宿主打开结果、注入数据,双面组件让同一份自定义报告同时用于 niceeval show 与 niceeval view。"
5
- ---
6
-
7
- [查看结果](/zh/how-to/viewing-results)讲「用」:官方两扇门 `niceeval show`(终端)和 `niceeval view`(网页)怎么看。本页讲「写」:官方摆法不够时,怎么写一份自己的报告——考试成绩单、代码行数榜、质量 × 成本 frontier。
8
-
9
- 一份报告就是一个报告文件。你不用打开结果目录、不用写渲染代码、不用起自己的应用:`niceeval show` / `niceeval view` 本体就是宿主——替你打开结果、把数据注入进来,你用官方组件和 `Row` / `Col` 摆版面,写完把文件路径递给 `--report`,终端和网页两扇门就都认它——官方的证据深链、`--results` 换根、静态导出,自定义报告全部原样享有。
10
-
11
- ## show / view 的默认报告也是一份报告文件
12
-
13
- `niceeval show` / `view` 不传 `--report` 时渲染的默认报告不是私有实现,而是包里自带的一个普通报告文件:三个页面(报告、Attempts、追踪),每页由 `niceeval/report` 公开导出的组件搭成——页首的标题区(`Hero`)、选择警告(`ScopeWarnings`),首页的比较组件 `ExperimentComparison`,Attempts 页的 Attempt 列表(`AttemptList`),追踪页的追踪瀑布(`TraceWaterfall`)。
14
-
15
- 这份默认报告本身以 `standard` 为名从 `niceeval/report/built-in` 导出。只想在默认报告上加站点标题、GitHub 链接或统计脚本时,用 `extends` 在它上面叠自己的外壳,页面内容一行不用写;NiceEval 升级带来的页面改进也会自动跟过来:
16
-
17
- ```tsx
18
- // reports/branded.tsx —— 默认报告整站 + 自己的标题和链接
19
- import { defineReport } from "niceeval/report";
20
- import { standard } from "niceeval/report/built-in";
21
-
22
- export default defineReport({
23
- extends: standard,
24
- title: "Memory Evals",
25
- links: [{ label: "GitHub", href: "https://github.com/you/repo" }],
26
- });
27
- ```
28
-
29
- `niceeval/report/built-in` 是内置报告的集合,每份一个名字;今天只有 `standard`,以后新增的内置报告也从这里按名字导入。想改页面内容本身,用同一批公开组件自己搭——你的报告文件能逐字写出同样的页面,也能只留自己要的部分:
30
-
31
- ```ts
32
- import { ExperimentComparison, Hero, ScopeWarnings } from "niceeval/report";
33
- ```
34
-
35
- `ExperimentComparison` 先按 experiment id 的父目录切成可比组,再为每组分别计算成本 × 端到端成功率散点图和实验明细表。`compare/bub` 与 `compare/codex` 可以同图,`dev-e2b/bub` 必须在另一个组;顶层 experiment 各自成为单例组。网页持有全部组并一次聚焦一组;终端命中多组时只列索引和单组查看命令,命中单组时才展开详情。两面都不会生成跨组总榜。
36
-
37
- | | 网页(人看) | 终端(agent 和你看) |
38
- | --- | --- | --- |
39
- | 官方默认 | 分组比较报告(网页面) | 同一分组比较报告(文本面) |
40
- | 自定义摆法 | `niceeval view --report reports/exam.tsx` | `niceeval show --report reports/exam.tsx` |
41
-
42
- 四个格子是同一个宿主。自定义只有三个层次,逐层深入:
43
-
44
- 1. **换摆法**:用官方组件和 `Row` / `Col` 重摆版面——大多数「要自己的口径」到这层就够了。
45
- 2. **换口径**:`defineMetric` 定义自己的指标,自定义维度和 `flag()` 定义自己的分组。
46
- 3. **换形态**:官方组件摆不出的展示——是表就用 `<Table>` 自定义列,不是表就用 `defineComponent` 写自己的组件,网页怎么渲染、终端字符怎么排,两个面你都说了算。
47
-
48
- ## 一份报告 = 一个报告文件
49
-
50
- 先交代唯一的前置:报告文件是 `.tsx`,写它的项目要装 `react`(写自定义组件的 web 面还要 `@types/react`),tsconfig 里 `compilerOptions.jsx` 设为 `"react-jsx"`。裸跑 `niceeval show` / `niceeval view` 不需要这些——只有自己写报告文件才需要。
51
-
52
- 报告基座是 `defineReport`:宿主打开结果目录(含 `--results` 指定的结果根)、按官方口径挑好结果快照,注入给你的函数;你只负责折数据和摆积木:
53
-
54
- ```tsx
55
- // reports/exam.tsx —— 一份定义,两扇门共用
56
- import {
57
- defineReport, Col, Section,
58
- ExperimentComparison, Scoreboard,
59
- } from "niceeval/report";
60
-
61
- export default defineReport(async ({ selection }) => {
62
- return (
63
- <Col>
64
- <ExperimentComparison data={await ExperimentComparison.data(selection)} />
65
- <Section title="考试成绩单">
66
- <Scoreboard data={await Scoreboard.data(selection, { rows: "agent", subjects: "evalGroup" })} />
67
- </Section>
68
- </Col>
69
- );
70
- });
71
- ```
72
-
73
- ```bash
74
- niceeval show --report reports/exam.tsx # 终端:同一棵树走文本面
75
- niceeval view --report reports/exam.tsx # 网页:同一棵树走网页面,attempt 深链直达证据室
76
- ```
77
-
78
- 注入的上下文只有两样,全部是[结果数据 API](/zh/reference/results-data) 的原语,宿主没有私有通道:`results` 就是 `openResults(".niceeval")` 的返回(实验、历次快照、attempt 级 `diff()`、`trace()` 都在上面);`selection` 是宿主替你挑好的那份结果。挑选规则是:对每个实验、每道 eval,取该实验历史运行里最新的那次判定——只按前缀重跑了一部分 eval,其余 eval 的判定从更早的运行补齐,不会因为一次局部重跑就整体退回某一份残缺快照。默认报告吃的就是这份 `selection`,快照在 `selection.snapshots`,挑选提醒在 `selection.warnings`。默认挑法不合口径时,拿 `results` 自己挑,手工挑的 `Snapshot[]` 数组同样能喂给每个组件。
79
-
80
- 报告文件里有两种数据形态。实体列表的 `.data(selection)` 返回普通数组:`ExperimentListItem[]`、`EvalListItem[]`、`AttemptListItem[]`。报告作者用 JavaScript `.filter()` / `.slice()` 决定展示哪些实体,再把数组作为 `items` 传给列表;组件不藏另一套过滤 DSL。指标图形和汇总组件收算好的 `data`;其中 `MetricScatter` 也提供 `selection` 简写,由宿主在渲染前计算。计算后的组件只渲染传入数据,不碰结果目录。
81
-
82
- 挑选提醒(覆盖不全、快照过期、有没跑完的运行、读不出来的快照)由选择警告(`ScopeWarnings`)组件显示:提醒按实验归组,组头列出实验名、问题标签和一条可复制的重跑命令,每条提醒的原文收在组内可展开的折叠块里(提醒少时默认展开),读不出来的快照单独归成一组。默认报告每一页都摆了它;自己的报告想显示提醒,就在页首摆一个 `<ScopeWarnings />`——不摆就不显示,数字是否附带完整性提醒由你对读者负责。
83
-
84
- 命令行的范围先作用在挑选上,报告拿到的就是收窄到这个范围后的 `selection`:位置参数的 eval id 前缀收窄 Selection 覆盖的 eval(覆盖提醒的分母同样收窄到范围内),`--results` 把结果根换成指定目录,`--experiment` 让 Selection 只留该实验。`--history` 与 `--report` 互斥——趋势在报告里用 `exp.snapshots` 自己摆;证据切面(`--eval` / `--execution` / `--diff`)只看证据,不渲染报告。
85
-
86
- 页面里的每个组件都是**双面**的:网页面是 React 渲染,终端面是字符渲染,两面吃同一份算好的数据。实体列表按 experiment → Eval → Attempt 展示事实;指标表、矩阵、条形图、成绩单、散点图、趋势图和差异表展示聚合值。完整清单见[报告组件](/zh/reference/report-components)。网页面的实体、格子和点深链到 Attempt 详情,终端面印出对应的 `niceeval show <eval id>` 下钻命令。
87
-
88
- 默认报告没有特权:它的三个页面全部由公开组件搭成,你的报告和它同级。需要同样的“按目录分组、组内比较”摆法就直接写 `<ExperimentComparison />`,需要 Attempts 页就写带过滤的 `<AttemptList />`,不需要就不摆——包括页首标题区和 `Powered by NiceEval` 品牌行:它们分别是 `Hero` 和 `PoweredBy` 组件,用了就带、不用就没有,组件本身不提供开关。自己直接组合 `MetricScatter` / `ExperimentList` 时,通用组件只消费你传入的数据,不会自动分组;把跨组数据传进去就表示你明确要做跨组分析。
89
-
90
- ## 排版:Row 与 Col 在两个面都成立
91
-
92
- 排版原语也是双面组件,一次摆放,两个面各自成立:
93
-
94
- - **`<Col>`**:纵向依次排列——网页是块级堆叠,终端是逐块输出。
95
- - **`<Row>`**:并排——网页是横向排布,终端是字符分栏;终端宽度不够时自动降级为纵向,不硬挤。
96
- - **`<Section title="…">`**:带标题的块,网页是标题层级,终端是标题行加缩进。
97
- - **`<Text>`**:说明文字,网页是段落,终端是折行文本。
98
- - **`<Table>`**:自定义列的表——网页是 `<table>`,终端是按显示宽度对齐的字符表,中文列不撕歪。
99
-
100
- ```tsx
101
- <Row>
102
- <Scoreboard data={board} />
103
- <MetricTable data={bench} />
104
- </Row>
105
- ```
106
-
107
- ```text
108
- $ niceeval show --report reports/exam.tsx
109
- 考试成绩单 │ benchmark 榜
110
- agent 总分 algebra geometry │ agent pass cost
111
- bub 86.5/100 45/50 41.5/50 │ bub 87% $0.42
112
- codex 78.0/100 40/50 38/50 │ codex 80% $0.51
113
- ```
114
-
115
- 页面树里只放双面组件和排版原语,不放裸 HTML 标签——终端面没法渲染一个 `<div>`。要自由内容,说明文字用 `<Text>`,更自由的走下面的自定义组件。
116
-
117
- ## 按组摘要:GroupSummary
118
-
119
- 想把实验按目录前缀分组、每组顶部摆一块紧凑统计(通过率、experiment/eval 数、failed/errored、总成本、最后运行时间),用官方组件 `GroupSummary`。它的计算函数 `GroupSummary.data` 只吃一份已经收窄好的 Selection——不管你怎么分组,把对应组的 Selection 传进去,就是同一套折叠口径(eval 级折叠计票、null-safe 总成本、组内最后运行时间的最大值),不是另一套近似公式。
120
-
121
- 常见的分法是按 experiment id 的目录前缀(`compare/bub-low` 属于组 `compare`,顶层实验以自己的完整 id 形成单例组);用 `Selection.filter` 收窄出每组的 Selection,就能一组摆一块:
122
-
123
- ```tsx
124
- // reports/groups.tsx —— 按 experiment id 前缀分组,每组顶部摆一块统计
125
- import { defineReport, Col, Section, GroupSummary } from "niceeval/report";
126
- import type { Snapshot } from "niceeval/report";
127
-
128
- // experiment id 的完整父路径当组名;顶层 experiment 各自成为单例组。
129
- function groupOf(snapshot: Snapshot): string {
130
- const parts = snapshot.experimentId.split("/");
131
- return parts.length > 1 ? parts.slice(0, -1).join("/") : snapshot.experimentId;
132
- }
133
-
134
- export default defineReport(async ({ selection }) => {
135
- const groups = [...new Set(selection.snapshots.map(groupOf))];
136
- return (
137
- <Col>
138
- {await Promise.all(
139
- groups.map(async (key) => {
140
- const scoped = selection.filter((s) => groupOf(s) === key);
141
- const summary = <GroupSummary data={await GroupSummary.data(scoped)} />;
142
- return <Section key={key} title={key}>{summary}</Section>;
143
- }),
144
- )}
145
- </Col>
146
- );
147
- });
148
- ```
149
-
150
- ```text
151
- $ niceeval show --report reports/groups.tsx
152
- compare
153
- Pass rate 60% · 2 experiments · 6 evals · failed 1 · errored 1 · $1.50
154
- latest 2026-07-01T11:30:00Z
155
- ```
156
-
157
- `GroupSummary` 之后想接着摆散点图或实验列表,把同一份 `scoped` Selection 传给散点图,再用 `await ExperimentList.data(scoped)` 得到可自行过滤的 experiment 数组。想自由换行维度和指标列,再用下面的通用 `MetricTable`。
158
-
159
- ## 换口径:自定义指标
160
-
161
- 实验、Eval、Attempt 的固定诊断字段由三个实体列表承接。下面的例子是另一种需求:用通用 `MetricTable` 自由换指标口径——每个组件的数据算法都挂在自己身上,换口径只需换 columns:
162
-
163
- ```tsx
164
- // reports/golf.tsx —— code-golf:谁写出能用的代码,谁写得短
165
- import {
166
- defineReport, Col, MetricTable,
167
- defineMetric, endToEndPassRate, costUSD,
168
- } from "niceeval/report";
169
-
170
- // 项目自己的口径:只比能用的代码的行数
171
- const codeLines = defineMetric({
172
- name: "code-lines",
173
- label: "代码行数",
174
- better: "lower",
175
- unit: "lines",
176
- where: (a) => a.result.verdict === "passed",
177
- async value(attempt) {
178
- const diff = await attempt.diff();
179
- if (!diff) return null;
180
- return Object.keys(diff.files)
181
- .reduce((sum, path) => sum + (diff.get(path) ?? "").split("\n").length, 0);
182
- },
183
- });
184
-
185
- export default defineReport(async ({ selection }) => (
186
- <Col>
187
- <MetricTable data={await MetricTable.data(selection, {
188
- rows: "agent",
189
- columns: [endToEndPassRate, codeLines, costUSD],
190
- sort: endToEndPassRate,
191
- })} />
192
- </Col>
193
- ));
194
- ```
195
-
196
- ```text
197
- $ niceeval show --report reports/golf.tsx
198
- agent pass rate code lines cost
199
- bub 87% 312 lines $0.42
200
- codex 80% 355 lines $0.51
201
- ```
202
-
203
- 格子里的终值是**两级折叠**出来的:同一道题的多个 attempt 先折成题级值,再跨题折成格子值,两级默认都是平均。分两级不是形式主义——失败的题天然比通过的题 attempt 多(重试跑满、通过即停),平铺求均值会让分数和重试策略纠缠在一起。两级都能在 `defineMetric` 的 `aggregate` 里换:`aggregate: { perEval: "max", across: "mean" }` 就是 pass@k(题内取最好一次,跨题取占比)。
204
-
205
- 两个面继承同一套诚实契约:排序方向随指标的 `better`,覆盖不全的格子带 `12/15` 角标(15 个 attempt 里 12 个测得了该指标),缺数据渲染成 `—` 而不是 0。指标只定义一次,两个面共用——人在网页上看到的数字,就是 agent 在 stdout 里读到的数字,判断口径永远一致。给 agent 的指引也只多一行:跑 `niceeval show --report reports/golf.tsx`,读 stdout。
206
-
207
- `label` 可以是一份文案,也可以按语言给:`label: { en: "Code lines", "zh-CN": "代码行数" }`——查看器界面切语言时,按语言给的 label 跟着切;只给一份就两种语言都用它。指标算出来的数字本身不分语言。
208
-
209
- 内置指标里 `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/reference/results-data)的「发布」一节),指标渲染成 `—`,不是 0。自己写的指标只要读了 `o11y()` / `diff()` 这类 artifact(就像上面 `codeLines` 读 `attempt.diff()`),发布前都要过一遍同样的检查。
210
-
211
- ## 换分组:三种维度
212
-
213
- 维度决定分组——表的行、矩阵的行列、散点的点。每个维度槽收三种值:
214
-
215
- - **内置维度**:`"agent"`、`"model"`、`"experiment"`、`"eval"`、`"evalGroup"`、`"snapshot"`,结果里现成的身份字段。
216
- - **自定义维度**:`{ name, of }`,一个纯函数吃 attempt、吐组名。定义只住在报告文件里,不用改任何 experiment 文件。
217
- - **`flag()`**:引用 experiment 用 `flags` 声明的变量。
218
-
219
- 选哪种看变量住在哪:分组能从结果已有数据**算**出来的,用自定义维度——报告只管「怎么摆」,不反过来要求 experiment 为一种摆法改配置;变量是 experiment 本身的**配置**——scaling 档位、要当数值轴用、要在 experiment 定义处一眼看到的——才声明成 `flags`,报告用 `flag()` 引用。数值轴(`MetricLine` 的 `x`)只收 `flag()`:刻度必须来自声明的数值,函数派生的组名当不了刻度。
220
-
221
- ### 自定义维度:从已有数据派生分组
222
-
223
- 比如把不同模型折成厂商,按厂商比通过率——分组从 `result.model` 算出来,experiment 文件一个都不用碰:
224
-
225
- ```tsx
226
- import type { Dimension } from "niceeval/report";
227
-
228
- const vendor: Dimension = {
229
- name: "vendor",
230
- of: (a) => (a.result.model?.startsWith("gpt-") ? "OpenAI" : "Anthropic"),
231
- };
232
-
233
- <MetricTable data={await MetricTable.data(selection, {
234
- rows: vendor,
235
- columns: [endToEndPassRate, costUSD],
236
- })} />
237
- ```
238
-
239
- `of` 能读 attempt 的全部已有数据:`evalId`、`experimentId`、`result` 里的判定与 experiment 元数据。它只能派生、不能补造:如果两组 experiment 的差别只体现在文件命名里(`bub-baseline.ts` / `bub-mempal.ts`),`of` 就只能去解析这个名字——命名约定一改,分组静默散掉。这种时候变量该搬回配置,往下看。
240
-
241
- ### `flag()`:变量来自配置,不来自命名
242
-
243
- 画「并行 agent 数 × 模拟延迟 × 得分」这类 scaling 趋势时,图上的变量不该编码进 experiment id(`ultra-16agents-300ms`),再在报告里解析字符串抠出来——那是约定不是配置,改个命名整张图就散。变量的家是 experiment 文件的 `flags`:一文件一个档位,把变量声明成数据:
244
-
245
- ```ts
246
- // experiments/ultra/agents-16.ts
247
- import { defineExperiment } from "niceeval";
248
- import { bub } from "../../agents/bub.ts";
249
-
250
- export default defineExperiment({
251
- agent: bub({ mode: "ultra" }),
252
- flags: { agents: 16, latencyMs: 300 },
253
- });
254
- ```
255
-
256
- 报告侧用 `flag()` 把声明的值直接当维度或轴用,不解析任何名字:
257
-
258
- ```tsx
259
- <MetricLine data={await MetricLine.data(selection, {
260
- x: flag("latencyMs", { label: "Simulated latency", unit: "ms" }),
261
- series: flag("agents", { label: (v) => `${v} agents` }),
262
- y: endToEndPassRate,
263
- })} />
264
- ```
265
-
266
- `flag()` 用在维度槽(`series` / `rows` / `columns` / `points`)就按声明值分组,用在轴槽(`x`)就要求数值并驱动刻度。没有声明该 flag 的 experiment 不猜:分组时如实归入「未配置」一组,作轴时该点不画、注脚报数。flags 随快照落盘,所以历史 run 也能按当时声明的变量重摆——报告读的是配置的事实,不是命名的巧合。
267
-
268
- ## 换形态:表格用 Table,其余自己画
269
-
270
- 官方组件摆不出来的展示分两种,各走一条路:是一张表,用排版原语 `<Table>`;不是表(通过率条形、预算燃尽、项目自己的徽章),用 `defineComponent` 写自己的双面组件。
271
-
272
- ### 一张表:`<Table>`
273
-
274
- 列是你定的,格子是你算好的显示值,`<Table>` 负责把网页和终端两个面都排整齐:
275
-
276
- ```tsx
277
- // reports/cost-board.tsx
278
- import { defineReport, Col, Table, MetricTable, costUSD, endToEndPassRate } from "niceeval/report";
279
-
280
- export default defineReport(async ({ selection }) => {
281
- const board = await MetricTable.data(selection, {
282
- rows: "agent",
283
- columns: [endToEndPassRate, costUSD],
284
- });
285
- return (
286
- <Col>
287
- <Table
288
- columns={[
289
- { key: "agent", header: "Agent" },
290
- { key: "pass", header: "通过率", align: "right" },
291
- { key: "cost", header: "成本", align: "right" },
292
- ]}
293
- rows={board.rows.map((r) => ({
294
- key: r.key,
295
- cells: {
296
- agent: r.key,
297
- pass: r.cells[endToEndPassRate.name].display,
298
- // 缺数据交 null,组件渲染成 —;不要自己填 0
299
- cost: r.cells[costUSD.name].value === null ? null : r.cells[costUSD.name].display,
300
- },
301
- }))}
302
- />
303
- </Col>
304
- );
305
- });
306
- ```
307
-
308
- ```text
309
- $ niceeval show --report reports/cost-board.tsx
310
- Agent 通过率 成本
311
- bub 87% $0.42
312
- codex 80% $0.51
313
- 克劳德 — —
314
- ```
315
-
316
- 列宽按**终端显示宽度**算:一个汉字占 2 列,所以中文 agent 名、中文题目名都不会把表撕歪。`align: "right"` 让数字列右对齐。格子给 `null` 就渲染 `—`,不补 0。行上带 `locator` 就多出一列 attempt,网页面点进证据室、终端面把 locator 交给 `niceeval show`。完整字段见[报告组件](/zh/reference/report-components)的「表格」一节。
317
-
318
- ### 不是表:`defineComponent` 加文本排版函数
319
-
320
- `defineComponent` 声明两个渲染面,和 `defineExperiment` / `defineMetric` / `defineReport` 同一个家族。终端面要自己排字符,用 `niceeval/report` 导出的这组函数——官方组件排的就是这几把尺子:
321
-
322
- | 函数 | 干什么 |
323
- | --- | --- |
324
- | `stringWidth(text)` | 显示宽度:CJK 和全角字符记 2 列,其余记 1 列 |
325
- | `padEnd(text, width)` | 按显示宽度在右侧补齐(左对齐) |
326
- | `padStart(text, width)` | 按显示宽度在左侧补齐(右对齐,数字列用) |
327
- | `wrapText(text, width)` | 按显示宽度折行,返回若干行 |
328
- | `indent(block, prefix)` | 每行加缩进 |
329
- | `bar(ratio, width)` | 字符条:`█` 填充、`░` 补齐到 `width` |
330
- | `columns(blocks, widths, separator?)` | 多块并排 |
331
-
332
- **不要用 `String.prototype.padEnd` / `padStart` 对齐终端输出。** 它们数的是 UTF-16 码元,不是显示列宽:一个汉字占 2 列,却只算 1 个码元。用它们补齐,中文一进来列就错位,而中文 agent 名和中文题目名恰恰是最常见的情形。列宽也要随内容和 `ctx.width` 算,不要硬编码一个数字。
333
-
334
- ```tsx
335
- // reports/passbars.tsx
336
- import {
337
- defineReport, defineComponent, Col, Style, MetricTable,
338
- bar, padEnd, stringWidth, endToEndPassRate,
339
- } from "niceeval/report";
340
-
341
- interface BarRow { key: string; ratio: number | null; display: string }
342
-
343
- const PassBars = defineComponent<{ rows: BarRow[] }>({
344
- web({ rows }) {
345
- return (
346
- <ul className="passbars">
347
- {rows.map((r) => (
348
- <li key={r.key}>
349
- <span>{r.key}</span>
350
- <i style={{ width: `${(r.ratio ?? 0) * 100}%` }} />
351
- <b>{r.ratio === null ? "—" : r.display}</b>
352
- </li>
353
- ))}
354
- </ul>
355
- );
356
- },
357
- text({ rows }, { width }) {
358
- const label = Math.max(...rows.map((r) => stringWidth(r.key))); // 列宽随内容
359
- const barWidth = Math.min(20, width - label - 8); // 也随可用列宽
360
- return rows
361
- .map((r) => {
362
- const chart = r.ratio === null ? padEnd("—", barWidth) : bar(r.ratio, barWidth);
363
- return `${padEnd(r.key, label)} ${chart} ${r.display}`;
364
- })
365
- .join("\n");
366
- },
367
- });
368
-
369
- export default defineReport(async ({ selection }) => {
370
- const board = await MetricTable.data(selection, { rows: "agent", columns: [endToEndPassRate] });
371
- const rows = board.rows.map((r) => ({
372
- key: r.key,
373
- ratio: r.cells[endToEndPassRate.name].value, // 格子键锚在指标对象上,不裸写字符串
374
- display: r.cells[endToEndPassRate.name].display,
375
- }));
376
- return (
377
- <Col>
378
- <Style>{`.passbars li { display: flex; gap: 8px; } .passbars i { background: #4a7; height: 12px; }`}</Style>
379
- <PassBars rows={rows} />
380
- </Col>
381
- );
382
- });
383
- ```
384
-
385
- ```text
386
- $ niceeval show --report reports/passbars.tsx
387
- bub █████████████████░░░ 87%
388
- codex ████████████████░░░░ 80%
389
- 克劳德 — —
390
- ```
391
-
392
- 组件的契约:
393
-
394
- - **计算在报告函数体里,渲染面是纯函数。** 读 attempt 句柄、`await` 折数据都发生在 `defineReport` 的函数体里;`web` 和 `text` 只认算好的数据,零 IO、同步——这条边界让同一棵树能被烘进静态导出。
395
- - **面收第二参上下文。** `text(props, ctx)` 的 `ctx.width` 是可用列宽(`Row` 分栏后会变窄),`ctx.attemptCommand(ref)` 生成下钻命令;`web(props, ctx)` 的 `ctx.attemptHref(ref)` 生成证据室深链——自定义组件和官方组件通到同一间证据室。
396
- - **网页面静态渲染,不 hydrate。** 宿主在计算侧把 `web` 面渲染成静态 HTML,不打包你的代码进查看器:交互用普通链接和 `<details>`,与官方组件同一条静态契约。
397
- - **样式随树带走。** 静态导出不打包你的代码,`className` 引用的 CSS 用内置原语 `<Style>{css}</Style>` 放进页面树——web 面吐 `<style>` 标签,text 面渲染为空,上面的例子就是这么给 `.passbars` 上样式的。
398
- - **诚实契约同样适用。** 缺数据渲染 `—` 不补 0,截断如实标注剩余数量——上面例子里 `claude` 没有样本就是 `—`。
399
-
400
- ## 发布:导出即静态站
401
-
402
- 发布自定义报告和发布官方查看器是同一个动作——`--out` 加上 `--report`:
403
-
404
- ```bash
405
- niceeval view --report reports/exam.tsx --out site
406
- ```
407
-
408
- 产物是纯静态文件:你的报告页是首页,证据室(transcript、trace、代码视图)在同一站内,报告里的每个数字点进去就是证据。组件不 hydrate;页面内联一小段官方脚本,提供表头排序、行过滤、图表悬停这些浏览操作,浏览器禁用 JS 时页面仍完整可读。交给任何静态托管即可。CI 上没有 `.niceeval/` 时,先用 `copySnapshots` 把快照瘦身进仓库再导出,流程见[查看结果](/zh/how-to/viewing-results#导出与静态托管)。
409
-
410
- 双面组件的网页面就是普通 React 组件,`data` 函数就是普通 TS 函数——想把某一块指标表嵌进已有的内部面板,import 组件喂数据就行。那是零件的复用,不是另一套报告系统:报告的家在官方宿主。算数据与渲染分离部署时(CI 落 JSON、另一个应用 fetch),两侧锁同一个 niceeval 版本是硬要求——组件数据不带版本戳,兼容性跟随包版本。
411
-
412
- ## 界线:内置命令不长配置
413
-
414
- 一次只渲染一份报告,`--report` 收显式文件路径——没有 `reports/` 目录自动发现、没有插件注册表、没有配置文件。自定义指标和自定义组件都住在你的报告文件里,随文件一起递入,宿主不为它们长任何注册面。不传 `--report` 时渲染的就是默认报告,你的报告和它是同级实现。