niceeval 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/types.d.ts +67 -5
- package/dist/context/types.d.ts +32 -12
- package/dist/i18n/en.d.ts +54 -0
- package/dist/i18n/zh-CN.d.ts +55 -1
- package/dist/o11y/types.d.ts +16 -2
- package/dist/report/aggregate.d.ts +5 -3
- package/dist/report/aggregate.js +32 -5
- package/dist/report/built-ins/experiment-comparison.d.ts +39 -1
- package/dist/report/built-ins/experiment-comparison.js +116 -10
- package/dist/report/built-ins/index.d.ts +1 -0
- package/dist/report/built-ins/index.js +1 -1
- package/dist/report/components.d.ts +8 -2
- package/dist/report/components.js +3 -3
- package/dist/report/compute.d.ts +11 -18
- package/dist/report/compute.js +54 -34
- package/dist/report/flag.d.ts +16 -1
- package/dist/report/flag.js +19 -1
- package/dist/report/format.d.ts +16 -8
- package/dist/report/format.js +27 -12
- package/dist/report/index.d.ts +4 -3
- package/dist/report/index.js +5 -4
- package/dist/report/locale.d.ts +11 -2
- package/dist/report/locale.js +23 -5
- package/dist/report/metrics.d.ts +13 -1
- package/dist/report/metrics.js +65 -14
- package/dist/report/primitives.d.ts +6 -0
- package/dist/report/react/AttemptList.d.ts +2 -2
- package/dist/report/react/AttemptList.js +5 -6
- package/dist/report/react/EvalList.d.ts +1 -1
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentComparison.d.ts +8 -0
- package/dist/report/react/ExperimentComparison.js +11 -0
- package/dist/report/react/ExperimentList.d.ts +2 -1
- package/dist/report/react/ExperimentList.js +8 -10
- package/dist/report/react/MetricScatter.js +5 -11
- package/dist/report/react/chart-math.d.ts +23 -6
- package/dist/report/react/chart-math.js +71 -19
- package/dist/report/react/fixtures.d.ts +3 -3
- package/dist/report/react/fixtures.js +21 -14
- package/dist/report/report.d.ts +5 -1
- package/dist/report/report.js +6 -2
- package/dist/report/text/faces.d.ts +1 -1
- package/dist/report/text/faces.js +42 -41
- package/dist/report/text/table.js +36 -5
- package/dist/report/types.d.ts +39 -21
- package/dist/results/types.d.ts +11 -0
- package/dist/runner/feedback/sink.d.ts +110 -0
- package/dist/runner/types.d.ts +513 -22
- package/dist/sandbox/docker.d.ts +23 -2
- package/dist/sandbox/e2b.d.ts +15 -1
- package/dist/sandbox/errors.d.ts +30 -3
- package/dist/sandbox/io-retry.d.ts +17 -0
- package/dist/sandbox/registry.d.ts +2 -0
- package/dist/sandbox/resolve.d.ts +18 -5
- package/dist/sandbox/retry.d.ts +11 -1
- package/dist/sandbox/types.d.ts +39 -5
- package/dist/sandbox/vercel.d.ts +7 -1
- package/dist/scoring/coverage.d.ts +30 -0
- package/dist/scoring/display.d.ts +21 -0
- package/dist/scoring/display.js +120 -0
- package/dist/scoring/types.d.ts +103 -20
- package/dist/shared/aggregate.d.ts +1 -0
- package/dist/shared/aggregate.js +3 -3
- package/dist/shared/types.d.ts +28 -0
- package/dist/tty-line.d.ts +0 -4
- package/dist/util.d.ts +23 -0
- package/docs-site/zh/concepts/adapter.mdx +22 -4
- package/docs-site/zh/concepts/experiment.mdx +1 -1
- package/docs-site/zh/concepts/overview.mdx +6 -6
- package/docs-site/zh/guides/agent-feedback-loop.mdx +28 -26
- package/docs-site/zh/guides/authoring.mdx +33 -0
- package/docs-site/zh/guides/ci-integration.mdx +23 -12
- package/docs-site/zh/guides/connect-your-agent.mdx +29 -3
- package/docs-site/zh/guides/custom-reports.mdx +29 -34
- package/docs-site/zh/guides/dataset-fanout.mdx +25 -3
- package/docs-site/zh/guides/debug-sandbox.mdx +57 -0
- package/docs-site/zh/guides/debugging.mdx +210 -0
- package/docs-site/zh/guides/experiments.mdx +10 -3
- package/docs-site/zh/guides/official-adapters.mdx +26 -2
- package/docs-site/zh/guides/publish-report.mdx +30 -16
- package/docs-site/zh/guides/report-components.mdx +42 -30
- package/docs-site/zh/guides/reporters.mdx +2 -2
- package/docs-site/zh/guides/results-data.mdx +17 -9
- package/docs-site/zh/guides/runner.mdx +17 -7
- package/docs-site/zh/guides/sandbox-agent.mdx +56 -7
- package/docs-site/zh/guides/sandbox-providers.mdx +257 -9
- package/docs-site/zh/guides/scoring-guide.mdx +4 -4
- package/docs-site/zh/guides/viewing-results.mdx +79 -36
- package/docs-site/zh/guides/write-experiment.mdx +5 -3
- package/docs-site/zh/guides/write-send.mdx +17 -1
- package/docs-site/zh/index.mdx +1 -1
- package/docs-site/zh/reference/builtin-agents.mdx +27 -0
- package/docs-site/zh/reference/capabilities.mdx +2 -2
- package/docs-site/zh/reference/cli.mdx +33 -7
- package/docs-site/zh/reference/define-agent.mdx +57 -4
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +42 -9
- package/docs-site/zh/reference/expect.mdx +26 -1
- package/package.json +5 -1
- package/src/agents/ai-sdk-otel.test.ts +1 -0
- package/src/agents/ai-sdk.test.ts +3 -0
- package/src/agents/ai-sdk.ts +3 -0
- package/src/agents/bub-install-spec.test.ts +34 -0
- package/src/agents/bub-install-spec.ts +32 -0
- package/src/agents/bub.ts +31 -32
- package/src/agents/claude-code.test.ts +130 -9
- package/src/agents/claude-code.ts +76 -4
- package/src/agents/codex.test.ts +189 -40
- package/src/agents/codex.ts +155 -14
- package/src/agents/coding-cli-versions.test.ts +15 -0
- package/src/agents/coding-cli-versions.ts +3 -0
- package/src/agents/index.ts +11 -0
- package/src/agents/langgraph.test.ts +204 -0
- package/src/agents/langgraph.ts +495 -0
- package/src/agents/marketplace.ts +85 -0
- package/src/agents/native-config.test.ts +179 -0
- package/src/agents/native-config.ts +267 -0
- package/src/agents/openai-compat.test.ts +1 -0
- package/src/agents/openclaw.test.ts +31 -0
- package/src/agents/openclaw.ts +171 -0
- package/src/agents/plugin-config.test.ts +1 -0
- package/src/agents/sdk-streams.test.ts +79 -0
- package/src/agents/sdk-streams.ts +55 -10
- package/src/agents/skills.test.ts +1 -0
- package/src/agents/streaming.test.ts +3 -9
- package/src/agents/types.ts +68 -5
- package/src/agents/ui-message-stream.test.ts +3 -0
- package/src/cli.ts +411 -108
- package/src/context/context.test.ts +51 -12
- package/src/context/context.ts +161 -29
- package/src/context/session.test.ts +1 -0
- package/src/context/session.ts +114 -6
- package/src/context/types.ts +30 -12
- package/src/define.test.ts +13 -8
- package/src/define.ts +25 -4
- package/src/expect/index.ts +53 -23
- package/src/i18n/en.ts +64 -2
- package/src/i18n/zh-CN.ts +65 -3
- package/src/o11y/cost.test.ts +1 -0
- package/src/o11y/execution-tree.test.ts +1 -20
- package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
- package/src/o11y/otlp/parse.test.ts +1 -0
- package/src/o11y/otlp/turn-otel.test.ts +1 -0
- package/src/o11y/parsers/bub.test.ts +1 -0
- package/src/o11y/parsers/claude-code.test.ts +1 -34
- package/src/o11y/parsers/openclaw.test.ts +154 -0
- package/src/o11y/parsers/openclaw.ts +310 -0
- package/src/o11y/prices.json +746 -311
- package/src/o11y/tool-names.test.ts +1 -0
- package/src/o11y/types.ts +16 -2
- package/src/report/aggregate.ts +34 -5
- package/src/report/built-in-user-parity.test.tsx +110 -153
- package/src/report/built-ins/experiment-comparison.tsx +173 -13
- package/src/report/built-ins/index.ts +6 -1
- package/src/report/components.tsx +9 -3
- package/src/report/compute.ts +70 -40
- package/src/report/dual-render.test.tsx +194 -67
- package/src/report/flag.ts +30 -2
- package/src/report/format.ts +35 -11
- package/src/report/index.ts +22 -4
- package/src/report/locale.ts +25 -5
- package/src/report/metrics.ts +67 -14
- package/src/report/primitives.tsx +6 -0
- package/src/report/react/AttemptList.tsx +6 -31
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentComparison.tsx +68 -0
- package/src/report/react/ExperimentList.tsx +15 -9
- package/src/report/react/MetricScatter.tsx +12 -14
- package/src/report/react/chart-math.test.ts +85 -0
- package/src/report/react/chart-math.ts +101 -22
- package/src/report/react/enhance.js +33 -1
- package/src/report/react/fixtures.ts +24 -17
- package/src/report/react/render.test.tsx +9 -64
- package/src/report/react/styles.css +73 -2
- package/src/report/report.test.ts +306 -98
- package/src/report/report.ts +6 -2
- package/src/report/text/faces.ts +47 -43
- package/src/report/text/table.ts +42 -5
- package/src/report/types.ts +41 -21
- package/src/results/annotated-source.test.ts +62 -9
- package/src/results/annotated-source.ts +64 -6
- package/src/results/attempt-evidence.test.ts +9 -7
- package/src/results/attempt-evidence.ts +15 -8
- package/src/results/attempt-source.ts +6 -3
- package/src/results/copy.ts +145 -55
- package/src/results/host-equivalence.test.ts +8 -6
- package/src/results/index.ts +2 -0
- package/src/results/locator.test.ts +1 -22
- package/src/results/open.ts +7 -1
- package/src/results/publish.ts +149 -0
- package/src/results/results.test.ts +85 -51
- package/src/results/truncate.ts +90 -0
- package/src/results/types.ts +7 -0
- package/src/results/writer.ts +31 -13
- package/src/runner/attempt.test.ts +138 -7
- package/src/runner/attempt.ts +603 -104
- package/src/runner/discover.test.ts +47 -0
- package/src/runner/discover.ts +36 -2
- package/src/runner/eval-source.test.ts +1 -27
- package/src/runner/feedback/agent.test.ts +504 -0
- package/src/runner/feedback/agent.ts +409 -0
- package/src/runner/feedback/ci.test.ts +562 -0
- package/src/runner/feedback/ci.ts +401 -0
- package/src/runner/feedback/coordinator.test.ts +317 -0
- package/src/runner/feedback/coordinator.ts +397 -0
- package/src/runner/feedback/failure.ts +40 -0
- package/src/runner/feedback/human.test.ts +616 -0
- package/src/runner/feedback/human.ts +535 -0
- package/src/runner/feedback/index.ts +66 -0
- package/src/runner/feedback/io.ts +78 -0
- package/src/runner/feedback/profile.test.ts +50 -0
- package/src/runner/feedback/profile.ts +58 -0
- package/src/runner/feedback/reducer.test.ts +395 -0
- package/src/runner/feedback/reducer.ts +260 -0
- package/src/runner/feedback/renderer.ts +82 -0
- package/src/runner/feedback/sink.ts +203 -0
- package/src/runner/feedback/testing.ts +106 -0
- package/src/runner/ledger.test.ts +230 -0
- package/src/runner/ledger.ts +329 -0
- package/src/runner/report.test.ts +128 -3
- package/src/runner/report.ts +33 -9
- package/src/runner/reporters/artifacts.ts +8 -2
- package/src/runner/reporters/braintrust.test.ts +8 -7
- package/src/runner/reporters/braintrust.ts +9 -2
- package/src/runner/reporters/index.ts +2 -2
- package/src/runner/reporters/json.test.ts +162 -0
- package/src/runner/reporters/json.ts +35 -8
- package/src/runner/reporters/shared.ts +1 -5
- package/src/runner/run.test.ts +760 -3
- package/src/runner/run.ts +242 -36
- package/src/runner/sandbox-prep.ts +3 -42
- package/src/runner/timing.ts +158 -0
- package/src/runner/types.ts +518 -22
- package/src/sandbox/checkpoint.test.ts +55 -0
- package/src/sandbox/checkpoint.ts +29 -8
- package/src/sandbox/cli-commands.ts +407 -0
- package/src/sandbox/docker.ts +115 -16
- package/src/sandbox/e2b-agent-template.test.ts +56 -0
- package/src/sandbox/e2b-agent-template.ts +94 -0
- package/src/sandbox/e2b.ts +74 -9
- package/src/sandbox/errors.ts +111 -4
- package/src/sandbox/index.ts +2 -0
- package/src/sandbox/io-retry.test.ts +58 -0
- package/src/sandbox/io-retry.ts +45 -0
- package/src/sandbox/keep-registry.test.ts +86 -0
- package/src/sandbox/keep-registry.ts +142 -0
- package/src/sandbox/keep.ts +178 -0
- package/src/sandbox/paths.test.ts +1 -0
- package/src/sandbox/paths.ts +19 -8
- package/src/sandbox/registry.ts +20 -3
- package/src/sandbox/resolve.ts +76 -11
- package/src/sandbox/retry.test.ts +70 -0
- package/src/sandbox/retry.ts +46 -4
- package/src/sandbox/types.ts +44 -6
- package/src/sandbox/vercel.ts +43 -20
- package/src/scoring/collector.ts +60 -17
- package/src/scoring/coverage.ts +95 -0
- package/src/scoring/diff.ts +81 -0
- package/src/scoring/display.test.ts +121 -0
- package/src/scoring/display.ts +133 -0
- package/src/scoring/evidence.test.ts +189 -0
- package/src/scoring/judge.test.ts +142 -0
- package/src/scoring/judge.ts +15 -18
- package/src/scoring/scoped.ts +217 -50
- package/src/scoring/types.ts +117 -20
- package/src/scoring/verdict.ts +16 -4
- package/src/shared/aggregate.ts +3 -2
- package/src/shared/types.ts +31 -0
- package/src/show/compose.ts +2 -2
- package/src/show/index.ts +21 -1
- package/src/show/render.ts +619 -104
- package/src/show/show.test.ts +235 -19
- package/src/tty-line.ts +8 -26
- package/src/util.test.ts +1 -0
- package/src/util.ts +41 -0
- package/src/view/app/components/AttemptModal.tsx +153 -2
- package/src/view/app/components/CodeView.tsx +32 -11
- package/src/view/app/components/CopyControls.tsx +2 -2
- package/src/view/app/i18n.ts +6 -0
- package/src/view/app/lib/attempt-route.test.ts +1 -0
- package/src/view/app/lib/verdict.ts +7 -9
- package/src/view/artifact-serving.test.ts +2 -1
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +17 -17
- package/src/view/data.test.ts +1 -0
- package/src/view/data.ts +11 -1
- package/src/view/index.ts +11 -0
- package/src/view/server.ts +2 -0
- package/src/view/styles.css +3 -0
- package/src/view/view-report.test.ts +6 -5
- package/src/runner/reporters/console.ts +0 -70
- package/src/runner/reporters/live.test.ts +0 -56
- package/src/runner/reporters/live.ts +0 -247
- package/src/runner/reporters/quiet.test.ts +0 -66
- package/src/runner/reporters/quiet.ts +0 -49
- package/src/runner/reporters/table.ts +0 -277
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// cases: docs/engineering/unit-tests/reports/cases.md
|
|
1
2
|
// 双面验收:每个官方组件对同一份数据,web 面(renderToStaticMarkup)与 text 面
|
|
2
3
|
// 给出一致判读 —— 排序方向随 better、samples < total 角标、缺数据 — 不补 0、
|
|
3
4
|
// 截断如实报剩余;text 面形态以内联快照锁定(照 report-components.mdx 的示例形态)。
|
|
@@ -35,9 +36,10 @@ import {
|
|
|
35
36
|
costUSD,
|
|
36
37
|
defineComponent,
|
|
37
38
|
defineReport,
|
|
39
|
+
endToEndPassRate,
|
|
38
40
|
isReportDefinition,
|
|
39
41
|
padEnd,
|
|
40
|
-
|
|
42
|
+
taskPassRate,
|
|
41
43
|
renderReportToText,
|
|
42
44
|
stringWidth,
|
|
43
45
|
} from "./index.ts";
|
|
@@ -69,7 +71,7 @@ describe("RunOverview 双面", () => {
|
|
|
69
71
|
|
|
70
72
|
it("text 面形态:头行(含通过率)+ 判定行 + 警告行", () => {
|
|
71
73
|
expect(term).toMatchInlineSnapshot(`
|
|
72
|
-
"2 experiments · 12 evals · 48 attempts ·
|
|
74
|
+
"2 experiments · 12 evals · 48 attempts · End-to-end pass rate 70% 46/48 · composed from 2 runs · latest 2026-07-01T11:30:00Z
|
|
73
75
|
passed 36 · failed 8 · errored 2 · skipped 2 · no data · 4m 21s
|
|
74
76
|
! snapshot covers 9 of 12 evals seen in history; re-run \`niceeval exp compare/bub\` for a full snapshot"
|
|
75
77
|
`);
|
|
@@ -89,13 +91,14 @@ describe("RunOverview 双面", () => {
|
|
|
89
91
|
|
|
90
92
|
// fixtures.overviewData 是手工摆好的终值,只验证渲染面「原样显示 MetricCell,不重算」;
|
|
91
93
|
// 下面这组用真实 Selection 走一遍 RunOverview.data(= compute.ts 的 overviewData()),
|
|
92
|
-
// 专门验证 totals.passRate 本身的计算口径 ——
|
|
93
|
-
//
|
|
94
|
-
// 跨题均值 (2/3 + 1) /
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
94
|
+
// 专门验证 totals.passRate 本身的计算口径 —— 四种通过率公式在这个 fixture 上各不相同:
|
|
95
|
+
// 默认端到端两级聚合:eval a 题内 2/3、eval b 题内 1、eval c errored=0,
|
|
96
|
+
// 跨题均值 (2/3 + 1 + 0) / 3 = 5/9 ≈ 55.6%
|
|
97
|
+
// 条件 taskPassRate 排除 errored:(2/3 + 1) / 2 = 5/6 ≈ 83.3%
|
|
98
|
+
// 端到端 attempt 平铺:3 passed / (3 passed + 1 failed + 1 errored) = 60%
|
|
99
|
+
// eval 折叠投票:a、b passed,c errored → 2/3 ≈ 66.7%
|
|
100
|
+
// 四个数互不相同,任何一处偷懒复用另一个公式都会在这里露馅。
|
|
101
|
+
describe("RunOverview.data · endToEndPassRate 两级聚合口径", () => {
|
|
99
102
|
function fakeVaryingAttemptsContext(): { selection: Selection; attempts: { evalId: string; result: { verdict: EvalResult["verdict"] } }[] } {
|
|
100
103
|
const dir = "/results/compare_bub/snap-1";
|
|
101
104
|
const base = {
|
|
@@ -106,7 +109,7 @@ describe("RunOverview.data · passRate 两级聚合口径", () => {
|
|
|
106
109
|
schemaVersion: 1,
|
|
107
110
|
dir,
|
|
108
111
|
};
|
|
109
|
-
const mk = (evalId: string, verdict: "passed" | "failed" | "skipped", attemptIndex: number, minute: number) => ({
|
|
112
|
+
const mk = (evalId: string, verdict: "passed" | "failed" | "errored" | "skipped", attemptIndex: number, minute: number) => ({
|
|
110
113
|
evalId,
|
|
111
114
|
experimentId: "compare/bub",
|
|
112
115
|
result: {
|
|
@@ -133,8 +136,10 @@ describe("RunOverview.data · passRate 两级聚合口径", () => {
|
|
|
133
136
|
mk("algebra/a", "passed", 2, 2),
|
|
134
137
|
// eval b:1 attempt 通过 → 题内 1
|
|
135
138
|
mk("algebra/b", "passed", 0, 3),
|
|
136
|
-
// eval c:1 attempt
|
|
137
|
-
mk("algebra/c", "
|
|
139
|
+
// eval c:1 attempt errored —— 默认端到端记 0,条件 taskPassRate 才排除
|
|
140
|
+
mk("algebra/c", "errored", 0, 4),
|
|
141
|
+
// eval d:1 attempt 跳过 —— 两级聚合与 eval 折叠计票都要把它排除在分母外
|
|
142
|
+
mk("algebra/d", "skipped", 0, 5),
|
|
138
143
|
];
|
|
139
144
|
const evalIds = [...new Set(attempts.map((a) => a.evalId))];
|
|
140
145
|
const snapshot: Snapshot = {
|
|
@@ -146,18 +151,18 @@ describe("RunOverview.data · passRate 两级聚合口径", () => {
|
|
|
146
151
|
return { selection, attempts };
|
|
147
152
|
}
|
|
148
153
|
|
|
149
|
-
it("totals.passRate =
|
|
154
|
+
it("totals.passRate = 端到端两级聚合 55.6%,不排除 errored,也不复用平铺或 eval 折叠投票", async () => {
|
|
150
155
|
const { selection, attempts } = fakeVaryingAttemptsContext();
|
|
151
156
|
const data = await RunOverview.data(selection);
|
|
152
157
|
|
|
153
|
-
expect(data.totals.passRate.value).toBeCloseTo(5 /
|
|
154
|
-
expect(data.totals.passRate.display).toBe("
|
|
155
|
-
expect(data.totals.passRate.samples).toBe(
|
|
156
|
-
expect(data.totals.passRate.total).toBe(
|
|
158
|
+
expect(data.totals.passRate.value).toBeCloseTo(5 / 9, 10);
|
|
159
|
+
expect(data.totals.passRate.display).toBe("55.6%");
|
|
160
|
+
expect(data.totals.passRate.samples).toBe(5); // 6 attempts - 1 skipped(errored 进桶且记 0)
|
|
161
|
+
expect(data.totals.passRate.total).toBe(6);
|
|
157
162
|
|
|
158
163
|
// attempt 原始占比(旧 bug 公式):必须与两级聚合不同,证明没有从 passed/failed/errored 现算
|
|
159
164
|
const attemptFraction = data.totals.passed / (data.totals.passed + data.totals.failed + data.totals.errored);
|
|
160
|
-
expect(attemptFraction).toBeCloseTo(0.
|
|
165
|
+
expect(attemptFraction).toBeCloseTo(0.6, 10);
|
|
161
166
|
expect(attemptFraction).not.toBeCloseTo(data.totals.passRate.value as number, 3);
|
|
162
167
|
|
|
163
168
|
// eval 折叠投票(evalLevelStats,GroupSummary 的口径):也必须与两级聚合不同
|
|
@@ -165,20 +170,9 @@ describe("RunOverview.data · passRate 两级聚合口径", () => {
|
|
|
165
170
|
attempts.map((a) => ({ verdict: a.result.verdict, key: a.evalId })),
|
|
166
171
|
(r) => r.key,
|
|
167
172
|
);
|
|
168
|
-
expect(stats.passRate).toBeCloseTo(
|
|
173
|
+
expect(stats.passRate).toBeCloseTo(2 / 3, 10);
|
|
169
174
|
expect(stats.passRate).not.toBeCloseTo(data.totals.passRate.value as number, 3);
|
|
170
175
|
});
|
|
171
|
-
|
|
172
|
-
it("web 面与 text 面显示同一个 passRate.display,覆盖率角标(4/5)两面一致", async () => {
|
|
173
|
-
const { selection } = fakeVaryingAttemptsContext();
|
|
174
|
-
const data = await RunOverview.data(selection);
|
|
175
|
-
const html = renderToStaticMarkup(<RunOverview data={data} />);
|
|
176
|
-
const term = text(<RunOverview data={data} />);
|
|
177
|
-
for (const face of [html, term]) {
|
|
178
|
-
expect(face).toContain(data.totals.passRate.display);
|
|
179
|
-
expect(face).toContain("4/5");
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
176
|
});
|
|
183
177
|
|
|
184
178
|
// ───────────────────────── GroupSummary ─────────────────────────
|
|
@@ -189,7 +183,7 @@ describe("GroupSummary 双面", () => {
|
|
|
189
183
|
|
|
190
184
|
it("text 面形态:一行头(通过率 + experiment/eval 数 + failed/errored + 总成本)+ 最后运行时间", () => {
|
|
191
185
|
expect(term).toMatchInlineSnapshot(`
|
|
192
|
-
"
|
|
186
|
+
"End-to-end pass rate 60% 5/6 · 2 experiments · 6 evals · failed 1 · errored 1 · $1.50
|
|
193
187
|
latest 2026-07-01T11:30:00Z"
|
|
194
188
|
`);
|
|
195
189
|
});
|
|
@@ -237,9 +231,9 @@ describe("GroupSummary 双面", () => {
|
|
|
237
231
|
expect(term).toContain("errored 1");
|
|
238
232
|
});
|
|
239
233
|
|
|
240
|
-
it("zh-CN locale:web 面走中文字典(
|
|
234
|
+
it("zh-CN locale:web 面走中文字典(端到端成功率/失败/错误/总成本/实验数),text 面同理;display 数字不本地化", () => {
|
|
241
235
|
const zhHtml = renderToStaticMarkup(<GroupSummary data={groupSummaryData} locale="zh-CN" />);
|
|
242
|
-
expect(zhHtml).toContain("
|
|
236
|
+
expect(zhHtml).toContain("端到端成功率");
|
|
243
237
|
expect(zhHtml).toContain("失败");
|
|
244
238
|
expect(zhHtml).toContain("错误");
|
|
245
239
|
expect(zhHtml).toContain("总成本");
|
|
@@ -248,7 +242,7 @@ describe("GroupSummary 双面", () => {
|
|
|
248
242
|
|
|
249
243
|
const zhCtx = createTextContext({ width: 80, locale: "zh-CN" });
|
|
250
244
|
const zhTerm = renderNodeToText(<GroupSummary data={groupSummaryData} />, zhCtx);
|
|
251
|
-
expect(zhTerm).toContain("
|
|
245
|
+
expect(zhTerm).toContain("端到端成功率");
|
|
252
246
|
expect(zhTerm).toContain("失败 1");
|
|
253
247
|
expect(zhTerm).toContain("错误 1");
|
|
254
248
|
expect(zhTerm).toContain("60%");
|
|
@@ -462,11 +456,17 @@ describe("AttemptList 双面", () => {
|
|
|
462
456
|
const html = renderToStaticMarkup(<AttemptList items={attemptListItems} />);
|
|
463
457
|
const term = text(<AttemptList items={attemptListItems} />);
|
|
464
458
|
|
|
465
|
-
it("两面同口径:判定符 + locator +
|
|
466
|
-
for (const piece of ["roots-correct", "expected x=2,
|
|
459
|
+
it("两面同口径:判定符 + locator + 主失败/error 摘要一致", () => {
|
|
460
|
+
for (const piece of ["roots-correct", "expected x=2", "received x=3", "TypeError", "@1a4a4a4", "@1c1c1c1"]) {
|
|
467
461
|
expect(html).toContain(piece);
|
|
468
462
|
expect(term).toContain(piece);
|
|
469
463
|
}
|
|
464
|
+
// 结构化 error 只显示一层摘要:stack / cause / diagnostics 属于 locator 下钻详情,
|
|
465
|
+
// 随数据携带但不塞进比较列表(docs/feature/reports/library.md「AttemptList」)。
|
|
466
|
+
for (const face of [html, term]) {
|
|
467
|
+
expect(face).not.toContain("adapter.ts:42"); // error.stack
|
|
468
|
+
expect(face).not.toContain("sandbox teardown timed out"); // diagnostics[].message
|
|
469
|
+
}
|
|
470
470
|
// web 面走证据室路由(#/attempt/@<locator>);text 面只列 locator 本身,不重复整条命令
|
|
471
471
|
// (docs/feature/reports/architecture.md「text 输出只在整份报告末尾给一次命令模板」)。
|
|
472
472
|
expect(html).toContain('href="#/attempt/@1a4a4a4"');
|
|
@@ -481,7 +481,7 @@ describe("AttemptList 双面", () => {
|
|
|
481
481
|
expect(termTrunc).toContain("2 more not shown");
|
|
482
482
|
});
|
|
483
483
|
|
|
484
|
-
it("
|
|
484
|
+
it("比较列表不内联 assertion evidence,并保留 locator 作为完整证据入口", () => {
|
|
485
485
|
const longEvidence = "x".repeat(2_000);
|
|
486
486
|
const item = {
|
|
487
487
|
...attemptListItems[0],
|
|
@@ -489,17 +489,17 @@ describe("AttemptList 双面", () => {
|
|
|
489
489
|
};
|
|
490
490
|
const out = text(<AttemptList items={[item]} />);
|
|
491
491
|
expect(out).toContain(item.locator);
|
|
492
|
-
expect(out.replace(/\s+/g, " ")).toContain(`open ${item.locator} for full evidence`);
|
|
493
492
|
expect(out).not.toContain(longEvidence);
|
|
494
493
|
expect(out.length).toBeLessThan(1_000);
|
|
495
494
|
});
|
|
496
495
|
|
|
497
|
-
it("text
|
|
496
|
+
it("text 面每个 attempt 只显示一条有界结果摘要,不展开完整断言列表", () => {
|
|
498
497
|
const blocks = term.split("\n\n");
|
|
499
|
-
expect(blocks).toHaveLength(attemptListItems.length);
|
|
498
|
+
expect(blocks).toHaveLength(attemptListItems.length);
|
|
500
499
|
const lines = term.split("\n");
|
|
501
|
-
expect(lines.some((l) => /^ \S/.test(l))).toBe(true);
|
|
502
|
-
expect(lines.some((l) => /^ \S/.test(l))).toBe(
|
|
500
|
+
expect(lines.some((l) => /^ \S/.test(l))).toBe(true);
|
|
501
|
+
expect(lines.some((l) => /^ \S/.test(l))).toBe(false);
|
|
502
|
+
expect(term).not.toContain("strong-evidence");
|
|
503
503
|
});
|
|
504
504
|
});
|
|
505
505
|
|
|
@@ -524,6 +524,16 @@ describe("EvalList 双面", () => {
|
|
|
524
524
|
expect(blocks).toHaveLength(evalListItems.length);
|
|
525
525
|
expect(term.split("\n").some((l) => /^ \S/.test(l))).toBe(true); // 展开到 attempt 的行缩进两格
|
|
526
526
|
});
|
|
527
|
+
|
|
528
|
+
// bug: memory/eval-parent-repeats-attempt-failure.md
|
|
529
|
+
it("Eval 父行不复述 Attempt 失败摘要,失败题仍显示题级平均值", () => {
|
|
530
|
+
const reason = "roots-correct · expected x=2 · received x=3";
|
|
531
|
+
for (const face of [html, term]) {
|
|
532
|
+
expect(face.split(reason)).toHaveLength(2);
|
|
533
|
+
expect(face).toContain("32.0s avg");
|
|
534
|
+
expect(face).toContain("$0.12 avg");
|
|
535
|
+
}
|
|
536
|
+
});
|
|
527
537
|
});
|
|
528
538
|
|
|
529
539
|
describe("ExperimentList 双面", () => {
|
|
@@ -535,7 +545,7 @@ describe("ExperimentList 双面", () => {
|
|
|
535
545
|
expect(html).toContain(piece);
|
|
536
546
|
expect(term).toContain(piece);
|
|
537
547
|
}
|
|
538
|
-
// 官方两级聚合
|
|
548
|
+
// 官方两级聚合 endToEndPassRate.display 两面同一个数字,不各自重算
|
|
539
549
|
expect(html).toContain("50%");
|
|
540
550
|
expect(term).toContain("50%");
|
|
541
551
|
});
|
|
@@ -548,6 +558,47 @@ describe("ExperimentList 双面", () => {
|
|
|
548
558
|
expect(blocks[1]!.match(/algebra\/quadratic/g)).toHaveLength(1);
|
|
549
559
|
expect(blocks[1]).toMatch(/✗ failed\s+algebra\/quadratic[\s\S]*├─ @1a4a4a4[\s\S]*└─ @1b5b5b5/);
|
|
550
560
|
});
|
|
561
|
+
|
|
562
|
+
// bug: memory/eval-parent-repeats-attempt-failure.md
|
|
563
|
+
it("web 的 Eval 父行使用固定题级汇总,失败摘要只在 Attempt 子行出现", () => {
|
|
564
|
+
const reason = "roots-correct · expected x=2 · received x=3";
|
|
565
|
+
expect(html.split(reason)).toHaveLength(3); // 两个 failed Attempt,各显示一次
|
|
566
|
+
expect(html).toContain('<span class="nre-eval-rollup">32.0s avg · $0.12 avg</span>');
|
|
567
|
+
expect(term).toMatch(/✗ failed\s+algebra\/quadratic\s+32\.0s avg\s+\$0\.12 avg/);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it("Result 是两行收口的预览:received 携带整份多行源码时既不逐行铺表也不无限折行", () => {
|
|
571
|
+
const fatAttempt = {
|
|
572
|
+
...experimentListItems[0]!.evalRows[0]!.attempts[0]!,
|
|
573
|
+
assertions: [{
|
|
574
|
+
name: "includes(/['\"]use cache['\"];?/)",
|
|
575
|
+
groupPath: ["Catalog reads use use-cache directive and products cache tag"],
|
|
576
|
+
severity: "gate" as const,
|
|
577
|
+
score: 0,
|
|
578
|
+
outcome: "failed" as const,
|
|
579
|
+
expected: "matches /['\"]use cache['\"];?/",
|
|
580
|
+
received: `\n// next.config.ts\n${"import type { NextConfig } from 'next';\n\n".repeat(50)}`,
|
|
581
|
+
}],
|
|
582
|
+
};
|
|
583
|
+
const fatItems = [{
|
|
584
|
+
...experimentListItems[0]!,
|
|
585
|
+
evalRows: [{
|
|
586
|
+
...experimentListItems[0]!.evalRows[0]!,
|
|
587
|
+
attempts: [fatAttempt],
|
|
588
|
+
}],
|
|
589
|
+
}];
|
|
590
|
+
const detail = renderNodeToText(<ExperimentList items={fatItems} />, createTextContext({ width: 100 }))
|
|
591
|
+
.split("\n\n")
|
|
592
|
+
.at(-1)!;
|
|
593
|
+
const lines = detail.split("\n");
|
|
594
|
+
// 表头 1 + eval 父行 1 + attempt 子行 ≤2(Result 列 maxLines: 2),外加 experimentId 头行
|
|
595
|
+
expect(lines.length).toBeLessThanOrEqual(6);
|
|
596
|
+
expect(detail).toContain("…");
|
|
597
|
+
expect(lines.every((l) => l.trim().length > 0)).toBe(true); // 值里的空行不进表
|
|
598
|
+
// 优先级让位:语义标题先截(全称不再出现),事实字段(expected/received)最后截
|
|
599
|
+
expect(detail).not.toContain("Catalog reads use use-cache directive and products cache tag");
|
|
600
|
+
expect(detail).toContain("expected");
|
|
601
|
+
});
|
|
551
602
|
});
|
|
552
603
|
|
|
553
604
|
// ───────────────────────── 排版原语 ─────────────────────────
|
|
@@ -894,8 +945,8 @@ function fakeContext(): { selection: Selection; results: Results } {
|
|
|
894
945
|
}
|
|
895
946
|
|
|
896
947
|
/**
|
|
897
|
-
*
|
|
898
|
-
* 一个多实验 Selection 夹具:ExperimentList
|
|
948
|
+
* 五份快照、两个双配置目录组 + 一个根目录单例组,全部通过、无成本——
|
|
949
|
+
* 一个多实验 Selection 夹具:ExperimentList 出五项,MetricScatter 五个点都无成本
|
|
899
950
|
* (0 可画点,如实走空态)。`filter` 落实真实语义(不像 fakeContext 那样恒等返回自己)。
|
|
900
951
|
*/
|
|
901
952
|
function fakeMultiGroupContext(): { selection: Selection; results: Results } {
|
|
@@ -938,7 +989,9 @@ function fakeMultiGroupContext(): { selection: Selection; results: Results } {
|
|
|
938
989
|
|
|
939
990
|
const snapshots = [
|
|
940
991
|
mkSnapshot("compare/bub", "bub", "compare_bub/snap-1"),
|
|
992
|
+
mkSnapshot("compare/codex", "codex", "compare_codex/snap-1"),
|
|
941
993
|
mkSnapshot("other/codex", "codex", "other_codex/snap-1"),
|
|
994
|
+
mkSnapshot("other/bub", "bub", "other_bub/snap-1"),
|
|
942
995
|
mkSnapshot("solo", "bub", "solo/snap-1"),
|
|
943
996
|
];
|
|
944
997
|
|
|
@@ -991,7 +1044,7 @@ describe("defineReport + 渲染入口", () => {
|
|
|
991
1044
|
expect(out).toContain("1 experiment · 2 evals · 2 attempts");
|
|
992
1045
|
// ExperimentList:主行 + eval 级折叠计票 + 逐 Attempt locator 与失败诊断
|
|
993
1046
|
expect(out).toMatch(/compare\/bub\s+default\s+bub/);
|
|
994
|
-
expect(out).
|
|
1047
|
+
expect(out).toMatch(/1 passed[\s\S]*?\/ 1\s+failed/);
|
|
995
1048
|
expect(out).toContain("50%");
|
|
996
1049
|
expect(out).toMatch(/✗ failed\s+algebra\/y[\s\S]*└─ @[0-9a-z]+/);
|
|
997
1050
|
// 自己的口径:成绩单
|
|
@@ -1043,7 +1096,7 @@ describe("defineReport + 渲染入口", () => {
|
|
|
1043
1096
|
// selection-form 的官方组件(ExperimentList/EvalList/AttemptList 没有 selection-form,
|
|
1044
1097
|
// 这条契约不适用于它们)。
|
|
1045
1098
|
expect(() =>
|
|
1046
|
-
renderToStaticMarkup(<MetricScatter selection={selection} points="experiment" x={costUSD} y={
|
|
1099
|
+
renderToStaticMarkup(<MetricScatter selection={selection} points="experiment" x={costUSD} y={taskPassRate} />),
|
|
1047
1100
|
).toThrow(/received unresolved \(selection-form\) props/);
|
|
1048
1101
|
});
|
|
1049
1102
|
});
|
|
@@ -1056,9 +1109,9 @@ describe("defineReport + 渲染入口", () => {
|
|
|
1056
1109
|
// ExperimentComparison / defineReport 报告在同一文件里编译验证。
|
|
1057
1110
|
function metricScatterPropsTypeChecks(selection: Selection, data: ScatterData): void {
|
|
1058
1111
|
const ok1: MetricScatterProps = { data }; // 合法:data 形态
|
|
1059
|
-
const ok2: MetricScatterProps = { selection, points: "experiment", series: "agent", x: costUSD, y:
|
|
1112
|
+
const ok2: MetricScatterProps = { selection, points: "experiment", series: "agent", x: costUSD, y: taskPassRate }; // 合法:selection 形态
|
|
1060
1113
|
// @ts-expect-error 同时传 data 与 selection:非法
|
|
1061
|
-
const bad1: MetricScatterProps = { data, selection, points: "experiment", x: costUSD, y:
|
|
1114
|
+
const bad1: MetricScatterProps = { data, selection, points: "experiment", x: costUSD, y: taskPassRate };
|
|
1062
1115
|
// @ts-expect-error data 与 selection 都不传:非法
|
|
1063
1116
|
const bad2: MetricScatterProps = { pointHref: () => "/x" };
|
|
1064
1117
|
// @ts-expect-error selection 形态缺必填的 x / y:非法
|
|
@@ -1075,55 +1128,129 @@ void metricScatterPropsTypeChecks;
|
|
|
1075
1128
|
// ───────────────────────── ExperimentComparison(内置默认报告)─────────────────────────
|
|
1076
1129
|
|
|
1077
1130
|
describe("ExperimentComparison", () => {
|
|
1078
|
-
it("是普通 ReportDefinition
|
|
1131
|
+
it("是普通 ReportDefinition;单组 text 面 = 组摘要 + 成本×端到端成功率散点 + 实验列表", async () => {
|
|
1079
1132
|
expect(isReportDefinition(ExperimentComparison)).toBe(true);
|
|
1080
1133
|
const out = await renderReportToText(ExperimentComparison, fakeContext(), { width: 100 });
|
|
1081
1134
|
// 散点:fakeContext 无成本数据 → 0 可画点,显式说明缺哪两个指标(而不是画一张空图)
|
|
1082
1135
|
expect(out).toContain("No data to plot");
|
|
1083
1136
|
expect(out).not.toContain("better → upper right");
|
|
1084
1137
|
// 实验列表主行 + eval 级折叠计票 + 失败诊断(ExperimentList.data 在 build() 里直接 await)
|
|
1085
|
-
|
|
1086
|
-
expect(out).
|
|
1138
|
+
// 组已由 section 标题 "compare" 标注,列表行传 relativeTo=组键 去掉前缀,只显示 id 末段
|
|
1139
|
+
expect(out).toMatch(/^compare$/m);
|
|
1140
|
+
expect(out).toMatch(/^\s*bub\s+default\s+bub/m);
|
|
1141
|
+
expect(out).not.toContain("compare/bub");
|
|
1142
|
+
expect(out).toMatch(/1 passed[\s\S]*?\/ 1\s+failed/);
|
|
1087
1143
|
expect(out).toContain("50%");
|
|
1088
1144
|
expect(out).toMatch(/✗ failed\s+algebra\/y[\s\S]*└─ @[0-9a-z]+/);
|
|
1089
|
-
//
|
|
1145
|
+
// 单组直接进入详情,不出现多组查看命令
|
|
1090
1146
|
expect(out).not.toContain("Current verdicts");
|
|
1147
|
+
expect(out).not.toContain("niceeval show --experiment");
|
|
1091
1148
|
});
|
|
1092
1149
|
|
|
1093
|
-
it("web
|
|
1150
|
+
it("web 面:完整组索引 + 首组 panel + 实验列表 <details> 展开区,无 <script>", async () => {
|
|
1094
1151
|
const html = await renderReportToStaticHtml(ExperimentComparison, fakeContext());
|
|
1152
|
+
expect(html).toContain("nre-experiment-group-tabs");
|
|
1153
|
+
expect(html).toMatch(/<details[^>]*data-nre-experiment-group-panel="0"[^>]*\sopen=""/);
|
|
1095
1154
|
expect(html).toContain("nre-metric-scatter");
|
|
1096
1155
|
expect(html).toContain("nre-scatter-empty"); // 0 可画点的空态
|
|
1097
1156
|
expect(html).toContain('<details class="nre-experiment-entry">');
|
|
1157
|
+
// 行标签去掉组前缀只显示 id 末段;完整 id 仍留在 data-sort-value 作排序/身份键
|
|
1158
|
+
expect(html).toMatch(/nre-experiment-id[^>]*>\s*bub\s*<\/b>/);
|
|
1159
|
+
expect(html).toContain('data-sort-value="compare/bub"');
|
|
1098
1160
|
expect(html).toContain("nre-experiment-head");
|
|
1099
1161
|
expect(html).toContain('data-nre-experiment-filter=""');
|
|
1100
1162
|
expect(html).toContain("nre-experiment-evals");
|
|
1101
|
-
expect(html).not.toContain("nre-section");
|
|
1102
1163
|
expect(html).not.toContain("<script");
|
|
1103
1164
|
});
|
|
1104
1165
|
|
|
1105
1166
|
it("locale 变体:en / zh-CN 都渲染(chrome 分语言),散点空态两面同一事实", async () => {
|
|
1106
1167
|
const zhHtml = await renderReportToStaticHtml(ExperimentComparison, fakeContext(), { locale: "zh-CN" });
|
|
1107
|
-
expect(zhHtml).toContain("
|
|
1168
|
+
expect(zhHtml).toContain("端到端成功率");
|
|
1108
1169
|
const enHtml = await renderReportToStaticHtml(ExperimentComparison, fakeContext(), { locale: "en" });
|
|
1109
|
-
expect(enHtml).toContain("
|
|
1170
|
+
expect(enHtml).toContain("End-to-end pass rate");
|
|
1110
1171
|
const zhText = await renderReportToText(ExperimentComparison, fakeContext(), { locale: "zh-CN" });
|
|
1111
1172
|
expect(zhText).toContain("没有可绘制的数据"); // 散点空态 zh
|
|
1112
1173
|
const enText = await renderReportToText(ExperimentComparison, fakeContext(), { locale: "en" });
|
|
1113
1174
|
expect(enText).toContain("No data to plot");
|
|
1114
1175
|
});
|
|
1115
1176
|
|
|
1116
|
-
it("
|
|
1177
|
+
it("多组 fixture:text 只给组索引与命令;web 保留全部独立 panel", async () => {
|
|
1117
1178
|
const out = await renderReportToText(ExperimentComparison, fakeMultiGroupContext(), { width: 100 });
|
|
1118
|
-
|
|
1119
|
-
expect(out).
|
|
1120
|
-
expect(out).
|
|
1121
|
-
expect(out).
|
|
1122
|
-
|
|
1123
|
-
expect(out).toContain("No data to plot");
|
|
1124
|
-
// 没有组分 Section 标题(不再按目录前缀分组)
|
|
1125
|
-
expect(out).not.toMatch(/^compare$/m);
|
|
1179
|
+
expect(out).toContain("Experiment groups");
|
|
1180
|
+
expect(out).toContain("niceeval show --experiment compare");
|
|
1181
|
+
expect(out).toContain("niceeval show --experiment other");
|
|
1182
|
+
expect(out).toContain("niceeval show --experiment solo");
|
|
1183
|
+
expect(out).not.toMatch(/compare\/bub\s+default\s+bub/);
|
|
1184
|
+
expect(out).not.toContain("No data to plot");
|
|
1126
1185
|
const html = await renderReportToStaticHtml(ExperimentComparison, fakeMultiGroupContext());
|
|
1127
|
-
expect(html
|
|
1186
|
+
expect(html.match(/data-nre-experiment-group-panel=/g)).toHaveLength(3);
|
|
1187
|
+
expect(html.match(/<details[^>]*nre-experiment-group-panel[^>]*\sopen=""/g)).toHaveLength(1);
|
|
1188
|
+
expect(html).toContain("compare/bub");
|
|
1189
|
+
expect(html).toContain("other/codex");
|
|
1190
|
+
expect(html).toContain("solo");
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
it(".data():组内三个子块 = 对该组单独调用 GroupSummary / MetricScatter / ExperimentList", async () => {
|
|
1194
|
+
const { selection } = fakeContext();
|
|
1195
|
+
const data = await ExperimentComparison.data(selection);
|
|
1196
|
+
expect(data.groups).toHaveLength(1);
|
|
1197
|
+
expect(data.groups[0]!.key).toBe("compare");
|
|
1198
|
+
expect(data.groups[0]!.summary).toEqual(await GroupSummary.data(selection));
|
|
1199
|
+
expect(data.groups[0]!.scatter).toEqual(
|
|
1200
|
+
await MetricScatter.data(selection, { points: "experiment", series: "agent", x: costUSD, y: endToEndPassRate }),
|
|
1201
|
+
);
|
|
1202
|
+
expect(data.groups[0]!.experiments).toEqual(await ExperimentList.data(selection));
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
it(".data():计算前按完整父路径分区,两个目录组和根目录单例互不串数据或 refs", async () => {
|
|
1206
|
+
const { selection } = fakeMultiGroupContext();
|
|
1207
|
+
const data = await ExperimentComparison.data(selection);
|
|
1208
|
+
expect(data.groups.map((group) => group.key)).toEqual(["compare", "other", "solo"]);
|
|
1209
|
+
|
|
1210
|
+
for (const group of data.groups) {
|
|
1211
|
+
const groupSelection = selection.filter((snapshot) => {
|
|
1212
|
+
const slash = snapshot.experimentId.lastIndexOf("/");
|
|
1213
|
+
const key = slash === -1 ? snapshot.experimentId : snapshot.experimentId.slice(0, slash);
|
|
1214
|
+
return key === group.key;
|
|
1215
|
+
});
|
|
1216
|
+
expect(group.summary).toEqual(await GroupSummary.data(groupSelection));
|
|
1217
|
+
expect(group.scatter).toEqual(
|
|
1218
|
+
await MetricScatter.data(groupSelection, {
|
|
1219
|
+
points: "experiment",
|
|
1220
|
+
series: "agent",
|
|
1221
|
+
x: costUSD,
|
|
1222
|
+
y: endToEndPassRate,
|
|
1223
|
+
}),
|
|
1224
|
+
);
|
|
1225
|
+
expect(group.experiments).toEqual(await ExperimentList.data(groupSelection));
|
|
1226
|
+
expect(group.scatter.rows.every((row) => group.experiments.some((item) => item.experimentId === row.key))).toBe(
|
|
1227
|
+
true,
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
expect(data.groups.find((group) => group.key === "compare")!.experiments.map((item) => item.experimentId)).toEqual([
|
|
1232
|
+
"compare/bub",
|
|
1233
|
+
"compare/codex",
|
|
1234
|
+
]);
|
|
1235
|
+
expect(data.groups.find((group) => group.key === "other")!.experiments.map((item) => item.experimentId)).toEqual([
|
|
1236
|
+
"other/bub",
|
|
1237
|
+
"other/codex",
|
|
1238
|
+
]);
|
|
1239
|
+
expect(data.groups.find((group) => group.key === "solo")!.experiments.map((item) => item.experimentId)).toEqual([
|
|
1240
|
+
"solo",
|
|
1241
|
+
]);
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1244
|
+
it("组合件形态:<ExperimentComparison data={await .data(selection)}/> 与裸跑(build 面)渲染同一事实", async () => {
|
|
1245
|
+
const data = await ExperimentComparison.data(fakeContext().selection);
|
|
1246
|
+
const asComponent = defineReport(() => <ExperimentComparison data={data} />);
|
|
1247
|
+
// text 面逐字相等:同一份数据、同一套子组件口径
|
|
1248
|
+
const componentText = await renderReportToText(asComponent, fakeContext(), { width: 100 });
|
|
1249
|
+
const bareText = await renderReportToText(ExperimentComparison, fakeContext(), { width: 100 });
|
|
1250
|
+
expect(componentText).toBe(bareText);
|
|
1251
|
+
// web 面同一事实
|
|
1252
|
+
const componentHtml = await renderReportToStaticHtml(asComponent, fakeContext());
|
|
1253
|
+
const bareHtml = await renderReportToStaticHtml(ExperimentComparison, fakeContext());
|
|
1254
|
+
expect(componentHtml).toBe(bareHtml);
|
|
1128
1255
|
});
|
|
1129
1256
|
});
|
package/src/report/flag.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
// flag():把 experiment
|
|
1
|
+
// flag() / config():把 experiment 声明的变量当维度或轴(docs/feature/reports/library.md「维度与 flags」)。
|
|
2
2
|
// 变量来自配置,不来自命名 —— 报告不解析 experiment id 字符串抠变量。
|
|
3
|
+
// flag() 只读 `ExperimentDef.flags` 里显式声明的 KV;model / reasoningEffort / budget / runs
|
|
4
|
+
// 这类顶层运行配置不在 flags 里,用 config() 读快照的 ExperimentRunInfo 投影。
|
|
3
5
|
|
|
4
|
-
import type { FlagRef } from "./types.ts";
|
|
6
|
+
import type { ConfigRef, FlagRef } from "./types.ts";
|
|
5
7
|
|
|
8
|
+
/**
|
|
9
|
+
* 把 experiment 声明的一个 flag 当分组维度(series / rows / columns / points 槽)或数值轴
|
|
10
|
+
* (MetricLine 的 x 槽)。只读 `ExperimentDef.flags` 里显式声明的 KV;未声明的 experiment
|
|
11
|
+
* 分组归 `(unset)`,作轴不画点并报告缺失。
|
|
12
|
+
*/
|
|
6
13
|
export function flag(
|
|
7
14
|
name: string,
|
|
8
15
|
opts?: {
|
|
@@ -16,3 +23,24 @@ export function flag(
|
|
|
16
23
|
}
|
|
17
24
|
return { kind: "flag", name, label: opts?.label, unit: opts?.unit };
|
|
18
25
|
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 把一项顶层运行配置当分组维度或数值轴,与 {@link flag} 同一套用法。读快照的
|
|
29
|
+
* `ExperimentRunInfo` 投影(可用键是那张接口的字段全集),外加桥接到快照顶层权威字段的
|
|
30
|
+
* `model` / `agent` 两个键。未投影的值分组归 `(unset)`,作轴不画点并报告缺失。
|
|
31
|
+
*/
|
|
32
|
+
export function config(
|
|
33
|
+
name: string,
|
|
34
|
+
opts?: {
|
|
35
|
+
/** 组标签 / 轴标签;函数形态把投影值折成组名(如 `(v) => \`effort ${v}\``)。 */
|
|
36
|
+
label?: string | ((value: string | number | boolean) => string);
|
|
37
|
+
unit?: string;
|
|
38
|
+
},
|
|
39
|
+
): ConfigRef {
|
|
40
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"config: name must be a non-empty string (an ExperimentRunInfo field, or the bridged \"model\" / \"agent\" keys).",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return { kind: "config", name, label: opts?.label, unit: opts?.unit };
|
|
46
|
+
}
|
package/src/report/format.ts
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
// metric.display 可整体覆盖;这里只负责默认。
|
|
4
4
|
|
|
5
5
|
import type { AssertionResult, Verdict } from "../types.ts";
|
|
6
|
+
import { compactAssertionSummary, fitCompactAssertionSummary, primaryAssertionSummary } from "../scoring/display.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* experiment 行的显示名:给了父路径 `relativeTo` 且它确是前缀,就去掉 `relativeTo + "/"`,
|
|
10
|
+
* 只留 id 末段——用在已经以组为标题的上下文(如默认 `ExperimentComparison` 的每组面板)里,
|
|
11
|
+
* 避免每行重复文件夹名。组键就是 experiment id 的父目录,因此这里的末段与 `MetricScatter`
|
|
12
|
+
* 点标签取的末段同源。不给 `relativeTo`、或它不是前缀(如根目录单例组)时原样返回完整 id。
|
|
13
|
+
* 完整 id 仍是排序 / 着色 / 折叠的键,调用方不要拿这个显示名当身份用。
|
|
14
|
+
*/
|
|
15
|
+
export function experimentDisplayName(experimentId: string, relativeTo?: string): string {
|
|
16
|
+
if (relativeTo && experimentId.startsWith(`${relativeTo}/`)) {
|
|
17
|
+
return experimentId.slice(relativeTo.length + 1);
|
|
18
|
+
}
|
|
19
|
+
return experimentId;
|
|
20
|
+
}
|
|
6
21
|
|
|
7
22
|
/** 一位小数、去掉无意义的 ".0" 尾巴。 */
|
|
8
23
|
function trimmed(n: number): string {
|
|
@@ -94,16 +109,25 @@ export function verdictMark(verdict: Verdict): string {
|
|
|
94
109
|
}
|
|
95
110
|
|
|
96
111
|
/**
|
|
97
|
-
*
|
|
98
|
-
* (
|
|
99
|
-
*
|
|
100
|
-
* 没有 `skipReason` 字段(AttemptListItem 不携带),skipped 的 attempt 因此没有原因摘要,
|
|
101
|
-
* 这与它们本来就不该出现在「为什么失败」列表里的事实一致。EvalList / ExperimentList 的
|
|
102
|
-
* 逐 attempt 徽标行用它给每个 attempt 自己的原因(而不是复用整道题的代表原因)。
|
|
112
|
+
* Attempt 比较项的一层结果摘要;完整 assertions 只在 locator 详情里展开。
|
|
113
|
+
* maxChars(可选)是渲染面的宽度收口预算(如两行单元格 = 2 × 列宽):断言摘要按
|
|
114
|
+
* fitCompactAssertionSummary 的优先级让位,error 摘要折单行后尾截。
|
|
103
115
|
*/
|
|
104
|
-
export function attemptItemReason(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
116
|
+
export function attemptItemReason(
|
|
117
|
+
item: {
|
|
118
|
+
verdict: Verdict;
|
|
119
|
+
error?: { message: string };
|
|
120
|
+
assertions: AssertionResult[];
|
|
121
|
+
},
|
|
122
|
+
maxChars?: number,
|
|
123
|
+
): string | undefined {
|
|
124
|
+
if (item.error !== undefined) {
|
|
125
|
+
const message = item.error.message.replace(/\s+/g, " ").trim();
|
|
126
|
+
return maxChars !== undefined && message.length > maxChars
|
|
127
|
+
? `${message.slice(0, Math.max(0, maxChars - 1))}…`
|
|
128
|
+
: message;
|
|
129
|
+
}
|
|
130
|
+
const summary = primaryAssertionSummary(item.assertions, item.verdict);
|
|
131
|
+
if (summary === undefined) return undefined;
|
|
132
|
+
return maxChars === undefined ? compactAssertionSummary(summary) : fitCompactAssertionSummary(summary, maxChars);
|
|
109
133
|
}
|
package/src/report/index.ts
CHANGED
|
@@ -8,9 +8,19 @@
|
|
|
8
8
|
// ./web.ts,只有那一侧 import react-dom。写报告文件的项目要装 react(.tsx 编译产物
|
|
9
9
|
// import react/jsx-runtime)。
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
export {
|
|
13
|
-
|
|
11
|
+
// 指标与维度读取器(flag / config)
|
|
12
|
+
export {
|
|
13
|
+
defineMetric,
|
|
14
|
+
taskPassRate,
|
|
15
|
+
executionReliability,
|
|
16
|
+
endToEndPassRate,
|
|
17
|
+
examScore,
|
|
18
|
+
durationMs,
|
|
19
|
+
tokens,
|
|
20
|
+
costUSD,
|
|
21
|
+
turns,
|
|
22
|
+
} from "./metrics.ts";
|
|
23
|
+
export { flag, config } from "./flag.ts";
|
|
14
24
|
|
|
15
25
|
// 报告基座与双面组件基座
|
|
16
26
|
export { defineReport, isReportDefinition, renderReportToText } from "./report.ts";
|
|
@@ -44,8 +54,14 @@ export {
|
|
|
44
54
|
} from "./text/layout.ts";
|
|
45
55
|
export type { ColumnAlign } from "./text/layout.ts";
|
|
46
56
|
|
|
47
|
-
//
|
|
57
|
+
// 内置报告兼组合件(show / view 裸跑时报告槽的出厂填充;也可作组件整体引用,
|
|
58
|
+
// `<ExperimentComparison data={await ExperimentComparison.data(selection)} />`),无 renderer 特权
|
|
48
59
|
export { ExperimentComparison } from "./built-ins/index.ts";
|
|
60
|
+
export type {
|
|
61
|
+
ExperimentComparisonData,
|
|
62
|
+
ExperimentComparisonGroupData,
|
|
63
|
+
ExperimentComparisonProps,
|
|
64
|
+
} from "./built-ins/index.ts";
|
|
49
65
|
|
|
50
66
|
// locale:官方组件 chrome 文案的语言(en / zh-CN);指标 label 可按 locale 给字典
|
|
51
67
|
export { DEFAULT_REPORT_LOCALE, resolveMetricLabel } from "./locale.ts";
|
|
@@ -97,6 +113,8 @@ export type {
|
|
|
97
113
|
Aggregator,
|
|
98
114
|
AttemptListItem,
|
|
99
115
|
AttemptLocator,
|
|
116
|
+
AxisInput,
|
|
117
|
+
ConfigRef,
|
|
100
118
|
DeltaData,
|
|
101
119
|
Dimension,
|
|
102
120
|
DimensionInput,
|