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
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// ExperimentComparison:裸 niceeval show / view 首页渲染的内置默认报告,同时是可整体引用的
|
|
3
|
+
// 官方组合件(docs/feature/reports/library.md「ExperimentComparison」)。
|
|
4
|
+
//
|
|
5
|
+
// 名字描述整份报告回答的问题,不绑定其中某一张图。它是组合件而非新的数据源——每组的
|
|
6
|
+
// GroupSummary、成本 × 端到端成功率散点、固定列 ExperimentList 都消费各自公开 .data() 的结果。
|
|
7
|
+
// 两种用法共享同一份口径:
|
|
8
|
+
// · 默认报告:宿主把它当普通 ReportDefinition(build 面),没有 renderer 私有通道;
|
|
9
|
+
// · 报告组件:`<ExperimentComparison data={await ExperimentComparison.data(selection)} />`,
|
|
10
|
+
// 在自定义报告或用户自己的 React 页面里整体引用。
|
|
11
|
+
import { Col, Section, Table } from "../primitives.js";
|
|
12
|
+
import { ExperimentList, GroupSummary, MetricScatter } from "../components.js";
|
|
13
|
+
import { costUSD, endToEndPassRate } from "../metrics.js";
|
|
14
|
+
import { defineReport } from "../report.js";
|
|
15
|
+
import { defineComponent } from "../tree.js";
|
|
16
|
+
import { localeText } from "../locale.js";
|
|
17
|
+
import { resolveInput } from "../aggregate.js";
|
|
18
|
+
import { ExperimentComparisonView } from "../react/ExperimentComparison.js";
|
|
19
|
+
import { wrapDisplay } from "../text/layout.js";
|
|
20
|
+
import { formatUSD } from "../format.js";
|
|
21
|
+
// 每组散点的唯一口径:默认 definition 与公开 `.data()` 共用,不各写一份。
|
|
22
|
+
const SCATTER_OPTIONS = {
|
|
23
|
+
points: "experiment",
|
|
24
|
+
series: "agent",
|
|
25
|
+
x: costUSD,
|
|
26
|
+
y: endToEndPassRate,
|
|
27
|
+
};
|
|
28
|
+
/** 完整父路径是组键;没有父路径的 experiment 不能互相比,自己形成单例组。 */
|
|
29
|
+
export function experimentComparisonGroupKey(experimentId) {
|
|
30
|
+
const slash = experimentId.lastIndexOf("/");
|
|
31
|
+
return slash === -1 ? experimentId : experimentId.slice(0, slash);
|
|
32
|
+
}
|
|
33
|
+
/** 组合件的数据计算:先分区,再让三个官方子块在各组内并行计算。 */
|
|
34
|
+
async function experimentComparisonData(input) {
|
|
35
|
+
const { snapshots } = resolveInput(input);
|
|
36
|
+
const snapshotsByGroup = new Map();
|
|
37
|
+
for (const snapshot of snapshots) {
|
|
38
|
+
const key = experimentComparisonGroupKey(snapshot.experimentId);
|
|
39
|
+
const group = snapshotsByGroup.get(key);
|
|
40
|
+
if (group)
|
|
41
|
+
group.push(snapshot);
|
|
42
|
+
else
|
|
43
|
+
snapshotsByGroup.set(key, [snapshot]);
|
|
44
|
+
}
|
|
45
|
+
const groups = await Promise.all([...snapshotsByGroup.entries()]
|
|
46
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
47
|
+
.map(async ([key, groupSnapshots]) => {
|
|
48
|
+
const [summary, scatter, experiments] = await Promise.all([
|
|
49
|
+
GroupSummary.data(groupSnapshots),
|
|
50
|
+
MetricScatter.data(groupSnapshots, SCATTER_OPTIONS),
|
|
51
|
+
ExperimentList.data(groupSnapshots),
|
|
52
|
+
]);
|
|
53
|
+
return { key, summary, scatter, experiments };
|
|
54
|
+
}));
|
|
55
|
+
return { groups };
|
|
56
|
+
}
|
|
57
|
+
/** 单组详情:散点与实验列表全部来自同一个预分区结果。 */
|
|
58
|
+
function groupComposition(group, locale, className) {
|
|
59
|
+
return (_jsxs(Col, { className: className, children: [_jsx(MetricScatter, { data: group.scatter, locale: locale }), _jsx(ExperimentList, { items: group.experiments, filter: true, locale: locale, relativeTo: group.key })] }));
|
|
60
|
+
}
|
|
61
|
+
function shellQuote(value) {
|
|
62
|
+
return /^[A-Za-z0-9._/-]+$/.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
63
|
+
}
|
|
64
|
+
function verdictsText(group, locale) {
|
|
65
|
+
const parts = [];
|
|
66
|
+
for (const verdict of ["passed", "failed", "errored", "skipped"]) {
|
|
67
|
+
const count = group.summary.verdicts[verdict];
|
|
68
|
+
if (count > 0)
|
|
69
|
+
parts.push(`${count} ${localeText(locale, `verdict.${verdict}`)}`);
|
|
70
|
+
}
|
|
71
|
+
return parts.length > 0 ? parts.join(" / ") : "—";
|
|
72
|
+
}
|
|
73
|
+
function textFace(data, className, ctx) {
|
|
74
|
+
if (data.groups.length === 0)
|
|
75
|
+
return localeText(ctx.locale, "experimentComparison.empty");
|
|
76
|
+
if (data.groups.length === 1) {
|
|
77
|
+
const group = data.groups[0];
|
|
78
|
+
return ctx.render(_jsx(Section, { title: group.key, className: className, children: groupComposition(group) }));
|
|
79
|
+
}
|
|
80
|
+
const table = ctx.render(_jsx(Table, { columns: [
|
|
81
|
+
{ key: "group", header: localeText(ctx.locale, "experimentComparison.group") },
|
|
82
|
+
{ key: "experiments", header: localeText(ctx.locale, "groupSummary.experiments"), align: "right" },
|
|
83
|
+
{ key: "evals", header: localeText(ctx.locale, "overview.evals"), align: "right" },
|
|
84
|
+
{ key: "passRate", header: localeText(ctx.locale, "overview.passRate"), align: "right" },
|
|
85
|
+
{ key: "results", header: localeText(ctx.locale, "experimentComparison.results") },
|
|
86
|
+
{ key: "cost", header: localeText(ctx.locale, "overview.totalCost"), align: "right" },
|
|
87
|
+
{ key: "lastRun", header: localeText(ctx.locale, "experimentComparison.lastRun") },
|
|
88
|
+
], rows: data.groups.map((group) => ({
|
|
89
|
+
key: group.key,
|
|
90
|
+
cells: {
|
|
91
|
+
group: group.key,
|
|
92
|
+
experiments: String(group.summary.experiments),
|
|
93
|
+
evals: String(group.summary.evals),
|
|
94
|
+
passRate: group.summary.passRate.display,
|
|
95
|
+
results: verdictsText(group, ctx.locale),
|
|
96
|
+
cost: group.summary.totalCostUSD === null ? null : formatUSD(group.summary.totalCostUSD),
|
|
97
|
+
lastRun: group.summary.lastRunAt ?? null,
|
|
98
|
+
},
|
|
99
|
+
})) }));
|
|
100
|
+
const commands = data.groups
|
|
101
|
+
.map((group) => {
|
|
102
|
+
const command = `niceeval show --experiment ${shellQuote(group.key)}`;
|
|
103
|
+
return wrapDisplay(localeText(ctx.locale, "experimentComparison.command", { command }), ctx.width).join("\n");
|
|
104
|
+
})
|
|
105
|
+
.join("\n");
|
|
106
|
+
return `${localeText(ctx.locale, "experimentComparison.groups")}\n\n${table}\n\n${commands}`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 内置默认报告兼官方组合件:作为组件时收 `data`(`.data()` 的产物),两个渲染面直接摆
|
|
110
|
+
* 数据形态的子组件;作为默认报告时经 build 面走与用户 `--report` 完全相同的管线。
|
|
111
|
+
*/
|
|
112
|
+
export const ExperimentComparison = Object.assign(defineComponent({
|
|
113
|
+
web: (props, ctx) => (_jsx(ExperimentComparisonView, { data: props.data, locale: props.locale ?? ctx.locale, className: props.className })),
|
|
114
|
+
text: ({ data, className }, ctx) => textFace(data, className, ctx),
|
|
115
|
+
}), defineReport(async ({ selection }) => {
|
|
116
|
+
const data = await experimentComparisonData(selection);
|
|
117
|
+
return _jsx(ExperimentComparison, { data: data });
|
|
118
|
+
}), { data: experimentComparisonData });
|
|
119
|
+
ExperimentComparison.displayName = "ExperimentComparison";
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { ExperimentComparison } from "./experiment-comparison.tsx";
|
|
2
|
+
export type { ExperimentComparisonData, ExperimentComparisonGroupData, ExperimentComparisonProps, } from "./experiment-comparison.tsx";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
//
|
|
2
|
-
export {
|
|
1
|
+
// 内置报告目录:一个文件一个按展示内容命名的组合件(组件兼默认报告),显式导出值,不建字符串 registry。
|
|
2
|
+
export { ExperimentComparison } from "./experiment-comparison.js";
|
|
@@ -24,6 +24,14 @@ export interface GroupSummaryProps {
|
|
|
24
24
|
*/
|
|
25
25
|
export interface ExperimentListProps {
|
|
26
26
|
items: ExperimentListItem[];
|
|
27
|
+
/** web 面在比较表前显示实验过滤框;text 面忽略。 */
|
|
28
|
+
filter?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* 可选父路径:两面的行标签去掉与它相同的前缀,只显示 experiment id 末段,避免在已经以组为
|
|
31
|
+
* 标题的上下文里重复文件夹名(默认 `ExperimentComparison` 给每组传组键)。完整 id 仍是排序 /
|
|
32
|
+
* 着色 / 过滤 / 折叠的键;不传或它不是前缀(如根目录单例组)时显示完整 id。
|
|
33
|
+
*/
|
|
34
|
+
relativeTo?: string;
|
|
27
35
|
/** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
|
|
28
36
|
locale?: ReportLocale;
|
|
29
37
|
className?: string;
|
|
@@ -140,8 +148,8 @@ export declare const EvalList: ReportComponent<EvalListProps> & {
|
|
|
140
148
|
data: typeof evalListData;
|
|
141
149
|
};
|
|
142
150
|
/**
|
|
143
|
-
* Attempt 列表:实体列表的叶子层,每项一个 Attempt
|
|
144
|
-
* (assertions
|
|
151
|
+
* Attempt 列表:实体列表的叶子层,每项一个 Attempt,固定展示判定、一条主失败断言摘要或
|
|
152
|
+
* error 一层摘要与证据引用(locator)。完整 assertions / evidence 经 locator 下钻。它不预设只看失败;
|
|
145
153
|
* 报告作者过滤 `AttemptListItem[]`、用 `.slice()` 限量,`total` 让渲染面如实报告剩余数量。
|
|
146
154
|
*/
|
|
147
155
|
export declare const AttemptList: ReportComponent<AttemptListProps> & {
|
|
@@ -57,7 +57,7 @@ GroupSummary.displayName = "GroupSummary";
|
|
|
57
57
|
*/
|
|
58
58
|
export const ExperimentList = Object.assign(defineComponent({
|
|
59
59
|
web: (props, ctx) => _jsx(ExperimentListWeb, { ...props, locale: props.locale ?? ctx.locale, attemptHref: ctx.attemptHref }),
|
|
60
|
-
text: ({ items }, ctx) => experimentListText(items, ctx),
|
|
60
|
+
text: ({ items, relativeTo }, ctx) => experimentListText(items, ctx, relativeTo),
|
|
61
61
|
}), { data: experimentListData });
|
|
62
62
|
ExperimentList.displayName = "ExperimentList";
|
|
63
63
|
/**
|
|
@@ -70,8 +70,8 @@ export const EvalList = Object.assign(defineComponent({
|
|
|
70
70
|
}), { data: evalListData });
|
|
71
71
|
EvalList.displayName = "EvalList";
|
|
72
72
|
/**
|
|
73
|
-
* Attempt 列表:实体列表的叶子层,每项一个 Attempt
|
|
74
|
-
* (assertions
|
|
73
|
+
* Attempt 列表:实体列表的叶子层,每项一个 Attempt,固定展示判定、一条主失败断言摘要或
|
|
74
|
+
* error 一层摘要与证据引用(locator)。完整 assertions / evidence 经 locator 下钻。它不预设只看失败;
|
|
75
75
|
* 报告作者过滤 `AttemptListItem[]`、用 `.slice()` 限量,`total` 让渲染面如实报告剩余数量。
|
|
76
76
|
*/
|
|
77
77
|
export const AttemptList = Object.assign(defineComponent({
|
package/dist/report/compute.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AttemptListItem, DeltaData, DimensionInput, EvalListItem, ExperimentListItem, GroupSummaryData, LineData, MatrixData, Metric, OverviewData,
|
|
2
|
-
import type {
|
|
1
|
+
import type { AttemptListItem, AxisInput, DeltaData, DimensionInput, EvalListItem, ExperimentListItem, GroupSummaryData, LineData, MatrixData, Metric, OverviewData, ScatterData, ScoreboardData, TableData } from "./types.ts";
|
|
2
|
+
import type { EvalResult } from "../types.ts";
|
|
3
3
|
import { type SnapshotsInput } from "./aggregate.ts";
|
|
4
4
|
export interface TableDataOptions<M extends readonly Metric[]> {
|
|
5
5
|
/** 行维度(内置 / 自定义 / flag())。 */
|
|
@@ -11,22 +11,15 @@ export interface TableDataOptions<M extends readonly Metric[]> {
|
|
|
11
11
|
/** eval id 前缀过滤,同 CLI 位置参数语义。 */
|
|
12
12
|
evals?: string | string[];
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
15
|
-
* 一次 attempt 未通过的 gate 断言,原始声明顺序不变;soft 断言不参与判定,不算「失败原因」,
|
|
16
|
-
* 只影响得分,永不出现在这份列表里。`EvalList` / `ExperimentList` 的失败诊断与 `AttemptList`
|
|
17
|
-
* 的断言列表共用这同一份材料,保证同一个 attempt 在各处给出同一个原因。
|
|
18
|
-
*/
|
|
19
|
-
export declare function failingGateAssertions(result: EvalResult): AssertionResult[];
|
|
20
|
-
/**
|
|
21
|
-
* 一次 attempt 的失败原因文案,按优先级取第一个在场的:
|
|
22
|
-
* `error` → `skipReason` → 未通过的 gate 断言(原始声明顺序,`name`,detail 在场则
|
|
23
|
-
* `"name: detail"`,多条用「, 」连接)→ 都缺席则无原因(如某道题恰好没有失败信号)。
|
|
24
|
-
* soft 断言永不进入这份原因文案,soft 得分是独立概念,不与 reason 混用同一个字段。
|
|
25
|
-
*/
|
|
14
|
+
/** 一次 attempt 的有界结果摘要:error → skipReason → 一条主失败断言 + 其余失败计数。 */
|
|
26
15
|
export declare function reasonFor(result: EvalResult): string | undefined;
|
|
27
16
|
export declare function tableData<const M extends readonly Metric[]>(input: SnapshotsInput, opts: TableDataOptions<M>): Promise<TableData<M[number]["name"]>>;
|
|
28
17
|
export interface AttemptListDataOptions {
|
|
29
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* 展示层遮蔽:error 的 message/cause/stack、diagnostic 的 message/data、断言 detail 与
|
|
20
|
+
* evidence 经这个钩子;experimentId、evalId、locator、error/diagnostic code 与 lifecycle
|
|
21
|
+
* operation 等身份和分类字段不经它。只作用于这次计算产出的组件数据,不改盘上 artifact。
|
|
22
|
+
*/
|
|
30
23
|
redact?: (text: string) => string;
|
|
31
24
|
}
|
|
32
25
|
/** `AttemptList.data(selection)`:每个 Attempt 一项,顺序取自 Selection 展平顺序(不重排)。 */
|
|
@@ -74,10 +67,10 @@ export interface ScatterDataOptions {
|
|
|
74
67
|
}
|
|
75
68
|
export declare function scatterData(input: SnapshotsInput, opts: ScatterDataOptions): Promise<ScatterData>;
|
|
76
69
|
export interface LineDataOptions {
|
|
77
|
-
/** x 轴:experiment 声明的 flag(
|
|
78
|
-
x:
|
|
70
|
+
/** x 轴:experiment 声明的 flag 或顶层运行配置 config()(要求数值),不解析 experiment 命名。 */
|
|
71
|
+
x: AxisInput;
|
|
79
72
|
y: Metric;
|
|
80
|
-
/** 可选:每个系列一条线(flag 或普通维度);省略 = 单系列。 */
|
|
73
|
+
/** 可选:每个系列一条线(flag / config 或普通维度);省略 = 单系列。 */
|
|
81
74
|
series?: DimensionInput;
|
|
82
75
|
}
|
|
83
76
|
/** 每个点 = 一个 experiment 的聚合;同系列的点按 x 排序连线(排序在组件面,数据保持分组序)。 */
|
package/dist/report/compute.js
CHANGED
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
// - null ≠ 0:缺数据不编数,覆盖率经 samples/total 如实暴露;
|
|
11
11
|
// - core 中立:只认 Metric / Dimension 接口,不出现具体 agent 名的分支。
|
|
12
12
|
import { evalLevelStats, foldEvalVerdict } from "../shared/verdict.js";
|
|
13
|
-
import { applyAggregator, assertUniqueMetricNames, collectItems, computeCell, dimensionKey, dimensionName, displayValue, evalGroupOf, evalIdOf, evalPrefixPredicate, evaluateMetric, experimentIdOf, filterItems, groupItems,
|
|
14
|
-
import { attemptCostUSD, costUSD, durationMs,
|
|
13
|
+
import { applyAggregator, assertUniqueMetricNames, axisValue, collectItems, computeCell, dimensionKey, dimensionName, displayValue, evalGroupOf, evalIdOf, evalPrefixPredicate, evaluateMetric, experimentIdOf, filterItems, groupItems, locatorOf, resolveInput, snapshotKeyOf, toColumn, } from "./aggregate.js";
|
|
14
|
+
import { attemptCostUSD, costUSD, durationMs, endToEndPassRate, examScore, tokens } from "./metrics.js";
|
|
15
15
|
import { formatMetricValue, formatPlainNumber } from "./format.js";
|
|
16
|
+
import { compactAssertionSummary, primaryAssertionSummary } from "../scoring/display.js";
|
|
16
17
|
// 一组 Item 的 eval 全身份键:experimentId + eval id。单 experiment 场景(如 experimentRowMeta,
|
|
17
18
|
// 一组本就只有一个 experimentId)下退化为只按 eval id 折叠,与旧行为一致;多 experiment 场景
|
|
18
19
|
// (GroupSummary 的组可能跨多个 experiment)下避免两个 experiment 里同名 eval 被误合并成一道题。
|
|
@@ -102,29 +103,14 @@ function experimentRowMeta(group) {
|
|
|
102
103
|
...(stats.lastRunAt !== undefined ? { lastRunAt: stats.lastRunAt } : {}),
|
|
103
104
|
};
|
|
104
105
|
}
|
|
105
|
-
/**
|
|
106
|
-
* 一次 attempt 未通过的 gate 断言,原始声明顺序不变;soft 断言不参与判定,不算「失败原因」,
|
|
107
|
-
* 只影响得分,永不出现在这份列表里。`EvalList` / `ExperimentList` 的失败诊断与 `AttemptList`
|
|
108
|
-
* 的断言列表共用这同一份材料,保证同一个 attempt 在各处给出同一个原因。
|
|
109
|
-
*/
|
|
110
|
-
export function failingGateAssertions(result) {
|
|
111
|
-
return result.assertions.filter((a) => !a.passed && a.severity === "gate");
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* 一次 attempt 的失败原因文案,按优先级取第一个在场的:
|
|
115
|
-
* `error` → `skipReason` → 未通过的 gate 断言(原始声明顺序,`name`,detail 在场则
|
|
116
|
-
* `"name: detail"`,多条用「, 」连接)→ 都缺席则无原因(如某道题恰好没有失败信号)。
|
|
117
|
-
* soft 断言永不进入这份原因文案,soft 得分是独立概念,不与 reason 混用同一个字段。
|
|
118
|
-
*/
|
|
106
|
+
/** 一次 attempt 的有界结果摘要:error → skipReason → 一条主失败断言 + 其余失败计数。 */
|
|
119
107
|
export function reasonFor(result) {
|
|
120
108
|
if (result.error !== undefined)
|
|
121
|
-
return result.error;
|
|
109
|
+
return result.error.message;
|
|
122
110
|
if (result.skipReason !== undefined)
|
|
123
111
|
return result.skipReason;
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
return undefined;
|
|
127
|
-
return gates.map((a) => (a.detail ? `${a.name}: ${a.detail}` : a.name)).join(", ");
|
|
112
|
+
const summary = primaryAssertionSummary(result.assertions, result.verdict);
|
|
113
|
+
return summary === undefined ? undefined : compactAssertionSummary(summary);
|
|
128
114
|
}
|
|
129
115
|
export async function tableData(input, opts) {
|
|
130
116
|
assertUniqueMetricNames(opts.columns, "MetricTable.data columns");
|
|
@@ -174,45 +160,52 @@ export async function tableData(input, opts) {
|
|
|
174
160
|
// 没有列配置;过滤是报告作者对返回数组调用 .filter()/.slice() 的事,不进这里
|
|
175
161
|
// (docs/feature/reports/library.md「实体列表」)。AttemptListItem 是三者共用的叶子形状——
|
|
176
162
|
// ExperimentList / EvalList 的下钻数组直接复用它,不各自精简一份。
|
|
177
|
-
/**
|
|
178
|
-
* 一个 attempt 的证据能力标记,只读已有的瘦身摘要位(`hasSources` / `hasEvents` / `hasTrace`)
|
|
179
|
-
* 加一次 `diff()` 懒加载——不调用完整的 `loadAttemptEvidence`(那还会额外装配 Eval 源码标注
|
|
180
|
-
* 与 ExecutionTree,这里只要四个布尔位)。三位定义与 `AttemptEvidence.capabilities` 完全一致:
|
|
181
|
-
*
|
|
182
|
-
* - `eval`:`hasSources` 为真。真正的门槛是 `AnnotatedEvalSource` 非空且有内容(源码行或断言
|
|
183
|
-
* 非空),但 sources 存在时 `buildAnnotatedEvalSource` 恒产出至少一行源码,断言又永远来自
|
|
184
|
-
* `result.assertions`(不需要 IO)——`hasSources` 是这个条件的精确瘦身版,不是近似。
|
|
185
|
-
* - `execution`:`hasEvents` 为真,与 `loadAttemptEvidence` 的 `events !== null && events.length > 0`
|
|
186
|
-
* 同一个判定,写入面按同一条规则算的 `hasEvents` 就是它的瘦身镜像。
|
|
187
|
-
* - `timing`:`hasEvents && hasTrace` 为真——`execution` 成立且这次运行接入过 OTel
|
|
188
|
-
* (`ExecutionTree.timingAvailable` = `spans.length > 0`,`hasTrace` 正是这个判定的瘦身镜像)。
|
|
189
|
-
* - `diff`:唯一需要 IO 的一位,`attempt.diff()` 非空且至少改动/删除了一个文件——落盘没有
|
|
190
|
-
* 与 `hasEvents` 对应的瘦身摘要位,只能懒加载判定;成本是每个 item 一次 `diff()` 读取,
|
|
191
|
-
* 不是完整证据装配的四路 Promise.all。
|
|
192
|
-
*/
|
|
193
|
-
async function attemptCapabilities(attempt) {
|
|
194
|
-
const result = attempt.result;
|
|
195
|
-
const diff = await attempt.diff();
|
|
196
|
-
const diffCapable = diff !== null && (Object.keys(diff.generatedFiles).length > 0 || diff.deletedFiles.length > 0);
|
|
197
|
-
return {
|
|
198
|
-
eval: result.hasSources === true,
|
|
199
|
-
execution: result.hasEvents === true,
|
|
200
|
-
timing: result.hasEvents === true && result.hasTrace === true,
|
|
201
|
-
diff: diffCapable,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
/** 自由文本(error / 断言 detail / evidence)的发布消毒钩子;身份字段(name/severity/loc)不经它。 */
|
|
163
|
+
/** 自由文本(断言 detail / evidence)的展示层遮蔽钩子;身份字段(name/severity/loc)不经它。 */
|
|
205
164
|
function redactAssertions(assertions, redact) {
|
|
206
165
|
if (assertions.length === 0)
|
|
207
166
|
return assertions;
|
|
208
167
|
return assertions.map((a) => ({
|
|
209
168
|
...a,
|
|
210
169
|
...(a.detail !== undefined ? { detail: redact(a.detail) } : {}),
|
|
211
|
-
...(a.evidence !== undefined ? { evidence: redact(a.evidence) } : {}),
|
|
170
|
+
...(a.outcome !== "unavailable" && a.evidence !== undefined ? { evidence: redact(a.evidence) } : {}),
|
|
171
|
+
...(a.outcome !== "unavailable" && a.expected !== undefined ? { expected: redact(a.expected) } : {}),
|
|
172
|
+
...(a.outcome !== "unavailable" && a.received !== undefined ? { received: redact(a.received) } : {}),
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 结构化 error 的遮蔽:message / stack / cause.message 是自由文本,经钩子;
|
|
177
|
+
* code / operation / cause.name / cause.code 是分类与身份字段,原样保留
|
|
178
|
+
* (与 copySnapshots({ redact }) 的改写范围约定一致)。
|
|
179
|
+
*/
|
|
180
|
+
function redactError(error, redact) {
|
|
181
|
+
return {
|
|
182
|
+
...error,
|
|
183
|
+
message: redact(error.message),
|
|
184
|
+
...(error.stack !== undefined ? { stack: redact(error.stack) } : {}),
|
|
185
|
+
...(error.cause !== undefined ? { cause: { ...error.cause, message: redact(error.cause.message) } } : {}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/** JsonValue 树里逐个字符串值经钩子;结构与非字符串标量原样。 */
|
|
189
|
+
function redactJsonValue(value, redact) {
|
|
190
|
+
if (typeof value === "string")
|
|
191
|
+
return redact(value);
|
|
192
|
+
if (Array.isArray(value))
|
|
193
|
+
return value.map((v) => redactJsonValue(v, redact));
|
|
194
|
+
if (typeof value === "object" && value !== null) {
|
|
195
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, redactJsonValue(v, redact)]));
|
|
196
|
+
}
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
/** diagnostics 的遮蔽:message 与 data(自由内容)经钩子;code / operation / level / count 原样。 */
|
|
200
|
+
function redactDiagnostics(diagnostics, redact) {
|
|
201
|
+
return diagnostics.map((d) => ({
|
|
202
|
+
...d,
|
|
203
|
+
message: redact(d.message),
|
|
204
|
+
...(d.data !== undefined ? { data: redactJsonValue(d.data, redact) } : {}),
|
|
212
205
|
}));
|
|
213
206
|
}
|
|
214
207
|
/** AttemptList / ExperimentList / EvalList 共用的叶子构造:一个 Item → 一个 AttemptListItem。 */
|
|
215
|
-
|
|
208
|
+
function attemptListItemOf(item, redact) {
|
|
216
209
|
const result = item.attempt.result;
|
|
217
210
|
const cost = attemptCostUSD(result);
|
|
218
211
|
return {
|
|
@@ -221,12 +214,14 @@ async function attemptListItemOf(item, redact) {
|
|
|
221
214
|
attempt: result.attempt,
|
|
222
215
|
agent: result.agent,
|
|
223
216
|
verdict: result.verdict,
|
|
224
|
-
...(result.error !== undefined ? { error:
|
|
217
|
+
...(result.error !== undefined ? { error: redactError(result.error, redact) } : {}),
|
|
218
|
+
...(result.diagnostics !== undefined && result.diagnostics.length > 0
|
|
219
|
+
? { diagnostics: redactDiagnostics(result.diagnostics, redact) }
|
|
220
|
+
: {}),
|
|
225
221
|
assertions: redactAssertions(result.assertions, redact),
|
|
226
222
|
durationMs: result.durationMs,
|
|
227
223
|
...(cost !== null ? { costUSD: cost } : {}),
|
|
228
224
|
locator: locatorOf(item),
|
|
229
|
-
capabilities: await attemptCapabilities(item.attempt),
|
|
230
225
|
};
|
|
231
226
|
}
|
|
232
227
|
const identityRedact = (text) => text;
|
|
@@ -235,7 +230,7 @@ export async function attemptListData(input, opts) {
|
|
|
235
230
|
const { snapshots } = resolveInput(input);
|
|
236
231
|
const redact = opts?.redact ?? identityRedact;
|
|
237
232
|
const items = collectItems(snapshots);
|
|
238
|
-
return
|
|
233
|
+
return items.map((item) => attemptListItemOf(item, redact));
|
|
239
234
|
}
|
|
240
235
|
/** `EvalList.data(selection)`:每个 `experimentId + evalId` 一项,按 evalId 再按 experimentId 升序。 */
|
|
241
236
|
export async function evalListData(input) {
|
|
@@ -254,13 +249,11 @@ export async function evalListData(input) {
|
|
|
254
249
|
for (const group of groups.values()) {
|
|
255
250
|
const sorted = [...group].sort((a, b) => a.attempt.result.attempt - b.attempt.result.attempt);
|
|
256
251
|
const verdict = foldEvalVerdict(sorted.map((item) => item.attempt.result));
|
|
257
|
-
const
|
|
258
|
-
const attempts = await Promise.all(sorted.map((item) => attemptListItemOf(item, identityRedact)));
|
|
252
|
+
const attempts = sorted.map((item) => attemptListItemOf(item, identityRedact));
|
|
259
253
|
out.push({
|
|
260
254
|
evalId: evalIdOf(sorted[0]),
|
|
261
255
|
experimentId: experimentIdOf(sorted[0]),
|
|
262
256
|
verdict,
|
|
263
|
-
reason: reasonFor(representative.attempt.result),
|
|
264
257
|
score: await computeCell(examScore, sorted),
|
|
265
258
|
duration: await computeCell(durationMs, sorted),
|
|
266
259
|
cost: await computeCell(costUSD, sorted),
|
|
@@ -284,12 +277,10 @@ export async function experimentListData(input) {
|
|
|
284
277
|
for (const [evalId, evalItems] of evalGroups) {
|
|
285
278
|
const sorted = [...evalItems].sort((a, b) => a.attempt.result.attempt - b.attempt.result.attempt);
|
|
286
279
|
const verdict = foldEvalVerdict(sorted.map((item) => item.attempt.result));
|
|
287
|
-
const
|
|
288
|
-
const attempts = await Promise.all(sorted.map((item) => attemptListItemOf(item, identityRedact)));
|
|
280
|
+
const attempts = sorted.map((item) => attemptListItemOf(item, identityRedact));
|
|
289
281
|
evalRows.push({
|
|
290
282
|
evalId,
|
|
291
283
|
verdict,
|
|
292
|
-
reason: reasonFor(representative.attempt.result),
|
|
293
284
|
duration: await computeCell(durationMs, sorted),
|
|
294
285
|
cost: await computeCell(costUSD, sorted),
|
|
295
286
|
attempts,
|
|
@@ -305,7 +296,7 @@ export async function experimentListData(input) {
|
|
|
305
296
|
: {}),
|
|
306
297
|
...(experiment?.flags ? { flags: experiment.flags } : {}),
|
|
307
298
|
verdicts: stats.verdicts,
|
|
308
|
-
passRate: await computeCell(
|
|
299
|
+
passRate: await computeCell(endToEndPassRate, group),
|
|
309
300
|
cost: await computeCell(costUSD, group),
|
|
310
301
|
duration: await computeCell(durationMs, group),
|
|
311
302
|
tokens: await computeCell(tokens, group),
|
|
@@ -315,7 +306,17 @@ export async function experimentListData(input) {
|
|
|
315
306
|
evalRows,
|
|
316
307
|
});
|
|
317
308
|
}
|
|
318
|
-
|
|
309
|
+
// ExperimentList 是默认实验比较表:初始态按端到端成功率(endToEndPassRate)从高到低,缺数据沉底;
|
|
310
|
+
// 同分时按 experiment id 稳定排序。web 增强可临时重排,text 面沿用同一基准顺序。
|
|
311
|
+
out.sort((a, b) => {
|
|
312
|
+
if (a.passRate.value === null && b.passRate.value === null)
|
|
313
|
+
return a.experimentId.localeCompare(b.experimentId);
|
|
314
|
+
if (a.passRate.value === null)
|
|
315
|
+
return 1;
|
|
316
|
+
if (b.passRate.value === null)
|
|
317
|
+
return -1;
|
|
318
|
+
return b.passRate.value - a.passRate.value || a.experimentId.localeCompare(b.experimentId);
|
|
319
|
+
});
|
|
319
320
|
return out;
|
|
320
321
|
}
|
|
321
322
|
export async function matrixData(input, opts) {
|
|
@@ -475,7 +476,7 @@ export async function lineData(input, opts) {
|
|
|
475
476
|
const groups = groupItems(items, "experiment");
|
|
476
477
|
const rows = [];
|
|
477
478
|
for (const [key, group] of groups) {
|
|
478
|
-
const x =
|
|
479
|
+
const x = axisValue(opts.x, group[0]); // flag / config 都是 experiment 级声明,组内一致
|
|
479
480
|
rows.push({
|
|
480
481
|
key,
|
|
481
482
|
series: opts.series ? dimensionKey(opts.series, group[0]) : undefined,
|
|
@@ -529,9 +530,10 @@ export async function overviewData(input) {
|
|
|
529
530
|
if (cost !== null)
|
|
530
531
|
costUSD = (costUSD ?? 0) + cost;
|
|
531
532
|
}
|
|
532
|
-
//
|
|
533
|
-
//
|
|
534
|
-
|
|
533
|
+
// 默认成功率的唯一官方口径:endToEndPassRate 的两级聚合(computeCell),不是从上面四个
|
|
534
|
+
// verdict 计票现场重算——一道题内 attempt 部分通过要算部分 credit,不是二元投票;
|
|
535
|
+
// failed / errored 都记 0,只有 skipped 不进聚合。
|
|
536
|
+
const passRateCell = await computeCell(endToEndPassRate, items);
|
|
535
537
|
return {
|
|
536
538
|
snapshots: snapshots.map((s) => ({
|
|
537
539
|
experimentId: s.experimentId,
|
package/dist/report/flag.d.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
import type { FlagRef } from "./types.ts";
|
|
1
|
+
import type { ConfigRef, FlagRef } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* 把 experiment 声明的一个 flag 当分组维度(series / rows / columns / points 槽)或数值轴
|
|
4
|
+
* (MetricLine 的 x 槽)。只读 `ExperimentDef.flags` 里显式声明的 KV;未声明的 experiment
|
|
5
|
+
* 分组归 `(unset)`,作轴不画点并报告缺失。
|
|
6
|
+
*/
|
|
2
7
|
export declare function flag(name: string, opts?: {
|
|
3
8
|
/** 组标签 / 轴标签;函数形态把声明值折成组名(如 `(v) => \`${v} agents\``)。 */
|
|
4
9
|
label?: string | ((value: string | number | boolean) => string);
|
|
5
10
|
unit?: string;
|
|
6
11
|
}): FlagRef;
|
|
12
|
+
/**
|
|
13
|
+
* 把一项顶层运行配置当分组维度或数值轴,与 {@link flag} 同一套用法。读快照的
|
|
14
|
+
* `ExperimentRunInfo` 投影(可用键是那张接口的字段全集),外加桥接到快照顶层权威字段的
|
|
15
|
+
* `model` / `agent` 两个键。未投影的值分组归 `(unset)`,作轴不画点并报告缺失。
|
|
16
|
+
*/
|
|
17
|
+
export declare function config(name: string, opts?: {
|
|
18
|
+
/** 组标签 / 轴标签;函数形态把投影值折成组名(如 `(v) => \`effort ${v}\``)。 */
|
|
19
|
+
label?: string | ((value: string | number | boolean) => string);
|
|
20
|
+
unit?: string;
|
|
21
|
+
}): ConfigRef;
|
package/dist/report/flag.js
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
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 投影。
|
|
5
|
+
/**
|
|
6
|
+
* 把 experiment 声明的一个 flag 当分组维度(series / rows / columns / points 槽)或数值轴
|
|
7
|
+
* (MetricLine 的 x 槽)。只读 `ExperimentDef.flags` 里显式声明的 KV;未声明的 experiment
|
|
8
|
+
* 分组归 `(unset)`,作轴不画点并报告缺失。
|
|
9
|
+
*/
|
|
3
10
|
export function flag(name, opts) {
|
|
4
11
|
if (typeof name !== "string" || name.length === 0) {
|
|
5
12
|
throw new Error("flag: name must be a non-empty string (the key declared in the experiment's flags).");
|
|
6
13
|
}
|
|
7
14
|
return { kind: "flag", name, label: opts?.label, unit: opts?.unit };
|
|
8
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* 把一项顶层运行配置当分组维度或数值轴,与 {@link flag} 同一套用法。读快照的
|
|
18
|
+
* `ExperimentRunInfo` 投影(可用键是那张接口的字段全集),外加桥接到快照顶层权威字段的
|
|
19
|
+
* `model` / `agent` 两个键。未投影的值分组归 `(unset)`,作轴不画点并报告缺失。
|
|
20
|
+
*/
|
|
21
|
+
export function config(name, opts) {
|
|
22
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
23
|
+
throw new Error("config: name must be a non-empty string (an ExperimentRunInfo field, or the bridged \"model\" / \"agent\" keys).");
|
|
24
|
+
}
|
|
25
|
+
return { kind: "config", name, label: opts?.label, unit: opts?.unit };
|
|
26
|
+
}
|
package/dist/report/format.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import type { AttemptEvidenceCapabilities } from "../results/attempt-evidence.ts";
|
|
2
1
|
import type { AssertionResult, Verdict } from "../types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* experiment 行的显示名:给了父路径 `relativeTo` 且它确是前缀,就去掉 `relativeTo + "/"`,
|
|
4
|
+
* 只留 id 末段——用在已经以组为标题的上下文(如默认 `ExperimentComparison` 的每组面板)里,
|
|
5
|
+
* 避免每行重复文件夹名。组键就是 experiment id 的父目录,因此这里的末段与 `MetricScatter`
|
|
6
|
+
* 点标签取的末段同源。不给 `relativeTo`、或它不是前缀(如根目录单例组)时原样返回完整 id。
|
|
7
|
+
* 完整 id 仍是排序 / 着色 / 折叠的键,调用方不要拿这个显示名当身份用。
|
|
8
|
+
*/
|
|
9
|
+
export declare function experimentDisplayName(experimentId: string, relativeTo?: string): string;
|
|
3
10
|
export declare function formatMetricValue(value: number, unit?: string): string;
|
|
4
11
|
/** 无单位纯数字(scoreboard 总分等):一位小数,去尾零。 */
|
|
5
12
|
export declare function formatPlainNumber(value: number): string;
|
|
@@ -14,19 +21,14 @@ export declare function formatPercent(ratio: number): string;
|
|
|
14
21
|
/** passed / failed / errored / skipped 的判定符。 */
|
|
15
22
|
export declare function verdictMark(verdict: Verdict): string;
|
|
16
23
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
export declare function capabilityBadge(capabilities: AttemptEvidenceCapabilities): string;
|
|
21
|
-
/**
|
|
22
|
-
* 一个 AttemptListItem 的失败原因摘要,按优先级取第一个在场的:`error` → 未通过的 gate 断言
|
|
23
|
-
* (原始声明顺序,`name`,detail 在场则 `"name: detail"`,多条用「, 」连接)→ 缺席。与
|
|
24
|
-
* compute.ts 的 `reasonFor(EvalResult)` 同一口径,只是输入换成瘦身后的 AttemptListItem——
|
|
25
|
-
* 没有 `skipReason` 字段(AttemptListItem 不携带),skipped 的 attempt 因此没有原因摘要,
|
|
26
|
-
* 这与它们本来就不该出现在「为什么失败」列表里的事实一致。EvalList / ExperimentList 的
|
|
27
|
-
* 逐 attempt 徽标行用它给每个 attempt 自己的原因(而不是复用整道题的代表原因)。
|
|
24
|
+
* Attempt 比较项的一层结果摘要;完整 assertions 只在 locator 详情里展开。
|
|
25
|
+
* maxChars(可选)是渲染面的宽度收口预算(如两行单元格 = 2 × 列宽):断言摘要按
|
|
26
|
+
* fitCompactAssertionSummary 的优先级让位,error 摘要折单行后尾截。
|
|
28
27
|
*/
|
|
29
28
|
export declare function attemptItemReason(item: {
|
|
30
|
-
|
|
29
|
+
verdict: Verdict;
|
|
30
|
+
error?: {
|
|
31
|
+
message: string;
|
|
32
|
+
};
|
|
31
33
|
assertions: AssertionResult[];
|
|
32
|
-
}): string | undefined;
|
|
34
|
+
}, maxChars?: number): string | undefined;
|
package/dist/report/format.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
// unit 驱动的内置格式化(docs/feature/reports/library.md「指标」):
|
|
2
2
|
// "%" → 87% "ms" → 1.2s "$" → $0.31 其余 → 1.2k 缩写(带 unit 后缀)
|
|
3
3
|
// metric.display 可整体覆盖;这里只负责默认。
|
|
4
|
+
import { compactAssertionSummary, fitCompactAssertionSummary, primaryAssertionSummary } from "../scoring/display.js";
|
|
5
|
+
/**
|
|
6
|
+
* experiment 行的显示名:给了父路径 `relativeTo` 且它确是前缀,就去掉 `relativeTo + "/"`,
|
|
7
|
+
* 只留 id 末段——用在已经以组为标题的上下文(如默认 `ExperimentComparison` 的每组面板)里,
|
|
8
|
+
* 避免每行重复文件夹名。组键就是 experiment id 的父目录,因此这里的末段与 `MetricScatter`
|
|
9
|
+
* 点标签取的末段同源。不给 `relativeTo`、或它不是前缀(如根目录单例组)时原样返回完整 id。
|
|
10
|
+
* 完整 id 仍是排序 / 着色 / 折叠的键,调用方不要拿这个显示名当身份用。
|
|
11
|
+
*/
|
|
12
|
+
export function experimentDisplayName(experimentId, relativeTo) {
|
|
13
|
+
if (relativeTo && experimentId.startsWith(`${relativeTo}/`)) {
|
|
14
|
+
return experimentId.slice(relativeTo.length + 1);
|
|
15
|
+
}
|
|
16
|
+
return experimentId;
|
|
17
|
+
}
|
|
4
18
|
/** 一位小数、去掉无意义的 ".0" 尾巴。 */
|
|
5
19
|
function trimmed(n) {
|
|
6
20
|
const s = n.toFixed(1);
|
|
@@ -77,8 +91,7 @@ export function formatUSD(usd) {
|
|
|
77
91
|
export function formatPercent(ratio) {
|
|
78
92
|
return `${Math.round(ratio * 100)}%`;
|
|
79
93
|
}
|
|
80
|
-
// ── 实体列表(ExperimentList / EvalList / AttemptList)
|
|
81
|
-
// web / text 两面对同一个 AttemptListItem 给出同一个标记(docs-site「终端输出形成反馈闭环」)。
|
|
94
|
+
// ── 实体列表(ExperimentList / EvalList / AttemptList)共用的判定符 ──
|
|
82
95
|
/** passed / failed / errored / skipped 的判定符。 */
|
|
83
96
|
export function verdictMark(verdict) {
|
|
84
97
|
switch (verdict) {
|
|
@@ -93,34 +106,19 @@ export function verdictMark(verdict) {
|
|
|
93
106
|
}
|
|
94
107
|
}
|
|
95
108
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
109
|
+
* Attempt 比较项的一层结果摘要;完整 assertions 只在 locator 详情里展开。
|
|
110
|
+
* maxChars(可选)是渲染面的宽度收口预算(如两行单元格 = 2 × 列宽):断言摘要按
|
|
111
|
+
* fitCompactAssertionSummary 的优先级让位,error 摘要折单行后尾截。
|
|
98
112
|
*/
|
|
99
|
-
export function
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
marks.push("D");
|
|
109
|
-
return marks.length > 0 ? `[${marks.join(",")}]` : "";
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* 一个 AttemptListItem 的失败原因摘要,按优先级取第一个在场的:`error` → 未通过的 gate 断言
|
|
113
|
-
* (原始声明顺序,`name`,detail 在场则 `"name: detail"`,多条用「, 」连接)→ 缺席。与
|
|
114
|
-
* compute.ts 的 `reasonFor(EvalResult)` 同一口径,只是输入换成瘦身后的 AttemptListItem——
|
|
115
|
-
* 没有 `skipReason` 字段(AttemptListItem 不携带),skipped 的 attempt 因此没有原因摘要,
|
|
116
|
-
* 这与它们本来就不该出现在「为什么失败」列表里的事实一致。EvalList / ExperimentList 的
|
|
117
|
-
* 逐 attempt 徽标行用它给每个 attempt 自己的原因(而不是复用整道题的代表原因)。
|
|
118
|
-
*/
|
|
119
|
-
export function attemptItemReason(item) {
|
|
120
|
-
if (item.error !== undefined)
|
|
121
|
-
return item.error;
|
|
122
|
-
const gates = item.assertions.filter((a) => !a.passed && a.severity === "gate");
|
|
123
|
-
if (gates.length === 0)
|
|
113
|
+
export function attemptItemReason(item, maxChars) {
|
|
114
|
+
if (item.error !== undefined) {
|
|
115
|
+
const message = item.error.message.replace(/\s+/g, " ").trim();
|
|
116
|
+
return maxChars !== undefined && message.length > maxChars
|
|
117
|
+
? `${message.slice(0, Math.max(0, maxChars - 1))}…`
|
|
118
|
+
: message;
|
|
119
|
+
}
|
|
120
|
+
const summary = primaryAssertionSummary(item.assertions, item.verdict);
|
|
121
|
+
if (summary === undefined)
|
|
124
122
|
return undefined;
|
|
125
|
-
return
|
|
123
|
+
return maxChars === undefined ? compactAssertionSummary(summary) : fitCompactAssertionSummary(summary, maxChars);
|
|
126
124
|
}
|