@tangle-network/agent-eval 0.102.1 → 0.103.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/dist/campaign/index.d.ts +3 -3
- package/dist/campaign/index.js +4 -4
- package/dist/{chunk-YLKDN7JV.js → chunk-HV5PBTJF.js} +3 -3
- package/dist/{chunk-CMJSTXUR.js → chunk-IXOV77YF.js} +105 -18
- package/dist/chunk-IXOV77YF.js.map +1 -0
- package/dist/{chunk-Z3FLN24V.js → chunk-NTVWIH24.js} +104 -43
- package/dist/chunk-NTVWIH24.js.map +1 -0
- package/dist/chunk-RQNOLV3I.js +855 -0
- package/dist/chunk-RQNOLV3I.js.map +1 -0
- package/dist/{chunk-6Q2DYRWV.js → chunk-U3IDYATS.js} +2 -2
- package/dist/{chunk-QIT2XZ4E.js → chunk-XKA6ZGEY.js} +2 -2
- package/dist/{chunk-QIT2XZ4E.js.map → chunk-XKA6ZGEY.js.map} +1 -1
- package/dist/contract/index.js +4 -4
- package/dist/index.d.ts +7 -3
- package/dist/index.js +20 -6
- package/dist/index.js.map +1 -1
- package/dist/openapi.json +1 -1
- package/dist/{pre-registration-Dzg61IQA.d.ts → pre-registration-mWG2w8d-.d.ts} +42 -9
- package/dist/product-benchmark/index.d.ts +104 -1
- package/dist/product-benchmark/index.js +15 -1
- package/dist/{run-campaign-3NWW5PLF.js → run-campaign-2L4WCJHR.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-63MBSQTX.js +0 -350
- package/dist/chunk-63MBSQTX.js.map +0 -1
- package/dist/chunk-CMJSTXUR.js.map +0 -1
- package/dist/chunk-Z3FLN24V.js.map +0 -1
- /package/dist/{chunk-YLKDN7JV.js.map → chunk-HV5PBTJF.js.map} +0 -0
- /package/dist/{chunk-6Q2DYRWV.js.map → chunk-U3IDYATS.js.map} +0 -0
- /package/dist/{run-campaign-3NWW5PLF.js.map → run-campaign-2L4WCJHR.js.map} +0 -0
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ValidationError
|
|
3
|
+
} from "./chunk-3BFEG2F6.js";
|
|
4
|
+
|
|
5
|
+
// src/product-benchmark/index.ts
|
|
6
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "fs";
|
|
7
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
8
|
+
|
|
9
|
+
// src/product-benchmark/export.ts
|
|
10
|
+
import { spawnSync } from "child_process";
|
|
11
|
+
import { createHash } from "crypto";
|
|
12
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
13
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "path";
|
|
14
|
+
var productBenchmarkMutableSurfaces = [
|
|
15
|
+
"prompt",
|
|
16
|
+
"resources.files",
|
|
17
|
+
"tools",
|
|
18
|
+
"mcp",
|
|
19
|
+
"hooks",
|
|
20
|
+
"subagents"
|
|
21
|
+
];
|
|
22
|
+
function sha256(value) {
|
|
23
|
+
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
24
|
+
}
|
|
25
|
+
function safePathPart(value) {
|
|
26
|
+
return value.replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "artifact";
|
|
27
|
+
}
|
|
28
|
+
function git(args, fallback) {
|
|
29
|
+
const res = spawnSync("git", args, { cwd: process.cwd(), encoding: "utf8" });
|
|
30
|
+
const value = res.status === 0 ? res.stdout.trim() : "";
|
|
31
|
+
return value.length > 0 ? value : fallback;
|
|
32
|
+
}
|
|
33
|
+
function knownEnv(name) {
|
|
34
|
+
const value = process.env[name]?.trim();
|
|
35
|
+
return value && value !== "unknown" ? value : void 0;
|
|
36
|
+
}
|
|
37
|
+
function ciBranchName() {
|
|
38
|
+
const direct = knownEnv("GITHUB_HEAD_REF") ?? knownEnv("GITHUB_REF_NAME") ?? knownEnv("VERCEL_GIT_COMMIT_REF");
|
|
39
|
+
if (direct) return direct;
|
|
40
|
+
const ref = knownEnv("GITHUB_REF");
|
|
41
|
+
return ref?.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : void 0;
|
|
42
|
+
}
|
|
43
|
+
function repoBranch() {
|
|
44
|
+
const ci = ciBranchName();
|
|
45
|
+
if (ci) return ci;
|
|
46
|
+
const branch = git(["branch", "--show-current"], "");
|
|
47
|
+
if (branch) return branch;
|
|
48
|
+
const name = git(["name-rev", "--name-only", "--exclude=tags/*", "HEAD"], "");
|
|
49
|
+
if (name && name !== "undefined") return name;
|
|
50
|
+
return `detached:${git(["rev-parse", "--short", "HEAD"], "unknown")}`;
|
|
51
|
+
}
|
|
52
|
+
function productBenchmarkRepoIdentity() {
|
|
53
|
+
return {
|
|
54
|
+
url: git(["config", "--get", "remote.origin.url"], "unknown"),
|
|
55
|
+
commit: git(["rev-parse", "HEAD"], "unknown"),
|
|
56
|
+
branch: repoBranch()
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function packageVersion(name) {
|
|
60
|
+
const pkg = JSON.parse(readFileSync(resolve("package.json"), "utf8"));
|
|
61
|
+
if (pkg.name === name && pkg.version) return pkg.version;
|
|
62
|
+
const declared = pkg.dependencies?.[name] ?? pkg.devDependencies?.[name];
|
|
63
|
+
if (declared) return declared;
|
|
64
|
+
const installed = resolve("node_modules", name, "package.json");
|
|
65
|
+
if (existsSync(installed)) {
|
|
66
|
+
const installedPkg = JSON.parse(readFileSync(installed, "utf8"));
|
|
67
|
+
if (installedPkg.version) return installedPkg.version;
|
|
68
|
+
}
|
|
69
|
+
return "unknown";
|
|
70
|
+
}
|
|
71
|
+
function readRunRecords(path) {
|
|
72
|
+
const lines = readFileSync(path, "utf8").split("\n").map((line) => line.trim()).filter(Boolean);
|
|
73
|
+
return lines.map((line, index) => {
|
|
74
|
+
let parsed;
|
|
75
|
+
try {
|
|
76
|
+
parsed = JSON.parse(line);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
throw new ValidationError(
|
|
79
|
+
`${path}:${index + 1}: ${err instanceof Error ? err.message : String(err)}`,
|
|
80
|
+
{ cause: err }
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return expectRunRecordShape(parsed, `${path}:${index + 1}`);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function expectRunRecordShape(value, path) {
|
|
87
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
88
|
+
throw new ValidationError(`${path}: run record must be an object`);
|
|
89
|
+
}
|
|
90
|
+
const obj = value;
|
|
91
|
+
for (const key of ["runId", "model"]) {
|
|
92
|
+
if (typeof obj[key] !== "string" || obj[key].length === 0) {
|
|
93
|
+
throw new ValidationError(`${path}: run record ${key} must be a non-empty string`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const key of ["costUsd", "wallMs"]) {
|
|
97
|
+
if (typeof obj[key] !== "number" || !Number.isFinite(obj[key])) {
|
|
98
|
+
throw new ValidationError(`${path}: run record ${key} must be a finite number`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const tokenUsage = obj.tokenUsage;
|
|
102
|
+
if (!tokenUsage || typeof tokenUsage.input !== "number" || typeof tokenUsage.output !== "number") {
|
|
103
|
+
throw new ValidationError(`${path}: run record tokenUsage.input/output must be numbers`);
|
|
104
|
+
}
|
|
105
|
+
const outcome = obj.outcome;
|
|
106
|
+
if (!outcome || !outcome.raw || typeof outcome.raw !== "object") {
|
|
107
|
+
throw new ValidationError(`${path}: run record outcome.raw must be an object`);
|
|
108
|
+
}
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
function clamp01(value) {
|
|
112
|
+
return Math.max(0, Math.min(1, value));
|
|
113
|
+
}
|
|
114
|
+
function splitOf(record, opts) {
|
|
115
|
+
const custom = opts.classifySplit?.(record);
|
|
116
|
+
if (custom) return custom;
|
|
117
|
+
if (record.outcome.raw.safety === 1) return "safety";
|
|
118
|
+
if (record.splitTag === "holdout") return "holdout";
|
|
119
|
+
if (record.splitTag === "dev") return "dev";
|
|
120
|
+
return "practice";
|
|
121
|
+
}
|
|
122
|
+
function scoreOf(record) {
|
|
123
|
+
const score = record.outcome.holdoutScore ?? record.outcome.searchScore;
|
|
124
|
+
if (typeof score === "number" && Number.isFinite(score)) return clamp01(score);
|
|
125
|
+
const rawScore = record.outcome.raw.score ?? record.outcome.raw.composite;
|
|
126
|
+
return typeof rawScore === "number" && Number.isFinite(rawScore) ? clamp01(rawScore) : 0;
|
|
127
|
+
}
|
|
128
|
+
function rawPassOf(record) {
|
|
129
|
+
const rawPass = record.outcome.raw.pass;
|
|
130
|
+
if (typeof rawPass === "boolean") return rawPass;
|
|
131
|
+
if (typeof rawPass === "number" && Number.isFinite(rawPass)) return rawPass >= 1;
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
function passOf(record, score, threshold) {
|
|
135
|
+
const rawPass = rawPassOf(record);
|
|
136
|
+
if (rawPass !== null) return rawPass && !record.failureMode;
|
|
137
|
+
return score >= threshold && !record.failureMode;
|
|
138
|
+
}
|
|
139
|
+
function failureModeOf(record, score, threshold) {
|
|
140
|
+
if (record.failureMode) return record.failureMode;
|
|
141
|
+
const belowThreshold = `quality-below-threshold: ${Math.round(score * 100)}% < ${Math.round(threshold * 100)}%`;
|
|
142
|
+
if (rawPassOf(record) === false) {
|
|
143
|
+
return score < threshold ? belowThreshold : "product-pass-failed";
|
|
144
|
+
}
|
|
145
|
+
if (passOf(record, score, threshold)) return null;
|
|
146
|
+
return belowThreshold;
|
|
147
|
+
}
|
|
148
|
+
function numericDimensions(record) {
|
|
149
|
+
const dimensions = {};
|
|
150
|
+
for (const [key, value] of Object.entries(record.outcome.raw ?? {})) {
|
|
151
|
+
if (typeof value === "number" && Number.isFinite(value)) dimensions[key] = value;
|
|
152
|
+
}
|
|
153
|
+
if (Object.keys(dimensions).length === 0) dimensions.score = scoreOf(record);
|
|
154
|
+
return dimensions;
|
|
155
|
+
}
|
|
156
|
+
function armIdOf(record) {
|
|
157
|
+
if (record.candidateId) return record.candidateId;
|
|
158
|
+
const variant = record.agentProfile?.dimensions?.variant;
|
|
159
|
+
if (typeof variant === "string" && variant.length > 0) return variant;
|
|
160
|
+
const variantId = record.agentProfile?.dimensions?.variantId;
|
|
161
|
+
if (typeof variantId === "string" && variantId.length > 0) return variantId;
|
|
162
|
+
return "production-profile";
|
|
163
|
+
}
|
|
164
|
+
function backendOf(record) {
|
|
165
|
+
const backend = record.agentProfile?.dimensions?.backend;
|
|
166
|
+
return typeof backend === "string" && backend.length > 0 ? backend : "unknown";
|
|
167
|
+
}
|
|
168
|
+
function modelProvider(record) {
|
|
169
|
+
const backend = backendOf(record);
|
|
170
|
+
if (backend === "cli-bridge") return "cli-bridge";
|
|
171
|
+
if (backend === "sandbox") return "router";
|
|
172
|
+
if (record.model.startsWith("router/")) return "router";
|
|
173
|
+
return backend === "unknown" ? "router" : backend;
|
|
174
|
+
}
|
|
175
|
+
function runtimeResolution(record, opts) {
|
|
176
|
+
const reasoningEffort = record.agentProfile?.dimensions?.reasoningLevel;
|
|
177
|
+
return {
|
|
178
|
+
model: record.agentProfile?.model ?? record.model,
|
|
179
|
+
harness: record.agentProfile?.harness?.id ?? `${opts.projectId}-canonical-eval`,
|
|
180
|
+
backend: backendOf(record),
|
|
181
|
+
...typeof reasoningEffort === "string" && reasoningEffort.length > 0 ? { reasoningEffort } : {}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function sourceProfileHash(record) {
|
|
185
|
+
return record.agentProfile?.sourceProfile?.hash ?? record.agentProfile?.cellId ?? sha256(
|
|
186
|
+
JSON.stringify({
|
|
187
|
+
candidateId: record.candidateId,
|
|
188
|
+
promptHash: record.promptHash,
|
|
189
|
+
configHash: record.configHash
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
function materializedProfileHash(record, runtime) {
|
|
194
|
+
return sha256(
|
|
195
|
+
JSON.stringify({ sourceProfileHash: sourceProfileHash(record), model: runtime.model })
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
function profileIdOf(record, armId, runtime, opts) {
|
|
199
|
+
const base = record.agentProfile?.profileId ?? opts.fallbackProfileId ?? armId;
|
|
200
|
+
const withArm = base.endsWith(`:${armId}`) ? base : `${base}:${armId}`;
|
|
201
|
+
return `${withArm}:${runtime.model}`;
|
|
202
|
+
}
|
|
203
|
+
function toolCallsOf(record, runDir, opts) {
|
|
204
|
+
const raw = record.outcome.raw;
|
|
205
|
+
const primary = Number(raw.tool_call_count ?? raw.tool_calls ?? raw.toolCalls ?? 0);
|
|
206
|
+
if (Number.isFinite(primary) && primary > 0) return primary;
|
|
207
|
+
const fallback = opts.toolCallFallback?.(record, runDir);
|
|
208
|
+
if (fallback !== void 0 && Number.isFinite(fallback) && fallback > 0) return fallback;
|
|
209
|
+
return Number.isFinite(primary) ? primary : 0;
|
|
210
|
+
}
|
|
211
|
+
var TRACE_CANDIDATES = ["traces.jsonl", "traces", "trace", "trace-store"];
|
|
212
|
+
var RAW_CANDIDATES = ["raws.jsonl", "raw-events"];
|
|
213
|
+
var SCORE_CANDIDATES = ["scores.json", "manifest.json"];
|
|
214
|
+
function firstExisting(runDir, candidates) {
|
|
215
|
+
return candidates.find((candidate) => existsSync(join(runDir, candidate))) ?? candidates[0];
|
|
216
|
+
}
|
|
217
|
+
function relativeArtifacts(runDir) {
|
|
218
|
+
return {
|
|
219
|
+
records: "records.jsonl",
|
|
220
|
+
traces: firstExisting(runDir, TRACE_CANDIDATES),
|
|
221
|
+
raws: firstExisting(runDir, RAW_CANDIDATES),
|
|
222
|
+
scores: firstExisting(runDir, SCORE_CANDIDATES),
|
|
223
|
+
workspace: existsSync(join(runDir, "workspace")) ? "workspace" : "."
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function absoluteArtifacts(runDir) {
|
|
227
|
+
const rel = relativeArtifacts(runDir);
|
|
228
|
+
return {
|
|
229
|
+
records: resolve(runDir, rel.records),
|
|
230
|
+
traces: resolve(runDir, rel.traces),
|
|
231
|
+
raws: resolve(runDir, rel.raws),
|
|
232
|
+
scores: resolve(runDir, rel.scores),
|
|
233
|
+
workspace: resolve(runDir, rel.workspace)
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function materializeRunArtifacts(runDir, outDir, index) {
|
|
237
|
+
if (resolve(runDir) === resolve(outDir)) return relativeArtifacts(runDir);
|
|
238
|
+
const rel = relative(runDir, outDir);
|
|
239
|
+
if (rel && !rel.startsWith("..") && !isAbsolute(rel)) {
|
|
240
|
+
throw new ValidationError(`outDir must not be nested inside runDir: ${outDir}`);
|
|
241
|
+
}
|
|
242
|
+
const id = `${String(index + 1).padStart(2, "0")}-${safePathPart(basename(runDir))}-${sha256(runDir).slice(7, 19)}`;
|
|
243
|
+
const destRel = join("source-runs", id);
|
|
244
|
+
const destAbs = join(outDir, destRel);
|
|
245
|
+
mkdirSync(dirname(destAbs), { recursive: true });
|
|
246
|
+
cpSync(runDir, destAbs, { recursive: true, force: true });
|
|
247
|
+
const source = relativeArtifacts(runDir);
|
|
248
|
+
return {
|
|
249
|
+
records: join(destRel, source.records),
|
|
250
|
+
traces: join(destRel, source.traces),
|
|
251
|
+
raws: join(destRel, source.raws),
|
|
252
|
+
scores: join(destRel, source.scores),
|
|
253
|
+
workspace: source.workspace === "." ? destRel : join(destRel, source.workspace)
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function existsArtifact(artifactRoot, value) {
|
|
257
|
+
return existsSync(resolve(artifactRoot, value));
|
|
258
|
+
}
|
|
259
|
+
function runRecordToProductBenchmarkRecord(record, runDir, artifactRoot, artifacts, options) {
|
|
260
|
+
const opts = resolveOptions(options);
|
|
261
|
+
const runtime = runtimeResolution(record, opts);
|
|
262
|
+
const score = scoreOf(record);
|
|
263
|
+
const armId = armIdOf(record);
|
|
264
|
+
const inputTokens = record.tokenUsage.input;
|
|
265
|
+
const outputTokens = record.tokenUsage.output;
|
|
266
|
+
const toolCallCount = toolCallsOf(record, runDir, opts);
|
|
267
|
+
const dimensions = numericDimensions(record);
|
|
268
|
+
if (!("tool_calls" in dimensions)) dimensions.tool_calls = toolCallCount;
|
|
269
|
+
const product = {
|
|
270
|
+
schemaVersion: 1,
|
|
271
|
+
projectId: opts.projectId,
|
|
272
|
+
benchmarkId: opts.benchmarkId,
|
|
273
|
+
runId: record.runId,
|
|
274
|
+
scenarioId: record.scenarioId ?? record.experimentId,
|
|
275
|
+
split: splitOf(record, opts),
|
|
276
|
+
armId,
|
|
277
|
+
rep: Number(record.seed ?? 0) + 1,
|
|
278
|
+
agentProfile: {
|
|
279
|
+
id: profileIdOf(record, armId, runtime, opts),
|
|
280
|
+
hash: materializedProfileHash(record, runtime),
|
|
281
|
+
path: opts.agentProfilePath,
|
|
282
|
+
declared: runtime,
|
|
283
|
+
resolved: runtime
|
|
284
|
+
},
|
|
285
|
+
model: { provider: modelProvider(record), id: record.model },
|
|
286
|
+
backend: { kind: backendOf(record), version: opts.backendVersion },
|
|
287
|
+
outcome: {
|
|
288
|
+
pass: passOf(record, score, opts.passThreshold),
|
|
289
|
+
score,
|
|
290
|
+
dimensions,
|
|
291
|
+
failureMode: failureModeOf(record, score, opts.passThreshold)
|
|
292
|
+
},
|
|
293
|
+
usage: {
|
|
294
|
+
inputTokens,
|
|
295
|
+
outputTokens,
|
|
296
|
+
costUsd: record.costUsd,
|
|
297
|
+
// Rounded: the bundle contract requires integer milliseconds.
|
|
298
|
+
wallMs: Math.round(record.wallMs),
|
|
299
|
+
toolCalls: toolCallCount
|
|
300
|
+
},
|
|
301
|
+
integrity: {
|
|
302
|
+
realBackend: inputTokens + outputTokens > 0,
|
|
303
|
+
rawCapture: existsArtifact(artifactRoot, artifacts.raws),
|
|
304
|
+
traceCapture: existsArtifact(artifactRoot, artifacts.traces),
|
|
305
|
+
noStubRows: inputTokens + outputTokens > 0,
|
|
306
|
+
priced: record.costUsd > 0,
|
|
307
|
+
profileMaterialized: Boolean(record.agentProfile?.cellId)
|
|
308
|
+
},
|
|
309
|
+
artifacts
|
|
310
|
+
};
|
|
311
|
+
return validateProductBenchmarkRecord(product);
|
|
312
|
+
}
|
|
313
|
+
function uniqueArmRecords(records) {
|
|
314
|
+
const byArm = /* @__PURE__ */ new Map();
|
|
315
|
+
for (const record of records) {
|
|
316
|
+
const prior = byArm.get(record.armId);
|
|
317
|
+
if (!prior) {
|
|
318
|
+
byArm.set(record.armId, record);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const fields = [
|
|
322
|
+
["profileId", prior.agentProfile.id, record.agentProfile.id],
|
|
323
|
+
["model", prior.agentProfile.resolved.model, record.agentProfile.resolved.model],
|
|
324
|
+
["backend", prior.agentProfile.resolved.backend, record.agentProfile.resolved.backend],
|
|
325
|
+
["harness", prior.agentProfile.resolved.harness, record.agentProfile.resolved.harness],
|
|
326
|
+
[
|
|
327
|
+
"reasoningEffort",
|
|
328
|
+
prior.agentProfile.resolved.reasoningEffort,
|
|
329
|
+
record.agentProfile.resolved.reasoningEffort
|
|
330
|
+
]
|
|
331
|
+
];
|
|
332
|
+
for (const [name, a, b] of fields) {
|
|
333
|
+
if (a !== b) {
|
|
334
|
+
throw new ValidationError(
|
|
335
|
+
`records for arm '${record.armId}' disagree on ${name} (${String(a)} vs ${String(b)}) \u2014 one arm id must map to one policy`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return [...byArm.values()];
|
|
341
|
+
}
|
|
342
|
+
function uniqueScenarioRecords(records) {
|
|
343
|
+
const byScenario = /* @__PURE__ */ new Map();
|
|
344
|
+
for (const record of records) {
|
|
345
|
+
const prior = byScenario.get(record.scenarioId);
|
|
346
|
+
if (!prior) {
|
|
347
|
+
byScenario.set(record.scenarioId, record);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (prior.split !== record.split) {
|
|
351
|
+
throw new ValidationError(
|
|
352
|
+
`records for scenario '${record.scenarioId}' disagree on split (${prior.split} vs ${record.split})`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return [...byScenario.values()];
|
|
357
|
+
}
|
|
358
|
+
function buildProductBenchmarkManifest(records, options) {
|
|
359
|
+
if (records.length === 0) {
|
|
360
|
+
throw new ValidationError("cannot build a product benchmark manifest from zero records");
|
|
361
|
+
}
|
|
362
|
+
const scenarioTagPrefix = options.scenarioTagPrefix ?? defaultScenarioTagPrefix(options.projectId);
|
|
363
|
+
const mutableSurfaces = options.mutableSurfaces ?? productBenchmarkMutableSurfaces;
|
|
364
|
+
const byProfile = /* @__PURE__ */ new Map();
|
|
365
|
+
for (const record of records) {
|
|
366
|
+
byProfile.set(record.agentProfile.id, {
|
|
367
|
+
id: record.agentProfile.id,
|
|
368
|
+
profileHash: record.agentProfile.hash,
|
|
369
|
+
agentProfilePath: record.agentProfile.path
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
const manifest = {
|
|
373
|
+
schemaVersion: 1,
|
|
374
|
+
projectId: options.projectId,
|
|
375
|
+
benchmarkId: options.benchmarkId,
|
|
376
|
+
repo: productBenchmarkRepoIdentity(),
|
|
377
|
+
substrate: {
|
|
378
|
+
agentEval: packageVersion("@tangle-network/agent-eval"),
|
|
379
|
+
agentRuntime: packageVersion("@tangle-network/agent-runtime"),
|
|
380
|
+
agentInterface: packageVersion("@tangle-network/agent-interface"),
|
|
381
|
+
sandbox: packageVersion("@tangle-network/sandbox"),
|
|
382
|
+
...options.substrate
|
|
383
|
+
},
|
|
384
|
+
profiles: [...byProfile.values()],
|
|
385
|
+
arms: uniqueArmRecords(records).map((record) => ({
|
|
386
|
+
id: record.armId,
|
|
387
|
+
profileId: record.agentProfile.id,
|
|
388
|
+
mutableSurfaces: [...mutableSurfaces],
|
|
389
|
+
policyAxes: {
|
|
390
|
+
carrier: record.armId.includes("policy") ? "resource-file" : "profile",
|
|
391
|
+
model: record.agentProfile.resolved.model,
|
|
392
|
+
backend: record.agentProfile.resolved.backend,
|
|
393
|
+
harness: record.agentProfile.resolved.harness,
|
|
394
|
+
...record.agentProfile.resolved.reasoningEffort !== void 0 ? { reasoningEffort: record.agentProfile.resolved.reasoningEffort } : {}
|
|
395
|
+
}
|
|
396
|
+
})),
|
|
397
|
+
scenarios: uniqueScenarioRecords(records).map((record) => ({
|
|
398
|
+
id: record.scenarioId,
|
|
399
|
+
split: record.split,
|
|
400
|
+
tags: [scenarioTagPrefix, options.benchmarkId, record.split],
|
|
401
|
+
sourceAllowedForSynthesis: false
|
|
402
|
+
})),
|
|
403
|
+
budgets: {
|
|
404
|
+
maxUsd: records.reduce((sum2, record) => sum2 + record.usage.costUsd, 0),
|
|
405
|
+
maxCells: records.length,
|
|
406
|
+
maxWallMs: records.reduce((sum2, record) => sum2 + record.usage.wallMs, 0)
|
|
407
|
+
},
|
|
408
|
+
expectedArtifactDir: resolve(options.outDir)
|
|
409
|
+
};
|
|
410
|
+
return validateProductBenchmarkManifest(manifest);
|
|
411
|
+
}
|
|
412
|
+
function exportProductBenchmark(options) {
|
|
413
|
+
const { runDir, ...rest } = options;
|
|
414
|
+
return exportProductBenchmarkRuns({ ...rest, runDirs: [runDir] });
|
|
415
|
+
}
|
|
416
|
+
function exportProductBenchmarkRuns(options) {
|
|
417
|
+
const opts = resolveOptions(options);
|
|
418
|
+
const outDir = resolve(options.outDir);
|
|
419
|
+
const runDirs = options.runDirs.map((runDir) => resolve(runDir));
|
|
420
|
+
if (runDirs.length === 0) throw new ValidationError("export requires at least one run directory");
|
|
421
|
+
const materialize = options.materializeSourceRuns !== false;
|
|
422
|
+
const rows = runDirs.flatMap((runDir) => {
|
|
423
|
+
const sourceRecordsPath = join(runDir, "records.jsonl");
|
|
424
|
+
if (!existsSync(sourceRecordsPath)) throw new ValidationError(`missing ${sourceRecordsPath}`);
|
|
425
|
+
const records = readRunRecords(sourceRecordsPath);
|
|
426
|
+
if (records.length === 0) throw new ValidationError(`${sourceRecordsPath} is empty`);
|
|
427
|
+
return records.map((record) => ({ record, runDir }));
|
|
428
|
+
});
|
|
429
|
+
mkdirSync(outDir, { recursive: true });
|
|
430
|
+
const artifactsByRunDir = /* @__PURE__ */ new Map();
|
|
431
|
+
for (const [index, runDir] of runDirs.entries()) {
|
|
432
|
+
artifactsByRunDir.set(
|
|
433
|
+
runDir,
|
|
434
|
+
materialize ? materializeRunArtifacts(runDir, outDir, index) : absoluteArtifacts(runDir)
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
const artifactRoot = materialize ? outDir : "/";
|
|
438
|
+
const normalized = rows.map(
|
|
439
|
+
({ record, runDir }) => runRecordToProductBenchmarkRecord(
|
|
440
|
+
record,
|
|
441
|
+
runDir,
|
|
442
|
+
artifactRoot,
|
|
443
|
+
artifactsByRunDir.get(runDir),
|
|
444
|
+
{ ...options, runDirs, backendVersion: opts.backendVersion }
|
|
445
|
+
)
|
|
446
|
+
);
|
|
447
|
+
const manifest = buildProductBenchmarkManifest(normalized, {
|
|
448
|
+
outDir,
|
|
449
|
+
projectId: opts.projectId,
|
|
450
|
+
benchmarkId: opts.benchmarkId,
|
|
451
|
+
scenarioTagPrefix: opts.scenarioTagPrefix,
|
|
452
|
+
mutableSurfaces: opts.mutableSurfaces,
|
|
453
|
+
...options.substrate !== void 0 ? { substrate: options.substrate } : {}
|
|
454
|
+
});
|
|
455
|
+
const manifestPath = join(outDir, "product-benchmark-manifest.json");
|
|
456
|
+
const recordsPath = join(outDir, "product-benchmark-records.jsonl");
|
|
457
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
458
|
+
`);
|
|
459
|
+
writeFileSync(recordsPath, `${normalized.map((record) => JSON.stringify(record)).join("\n")}
|
|
460
|
+
`);
|
|
461
|
+
return { manifestPath, recordsPath, records: normalized.length };
|
|
462
|
+
}
|
|
463
|
+
function defaultScenarioTagPrefix(projectId) {
|
|
464
|
+
return projectId.replace(/-agent$/, "") || projectId;
|
|
465
|
+
}
|
|
466
|
+
function resolveOptions(options) {
|
|
467
|
+
return {
|
|
468
|
+
projectId: options.projectId,
|
|
469
|
+
benchmarkId: options.benchmarkId,
|
|
470
|
+
agentProfilePath: options.agentProfilePath,
|
|
471
|
+
passThreshold: options.passThreshold ?? 0.7,
|
|
472
|
+
scenarioTagPrefix: options.scenarioTagPrefix ?? defaultScenarioTagPrefix(options.projectId),
|
|
473
|
+
...options.fallbackProfileId !== void 0 ? { fallbackProfileId: options.fallbackProfileId } : {},
|
|
474
|
+
mutableSurfaces: options.mutableSurfaces ?? productBenchmarkMutableSurfaces,
|
|
475
|
+
...options.classifySplit !== void 0 ? { classifySplit: options.classifySplit } : {},
|
|
476
|
+
...options.toolCallFallback !== void 0 ? { toolCallFallback: options.toolCallFallback } : {},
|
|
477
|
+
backendVersion: options.backendVersion ?? packageVersion("@tangle-network/sandbox")
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/product-benchmark/index.ts
|
|
482
|
+
var productBenchmarkSplits = ["practice", "dev", "holdout", "safety", "sentinel"];
|
|
483
|
+
function isObject(value) {
|
|
484
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
485
|
+
}
|
|
486
|
+
function fail(path, message) {
|
|
487
|
+
throw new ValidationError(`${path}: ${message}`);
|
|
488
|
+
}
|
|
489
|
+
function wrapValidationError(path, err) {
|
|
490
|
+
if (err instanceof ValidationError) {
|
|
491
|
+
throw new ValidationError(`${path}: ${err.message}`, { cause: err });
|
|
492
|
+
}
|
|
493
|
+
throw new ValidationError(`${path}: ${err instanceof Error ? err.message : String(err)}`, {
|
|
494
|
+
cause: err
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
function expectObject(value, path) {
|
|
498
|
+
if (!isObject(value)) fail(path, "must be an object");
|
|
499
|
+
return value;
|
|
500
|
+
}
|
|
501
|
+
function expectString(value, path) {
|
|
502
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
503
|
+
fail(path, "must be a non-empty string");
|
|
504
|
+
return value;
|
|
505
|
+
}
|
|
506
|
+
function expectBoolean(value, path) {
|
|
507
|
+
if (typeof value !== "boolean") fail(path, "must be a boolean");
|
|
508
|
+
return value;
|
|
509
|
+
}
|
|
510
|
+
function expectNumber(value, path, opts = {}) {
|
|
511
|
+
if (typeof value !== "number" || !Number.isFinite(value)) fail(path, "must be a finite number");
|
|
512
|
+
if (opts.integer && !Number.isInteger(value)) fail(path, "must be an integer");
|
|
513
|
+
if (opts.min !== void 0 && value < opts.min) fail(path, `must be >= ${opts.min}`);
|
|
514
|
+
if (opts.max !== void 0 && value > opts.max) fail(path, `must be <= ${opts.max}`);
|
|
515
|
+
return value;
|
|
516
|
+
}
|
|
517
|
+
function expectStringArray(value, path) {
|
|
518
|
+
if (!Array.isArray(value)) fail(path, "must be an array");
|
|
519
|
+
return value.map((entry, index) => expectString(entry, `${path}[${index}]`));
|
|
520
|
+
}
|
|
521
|
+
function expectObjectArray(value, path) {
|
|
522
|
+
if (!Array.isArray(value)) fail(path, "must be an array");
|
|
523
|
+
return value.map((entry, index) => expectObject(entry, `${path}[${index}]`));
|
|
524
|
+
}
|
|
525
|
+
function expectSplit(value, path) {
|
|
526
|
+
const split = expectString(value, path);
|
|
527
|
+
if (!productBenchmarkSplits.includes(split)) {
|
|
528
|
+
fail(path, `must be one of ${productBenchmarkSplits.join(", ")}`);
|
|
529
|
+
}
|
|
530
|
+
return split;
|
|
531
|
+
}
|
|
532
|
+
function optionalString(value, path) {
|
|
533
|
+
if (value === void 0) return void 0;
|
|
534
|
+
return expectString(value, path);
|
|
535
|
+
}
|
|
536
|
+
function expectDimensions(value, path) {
|
|
537
|
+
const obj = expectObject(value, path);
|
|
538
|
+
const out = {};
|
|
539
|
+
for (const [key, raw] of Object.entries(obj)) out[key] = expectNumber(raw, `${path}.${key}`);
|
|
540
|
+
return out;
|
|
541
|
+
}
|
|
542
|
+
function expectRuntimeResolution(value, path) {
|
|
543
|
+
const obj = expectObject(value, path);
|
|
544
|
+
return {
|
|
545
|
+
model: expectString(obj.model, `${path}.model`),
|
|
546
|
+
harness: expectString(obj.harness, `${path}.harness`),
|
|
547
|
+
backend: expectString(obj.backend, `${path}.backend`),
|
|
548
|
+
...obj.reasoningEffort !== void 0 ? { reasoningEffort: optionalString(obj.reasoningEffort, `${path}.reasoningEffort`) } : {}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function validateProductBenchmarkManifest(value) {
|
|
552
|
+
const obj = expectObject(value, "manifest");
|
|
553
|
+
if (obj.schemaVersion !== 1) fail("manifest.schemaVersion", "must be 1");
|
|
554
|
+
const repo = expectObject(obj.repo, "manifest.repo");
|
|
555
|
+
const substrate = expectObject(obj.substrate, "manifest.substrate");
|
|
556
|
+
const profiles = expectObjectArray(obj.profiles, "manifest.profiles").map((profile, index) => ({
|
|
557
|
+
id: expectString(profile.id, `manifest.profiles[${index}].id`),
|
|
558
|
+
profileHash: expectString(profile.profileHash, `manifest.profiles[${index}].profileHash`),
|
|
559
|
+
agentProfilePath: expectString(
|
|
560
|
+
profile.agentProfilePath,
|
|
561
|
+
`manifest.profiles[${index}].agentProfilePath`
|
|
562
|
+
)
|
|
563
|
+
}));
|
|
564
|
+
const arms = expectObjectArray(obj.arms, "manifest.arms").map((arm, index) => ({
|
|
565
|
+
id: expectString(arm.id, `manifest.arms[${index}].id`),
|
|
566
|
+
profileId: expectString(arm.profileId, `manifest.arms[${index}].profileId`),
|
|
567
|
+
mutableSurfaces: expectStringArray(
|
|
568
|
+
arm.mutableSurfaces,
|
|
569
|
+
`manifest.arms[${index}].mutableSurfaces`
|
|
570
|
+
),
|
|
571
|
+
policyAxes: expectObject(arm.policyAxes, `manifest.arms[${index}].policyAxes`)
|
|
572
|
+
}));
|
|
573
|
+
const scenarios = expectObjectArray(obj.scenarios, "manifest.scenarios").map(
|
|
574
|
+
(scenario, index) => ({
|
|
575
|
+
id: expectString(scenario.id, `manifest.scenarios[${index}].id`),
|
|
576
|
+
split: expectSplit(scenario.split, `manifest.scenarios[${index}].split`),
|
|
577
|
+
tags: expectStringArray(scenario.tags, `manifest.scenarios[${index}].tags`),
|
|
578
|
+
sourceAllowedForSynthesis: expectBoolean(
|
|
579
|
+
scenario.sourceAllowedForSynthesis,
|
|
580
|
+
`manifest.scenarios[${index}].sourceAllowedForSynthesis`
|
|
581
|
+
)
|
|
582
|
+
})
|
|
583
|
+
);
|
|
584
|
+
const budgets = expectObject(obj.budgets, "manifest.budgets");
|
|
585
|
+
if (profiles.length === 0) fail("manifest.profiles", "must contain at least one profile");
|
|
586
|
+
if (arms.length === 0) fail("manifest.arms", "must contain at least one arm");
|
|
587
|
+
if (scenarios.length === 0) fail("manifest.scenarios", "must contain at least one scenario");
|
|
588
|
+
assertUnique(
|
|
589
|
+
profiles.map((profile) => profile.id),
|
|
590
|
+
"manifest.profiles.id"
|
|
591
|
+
);
|
|
592
|
+
assertUnique(
|
|
593
|
+
arms.map((arm) => arm.id),
|
|
594
|
+
"manifest.arms.id"
|
|
595
|
+
);
|
|
596
|
+
assertUnique(
|
|
597
|
+
scenarios.map((scenario) => scenario.id),
|
|
598
|
+
"manifest.scenarios.id"
|
|
599
|
+
);
|
|
600
|
+
const profileIds = new Set(profiles.map((profile) => profile.id));
|
|
601
|
+
for (const arm of arms) {
|
|
602
|
+
if (!profileIds.has(arm.profileId))
|
|
603
|
+
fail(`manifest.arms.${arm.id}.profileId`, `unknown profile ${arm.profileId}`);
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
schemaVersion: 1,
|
|
607
|
+
projectId: expectString(obj.projectId, "manifest.projectId"),
|
|
608
|
+
benchmarkId: expectString(obj.benchmarkId, "manifest.benchmarkId"),
|
|
609
|
+
repo: {
|
|
610
|
+
url: expectString(repo.url, "manifest.repo.url"),
|
|
611
|
+
commit: expectString(repo.commit, "manifest.repo.commit"),
|
|
612
|
+
branch: expectString(repo.branch, "manifest.repo.branch")
|
|
613
|
+
},
|
|
614
|
+
substrate: {
|
|
615
|
+
agentEval: expectString(substrate.agentEval, "manifest.substrate.agentEval"),
|
|
616
|
+
agentRuntime: expectString(substrate.agentRuntime, "manifest.substrate.agentRuntime"),
|
|
617
|
+
agentInterface: expectString(substrate.agentInterface, "manifest.substrate.agentInterface"),
|
|
618
|
+
sandbox: expectString(substrate.sandbox, "manifest.substrate.sandbox"),
|
|
619
|
+
...substrate.agentBench !== void 0 ? { agentBench: expectString(substrate.agentBench, "manifest.substrate.agentBench") } : {}
|
|
620
|
+
},
|
|
621
|
+
profiles,
|
|
622
|
+
arms,
|
|
623
|
+
scenarios,
|
|
624
|
+
budgets: {
|
|
625
|
+
maxUsd: expectNumber(budgets.maxUsd, "manifest.budgets.maxUsd", { min: 0 }),
|
|
626
|
+
maxCells: expectNumber(budgets.maxCells, "manifest.budgets.maxCells", {
|
|
627
|
+
min: 0,
|
|
628
|
+
integer: true
|
|
629
|
+
}),
|
|
630
|
+
maxWallMs: expectNumber(budgets.maxWallMs, "manifest.budgets.maxWallMs", {
|
|
631
|
+
min: 0,
|
|
632
|
+
integer: true
|
|
633
|
+
})
|
|
634
|
+
},
|
|
635
|
+
expectedArtifactDir: expectString(obj.expectedArtifactDir, "manifest.expectedArtifactDir")
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function validateProductBenchmarkRecord(value) {
|
|
639
|
+
const obj = expectObject(value, "record");
|
|
640
|
+
if (obj.schemaVersion !== 1) fail("record.schemaVersion", "must be 1");
|
|
641
|
+
const agentProfile = expectObject(obj.agentProfile, "record.agentProfile");
|
|
642
|
+
const model = expectObject(obj.model, "record.model");
|
|
643
|
+
const backend = expectObject(obj.backend, "record.backend");
|
|
644
|
+
const outcome = expectObject(obj.outcome, "record.outcome");
|
|
645
|
+
const usage = expectObject(obj.usage, "record.usage");
|
|
646
|
+
const integrity = expectObject(obj.integrity, "record.integrity");
|
|
647
|
+
const artifacts = expectObject(obj.artifacts, "record.artifacts");
|
|
648
|
+
const record = {
|
|
649
|
+
schemaVersion: 1,
|
|
650
|
+
projectId: expectString(obj.projectId, "record.projectId"),
|
|
651
|
+
benchmarkId: expectString(obj.benchmarkId, "record.benchmarkId"),
|
|
652
|
+
runId: expectString(obj.runId, "record.runId"),
|
|
653
|
+
scenarioId: expectString(obj.scenarioId, "record.scenarioId"),
|
|
654
|
+
split: expectSplit(obj.split, "record.split"),
|
|
655
|
+
armId: expectString(obj.armId, "record.armId"),
|
|
656
|
+
rep: expectNumber(obj.rep, "record.rep", { min: 1, integer: true }),
|
|
657
|
+
agentProfile: {
|
|
658
|
+
id: expectString(agentProfile.id, "record.agentProfile.id"),
|
|
659
|
+
hash: expectString(agentProfile.hash, "record.agentProfile.hash"),
|
|
660
|
+
path: expectString(agentProfile.path, "record.agentProfile.path"),
|
|
661
|
+
declared: expectRuntimeResolution(agentProfile.declared, "record.agentProfile.declared"),
|
|
662
|
+
resolved: expectRuntimeResolution(agentProfile.resolved, "record.agentProfile.resolved")
|
|
663
|
+
},
|
|
664
|
+
model: {
|
|
665
|
+
provider: expectString(model.provider, "record.model.provider"),
|
|
666
|
+
id: expectString(model.id, "record.model.id")
|
|
667
|
+
},
|
|
668
|
+
backend: {
|
|
669
|
+
kind: expectString(backend.kind, "record.backend.kind"),
|
|
670
|
+
version: expectString(backend.version, "record.backend.version")
|
|
671
|
+
},
|
|
672
|
+
outcome: {
|
|
673
|
+
pass: expectBoolean(outcome.pass, "record.outcome.pass"),
|
|
674
|
+
score: expectNumber(outcome.score, "record.outcome.score", { min: 0, max: 1 }),
|
|
675
|
+
dimensions: expectDimensions(outcome.dimensions, "record.outcome.dimensions"),
|
|
676
|
+
failureMode: outcome.failureMode === null ? null : expectString(outcome.failureMode, "record.outcome.failureMode")
|
|
677
|
+
},
|
|
678
|
+
usage: {
|
|
679
|
+
inputTokens: expectNumber(usage.inputTokens, "record.usage.inputTokens", {
|
|
680
|
+
min: 0,
|
|
681
|
+
integer: true
|
|
682
|
+
}),
|
|
683
|
+
outputTokens: expectNumber(usage.outputTokens, "record.usage.outputTokens", {
|
|
684
|
+
min: 0,
|
|
685
|
+
integer: true
|
|
686
|
+
}),
|
|
687
|
+
costUsd: expectNumber(usage.costUsd, "record.usage.costUsd", { min: 0 }),
|
|
688
|
+
wallMs: expectNumber(usage.wallMs, "record.usage.wallMs", { min: 0, integer: true }),
|
|
689
|
+
toolCalls: expectNumber(usage.toolCalls, "record.usage.toolCalls", { min: 0, integer: true })
|
|
690
|
+
},
|
|
691
|
+
integrity: {
|
|
692
|
+
realBackend: expectBoolean(integrity.realBackend, "record.integrity.realBackend"),
|
|
693
|
+
rawCapture: expectBoolean(integrity.rawCapture, "record.integrity.rawCapture"),
|
|
694
|
+
traceCapture: expectBoolean(integrity.traceCapture, "record.integrity.traceCapture"),
|
|
695
|
+
noStubRows: expectBoolean(integrity.noStubRows, "record.integrity.noStubRows"),
|
|
696
|
+
priced: expectBoolean(integrity.priced, "record.integrity.priced"),
|
|
697
|
+
profileMaterialized: expectBoolean(
|
|
698
|
+
integrity.profileMaterialized,
|
|
699
|
+
"record.integrity.profileMaterialized"
|
|
700
|
+
)
|
|
701
|
+
},
|
|
702
|
+
artifacts: {
|
|
703
|
+
records: expectString(artifacts.records, "record.artifacts.records"),
|
|
704
|
+
traces: expectString(artifacts.traces, "record.artifacts.traces"),
|
|
705
|
+
raws: expectString(artifacts.raws, "record.artifacts.raws"),
|
|
706
|
+
scores: expectString(artifacts.scores, "record.artifacts.scores"),
|
|
707
|
+
workspace: expectString(artifacts.workspace, "record.artifacts.workspace")
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
if (record.integrity.realBackend && record.usage.inputTokens + record.usage.outputTokens === 0) {
|
|
711
|
+
fail("record.usage", "realBackend rows must carry non-zero token usage");
|
|
712
|
+
}
|
|
713
|
+
return record;
|
|
714
|
+
}
|
|
715
|
+
function productBenchmarkIntegrityFailures(record) {
|
|
716
|
+
const failures = [];
|
|
717
|
+
for (const [key, value] of Object.entries(record.integrity)) {
|
|
718
|
+
if (!value) failures.push(`${record.runId}:${key}=false`);
|
|
719
|
+
}
|
|
720
|
+
return failures;
|
|
721
|
+
}
|
|
722
|
+
function readProductBenchmarkRecords(path) {
|
|
723
|
+
const lines = readFileSync2(path, "utf8").split("\n").map((line) => line.trim()).filter(Boolean);
|
|
724
|
+
const records = [];
|
|
725
|
+
for (const [index, line] of lines.entries()) {
|
|
726
|
+
try {
|
|
727
|
+
records.push(validateProductBenchmarkRecord(JSON.parse(line)));
|
|
728
|
+
} catch (err) {
|
|
729
|
+
wrapValidationError(`${path}:${index + 1}`, err);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return records;
|
|
733
|
+
}
|
|
734
|
+
function readProductBenchmarkManifest(path) {
|
|
735
|
+
try {
|
|
736
|
+
return validateProductBenchmarkManifest(JSON.parse(readFileSync2(path, "utf8")));
|
|
737
|
+
} catch (err) {
|
|
738
|
+
wrapValidationError(path, err);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function validateProductBenchmarkRun(input) {
|
|
742
|
+
const manifest = readProductBenchmarkManifest(input.manifestPath);
|
|
743
|
+
const records = readProductBenchmarkRecords(input.recordsPath);
|
|
744
|
+
const manifestProjectBench = `${manifest.projectId}/${manifest.benchmarkId}`;
|
|
745
|
+
const integrityFailures = records.flatMap(productBenchmarkIntegrityFailures);
|
|
746
|
+
const missingArtifacts = input.checkArtifacts === false ? [] : records.flatMap(
|
|
747
|
+
(record) => missingArtifactsForRecord(record, input.artifactRoot ?? dirname2(input.recordsPath))
|
|
748
|
+
);
|
|
749
|
+
for (const [index, record] of records.entries()) {
|
|
750
|
+
const recordProjectBench = `${record.projectId}/${record.benchmarkId}`;
|
|
751
|
+
if (recordProjectBench !== manifestProjectBench) {
|
|
752
|
+
fail(
|
|
753
|
+
`records[${index}]`,
|
|
754
|
+
`project/benchmark ${recordProjectBench} does not match manifest ${manifestProjectBench}`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
if (!manifest.arms.some((arm) => arm.id === record.armId))
|
|
758
|
+
fail(`records[${index}].armId`, `unknown arm ${record.armId}`);
|
|
759
|
+
if (!manifest.scenarios.some((scenario) => scenario.id === record.scenarioId)) {
|
|
760
|
+
fail(`records[${index}].scenarioId`, `unknown scenario ${record.scenarioId}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
const repoFailures = ["url", "commit", "branch"].filter((key) => manifest.repo[key].trim().length === 0 || manifest.repo[key] === "unknown").map((key) => `manifest.repo.${key}`);
|
|
764
|
+
const substrateFailures = ["agentEval", "agentRuntime", "agentInterface", "sandbox"].filter(
|
|
765
|
+
(key) => manifest.substrate[key].trim().length === 0 || manifest.substrate[key] === "unknown"
|
|
766
|
+
).map((key) => `manifest.substrate.${key}`);
|
|
767
|
+
return {
|
|
768
|
+
manifestPath: input.manifestPath,
|
|
769
|
+
recordsPath: input.recordsPath,
|
|
770
|
+
records: records.length,
|
|
771
|
+
repoFailures,
|
|
772
|
+
substrateFailures,
|
|
773
|
+
projects: sortedUnique(records.map((record) => record.projectId)),
|
|
774
|
+
benchmarks: sortedUnique(records.map((record) => record.benchmarkId)),
|
|
775
|
+
arms: sortedUnique(records.map((record) => record.armId)),
|
|
776
|
+
scenarios: sortedUnique(records.map((record) => record.scenarioId)),
|
|
777
|
+
passed: records.filter((record) => record.outcome.pass).length,
|
|
778
|
+
failed: records.filter((record) => !record.outcome.pass).length,
|
|
779
|
+
inputTokens: sum(records, (record) => record.usage.inputTokens),
|
|
780
|
+
outputTokens: sum(records, (record) => record.usage.outputTokens),
|
|
781
|
+
costUsd: sum(records, (record) => record.usage.costUsd),
|
|
782
|
+
wallMs: sum(records, (record) => record.usage.wallMs),
|
|
783
|
+
integrityFailures,
|
|
784
|
+
missingArtifacts
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function missingArtifactsForRecord(record, artifactRoot) {
|
|
788
|
+
const missing = [];
|
|
789
|
+
for (const [key, value] of Object.entries(record.artifacts)) {
|
|
790
|
+
const path = resolveArtifactPath(value, artifactRoot);
|
|
791
|
+
if (!existsSync2(path)) missing.push(`${record.runId}:${key}:${value}`);
|
|
792
|
+
}
|
|
793
|
+
return missing;
|
|
794
|
+
}
|
|
795
|
+
function resolveArtifactPath(value, artifactRoot) {
|
|
796
|
+
return value.startsWith("/") ? value : join2(artifactRoot, value);
|
|
797
|
+
}
|
|
798
|
+
function assertUnique(values, path) {
|
|
799
|
+
const seen = /* @__PURE__ */ new Set();
|
|
800
|
+
for (const value of values) {
|
|
801
|
+
if (seen.has(value)) fail(path, `duplicate ${value}`);
|
|
802
|
+
seen.add(value);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function sortedUnique(values) {
|
|
806
|
+
return [...new Set(values)].sort();
|
|
807
|
+
}
|
|
808
|
+
function sum(items, fn) {
|
|
809
|
+
return items.reduce((total, item) => total + fn(item), 0);
|
|
810
|
+
}
|
|
811
|
+
function findProductBenchmarkArtifacts(runDir) {
|
|
812
|
+
const manifestPath = join2(runDir, "product-benchmark-manifest.json");
|
|
813
|
+
const recordsPath = join2(runDir, "product-benchmark-records.jsonl");
|
|
814
|
+
if (existsSync2(manifestPath) && statSync(manifestPath).isFile() && existsSync2(recordsPath) && statSync(recordsPath).isFile()) {
|
|
815
|
+
return { manifestPath, recordsPath };
|
|
816
|
+
}
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
function assertProductBenchmarkRun(runDir) {
|
|
820
|
+
const artifacts = findProductBenchmarkArtifacts(runDir);
|
|
821
|
+
if (!artifacts) {
|
|
822
|
+
fail(runDir, "missing product-benchmark-manifest.json or product-benchmark-records.jsonl");
|
|
823
|
+
}
|
|
824
|
+
const report = validateProductBenchmarkRun({ ...artifacts, artifactRoot: runDir });
|
|
825
|
+
const failures = [
|
|
826
|
+
...report.repoFailures,
|
|
827
|
+
...report.substrateFailures,
|
|
828
|
+
...report.integrityFailures,
|
|
829
|
+
...report.missingArtifacts
|
|
830
|
+
];
|
|
831
|
+
if (failures.length > 0) {
|
|
832
|
+
fail(runDir, `product benchmark validation failed:
|
|
833
|
+
${failures.join("\n")}`);
|
|
834
|
+
}
|
|
835
|
+
return report;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
export {
|
|
839
|
+
productBenchmarkMutableSurfaces,
|
|
840
|
+
productBenchmarkRepoIdentity,
|
|
841
|
+
runRecordToProductBenchmarkRecord,
|
|
842
|
+
buildProductBenchmarkManifest,
|
|
843
|
+
exportProductBenchmark,
|
|
844
|
+
exportProductBenchmarkRuns,
|
|
845
|
+
productBenchmarkSplits,
|
|
846
|
+
validateProductBenchmarkManifest,
|
|
847
|
+
validateProductBenchmarkRecord,
|
|
848
|
+
productBenchmarkIntegrityFailures,
|
|
849
|
+
readProductBenchmarkRecords,
|
|
850
|
+
readProductBenchmarkManifest,
|
|
851
|
+
validateProductBenchmarkRun,
|
|
852
|
+
findProductBenchmarkArtifacts,
|
|
853
|
+
assertProductBenchmarkRun
|
|
854
|
+
};
|
|
855
|
+
//# sourceMappingURL=chunk-RQNOLV3I.js.map
|