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,179 @@
|
|
|
1
|
+
// ExperimentComparison:裸 niceeval show / view 首页渲染的内置默认报告,同时是可整体引用的
|
|
2
|
+
// 官方组合件(docs/feature/reports/library.md「ExperimentComparison」)。
|
|
3
|
+
//
|
|
4
|
+
// 名字描述整份报告回答的问题,不绑定其中某一张图。它是组合件而非新的数据源——每组的
|
|
5
|
+
// GroupSummary、成本 × 端到端成功率散点、固定列 ExperimentList 都消费各自公开 .data() 的结果。
|
|
6
|
+
// 两种用法共享同一份口径:
|
|
7
|
+
// · 默认报告:宿主把它当普通 ReportDefinition(build 面),没有 renderer 私有通道;
|
|
8
|
+
// · 报告组件:`<ExperimentComparison data={await ExperimentComparison.data(selection)} />`,
|
|
9
|
+
// 在自定义报告或用户自己的 React 页面里整体引用。
|
|
10
|
+
|
|
11
|
+
import { Col, Section, Table } from "../primitives.tsx";
|
|
12
|
+
import { ExperimentList, GroupSummary, MetricScatter } from "../components.tsx";
|
|
13
|
+
import { costUSD, endToEndPassRate } from "../metrics.ts";
|
|
14
|
+
import { defineReport, type ReportDefinition } from "../report.ts";
|
|
15
|
+
import { defineComponent, type ReportComponent, type TextContext } from "../tree.ts";
|
|
16
|
+
import { localeText, type ReportLocale } from "../locale.ts";
|
|
17
|
+
import type { ExperimentListItem, GroupSummaryData, ScatterData } from "../types.ts";
|
|
18
|
+
import type { ScatterDataOptions } from "../compute.ts";
|
|
19
|
+
import { resolveInput, type SnapshotsInput } from "../aggregate.ts";
|
|
20
|
+
import { ExperimentComparisonView } from "../react/ExperimentComparison.tsx";
|
|
21
|
+
import { wrapDisplay } from "../text/layout.ts";
|
|
22
|
+
import { formatUSD } from "../format.ts";
|
|
23
|
+
|
|
24
|
+
// 每组散点的唯一口径:默认 definition 与公开 `.data()` 共用,不各写一份。
|
|
25
|
+
const SCATTER_OPTIONS: ScatterDataOptions = {
|
|
26
|
+
points: "experiment",
|
|
27
|
+
series: "agent",
|
|
28
|
+
x: costUSD,
|
|
29
|
+
y: endToEndPassRate,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 一个可比组的数据。三个子块都只消费本组快照,不能含其它父目录的引用。
|
|
34
|
+
*/
|
|
35
|
+
export interface ExperimentComparisonGroupData {
|
|
36
|
+
/** experiment id 的完整父路径;根目录 experiment 使用自己的完整 id。 */
|
|
37
|
+
key: string;
|
|
38
|
+
summary: GroupSummaryData;
|
|
39
|
+
/** 成本 × 端到端成功率散点(`MetricScatter.data` 的口径:points=experiment、series=agent)。 */
|
|
40
|
+
scatter: ScatterData;
|
|
41
|
+
/** 固定列 experiment 比较表(`ExperimentList.data` 的口径,已按端到端成功率排序)。 */
|
|
42
|
+
experiments: ExperimentListItem[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** `ExperimentComparison.data(selection)` 的穷尽产物:按 key 排序的独立可比组。 */
|
|
46
|
+
export interface ExperimentComparisonData {
|
|
47
|
+
groups: ExperimentComparisonGroupData[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 完整父路径是组键;没有父路径的 experiment 不能互相比,自己形成单例组。 */
|
|
51
|
+
export function experimentComparisonGroupKey(experimentId: string): string {
|
|
52
|
+
const slash = experimentId.lastIndexOf("/");
|
|
53
|
+
return slash === -1 ? experimentId : experimentId.slice(0, slash);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 组合件的数据计算:先分区,再让三个官方子块在各组内并行计算。 */
|
|
57
|
+
async function experimentComparisonData(input: SnapshotsInput): Promise<ExperimentComparisonData> {
|
|
58
|
+
const { snapshots } = resolveInput(input);
|
|
59
|
+
const snapshotsByGroup = new Map<string, typeof snapshots>();
|
|
60
|
+
for (const snapshot of snapshots) {
|
|
61
|
+
const key = experimentComparisonGroupKey(snapshot.experimentId);
|
|
62
|
+
const group = snapshotsByGroup.get(key);
|
|
63
|
+
if (group) group.push(snapshot);
|
|
64
|
+
else snapshotsByGroup.set(key, [snapshot]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const groups = await Promise.all(
|
|
68
|
+
[...snapshotsByGroup.entries()]
|
|
69
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
70
|
+
.map(async ([key, groupSnapshots]): Promise<ExperimentComparisonGroupData> => {
|
|
71
|
+
const [summary, scatter, experiments] = await Promise.all([
|
|
72
|
+
GroupSummary.data(groupSnapshots),
|
|
73
|
+
MetricScatter.data(groupSnapshots, SCATTER_OPTIONS),
|
|
74
|
+
ExperimentList.data(groupSnapshots),
|
|
75
|
+
]);
|
|
76
|
+
return { key, summary, scatter, experiments };
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
return { groups };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ExperimentComparisonProps {
|
|
83
|
+
data: ExperimentComparisonData;
|
|
84
|
+
/** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
|
|
85
|
+
locale?: ReportLocale;
|
|
86
|
+
className?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 单组详情:散点与实验列表全部来自同一个预分区结果。 */
|
|
90
|
+
function groupComposition(group: ExperimentComparisonGroupData, locale?: ReportLocale, className?: string) {
|
|
91
|
+
return (
|
|
92
|
+
<Col className={className}>
|
|
93
|
+
<MetricScatter data={group.scatter} locale={locale} />
|
|
94
|
+
<ExperimentList items={group.experiments} filter locale={locale} relativeTo={group.key} />
|
|
95
|
+
</Col>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function shellQuote(value: string): string {
|
|
100
|
+
return /^[A-Za-z0-9._/-]+$/.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function verdictsText(group: ExperimentComparisonGroupData, locale: ReportLocale): string {
|
|
104
|
+
const parts: string[] = [];
|
|
105
|
+
for (const verdict of ["passed", "failed", "errored", "skipped"] as const) {
|
|
106
|
+
const count = group.summary.verdicts[verdict];
|
|
107
|
+
if (count > 0) parts.push(`${count} ${localeText(locale, `verdict.${verdict}`)}`);
|
|
108
|
+
}
|
|
109
|
+
return parts.length > 0 ? parts.join(" / ") : "—";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function textFace(data: ExperimentComparisonData, className: string | undefined, ctx: TextContext): string {
|
|
113
|
+
if (data.groups.length === 0) return localeText(ctx.locale, "experimentComparison.empty");
|
|
114
|
+
if (data.groups.length === 1) {
|
|
115
|
+
const group = data.groups[0]!;
|
|
116
|
+
return ctx.render(
|
|
117
|
+
<Section title={group.key} className={className}>
|
|
118
|
+
{groupComposition(group)}
|
|
119
|
+
</Section>,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const table = ctx.render(
|
|
124
|
+
<Table
|
|
125
|
+
columns={[
|
|
126
|
+
{ key: "group", header: localeText(ctx.locale, "experimentComparison.group") },
|
|
127
|
+
{ key: "experiments", header: localeText(ctx.locale, "groupSummary.experiments"), align: "right" },
|
|
128
|
+
{ key: "evals", header: localeText(ctx.locale, "overview.evals"), align: "right" },
|
|
129
|
+
{ key: "passRate", header: localeText(ctx.locale, "overview.passRate"), align: "right" },
|
|
130
|
+
{ key: "results", header: localeText(ctx.locale, "experimentComparison.results") },
|
|
131
|
+
{ key: "cost", header: localeText(ctx.locale, "overview.totalCost"), align: "right" },
|
|
132
|
+
{ key: "lastRun", header: localeText(ctx.locale, "experimentComparison.lastRun") },
|
|
133
|
+
]}
|
|
134
|
+
rows={data.groups.map((group) => ({
|
|
135
|
+
key: group.key,
|
|
136
|
+
cells: {
|
|
137
|
+
group: group.key,
|
|
138
|
+
experiments: String(group.summary.experiments),
|
|
139
|
+
evals: String(group.summary.evals),
|
|
140
|
+
passRate: group.summary.passRate.display,
|
|
141
|
+
results: verdictsText(group, ctx.locale),
|
|
142
|
+
cost: group.summary.totalCostUSD === null ? null : formatUSD(group.summary.totalCostUSD),
|
|
143
|
+
lastRun: group.summary.lastRunAt ?? null,
|
|
144
|
+
},
|
|
145
|
+
}))}
|
|
146
|
+
/>,
|
|
147
|
+
);
|
|
148
|
+
const commands = data.groups
|
|
149
|
+
.map((group) => {
|
|
150
|
+
const command = `niceeval show --experiment ${shellQuote(group.key)}`;
|
|
151
|
+
return wrapDisplay(localeText(ctx.locale, "experimentComparison.command", { command }), ctx.width).join("\n");
|
|
152
|
+
})
|
|
153
|
+
.join("\n");
|
|
154
|
+
return `${localeText(ctx.locale, "experimentComparison.groups")}\n\n${table}\n\n${commands}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 内置默认报告兼官方组合件:作为组件时收 `data`(`.data()` 的产物),两个渲染面直接摆
|
|
159
|
+
* 数据形态的子组件;作为默认报告时经 build 面走与用户 `--report` 完全相同的管线。
|
|
160
|
+
*/
|
|
161
|
+
export const ExperimentComparison: ReportComponent<ExperimentComparisonProps> &
|
|
162
|
+
ReportDefinition & { data: typeof experimentComparisonData } = Object.assign(
|
|
163
|
+
defineComponent<ExperimentComparisonProps>({
|
|
164
|
+
web: (props, ctx) => (
|
|
165
|
+
<ExperimentComparisonView
|
|
166
|
+
data={props.data}
|
|
167
|
+
locale={props.locale ?? ctx.locale}
|
|
168
|
+
className={props.className}
|
|
169
|
+
/>
|
|
170
|
+
),
|
|
171
|
+
text: ({ data, className }, ctx) => textFace(data, className, ctx),
|
|
172
|
+
}),
|
|
173
|
+
defineReport(async ({ selection }) => {
|
|
174
|
+
const data = await experimentComparisonData(selection);
|
|
175
|
+
return <ExperimentComparison data={data} />;
|
|
176
|
+
}),
|
|
177
|
+
{ data: experimentComparisonData },
|
|
178
|
+
);
|
|
179
|
+
ExperimentComparison.displayName = "ExperimentComparison";
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
export {
|
|
1
|
+
// 内置报告目录:一个文件一个按展示内容命名的组合件(组件兼默认报告),显式导出值,不建字符串 registry。
|
|
2
|
+
export { ExperimentComparison } from "./experiment-comparison.tsx";
|
|
3
|
+
export type {
|
|
4
|
+
ExperimentComparisonData,
|
|
5
|
+
ExperimentComparisonGroupData,
|
|
6
|
+
ExperimentComparisonProps,
|
|
7
|
+
} from "./experiment-comparison.tsx";
|
|
@@ -90,6 +90,14 @@ export interface GroupSummaryProps {
|
|
|
90
90
|
*/
|
|
91
91
|
export interface ExperimentListProps {
|
|
92
92
|
items: ExperimentListItem[];
|
|
93
|
+
/** web 面在比较表前显示实验过滤框;text 面忽略。 */
|
|
94
|
+
filter?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* 可选父路径:两面的行标签去掉与它相同的前缀,只显示 experiment id 末段,避免在已经以组为
|
|
97
|
+
* 标题的上下文里重复文件夹名(默认 `ExperimentComparison` 给每组传组键)。完整 id 仍是排序 /
|
|
98
|
+
* 着色 / 过滤 / 折叠的键;不传或它不是前缀(如根目录单例组)时显示完整 id。
|
|
99
|
+
*/
|
|
100
|
+
relativeTo?: string;
|
|
93
101
|
/** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
|
|
94
102
|
locale?: ReportLocale;
|
|
95
103
|
className?: string;
|
|
@@ -233,7 +241,7 @@ export const ExperimentList: ReportComponent<ExperimentListProps> & { data: type
|
|
|
233
241
|
Object.assign(
|
|
234
242
|
defineComponent<ExperimentListProps>({
|
|
235
243
|
web: (props, ctx) => <ExperimentListWeb {...props} locale={props.locale ?? ctx.locale} attemptHref={ctx.attemptHref} />,
|
|
236
|
-
text: ({ items }, ctx) => experimentListText(items, ctx),
|
|
244
|
+
text: ({ items, relativeTo }, ctx) => experimentListText(items, ctx, relativeTo),
|
|
237
245
|
}),
|
|
238
246
|
{ data: experimentListData },
|
|
239
247
|
);
|
|
@@ -253,8 +261,8 @@ export const EvalList: ReportComponent<EvalListProps> & { data: typeof evalListD
|
|
|
253
261
|
EvalList.displayName = "EvalList";
|
|
254
262
|
|
|
255
263
|
/**
|
|
256
|
-
* Attempt 列表:实体列表的叶子层,每项一个 Attempt
|
|
257
|
-
* (assertions
|
|
264
|
+
* Attempt 列表:实体列表的叶子层,每项一个 Attempt,固定展示判定、一条主失败断言摘要或
|
|
265
|
+
* error 一层摘要与证据引用(locator)。完整 assertions / evidence 经 locator 下钻。它不预设只看失败;
|
|
258
266
|
* 报告作者过滤 `AttemptListItem[]`、用 `.slice()` 限量,`total` 让渲染面如实报告剩余数量。
|
|
259
267
|
*/
|
|
260
268
|
export const AttemptList: ReportComponent<AttemptListProps> & { data: typeof attemptListData } = Object.assign(
|
package/src/report/compute.ts
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
// - core 中立:只认 Metric / Dimension 接口,不出现具体 agent 名的分支。
|
|
12
12
|
|
|
13
13
|
import type {
|
|
14
|
-
AttemptEvidenceCapabilities,
|
|
15
14
|
AttemptListItem,
|
|
16
15
|
AttemptLocator,
|
|
16
|
+
AxisInput,
|
|
17
17
|
DeltaData,
|
|
18
18
|
DimensionInput,
|
|
19
19
|
EvalListItem,
|
|
@@ -25,18 +25,18 @@ import type {
|
|
|
25
25
|
Metric,
|
|
26
26
|
MetricCell,
|
|
27
27
|
OverviewData,
|
|
28
|
-
FlagRef,
|
|
29
28
|
ScatterData,
|
|
30
29
|
ScoreboardData,
|
|
31
30
|
TableData,
|
|
32
31
|
TableRowMeta,
|
|
33
32
|
} from "./types.ts";
|
|
34
|
-
import type { AssertionResult, EvalResult } from "../types.ts";
|
|
33
|
+
import type { AssertionResult, AttemptError, DiagnosticRecord, EvalResult } from "../types.ts";
|
|
35
34
|
import type { AttemptHandle } from "../results/types.ts";
|
|
36
35
|
import { evalLevelStats, foldEvalVerdict } from "../shared/verdict.ts";
|
|
37
36
|
import {
|
|
38
37
|
applyAggregator,
|
|
39
38
|
assertUniqueMetricNames,
|
|
39
|
+
axisValue,
|
|
40
40
|
collectItems,
|
|
41
41
|
computeCell,
|
|
42
42
|
dimensionKey,
|
|
@@ -49,7 +49,6 @@ import {
|
|
|
49
49
|
experimentIdOf,
|
|
50
50
|
filterItems,
|
|
51
51
|
groupItems,
|
|
52
|
-
flagAxisValue,
|
|
53
52
|
locatorOf,
|
|
54
53
|
resolveInput,
|
|
55
54
|
snapshotKeyOf,
|
|
@@ -57,8 +56,9 @@ import {
|
|
|
57
56
|
type Item,
|
|
58
57
|
type SnapshotsInput,
|
|
59
58
|
} from "./aggregate.ts";
|
|
60
|
-
import { attemptCostUSD, costUSD, durationMs,
|
|
59
|
+
import { attemptCostUSD, costUSD, durationMs, endToEndPassRate, examScore, tokens } from "./metrics.ts";
|
|
61
60
|
import { formatMetricValue, formatPlainNumber } from "./format.ts";
|
|
61
|
+
import { compactAssertionSummary, primaryAssertionSummary } from "../scoring/display.ts";
|
|
62
62
|
|
|
63
63
|
// ───────────────────────── MetricTable.data ─────────────────────────
|
|
64
64
|
|
|
@@ -176,27 +176,12 @@ function experimentRowMeta(group: Item[]): TableRowMeta {
|
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
/**
|
|
180
|
-
* 一次 attempt 未通过的 gate 断言,原始声明顺序不变;soft 断言不参与判定,不算「失败原因」,
|
|
181
|
-
* 只影响得分,永不出现在这份列表里。`EvalList` / `ExperimentList` 的失败诊断与 `AttemptList`
|
|
182
|
-
* 的断言列表共用这同一份材料,保证同一个 attempt 在各处给出同一个原因。
|
|
183
|
-
*/
|
|
184
|
-
export function failingGateAssertions(result: EvalResult): AssertionResult[] {
|
|
185
|
-
return result.assertions.filter((a) => !a.passed && a.severity === "gate");
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* 一次 attempt 的失败原因文案,按优先级取第一个在场的:
|
|
190
|
-
* `error` → `skipReason` → 未通过的 gate 断言(原始声明顺序,`name`,detail 在场则
|
|
191
|
-
* `"name: detail"`,多条用「, 」连接)→ 都缺席则无原因(如某道题恰好没有失败信号)。
|
|
192
|
-
* soft 断言永不进入这份原因文案,soft 得分是独立概念,不与 reason 混用同一个字段。
|
|
193
|
-
*/
|
|
179
|
+
/** 一次 attempt 的有界结果摘要:error → skipReason → 一条主失败断言 + 其余失败计数。 */
|
|
194
180
|
export function reasonFor(result: EvalResult): string | undefined {
|
|
195
|
-
if (result.error !== undefined) return result.error;
|
|
181
|
+
if (result.error !== undefined) return result.error.message;
|
|
196
182
|
if (result.skipReason !== undefined) return result.skipReason;
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
return gates.map((a) => (a.detail ? `${a.name}: ${a.detail}` : a.name)).join(", ");
|
|
183
|
+
const summary = primaryAssertionSummary(result.assertions, result.verdict);
|
|
184
|
+
return summary === undefined ? undefined : compactAssertionSummary(summary);
|
|
200
185
|
}
|
|
201
186
|
|
|
202
187
|
export async function tableData<const M extends readonly Metric[]>(
|
|
@@ -248,46 +233,58 @@ export async function tableData<const M extends readonly Metric[]>(
|
|
|
248
233
|
// (docs/feature/reports/library.md「实体列表」)。AttemptListItem 是三者共用的叶子形状——
|
|
249
234
|
// ExperimentList / EvalList 的下钻数组直接复用它,不各自精简一份。
|
|
250
235
|
|
|
236
|
+
/** 自由文本(断言 detail / evidence)的展示层遮蔽钩子;身份字段(name/severity/loc)不经它。 */
|
|
237
|
+
function redactAssertions(assertions: AssertionResult[], redact: (text: string) => string): AssertionResult[] {
|
|
238
|
+
if (assertions.length === 0) return assertions;
|
|
239
|
+
return assertions.map((a) => ({
|
|
240
|
+
...a,
|
|
241
|
+
...(a.detail !== undefined ? { detail: redact(a.detail) } : {}),
|
|
242
|
+
...(a.outcome !== "unavailable" && a.evidence !== undefined ? { evidence: redact(a.evidence) } : {}),
|
|
243
|
+
...(a.outcome !== "unavailable" && a.expected !== undefined ? { expected: redact(a.expected) } : {}),
|
|
244
|
+
...(a.outcome !== "unavailable" && a.received !== undefined ? { received: redact(a.received) } : {}),
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
|
|
251
248
|
/**
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
* 与
|
|
255
|
-
*
|
|
256
|
-
* - `eval`:`hasSources` 为真。真正的门槛是 `AnnotatedEvalSource` 非空且有内容(源码行或断言
|
|
257
|
-
* 非空),但 sources 存在时 `buildAnnotatedEvalSource` 恒产出至少一行源码,断言又永远来自
|
|
258
|
-
* `result.assertions`(不需要 IO)——`hasSources` 是这个条件的精确瘦身版,不是近似。
|
|
259
|
-
* - `execution`:`hasEvents` 为真,与 `loadAttemptEvidence` 的 `events !== null && events.length > 0`
|
|
260
|
-
* 同一个判定,写入面按同一条规则算的 `hasEvents` 就是它的瘦身镜像。
|
|
261
|
-
* - `timing`:`hasEvents && hasTrace` 为真——`execution` 成立且这次运行接入过 OTel
|
|
262
|
-
* (`ExecutionTree.timingAvailable` = `spans.length > 0`,`hasTrace` 正是这个判定的瘦身镜像)。
|
|
263
|
-
* - `diff`:唯一需要 IO 的一位,`attempt.diff()` 非空且至少改动/删除了一个文件——落盘没有
|
|
264
|
-
* 与 `hasEvents` 对应的瘦身摘要位,只能懒加载判定;成本是每个 item 一次 `diff()` 读取,
|
|
265
|
-
* 不是完整证据装配的四路 Promise.all。
|
|
249
|
+
* 结构化 error 的遮蔽:message / stack / cause.message 是自由文本,经钩子;
|
|
250
|
+
* code / operation / cause.name / cause.code 是分类与身份字段,原样保留
|
|
251
|
+
* (与 copySnapshots({ redact }) 的改写范围约定一致)。
|
|
266
252
|
*/
|
|
267
|
-
|
|
268
|
-
const result = attempt.result;
|
|
269
|
-
const diff = await attempt.diff();
|
|
270
|
-
const diffCapable = diff !== null && (Object.keys(diff.generatedFiles).length > 0 || diff.deletedFiles.length > 0);
|
|
253
|
+
function redactError(error: AttemptError, redact: (text: string) => string): AttemptError {
|
|
271
254
|
return {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
255
|
+
...error,
|
|
256
|
+
message: redact(error.message),
|
|
257
|
+
...(error.stack !== undefined ? { stack: redact(error.stack) } : {}),
|
|
258
|
+
...(error.cause !== undefined ? { cause: { ...error.cause, message: redact(error.cause.message) } } : {}),
|
|
276
259
|
};
|
|
277
260
|
}
|
|
278
261
|
|
|
279
|
-
/**
|
|
280
|
-
function
|
|
281
|
-
if (
|
|
282
|
-
return
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
262
|
+
/** JsonValue 树里逐个字符串值经钩子;结构与非字符串标量原样。 */
|
|
263
|
+
function redactJsonValue<T>(value: T, redact: (text: string) => string): T {
|
|
264
|
+
if (typeof value === "string") return redact(value) as unknown as T;
|
|
265
|
+
if (Array.isArray(value)) return value.map((v) => redactJsonValue(v, redact)) as unknown as T;
|
|
266
|
+
if (typeof value === "object" && value !== null) {
|
|
267
|
+
return Object.fromEntries(
|
|
268
|
+
Object.entries(value).map(([k, v]) => [k, redactJsonValue(v, redact)]),
|
|
269
|
+
) as unknown as T;
|
|
270
|
+
}
|
|
271
|
+
return value;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** diagnostics 的遮蔽:message 与 data(自由内容)经钩子;code / operation / level / count 原样。 */
|
|
275
|
+
function redactDiagnostics(
|
|
276
|
+
diagnostics: readonly DiagnosticRecord[],
|
|
277
|
+
redact: (text: string) => string,
|
|
278
|
+
): DiagnosticRecord[] {
|
|
279
|
+
return diagnostics.map((d) => ({
|
|
280
|
+
...d,
|
|
281
|
+
message: redact(d.message),
|
|
282
|
+
...(d.data !== undefined ? { data: redactJsonValue(d.data, redact) } : {}),
|
|
286
283
|
}));
|
|
287
284
|
}
|
|
288
285
|
|
|
289
286
|
/** AttemptList / ExperimentList / EvalList 共用的叶子构造:一个 Item → 一个 AttemptListItem。 */
|
|
290
|
-
|
|
287
|
+
function attemptListItemOf(item: Item, redact: (text: string) => string): AttemptListItem {
|
|
291
288
|
const result = item.attempt.result;
|
|
292
289
|
const cost = attemptCostUSD(result);
|
|
293
290
|
return {
|
|
@@ -296,19 +293,25 @@ async function attemptListItemOf(item: Item, redact: (text: string) => string):
|
|
|
296
293
|
attempt: result.attempt,
|
|
297
294
|
agent: result.agent,
|
|
298
295
|
verdict: result.verdict,
|
|
299
|
-
...(result.error !== undefined ? { error:
|
|
296
|
+
...(result.error !== undefined ? { error: redactError(result.error, redact) } : {}),
|
|
297
|
+
...(result.diagnostics !== undefined && result.diagnostics.length > 0
|
|
298
|
+
? { diagnostics: redactDiagnostics(result.diagnostics, redact) }
|
|
299
|
+
: {}),
|
|
300
300
|
assertions: redactAssertions(result.assertions, redact),
|
|
301
301
|
durationMs: result.durationMs,
|
|
302
302
|
...(cost !== null ? { costUSD: cost } : {}),
|
|
303
303
|
locator: locatorOf(item),
|
|
304
|
-
capabilities: await attemptCapabilities(item.attempt),
|
|
305
304
|
};
|
|
306
305
|
}
|
|
307
306
|
|
|
308
307
|
const identityRedact = (text: string): string => text;
|
|
309
308
|
|
|
310
309
|
export interface AttemptListDataOptions {
|
|
311
|
-
/**
|
|
310
|
+
/**
|
|
311
|
+
* 展示层遮蔽:error 的 message/cause/stack、diagnostic 的 message/data、断言 detail 与
|
|
312
|
+
* evidence 经这个钩子;experimentId、evalId、locator、error/diagnostic code 与 lifecycle
|
|
313
|
+
* operation 等身份和分类字段不经它。只作用于这次计算产出的组件数据,不改盘上 artifact。
|
|
314
|
+
*/
|
|
312
315
|
redact?: (text: string) => string;
|
|
313
316
|
}
|
|
314
317
|
|
|
@@ -320,7 +323,7 @@ export async function attemptListData(
|
|
|
320
323
|
const { snapshots } = resolveInput(input);
|
|
321
324
|
const redact = opts?.redact ?? identityRedact;
|
|
322
325
|
const items = collectItems(snapshots);
|
|
323
|
-
return
|
|
326
|
+
return items.map((item) => attemptListItemOf(item, redact));
|
|
324
327
|
}
|
|
325
328
|
|
|
326
329
|
/** `EvalList.data(selection)`:每个 `experimentId + evalId` 一项,按 evalId 再按 experimentId 升序。 */
|
|
@@ -338,13 +341,11 @@ export async function evalListData(input: SnapshotsInput): Promise<EvalListItem[
|
|
|
338
341
|
for (const group of groups.values()) {
|
|
339
342
|
const sorted = [...group].sort((a, b) => a.attempt.result.attempt - b.attempt.result.attempt);
|
|
340
343
|
const verdict = foldEvalVerdict(sorted.map((item) => item.attempt.result));
|
|
341
|
-
const
|
|
342
|
-
const attempts = await Promise.all(sorted.map((item) => attemptListItemOf(item, identityRedact)));
|
|
344
|
+
const attempts = sorted.map((item) => attemptListItemOf(item, identityRedact));
|
|
343
345
|
out.push({
|
|
344
346
|
evalId: evalIdOf(sorted[0]!),
|
|
345
347
|
experimentId: experimentIdOf(sorted[0]!),
|
|
346
348
|
verdict,
|
|
347
|
-
reason: reasonFor(representative.attempt.result),
|
|
348
349
|
score: await computeCell(examScore, sorted),
|
|
349
350
|
duration: await computeCell(durationMs, sorted),
|
|
350
351
|
cost: await computeCell(costUSD, sorted),
|
|
@@ -369,12 +370,10 @@ export async function experimentListData(input: SnapshotsInput): Promise<Experim
|
|
|
369
370
|
for (const [evalId, evalItems] of evalGroups) {
|
|
370
371
|
const sorted = [...evalItems].sort((a, b) => a.attempt.result.attempt - b.attempt.result.attempt);
|
|
371
372
|
const verdict = foldEvalVerdict(sorted.map((item) => item.attempt.result));
|
|
372
|
-
const
|
|
373
|
-
const attempts = await Promise.all(sorted.map((item) => attemptListItemOf(item, identityRedact)));
|
|
373
|
+
const attempts = sorted.map((item) => attemptListItemOf(item, identityRedact));
|
|
374
374
|
evalRows.push({
|
|
375
375
|
evalId,
|
|
376
376
|
verdict,
|
|
377
|
-
reason: reasonFor(representative.attempt.result),
|
|
378
377
|
duration: await computeCell(durationMs, sorted),
|
|
379
378
|
cost: await computeCell(costUSD, sorted),
|
|
380
379
|
attempts,
|
|
@@ -390,7 +389,7 @@ export async function experimentListData(input: SnapshotsInput): Promise<Experim
|
|
|
390
389
|
: {}),
|
|
391
390
|
...(experiment?.flags ? { flags: experiment.flags } : {}),
|
|
392
391
|
verdicts: stats.verdicts,
|
|
393
|
-
passRate: await computeCell(
|
|
392
|
+
passRate: await computeCell(endToEndPassRate, group),
|
|
394
393
|
cost: await computeCell(costUSD, group),
|
|
395
394
|
duration: await computeCell(durationMs, group),
|
|
396
395
|
tokens: await computeCell(tokens, group),
|
|
@@ -400,7 +399,14 @@ export async function experimentListData(input: SnapshotsInput): Promise<Experim
|
|
|
400
399
|
evalRows,
|
|
401
400
|
});
|
|
402
401
|
}
|
|
403
|
-
|
|
402
|
+
// ExperimentList 是默认实验比较表:初始态按端到端成功率(endToEndPassRate)从高到低,缺数据沉底;
|
|
403
|
+
// 同分时按 experiment id 稳定排序。web 增强可临时重排,text 面沿用同一基准顺序。
|
|
404
|
+
out.sort((a, b) => {
|
|
405
|
+
if (a.passRate.value === null && b.passRate.value === null) return a.experimentId.localeCompare(b.experimentId);
|
|
406
|
+
if (a.passRate.value === null) return 1;
|
|
407
|
+
if (b.passRate.value === null) return -1;
|
|
408
|
+
return b.passRate.value - a.passRate.value || a.experimentId.localeCompare(b.experimentId);
|
|
409
|
+
});
|
|
404
410
|
return out;
|
|
405
411
|
}
|
|
406
412
|
|
|
@@ -594,10 +600,10 @@ export async function scatterData(input: SnapshotsInput, opts: ScatterDataOption
|
|
|
594
600
|
// ───────────────────────── MetricLine.data ─────────────────────────
|
|
595
601
|
|
|
596
602
|
export interface LineDataOptions {
|
|
597
|
-
/** x 轴:experiment 声明的 flag(
|
|
598
|
-
x:
|
|
603
|
+
/** x 轴:experiment 声明的 flag 或顶层运行配置 config()(要求数值),不解析 experiment 命名。 */
|
|
604
|
+
x: AxisInput;
|
|
599
605
|
y: Metric;
|
|
600
|
-
/** 可选:每个系列一条线(flag 或普通维度);省略 = 单系列。 */
|
|
606
|
+
/** 可选:每个系列一条线(flag / config 或普通维度);省略 = 单系列。 */
|
|
601
607
|
series?: DimensionInput;
|
|
602
608
|
}
|
|
603
609
|
|
|
@@ -608,7 +614,7 @@ export async function lineData(input: SnapshotsInput, opts: LineDataOptions): Pr
|
|
|
608
614
|
const groups = groupItems(items, "experiment");
|
|
609
615
|
const rows: LineData["rows"] = [];
|
|
610
616
|
for (const [key, group] of groups) {
|
|
611
|
-
const x =
|
|
617
|
+
const x = axisValue(opts.x, group[0]); // flag / config 都是 experiment 级声明,组内一致
|
|
612
618
|
rows.push({
|
|
613
619
|
key,
|
|
614
620
|
series: opts.series ? dimensionKey(opts.series, group[0]) : undefined,
|
|
@@ -663,9 +669,10 @@ export async function overviewData(input: SnapshotsInput): Promise<OverviewData>
|
|
|
663
669
|
const cost = attemptCostUSD(result);
|
|
664
670
|
if (cost !== null) costUSD = (costUSD ?? 0) + cost;
|
|
665
671
|
}
|
|
666
|
-
//
|
|
667
|
-
//
|
|
668
|
-
|
|
672
|
+
// 默认成功率的唯一官方口径:endToEndPassRate 的两级聚合(computeCell),不是从上面四个
|
|
673
|
+
// verdict 计票现场重算——一道题内 attempt 部分通过要算部分 credit,不是二元投票;
|
|
674
|
+
// failed / errored 都记 0,只有 skipped 不进聚合。
|
|
675
|
+
const passRateCell = await computeCell(endToEndPassRate, items);
|
|
669
676
|
return {
|
|
670
677
|
snapshots: snapshots.map((s) => ({
|
|
671
678
|
experimentId: s.experimentId,
|
|
@@ -772,4 +779,3 @@ function deltaDisplay(metric: Metric, delta: number | null): string {
|
|
|
772
779
|
const text = displayValue(metric, delta); // 负号由格式化自带
|
|
773
780
|
return delta > 0 ? `+${text}` : text;
|
|
774
781
|
}
|
|
775
|
-
|