niceeval 0.4.5 → 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/bub.ts +14 -4
- 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
package/src/results/select.ts
CHANGED
|
@@ -1,75 +1,115 @@
|
|
|
1
|
-
//
|
|
1
|
+
// 快照选集与 attempt 去重(定稿见 docs/results-lib.md「选择快照」「身份键与去重」)。
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// 选择器只有一个(latest),长在集合上;它不是 DSL,只是最常用的那次筛选。
|
|
4
|
+
// 选择器必须诚实:残缺、落后、合成键都被算出来,以结构化 warnings 随选集走 ——
|
|
5
|
+
// 渲染与否在消费方(message 是渲染好的英文句子),但缺口不静默。
|
|
6
6
|
|
|
7
7
|
import { experimentKeyOf } from "./format.ts";
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
AttemptHandle,
|
|
10
|
+
DedupeWarning,
|
|
11
|
+
Experiment,
|
|
12
|
+
RunDir,
|
|
13
|
+
Selection,
|
|
14
|
+
SelectionWarning,
|
|
15
|
+
Snapshot,
|
|
16
|
+
} from "./types.ts";
|
|
9
17
|
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* "compare/..." 子级;允许写成 "compare/",等价)。
|
|
14
|
-
*/
|
|
15
|
-
export function latestPerExperiment(
|
|
16
|
-
snapshots: SnapshotHandle[],
|
|
18
|
+
/** Results.latest() 的实现:每个实验取最新一次快照(= exp.snapshots[0]),生成挑选警告。 */
|
|
19
|
+
export function selectLatest(
|
|
20
|
+
experiments: Experiment[],
|
|
17
21
|
opts?: { experiments?: string | string[] },
|
|
18
|
-
):
|
|
19
|
-
const
|
|
22
|
+
): Selection {
|
|
23
|
+
const selected = filterByExperimentPrefix(experiments, opts?.experiments);
|
|
24
|
+
const snapshots = selected.map((exp) => exp.latest);
|
|
25
|
+
const warnings: SelectionWarning[] = [];
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (
|
|
25
|
-
else byExperiment.set(snapshot.experimentId, [snapshot]);
|
|
27
|
+
// stale 的基准:选集中最新的落盘(无阈值,如实触发;要阈值消费方按字段自比)。
|
|
28
|
+
let latestStartedAt = "";
|
|
29
|
+
for (const snapshot of snapshots) {
|
|
30
|
+
if (snapshot.startedAt > latestStartedAt) latestStartedAt = snapshot.startedAt;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
let latest = group[0];
|
|
32
|
-
for (const candidate of group) {
|
|
33
|
-
if (isNewerRun(candidate.run, latest.run)) latest = candidate;
|
|
34
|
-
}
|
|
35
|
-
// 残缺检测:与该 experiment 全部历史快照的 evalId 并集对比。
|
|
33
|
+
for (const exp of selected) {
|
|
34
|
+
const snapshot = exp.latest;
|
|
35
|
+
// 残缺检测:分母 = 该实验已知 eval 并集(本地历史 ∪ 各快照携带的 knownEvalIds)。
|
|
36
36
|
// 位置参数允许只重跑一道题,产出的「最新快照」可能只有一道题 —— 不能安静吞下。
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
if (
|
|
37
|
+
const covered = snapshot.evals.length;
|
|
38
|
+
const total = exp.evalIds.length;
|
|
39
|
+
if (covered < total) {
|
|
40
40
|
// 合成键不是真实 experiment id,拼不出可执行的 `niceeval exp` 命令,提示退化成中性说法。
|
|
41
|
-
const hint =
|
|
42
|
-
? "
|
|
43
|
-
: `
|
|
44
|
-
warnings.push(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
const hint = snapshot.synthetic
|
|
42
|
+
? "re-run the experiment for a full snapshot"
|
|
43
|
+
: `re-run \`niceeval exp ${exp.id}\` for a full snapshot`;
|
|
44
|
+
warnings.push({
|
|
45
|
+
kind: "partial-coverage",
|
|
46
|
+
experimentId: exp.id,
|
|
47
|
+
covered,
|
|
48
|
+
total,
|
|
49
|
+
message: `snapshot covers ${covered} of ${total} evals seen in history; ${hint}`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (snapshot.startedAt < latestStartedAt) {
|
|
53
|
+
warnings.push({
|
|
54
|
+
kind: "stale-snapshot",
|
|
55
|
+
experimentId: exp.id,
|
|
56
|
+
startedAt: snapshot.startedAt,
|
|
57
|
+
latestStartedAt,
|
|
58
|
+
message: `snapshot "${exp.id}" (${snapshot.startedAt}) predates the latest run in this selection by ${humanizeGap(snapshot.startedAt, latestStartedAt)}`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (snapshot.synthetic) {
|
|
62
|
+
warnings.push({
|
|
63
|
+
kind: "synthetic-experiment-id",
|
|
64
|
+
experimentId: exp.id,
|
|
65
|
+
runDir: snapshot.runDir.dir,
|
|
66
|
+
message: `run "${snapshot.runDir.dir}" has results without experimentId; grouped as "${exp.id}" by agent/model`,
|
|
67
|
+
});
|
|
48
68
|
}
|
|
49
|
-
picked.push(latest);
|
|
50
69
|
}
|
|
70
|
+
return makeSelection(snapshots, warnings);
|
|
71
|
+
}
|
|
51
72
|
|
|
52
|
-
|
|
53
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Selection 构造:filter 只删不换 —— 快照删减,warnings 修剪规则是
|
|
75
|
+
* 「experimentId 不在幸存快照中的丢弃,非实验作用域的保留」(为将来非 per-experiment 的 kind 留位置)。
|
|
76
|
+
*/
|
|
77
|
+
export function makeSelection(snapshots: Snapshot[], warnings: SelectionWarning[]): Selection {
|
|
78
|
+
return {
|
|
79
|
+
snapshots,
|
|
80
|
+
warnings,
|
|
81
|
+
filter(predicate: (snapshot: Snapshot) => boolean): Selection {
|
|
82
|
+
const kept = snapshots.filter(predicate);
|
|
83
|
+
const survivors = new Set(kept.map((s) => s.experimentId));
|
|
84
|
+
const keptWarnings = warnings.filter((w) => {
|
|
85
|
+
const scope = (w as { experimentId?: unknown }).experimentId;
|
|
86
|
+
return typeof scope !== "string" || survivors.has(scope);
|
|
87
|
+
});
|
|
88
|
+
return makeSelection(kept, keptWarnings);
|
|
89
|
+
},
|
|
90
|
+
};
|
|
54
91
|
}
|
|
55
92
|
|
|
56
93
|
/**
|
|
57
94
|
* 跨快照聚合前的身份键去重:(experimentId, evalId, attempt, startedAt)。
|
|
58
95
|
* --resume 会把上一轮已通过的结果原样合入新 run 的 summary,同一 attempt 因此存在于多份落盘;
|
|
59
|
-
* 重复时保留最新 run
|
|
60
|
-
* startedAt
|
|
96
|
+
* 重复时保留最新 run 目录里的那份(内容相同,取新 run 的副本让 ref 落在最新落盘上;
|
|
97
|
+
* 位置取首次出现处,顺序稳定)。startedAt 缺失时宁可不去重也不误删,记入 warnings。
|
|
61
98
|
*/
|
|
62
|
-
export function dedupeAttempts(attempts: AttemptHandle[]): { attempts: AttemptHandle[]; warnings:
|
|
99
|
+
export function dedupeAttempts(attempts: AttemptHandle[]): { attempts: AttemptHandle[]; warnings: DedupeWarning[] } {
|
|
63
100
|
const deduped: AttemptHandle[] = [];
|
|
64
101
|
const indexByKey = new Map<string, number>();
|
|
65
|
-
const warnings:
|
|
102
|
+
const warnings: DedupeWarning[] = [];
|
|
66
103
|
|
|
67
104
|
for (const attempt of attempts) {
|
|
68
105
|
const r = attempt.result;
|
|
69
106
|
if (!r.startedAt) {
|
|
70
|
-
warnings.push(
|
|
71
|
-
|
|
72
|
-
|
|
107
|
+
warnings.push({
|
|
108
|
+
kind: "missing-startedAt",
|
|
109
|
+
experimentId: attempt.experimentId,
|
|
110
|
+
evalId: attempt.evalId,
|
|
111
|
+
message: `attempt ${r.attempt} of eval "${attempt.evalId}" in experiment "${attempt.experimentId}" has no startedAt; kept as-is without dedupe`,
|
|
112
|
+
});
|
|
73
113
|
deduped.push(attempt);
|
|
74
114
|
continue;
|
|
75
115
|
}
|
|
@@ -78,7 +118,7 @@ export function dedupeAttempts(attempts: AttemptHandle[]): { attempts: AttemptHa
|
|
|
78
118
|
if (existing === undefined) {
|
|
79
119
|
indexByKey.set(key, deduped.length);
|
|
80
120
|
deduped.push(attempt);
|
|
81
|
-
} else if (
|
|
121
|
+
} else if (isNewerRunDir(attempt.runDir, deduped[existing].runDir)) {
|
|
82
122
|
deduped[existing] = attempt;
|
|
83
123
|
}
|
|
84
124
|
}
|
|
@@ -86,16 +126,28 @@ export function dedupeAttempts(attempts: AttemptHandle[]): { attempts: AttemptHa
|
|
|
86
126
|
}
|
|
87
127
|
|
|
88
128
|
/** run 新旧比较:startedAt 优先,同刻按目录名(时间戳目录,字典序即时序)。 */
|
|
89
|
-
export function
|
|
129
|
+
export function isNewerRunDir(a: RunDir, b: RunDir): boolean {
|
|
90
130
|
const byStart = a.summary.startedAt.localeCompare(b.summary.startedAt);
|
|
91
131
|
if (byStart !== 0) return byStart > 0;
|
|
92
132
|
return a.dir.localeCompare(b.dir) > 0;
|
|
93
133
|
}
|
|
94
134
|
|
|
95
|
-
function
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
135
|
+
function filterByExperimentPrefix(experiments: Experiment[], filter?: string | string[]): Experiment[] {
|
|
136
|
+
if (filter === undefined) return experiments;
|
|
137
|
+
// 允许 "compare/" 这种带尾斜杠的写法,与 "compare" 等价;分段匹配不误配 "compare2"。
|
|
138
|
+
const prefixes = (Array.isArray(filter) ? filter : [filter]).map((p) => p.replace(/\/+$/, ""));
|
|
139
|
+
return experiments.filter((exp) => prefixes.some((p) => exp.id === p || exp.id.startsWith(p + "/")));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** stale 警告的人话时距:选粒度最大的单位,四舍五入。 */
|
|
143
|
+
function humanizeGap(fromIso: string, toIso: string): string {
|
|
144
|
+
const ms = Math.max(0, Date.parse(toIso) - Date.parse(fromIso));
|
|
145
|
+
const seconds = Math.round(ms / 1000);
|
|
146
|
+
if (seconds < 90) return `${seconds} second${seconds === 1 ? "" : "s"}`;
|
|
147
|
+
const minutes = Math.round(seconds / 60);
|
|
148
|
+
if (minutes < 90) return `${minutes} minute${minutes === 1 ? "" : "s"}`;
|
|
149
|
+
const hours = Math.round(minutes / 60);
|
|
150
|
+
if (hours < 36) return `${hours} hour${hours === 1 ? "" : "s"}`;
|
|
151
|
+
const days = Math.round(hours / 24);
|
|
152
|
+
return `${days} day${days === 1 ? "" : "s"}`;
|
|
101
153
|
}
|
package/src/results/types.ts
CHANGED
|
@@ -1,47 +1,56 @@
|
|
|
1
|
-
// results 域类型:openResults
|
|
1
|
+
// results 域类型:openResults 的分层读取契约与选集(定稿见 docs/results-lib.md)。
|
|
2
2
|
//
|
|
3
3
|
// 结果数据类型(EvalResult / RunSummary / StreamEvent / …)仍住在各自的域文件里,
|
|
4
|
-
// 这里只 import,不搬家 ——
|
|
4
|
+
// 这里只 import,不搬家 —— 「类型的家」迁移(facade 反向 re-export)是下一波,不在本次范围。
|
|
5
|
+
//
|
|
6
|
+
// 命名约定:Experiment / Snapshot / Eval / RunDir 是纯数据,不带 Handle 后缀;
|
|
7
|
+
// 唯一叫 AttemptHandle 的是 attempt —— 它的方法真的会碰磁盘,后缀标记的就是这件事。
|
|
5
8
|
|
|
6
|
-
import type { EvalResult, RunSummary } from "../
|
|
7
|
-
import type { O11ySummary, StreamEvent, TraceSpan } from "../
|
|
8
|
-
import type { DiffData } from "../
|
|
9
|
-
import type { SourceArtifact } from "../shared/types.ts";
|
|
9
|
+
import type { EvalResult, RunSummary } from "../types.ts";
|
|
10
|
+
import type { O11ySummary, StreamEvent, TraceSpan } from "../types.ts";
|
|
11
|
+
import type { DiffData, SourceArtifact } from "../types.ts";
|
|
10
12
|
|
|
11
13
|
/** attempt 级工件的种类;文件名恒为 `<kind>.json`,布局见 docs/results-format.md。 */
|
|
12
|
-
export const ARTIFACT_KINDS = ["events", "
|
|
14
|
+
export const ARTIFACT_KINDS = ["events", "trace", "o11y", "diff", "sources"] as const;
|
|
13
15
|
export type ArtifactKind = (typeof ARTIFACT_KINDS)[number];
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* 回到证据的引用:run 目录名(相对结果根目录)+ summary.results[] 下标。
|
|
17
|
-
*
|
|
19
|
+
* 字段名(run / result)是 view 深链 `#/attempt/<run>/<result>` 的持久化路由契约,不随句柄改名;
|
|
20
|
+
* Reports 的 MetricCell.refs 用的是同一个身份。
|
|
18
21
|
*/
|
|
19
22
|
export interface AttemptRef {
|
|
20
23
|
run: string;
|
|
21
24
|
result: number;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
/**
|
|
25
|
-
export interface
|
|
27
|
+
/** 一个物理落盘 run 目录(低层忠实磁盘面);多数消费方走 experiments,不碰它。 */
|
|
28
|
+
export interface RunDir {
|
|
26
29
|
/** run 目录的绝对路径(summary.json 所在目录)。 */
|
|
27
30
|
dir: string;
|
|
28
31
|
/** 与写入侧同一类型的 RunSummary(results[] 是瘦身条目)。 */
|
|
29
32
|
summary: RunSummary;
|
|
30
|
-
/** summary.results[]
|
|
33
|
+
/** summary.results[] 逐条包一层懒加载句柄(按下标顺序)。 */
|
|
31
34
|
attempts: AttemptHandle[];
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
/**
|
|
35
38
|
* 单个 eval attempt:瘦身条目 + 重工件的懒加载方法。
|
|
36
|
-
* 懒加载即存在性判断:工件缺失返回 null,不抛错;同一 handle
|
|
39
|
+
* 懒加载即存在性判断:工件缺失返回 null,不抛错;同一 handle 内读过一次即记忆化。
|
|
40
|
+
* 工件定位按候选顺序回退:先本 run 的 artifactsDir,再 artifactBase 指向的原 run 目录
|
|
41
|
+
* (--resume 合入条目的工件留在原 run 里);原 run 被清理后如实返回 null。
|
|
37
42
|
*/
|
|
38
43
|
export interface AttemptHandle {
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
|
|
44
|
+
/** 属于哪道题 —— 直达字段,不绕 result。 */
|
|
45
|
+
evalId: string;
|
|
46
|
+
/** 属于哪个实验;落盘缺 experimentId 时是 "<agent>/<model>" 合成键(所属快照 synthetic: true)。 */
|
|
47
|
+
experimentId: string;
|
|
43
48
|
/** EvalResult 瘦身条目:判决、断言、用量、成本、experiment 元数据。 */
|
|
44
49
|
result: EvalResult;
|
|
50
|
+
/** 证据引用,指条目所在的落盘(合入后的新 run);工件经候选回退仍可达。 */
|
|
51
|
+
ref: AttemptRef;
|
|
52
|
+
/** 条目所在的物理落盘;去重「保留最新 run 目录里的那份」靠它比较新旧。 */
|
|
53
|
+
runDir: RunDir;
|
|
45
54
|
events(): Promise<StreamEvent[] | null>;
|
|
46
55
|
trace(): Promise<TraceSpan[] | null>;
|
|
47
56
|
o11y(): Promise<O11ySummary | null>;
|
|
@@ -49,50 +58,133 @@ export interface AttemptHandle {
|
|
|
49
58
|
sources(): Promise<SourceArtifact[] | null>;
|
|
50
59
|
}
|
|
51
60
|
|
|
61
|
+
/** 一道题在一个快照里的结果:id + 该题的全部 attempt(重试历史)。 */
|
|
62
|
+
export interface Eval {
|
|
63
|
+
id: string;
|
|
64
|
+
attempts: AttemptHandle[];
|
|
65
|
+
}
|
|
66
|
+
|
|
52
67
|
/**
|
|
53
|
-
* 快照 =
|
|
54
|
-
*
|
|
55
|
-
* 最新一次」只能用快照粒度表达,不能用 run 粒度。
|
|
68
|
+
* 快照 = 单次跑的实验(experiment × run 切片)。一个 run 目录可以装多个 experiment
|
|
69
|
+
* (`niceeval exp compare`),所以「每个 experiment 最新一次」只能用快照粒度表达。
|
|
56
70
|
*/
|
|
57
|
-
export interface
|
|
58
|
-
/**
|
|
71
|
+
export interface Snapshot {
|
|
72
|
+
/** 结果里缺 experimentId 时以 "<agent>/<model>" 合成键,并经 latest() 记入 warnings。 */
|
|
59
73
|
experimentId: string;
|
|
60
|
-
/** 所属物理 run。 */
|
|
61
|
-
run: RunHandle;
|
|
62
|
-
/** = run.summary.startedAt;快照新旧按它比较。 */
|
|
63
74
|
startedAt: string;
|
|
64
|
-
/** 本快照自己的 agent ——
|
|
75
|
+
/** 本快照自己的 agent —— 不是落盘顶层那个「第一个配置」。 */
|
|
65
76
|
agent: string;
|
|
66
77
|
model?: string;
|
|
78
|
+
/** 谁写的这份结果(niceeval 或第三方 harness;legacy 结果可能缺失)。 */
|
|
79
|
+
producer?: RunSummary["producer"];
|
|
80
|
+
/** 结果格式版本(能读进来的恒为当前版本;不兼容的在 skipped),缺失按 1。 */
|
|
81
|
+
schemaVersion: number;
|
|
82
|
+
/** 每道题一项:{ id, attempts };残缺检测 / 逐题遍历从这里走。 */
|
|
83
|
+
evals: Eval[];
|
|
84
|
+
/** 全部 attempt 平铺(= evals 逐题展开),不关心题目边界的聚合消费用。 */
|
|
67
85
|
attempts: AttemptHandle[];
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
-
/** experimentId 是合成键(结果里没有 experimentId)时为 true
|
|
86
|
+
/** 所属物理落盘(低层面)。 */
|
|
87
|
+
runDir: RunDir;
|
|
88
|
+
/** experimentId 是合成键(结果里没有 experimentId)时为 true。 */
|
|
71
89
|
synthetic?: boolean;
|
|
90
|
+
/** 写入时刻该实验已知的 eval 并集(可选);copySnapshots 自动补记,writer.snapshot() 也可声明。 */
|
|
91
|
+
knownEvalIds?: string[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** 一个实验的全部历史:同一 experiment id 的历次快照归在一起。 */
|
|
95
|
+
export interface Experiment {
|
|
96
|
+
id: string;
|
|
97
|
+
/** 历次快照,最新在前。 */
|
|
98
|
+
snapshots: Snapshot[];
|
|
99
|
+
/** 最新一次(= snapshots[0])。 */
|
|
100
|
+
latest: Snapshot;
|
|
101
|
+
/** 已知 eval 并集 = 本地历史 ∪ 各快照携带的 knownEvalIds —— 残缺检测的分母。 */
|
|
102
|
+
evalIds: string[];
|
|
72
103
|
}
|
|
73
104
|
|
|
74
|
-
/**
|
|
75
|
-
export interface
|
|
105
|
+
/** 目录扫描里读不了、但必须让调用方知道的落盘;无关 JSON 不记(静默忽略)。 */
|
|
106
|
+
export interface SkippedRun {
|
|
76
107
|
/** run 目录的绝对路径。 */
|
|
77
108
|
dir: string;
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
109
|
+
/**
|
|
110
|
+
* incompatible-version:schemaVersion 与读取器不同(不解析、不迁移、不降级);
|
|
111
|
+
* malformed:summary.json 是坏数据;
|
|
112
|
+
* incomplete:有 attempt 工件、没有 summary.json —— run 中途 crash、writer 没走到 finish()。
|
|
113
|
+
* summary 是收尾事实,reader 不读无 summary 的目录;已完成的工件留在盘上供手工排查。
|
|
114
|
+
*/
|
|
115
|
+
reason: "incompatible-version" | "malformed" | "incomplete";
|
|
116
|
+
/** 那份结果声明的 schemaVersion(incomplete 没有 summary,自然缺失)。 */
|
|
82
117
|
schemaVersion?: number;
|
|
83
|
-
/**
|
|
84
|
-
|
|
118
|
+
/** 完整的 producer(name + version):只有 name === "niceeval" 才能拼 npx 提示,第三方如实报名字。 */
|
|
119
|
+
producer?: RunSummary["producer"];
|
|
85
120
|
/** malformed:一句英文诊断。 */
|
|
86
121
|
detail?: string;
|
|
87
122
|
}
|
|
88
123
|
|
|
89
|
-
/** openResults 的返回:
|
|
90
|
-
export interface
|
|
91
|
-
/**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
|
|
124
|
+
/** openResults 的返回:experiments 分层;skipped 不静默丢;runDirs 忠实磁盘(新→旧)。 */
|
|
125
|
+
export interface Results {
|
|
126
|
+
/** 每个实验一项,挂着自己的全部历史(id 字典序)。 */
|
|
127
|
+
experiments: Experiment[];
|
|
128
|
+
skipped: SkippedRun[];
|
|
129
|
+
/** 低层忠实磁盘面:物理落盘目录,新→旧;多数消费方不碰。 */
|
|
130
|
+
runDirs: RunDir[];
|
|
131
|
+
/**
|
|
132
|
+
* 每个实验取最新一次快照,返回 Selection(快照与挑选警告绑在一起走)。
|
|
133
|
+
* `experiments` 是 experiment id 前缀过滤(string | string[]),同 CLI 位置参数语义。
|
|
134
|
+
*/
|
|
135
|
+
latest(opts?: { experiments?: string | string[] }): Selection;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 选集:选出的快照 + 挑选过程算出的警告。渲染与否在消费方,但缺口永远被算出来。
|
|
140
|
+
* 下游(Reports 计算函数、copySnapshots)收 `Selection | Snapshot[]`;
|
|
141
|
+
* 手工挑的裸数组没有挑选过程,自然没有 warnings 可带,也如实。
|
|
142
|
+
*/
|
|
143
|
+
export interface Selection {
|
|
144
|
+
snapshots: Snapshot[];
|
|
145
|
+
warnings: SelectionWarning[];
|
|
146
|
+
/**
|
|
147
|
+
* 只删不换:返回新 Selection,快照删减,warnings 按规则修剪 ——
|
|
148
|
+
* experimentId 不在幸存快照中的丢弃,非实验作用域的保留。
|
|
149
|
+
* 「换成上一个完整快照」这类替换式重挑不给方法,回 exp.snapshots 自己挑。
|
|
150
|
+
*/
|
|
151
|
+
filter(predicate: (snapshot: Snapshot) => boolean): Selection;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 挑选警告:每种带 kind、可判断的结构化字段和渲染好的英文 message。
|
|
156
|
+
* kind 是契约的一部分;全集与触发条件见 docs/results-lib.md「警告 kind 全集」。
|
|
157
|
+
*/
|
|
158
|
+
export type SelectionWarning =
|
|
159
|
+
| {
|
|
160
|
+
/** 选中快照的覆盖 < 该实验已知 eval 并集(本地历史 ∪ knownEvalIds)。 */
|
|
161
|
+
kind: "partial-coverage";
|
|
162
|
+
experimentId: string;
|
|
163
|
+
covered: number;
|
|
164
|
+
total: number;
|
|
165
|
+
message: string;
|
|
166
|
+
}
|
|
167
|
+
| {
|
|
168
|
+
/** 该实验选中的快照早于选集中最新的落盘;无阈值,如实触发,要阈值消费方按字段自比。 */
|
|
169
|
+
kind: "stale-snapshot";
|
|
170
|
+
experimentId: string;
|
|
171
|
+
startedAt: string;
|
|
172
|
+
latestStartedAt: string;
|
|
173
|
+
message: string;
|
|
174
|
+
}
|
|
175
|
+
| {
|
|
176
|
+
/** 落盘缺 experimentId,以 "<agent>/<model>" 合成键(快照的 synthetic: true 同源)。 */
|
|
177
|
+
kind: "synthetic-experiment-id";
|
|
178
|
+
experimentId: string;
|
|
179
|
+
/** 该快照所在 run 目录的绝对路径。 */
|
|
180
|
+
runDir: string;
|
|
181
|
+
message: string;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/** dedupeAttempts 的警告:身份键缺 startedAt,宁可不去重也不误删。 */
|
|
185
|
+
export interface DedupeWarning {
|
|
186
|
+
kind: "missing-startedAt";
|
|
187
|
+
experimentId: string;
|
|
188
|
+
evalId: string;
|
|
189
|
+
message: string;
|
|
98
190
|
}
|