niceeval 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/types.d.ts +72 -6
- 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 -0
- package/dist/report/built-ins/experiment-comparison.js +119 -0
- package/dist/report/built-ins/index.d.ts +2 -1
- package/dist/report/built-ins/index.js +2 -2
- package/dist/report/components.d.ts +10 -2
- package/dist/report/components.js +3 -3
- package/dist/report/compute.d.ts +11 -18
- package/dist/report/compute.js +68 -66
- package/dist/report/flag.d.ts +16 -1
- package/dist/report/flag.js +19 -1
- package/dist/report/format.d.ts +16 -14
- package/dist/report/format.js +28 -30
- package/dist/report/index.d.ts +5 -4
- package/dist/report/index.js +6 -5
- package/dist/report/locale.d.ts +23 -3
- package/dist/report/locale.js +47 -6
- package/dist/report/metrics.d.ts +13 -1
- package/dist/report/metrics.js +66 -15
- package/dist/report/primitives.d.ts +6 -0
- package/dist/report/react/AttemptList.d.ts +4 -4
- package/dist/report/react/AttemptList.js +8 -10
- 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 +4 -2
- package/dist/report/react/ExperimentList.js +57 -7
- package/dist/report/react/MetricScatter.js +6 -16
- 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 +30 -18
- package/dist/report/react/format.d.ts +1 -1
- package/dist/report/react/format.js +1 -1
- package/dist/report/react/index.d.ts +1 -1
- 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 +100 -61
- package/dist/report/text/table.js +36 -5
- package/dist/report/types.d.ts +40 -34
- 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 +24 -6
- package/docs-site/zh/concepts/assert.mdx +11 -10
- package/docs-site/zh/concepts/evals.mdx +7 -6
- package/docs-site/zh/concepts/experiment.mdx +1 -1
- package/docs-site/zh/concepts/overview.mdx +7 -7
- package/docs-site/zh/guides/agent-feedback-loop.mdx +35 -31
- 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/fixtures.mdx +3 -1
- package/docs-site/zh/guides/official-adapters.mdx +27 -3
- package/docs-site/zh/guides/publish-report.mdx +30 -16
- package/docs-site/zh/guides/report-components.mdx +49 -37
- package/docs-site/zh/guides/reporters.mdx +2 -2
- package/docs-site/zh/guides/results-data.mdx +42 -8
- package/docs-site/zh/guides/runner.mdx +17 -7
- package/docs-site/zh/guides/sandbox-agent.mdx +57 -7
- package/docs-site/zh/guides/sandbox-providers.mdx +258 -10
- package/docs-site/zh/guides/scoring-guide.mdx +4 -4
- package/docs-site/zh/guides/viewing-results.mdx +85 -41
- package/docs-site/zh/guides/write-experiment.mdx +5 -3
- package/docs-site/zh/guides/write-send.mdx +19 -2
- 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 +35 -9
- package/docs-site/zh/reference/define-agent.mdx +60 -5
- 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/events.mdx +2 -2
- package/docs-site/zh/reference/expect.mdx +36 -6
- 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 +73 -6
- 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 +65 -4
- package/src/i18n/zh-CN.ts +66 -4
- 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 +127 -173
- package/src/report/built-ins/experiment-comparison.tsx +179 -0
- package/src/report/built-ins/index.ts +7 -2
- package/src/report/components.tsx +11 -3
- package/src/report/compute.ts +80 -74
- package/src/report/dual-render.test.tsx +222 -91
- package/src/report/flag.ts +30 -2
- package/src/report/format.ts +36 -27
- package/src/report/index.ts +23 -6
- package/src/report/locale.ts +49 -6
- package/src/report/metrics.ts +68 -15
- package/src/report/primitives.tsx +6 -0
- package/src/report/react/AttemptList.tsx +9 -36
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentComparison.tsx +68 -0
- package/src/report/react/ExperimentList.tsx +173 -55
- package/src/report/react/MetricScatter.tsx +13 -25
- 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 +72 -1
- package/src/report/react/fixtures.ts +34 -21
- package/src/report/react/format.ts +1 -1
- package/src/report/react/index.tsx +0 -1
- package/src/report/react/render.test.tsx +30 -69
- package/src/report/react/styles.css +112 -14
- package/src/report/report.test.ts +308 -105
- package/src/report/report.ts +6 -2
- package/src/report/text/faces.ts +111 -67
- package/src/report/text/table.ts +42 -5
- package/src/report/types.ts +42 -34
- 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 +11 -9
- 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 +29 -16
- package/src/show/render.ts +626 -308
- package/src/show/show.test.ts +251 -36
- 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 +2 -1
- package/src/view/data.ts +17 -7
- package/src/view/index.ts +12 -1
- package/src/view/server.ts +2 -0
- package/src/view/shared/types.ts +1 -1
- package/src/view/styles.css +3 -0
- package/src/view/view-report.test.ts +11 -10
- package/dist/o11y/execution-tree.d.ts +0 -103
- package/dist/o11y/otlp/select.d.ts +0 -22
- package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +0 -1
- package/dist/report/built-ins/cost-pass-rate-comparison.js +0 -17
- package/dist/results/annotated-source.d.ts +0 -61
- package/dist/results/attempt-evidence.d.ts +0 -69
- package/dist/results/attempt-source.d.ts +0 -15
- package/src/report/built-ins/cost-pass-rate-comparison.tsx +0 -23
- 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,13 +36,14 @@ 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";
|
|
44
|
-
import {
|
|
46
|
+
import { ExperimentComparison } from "./built-ins/index.ts";
|
|
45
47
|
import { renderReportToStaticHtml } from "./web.ts";
|
|
46
48
|
import { createTextContext, renderNodeToText, validateReportTree } from "./tree.ts";
|
|
47
49
|
import {
|
|
@@ -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,17 +456,22 @@ 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"');
|
|
473
|
-
|
|
474
|
-
expect(
|
|
475
|
-
expect(html).toContain("[E,X,⏱]");
|
|
473
|
+
expect(term).not.toMatch(/\[[EXD⏱,]+\]/);
|
|
474
|
+
expect(html).not.toMatch(/\[[EXD⏱,]+\]/);
|
|
476
475
|
});
|
|
477
476
|
|
|
478
477
|
it("total > items.length 时两面都如实报剩余数量", () => {
|
|
@@ -482,7 +481,7 @@ describe("AttemptList 双面", () => {
|
|
|
482
481
|
expect(termTrunc).toContain("2 more not shown");
|
|
483
482
|
});
|
|
484
483
|
|
|
485
|
-
it("
|
|
484
|
+
it("比较列表不内联 assertion evidence,并保留 locator 作为完整证据入口", () => {
|
|
486
485
|
const longEvidence = "x".repeat(2_000);
|
|
487
486
|
const item = {
|
|
488
487
|
...attemptListItems[0],
|
|
@@ -490,17 +489,17 @@ describe("AttemptList 双面", () => {
|
|
|
490
489
|
};
|
|
491
490
|
const out = text(<AttemptList items={[item]} />);
|
|
492
491
|
expect(out).toContain(item.locator);
|
|
493
|
-
expect(out.replace(/\s+/g, " ")).toContain(`open ${item.locator} for full evidence`);
|
|
494
492
|
expect(out).not.toContain(longEvidence);
|
|
495
493
|
expect(out.length).toBeLessThan(1_000);
|
|
496
494
|
});
|
|
497
495
|
|
|
498
|
-
it("text
|
|
496
|
+
it("text 面每个 attempt 只显示一条有界结果摘要,不展开完整断言列表", () => {
|
|
499
497
|
const blocks = term.split("\n\n");
|
|
500
|
-
expect(blocks).toHaveLength(attemptListItems.length);
|
|
498
|
+
expect(blocks).toHaveLength(attemptListItems.length);
|
|
501
499
|
const lines = term.split("\n");
|
|
502
|
-
expect(lines.some((l) => /^ \S/.test(l))).toBe(true);
|
|
503
|
-
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");
|
|
504
503
|
});
|
|
505
504
|
});
|
|
506
505
|
|
|
@@ -525,6 +524,16 @@ describe("EvalList 双面", () => {
|
|
|
525
524
|
expect(blocks).toHaveLength(evalListItems.length);
|
|
526
525
|
expect(term.split("\n").some((l) => /^ \S/.test(l))).toBe(true); // 展开到 attempt 的行缩进两格
|
|
527
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
|
+
});
|
|
528
537
|
});
|
|
529
538
|
|
|
530
539
|
describe("ExperimentList 双面", () => {
|
|
@@ -536,15 +545,59 @@ describe("ExperimentList 双面", () => {
|
|
|
536
545
|
expect(html).toContain(piece);
|
|
537
546
|
expect(term).toContain(piece);
|
|
538
547
|
}
|
|
539
|
-
// 官方两级聚合
|
|
548
|
+
// 官方两级聚合 endToEndPassRate.display 两面同一个数字,不各自重算
|
|
540
549
|
expect(html).toContain("50%");
|
|
541
550
|
expect(term).toContain("50%");
|
|
542
551
|
});
|
|
543
552
|
|
|
544
|
-
it("text
|
|
553
|
+
it("text 面先给比较表,再按 experiment 给 Eval / Attempt 父子表", () => {
|
|
545
554
|
const blocks = term.split("\n\n");
|
|
546
|
-
expect(blocks).toHaveLength(experimentListItems.length);
|
|
547
|
-
expect(
|
|
555
|
+
expect(blocks).toHaveLength(experimentListItems.length + 1);
|
|
556
|
+
expect(blocks[0]).toContain("Model");
|
|
557
|
+
expect(blocks[0]).toContain("Avg");
|
|
558
|
+
expect(blocks[1]!.match(/algebra\/quadratic/g)).toHaveLength(1);
|
|
559
|
+
expect(blocks[1]).toMatch(/✗ failed\s+algebra\/quadratic[\s\S]*├─ @1a4a4a4[\s\S]*└─ @1b5b5b5/);
|
|
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");
|
|
548
601
|
});
|
|
549
602
|
});
|
|
550
603
|
|
|
@@ -892,8 +945,8 @@ function fakeContext(): { selection: Selection; results: Results } {
|
|
|
892
945
|
}
|
|
893
946
|
|
|
894
947
|
/**
|
|
895
|
-
*
|
|
896
|
-
* 一个多实验 Selection 夹具:ExperimentList
|
|
948
|
+
* 五份快照、两个双配置目录组 + 一个根目录单例组,全部通过、无成本——
|
|
949
|
+
* 一个多实验 Selection 夹具:ExperimentList 出五项,MetricScatter 五个点都无成本
|
|
897
950
|
* (0 可画点,如实走空态)。`filter` 落实真实语义(不像 fakeContext 那样恒等返回自己)。
|
|
898
951
|
*/
|
|
899
952
|
function fakeMultiGroupContext(): { selection: Selection; results: Results } {
|
|
@@ -936,7 +989,9 @@ function fakeMultiGroupContext(): { selection: Selection; results: Results } {
|
|
|
936
989
|
|
|
937
990
|
const snapshots = [
|
|
938
991
|
mkSnapshot("compare/bub", "bub", "compare_bub/snap-1"),
|
|
992
|
+
mkSnapshot("compare/codex", "codex", "compare_codex/snap-1"),
|
|
939
993
|
mkSnapshot("other/codex", "codex", "other_codex/snap-1"),
|
|
994
|
+
mkSnapshot("other/bub", "bub", "other_bub/snap-1"),
|
|
940
995
|
mkSnapshot("solo", "bub", "solo/snap-1"),
|
|
941
996
|
];
|
|
942
997
|
|
|
@@ -987,11 +1042,11 @@ describe("defineReport + 渲染入口", () => {
|
|
|
987
1042
|
const out = await renderReportToText(report, fakeContext(), { width: 100 });
|
|
988
1043
|
// RunOverview(data 形态,预计算)
|
|
989
1044
|
expect(out).toContain("1 experiment · 2 evals · 2 attempts");
|
|
990
|
-
// ExperimentList:主行 + eval 级折叠计票 +
|
|
991
|
-
expect(out).
|
|
992
|
-
expect(out).
|
|
1045
|
+
// ExperimentList:主行 + eval 级折叠计票 + 逐 Attempt locator 与失败诊断
|
|
1046
|
+
expect(out).toMatch(/compare\/bub\s+default\s+bub/);
|
|
1047
|
+
expect(out).toMatch(/1 passed[\s\S]*?\/ 1\s+failed/);
|
|
993
1048
|
expect(out).toContain("50%");
|
|
994
|
-
expect(out).toMatch(/✗ algebra\/y\s
|
|
1049
|
+
expect(out).toMatch(/✗ failed\s+algebra\/y[\s\S]*└─ @[0-9a-z]+/);
|
|
995
1050
|
// 自己的口径:成绩单
|
|
996
1051
|
expect(out).toContain("考试成绩单");
|
|
997
1052
|
expect(out).toContain("bub");
|
|
@@ -1041,7 +1096,7 @@ describe("defineReport + 渲染入口", () => {
|
|
|
1041
1096
|
// selection-form 的官方组件(ExperimentList/EvalList/AttemptList 没有 selection-form,
|
|
1042
1097
|
// 这条契约不适用于它们)。
|
|
1043
1098
|
expect(() =>
|
|
1044
|
-
renderToStaticMarkup(<MetricScatter selection={selection} points="experiment" x={costUSD} y={
|
|
1099
|
+
renderToStaticMarkup(<MetricScatter selection={selection} points="experiment" x={costUSD} y={taskPassRate} />),
|
|
1045
1100
|
).toThrow(/received unresolved \(selection-form\) props/);
|
|
1046
1101
|
});
|
|
1047
1102
|
});
|
|
@@ -1051,12 +1106,12 @@ describe("defineReport + 渲染入口", () => {
|
|
|
1051
1106
|
// ts-expect-error 注释对应一处编译错误。直接标注 Props 类型(而非 JSX)——JSX 的 union
|
|
1052
1107
|
// 属性检查对「缺必填字段」较宽松,直接的类型赋值才严格钉住互斥不变量:同时传 data 与
|
|
1053
1108
|
// selection、或两者都不传、或 selection 形态缺必填计算选项,全部编译失败。正向 JSX 用法由
|
|
1054
|
-
//
|
|
1109
|
+
// ExperimentComparison / defineReport 报告在同一文件里编译验证。
|
|
1055
1110
|
function metricScatterPropsTypeChecks(selection: Selection, data: ScatterData): void {
|
|
1056
1111
|
const ok1: MetricScatterProps = { data }; // 合法:data 形态
|
|
1057
|
-
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 形态
|
|
1058
1113
|
// @ts-expect-error 同时传 data 与 selection:非法
|
|
1059
|
-
const bad1: MetricScatterProps = { data, selection, points: "experiment", x: costUSD, y:
|
|
1114
|
+
const bad1: MetricScatterProps = { data, selection, points: "experiment", x: costUSD, y: taskPassRate };
|
|
1060
1115
|
// @ts-expect-error data 与 selection 都不传:非法
|
|
1061
1116
|
const bad2: MetricScatterProps = { pointHref: () => "/x" };
|
|
1062
1117
|
// @ts-expect-error selection 形态缺必填的 x / y:非法
|
|
@@ -1070,56 +1125,132 @@ function metricScatterPropsTypeChecks(selection: Selection, data: ScatterData):
|
|
|
1070
1125
|
|
|
1071
1126
|
void metricScatterPropsTypeChecks;
|
|
1072
1127
|
|
|
1073
|
-
// ─────────────────────────
|
|
1128
|
+
// ───────────────────────── ExperimentComparison(内置默认报告)─────────────────────────
|
|
1074
1129
|
|
|
1075
|
-
describe("
|
|
1076
|
-
it("是普通 ReportDefinition
|
|
1077
|
-
expect(isReportDefinition(
|
|
1078
|
-
const out = await renderReportToText(
|
|
1130
|
+
describe("ExperimentComparison", () => {
|
|
1131
|
+
it("是普通 ReportDefinition;单组 text 面 = 组摘要 + 成本×端到端成功率散点 + 实验列表", async () => {
|
|
1132
|
+
expect(isReportDefinition(ExperimentComparison)).toBe(true);
|
|
1133
|
+
const out = await renderReportToText(ExperimentComparison, fakeContext(), { width: 100 });
|
|
1079
1134
|
// 散点:fakeContext 无成本数据 → 0 可画点,显式说明缺哪两个指标(而不是画一张空图)
|
|
1080
1135
|
expect(out).toContain("No data to plot");
|
|
1081
1136
|
expect(out).not.toContain("better → upper right");
|
|
1082
1137
|
// 实验列表主行 + eval 级折叠计票 + 失败诊断(ExperimentList.data 在 build() 里直接 await)
|
|
1083
|
-
|
|
1084
|
-
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/);
|
|
1085
1143
|
expect(out).toContain("50%");
|
|
1086
|
-
expect(out).toMatch(/✗ algebra\/y\s
|
|
1087
|
-
//
|
|
1144
|
+
expect(out).toMatch(/✗ failed\s+algebra\/y[\s\S]*└─ @[0-9a-z]+/);
|
|
1145
|
+
// 单组直接进入详情,不出现多组查看命令
|
|
1088
1146
|
expect(out).not.toContain("Current verdicts");
|
|
1147
|
+
expect(out).not.toContain("niceeval show --experiment");
|
|
1089
1148
|
});
|
|
1090
1149
|
|
|
1091
|
-
it("web
|
|
1092
|
-
const html = await renderReportToStaticHtml(
|
|
1150
|
+
it("web 面:完整组索引 + 首组 panel + 实验列表 <details> 展开区,无 <script>", async () => {
|
|
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=""/);
|
|
1093
1154
|
expect(html).toContain("nre-metric-scatter");
|
|
1094
1155
|
expect(html).toContain("nre-scatter-empty"); // 0 可画点的空态
|
|
1095
|
-
expect(html).toContain('<
|
|
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"');
|
|
1160
|
+
expect(html).toContain("nre-experiment-head");
|
|
1161
|
+
expect(html).toContain('data-nre-experiment-filter=""');
|
|
1096
1162
|
expect(html).toContain("nre-experiment-evals");
|
|
1097
|
-
expect(html).not.toContain("nre-section");
|
|
1098
1163
|
expect(html).not.toContain("<script");
|
|
1099
1164
|
});
|
|
1100
1165
|
|
|
1101
1166
|
it("locale 变体:en / zh-CN 都渲染(chrome 分语言),散点空态两面同一事实", async () => {
|
|
1102
|
-
const zhHtml = await renderReportToStaticHtml(
|
|
1103
|
-
expect(zhHtml).toContain("
|
|
1104
|
-
const enHtml = await renderReportToStaticHtml(
|
|
1105
|
-
expect(enHtml).toContain("
|
|
1106
|
-
const zhText = await renderReportToText(
|
|
1167
|
+
const zhHtml = await renderReportToStaticHtml(ExperimentComparison, fakeContext(), { locale: "zh-CN" });
|
|
1168
|
+
expect(zhHtml).toContain("端到端成功率");
|
|
1169
|
+
const enHtml = await renderReportToStaticHtml(ExperimentComparison, fakeContext(), { locale: "en" });
|
|
1170
|
+
expect(enHtml).toContain("End-to-end pass rate");
|
|
1171
|
+
const zhText = await renderReportToText(ExperimentComparison, fakeContext(), { locale: "zh-CN" });
|
|
1107
1172
|
expect(zhText).toContain("没有可绘制的数据"); // 散点空态 zh
|
|
1108
|
-
const enText = await renderReportToText(
|
|
1173
|
+
const enText = await renderReportToText(ExperimentComparison, fakeContext(), { locale: "en" });
|
|
1109
1174
|
expect(enText).toContain("No data to plot");
|
|
1110
1175
|
});
|
|
1111
1176
|
|
|
1112
|
-
it("
|
|
1113
|
-
const out = await renderReportToText(
|
|
1114
|
-
|
|
1115
|
-
expect(out).toContain("
|
|
1116
|
-
expect(out).toContain("
|
|
1117
|
-
expect(out).toContain("
|
|
1118
|
-
|
|
1119
|
-
expect(out).toContain("No data to plot");
|
|
1120
|
-
|
|
1121
|
-
expect(
|
|
1122
|
-
|
|
1123
|
-
expect(html).
|
|
1177
|
+
it("多组 fixture:text 只给组索引与命令;web 保留全部独立 panel", async () => {
|
|
1178
|
+
const out = await renderReportToText(ExperimentComparison, fakeMultiGroupContext(), { width: 100 });
|
|
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");
|
|
1185
|
+
const html = await renderReportToStaticHtml(ExperimentComparison, fakeMultiGroupContext());
|
|
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);
|
|
1124
1255
|
});
|
|
1125
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
|
+
}
|