niceeval 0.4.6 → 0.5.0
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/docs-site/zh/concepts/adapter.mdx +6 -6
- package/docs-site/zh/concepts/experiment.mdx +7 -7
- package/docs-site/zh/concepts/overview.mdx +1 -1
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/guides/agent-feedback-loop.mdx +63 -20
- package/docs-site/zh/guides/connect-your-agent.mdx +7 -7
- package/docs-site/zh/guides/custom-reports.mdx +243 -0
- package/docs-site/zh/guides/experiments.mdx +3 -3
- package/docs-site/zh/guides/report-components.mdx +252 -0
- package/docs-site/zh/guides/reporters.mdx +1 -1
- package/docs-site/zh/guides/results-data.mdx +224 -0
- package/docs-site/zh/guides/runner.mdx +1 -1
- package/docs-site/zh/guides/sandbox-agent.mdx +2 -2
- package/docs-site/zh/guides/viewing-results.mdx +147 -77
- package/docs-site/zh/guides/write-experiment.mdx +9 -9
- package/docs-site/zh/guides/write-send.mdx +6 -6
- package/docs-site/zh/index.mdx +1 -1
- package/docs-site/zh/introduction.mdx +1 -1
- package/docs-site/zh/reference/cli.mdx +10 -2
- package/docs-site/zh/reference/define-agent.mdx +5 -5
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +3 -3
- package/package.json +3 -2
- package/src/agents/ai-sdk.test.ts +1 -1
- package/src/agents/ai-sdk.ts +2 -2
- package/src/agents/claude-code.ts +1 -1
- package/src/agents/codex.ts +2 -2
- package/src/agents/streaming.test.ts +1 -1
- package/src/agents/types.ts +4 -4
- package/src/agents/ui-message-stream.test.ts +1 -1
- package/src/cli.ts +104 -14
- package/src/context/context.test.ts +1 -1
- package/src/context/context.ts +3 -3
- package/src/context/session.ts +2 -2
- package/src/context/types.ts +2 -2
- package/src/i18n/en.ts +29 -7
- package/src/i18n/zh-CN.ts +24 -2
- package/src/report/aggregate.ts +79 -50
- package/src/report/components.tsx +190 -0
- package/src/report/compute.ts +149 -84
- package/src/report/default-report.tsx +222 -0
- package/src/report/dual-face.test.tsx +527 -0
- package/src/report/format.ts +29 -0
- package/src/report/index.ts +79 -24
- package/src/report/load.ts +67 -0
- package/src/report/metrics.ts +2 -1
- package/src/report/param.ts +18 -0
- package/src/report/primitives.tsx +117 -0
- package/src/report/react/CaseList.tsx +2 -2
- package/src/report/react/DeltaTable.tsx +2 -2
- package/src/report/react/MetricBars.tsx +109 -0
- package/src/report/react/MetricLine.tsx +200 -0
- package/src/report/react/MetricMatrix.tsx +1 -1
- package/src/report/react/MetricScatter.tsx +1 -1
- package/src/report/react/MetricTable.tsx +1 -1
- package/src/report/react/RunOverview.tsx +4 -4
- package/src/report/react/Scoreboard.tsx +3 -3
- package/src/report/react/cell.tsx +1 -1
- package/src/report/react/fixtures.ts +98 -36
- package/src/report/react/format.ts +3 -28
- package/src/report/react/index.tsx +41 -24
- package/src/report/react/render.test.tsx +8 -8
- package/src/report/react/styles.css +77 -0
- package/src/report/report.test.ts +248 -128
- package/src/report/report.ts +69 -0
- package/src/report/text/faces.ts +356 -0
- package/src/report/text/layout.ts +125 -0
- package/src/report/text/plot.ts +127 -0
- package/src/report/tree.ts +220 -0
- package/src/report/types.ts +75 -27
- package/src/report/web.ts +40 -0
- package/src/results/copy.ts +95 -41
- package/src/results/format.ts +12 -11
- package/src/results/index.ts +34 -17
- package/src/results/open.ts +201 -81
- package/src/results/results.test.ts +595 -191
- package/src/results/select.ts +107 -55
- package/src/results/types.ts +137 -45
- package/src/results/writer.ts +258 -0
- package/src/runner/attempt.ts +3 -3
- package/src/runner/fingerprint.ts +1 -1
- package/src/runner/reporters/artifacts.ts +38 -78
- package/src/runner/reporters/braintrust.test.ts +2 -2
- package/src/runner/reporters/braintrust.ts +3 -3
- package/src/runner/run.ts +1 -1
- package/src/runner/types.ts +18 -12
- package/src/show/compose.ts +215 -0
- package/src/show/index.ts +263 -0
- package/src/show/render.ts +456 -0
- package/src/show/show.test.ts +505 -0
- package/src/view/app/App.tsx +85 -24
- package/src/view/app/components/CostScoreChart.tsx +29 -10
- package/src/view/app/components/ExperimentTable.tsx +27 -9
- package/src/view/app/components/GroupSelector.tsx +1 -1
- package/src/view/app/i18n.ts +18 -3
- package/src/view/app/lib/attempt-route.test.ts +9 -9
- package/src/view/app/lib/attempt-route.ts +5 -5
- package/src/view/app/lib/outcome.ts +1 -3
- package/src/view/app/lib/rows.ts +95 -15
- package/src/view/app/main.tsx +18 -6
- package/src/view/app/pages/RunsPage.tsx +4 -7
- package/src/view/app/pages/TracesPage.tsx +3 -8
- package/src/view/app/types.ts +50 -6
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +20 -20
- package/src/view/data.test.ts +254 -0
- package/src/view/data.ts +336 -0
- package/src/view/index.ts +64 -10
- package/src/view/server.ts +31 -9
- package/src/view/shared/types.ts +47 -41
- package/src/view/styles.css +5 -0
- package/src/view/template.html +1 -0
- package/src/view/view-report.test.ts +308 -0
- package/docs-site/.mintignore +0 -13
- package/docs-site/AGENTS.md +0 -46
- package/docs-site/concepts/adapter.mdx +0 -287
- package/docs-site/concepts/assert.mdx +0 -243
- package/docs-site/concepts/drive.mdx +0 -118
- package/docs-site/concepts/evals.mdx +0 -108
- package/docs-site/concepts/experiment.mdx +0 -37
- package/docs-site/concepts/hitl.mdx +0 -83
- package/docs-site/concepts/judge.mdx +0 -129
- package/docs-site/concepts/overview.mdx +0 -98
- package/docs-site/concepts/tier.mdx +0 -42
- package/docs-site/docs-ref/00-index.md +0 -23
- package/docs-site/docs-ref/01-page-types.md +0 -43
- package/docs-site/docs-ref/03-style-rules.md +0 -45
- package/docs-site/docs-ref/04-code-examples.md +0 -37
- package/docs-site/docs-ref/05-dx-failure-paths.md +0 -45
- package/docs-site/docs-ref/06-checklists.md +0 -53
- package/docs-site/docs.json +0 -256
- package/docs-site/example/ai-agent-application.mdx +0 -148
- package/docs-site/example/claude-code-codex-plugin.mdx +0 -162
- package/docs-site/example/claude-code-codex-skill.mdx +0 -147
- package/docs-site/example/showcase.mdx +0 -39
- package/docs-site/example/tier1-ai-sdk-v7.mdx +0 -136
- package/docs-site/example/tier1-claude-sdk.mdx +0 -119
- package/docs-site/example/tier1-codex-sdk.mdx +0 -111
- package/docs-site/example/tier1-langgraph.mdx +0 -119
- package/docs-site/example/tier1-pi-sdk.mdx +0 -119
- package/docs-site/favicon.svg +0 -5
- package/docs-site/github-diff.css +0 -135
- package/docs-site/github-diff.js +0 -11
- package/docs-site/guides/authoring.mdx +0 -129
- package/docs-site/guides/ci-integration.mdx +0 -97
- package/docs-site/guides/connect-otel.mdx +0 -209
- package/docs-site/guides/connect-your-agent.mdx +0 -221
- package/docs-site/guides/dataset-fanout.mdx +0 -98
- package/docs-site/guides/experiments.mdx +0 -71
- package/docs-site/guides/fixtures.mdx +0 -147
- package/docs-site/guides/reporters.mdx +0 -113
- package/docs-site/guides/runner.mdx +0 -79
- package/docs-site/guides/sandbox-agent.mdx +0 -138
- package/docs-site/guides/sandbox-backends.mdx +0 -64
- package/docs-site/guides/scoring-guide.mdx +0 -92
- package/docs-site/guides/viewing-results.mdx +0 -195
- package/docs-site/guides/write-experiment.mdx +0 -81
- package/docs-site/guides/write-send.mdx +0 -347
- package/docs-site/index.mdx +0 -181
- package/docs-site/introduction.mdx +0 -141
- package/docs-site/quickstart.mdx +0 -136
- package/docs-site/reference/builtin-agents.mdx +0 -161
- package/docs-site/reference/capabilities.mdx +0 -76
- package/docs-site/reference/cli.mdx +0 -120
- package/docs-site/reference/define-agent.mdx +0 -168
- package/docs-site/reference/define-config.mdx +0 -41
- package/docs-site/reference/define-eval.mdx +0 -160
- package/docs-site/reference/events.mdx +0 -131
- package/docs-site/reference/expect.mdx +0 -112
- package/docs-site/tracker.js +0 -8
- package/src/report/react/data.ts +0 -17
- package/src/view/aggregate.ts +0 -103
- package/src/view/loader.test.ts +0 -61
- package/src/view/loader.ts +0 -257
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// 官方双面组件的装配点:web 面(./react/ 的纯 React 组件)+ text 面(./text/faces.ts)
|
|
2
|
+
// + 挂在组件上的 data 计算函数。faces 两键必填 —— 配对是结构义务,不是配对表;
|
|
3
|
+
// MetricBars.data 就是 MetricMatrix.data(同一份矩阵数据的另一种摆法),别名显式化。
|
|
4
|
+
//
|
|
5
|
+
// 官方组件在宿主里自动接上证据室:web 面的 attemptHref 缺省取 ctx.attemptHref
|
|
6
|
+
// (宿主注入的证据室深链);显式传 prop 可覆盖(嵌进自己应用时自定去处)。
|
|
7
|
+
|
|
8
|
+
import { defineComponent, isHostWebContextActive } from "./tree.ts";
|
|
9
|
+
import type { AttemptRef } from "../results/index.ts";
|
|
10
|
+
import type {
|
|
11
|
+
CaseListData,
|
|
12
|
+
DeltaData,
|
|
13
|
+
LineData,
|
|
14
|
+
MatrixData,
|
|
15
|
+
OverviewData,
|
|
16
|
+
ScatterData,
|
|
17
|
+
ScoreboardData,
|
|
18
|
+
TableData,
|
|
19
|
+
} from "./types.ts";
|
|
20
|
+
import {
|
|
21
|
+
caseListData,
|
|
22
|
+
deltaData,
|
|
23
|
+
lineData,
|
|
24
|
+
matrixData,
|
|
25
|
+
overviewData,
|
|
26
|
+
scatterData,
|
|
27
|
+
scoreboardData,
|
|
28
|
+
tableData,
|
|
29
|
+
} from "./compute.ts";
|
|
30
|
+
import {
|
|
31
|
+
barsText,
|
|
32
|
+
caseListText,
|
|
33
|
+
deltaText,
|
|
34
|
+
lineText,
|
|
35
|
+
matrixText,
|
|
36
|
+
overviewText,
|
|
37
|
+
scatterText,
|
|
38
|
+
scoreboardText,
|
|
39
|
+
tableText,
|
|
40
|
+
} from "./text/faces.ts";
|
|
41
|
+
import { RunOverview as RunOverviewWeb } from "./react/RunOverview.tsx";
|
|
42
|
+
import { MetricTable as MetricTableWeb } from "./react/MetricTable.tsx";
|
|
43
|
+
import { MetricMatrix as MetricMatrixWeb } from "./react/MetricMatrix.tsx";
|
|
44
|
+
import { MetricBars as MetricBarsWeb } from "./react/MetricBars.tsx";
|
|
45
|
+
import { Scoreboard as ScoreboardWeb } from "./react/Scoreboard.tsx";
|
|
46
|
+
import { MetricScatter as MetricScatterWeb } from "./react/MetricScatter.tsx";
|
|
47
|
+
import { MetricLine as MetricLineWeb } from "./react/MetricLine.tsx";
|
|
48
|
+
import { DeltaTable as DeltaTableWeb } from "./react/DeltaTable.tsx";
|
|
49
|
+
import { CaseList as CaseListWeb } from "./react/CaseList.tsx";
|
|
50
|
+
|
|
51
|
+
// ───────────────────────── props ─────────────────────────
|
|
52
|
+
|
|
53
|
+
export interface RunOverviewProps {
|
|
54
|
+
data: OverviewData;
|
|
55
|
+
className?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface MetricTableProps {
|
|
59
|
+
data: TableData;
|
|
60
|
+
/** 传了,格子可点、下钻去处你定;不传,宿主里走证据室深链,宿主外纯展示。 */
|
|
61
|
+
attemptHref?: (ref: AttemptRef) => string;
|
|
62
|
+
className?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface MetricMatrixProps {
|
|
66
|
+
data: MatrixData;
|
|
67
|
+
attemptHref?: (ref: AttemptRef) => string;
|
|
68
|
+
className?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ScoreboardProps {
|
|
72
|
+
data: ScoreboardData;
|
|
73
|
+
className?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface MetricScatterProps {
|
|
77
|
+
data: ScatterData;
|
|
78
|
+
/** 点一个点 → 该配置的下钻页。 */
|
|
79
|
+
pointHref?: (row: ScatterData["rows"][number]) => string;
|
|
80
|
+
className?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface MetricLineProps {
|
|
84
|
+
data: LineData;
|
|
85
|
+
pointHref?: (row: LineData["rows"][number]) => string;
|
|
86
|
+
className?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface DeltaTableProps {
|
|
90
|
+
data: DeltaData;
|
|
91
|
+
className?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface CaseListProps {
|
|
95
|
+
data: CaseListData;
|
|
96
|
+
attemptHref?: (ref: AttemptRef) => string;
|
|
97
|
+
className?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ───────────────────────── 装配 ─────────────────────────
|
|
101
|
+
|
|
102
|
+
/** 页头 KPI 条:何时跑的、几个配置、几道题、通过率、总成本;选集的警告随行显示在条内。 */
|
|
103
|
+
export const RunOverview = Object.assign(
|
|
104
|
+
defineComponent<RunOverviewProps>({
|
|
105
|
+
web: (props) => <RunOverviewWeb {...props} />,
|
|
106
|
+
text: ({ data }) => overviewText(data),
|
|
107
|
+
}),
|
|
108
|
+
{ data: overviewData },
|
|
109
|
+
);
|
|
110
|
+
RunOverview.displayName = "RunOverview";
|
|
111
|
+
|
|
112
|
+
/** 榜单:一行一个维度值、一列一个指标,回答「谁整体更好」。 */
|
|
113
|
+
export const MetricTable = Object.assign(
|
|
114
|
+
defineComponent<MetricTableProps>({
|
|
115
|
+
web: (props, ctx) => <MetricTableWeb {...props} attemptHref={props.attemptHref ?? (isHostWebContextActive() ? ctx.attemptHref : undefined)} />,
|
|
116
|
+
text: ({ data }) => tableText(data),
|
|
117
|
+
}),
|
|
118
|
+
{ data: tableData },
|
|
119
|
+
);
|
|
120
|
+
MetricTable.displayName = "MetricTable";
|
|
121
|
+
|
|
122
|
+
/** 逐题格子:行 × 列两个维度、格子里一个指标,回答「哪道题谁挂了」。 */
|
|
123
|
+
export const MetricMatrix = Object.assign(
|
|
124
|
+
defineComponent<MetricMatrixProps>({
|
|
125
|
+
web: (props, ctx) => <MetricMatrixWeb {...props} attemptHref={props.attemptHref ?? (isHostWebContextActive() ? ctx.attemptHref : undefined)} />,
|
|
126
|
+
text: ({ data }) => matrixText(data),
|
|
127
|
+
}),
|
|
128
|
+
{ data: matrixData },
|
|
129
|
+
);
|
|
130
|
+
MetricMatrix.displayName = "MetricMatrix";
|
|
131
|
+
|
|
132
|
+
/** 分组条形:同一份矩阵数据的另一种摆法;MetricBars.data 就是 MetricMatrix.data 的别名。 */
|
|
133
|
+
export const MetricBars = Object.assign(
|
|
134
|
+
defineComponent<MetricMatrixProps>({
|
|
135
|
+
web: (props, ctx) => <MetricBarsWeb {...props} attemptHref={props.attemptHref ?? (isHostWebContextActive() ? ctx.attemptHref : undefined)} />,
|
|
136
|
+
text: ({ data }) => barsText(data),
|
|
137
|
+
}),
|
|
138
|
+
{ data: matrixData },
|
|
139
|
+
);
|
|
140
|
+
MetricBars.displayName = "MetricBars";
|
|
141
|
+
|
|
142
|
+
/** 考试成绩单:总分 + 分科小计,固定分母、missing 如实报。 */
|
|
143
|
+
export const Scoreboard = Object.assign(
|
|
144
|
+
defineComponent<ScoreboardProps>({
|
|
145
|
+
web: (props) => <ScoreboardWeb {...props} />,
|
|
146
|
+
text: ({ data }) => scoreboardText(data),
|
|
147
|
+
}),
|
|
148
|
+
{ data: scoreboardData },
|
|
149
|
+
);
|
|
150
|
+
Scoreboard.displayName = "Scoreboard";
|
|
151
|
+
|
|
152
|
+
/** 质量 × 成本 frontier:每个点一个配置、两个指标各占一轴,「好」的角落恒在右上。 */
|
|
153
|
+
export const MetricScatter = Object.assign(
|
|
154
|
+
defineComponent<MetricScatterProps>({
|
|
155
|
+
web: (props) => <MetricScatterWeb {...props} />,
|
|
156
|
+
text: ({ data }, ctx) => scatterText(data, ctx),
|
|
157
|
+
}),
|
|
158
|
+
{ data: scatterData },
|
|
159
|
+
);
|
|
160
|
+
MetricScatter.displayName = "MetricScatter";
|
|
161
|
+
|
|
162
|
+
/** 趋势线:x 是 experiment 声明的 param(param()),同系列按 x 排序连线。 */
|
|
163
|
+
export const MetricLine = Object.assign(
|
|
164
|
+
defineComponent<MetricLineProps>({
|
|
165
|
+
web: (props) => <MetricLineWeb {...props} />,
|
|
166
|
+
text: ({ data }, ctx) => lineText(data, ctx),
|
|
167
|
+
}),
|
|
168
|
+
{ data: lineData },
|
|
169
|
+
);
|
|
170
|
+
MetricLine.displayName = "MetricLine";
|
|
171
|
+
|
|
172
|
+
/** 成对对比:每行一对配置、格子里 A、B、Δ 三个值,涨跌好坏由 better 判定。 */
|
|
173
|
+
export const DeltaTable = Object.assign(
|
|
174
|
+
defineComponent<DeltaTableProps>({
|
|
175
|
+
web: (props) => <DeltaTableWeb {...props} />,
|
|
176
|
+
text: ({ data }) => deltaText(data),
|
|
177
|
+
}),
|
|
178
|
+
{ data: deltaData },
|
|
179
|
+
);
|
|
180
|
+
DeltaTable.displayName = "DeltaTable";
|
|
181
|
+
|
|
182
|
+
/** 失败案例清单:榜单回答「多少」,它回答「为什么」;truncated 如实报剩余。 */
|
|
183
|
+
export const CaseList = Object.assign(
|
|
184
|
+
defineComponent<CaseListProps>({
|
|
185
|
+
web: (props, ctx) => <CaseListWeb {...props} attemptHref={props.attemptHref ?? (isHostWebContextActive() ? ctx.attemptHref : undefined)} />,
|
|
186
|
+
text: (props, ctx) => caseListText(props.data, ctx),
|
|
187
|
+
}),
|
|
188
|
+
{ data: caseListData },
|
|
189
|
+
);
|
|
190
|
+
CaseList.displayName = "CaseList";
|
package/src/report/compute.ts
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
//
|
|
2
|
-
// (终值 + 渲染提示,不含公式)
|
|
1
|
+
// 计算函数:选集 → 一份组件数据。跑在 Node 侧,产物是算好的、可序列化的普通 JSON
|
|
2
|
+
// (终值 + 渲染提示,不含公式);渲染面(web/text)只做展示。
|
|
3
|
+
//
|
|
4
|
+
// 这些函数不做顶层导出,而是挂在对应组件上(MetricTable.data / Scoreboard.data …,
|
|
5
|
+
// 见 components.tsx):配对打点即发现,泛化名不占顶层导出。
|
|
3
6
|
//
|
|
4
7
|
// 共同约定(docs/reports.md「边界与不变量」):
|
|
5
|
-
// -
|
|
8
|
+
// - 第一参收 Selection | Snapshot[];收 Selection 时 warnings 随行进 OverviewData;
|
|
9
|
+
// - 聚合前按身份键去重(dedupeAttempts;missing-startedAt 不去重、如实保留、不透出警告);
|
|
6
10
|
// - null ≠ 0:缺数据不编数,覆盖率经 samples/total 如实暴露;
|
|
7
11
|
// - core 中立:只认 Metric / Dimension 接口,不出现具体 agent 名的分支。
|
|
8
12
|
|
|
9
|
-
import type { SnapshotHandle } from "../results/types.ts";
|
|
10
13
|
import type {
|
|
11
14
|
CaseListData,
|
|
12
15
|
DeltaData,
|
|
13
|
-
|
|
16
|
+
DimensionInput,
|
|
17
|
+
LineData,
|
|
14
18
|
MatrixData,
|
|
15
19
|
Metric,
|
|
16
20
|
MetricCell,
|
|
17
21
|
OverviewData,
|
|
22
|
+
ParamRef,
|
|
18
23
|
ScatterData,
|
|
19
24
|
ScoreboardData,
|
|
20
25
|
TableData,
|
|
@@ -22,41 +27,50 @@ import type {
|
|
|
22
27
|
import {
|
|
23
28
|
applyAggregator,
|
|
24
29
|
assertUniqueMetricNames,
|
|
30
|
+
collectItems,
|
|
25
31
|
computeCell,
|
|
26
|
-
dedupeAttempts,
|
|
27
32
|
dimensionKey,
|
|
28
33
|
dimensionName,
|
|
29
34
|
displayValue,
|
|
30
35
|
evalGroupOf,
|
|
36
|
+
evalIdOf,
|
|
31
37
|
evalPrefixPredicate,
|
|
32
38
|
evaluateMetric,
|
|
33
39
|
experimentIdOf,
|
|
34
40
|
filterItems,
|
|
35
41
|
groupItems,
|
|
42
|
+
paramAxisValue,
|
|
43
|
+
resolveInput,
|
|
44
|
+
snapshotKeyOf,
|
|
36
45
|
toColumn,
|
|
37
46
|
type Item,
|
|
47
|
+
type SnapshotsInput,
|
|
38
48
|
} from "./aggregate.ts";
|
|
39
49
|
import { attemptCostUSD, examScore } from "./metrics.ts";
|
|
40
|
-
import { formatPlainNumber } from "./format.ts";
|
|
50
|
+
import { formatMetricValue, formatPlainNumber } from "./format.ts";
|
|
41
51
|
|
|
42
|
-
// ─────────────────────────
|
|
52
|
+
// ───────────────────────── MetricTable.data ─────────────────────────
|
|
43
53
|
|
|
44
|
-
export interface
|
|
45
|
-
/**
|
|
46
|
-
rows:
|
|
47
|
-
/**
|
|
48
|
-
columns:
|
|
49
|
-
/** 构建时排序,方向随 better(higher 降序,「好」的一头在上)
|
|
54
|
+
export interface TableDataOptions<M extends readonly Metric[]> {
|
|
55
|
+
/** 行维度(内置 / 自定义 / param())。 */
|
|
56
|
+
rows: DimensionInput;
|
|
57
|
+
/** 每列一个指标;列键 = metric.name 的字面量,拼错编译不过。 */
|
|
58
|
+
columns: M;
|
|
59
|
+
/** 构建时排序,方向随 better(higher 降序,「好」的一头在上);缺数据行沉底。两面同口径,预排即终排。 */
|
|
50
60
|
sort?: Metric;
|
|
51
61
|
/** eval id 前缀过滤,同 CLI 位置参数语义。 */
|
|
52
62
|
evals?: string | string[];
|
|
53
63
|
}
|
|
54
64
|
|
|
55
|
-
export async function
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
export async function tableData<const M extends readonly Metric[]>(
|
|
66
|
+
input: SnapshotsInput,
|
|
67
|
+
opts: TableDataOptions<M>,
|
|
68
|
+
): Promise<TableData<M[number]["name"]>> {
|
|
69
|
+
assertUniqueMetricNames(opts.columns, "MetricTable.data columns");
|
|
70
|
+
const { snapshots } = resolveInput(input);
|
|
71
|
+
const items = filterItems(collectItems(snapshots), opts.evals);
|
|
58
72
|
const groups = groupItems(items, opts.rows);
|
|
59
|
-
const rows:
|
|
73
|
+
const rows: { key: string; cells: Record<string, MetricCell> }[] = [];
|
|
60
74
|
const sortCells = new Map<string, MetricCell>();
|
|
61
75
|
for (const [key, group] of groups) {
|
|
62
76
|
const cells: Record<string, MetricCell> = {};
|
|
@@ -78,19 +92,26 @@ export async function table(snapshots: SnapshotHandle[], opts: TableOptions): Pr
|
|
|
78
92
|
return better === "lower" ? va - vb : vb - va;
|
|
79
93
|
});
|
|
80
94
|
}
|
|
81
|
-
return {
|
|
95
|
+
return {
|
|
96
|
+
dimension: dimensionName(opts.rows),
|
|
97
|
+
columns: opts.columns.map(toColumn),
|
|
98
|
+
rows,
|
|
99
|
+
} as TableData<M[number]["name"]>;
|
|
82
100
|
}
|
|
83
101
|
|
|
84
|
-
// ─────────────────────────
|
|
102
|
+
// ───────────────────────── MetricMatrix.data(= MetricBars.data)─────────────────────────
|
|
85
103
|
|
|
86
|
-
export interface
|
|
87
|
-
rows:
|
|
88
|
-
columns:
|
|
104
|
+
export interface MatrixDataOptions {
|
|
105
|
+
rows: DimensionInput;
|
|
106
|
+
columns: DimensionInput;
|
|
89
107
|
cell: Metric;
|
|
108
|
+
/** eval id 前缀过滤,同 CLI 位置参数语义。 */
|
|
109
|
+
evals?: string | string[];
|
|
90
110
|
}
|
|
91
111
|
|
|
92
|
-
export async function
|
|
93
|
-
const
|
|
112
|
+
export async function matrixData(input: SnapshotsInput, opts: MatrixDataOptions): Promise<MatrixData> {
|
|
113
|
+
const { snapshots } = resolveInput(input);
|
|
114
|
+
const items = filterItems(collectItems(snapshots), opts.evals);
|
|
94
115
|
// 稀疏分组:只有真有 attempt 的 (row, column) 组合成格;没有样本的格子不出现
|
|
95
116
|
const groups = new Map<string, { row: string; column: string; items: Item[] }>();
|
|
96
117
|
for (const item of items) {
|
|
@@ -113,13 +134,13 @@ export async function matrix(snapshots: SnapshotHandle[], opts: MatrixOptions):
|
|
|
113
134
|
};
|
|
114
135
|
}
|
|
115
136
|
|
|
116
|
-
// ─────────────────────────
|
|
137
|
+
// ───────────────────────── Scoreboard.data ─────────────────────────
|
|
117
138
|
|
|
118
|
-
export interface
|
|
119
|
-
/** 给谁打分(被打分的维度)。 */
|
|
120
|
-
|
|
139
|
+
export interface ScoreboardDataOptions {
|
|
140
|
+
/** 给谁打分(被打分的维度);维度槽与 MetricTable.data 统一叫 rows。 */
|
|
141
|
+
rows: DimensionInput;
|
|
121
142
|
/** 按什么分科;默认 "evalGroup"(考试里的「科目」)。 */
|
|
122
|
-
subjects?:
|
|
143
|
+
subjects?: DimensionInput;
|
|
123
144
|
/** eval id 前缀 → 每题分值;未列默认 1;前缀重叠时最长的生效。 */
|
|
124
145
|
weights?: Record<string, number>;
|
|
125
146
|
/** 折算满分;默认 100。 */
|
|
@@ -136,29 +157,31 @@ export interface ScoreboardOptions {
|
|
|
136
157
|
* 总分 = fullMarks × Σ(题得分 × 题分值) / Σ(题分值) Σ 遍历选中范围内全部题
|
|
137
158
|
* 没跑到的题挣 0 分但留在分母里,missing 如实报 —— 这是显式的考试契约,不是「null ≠ 0」的例外。
|
|
138
159
|
*/
|
|
139
|
-
export async function
|
|
140
|
-
|
|
141
|
-
opts:
|
|
160
|
+
export async function scoreboardData(
|
|
161
|
+
input: SnapshotsInput,
|
|
162
|
+
opts: ScoreboardDataOptions,
|
|
142
163
|
): Promise<ScoreboardData> {
|
|
164
|
+
const { snapshots } = resolveInput(input);
|
|
143
165
|
const fullMarks = opts.fullMarks ?? 100;
|
|
144
166
|
const scoreMetric = opts.score ?? examScore;
|
|
145
|
-
const subjectsDim:
|
|
167
|
+
const subjectsDim: DimensionInput = opts.subjects ?? "evalGroup";
|
|
146
168
|
const match = evalPrefixPredicate(opts.evals);
|
|
147
|
-
const items = filterItems(
|
|
169
|
+
const items = filterItems(collectItems(snapshots), opts.evals);
|
|
148
170
|
|
|
149
171
|
// 题集(固定分母):选中范围内、任一快照声明覆盖或实际出现过的全部题
|
|
150
172
|
const universe = new Set<string>();
|
|
151
173
|
for (const snapshot of snapshots) {
|
|
152
|
-
for (const
|
|
174
|
+
for (const e of snapshot.evals) if (match(e.id)) universe.add(e.id);
|
|
175
|
+
for (const id of snapshot.knownEvalIds ?? []) if (match(id)) universe.add(id);
|
|
153
176
|
}
|
|
154
|
-
for (const item of items) universe.add(item
|
|
177
|
+
for (const item of items) universe.add(evalIdOf(item));
|
|
155
178
|
const sortedUniverse = [...universe].sort();
|
|
156
179
|
|
|
157
180
|
// 每题的科目:先从任一 attempt 解析(自定义 subjects 维度也能算);
|
|
158
181
|
// 全程无 attempt 的题按内置规则兜底,自定义维度无从计算时如实标 "(unknown)"
|
|
159
182
|
const subjectByEval = new Map<string, string>();
|
|
160
183
|
for (const item of items) {
|
|
161
|
-
const id = item
|
|
184
|
+
const id = evalIdOf(item);
|
|
162
185
|
if (!subjectByEval.has(id)) subjectByEval.set(id, dimensionKey(subjectsDim, item));
|
|
163
186
|
}
|
|
164
187
|
const subjectOf = (id: string): string => {
|
|
@@ -173,20 +196,18 @@ export async function scoreboard(
|
|
|
173
196
|
const weights = Object.entries(opts.weights ?? {})
|
|
174
197
|
.map(([prefix, weight]) => ({ prefix, weight }))
|
|
175
198
|
.sort((a, b) => b.prefix.length - a.prefix.length);
|
|
176
|
-
const weightOf = (id: string): number =>
|
|
177
|
-
weights.find((w) => id.startsWith(w.prefix))?.weight ?? 1;
|
|
199
|
+
const weightOf = (id: string): number => weights.find((w) => id.startsWith(w.prefix))?.weight ?? 1;
|
|
178
200
|
|
|
179
|
-
const groups = groupItems(items, opts.
|
|
201
|
+
const groups = groupItems(items, opts.rows);
|
|
180
202
|
const rows: ScoreboardData["rows"] = [];
|
|
181
203
|
for (const [key, group] of groups) {
|
|
182
|
-
// 题得分:perEval 折叠(同 eval × 快照 内)
|
|
183
|
-
|
|
184
|
-
const perSnapshot = new Map<string, Map<string, number[]>>(); // evalId → snapshot 身份 → 原始值
|
|
204
|
+
// 题得分:perEval 折叠(同 eval × 快照 内);同题出现在多个快照时取快照级值的均值
|
|
205
|
+
const perSnapshot = new Map<string, Map<string, number[]>>(); // evalId → 快照键 → 原始值
|
|
185
206
|
for (const item of group) {
|
|
186
207
|
const value = await evaluateMetric(scoreMetric, item.attempt);
|
|
187
208
|
if (value === null) continue; // 测不了的 attempt 不进题得分;整题无样本 → missing
|
|
188
|
-
const id = item
|
|
189
|
-
const snapKey =
|
|
209
|
+
const id = evalIdOf(item);
|
|
210
|
+
const snapKey = snapshotKeyOf(item.snapshot);
|
|
190
211
|
let bySnap = perSnapshot.get(id);
|
|
191
212
|
if (!bySnap) perSnapshot.set(id, (bySnap = new Map()));
|
|
192
213
|
const bucket = bySnap.get(snapKey);
|
|
@@ -208,7 +229,9 @@ export async function scoreboard(
|
|
|
208
229
|
for (const id of sortedUniverse) {
|
|
209
230
|
const subjectKey = subjectOf(id);
|
|
210
231
|
let subject = subjects.get(subjectKey);
|
|
211
|
-
if (!subject)
|
|
232
|
+
if (!subject) {
|
|
233
|
+
subjects.set(subjectKey, (subject = { key: subjectKey, earned: 0, possible: 0, evals: 0, missing: 0 }));
|
|
234
|
+
}
|
|
212
235
|
const weight = weightOf(id);
|
|
213
236
|
const got = scoreByEval.get(id);
|
|
214
237
|
subject.earned += (got ?? 0) * weight;
|
|
@@ -226,22 +249,23 @@ export async function scoreboard(
|
|
|
226
249
|
rows.push({ key, total: { value, display: formatPlainNumber(value) }, subjects: [...subjects.values()] });
|
|
227
250
|
}
|
|
228
251
|
|
|
229
|
-
return {
|
|
252
|
+
return { dimension: dimensionName(opts.rows), fullMarks, weights, rows };
|
|
230
253
|
}
|
|
231
254
|
|
|
232
|
-
// ─────────────────────────
|
|
255
|
+
// ───────────────────────── MetricScatter.data ─────────────────────────
|
|
233
256
|
|
|
234
|
-
export interface
|
|
257
|
+
export interface ScatterDataOptions {
|
|
235
258
|
/** 点维度:每个点 = 该组 attempt 的聚合。 */
|
|
236
|
-
points:
|
|
259
|
+
points: DimensionInput;
|
|
237
260
|
/** 可选:同系列的点连成线;省略 = 纯散点。 */
|
|
238
|
-
series?:
|
|
261
|
+
series?: DimensionInput;
|
|
239
262
|
x: Metric;
|
|
240
263
|
y: Metric;
|
|
241
264
|
}
|
|
242
265
|
|
|
243
|
-
export async function
|
|
244
|
-
const
|
|
266
|
+
export async function scatterData(input: SnapshotsInput, opts: ScatterDataOptions): Promise<ScatterData> {
|
|
267
|
+
const { snapshots } = resolveInput(input);
|
|
268
|
+
const items = collectItems(snapshots);
|
|
245
269
|
const groups = groupItems(items, opts.points);
|
|
246
270
|
const rows: ScatterData["rows"] = [];
|
|
247
271
|
for (const [key, group] of groups) {
|
|
@@ -262,18 +286,50 @@ export async function scatter(snapshots: SnapshotHandle[], opts: ScatterOptions)
|
|
|
262
286
|
};
|
|
263
287
|
}
|
|
264
288
|
|
|
265
|
-
// ─────────────────────────
|
|
289
|
+
// ───────────────────────── MetricLine.data ─────────────────────────
|
|
266
290
|
|
|
267
|
-
export interface
|
|
268
|
-
/**
|
|
269
|
-
|
|
291
|
+
export interface LineDataOptions {
|
|
292
|
+
/** x 轴:experiment 声明的 param(数值),不解析 experiment 命名。 */
|
|
293
|
+
x: ParamRef;
|
|
294
|
+
y: Metric;
|
|
295
|
+
/** 可选:每个系列一条线(param 或普通维度);省略 = 单系列。 */
|
|
296
|
+
series?: DimensionInput;
|
|
270
297
|
}
|
|
271
298
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const
|
|
299
|
+
/** 每个点 = 一个 experiment 的聚合;同系列的点按 x 排序连线(排序在组件面,数据保持分组序)。 */
|
|
300
|
+
export async function lineData(input: SnapshotsInput, opts: LineDataOptions): Promise<LineData> {
|
|
301
|
+
const { snapshots } = resolveInput(input);
|
|
302
|
+
const items = collectItems(snapshots);
|
|
303
|
+
const groups = groupItems(items, "experiment");
|
|
304
|
+
const rows: LineData["rows"] = [];
|
|
305
|
+
for (const [key, group] of groups) {
|
|
306
|
+
const x = paramAxisValue(opts.x, group[0]); // param 是 experiment 级声明,组内一致
|
|
307
|
+
rows.push({
|
|
308
|
+
key,
|
|
309
|
+
series: opts.series ? dimensionKey(opts.series, group[0]) : undefined,
|
|
310
|
+
x,
|
|
311
|
+
xDisplay: x === null ? "" : formatMetricValue(x, opts.x.unit),
|
|
312
|
+
y: await computeCell(opts.y, group),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
x: {
|
|
317
|
+
key: opts.x.name,
|
|
318
|
+
label: typeof opts.x.label === "string" ? opts.x.label : opts.x.name,
|
|
319
|
+
unit: opts.x.unit,
|
|
320
|
+
},
|
|
321
|
+
series: opts.series ? dimensionName(opts.series) : undefined,
|
|
322
|
+
y: toColumn(opts.y),
|
|
323
|
+
rows,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ───────────────────────── RunOverview.data ─────────────────────────
|
|
328
|
+
|
|
329
|
+
/** 选集的 warnings 随行进 OverviewData,RunOverview 直接渲染 —— 诚实不靠使用者记得接线。 */
|
|
330
|
+
export async function overviewData(input: SnapshotsInput): Promise<OverviewData> {
|
|
331
|
+
const { snapshots, warnings } = resolveInput(input);
|
|
332
|
+
const items = collectItems(snapshots);
|
|
277
333
|
const evalIds = new Set<string>();
|
|
278
334
|
let passed = 0;
|
|
279
335
|
let failed = 0;
|
|
@@ -281,9 +337,9 @@ export async function overview(
|
|
|
281
337
|
let skipped = 0;
|
|
282
338
|
let durationMs = 0;
|
|
283
339
|
let costUSD: number | null = null; // 任一 attempt 报了成本才有;全缺 = null,不编 0
|
|
284
|
-
for (const
|
|
285
|
-
const result = attempt.result;
|
|
286
|
-
evalIds.add(
|
|
340
|
+
for (const item of items) {
|
|
341
|
+
const result = item.attempt.result;
|
|
342
|
+
evalIds.add(evalIdOf(item));
|
|
287
343
|
switch (result.outcome) {
|
|
288
344
|
case "passed":
|
|
289
345
|
passed += 1;
|
|
@@ -310,34 +366,41 @@ export async function overview(
|
|
|
310
366
|
startedAt: s.startedAt,
|
|
311
367
|
})),
|
|
312
368
|
totals: { evals: evalIds.size, attempts: items.length, passed, failed, errored, skipped, costUSD, durationMs },
|
|
313
|
-
warnings: [...
|
|
369
|
+
warnings: [...warnings],
|
|
314
370
|
};
|
|
315
371
|
}
|
|
316
372
|
|
|
317
|
-
// ─────────────────────────
|
|
373
|
+
// ───────────────────────── DeltaTable.data ─────────────────────────
|
|
318
374
|
|
|
319
375
|
export interface DeltaPair {
|
|
320
|
-
/**
|
|
376
|
+
/** 基线侧:experiment id,或快照键 "<experimentId> @ <startedAt>"(时间轴对比用手挑的快照数组)。 */
|
|
321
377
|
a: string;
|
|
322
|
-
/**
|
|
378
|
+
/** 对比侧,同上。 */
|
|
323
379
|
b: string;
|
|
324
380
|
label?: string;
|
|
325
381
|
}
|
|
326
382
|
|
|
327
|
-
export interface
|
|
383
|
+
export interface DeltaDataOptions<M extends readonly Metric[]> {
|
|
328
384
|
/** 每行一对:B 相对 A。 */
|
|
329
385
|
pairs: DeltaPair[];
|
|
330
|
-
metrics:
|
|
386
|
+
metrics: M;
|
|
331
387
|
}
|
|
332
388
|
|
|
333
|
-
export async function
|
|
334
|
-
|
|
335
|
-
|
|
389
|
+
export async function deltaData<const M extends readonly Metric[]>(
|
|
390
|
+
input: SnapshotsInput,
|
|
391
|
+
opts: DeltaDataOptions<M>,
|
|
392
|
+
): Promise<DeltaData<M[number]["name"]>> {
|
|
393
|
+
assertUniqueMetricNames(opts.metrics, "DeltaTable.data metrics");
|
|
394
|
+
const { snapshots } = resolveInput(input);
|
|
395
|
+
const items = collectItems(snapshots);
|
|
396
|
+
// 一侧的键既匹配 experiment id 也匹配快照键 —— 与 "snapshot" 维度同一格式,不另造对比语义
|
|
397
|
+
const sideItems = (key: string) =>
|
|
398
|
+
items.filter((item) => experimentIdOf(item) === key || snapshotKeyOf(item.snapshot) === key);
|
|
336
399
|
const rows: DeltaData["rows"] = [];
|
|
337
400
|
for (const pair of opts.pairs) {
|
|
338
|
-
const aItems =
|
|
339
|
-
const bItems =
|
|
340
|
-
const cells: DeltaData["rows"][number]["cells"] = {};
|
|
401
|
+
const aItems = sideItems(pair.a);
|
|
402
|
+
const bItems = sideItems(pair.b);
|
|
403
|
+
const cells: Record<string, DeltaData["rows"][number]["cells"][string]> = {};
|
|
341
404
|
for (const metric of opts.metrics) {
|
|
342
405
|
const a = await computeCell(metric, aItems);
|
|
343
406
|
const b = await computeCell(metric, bItems);
|
|
@@ -351,18 +414,19 @@ export async function delta(snapshots: SnapshotHandle[], opts: DeltaOptions): Pr
|
|
|
351
414
|
cells,
|
|
352
415
|
});
|
|
353
416
|
}
|
|
354
|
-
return { columns: opts.metrics.map(toColumn), rows }
|
|
417
|
+
return { columns: opts.metrics.map(toColumn), rows } as DeltaData<M[number]["name"]>;
|
|
355
418
|
}
|
|
356
419
|
|
|
357
420
|
function deltaDisplay(metric: Metric, delta: number | null): string {
|
|
358
421
|
if (delta === null) return "—"; // 任一侧缺数据:Δ 显示为缺,不硬算
|
|
422
|
+
if (delta === 0) return "±0";
|
|
359
423
|
const text = displayValue(metric, delta); // 负号由格式化自带
|
|
360
424
|
return delta > 0 ? `+${text}` : text;
|
|
361
425
|
}
|
|
362
426
|
|
|
363
|
-
// ─────────────────────────
|
|
427
|
+
// ───────────────────────── CaseList.data ─────────────────────────
|
|
364
428
|
|
|
365
|
-
export interface
|
|
429
|
+
export interface CaseListDataOptions {
|
|
366
430
|
/** 要列出的判决;默认 failed + errored。 */
|
|
367
431
|
outcomes?: ("failed" | "errored")[];
|
|
368
432
|
/** 超出如实报 truncated,不静默截断。 */
|
|
@@ -371,10 +435,11 @@ export interface CasesOptions {
|
|
|
371
435
|
redact?: (text: string) => string;
|
|
372
436
|
}
|
|
373
437
|
|
|
374
|
-
export async function
|
|
438
|
+
export async function caseListData(input: SnapshotsInput, opts?: CaseListDataOptions): Promise<CaseListData> {
|
|
439
|
+
const { snapshots } = resolveInput(input);
|
|
375
440
|
const wanted = new Set<"failed" | "errored">(opts?.outcomes ?? ["failed", "errored"]);
|
|
376
441
|
const redact = opts?.redact ?? ((text: string) => text);
|
|
377
|
-
const selected =
|
|
442
|
+
const selected = collectItems(snapshots).filter((item) => {
|
|
378
443
|
const outcome = item.attempt.result.outcome;
|
|
379
444
|
return (outcome === "failed" || outcome === "errored") && wanted.has(outcome);
|
|
380
445
|
});
|
|
@@ -383,7 +448,7 @@ export async function cases(snapshots: SnapshotHandle[], opts?: CasesOptions): P
|
|
|
383
448
|
const result = item.attempt.result;
|
|
384
449
|
const cost = attemptCostUSD(result);
|
|
385
450
|
return {
|
|
386
|
-
eval:
|
|
451
|
+
eval: evalIdOf(item),
|
|
387
452
|
experimentId: experimentIdOf(item),
|
|
388
453
|
agent: result.agent,
|
|
389
454
|
outcome: result.outcome as "failed" | "errored",
|