niceeval 0.5.4 → 0.6.1
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.
- package/INDEX.md +55 -0
- package/dist/agents/types.d.ts +321 -0
- package/dist/context/types.d.ts +335 -0
- package/dist/i18n/core.d.ts +11 -0
- package/dist/i18n/en.d.ts +181 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/zh-CN.d.ts +183 -0
- package/dist/o11y/otlp/parse.d.ts +3 -0
- package/dist/o11y/otlp/receiver.d.ts +21 -0
- package/dist/o11y/otlp/turn-otel.d.ts +43 -0
- package/dist/o11y/types.d.ts +190 -0
- package/dist/report/aggregate.d.ts +57 -0
- package/dist/report/aggregate.js +212 -0
- package/dist/report/built-ins/experiment-comparison.d.ts +1 -0
- package/dist/report/built-ins/experiment-comparison.js +13 -0
- package/dist/report/built-ins/index.d.ts +1 -0
- package/dist/report/built-ins/index.js +2 -0
- package/dist/report/components.d.ts +180 -0
- package/dist/report/components.js +132 -0
- package/dist/report/compute.d.ts +106 -0
- package/dist/report/compute.js +600 -0
- package/dist/report/flag.d.ts +6 -0
- package/dist/report/flag.js +8 -0
- package/dist/report/format.d.ts +26 -0
- package/dist/report/format.js +109 -0
- package/dist/report/index.d.ts +19 -0
- package/dist/report/index.js +30 -0
- package/dist/report/load.d.ts +12 -0
- package/dist/report/load.js +56 -0
- package/dist/report/locale.d.ts +94 -0
- package/dist/report/locale.js +183 -0
- package/dist/report/metrics.d.ts +21 -0
- package/dist/report/metrics.js +115 -0
- package/dist/report/primitives.d.ts +64 -0
- package/dist/report/primitives.js +113 -0
- package/dist/report/react/AttemptList.d.ts +23 -0
- package/dist/report/react/AttemptList.js +19 -0
- package/dist/report/react/DeltaTable.d.ts +8 -0
- package/dist/report/react/DeltaTable.js +24 -0
- package/dist/report/react/EvalList.d.ts +10 -0
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentList.d.ts +11 -0
- package/dist/report/react/ExperimentList.js +67 -0
- package/dist/report/react/GroupSummary.d.ts +8 -0
- package/dist/report/react/GroupSummary.js +8 -0
- package/dist/report/react/MetricBars.d.ts +10 -0
- package/dist/report/react/MetricBars.js +48 -0
- package/dist/report/react/MetricLine.d.ts +9 -0
- package/dist/report/react/MetricLine.js +68 -0
- package/dist/report/react/MetricMatrix.d.ts +10 -0
- package/dist/report/react/MetricMatrix.js +23 -0
- package/dist/report/react/MetricScatter.d.ts +9 -0
- package/dist/report/react/MetricScatter.js +96 -0
- package/dist/report/react/MetricTable.d.ts +11 -0
- package/dist/report/react/MetricTable.js +26 -0
- package/dist/report/react/RunOverview.d.ts +8 -0
- package/dist/report/react/RunOverview.js +12 -0
- package/dist/report/react/Scoreboard.d.ts +8 -0
- package/dist/report/react/Scoreboard.js +26 -0
- package/dist/report/react/cell.d.ts +9 -0
- package/dist/report/react/cell.js +9 -0
- package/dist/report/react/chart-math.d.ts +14 -0
- package/dist/report/react/chart-math.js +68 -0
- package/dist/report/react/colors.d.ts +14 -0
- package/dist/report/react/colors.js +45 -0
- package/dist/report/react/fixtures.d.ts +19 -0
- package/dist/report/react/fixtures.js +398 -0
- package/dist/report/react/format.d.ts +3 -0
- package/dist/report/react/format.js +7 -0
- package/dist/report/react/index.d.ts +6 -0
- package/dist/report/react/index.js +14 -0
- package/dist/report/report.d.ts +24 -0
- package/dist/report/report.js +49 -0
- package/dist/report/text/faces.d.ts +29 -0
- package/dist/report/text/faces.js +541 -0
- package/dist/report/text/layout.d.ts +75 -0
- package/dist/report/text/layout.js +173 -0
- package/dist/report/text/plot.d.ts +37 -0
- package/dist/report/text/plot.js +81 -0
- package/dist/report/text/table.d.ts +8 -0
- package/dist/report/text/table.js +86 -0
- package/dist/report/tree.d.ts +99 -0
- package/dist/report/tree.js +228 -0
- package/dist/report/types.d.ts +408 -0
- package/dist/report/types.js +4 -0
- package/dist/report/web.d.ts +15 -0
- package/dist/report/web.js +37 -0
- package/dist/results/locator.d.ts +75 -0
- package/dist/results/locator.js +0 -0
- package/dist/results/select.d.ts +45 -0
- package/dist/results/select.js +230 -0
- package/dist/results/source-hash.d.ts +9 -0
- package/dist/results/types.d.ts +204 -0
- package/dist/runner/eval-source.d.ts +22 -0
- package/dist/runner/fingerprint.d.ts +23 -0
- package/dist/runner/types.d.ts +332 -0
- package/dist/sandbox/docker-stream.d.ts +24 -0
- package/dist/sandbox/docker.d.ts +84 -0
- package/dist/sandbox/e2b.d.ts +31 -0
- package/dist/sandbox/errors.d.ts +4 -0
- package/dist/sandbox/local-files.d.ts +6 -0
- package/dist/sandbox/paths.d.ts +4 -0
- package/dist/sandbox/registry.d.ts +13 -0
- package/dist/sandbox/resolve.d.ts +40 -0
- package/dist/sandbox/retry.d.ts +10 -0
- package/dist/sandbox/shell.d.ts +11 -0
- package/dist/sandbox/source-files.d.ts +13 -0
- package/dist/sandbox/types.d.ts +203 -0
- package/dist/sandbox/vercel.d.ts +37 -0
- package/dist/scoring/types.d.ts +70 -0
- package/dist/shared/aggregate.d.ts +29 -0
- package/dist/shared/aggregate.js +55 -0
- package/dist/shared/types.d.ts +27 -0
- package/dist/shared/verdict.d.ts +27 -0
- package/dist/shared/verdict.js +34 -0
- package/dist/tty-line.d.ts +6 -0
- package/dist/types.d.ts +7 -0
- package/dist/util.d.ts +22 -0
- package/docs-site/zh/concepts/adapter.mdx +7 -7
- package/docs-site/zh/concepts/assert.mdx +19 -18
- package/docs-site/zh/concepts/evals.mdx +12 -11
- package/docs-site/zh/concepts/experiment.mdx +2 -2
- package/docs-site/zh/concepts/judge.mdx +9 -9
- package/docs-site/zh/concepts/overview.mdx +3 -3
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
- package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
- package/docs-site/zh/guides/agent-feedback-loop.mdx +237 -78
- package/docs-site/zh/guides/connect-otel.mdx +3 -3
- package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
- package/docs-site/zh/guides/custom-reports.mdx +196 -36
- package/docs-site/zh/guides/experiments.mdx +7 -1
- package/docs-site/zh/guides/fixtures.mdx +4 -2
- package/docs-site/zh/guides/official-adapters.mdx +31 -9
- package/docs-site/zh/guides/publish-report.mdx +4 -4
- package/docs-site/zh/guides/report-components.mdx +194 -55
- package/docs-site/zh/guides/reporters.mdx +2 -2
- package/docs-site/zh/guides/results-data.mdx +61 -34
- package/docs-site/zh/guides/runner.mdx +2 -2
- package/docs-site/zh/guides/sandbox-agent.mdx +3 -2
- package/docs-site/zh/guides/{sandbox-backends.mdx → sandbox-providers.mdx} +12 -12
- package/docs-site/zh/guides/viewing-results.mdx +200 -79
- package/docs-site/zh/guides/write-experiment.mdx +9 -1
- package/docs-site/zh/guides/write-send.mdx +4 -3
- package/docs-site/zh/index.mdx +4 -3
- package/docs-site/zh/introduction.mdx +2 -1
- package/docs-site/zh/quickstart.mdx +5 -3
- package/docs-site/zh/reference/builtin-agents.mdx +38 -13
- package/docs-site/zh/reference/capabilities.mdx +1 -1
- package/docs-site/zh/reference/cli.mdx +36 -13
- package/docs-site/zh/reference/define-agent.mdx +15 -13
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +3 -3
- package/docs-site/zh/reference/events.mdx +9 -3
- package/docs-site/zh/reference/expect.mdx +13 -8
- package/package.json +10 -6
- package/src/agents/bub.ts +129 -42
- package/src/agents/claude-code.test.ts +152 -0
- package/src/agents/claude-code.ts +127 -13
- package/src/agents/codex.test.ts +192 -0
- package/src/agents/codex.ts +151 -12
- package/src/agents/index.ts +9 -3
- package/src/agents/manifest.ts +41 -0
- package/src/agents/plugin-config.test.ts +94 -0
- package/src/agents/shared.ts +33 -2
- package/src/agents/skills.test.ts +214 -0
- package/src/agents/skills.ts +304 -0
- package/src/agents/streaming.ts +5 -5
- package/src/agents/types.ts +59 -3
- package/src/cli.ts +74 -47
- package/src/context/context.test.ts +20 -2
- package/src/context/control-flow.ts +1 -1
- package/src/context/session.test.ts +92 -1
- package/src/context/session.ts +23 -1
- package/src/context/types.ts +4 -4
- package/src/define.test.ts +8 -8
- package/src/define.ts +11 -11
- package/src/expect/index.ts +5 -5
- package/src/i18n/en.ts +33 -15
- package/src/i18n/zh-CN.ts +37 -21
- package/src/index.ts +1 -1
- package/src/o11y/execution-tree.test.ts +452 -0
- package/src/o11y/execution-tree.ts +367 -0
- package/src/o11y/otlp/receiver.ts +1 -1
- package/src/o11y/otlp/select.ts +7 -3
- package/src/o11y/parsers/claude-code.test.ts +175 -0
- package/src/o11y/parsers/claude-code.ts +32 -0
- package/src/o11y/types.ts +6 -1
- package/src/report/aggregate.ts +26 -20
- package/src/report/built-in-user-parity.test.tsx +640 -0
- package/src/report/built-ins/experiment-comparison.tsx +19 -0
- package/src/report/built-ins/index.ts +2 -0
- package/src/report/components.tsx +225 -50
- package/src/report/compute.ts +335 -54
- package/src/report/dual-render.test.tsx +1129 -0
- package/src/report/flag.ts +1 -1
- package/src/report/format.ts +35 -2
- package/src/report/index.ts +47 -14
- package/src/report/load.ts +1 -1
- package/src/report/locale.ts +229 -0
- package/src/report/metrics.ts +39 -15
- package/src/report/primitives.tsx +100 -2
- package/src/report/react/AttemptList.tsx +113 -0
- package/src/report/react/DeltaTable.tsx +9 -6
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentList.tsx +220 -0
- package/src/report/react/GroupSummary.tsx +66 -0
- package/src/report/react/MetricBars.tsx +24 -13
- package/src/report/react/MetricLine.tsx +21 -21
- package/src/report/react/MetricMatrix.tsx +8 -4
- package/src/report/react/MetricScatter.tsx +126 -96
- package/src/report/react/MetricTable.tsx +115 -15
- package/src/report/react/RunOverview.tsx +48 -26
- package/src/report/react/Scoreboard.tsx +15 -7
- package/src/report/react/cell.tsx +18 -9
- package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
- package/src/report/react/colors.ts +24 -15
- package/src/report/react/enhance.js +168 -0
- package/src/report/react/fixtures.ts +169 -34
- package/src/report/react/format.ts +1 -1
- package/src/report/react/index.tsx +21 -6
- package/src/report/react/render.test.tsx +180 -31
- package/src/report/react/styles.css +432 -213
- package/src/report/report.test.ts +398 -94
- package/src/report/report.ts +29 -11
- package/src/report/text/faces.ts +344 -96
- package/src/report/text/layout.ts +76 -11
- package/src/report/text/table.ts +98 -0
- package/src/report/tree.ts +103 -20
- package/src/report/types.ts +184 -25
- package/src/report/web.ts +46 -19
- package/src/results/annotated-source.test.ts +146 -0
- package/src/results/annotated-source.ts +128 -0
- package/src/results/attempt-evidence.test.ts +158 -0
- package/src/results/attempt-evidence.ts +158 -0
- package/src/results/attempt-source.ts +48 -0
- package/src/results/copy.ts +154 -139
- package/src/results/format.ts +54 -47
- package/src/results/host-equivalence.test.ts +527 -0
- package/src/results/index.ts +56 -14
- package/src/results/locator.test.ts +198 -0
- package/src/results/locator.ts +0 -0
- package/src/results/open.ts +392 -175
- package/src/results/results.test.ts +943 -494
- package/src/results/select.ts +129 -23
- package/src/results/skipped-notice.ts +0 -0
- package/src/results/source-hash.ts +28 -0
- package/src/results/types.ts +98 -62
- package/src/results/writer.ts +304 -176
- package/src/runner/attempt.test.ts +170 -0
- package/src/runner/attempt.ts +48 -13
- package/src/runner/discover.test.ts +65 -0
- package/src/runner/discover.ts +6 -2
- package/src/runner/eval-source.test.ts +121 -0
- package/src/runner/eval-source.ts +45 -0
- package/src/runner/fingerprint.ts +54 -1
- package/src/runner/remote-sandbox.ts +1 -1
- package/src/runner/report.test.ts +6 -8
- package/src/runner/report.ts +5 -4
- package/src/runner/reporters/artifacts.ts +27 -41
- package/src/runner/reporters/braintrust.test.ts +3 -3
- package/src/runner/reporters/braintrust.ts +1 -1
- package/src/runner/reporters/console.ts +6 -6
- package/src/runner/reporters/json.ts +4 -4
- package/src/runner/reporters/live.test.ts +56 -0
- package/src/runner/reporters/live.ts +36 -10
- package/src/runner/reporters/quiet.test.ts +66 -0
- package/src/runner/reporters/quiet.ts +49 -0
- package/src/runner/reporters/shared.ts +5 -5
- package/src/runner/reporters/table.ts +17 -17
- package/src/runner/run.ts +61 -102
- package/src/runner/types.ts +43 -32
- package/src/sandbox/checkpoint.ts +1 -1
- package/src/sandbox/docker.ts +15 -2
- package/src/sandbox/e2b.ts +11 -5
- package/src/sandbox/errors.ts +13 -0
- package/src/sandbox/index.ts +3 -3
- package/src/sandbox/local-files.ts +1 -1
- package/src/sandbox/registry.ts +6 -5
- package/src/sandbox/resolve.ts +38 -23
- package/src/sandbox/retry.test.ts +68 -0
- package/src/sandbox/retry.ts +44 -0
- package/src/sandbox/types.ts +24 -24
- package/src/sandbox/vercel.ts +17 -3
- package/src/scoring/collector.ts +3 -3
- package/src/scoring/judge.ts +2 -2
- package/src/scoring/scoped.ts +13 -1
- package/src/scoring/types.ts +7 -7
- package/src/scoring/verdict.ts +4 -4
- package/src/shared/aggregate.ts +23 -3
- package/src/shared/types.ts +2 -2
- package/src/shared/{outcome.ts → verdict.ts} +15 -15
- package/src/show/compose.ts +12 -132
- package/src/show/index.ts +119 -95
- package/src/show/render.ts +462 -159
- package/src/show/show.test.ts +471 -253
- package/src/source-loc.ts +1 -1
- package/src/tty-line.ts +32 -0
- package/src/types.ts +2 -2
- package/src/util.test.ts +21 -1
- package/src/util.ts +5 -1
- package/src/view/app/App.tsx +33 -198
- package/src/view/app/components/AttemptModal.tsx +6 -6
- package/src/view/app/components/CodeView.tsx +2 -2
- package/src/view/app/components/CopyControls.tsx +32 -36
- package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
- package/src/view/app/components/ui/badge.tsx +1 -1
- package/src/view/app/components/ui/dialog.tsx +2 -2
- package/src/view/app/i18n.ts +40 -31
- package/src/view/app/lib/artifact-url.ts +1 -1
- package/src/view/app/lib/attempt-route.test.ts +37 -47
- package/src/view/app/lib/attempt-route.ts +26 -26
- package/src/view/app/lib/rows.ts +9 -112
- package/src/view/app/lib/verdict.ts +25 -0
- package/src/view/app/main.tsx +9 -12
- package/src/view/app/pages/RunsPage.tsx +4 -4
- package/src/view/app/pages/TracesPage.tsx +3 -3
- package/src/view/app/shared.ts +0 -1
- package/src/view/app/types.ts +6 -51
- package/src/view/artifact-serving.test.ts +136 -0
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +21 -21
- package/src/view/data.test.ts +214 -143
- package/src/view/data.ts +166 -120
- package/src/view/index.ts +23 -11
- package/src/view/server.ts +52 -17
- package/src/view/shared/types.ts +22 -31
- package/src/view/styles.css +117 -327
- package/src/view/view-report.test.ts +146 -98
- package/src/report/default-report.tsx +0 -222
- package/src/report/dual-face.test.tsx +0 -527
- package/src/report/react/CaseList.tsx +0 -70
- package/src/view/app/components/CostScoreChart.tsx +0 -147
- package/src/view/app/components/ExperimentTable.tsx +0 -284
- package/src/view/app/components/GroupSelector.tsx +0 -61
- package/src/view/app/components/primitives.tsx +0 -43
- package/src/view/app/lib/outcome.ts +0 -82
|
@@ -1,50 +1,60 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "用报告积木搭自己的口径"
|
|
3
3
|
sidebarTitle: "自定义报告"
|
|
4
|
-
description: "
|
|
4
|
+
description: "一份报告就是一个报告文件:官方宿主打开结果、注入数据,双面组件让同一份自定义报告同时用于 niceeval show 与 niceeval view。"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
[查看结果](/zh/guides/viewing-results)讲「用」:官方两扇门 `niceeval show`(终端)和 `niceeval view`(网页)怎么看。本页讲「写」:官方摆法不够时,怎么写一份自己的报告——考试成绩单、代码行数榜、质量 × 成本 frontier。
|
|
8
8
|
|
|
9
9
|
一份报告就是一个报告文件。你不用打开结果目录、不用写渲染代码、不用起自己的应用:`niceeval show` / `niceeval view` 本体就是宿主——替你打开结果、把数据注入进来,你用官方组件和 `Row` / `Col` 摆版面,写完把文件路径递给 `--report`,终端和网页两扇门就都认它——官方的证据室深链、`--run` 历史切换、静态导出,自定义报告全部原样享有。
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## view 的默认报告也是一份报告定义
|
|
12
12
|
|
|
13
|
-
`niceeval view`
|
|
13
|
+
`niceeval view` 不传 `--report` 时渲染的默认报告不是私有实现,而是 `niceeval/report` 公开导出的一个值,也可显式传给 `niceeval show --report`:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { CostPassRateComparison } from "niceeval/report";
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`niceeval view` 缺省选择 `CostPassRateComparison`:一张成本 × 通过率散点图加一张实验明细表,没有私有数据通道。裸 `niceeval show` 使用更适合终端的 Attempt 索引;显式自定义报告(包括这份 definition)仍可由两个宿主按同一双面协议渲染。
|
|
14
20
|
|
|
15
21
|
| | 网页(人看) | 终端(agent 和你看) |
|
|
16
22
|
| --- | --- | --- |
|
|
17
|
-
|
|
|
23
|
+
| 官方默认 | 分析报告 | Attempt 索引 |
|
|
18
24
|
| 自定义摆法 | `niceeval view --report reports/exam.tsx` | `niceeval show --report reports/exam.tsx` |
|
|
19
25
|
|
|
20
26
|
四个格子是同一个宿主。自定义只有三个层次,逐层深入:
|
|
21
27
|
|
|
22
28
|
1. **换摆法**:用官方组件和 `Row` / `Col` 重摆版面——大多数「要自己的口径」到这层就够了。
|
|
23
|
-
2. **换口径**:`defineMetric`
|
|
24
|
-
3.
|
|
29
|
+
2. **换口径**:`defineMetric` 定义自己的指标,自定义维度和 `flag()` 定义自己的分组。
|
|
30
|
+
3. **换形态**:官方组件摆不出的展示——是表就用 `<Table>` 自定义列,不是表就用 `defineComponent` 写自己的组件,网页怎么渲染、终端字符怎么排,两个面你都说了算。
|
|
25
31
|
|
|
26
32
|
## 一份报告 = 一个报告文件
|
|
27
33
|
|
|
28
34
|
先交代唯一的前置:报告文件是 `.tsx`,写它的项目要装 `react`(写自定义组件的 web 面还要 `@types/react`),tsconfig 里 `compilerOptions.jsx` 设为 `"react-jsx"`。裸跑 `niceeval show` / `niceeval view` 不需要这些——只有自己写报告文件才需要。
|
|
29
35
|
|
|
30
|
-
报告基座是 `defineReport`:宿主打开结果目录(含 `--run` 指定历史 run
|
|
36
|
+
报告基座是 `defineReport`:宿主打开结果目录(含 `--run` 指定历史 run)、按官方口径挑好结果快照,注入给你的函数;你只负责折数据和摆积木:
|
|
31
37
|
|
|
32
38
|
```tsx
|
|
33
39
|
// reports/exam.tsx —— 一份定义,两扇门共用
|
|
34
40
|
import {
|
|
35
41
|
defineReport, Col, Section,
|
|
36
|
-
|
|
42
|
+
MetricScatter, ExperimentList, EvalList, AttemptList, Scoreboard,
|
|
43
|
+
costUSD, passRate,
|
|
37
44
|
} from "niceeval/report";
|
|
38
45
|
|
|
39
|
-
export default defineReport(async ({ selection }) =>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
export default defineReport(async ({ selection }) => {
|
|
47
|
+
const experiments = await ExperimentList.data(selection);
|
|
48
|
+
return (
|
|
49
|
+
<Col>
|
|
50
|
+
<MetricScatter selection={selection} points="experiment" series="agent" x={costUSD} y={passRate} />
|
|
51
|
+
<ExperimentList items={experiments} />
|
|
52
|
+
<Section title="考试成绩单">
|
|
53
|
+
<Scoreboard data={await Scoreboard.data(selection, { rows: "agent", subjects: "evalGroup" })} />
|
|
54
|
+
</Section>
|
|
55
|
+
</Col>
|
|
56
|
+
);
|
|
57
|
+
});
|
|
48
58
|
```
|
|
49
59
|
|
|
50
60
|
```bash
|
|
@@ -52,15 +62,17 @@ niceeval show --report reports/exam.tsx # 终端:同一棵树走文本面
|
|
|
52
62
|
niceeval view --report reports/exam.tsx # 网页:同一棵树走网页面,attempt 深链直达证据室
|
|
53
63
|
```
|
|
54
64
|
|
|
55
|
-
注入的上下文只有两样,全部是[结果数据 API](/zh/guides/results-data) 的原语,宿主没有私有通道:`results` 就是 `openResults(".niceeval")` 的返回(实验、历次快照、attempt 级 `diff()`、`trace()` 都在上面);`selection`
|
|
65
|
+
注入的上下文只有两样,全部是[结果数据 API](/zh/guides/results-data) 的原语,宿主没有私有通道:`results` 就是 `openResults(".niceeval")` 的返回(实验、历次快照、attempt 级 `diff()`、`trace()` 都在上面);`selection` 是宿主替你挑好的那份结果。挑选规则是:对每个实验、每道 eval,取该实验历史运行里最新的那次判定——只按前缀重跑了一部分 eval,其余 eval 的判定从更早的运行补齐,不会因为一次局部重跑就整体退回某一份残缺快照。默认报告吃的就是这份 `selection`,快照在 `selection.snapshots`,挑选提醒在 `selection.warnings`。默认挑法不合口径时,拿 `results` 自己挑,手工挑的 `Snapshot[]` 数组同样能喂给每个组件。
|
|
56
66
|
|
|
57
|
-
|
|
67
|
+
报告文件里有两种数据形态。实体列表的 `.data(selection)` 返回普通数组:`ExperimentListItem[]`、`EvalListItem[]`、`AttemptListItem[]`。报告作者用 JavaScript `.filter()` / `.slice()` 决定展示哪些实体,再把数组作为 `items` 传给列表;组件不藏另一套过滤 DSL。指标图形和汇总组件收算好的 `data`;其中 `MetricScatter` 也提供 `selection` 简写,由宿主在渲染前计算。计算后的组件只渲染传入数据,不碰结果目录。
|
|
58
68
|
|
|
59
|
-
|
|
69
|
+
挑选提醒(覆盖不全、快照过期、有没跑完的运行)不用你在报告里专门摆一个组件来显示:不管你的报告摆了哪些组件,`niceeval show` 和 `niceeval view` 都会把 `selection.warnings` 显示在报告正文上方,不会因为你没摆某一块就静默丢掉。
|
|
60
70
|
|
|
61
|
-
|
|
71
|
+
命令行的范围先作用在挑选上,报告拿到的就是收窄到这个范围后的 `selection`:位置参数的 eval id 前缀收窄 Selection 覆盖的 eval(覆盖提醒的分母同样收窄到范围内),`--run` 把结果根换成指定目录,`--experiment` 让 Selection 只留该实验。`--history` 与 `--report` 互斥——趋势在报告里用 `exp.snapshots` 自己摆;证据切面(`--eval` / `--execution` / `--diff`)只看证据,不渲染报告。
|
|
62
72
|
|
|
63
|
-
|
|
73
|
+
页面里的每个组件都是**双面**的:网页面是 React 渲染,终端面是字符渲染,两面吃同一份算好的数据。实体列表按 experiment → Eval → Attempt 展示事实;指标表、矩阵、条形图、成绩单、散点图、趋势图和差异表展示聚合值。完整清单见[报告组件](/zh/guides/report-components)。网页面的实体、格子和点深链到 Attempt 详情,终端面印出对应的 `niceeval show <eval id>` 下钻命令。
|
|
74
|
+
|
|
75
|
+
默认报告没有特权:它就是上面导出的那个值 `CostPassRateComparison`,正文是一张 `MetricScatter` 加一份 `ExperimentList`。你的报告和它同级;需要相同摆法时 import 同样的公开组件,不需要就不摆。
|
|
64
76
|
|
|
65
77
|
## 排版:Row 与 Col 在两个面都成立
|
|
66
78
|
|
|
@@ -70,6 +82,7 @@ niceeval view --report reports/exam.tsx # 网页:同一棵树走网页面
|
|
|
70
82
|
- **`<Row>`**:并排——网页是横向排布,终端是字符分栏;终端宽度不够时自动降级为纵向,不硬挤。
|
|
71
83
|
- **`<Section title="…">`**:带标题的块,网页是标题层级,终端是标题行加缩进。
|
|
72
84
|
- **`<Text>`**:说明文字,网页是段落,终端是折行文本。
|
|
85
|
+
- **`<Table>`**:自定义列的表——网页是 `<table>`,终端是按显示宽度对齐的字符表,中文列不撕歪。
|
|
73
86
|
|
|
74
87
|
```tsx
|
|
75
88
|
<Row>
|
|
@@ -88,9 +101,53 @@ codex 78.0/100 40/50 38/50 │ codex 80% $0.51
|
|
|
88
101
|
|
|
89
102
|
页面树里只放双面组件和排版原语,不放裸 HTML 标签——终端面没法渲染一个 `<div>`。要自由内容,说明文字用 `<Text>`,更自由的走下面的自定义组件。
|
|
90
103
|
|
|
104
|
+
## 按组摘要:GroupSummary
|
|
105
|
+
|
|
106
|
+
想把实验按目录前缀分组、每组顶部摆一块紧凑统计(通过率、experiment/eval 数、failed/errored、总成本、最后运行时间),用官方组件 `GroupSummary`。它的计算函数 `GroupSummary.data` 只吃一份已经收窄好的 Selection——不管你怎么分组,把对应组的 Selection 传进去,就是同一套折叠口径(eval 级折叠计票、null-safe 总成本、组内最后运行时间的最大值),不是另一套近似公式。
|
|
107
|
+
|
|
108
|
+
常见的分法是按 experiment id 的目录前缀(`compare/bub-low` 属于组 `compare`,顶层实验不属于任何组);用 `Selection.filter` 收窄出每组的 Selection,就能一组摆一块:
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
// reports/groups.tsx —— 按 experiment id 前缀分组,每组顶部摆一块统计
|
|
112
|
+
import { defineReport, Col, Section, GroupSummary } from "niceeval/report";
|
|
113
|
+
import type { Snapshot } from "niceeval/report";
|
|
114
|
+
|
|
115
|
+
// experiment id 的目录前缀当组名:顶层实验(id 无 "/")不属于任何组。
|
|
116
|
+
function groupOf(snapshot: Snapshot): string | undefined {
|
|
117
|
+
const parts = snapshot.experimentId.split("/");
|
|
118
|
+
return parts.length > 1 ? parts.slice(0, -1).join("/") : undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default defineReport(async ({ selection }) => {
|
|
122
|
+
const groups = [...new Set(selection.snapshots.map(groupOf))];
|
|
123
|
+
return (
|
|
124
|
+
<Col>
|
|
125
|
+
{await Promise.all(
|
|
126
|
+
groups.map(async (key) => {
|
|
127
|
+
const scoped = selection.filter((s) => groupOf(s) === key);
|
|
128
|
+
const summary = <GroupSummary data={await GroupSummary.data(scoped)} />;
|
|
129
|
+
return key === undefined ? summary : (
|
|
130
|
+
<Section key={key} title={key}>{summary}</Section>
|
|
131
|
+
);
|
|
132
|
+
}),
|
|
133
|
+
)}
|
|
134
|
+
</Col>
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```text
|
|
140
|
+
$ niceeval show --report reports/groups.tsx
|
|
141
|
+
compare
|
|
142
|
+
Pass rate 60% · 2 experiments · 6 evals · failed 1 · errored 1 · $1.50
|
|
143
|
+
latest 2026-07-01T11:30:00Z
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`GroupSummary` 之后想接着摆散点图或实验列表,把同一份 `scoped` Selection 传给散点图,再用 `await ExperimentList.data(scoped)` 得到可自行过滤的 experiment 数组。想自由换行维度和指标列,再用下面的通用 `MetricTable`。
|
|
147
|
+
|
|
91
148
|
## 换口径:自定义指标
|
|
92
149
|
|
|
93
|
-
|
|
150
|
+
实验、Eval、Attempt 的固定诊断字段由三个实体列表承接。下面的例子是另一种需求:用通用 `MetricTable` 自由换指标口径——每个组件的数据算法都挂在自己身上,换口径只需换 columns:
|
|
94
151
|
|
|
95
152
|
```tsx
|
|
96
153
|
// reports/golf.tsx —— code-golf:谁写出能用的代码,谁写得短
|
|
@@ -105,7 +162,7 @@ const codeLines = defineMetric({
|
|
|
105
162
|
label: "代码行数",
|
|
106
163
|
better: "lower",
|
|
107
164
|
unit: "lines",
|
|
108
|
-
where: (a) => a.result.
|
|
165
|
+
where: (a) => a.result.verdict === "passed",
|
|
109
166
|
async value(attempt) {
|
|
110
167
|
const diff = await attempt.diff();
|
|
111
168
|
if (!diff) return null;
|
|
@@ -136,7 +193,41 @@ codex 80% 355 lines $0.51
|
|
|
136
193
|
|
|
137
194
|
两个面继承同一套诚实契约:排序方向随指标的 `better`,覆盖不全的格子带 `12/15` 角标(15 个 attempt 里 12 个测得了该指标),缺数据渲染成 `—` 而不是 0。指标只定义一次,两个面共用——人在网页上看到的数字,就是 agent 在 stdout 里读到的数字,判断口径永远一致。给 agent 的指引也只多一行:跑 `niceeval show --report reports/golf.tsx`,读 stdout。
|
|
138
195
|
|
|
139
|
-
|
|
196
|
+
`label` 可以是一份文案,也可以按语言给:`label: { en: "Code lines", "zh-CN": "代码行数" }`——查看器界面切语言时,按语言给的 label 跟着切;只给一份就两种语言都用它。指标算出来的数字本身不分语言。
|
|
197
|
+
|
|
198
|
+
内置指标里 `passRate` / `costUSD` / `durationMs` / `tokens` 只读 attempt 自带的判定、用量这些字段,任何一份结果目录都算得出。`turns`(agent 的总轮次)不一样,它读 `attempt.o11y()`——这份数据发布时不一定随行,`copySnapshots` 要显式把 `"o11y"` 加进 `artifacts` 才带得上(见[结果数据 API](/zh/guides/results-data)的「发布」一节),漏了就渲染成 `—`,不是 0。自己写的指标只要读了 `o11y()` / `diff()` 这类 artifact(就像上面 `codeLines` 读 `attempt.diff()`),发布前都要过一遍同样的检查。
|
|
199
|
+
|
|
200
|
+
## 换分组:三种维度
|
|
201
|
+
|
|
202
|
+
维度决定分组——表的行、矩阵的行列、散点的点。每个维度槽收三种值:
|
|
203
|
+
|
|
204
|
+
- **内置维度**:`"agent"`、`"model"`、`"experiment"`、`"eval"`、`"evalGroup"`、`"snapshot"`,结果里现成的身份字段。
|
|
205
|
+
- **自定义维度**:`{ name, of }`,一个纯函数吃 attempt、吐组名。定义只住在报告文件里,不用改任何 experiment 文件。
|
|
206
|
+
- **`flag()`**:引用 experiment 用 `flags` 声明的变量。
|
|
207
|
+
|
|
208
|
+
选哪种看变量住在哪:分组能从结果已有数据**算**出来的,用自定义维度——报告只管「怎么摆」,不反过来要求 experiment 为一种摆法改配置;变量是 experiment 本身的**配置**——scaling 档位、要当数值轴用、要在 experiment 定义处一眼看到的——才声明成 `flags`,报告用 `flag()` 引用。数值轴(`MetricLine` 的 `x`)只收 `flag()`:刻度必须来自声明的数值,函数派生的组名当不了刻度。
|
|
209
|
+
|
|
210
|
+
### 自定义维度:从已有数据派生分组
|
|
211
|
+
|
|
212
|
+
比如把不同模型折成厂商,按厂商比通过率——分组从 `result.model` 算出来,experiment 文件一个都不用碰:
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
import type { Dimension } from "niceeval/report";
|
|
216
|
+
|
|
217
|
+
const vendor: Dimension = {
|
|
218
|
+
name: "vendor",
|
|
219
|
+
of: (a) => (a.result.model?.startsWith("gpt-") ? "OpenAI" : "Anthropic"),
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
<MetricTable data={await MetricTable.data(selection, {
|
|
223
|
+
rows: vendor,
|
|
224
|
+
columns: [passRate, costUSD],
|
|
225
|
+
})} />
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`of` 能读 attempt 的全部已有数据:`evalId`、`experimentId`、`result` 里的判定与 experiment 元数据。它只能派生、不能补造:如果两组 experiment 的差别只体现在文件命名里(`bub-baseline.ts` / `bub-mempal.ts`),`of` 就只能去解析这个名字——命名约定一改,分组静默散掉。这种时候变量该搬回配置,往下看。
|
|
229
|
+
|
|
230
|
+
### `flag()`:变量来自配置,不来自命名
|
|
140
231
|
|
|
141
232
|
画「并行 agent 数 × 模拟延迟 × 得分」这类 scaling 趋势时,图上的变量不该编码进 experiment id(`ultra-16agents-300ms`),再在报告里解析字符串抠出来——那是约定不是配置,改个命名整张图就散。变量的家是 experiment 文件的 `flags`:一文件一个档位,把变量声明成数据:
|
|
142
233
|
|
|
@@ -161,15 +252,80 @@ export default defineExperiment({
|
|
|
161
252
|
})} />
|
|
162
253
|
```
|
|
163
254
|
|
|
164
|
-
`flag()` 用在维度槽(`series` / `rows` / `columns` / `points`)就按声明值分组,用在轴槽(`x
|
|
255
|
+
`flag()` 用在维度槽(`series` / `rows` / `columns` / `points`)就按声明值分组,用在轴槽(`x`)就要求数值并驱动刻度。没有声明该 flag 的 experiment 不猜:分组时如实归入「未配置」一组,作轴时该点不画、注脚报数。flags 随快照落盘,所以历史 run 也能按当时声明的变量重摆——报告读的是配置的事实,不是命名的巧合。
|
|
256
|
+
|
|
257
|
+
## 换形态:表格用 Table,其余自己画
|
|
165
258
|
|
|
166
|
-
|
|
259
|
+
官方组件摆不出来的展示分两种,各走一条路:是一张表,用排版原语 `<Table>`;不是表(通过率条形、预算燃尽、项目自己的徽章),用 `defineComponent` 写自己的双面组件。
|
|
167
260
|
|
|
168
|
-
|
|
261
|
+
### 一张表:`<Table>`
|
|
262
|
+
|
|
263
|
+
列是你定的,格子是你算好的显示值,`<Table>` 负责把网页和终端两个面都排整齐:
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
// reports/cost-board.tsx
|
|
267
|
+
import { defineReport, Col, Table, MetricTable, costUSD, passRate } from "niceeval/report";
|
|
268
|
+
|
|
269
|
+
export default defineReport(async ({ selection }) => {
|
|
270
|
+
const board = await MetricTable.data(selection, {
|
|
271
|
+
rows: "agent",
|
|
272
|
+
columns: [passRate, costUSD],
|
|
273
|
+
});
|
|
274
|
+
return (
|
|
275
|
+
<Col>
|
|
276
|
+
<Table
|
|
277
|
+
columns={[
|
|
278
|
+
{ key: "agent", header: "Agent" },
|
|
279
|
+
{ key: "pass", header: "通过率", align: "right" },
|
|
280
|
+
{ key: "cost", header: "成本", align: "right" },
|
|
281
|
+
]}
|
|
282
|
+
rows={board.rows.map((r) => ({
|
|
283
|
+
key: r.key,
|
|
284
|
+
cells: {
|
|
285
|
+
agent: r.key,
|
|
286
|
+
pass: r.cells[passRate.name].display,
|
|
287
|
+
// 缺数据交 null,组件渲染成 —;不要自己填 0
|
|
288
|
+
cost: r.cells[costUSD.name].value === null ? null : r.cells[costUSD.name].display,
|
|
289
|
+
},
|
|
290
|
+
}))}
|
|
291
|
+
/>
|
|
292
|
+
</Col>
|
|
293
|
+
);
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
```text
|
|
298
|
+
$ niceeval show --report reports/cost-board.tsx
|
|
299
|
+
Agent 通过率 成本
|
|
300
|
+
bub 87% $0.42
|
|
301
|
+
codex 80% $0.51
|
|
302
|
+
克劳德 — —
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
列宽按**终端显示宽度**算:一个汉字占 2 列,所以中文 agent 名、中文题目名都不会把表撕歪。`align: "right"` 让数字列右对齐。格子给 `null` 就渲染 `—`,不补 0。行上带 `locator` 就多出一列 attempt,网页面点进证据室、终端面把 locator 交给 `niceeval show`。完整字段见[报告组件](/zh/guides/report-components)的「表格」一节。
|
|
306
|
+
|
|
307
|
+
### 不是表:`defineComponent` 加文本排版函数
|
|
308
|
+
|
|
309
|
+
`defineComponent` 声明两个渲染面,和 `defineExperiment` / `defineMetric` / `defineReport` 同一个家族。终端面要自己排字符,用 `niceeval/report` 导出的这组函数——官方组件排的就是这几把尺子:
|
|
310
|
+
|
|
311
|
+
| 函数 | 干什么 |
|
|
312
|
+
| --- | --- |
|
|
313
|
+
| `stringWidth(text)` | 显示宽度:CJK 和全角字符记 2 列,其余记 1 列 |
|
|
314
|
+
| `padEnd(text, width)` | 按显示宽度在右侧补齐(左对齐) |
|
|
315
|
+
| `padStart(text, width)` | 按显示宽度在左侧补齐(右对齐,数字列用) |
|
|
316
|
+
| `wrapText(text, width)` | 按显示宽度折行,返回若干行 |
|
|
317
|
+
| `indent(block, prefix)` | 每行加缩进 |
|
|
318
|
+
| `bar(ratio, width)` | 字符条:`█` 填充、`░` 补齐到 `width` |
|
|
319
|
+
| `columns(blocks, widths, separator?)` | 多块并排 |
|
|
320
|
+
|
|
321
|
+
**不要用 `String.prototype.padEnd` / `padStart` 对齐终端输出。** 它们数的是 UTF-16 码元,不是显示列宽:一个汉字占 2 列,却只算 1 个码元。用它们补齐,中文一进来列就错位,而中文 agent 名和中文题目名恰恰是最常见的情形。列宽也要随内容和 `ctx.width` 算,不要硬编码一个数字。
|
|
169
322
|
|
|
170
323
|
```tsx
|
|
171
324
|
// reports/passbars.tsx
|
|
172
|
-
import {
|
|
325
|
+
import {
|
|
326
|
+
defineReport, defineComponent, Col, Style, MetricTable,
|
|
327
|
+
bar, padEnd, stringWidth, passRate,
|
|
328
|
+
} from "niceeval/report";
|
|
173
329
|
|
|
174
330
|
interface BarRow { key: string; ratio: number | null; display: string }
|
|
175
331
|
|
|
@@ -188,9 +344,13 @@ const PassBars = defineComponent<{ rows: BarRow[] }>({
|
|
|
188
344
|
);
|
|
189
345
|
},
|
|
190
346
|
text({ rows }, { width }) {
|
|
191
|
-
const
|
|
347
|
+
const label = Math.max(...rows.map((r) => stringWidth(r.key))); // 列宽随内容
|
|
348
|
+
const barWidth = Math.min(20, width - label - 8); // 也随可用列宽
|
|
192
349
|
return rows
|
|
193
|
-
.map((r) =>
|
|
350
|
+
.map((r) => {
|
|
351
|
+
const chart = r.ratio === null ? padEnd("—", barWidth) : bar(r.ratio, barWidth);
|
|
352
|
+
return `${padEnd(r.key, label)} ${chart} ${r.display}`;
|
|
353
|
+
})
|
|
194
354
|
.join("\n");
|
|
195
355
|
},
|
|
196
356
|
});
|
|
@@ -213,9 +373,9 @@ export default defineReport(async ({ selection }) => {
|
|
|
213
373
|
|
|
214
374
|
```text
|
|
215
375
|
$ niceeval show --report reports/passbars.tsx
|
|
216
|
-
bub
|
|
217
|
-
codex
|
|
218
|
-
|
|
376
|
+
bub █████████████████░░░ 87%
|
|
377
|
+
codex ████████████████░░░░ 80%
|
|
378
|
+
克劳德 — —
|
|
219
379
|
```
|
|
220
380
|
|
|
221
381
|
组件的契约:
|
|
@@ -234,10 +394,10 @@ claude — —
|
|
|
234
394
|
niceeval view --report reports/exam.tsx --out site
|
|
235
395
|
```
|
|
236
396
|
|
|
237
|
-
产物是纯静态文件:你的报告页是首页,证据室(transcript、trace、代码视图)在同一站内,报告里的每个数字点进去就是证据。组件不 hydrate
|
|
397
|
+
产物是纯静态文件:你的报告页是首页,证据室(transcript、trace、代码视图)在同一站内,报告里的每个数字点进去就是证据。组件不 hydrate;页面内联一小段官方脚本,提供表头排序、行过滤、图表悬停这些浏览操作,浏览器禁用 JS 时页面仍完整可读。交给任何静态托管即可。CI 上没有 `.niceeval/` 时,先用 `copySnapshots` 把快照瘦身进仓库再导出,流程见[查看结果](/zh/guides/viewing-results#导出与静态托管)。
|
|
238
398
|
|
|
239
399
|
双面组件的网页面就是普通 React 组件,`data` 函数就是普通 TS 函数——想把某一块指标表嵌进已有的内部面板,import 组件喂数据就行。那是零件的复用,不是另一套报告系统:报告的家在官方宿主。算数据与渲染分离部署时(CI 落 JSON、另一个应用 fetch),两侧锁同一个 niceeval 版本是硬要求——组件数据不带版本戳,兼容性跟随包版本。
|
|
240
400
|
|
|
241
401
|
## 界线:内置命令不长配置
|
|
242
402
|
|
|
243
|
-
|
|
403
|
+
一次只渲染一份报告,`--report` 收显式文件路径——没有 `reports/` 目录自动发现、没有插件注册表、没有配置文件。自定义指标和自定义组件都住在你的报告文件里,随文件一起递入,宿主不为它们长任何注册面。不传 `--report` 时渲染的就是内置的 `CostPassRateComparison`,你的报告和它是同级实现。
|
|
@@ -37,6 +37,12 @@ npx niceeval exp compare-models # 把同组各 model 并排出报告
|
|
|
37
37
|
|
|
38
38
|
这样每个配置独立成文件:可命名、可 diff、可单独 review,"这一组就是对照"在文件结构上就讲清楚了。
|
|
39
39
|
|
|
40
|
+
某一格结果反常、需要单独复现时,用该配置的完整 id(`组/文件名`)精确只跑这一格,不用先跑完整组:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx niceeval exp compare-models/gpt-5.4
|
|
44
|
+
```
|
|
45
|
+
|
|
40
46
|
`defineExperiment` 的字段怎么写、`flags` 怎么透传到 adapter,见[写实验](/zh/guides/write-experiment)。
|
|
41
47
|
|
|
42
48
|
## 适合比较什么
|
|
@@ -45,7 +51,7 @@ npx niceeval exp compare-models # 把同组各 model 并排出报告
|
|
|
45
51
|
- 不同模型(Tier 1 接入即可:应用接口暴露模型选择,`model` 经 `ctx.model` 透传)。
|
|
46
52
|
- 不同 prompts 或 feature flags(要求 Tier 3 接入:变体在应用内部,需要应用把它暴露成 experiment 可选的 flag,经 `flags` → `ctx.flags` 透传)。
|
|
47
53
|
- 不同 sandbox 后端。
|
|
48
|
-
- 不同运行环境条件(比如装不装某个记忆工具的二进制、有没有预置状态):环境差异写在 `sandbox` spec 的 `.setup()` / `.teardown()` 钩子里,一个变体一个 experiment 文件,见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-
|
|
54
|
+
- 不同运行环境条件(比如装不装某个记忆工具的二进制、有没有预置状态):环境差异写在 `sandbox` spec 的 `.setup()` / `.teardown()` 钩子里,一个变体一个 experiment 文件,见 [Sandbox 后端 · 环境钩子](/zh/guides/sandbox-providers#环境钩子)。
|
|
49
55
|
- 同一任务的 pass@N。
|
|
50
56
|
|
|
51
57
|
Tier 1 / Tier 2 / Tier 3 的定义见 [Tier](/zh/concepts/tier)。
|
|
@@ -6,7 +6,7 @@ description: "用 .eval.ts 给 coding agent 准备隔离 workspace、发送真
|
|
|
6
6
|
|
|
7
7
|
评估 coding agent 时,单纯检查一段回复不够。你通常要给它一个真实项目,让它读写文件、运行命令、提交改动,然后检查产物是否正确。[NiceEval](https://niceeval.com/) 的当前推荐写法是:用普通 `.eval.ts` 文件显式准备 sandbox workspace、发送任务并断言结果。
|
|
8
8
|
|
|
9
|
-
可运行参考见 [
|
|
9
|
+
可运行参考见 [coding-agent-skill](https://github.com/CorrectRoadH/coding-agent-skill)(独立仓库)。
|
|
10
10
|
|
|
11
11
|
## 推荐目录结构
|
|
12
12
|
|
|
@@ -117,8 +117,10 @@ t.check(result.stdout.trim(), includes(/ok/));
|
|
|
117
117
|
也可以运行项目自己的 test/lint/build 脚本:
|
|
118
118
|
|
|
119
119
|
```ts
|
|
120
|
+
import { commandSucceeded } from "niceeval/expect";
|
|
121
|
+
|
|
120
122
|
const result = await t.sandbox.runCommand("npm", ["test"]);
|
|
121
|
-
t.check(result
|
|
123
|
+
t.check(result, commandSucceeded());
|
|
122
124
|
```
|
|
123
125
|
|
|
124
126
|
## 用 experiment 做对照组
|
|
@@ -14,7 +14,8 @@ description: "NiceEval 内置的 Sandbox 和非 Sandbox 适配器分别是什么
|
|
|
14
14
|
|
|
15
15
|
- **鉴权**:`ANTHROPIC_API_KEY`(工厂参数 `apiKey` 可覆盖),可选 `ANTHROPIC_BASE_URL`(工厂参数 `baseUrl`)。
|
|
16
16
|
- **装 MCP server**:`mcpServers` 配置项,`setup` 阶段写进沙箱里用户级的 `~/.claude.json`(顶层 `mcpServers` 字段)。
|
|
17
|
-
- **装 Skill**:`skills: ["
|
|
17
|
+
- **装 Skill**:`skills: SkillSpec[]`——本地 Skill(`{ kind: "local", path }`,从项目根读文件或目录)或 Repo Skill(`{ kind: "repo", source, ref, skills }`,可钉 commit/tag、可只启用多 Skill 仓库里的一部分)。装进沙箱的 project 级 `.claude/skills/<name>/`,claude CLI 原生发现(原生 `Skill` 工具调用被 adapter 归一为 `skill.loaded` 事件,不重复记成工具调用;用 `t.loadedSkill()` 断言,不是 `t.calledTool("Skill", ...)`)。
|
|
18
|
+
- **装原生 Plugin**:`plugins: ClaudeCodePluginSpec[]`,每一项声明 Marketplace 连接(`name` / `source` / 可选 `ref`)和其中的 Plugin 名。这个类型只属于 claude-code,传不进 codex。
|
|
18
19
|
- **tracing**:claude CLI 的 beta 原生遥测(`CLAUDE_CODE_ENHANCED_TELEMETRY_BETA`),span 只有结构和计时,细节见 [OTel 接入](/zh/guides/connect-otel)。
|
|
19
20
|
|
|
20
21
|
```ts
|
|
@@ -27,7 +28,16 @@ export default defineExperiment({
|
|
|
27
28
|
mcpServers: [
|
|
28
29
|
{ name: "browser", command: "npx", args: ["-y", "@anthropic/mcp-browser"] },
|
|
29
30
|
],
|
|
30
|
-
skills: [
|
|
31
|
+
skills: [
|
|
32
|
+
{ kind: "repo", source: "Effect-TS/skills", ref: "8f3c1a2", skills: ["effect"] },
|
|
33
|
+
{ kind: "local", path: "skills/repository-guide.md" },
|
|
34
|
+
],
|
|
35
|
+
plugins: [
|
|
36
|
+
{
|
|
37
|
+
marketplace: { name: "acme", source: "acme/claude-code-plugins", ref: "v1.3.0" },
|
|
38
|
+
name: "safe-shell",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
31
41
|
}),
|
|
32
42
|
model: "claude-sonnet-4-6",
|
|
33
43
|
sandbox: dockerSandbox(),
|
|
@@ -43,7 +53,8 @@ export default defineExperiment({
|
|
|
43
53
|
是复数 `mcp_servers`:单数 `[mcp_server.x]` 会被 codex CLI 静默忽略,MCP 压根挂不上,且不会报错——自查用 `codex mcp list`。
|
|
44
54
|
</Warning>
|
|
45
55
|
|
|
46
|
-
- **装 Skill**:`skills: [
|
|
56
|
+
- **装 Skill**:`skills: SkillSpec[]`,与 claude-code 同一个类型。装进 `.agents/skills/<name>/`,并把发现指引写进 AGENTS.md——codex 没有 claude-code 那种原生 Skill 工具,只把文件装进去它不会主动去读;断言"用没用到"看它是否真的执行过读那个文件的 shell 命令,没有工具调用可以直接认。
|
|
57
|
+
- **装原生 Plugin**:`plugins: CodexPluginSpec[]`,声明 Marketplace 连接(`name` / `source` / 可选 `ref`)和其中的 Plugin 名。这个类型只属于 codex,传不进 claude-code。
|
|
47
58
|
- **tracing**:内置,通过 `config.toml` 的 `[otel.trace_exporter.otlp-http]` 段配置,协议 `http/json`。
|
|
48
59
|
|
|
49
60
|
```ts
|
|
@@ -56,7 +67,13 @@ export default defineExperiment({
|
|
|
56
67
|
mcpServers: [
|
|
57
68
|
{ name: "browser", command: "npx", args: ["-y", "@anthropic/mcp-browser"] },
|
|
58
69
|
],
|
|
59
|
-
skills: ["Effect-TS/skills"],
|
|
70
|
+
skills: [{ kind: "repo", source: "Effect-TS/skills", ref: "8f3c1a2", skills: ["effect"] }],
|
|
71
|
+
plugins: [
|
|
72
|
+
{
|
|
73
|
+
marketplace: { name: "acme", source: "acme/codex-plugins", ref: "8f3c1a2" },
|
|
74
|
+
name: "repo-map",
|
|
75
|
+
},
|
|
76
|
+
],
|
|
60
77
|
}),
|
|
61
78
|
model: "gpt-5.4",
|
|
62
79
|
sandbox: dockerSandbox(),
|
|
@@ -66,8 +83,9 @@ export default defineExperiment({
|
|
|
66
83
|
### bub
|
|
67
84
|
|
|
68
85
|
- **鉴权**:`BUB_API_KEY` + `BUB_API_BASE`(OpenAI 兼容代理),工厂参数 `apiKey` / `apiBase` 可覆盖。
|
|
69
|
-
-
|
|
70
|
-
-
|
|
86
|
+
- **装 Skill**:`skills: SkillSpec[]`,与另外两个 Adapter 同一个类型。装进 `.agents/skills/<name>/`,发现指引写进 AGENTS.md。
|
|
87
|
+
- **装插件**:`pythonPlugins: PythonPluginSpec[]`(`{ package }`:PyPI 包、版本约束或 git URL),`setup` 阶段进 `uv tool install … --with <package>`。这个类型只属于 bub;package 集合进安装 checkpoint key,插件不同的两个变体不会复用同一份安装缓存。
|
|
88
|
+
- bub 没有 `mcpServers`——MCP 只属于支持它的 Adapter,Config 上压根没有这个字段。
|
|
71
89
|
- **安装方式**:走 `uv tool install`(PyPI 包,不是 npm 包),首次安装会建 checkpoint 缓存加速后续沙箱。
|
|
72
90
|
- **tracing**:内置,通过环境变量注入,协议 `http/protobuf`。
|
|
73
91
|
|
|
@@ -78,7 +96,8 @@ import { dockerSandbox } from "niceeval/sandbox";
|
|
|
78
96
|
|
|
79
97
|
export default defineExperiment({
|
|
80
98
|
agent: bubAgent({
|
|
81
|
-
|
|
99
|
+
skills: [{ kind: "local", path: "skills/repository-guide.md" }],
|
|
100
|
+
pythonPlugins: [{ package: "bub-plugin-memory==1.3.0" }],
|
|
82
101
|
}),
|
|
83
102
|
model: "gpt-5.4",
|
|
84
103
|
sandbox: dockerSandbox(),
|
|
@@ -91,11 +110,14 @@ export default defineExperiment({
|
|
|
91
110
|
|---|---|---|---|
|
|
92
111
|
| 鉴权环境变量 | `ANTHROPIC_API_KEY` | `CODEX_API_KEY` | `BUB_API_KEY` + `BUB_API_BASE` |
|
|
93
112
|
| MCP server | ✅ `mcpServers` | ✅ `mcpServers` | ❌ |
|
|
94
|
-
| Skill | ✅ `skills
|
|
95
|
-
|
|
|
113
|
+
| Skill | ✅ `skills: SkillSpec[]`(原生发现) | ✅ `skills: SkillSpec[]`(+ 发现指引) | ✅ `skills: SkillSpec[]`(+ 发现指引) |
|
|
114
|
+
| 原生 Plugin | ✅ `plugins: ClaudeCodePluginSpec[]` | ✅ `plugins: CodexPluginSpec[]` | ❌ |
|
|
115
|
+
| Python Plugin | ❌ | ❌ | ✅ `pythonPlugins: PythonPluginSpec[]` |
|
|
96
116
|
| tracing | ✅(beta,仅结构与计时) | ✅ | ✅ |
|
|
97
117
|
| 安装方式 | npm 全局包 | npm 全局包 | `uv tool install`(PyPI) |
|
|
98
118
|
|
|
119
|
+
装了什么有据可查:Adapter 在 `setup` 收尾把安装清单写进沙箱的 `__niceeval__/agent-setup.json`,运行器把它存成 Attempt Artifact `agent-setup.json`(库里读 `attempt.agentSetup()`)。清单只记来源、ref、Skill/Plugin 名和解析出的版本,不记 API key 或环境变量值。
|
|
120
|
+
|
|
99
121
|
## 非 Sandbox 适配器
|
|
100
122
|
|
|
101
123
|
被测对象不需要隔离工作区——已经部署的 HTTP 服务、自研 agent loop——用非 Sandbox 适配器无侵入接入,不需要 Docker。
|
|
@@ -4,13 +4,13 @@ sidebarTitle: "CI 发布报告"
|
|
|
4
4
|
description: "把报告站接进 CI:.niceeval/ 提交进仓库,构建命令一行 view --out,push 即发布。发布的站和本地 niceeval view 完全一样;零可读结果时构建失败,保住上一次部署。"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
`niceeval view --out <目录>`
|
|
7
|
+
`niceeval view --out <目录>` 把查看器导出成一个纯静态目录:报告、结果快照列表、transcript、trace 瀑布,和本地 `niceeval view` 看到的完全一样(导出行为见[查看结果 · 导出与静态托管](/zh/guides/viewing-results#导出与静态托管))。CI 发布只需要让结果数据到构建机手里——把 `.niceeval/` 提交进仓库,仓库本身就是数据源。本地跑完 eval,提交并 push 就是发布,不需要任何脚本或本地构建命令。
|
|
8
8
|
|
|
9
9
|
## `.gitignore` 只排除 `diff.json`
|
|
10
10
|
|
|
11
11
|
```gitignore
|
|
12
12
|
# .niceeval/ 提交进仓库:它是线上报告的数据源,CI 构建时直接从这里生成整站。
|
|
13
|
-
# 只排除 diff.json —— 单轮可达上百 MB
|
|
13
|
+
# 只排除 diff.json —— 单轮可达上百 MB,体积太大不适合进仓库;Attempt 详情仍会读取它,只是不随静态导出复制。
|
|
14
14
|
.niceeval/**/diff.json
|
|
15
15
|
```
|
|
16
16
|
|
|
@@ -20,7 +20,7 @@ description: "把报告站接进 CI:.niceeval/ 提交进仓库,构建命令一
|
|
|
20
20
|
npx niceeval view --out site
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
`view` 对零可读结果直接报错、非零退出,不会导出一张空报告——`.niceeval/` checkout 坏掉,或所有落盘与当前 niceeval 的 schemaVersion 不兼容被整批跳过时,构建失败,Vercel / GitHub Pages
|
|
23
|
+
`view` 对零可读结果直接报错、非零退出,不会导出一张空报告——`.niceeval/` checkout 坏掉,或所有落盘与当前 niceeval 的 schemaVersion 不兼容被整批跳过时,构建失败,Vercel / GitHub Pages 保留上一次部署。错误逐条列出被跳过的快照目录与原因,schemaVersion 场景还给出能直接查看旧落盘的 `npx niceeval@<版本> view` 命令。
|
|
24
24
|
|
|
25
25
|
## 发布自定义报告
|
|
26
26
|
|
|
@@ -84,7 +84,7 @@ jobs:
|
|
|
84
84
|
uses: actions/deploy-pages@v4
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
日常循环从此只有两步:本地跑 eval,提交 `.niceeval/` 并 push。发布出去的站带完整历史——同事打开链接,看到的和你本地 `niceeval view` 一模一样;仓库、本地查看器、线上报告三处同一个真相源。仓库体积随落盘增长(`diff.json` 已排除,增长主要来自 transcript 和 trace
|
|
87
|
+
日常循环从此只有两步:本地跑 eval,提交 `.niceeval/` 并 push。发布出去的站带完整历史——同事打开链接,看到的和你本地 `niceeval view` 一模一样;仓库、本地查看器、线上报告三处同一个真相源。仓库体积随落盘增长(`diff.json` 已排除,增长主要来自 transcript 和 trace artifact),不想保留的旧快照直接删对应快照目录即可,查看器和报告跟着仓库走。
|
|
88
88
|
|
|
89
89
|
## 发布的是完整证据
|
|
90
90
|
|