niceeval 0.6.0 → 0.6.1
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 +5 -1
- package/dist/report/built-ins/experiment-comparison.d.ts +1 -0
- package/dist/report/built-ins/experiment-comparison.js +13 -0
- package/dist/report/built-ins/index.d.ts +1 -1
- package/dist/report/built-ins/index.js +1 -1
- package/dist/report/components.d.ts +3 -1
- package/dist/report/components.js +1 -1
- package/dist/report/compute.js +15 -33
- package/dist/report/format.d.ts +0 -6
- package/dist/report/format.js +1 -18
- package/dist/report/index.d.ts +2 -2
- package/dist/report/index.js +1 -1
- package/dist/report/locale.d.ts +13 -2
- package/dist/report/locale.js +26 -3
- package/dist/report/metrics.js +1 -1
- package/dist/report/react/AttemptList.d.ts +2 -2
- package/dist/report/react/AttemptList.js +3 -4
- package/dist/report/react/ExperimentList.d.ts +3 -2
- package/dist/report/react/ExperimentList.js +59 -7
- package/dist/report/react/MetricScatter.js +1 -5
- package/dist/report/react/fixtures.js +9 -4
- 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/text/faces.js +71 -33
- package/dist/report/types.d.ts +3 -15
- package/docs-site/zh/concepts/adapter.mdx +2 -2
- package/docs-site/zh/concepts/assert.mdx +11 -10
- package/docs-site/zh/concepts/evals.mdx +7 -6
- package/docs-site/zh/concepts/overview.mdx +1 -1
- package/docs-site/zh/guides/agent-feedback-loop.mdx +9 -7
- package/docs-site/zh/guides/fixtures.mdx +3 -1
- package/docs-site/zh/guides/official-adapters.mdx +1 -1
- package/docs-site/zh/guides/publish-report.mdx +1 -1
- package/docs-site/zh/guides/report-components.mdx +13 -13
- package/docs-site/zh/guides/results-data.mdx +28 -2
- package/docs-site/zh/guides/sandbox-agent.mdx +1 -0
- package/docs-site/zh/guides/sandbox-providers.mdx +1 -1
- package/docs-site/zh/guides/viewing-results.mdx +6 -5
- package/docs-site/zh/guides/write-send.mdx +2 -1
- package/docs-site/zh/reference/cli.mdx +3 -3
- package/docs-site/zh/reference/define-agent.mdx +3 -1
- package/docs-site/zh/reference/events.mdx +2 -2
- package/docs-site/zh/reference/expect.mdx +10 -5
- package/package.json +1 -1
- package/src/agents/types.ts +5 -1
- package/src/i18n/en.ts +1 -2
- package/src/i18n/zh-CN.ts +1 -1
- package/src/report/built-in-user-parity.test.tsx +44 -47
- package/src/report/built-ins/experiment-comparison.tsx +19 -0
- package/src/report/built-ins/index.ts +1 -1
- package/src/report/components.tsx +3 -1
- package/src/report/compute.ts +12 -36
- package/src/report/dual-render.test.tsx +33 -29
- package/src/report/format.ts +1 -16
- package/src/report/index.ts +1 -2
- package/src/report/locale.ts +26 -3
- package/src/report/metrics.ts +1 -1
- package/src/report/react/AttemptList.tsx +4 -6
- package/src/report/react/ExperimentList.tsx +167 -55
- package/src/report/react/MetricScatter.tsx +1 -11
- package/src/report/react/enhance.js +39 -0
- package/src/report/react/fixtures.ts +10 -4
- package/src/report/react/format.ts +1 -1
- package/src/report/react/index.tsx +0 -1
- package/src/report/react/render.test.tsx +24 -8
- package/src/report/react/styles.css +40 -13
- package/src/report/report.test.ts +2 -7
- package/src/report/text/faces.ts +76 -36
- package/src/report/types.ts +3 -15
- package/src/results/host-equivalence.test.ts +3 -3
- package/src/show/index.ts +8 -15
- package/src/show/render.ts +7 -204
- package/src/show/show.test.ts +22 -23
- package/src/view/data.test.ts +1 -1
- package/src/view/data.ts +6 -6
- package/src/view/index.ts +1 -1
- package/src/view/shared/types.ts +1 -1
- package/src/view/view-report.test.ts +5 -5
- 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/show/render.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { join, relative } from "node:path";
|
|
7
7
|
import type { AssertionResult, DiffData, EvalResult, Verdict } from "../types.ts";
|
|
8
|
-
import type { AttemptEvidence,
|
|
8
|
+
import type { AttemptEvidence, AttemptHandle, Snapshot } from "../results/index.ts";
|
|
9
9
|
import type { AnnotatedSourceLine } from "../results/index.ts";
|
|
10
10
|
import { groupIncompatibleVersionSkips } from "../results/index.ts";
|
|
11
11
|
import type { SkippedDir } from "../results/index.ts";
|
|
@@ -13,7 +13,7 @@ import type { ExecutionNode } from "../o11y/execution-tree.ts";
|
|
|
13
13
|
import { foldEvalVerdict } from "../shared/verdict.ts";
|
|
14
14
|
import { attemptCostUSD } from "../report/metrics.ts";
|
|
15
15
|
import { formatDurationMs, formatMetricValue, formatPlainNumber, formatUSD } from "../report/format.ts";
|
|
16
|
-
import { indentBlock, padDisplay, renderAlignedRows,
|
|
16
|
+
import { indentBlock, padDisplay, renderAlignedRows, wrapDisplay } from "../report/text/layout.ts";
|
|
17
17
|
import type { EvalHistoryRow, ExperimentHistoryRow } from "./compose.ts";
|
|
18
18
|
|
|
19
19
|
const MISSING = "—";
|
|
@@ -45,172 +45,6 @@ function verdictMark(verdict: Verdict): string {
|
|
|
45
45
|
return "✗";
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function showVerdictLabel(verdict: Verdict): string {
|
|
49
|
-
switch (verdict) {
|
|
50
|
-
case "passed":
|
|
51
|
-
return "✓ passed";
|
|
52
|
-
case "failed":
|
|
53
|
-
return "✗ failed";
|
|
54
|
-
case "errored":
|
|
55
|
-
return "! errored";
|
|
56
|
-
case "skipped":
|
|
57
|
-
return "○ skipped";
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** 按终端显示宽度截断,末尾保留一个省略号;不从 CJK 字符中间切开。 */
|
|
62
|
-
function clipDisplay(text: string, width: number): string {
|
|
63
|
-
if (stringWidth(text) <= width) return text;
|
|
64
|
-
if (width <= 1) return "…";
|
|
65
|
-
let out = "";
|
|
66
|
-
let used = 0;
|
|
67
|
-
for (const ch of text) {
|
|
68
|
-
const next = stringWidth(ch);
|
|
69
|
-
if (used + next > width - 1) break;
|
|
70
|
-
out += ch;
|
|
71
|
-
used += next;
|
|
72
|
-
}
|
|
73
|
-
return out + "…";
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function commandExitCode(evidence: string | undefined): string | undefined {
|
|
77
|
-
if (!evidence) return undefined;
|
|
78
|
-
try {
|
|
79
|
-
const parsed: unknown = JSON.parse(evidence);
|
|
80
|
-
if (typeof parsed === "object" && parsed !== null && "exitCode" in parsed) {
|
|
81
|
-
const exitCode = parsed.exitCode;
|
|
82
|
-
if (typeof exitCode === "number") return String(exitCode);
|
|
83
|
-
}
|
|
84
|
-
} catch {
|
|
85
|
-
// Assertion evidence is allowed to be arbitrary text; fall through to textual hints.
|
|
86
|
-
}
|
|
87
|
-
return /(?:returncode|retcode|exit(?:ed| code)?)\D{0,8}(-?\d+)/i.exec(evidence)?.[1];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** 默认索引 RESULT 列:只给能决定下一步的短原因,完整 evidence 留在 attempt 首页。 */
|
|
91
|
-
export function showResultReason(result: EvalResult): string {
|
|
92
|
-
if (result.verdict === "passed") return MISSING;
|
|
93
|
-
if (result.error !== undefined) return result.error;
|
|
94
|
-
if (result.skipReason !== undefined) return result.skipReason;
|
|
95
|
-
const gate = result.assertions.find((assertion) => !assertion.passed && assertion.severity === "gate");
|
|
96
|
-
if (!gate) return MISSING;
|
|
97
|
-
const expected = equalsExpected(gate.name);
|
|
98
|
-
if (expected !== undefined && gate.evidence !== undefined) {
|
|
99
|
-
return `expected ${expected}, received ${gate.evidence} · ${gate.name}`;
|
|
100
|
-
}
|
|
101
|
-
if (gate.name === "commandSucceeded()") {
|
|
102
|
-
const exitCode = commandExitCode(gate.evidence);
|
|
103
|
-
return `${exitCode === undefined ? "command failed" : `command exited ${exitCode}`} · ${gate.name}`;
|
|
104
|
-
}
|
|
105
|
-
return gate.detail ? `${gate.detail} · ${gate.name}` : gate.name;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function average(values: number[]): number {
|
|
109
|
-
return values.length === 0 ? 0 : values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function snapshotSummary(snapshot: Snapshot): string {
|
|
113
|
-
const counts: Record<Verdict, number> = { passed: 0, failed: 0, errored: 0, skipped: 0 };
|
|
114
|
-
for (const attempt of snapshot.attempts) counts[attempt.result.verdict] += 1;
|
|
115
|
-
const costs = snapshot.attempts.map((attempt) => attemptCostUSD(attempt.result)).filter((cost): cost is number => cost !== null);
|
|
116
|
-
const duration = average(snapshot.attempts.map((attempt) => attempt.result.durationMs));
|
|
117
|
-
return [
|
|
118
|
-
`${counts.passed} passed`,
|
|
119
|
-
`${counts.failed} failed`,
|
|
120
|
-
`${counts.errored} errored`,
|
|
121
|
-
`${counts.skipped} skipped`,
|
|
122
|
-
attemptsLabel(snapshot.attempts.length),
|
|
123
|
-
formatDurationMs(duration),
|
|
124
|
-
costs.length === 0 ? MISSING : formatUSD(average(costs)),
|
|
125
|
-
].join(" · ");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function warningText(selection: Selection, width: number): string {
|
|
129
|
-
return selection.warnings
|
|
130
|
-
.flatMap((warning) => {
|
|
131
|
-
const lines = wrapDisplay(warning.message, Math.max(20, width - 9));
|
|
132
|
-
return lines.map((line, index) => `${index === 0 ? "WARNING " : " "}${line}`);
|
|
133
|
-
})
|
|
134
|
-
.join("\n");
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function labeledBlock(label: string, value: string, width: number): string {
|
|
138
|
-
const prefix = padDisplay(label, 12);
|
|
139
|
-
return wrapDisplay(value, Math.max(8, width - stringWidth(prefix)))
|
|
140
|
-
.map((line, index) => `${index === 0 ? prefix : " ".repeat(stringWidth(prefix))}${line}`)
|
|
141
|
-
.join("\n");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function experimentAttemptTable(snapshot: Snapshot, width: number): string {
|
|
145
|
-
const separator = width >= 100 ? " " : width >= 70 ? " " : " ";
|
|
146
|
-
const fixedWidth = 9 + 9 + 8 + 5 + stringWidth(separator) * 5;
|
|
147
|
-
const flexible = Math.max(10, width - fixedWidth);
|
|
148
|
-
const naturalEval = Math.max(4, ...snapshot.attempts.map((attempt) => stringWidth(attempt.evalId)));
|
|
149
|
-
const evalWidth = Math.min(naturalEval, Math.max(4, Math.floor(flexible * 0.52)));
|
|
150
|
-
const resultWidth = Math.max(6, flexible - evalWidth);
|
|
151
|
-
const rows: string[][] = [["STATUS", "EVAL", "ATTEMPT", "RESULT", "DURATION", "COST"]];
|
|
152
|
-
for (const ev of snapshot.evals) {
|
|
153
|
-
for (const attempt of ev.attempts) {
|
|
154
|
-
const result = attempt.result;
|
|
155
|
-
const cost = attemptCostUSD(result);
|
|
156
|
-
rows.push([
|
|
157
|
-
showVerdictLabel(result.verdict),
|
|
158
|
-
clipDisplay(ev.id, evalWidth),
|
|
159
|
-
attempt.locator ?? MISSING,
|
|
160
|
-
clipDisplay(showResultReason(result), resultWidth),
|
|
161
|
-
result.verdict === "skipped" && result.durationMs === 0 ? MISSING : formatDurationMs(result.durationMs),
|
|
162
|
-
cost === null ? MISSING : formatUSD(cost),
|
|
163
|
-
]);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return renderAlignedRows(rows, ["left", "left", "left", "left", "right", "right"], separator);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function comparisonTable(snapshots: Snapshot[], width: number): string {
|
|
170
|
-
const rows: string[][] = [["EXPERIMENT", "AGENT", "MODEL", "PASS", "FAILED", "ERROR", "SKIP", "DURATION", "COST"]];
|
|
171
|
-
for (const snapshot of snapshots) {
|
|
172
|
-
const verdicts = snapshot.evals.map((ev) => foldEvalVerdict(ev.attempts.map((attempt) => attempt.result)));
|
|
173
|
-
const passed = verdicts.filter((verdict) => verdict === "passed").length;
|
|
174
|
-
const failed = verdicts.filter((verdict) => verdict === "failed").length;
|
|
175
|
-
const errored = verdicts.filter((verdict) => verdict === "errored").length;
|
|
176
|
-
const skipped = verdicts.filter((verdict) => verdict === "skipped").length;
|
|
177
|
-
const ran = verdicts.length - skipped;
|
|
178
|
-
const costs = snapshot.attempts.map((attempt) => attemptCostUSD(attempt.result)).filter((cost): cost is number => cost !== null);
|
|
179
|
-
rows.push([
|
|
180
|
-
clipDisplay(snapshot.experimentId, Math.max(12, Math.floor(width * 0.24))),
|
|
181
|
-
snapshot.agent,
|
|
182
|
-
snapshot.model ?? MISSING,
|
|
183
|
-
ran === 0 ? MISSING : `${Math.round((passed / ran) * 1000) / 10}% ${passed}/${ran}`,
|
|
184
|
-
String(failed),
|
|
185
|
-
String(errored),
|
|
186
|
-
String(skipped),
|
|
187
|
-
formatDurationMs(average(snapshot.attempts.map((attempt) => attempt.result.durationMs))),
|
|
188
|
-
costs.length === 0 ? MISSING : formatUSD(average(costs)),
|
|
189
|
-
]);
|
|
190
|
-
}
|
|
191
|
-
return renderAlignedRows(rows, ["left", "left", "left", "right", "right", "right", "right", "right", "right"]);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/** docs/feature/reports/show.md 的默认结果索引;只服务裸 show,不是可替换报告组件。 */
|
|
195
|
-
export function showIndexText(selection: Selection, width: number): string {
|
|
196
|
-
const blocks: string[] = [];
|
|
197
|
-
const warnings = warningText(selection, width);
|
|
198
|
-
if (warnings) blocks.push(warnings);
|
|
199
|
-
if (selection.snapshots.length >= 2) blocks.push(`COMPARISON\n${comparisonTable(selection.snapshots, width)}`);
|
|
200
|
-
for (const snapshot of selection.snapshots) {
|
|
201
|
-
blocks.push(
|
|
202
|
-
[
|
|
203
|
-
labeledBlock("EXPERIMENT", `${snapshot.experimentId} · ${snapshot.agent}${snapshot.model ? ` · ${snapshot.model}` : ""}`, width),
|
|
204
|
-
labeledBlock("SUMMARY", snapshotSummary(snapshot), width),
|
|
205
|
-
"",
|
|
206
|
-
experimentAttemptTable(snapshot, width),
|
|
207
|
-
].join("\n"),
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
blocks.push(labeledBlock("DRILL DOWN", "niceeval show @<attempt> [--eval | --execution | --diff]", width));
|
|
211
|
-
return blocks.join("\n\n");
|
|
212
|
-
}
|
|
213
|
-
|
|
214
48
|
function attemptsLabel(n: number): string {
|
|
215
49
|
return `${n} ${n === 1 ? "attempt" : "attempts"}`;
|
|
216
50
|
}
|
|
@@ -353,7 +187,7 @@ export function attemptHeader(attempt: AttemptHandle): string {
|
|
|
353
187
|
|
|
354
188
|
// ───────────────────────── AttemptEvidence 共用 ─────────────────────────
|
|
355
189
|
// evalSourceText / executionText / attemptOverviewText(--eval / --execution / 默认全景)
|
|
356
|
-
// 与 evalDetailText 的紧凑索引列共用的小件:locator
|
|
190
|
+
// 与 evalDetailText 的紧凑索引列共用的小件:locator 头、失败原因、
|
|
357
191
|
// 断言计票摘要。三个证据 renderer 与全景面都只消费同一份 AttemptEvidence,不各自读
|
|
358
192
|
// artifact 或重新判定 capability(loadAttemptEvidence 已经算好)。
|
|
359
193
|
|
|
@@ -362,36 +196,6 @@ export function attemptEvidenceHeader(evidence: AttemptEvidence): string {
|
|
|
362
196
|
return [evidence.locator, evidence.identity.evalId, evidence.identity.experimentId, evidence.result.verdict].join(" · ");
|
|
363
197
|
}
|
|
364
198
|
|
|
365
|
-
/** capability 位 → 展示字母,只列为 true 的位;全部为 false 时返回空串(不打空 `[]`)。 */
|
|
366
|
-
export function capabilityLetters(capabilities: AttemptEvidenceCapabilities): string {
|
|
367
|
-
const letters: string[] = [];
|
|
368
|
-
if (capabilities.eval) letters.push("E");
|
|
369
|
-
if (capabilities.execution) letters.push("X");
|
|
370
|
-
if (capabilities.timing) letters.push("⏱");
|
|
371
|
-
if (capabilities.diff) letters.push("D");
|
|
372
|
-
return letters.length > 0 ? `[${letters.join(",")}]` : "";
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* 紧凑多 attempt 索引(裸 `niceeval show`、单 eval 多 experiment 行、evidence flag 撞多个
|
|
377
|
-
* eval 时的消歧提示)用的廉价 capability 估算:只读瘦身 `EvalResult` 字段(`hasSources` /
|
|
378
|
-
* `hasEvents` / `hasTrace`),不为了点亮一个字母去读 artifact —— 与
|
|
379
|
-
* `report/compute.ts::attemptRow` 的 `hasEvidence` 同一个「只认瘦身字段」的口径(它同样只用
|
|
380
|
-
* `hasEvents` / `hasTrace`,同样不读 diff.json)。`diff` 位没有对应的瘦身字段(不像
|
|
381
|
-
* events/trace/sources,写入面从不给 diff 算一个 has* 布尔),而 diff.json 可达上百 MB,
|
|
382
|
-
* 不该为了这一个字母在渲染路径上打开它(见 docs/feature/reports/architecture.md「计算与
|
|
383
|
-
* 渲染分离」)——因此这里恒报 `false`;只有 `loadAttemptEvidence()` real 出的
|
|
384
|
-
* capabilities(单 attempt 路径,如 `@<locator>` 全景)才知道真实答案。
|
|
385
|
-
*/
|
|
386
|
-
export function cheapCapabilities(result: EvalResult): AttemptEvidenceCapabilities {
|
|
387
|
-
return {
|
|
388
|
-
eval: result.hasSources === true,
|
|
389
|
-
execution: result.hasEvents === true,
|
|
390
|
-
timing: result.hasEvents === true && result.hasTrace === true,
|
|
391
|
-
diff: false,
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
|
|
395
199
|
/**
|
|
396
200
|
* 一次 attempt 未通过的判定原因,单行、不含 detail——供紧凑索引行使用。precedence 与
|
|
397
201
|
* `report/compute.ts::reasonFor` 同一条规则(error → skipReason → 未通过的 gate 断言,
|
|
@@ -408,15 +212,14 @@ export function verdictReasonLine(result: EvalResult): string | undefined {
|
|
|
408
212
|
return gates.map((a) => `gate ${a.name}`).join(", ");
|
|
409
213
|
}
|
|
410
214
|
|
|
411
|
-
/** 紧凑多 attempt 索引的一行:`✗ weather/brooklyn @7K2M9Q
|
|
215
|
+
/** 紧凑多 attempt 索引的一行:`✗ weather/brooklyn @7K2M9Q gate calledTool(...)`。 */
|
|
412
216
|
export function attemptIndexLine(opts: {
|
|
413
217
|
evalId: string;
|
|
414
218
|
verdict: Verdict;
|
|
415
219
|
locator: string | undefined;
|
|
416
|
-
capabilities: AttemptEvidenceCapabilities;
|
|
417
220
|
reason?: string;
|
|
418
221
|
}): string {
|
|
419
|
-
const loc = opts.locator
|
|
222
|
+
const loc = opts.locator ?? MISSING;
|
|
420
223
|
const parts = [`${verdictMark(opts.verdict)} ${opts.evalId}`, loc];
|
|
421
224
|
if (opts.reason) parts.push(opts.reason);
|
|
422
225
|
return parts.join(" ");
|
|
@@ -463,7 +266,7 @@ export function evalDetailText(opts: EvalDetailOptions): string {
|
|
|
463
266
|
blocks.push(description ? `${evalId} — ${description}` : evalId);
|
|
464
267
|
|
|
465
268
|
// 每 experiment 一行:折叠判定、attempt 数、最新 attempt 的耗时、总成本、判定时间、
|
|
466
|
-
// 代表 attempt 的紧凑索引(locator +
|
|
269
|
+
// 代表 attempt 的紧凑索引(locator + 失败原因)——agent 从这张榜单
|
|
467
270
|
// 就能直接摘到一个 `@<locator>` 下钻,不必先跑一遍 `--eval`/`--execution` 才知道选谁。
|
|
468
271
|
const rows: string[][] = [];
|
|
469
272
|
for (const snapshot of snapshots) {
|
|
@@ -477,7 +280,7 @@ export function evalDetailText(opts: EvalDetailOptions): string {
|
|
|
477
280
|
if (c !== null) cost = (cost ?? 0) + c;
|
|
478
281
|
}
|
|
479
282
|
const rep = pickDetailAttempt(ev.attempts);
|
|
480
|
-
const locatorCell = rep?.locator
|
|
283
|
+
const locatorCell = rep?.locator ?? MISSING;
|
|
481
284
|
const reasonCell = rep ? (verdictReasonLine(rep.result) ?? "") : "";
|
|
482
285
|
rows.push([
|
|
483
286
|
snapshot.experimentId,
|
package/src/show/show.test.ts
CHANGED
|
@@ -146,33 +146,30 @@ async function seedComposedRoot(): Promise<string> {
|
|
|
146
146
|
|
|
147
147
|
// ───────────────────────── 榜单合成口径 ─────────────────────────
|
|
148
148
|
|
|
149
|
-
describe("
|
|
150
|
-
it("
|
|
149
|
+
describe("默认报告:跨快照合成的现刻水位(ExperimentComparison text 面)", () => {
|
|
150
|
+
it("局部重跑不撕报告:另一题从更早快照补齐,缺成本时散点明确空态", async () => {
|
|
151
151
|
const root = await seedComposedRoot();
|
|
152
152
|
const { out, code } = await show(root, []);
|
|
153
153
|
expect(code).toBe(0);
|
|
154
|
-
expect(out).
|
|
155
|
-
expect(out).
|
|
154
|
+
expect(out).toContain("No data to plot Cost × Pass rate");
|
|
155
|
+
expect(out).toContain("1 point missing data");
|
|
156
|
+
expect(out).not.toContain("needed to compare");
|
|
156
157
|
expect(out).not.toContain("COMPARISON");
|
|
157
|
-
expect(out).
|
|
158
|
-
expect(out).toContain("
|
|
159
|
-
expect(out).toMatch(
|
|
160
|
-
expect(out).toMatch(
|
|
161
|
-
expect(out).toMatch(
|
|
162
|
-
expect(out).not.toMatch(/@1[0-9a-z]{7}[✓✗]/);
|
|
163
|
-
expect(out).not.toContain("[E,X,");
|
|
164
|
-
expect(out).toContain("DRILL DOWN niceeval show @<attempt>");
|
|
158
|
+
expect(out).toMatch(/compare\/bub\s+default\s+bub\s+1s\s+50%/);
|
|
159
|
+
expect(out).toContain("1 passed / 1 failed");
|
|
160
|
+
expect(out).toMatch(/✗ failed\s+fixtures\/button[\s\S]*└─ @1[0-9a-z]{7}[\s\S]*fileChanged/);
|
|
161
|
+
expect(out).toMatch(/✓ passed\s+weather\/brooklyn[\s\S]*└─ @1[0-9a-z]{7}/);
|
|
162
|
+
expect(out).not.toMatch(/\[[EXD⏱,]+\]/);
|
|
165
163
|
});
|
|
166
164
|
|
|
167
|
-
it("裸 show
|
|
165
|
+
it("裸 show 的默认报告 chrome 跟随 locale", async () => {
|
|
168
166
|
const root = await seedComposedRoot();
|
|
169
167
|
process.env.NICEEVAL_LANG = "zh-CN";
|
|
170
168
|
try {
|
|
171
169
|
const { out, code } = await show(root, []);
|
|
172
170
|
expect(code).toBe(0);
|
|
173
|
-
expect(out).
|
|
174
|
-
expect(out).toContain("1
|
|
175
|
-
expect(out).not.toContain("没有可绘制的数据");
|
|
171
|
+
expect(out).toContain("预估成本 × 成功率 没有可绘制的数据");
|
|
172
|
+
expect(out).toContain("1 通过 / 1 失败");
|
|
176
173
|
expect(out).toContain("compare/bub");
|
|
177
174
|
} finally {
|
|
178
175
|
process.env.NICEEVAL_LANG = "en";
|
|
@@ -183,13 +180,13 @@ describe("榜单:跨快照合成的现刻水位(show 专用 attempt 表)", () =>
|
|
|
183
180
|
const root = await seedComposedRoot();
|
|
184
181
|
const { out, code } = await show(root, [], {}, 60);
|
|
185
182
|
expect(code).toBe(0);
|
|
186
|
-
expect(out).toContain("
|
|
183
|
+
expect(out).toContain("fixtures/button");
|
|
187
184
|
for (const line of out.trimEnd().split("\n")) {
|
|
188
185
|
expect(stringWidth(line), line).toBeLessThanOrEqual(60);
|
|
189
186
|
}
|
|
190
187
|
});
|
|
191
188
|
|
|
192
|
-
it("多个 experiment
|
|
189
|
+
it("多个 experiment 显示散点图,再逐 experiment → Eval → Attempt", async () => {
|
|
193
190
|
const root = await makeRoot();
|
|
194
191
|
await writeSnapshot(root, "2026-07-08T10-00-00-000Z", { experimentId: "compare/a", agent: "codex", model: "mini", startedAt: "2026-07-08T10:00:00.000Z" }, [
|
|
195
192
|
res("q1", "passed", { estimatedCostUSD: 0.1 }),
|
|
@@ -201,11 +198,13 @@ describe("榜单:跨快照合成的现刻水位(show 专用 attempt 表)", () =>
|
|
|
201
198
|
]);
|
|
202
199
|
const { out, code } = await show(root, []);
|
|
203
200
|
expect(code).toBe(0);
|
|
204
|
-
expect(out).
|
|
205
|
-
expect(out).toContain("
|
|
206
|
-
expect(out).
|
|
207
|
-
expect(out.
|
|
208
|
-
expect(out).
|
|
201
|
+
expect(out).toContain("better → upper right");
|
|
202
|
+
expect(out).toContain("A compare/a B compare/b");
|
|
203
|
+
expect(out).toMatch(/compare\/b\s+large\s+claude\s+1s\s+100%/);
|
|
204
|
+
expect(out).toMatch(/compare\/a\s+mini\s+codex\s+1s\s+50%/);
|
|
205
|
+
expect(out).toMatch(/✓ passed\s+q1[\s\S]*└─ @1[0-9a-z]{7}/);
|
|
206
|
+
expect(out).toMatch(/✗ failed\s+q2[\s\S]*└─ @1[0-9a-z]{7}/);
|
|
207
|
+
expect(out).not.toContain("needed to compare");
|
|
209
208
|
});
|
|
210
209
|
|
|
211
210
|
it("合成 Selection:每 experiment × eval 取最新判定;compose 不产生残缺警告", async () => {
|
package/src/view/data.test.ts
CHANGED
|
@@ -251,7 +251,7 @@ describe("loadViewScan · 新布局落盘直接可读(写入面 / 读取面同
|
|
|
251
251
|
);
|
|
252
252
|
|
|
253
253
|
const { viewData, reportHtml } = await loadViewScan(root);
|
|
254
|
-
// 报告槽(
|
|
254
|
+
// 报告槽(ExperimentComparison 的 web 面):Experiment 工作台行 + 官方通过率格子(1 过 1 败 = 50%)。
|
|
255
255
|
expect(reportHtml.en).toContain("compare/bub");
|
|
256
256
|
expect(reportHtml.en).toContain("50%");
|
|
257
257
|
const snapshot = viewData.snapshots[0]!;
|
package/src/view/data.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// view 的数据层:读取经 niceeval/results 的 openResults(布局/版本知识只住在那)。
|
|
2
2
|
// 这里只做编排:报告槽 Selection 恒经 selectCurrentResults(现刻水位;与 show 调同一个函数,
|
|
3
3
|
// 两扇门判定不分叉)、快照明细注入(locator / artifactBase)、skipped 透传、报告槽渲染
|
|
4
|
-
// (renderReportSlot:裸跑填充
|
|
4
|
+
// (renderReportSlot:裸跑填充 ExperimentComparison,--report 整槽替换,en / zh-CN 双语各渲染一遍)。
|
|
5
5
|
// --report 只换报告定义,注入的 Selection 与裸跑同一份。统计口径整体住在报告槽里
|
|
6
6
|
// (报告组件的官方计算函数),viewData 不再携带 overview / 榜单这类统计产物,
|
|
7
7
|
// 见 docs/feature/reports/view.md「打开与收窄」。
|
|
@@ -37,7 +37,7 @@ export interface ViewScan {
|
|
|
37
37
|
attemptsByBase: Map<string, AttemptHandle>;
|
|
38
38
|
/**
|
|
39
39
|
* 报告槽:报告树经 renderReportToStaticHtml 渲染出的静态 HTML(en / zh-CN 各一份),
|
|
40
|
-
* 裸跑填充内置默认报告
|
|
40
|
+
* 裸跑填充内置默认报告 ExperimentComparison,--report 整槽替换。作为两个 <template> 静态块
|
|
41
41
|
* 烘进页面(与 __NICEEVAL_VIEW_DATA__ 相邻),不进 viewData —— 前端只负责把当前
|
|
42
42
|
* 界面语言对应的那块 HTML 摆进报告槽位置,不解析。
|
|
43
43
|
*/
|
|
@@ -154,7 +154,7 @@ export async function loadLatestResultsPerEval(root = ".niceeval"): Promise<Eval
|
|
|
154
154
|
* 报告槽 Selection 恒经 selectCurrentResults 合成(现刻水位;与 `niceeval show` 调同一个
|
|
155
155
|
* 函数,裸跑与局部收窄不分叉),位置前缀 / --experiment 只作为 scope 传入,不切换选择口径。
|
|
156
156
|
* --report 本身不改挑选——它只换报告槽的填充,注入的 Selection 与裸跑同一份,
|
|
157
|
-
* 「裸跑 ≡ --report <
|
|
157
|
+
* 「裸跑 ≡ --report <ExperimentComparison>」靠这条成立(docs/feature/reports/architecture.md「Selection 是计算入口」)。
|
|
158
158
|
* 证据室数据(快照明细 / skipped)恒为全量,深链在任何收窄下都可达。
|
|
159
159
|
* 零可读结果一律抛 ViewInputError,不渲染/导出空页面(server 起不来,--out 非零退出)。
|
|
160
160
|
*/
|
|
@@ -200,7 +200,7 @@ export async function loadViewScan(input?: string, opts: ViewScanOptions = {}):
|
|
|
200
200
|
);
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
// 报告槽:裸跑填充
|
|
203
|
+
// 报告槽:裸跑填充 ExperimentComparison,--report 整槽替换。报告吃同一份注入 Selection,
|
|
204
204
|
// web 面在计算侧静态渲染成 HTML(en / zh-CN 各一遍,切界面语言不重算数据)。
|
|
205
205
|
const reportHtml = await renderReportSlot(opts.report, results, selection);
|
|
206
206
|
|
|
@@ -288,7 +288,7 @@ export async function loadAttemptIndex(input?: string): Promise<Map<string, Atte
|
|
|
288
288
|
|
|
289
289
|
/**
|
|
290
290
|
* 报告槽渲染:装载报告文件(--report;dev server 语义 —— 文件变更下次请求整页重算,
|
|
291
|
-
* 经 mtime cache-busting),缺省用内置默认报告
|
|
291
|
+
* 经 mtime cache-busting),缺省用内置默认报告 ExperimentComparison → 注入 Selection →
|
|
292
292
|
* web 面 renderToStaticMarkup 成静态 HTML,en / zh-CN 各渲染一遍(chrome 文案按 locale)。
|
|
293
293
|
* react / react-dom 动态加载:data.ts 还被 runner 的续跑携带(loadLatestResultsPerEval)
|
|
294
294
|
* 消费,渲染依赖不进那条路径。attemptHref 缺省即 `#/attempt/@<locator>` 深链路由(web.ts 的默认值),
|
|
@@ -307,7 +307,7 @@ async function renderReportSlot(
|
|
|
307
307
|
// 一个走 src。
|
|
308
308
|
const definition = report
|
|
309
309
|
? await loadReportFile(report.cwd, report.path, { freshImport: true })
|
|
310
|
-
: (await import("../../dist/report/built-ins/index.js")).
|
|
310
|
+
: (await import("../../dist/report/built-ins/index.js")).ExperimentComparison;
|
|
311
311
|
const { renderReportToStaticHtml } = await import("../../dist/report/web.js");
|
|
312
312
|
const ctx = { selection, results };
|
|
313
313
|
return {
|
package/src/view/index.ts
CHANGED
|
@@ -77,7 +77,7 @@ export function resolveViewInput(
|
|
|
77
77
|
* 导出静态报告(--out):只有目录式一种形态。写 <dir>/index.html,并把前端会 fetch 的 artifact
|
|
78
78
|
* (sources.json / events.json / trace.json)复制到 <dir>/artifact/<base>/——与本地
|
|
79
79
|
* server 的 /artifact/<rel> 路由同一布局,整个目录扔给任何静态托管即是完整体验。
|
|
80
|
-
* 首页即报告槽(裸跑填充
|
|
80
|
+
* 首页即报告槽(裸跑填充 ExperimentComparison,--report 整槽替换),证据室同站。
|
|
81
81
|
* 单文件(*.html)导出已移除:代码/transcript/trace 视图依赖 artifact 文件,单文件注定残缺,
|
|
82
82
|
* 存在本身就在诱导用户导出一份看不了证据的报告(docs/feature/reports/view.md「静态导出」)。
|
|
83
83
|
*/
|
package/src/view/shared/types.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// viewData 只携带证据室与壳需要的东西:快照明细(locator / artifactBase 已注入)、
|
|
5
5
|
// skipped、项目名与 run 元信息。统计口径(KPI / 榜单 / 挑选警告)整体住在报告槽的
|
|
6
|
-
// 静态 HTML 里(
|
|
6
|
+
// 静态 HTML 里(ExperimentComparison 或 --report 的报告自己算),壳与报告之间没有第二条数据通道。
|
|
7
7
|
|
|
8
8
|
import type { EvalResult, LocalizedText } from "../../types.ts";
|
|
9
9
|
import type { ReportLocale } from "../../report/locale.ts";
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// 覆盖:
|
|
4
4
|
// - 组合语义与 show 对齐:位置前缀收窄报告槽 Selection、--experiment 过滤、匹配不到直说;
|
|
5
5
|
// - 单文件模式共存:存在的文件路径 → 单文件模式,目录报错直说走 --run,其余按 eval 前缀;
|
|
6
|
-
// - 报告槽恒在:裸跑填充
|
|
7
|
-
// 裸跑 ≡ --report <re-export
|
|
6
|
+
// - 报告槽恒在:裸跑填充 ExperimentComparison,--report 整槽替换;en / zh-CN 双语各渲染一遍;
|
|
7
|
+
// 裸跑 ≡ --report <re-export ExperimentComparison 的文件>(等价性);
|
|
8
8
|
// - --out 静态导出:index.html 含两个语言的报告块、官方样式与增强 runtime,报告块零 <script>;
|
|
9
9
|
// - dev server 装载语义:报告文件变更 → 下次装载整页重算(mtime cache-busting)。
|
|
10
10
|
//
|
|
@@ -169,10 +169,10 @@ describe("loadViewScan · 组合语义", () => {
|
|
|
169
169
|
});
|
|
170
170
|
});
|
|
171
171
|
|
|
172
|
-
// ─────────────────── 报告槽恒在:裸跑 ≡ --report <
|
|
172
|
+
// ─────────────────── 报告槽恒在:裸跑 ≡ --report <ExperimentComparison> ───────────────────
|
|
173
173
|
|
|
174
174
|
describe("loadViewScan · 默认报告槽(裸跑)", () => {
|
|
175
|
-
it("裸跑产出的报告槽 HTML 与 --report <re-export
|
|
175
|
+
it("裸跑产出的报告槽 HTML 与 --report <re-export ExperimentComparison 的文件> 完全一致(双语)", async () => {
|
|
176
176
|
const root = await seedRoot();
|
|
177
177
|
const bare = await loadViewScan(root);
|
|
178
178
|
const viaReport = await loadViewScan(root, {
|
|
@@ -340,7 +340,7 @@ describe("buildView · --out 与 --report", () => {
|
|
|
340
340
|
).toBe(true);
|
|
341
341
|
});
|
|
342
342
|
|
|
343
|
-
it("默认导出(无 --report):报告槽填充
|
|
343
|
+
it("默认导出(无 --report):报告槽填充 ExperimentComparison,双语块与增强 runtime 恒内联", async () => {
|
|
344
344
|
const root = await seedRoot();
|
|
345
345
|
const out = join(root, "site");
|
|
346
346
|
await buildView({ input: root, out });
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import type { InputRequest, JsonValue, StreamEvent, ToolName, TraceSpan } from "../types.ts";
|
|
2
|
-
interface ExecutionNodeBase {
|
|
3
|
-
/** 本函数内确定性生成,同一份 (events, spans) 输入永远产出同一批 id(不是全局稳定 id,
|
|
4
|
-
* 不跨调用持久化——目前没有下游需要跨次运行比对同一个节点)。 */
|
|
5
|
-
id: string;
|
|
6
|
-
/** 唯一关联上的 OTel span(供下钻;渲染层用 endMs - startMs 算耗时)。action 节点的
|
|
7
|
-
* span.attributes 额外补了 io.tool/io.input/io.output/io.status(见 withIoAttributes);
|
|
8
|
-
* 其余节点原样保留。缺失 = timing unavailable——要么这次运行没有 OTel 接入,要么有 span
|
|
9
|
-
* 但没能唯一关联到这个节点上;两种情况都不是「假装有耗时」,统一表现为字段不存在。 */
|
|
10
|
-
span?: TraceSpan;
|
|
11
|
-
}
|
|
12
|
-
export interface ExecutionMessageNode extends ExecutionNodeBase {
|
|
13
|
-
kind: "message";
|
|
14
|
-
role: "assistant" | "user";
|
|
15
|
-
text: string;
|
|
16
|
-
}
|
|
17
|
-
export interface ExecutionThinkingNode extends ExecutionNodeBase {
|
|
18
|
-
kind: "thinking";
|
|
19
|
-
text: string;
|
|
20
|
-
}
|
|
21
|
-
/** Skill 加载节点——一等,直接来自 StreamEvent 的 "skill.loaded",不靠工具名/文本猜。 */
|
|
22
|
-
export interface ExecutionSkillNode extends ExecutionNodeBase {
|
|
23
|
-
kind: "skill.loaded";
|
|
24
|
-
skill: string;
|
|
25
|
-
/**
|
|
26
|
-
* 仅当原生协议把 Skill 加载表达成可关联的工具调用时才有,和 StreamEvent 同名字段同一含义。
|
|
27
|
-
* 存在时参与和 action/subagent 节点同一套 callId 关联(见主函数 nodeByCallId):Claude Code
|
|
28
|
-
* 的 Skill 调用本身就是一次 tool_use,OTel mapper 同样会把它的 tool_use_id 复制成
|
|
29
|
-
* span.attributes.call_id,唯一命中时这个节点也应该拿到 timing,不因为节点 kind 是
|
|
30
|
-
* skill.loaded 就被排除在 enrichment 之外。
|
|
31
|
-
*/
|
|
32
|
-
callId?: string;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* action.called + action.result 按 callId 合并成一个节点。`status` 多了 "pending"
|
|
36
|
-
* (StreamEvent 的 action.result.status 没有这一档)——这是 ExecutionTree 独有的中间态,
|
|
37
|
-
* 表示这次运行结束时结果始终没有回来(如超时截断的 transcript),诚实地区分「还没完成」
|
|
38
|
-
* 和「跑完但失败/被拒绝」。
|
|
39
|
-
*/
|
|
40
|
-
export interface ExecutionActionNode extends ExecutionNodeBase {
|
|
41
|
-
kind: "action";
|
|
42
|
-
callId: string;
|
|
43
|
-
/** 原始工具名(未归一化),对齐 StreamEvent.action.called.name。 */
|
|
44
|
-
name: string;
|
|
45
|
-
/** 归一化后的规范工具名,省略表示 adapter 没能归一(等同 ToolCall.name 缺省为 "unknown")。 */
|
|
46
|
-
tool?: ToolName;
|
|
47
|
-
input: JsonValue;
|
|
48
|
-
output?: JsonValue;
|
|
49
|
-
status: "pending" | "completed" | "failed" | "rejected";
|
|
50
|
-
}
|
|
51
|
-
/** subagent.called + subagent.completed 按 callId 合并;pending 语义同 ExecutionActionNode。 */
|
|
52
|
-
export interface ExecutionSubagentNode extends ExecutionNodeBase {
|
|
53
|
-
kind: "subagent";
|
|
54
|
-
callId: string;
|
|
55
|
-
name: string;
|
|
56
|
-
remoteUrl?: string;
|
|
57
|
-
output?: JsonValue;
|
|
58
|
-
status: "pending" | "completed" | "failed";
|
|
59
|
-
}
|
|
60
|
-
export interface ExecutionInputRequestedNode extends ExecutionNodeBase {
|
|
61
|
-
kind: "input.requested";
|
|
62
|
-
request: InputRequest;
|
|
63
|
-
}
|
|
64
|
-
export interface ExecutionCompactionNode extends ExecutionNodeBase {
|
|
65
|
-
kind: "compaction";
|
|
66
|
-
reason?: string;
|
|
67
|
-
}
|
|
68
|
-
export interface ExecutionErrorNode extends ExecutionNodeBase {
|
|
69
|
-
kind: "error";
|
|
70
|
-
message: string;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* span 存在、有意义,但唯一关联不到任何骨架节点——原样保留成独立节点,清楚标注「只有遥测、
|
|
74
|
-
* 没有对应事件」,不悄悄猜着并进某个骨架节点。`span` 是必填字段:这类节点的全部内容就是
|
|
75
|
-
* 这一条 span 本身,没有骨架事件可以叠加。
|
|
76
|
-
*/
|
|
77
|
-
export interface ExecutionTelemetryNode {
|
|
78
|
-
kind: "telemetry";
|
|
79
|
-
id: string;
|
|
80
|
-
span: TraceSpan;
|
|
81
|
-
}
|
|
82
|
-
export type ExecutionNode = ExecutionMessageNode | ExecutionThinkingNode | ExecutionSkillNode | ExecutionActionNode | ExecutionSubagentNode | ExecutionInputRequestedNode | ExecutionCompactionNode | ExecutionErrorNode | ExecutionTelemetryNode;
|
|
83
|
-
export interface ExecutionTree {
|
|
84
|
-
/**
|
|
85
|
-
* 骨架节点在前,顺序 = 事件出现顺序;telemetry-only 节点(未能唯一关联的 span)
|
|
86
|
-
* 按 span.startMs 追加在骨架之后——它们不属于骨架,不改变骨架的节点/顺序/内容,
|
|
87
|
-
* 只是叠加在末尾的额外证据。
|
|
88
|
-
*/
|
|
89
|
-
nodes: ExecutionNode[];
|
|
90
|
-
/**
|
|
91
|
-
* 这次运行是否提供过任何 span——不代表关联成功,只代表「OTel 接入过」。供渲染层区分
|
|
92
|
-
* 两种不同的诚实提示:整体没有 OTel 接入(该字段为 false,所有节点自然都没有 span),
|
|
93
|
-
* 和 OTel 接入了但这一个节点恰好关联不上(该字段为 true,该节点的 span 仍缺失)。
|
|
94
|
-
*/
|
|
95
|
-
timingAvailable: boolean;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* 纯函数:把标准事件流(骨架)与一批已挑选好的 OTel span(可选 enrichment)合成一棵
|
|
99
|
-
* ExecutionTree。events 决定节点的存在、顺序与内容;spans 只能给已存在的节点补时间,
|
|
100
|
-
* 从不新增、删除或重排骨架节点。
|
|
101
|
-
*/
|
|
102
|
-
export declare function buildExecutionTree(events: readonly StreamEvent[], spans: readonly TraceSpan[]): ExecutionTree;
|
|
103
|
-
export {};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { ToolCall, TraceSpan } from "../../types.ts";
|
|
2
|
-
/**
|
|
3
|
-
* 选出要保留并落盘的 span。
|
|
4
|
-
* 小 trace 原样返回;大 trace 按 canonical kind 过滤(kind ≠ "other" 留);
|
|
5
|
-
* 没 mapper(kind 全 "other")则回落到 firehose 频率过滤;仍过多再按耗时硬截断。
|
|
6
|
-
* 最后一律按起点排序,view 直接当瀑布图渲染。
|
|
7
|
-
*/
|
|
8
|
-
export declare function selectTraceSpans(spans: TraceSpan[]): TraceSpan[];
|
|
9
|
-
/**
|
|
10
|
-
* I/O 文本上限:文件内容/命令输出可能很大,截一下别把 trace 撑爆。导出给
|
|
11
|
-
* `execution-tree.ts` 复用——ExecutionTree 的 action 节点给合并上去的 span 补同一份
|
|
12
|
-
* io.* attributes,必须和这里同一个截断预算,不能另立一个数字。
|
|
13
|
-
*/
|
|
14
|
-
export declare const IO_MAX = 4000;
|
|
15
|
-
export declare function ioText(v: unknown): string;
|
|
16
|
-
/**
|
|
17
|
-
* 给工具执行 span 补上真实「入参/出参」:codex 等的 OTLP span 只带 tool_name/call_id,
|
|
18
|
-
* 命令文本与输出在 stdout transcript(events)里 —— 按 call_id 把 deriveRunFacts 的
|
|
19
|
-
* ToolCall.input/output join 到对应 span 的 attributes(io.input / io.output / io.tool)。
|
|
20
|
-
* 没匹配上的 span 原样返回。
|
|
21
|
-
*/
|
|
22
|
-
export declare function enrichTraceWithIO(spans: TraceSpan[], toolCalls: readonly ToolCall[]): TraceSpan[];
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const CostPassRateComparison: import("../report.ts").ReportDefinition;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// CostPassRateComparison:show / view 裸跑时的出厂报告槽。
|
|
3
|
-
//
|
|
4
|
-
// 它是一份普通 ReportDefinition,零特权:只声明「摆什么」——成本 × 通过率的散点(比较
|
|
5
|
-
// experiments)和实验列表。散点走 selection-form(`MetricScatter` 声明了 `resolve`,宿主
|
|
6
|
-
// 渲染前的 resolveReportTree 会替它调 `.data`);`ExperimentList` 没有 selection-form,
|
|
7
|
-
// 报告函数体里直接 `await ExperimentList.data(selection)` 拿到普通数组再传 `items`——
|
|
8
|
-
// 这正是三个实体列表统一的用法,内置默认报告不搞一套特权写法。包外用户复制这段 TSX
|
|
9
|
-
// (只改 import 路径与 export 形式)会走完全相同的解析与渲染管线。
|
|
10
|
-
import { Col } from "../primitives.js";
|
|
11
|
-
import { ExperimentList, MetricScatter } from "../components.js";
|
|
12
|
-
import { costUSD, passRate } from "../metrics.js";
|
|
13
|
-
import { defineReport } from "../report.js";
|
|
14
|
-
export const CostPassRateComparison = defineReport(async ({ selection }) => {
|
|
15
|
-
const experiments = await ExperimentList.data(selection);
|
|
16
|
-
return (_jsxs(Col, { children: [_jsx(MetricScatter, { selection: selection, points: "experiment", series: "agent", x: costUSD, y: passRate }), _jsx(ExperimentList, { items: experiments })] }));
|
|
17
|
-
});
|