@aigne/afs-llm-bench 1.12.0-beta.5
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/LICENSE.md +26 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/_virtual/rolldown_runtime.cjs +29 -0
- package/dist/bench.cjs +944 -0
- package/dist/bench.d.cts +89 -0
- package/dist/bench.d.cts.map +1 -0
- package/dist/bench.d.mts +89 -0
- package/dist/bench.d.mts.map +1 -0
- package/dist/bench.mjs +944 -0
- package/dist/bench.mjs.map +1 -0
- package/dist/errors.cjs +14 -0
- package/dist/errors.d.cts +13 -0
- package/dist/errors.d.cts.map +1 -0
- package/dist/errors.d.mts +13 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +14 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.cjs +38 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.mjs +10 -0
- package/dist/judge.cjs +189 -0
- package/dist/judge.d.cts +67 -0
- package/dist/judge.d.cts.map +1 -0
- package/dist/judge.d.mts +67 -0
- package/dist/judge.d.mts.map +1 -0
- package/dist/judge.mjs +185 -0
- package/dist/judge.mjs.map +1 -0
- package/dist/outlier.cjs +73 -0
- package/dist/outlier.d.cts +30 -0
- package/dist/outlier.d.cts.map +1 -0
- package/dist/outlier.d.mts +30 -0
- package/dist/outlier.d.mts.map +1 -0
- package/dist/outlier.mjs +73 -0
- package/dist/outlier.mjs.map +1 -0
- package/dist/patcher.cjs +296 -0
- package/dist/patcher.d.cts +119 -0
- package/dist/patcher.d.cts.map +1 -0
- package/dist/patcher.d.mts +119 -0
- package/dist/patcher.d.mts.map +1 -0
- package/dist/patcher.mjs +291 -0
- package/dist/patcher.mjs.map +1 -0
- package/dist/prompts.cjs +123 -0
- package/dist/prompts.d.cts +30 -0
- package/dist/prompts.d.cts.map +1 -0
- package/dist/prompts.d.mts +30 -0
- package/dist/prompts.d.mts.map +1 -0
- package/dist/prompts.mjs +121 -0
- package/dist/prompts.mjs.map +1 -0
- package/dist/reporter.cjs +322 -0
- package/dist/reporter.d.cts +27 -0
- package/dist/reporter.d.cts.map +1 -0
- package/dist/reporter.d.mts +27 -0
- package/dist/reporter.d.mts.map +1 -0
- package/dist/reporter.mjs +322 -0
- package/dist/reporter.mjs.map +1 -0
- package/dist/runner.cjs +710 -0
- package/dist/runner.d.cts +345 -0
- package/dist/runner.d.cts.map +1 -0
- package/dist/runner.d.mts +345 -0
- package/dist/runner.d.mts.map +1 -0
- package/dist/runner.mjs +697 -0
- package/dist/runner.mjs.map +1 -0
- package/dist/scoring.cjs +47 -0
- package/dist/scoring.d.cts +28 -0
- package/dist/scoring.d.cts.map +1 -0
- package/dist/scoring.d.mts +28 -0
- package/dist/scoring.d.mts.map +1 -0
- package/dist/scoring.mjs +46 -0
- package/dist/scoring.mjs.map +1 -0
- package/manifest.json +39 -0
- package/package.json +62 -0
package/dist/runner.cjs
ADDED
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_errors = require('./errors.cjs');
|
|
3
|
+
const require_judge = require('./judge.cjs');
|
|
4
|
+
const require_scoring = require('./scoring.cjs');
|
|
5
|
+
const require_outlier = require('./outlier.cjs');
|
|
6
|
+
let node_fs = require("node:fs");
|
|
7
|
+
let node_crypto = require("node:crypto");
|
|
8
|
+
let node_path = require("node:path");
|
|
9
|
+
|
|
10
|
+
//#region src/runner.ts
|
|
11
|
+
/**
|
|
12
|
+
* Phase 1 — runOne + execRun matrix + results writer.
|
|
13
|
+
*
|
|
14
|
+
* Dispatches a single (model, prompt) sample through `/dev/agent/.actions/run`,
|
|
15
|
+
* scores the trace via `scoring.ts`, and writes per-model aggregates to a JSON
|
|
16
|
+
* file. Credentials are NOT held by bench — the caller forwards a model id and
|
|
17
|
+
* ai-device routing injects vault secrets at the LLM call site.
|
|
18
|
+
*
|
|
19
|
+
* See plan.md L294-378 (impl) + L745-752 (test spec).
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_SAMPLES = 3;
|
|
22
|
+
const MAX_MODELS = 5;
|
|
23
|
+
const MAX_PROMPTS_PER_RUN = 5;
|
|
24
|
+
const MAX_MODELS_FULL = 200;
|
|
25
|
+
const MAX_PROMPTS_FULL = 200;
|
|
26
|
+
const MAX_SAMPLES = 100;
|
|
27
|
+
const MAX_PARALLEL = 4;
|
|
28
|
+
const DEFAULT_TOTAL_TOKENS = 24e4;
|
|
29
|
+
const DEFAULT_WALL_TIME_MS = 5 * 6e4;
|
|
30
|
+
/**
|
|
31
|
+
* Hub used to expand short model ids (e.g. `"claude-haiku-4-5"`) into a full
|
|
32
|
+
* AFS path before dispatching to `/dev/agent/.actions/run`. agent-run's own
|
|
33
|
+
* `resolveAgentModelPath` defaults to AigneHub, which requires credits — we
|
|
34
|
+
* default to Vertex so OAuth-backed local installs work without billing.
|
|
35
|
+
*
|
|
36
|
+
* Override via `runFull` arg `hub` (or by passing the full
|
|
37
|
+
* `/dev/ai/hubs/<hub>/models/<id>` path as the model id).
|
|
38
|
+
*/
|
|
39
|
+
const DEFAULT_HUB = "vertex";
|
|
40
|
+
/** Expand a short model id to its full hub path. Full paths pass through. */
|
|
41
|
+
function resolveModelPath(model, hub = DEFAULT_HUB) {
|
|
42
|
+
if (model.startsWith("/")) return model;
|
|
43
|
+
return `/dev/ai/hubs/${hub}/models/${model}`;
|
|
44
|
+
}
|
|
45
|
+
const RATE_LIMIT_MAX_RETRIES = 3;
|
|
46
|
+
const RATE_LIMIT_BASE_BACKOFF_MS = 2e3;
|
|
47
|
+
const MAX_RESULTS_SIZE_BYTES = 10 * 1024 * 1024;
|
|
48
|
+
const ALLOWED_JUDGE_DIMENSIONS = ["instruction_following"];
|
|
49
|
+
/**
|
|
50
|
+
* Hard cap on multi-judge fanout (Phase 0 of Run 4). Each judge is one extra
|
|
51
|
+
* `/dev/ai/.actions/chat` call per successful sample, so a 200×200 matrix
|
|
52
|
+
* with 100 samples explodes quickly. 10 is generous enough for ensemble
|
|
53
|
+
* experiments and small enough that runaway configs surface as validation
|
|
54
|
+
* errors rather than silently burning cost.
|
|
55
|
+
*/
|
|
56
|
+
const MAX_JUDGES = 10;
|
|
57
|
+
const JUDGE_ID_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
58
|
+
const SECRET_VALUE_PATTERNS = [
|
|
59
|
+
/-----BEGIN [A-Z0-9 ]*?PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*?PRIVATE KEY-----/g,
|
|
60
|
+
/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi,
|
|
61
|
+
/\bsk-[A-Za-z0-9_-]*[0-9][A-Za-z0-9_-]{6,}/g,
|
|
62
|
+
/\bya29\.[A-Za-z0-9._-]+/g,
|
|
63
|
+
/\bxox[baprs]-[A-Za-z0-9-]+/gi,
|
|
64
|
+
/\bAKIA[0-9A-Z]{16}\b/g
|
|
65
|
+
];
|
|
66
|
+
const SENSITIVE_PATTERNS = [
|
|
67
|
+
/\bapi[_-]?key\b/gi,
|
|
68
|
+
/\bauthorization\b/gi,
|
|
69
|
+
/\bbearer\b/gi,
|
|
70
|
+
/\bservice[_-]?account\b/gi,
|
|
71
|
+
/\bprivate[_-]?key\b/gi,
|
|
72
|
+
/\bAKIA[0-9A-Z]{0,16}\b/g
|
|
73
|
+
];
|
|
74
|
+
/** Permissive read-only AFS sandbox so benchmark prompts can explore mounts. */
|
|
75
|
+
function defaultBenchTools() {
|
|
76
|
+
return [{
|
|
77
|
+
path: "/**",
|
|
78
|
+
ops: [
|
|
79
|
+
"list",
|
|
80
|
+
"read",
|
|
81
|
+
"explain",
|
|
82
|
+
"stat",
|
|
83
|
+
"exec"
|
|
84
|
+
],
|
|
85
|
+
maxDepth: 10
|
|
86
|
+
}];
|
|
87
|
+
}
|
|
88
|
+
function defaultBenchSystem() {
|
|
89
|
+
return [
|
|
90
|
+
"You are an AFS power user evaluating mounts and actions.",
|
|
91
|
+
"Tools: afs_list, afs_read, afs_explain, afs_stat, afs_exec.",
|
|
92
|
+
"Prefer parallel tool calls when listing or explaining sibling paths.",
|
|
93
|
+
"Stop and summarize as soon as you have enough context to answer."
|
|
94
|
+
].join("\n");
|
|
95
|
+
}
|
|
96
|
+
async function runOne(input) {
|
|
97
|
+
const taskBody = input.prompt.sections.Task ?? input.prompt.body;
|
|
98
|
+
const systemBody = input.prompt.sections["System prompt"] ?? defaultBenchSystem();
|
|
99
|
+
const exec = await input.deps.exec("/dev/agent/.actions/run", {
|
|
100
|
+
task: taskBody,
|
|
101
|
+
model: resolveModelPath(input.model, input.hub),
|
|
102
|
+
tools: defaultBenchTools(),
|
|
103
|
+
budget: {
|
|
104
|
+
max_rounds: input.prompt.frontmatter.max_rounds * 2,
|
|
105
|
+
total_tokens: DEFAULT_TOTAL_TOKENS,
|
|
106
|
+
wall_time_ms: DEFAULT_WALL_TIME_MS
|
|
107
|
+
},
|
|
108
|
+
system: systemBody
|
|
109
|
+
});
|
|
110
|
+
const isBudgetExhausted = !exec.success && exec.error?.code === "BUDGET_EXHAUSTED" && exec.data && typeof exec.data === "object" && exec.data.status === "budget_exhausted";
|
|
111
|
+
if (!exec.success && !isBudgetExhausted) {
|
|
112
|
+
const err = (typeof exec.data === "object" && exec.data !== null ? exec.data.error : void 0) ?? exec.error?.message ?? "agent-run dispatch failed";
|
|
113
|
+
throw new Error(`[runOne] agent-run failed for model=${input.model}: ${err}`);
|
|
114
|
+
}
|
|
115
|
+
const result = exec.data;
|
|
116
|
+
return {
|
|
117
|
+
model: input.model,
|
|
118
|
+
prompt: input.prompt.id,
|
|
119
|
+
scores: require_scoring.scoreFromTrace(input.benchPrompt, result),
|
|
120
|
+
raw: result
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function promptToBenchPrompt(prompt) {
|
|
124
|
+
return {
|
|
125
|
+
max_rounds: prompt.frontmatter.max_rounds,
|
|
126
|
+
expected_paths: extractExpectedPaths(prompt),
|
|
127
|
+
expected_outcomes: extractExpectedOutcomes(prompt)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Extract the raw `# Expected outcomes` section text. Returns the entire
|
|
132
|
+
* section body (including any markdown table) so the LLM judge has the full
|
|
133
|
+
* rubric. Empty string when the section is missing — the judge still runs
|
|
134
|
+
* but has no rubric to score against; in practice this means a low score.
|
|
135
|
+
*/
|
|
136
|
+
function extractExpectedOutcomes(prompt) {
|
|
137
|
+
const key = Object.keys(prompt.sections).find((k) => k.startsWith("Expected outcomes"));
|
|
138
|
+
if (!key) return "";
|
|
139
|
+
return prompt.sections[key].trim();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Extract expected_paths from the `# Expected paths` body section's first
|
|
143
|
+
* fenced code block. Strips `// …` line comments. Lines that don't start with
|
|
144
|
+
* `/` are ignored (so prose inside the block doesn't pollute the path set).
|
|
145
|
+
*/
|
|
146
|
+
function extractExpectedPaths(prompt) {
|
|
147
|
+
const key = Object.keys(prompt.sections).find((k) => k.startsWith("Expected paths"));
|
|
148
|
+
if (!key) return [];
|
|
149
|
+
const section = prompt.sections[key];
|
|
150
|
+
const fenced = section.match(/```[^\n]*\n([\s\S]*?)```/);
|
|
151
|
+
const block = fenced ? fenced[1] : section;
|
|
152
|
+
const out = [];
|
|
153
|
+
for (const raw of block.split(/\r?\n/)) {
|
|
154
|
+
const noComment = raw.split("//")[0].trim();
|
|
155
|
+
if (noComment.startsWith("/")) out.push(noComment);
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
function validateRunArgs(args) {
|
|
160
|
+
const models = args.models;
|
|
161
|
+
if (!Array.isArray(models)) throw new require_errors.ValidationError("Phase 1: 'models' must be an explicit array (no 'all' shortcut)");
|
|
162
|
+
if (models.length < 1 || models.length > MAX_MODELS) throw new require_errors.ValidationError(`Phase 1: 'models' length must be in [1, ${MAX_MODELS}]`);
|
|
163
|
+
for (const m of models) if (typeof m !== "string" || m.length === 0) throw new require_errors.ValidationError("Phase 1: 'models' entries must be non-empty strings");
|
|
164
|
+
const promptsArg = args.prompts;
|
|
165
|
+
let prompts;
|
|
166
|
+
if (promptsArg === "all") prompts = "all";
|
|
167
|
+
else if (Array.isArray(promptsArg)) {
|
|
168
|
+
if (promptsArg.length < 1 || promptsArg.length > MAX_PROMPTS_PER_RUN) throw new require_errors.ValidationError(`Phase 1: 'prompts' length must be in [1, ${MAX_PROMPTS_PER_RUN}]`);
|
|
169
|
+
for (const p of promptsArg) if (typeof p !== "string" || p.length === 0) throw new require_errors.ValidationError("Phase 1: 'prompts' entries must be non-empty strings");
|
|
170
|
+
prompts = promptsArg;
|
|
171
|
+
} else throw new require_errors.ValidationError("Phase 1: 'prompts' must be 'all' or an array of prompt ids");
|
|
172
|
+
const samples = clampInt(args.samples, DEFAULT_SAMPLES, 1, MAX_SAMPLES);
|
|
173
|
+
const parallel = clampInt(args.parallel, 1, 1, MAX_PARALLEL);
|
|
174
|
+
let hub;
|
|
175
|
+
if (args.hub !== void 0) {
|
|
176
|
+
if (typeof args.hub !== "string" || args.hub.length === 0) throw new require_errors.ValidationError("'hub' must be a non-empty string");
|
|
177
|
+
if (!/^[A-Za-z0-9._-]+$/.test(args.hub)) throw new require_errors.ValidationError(`'hub' has invalid characters: ${args.hub}`);
|
|
178
|
+
hub = args.hub;
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
models,
|
|
182
|
+
prompts,
|
|
183
|
+
samples,
|
|
184
|
+
parallel,
|
|
185
|
+
...hub !== void 0 ? { hub } : {}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function clampInt(raw, fallback, lo, hi) {
|
|
189
|
+
const v = typeof raw === "number" && Number.isFinite(raw) ? Math.floor(raw) : fallback;
|
|
190
|
+
return Math.max(lo, Math.min(hi, v));
|
|
191
|
+
}
|
|
192
|
+
function validateRunFullArgs(args) {
|
|
193
|
+
const models = args.models;
|
|
194
|
+
if (!Array.isArray(models)) throw new require_errors.ValidationError("'models' must be an explicit array (no 'all' shortcut)");
|
|
195
|
+
if (models.length < 1 || models.length > MAX_MODELS_FULL) throw new require_errors.ValidationError(`'models' length must be in [1, ${MAX_MODELS_FULL}]`);
|
|
196
|
+
for (const m of models) if (typeof m !== "string" || m.length === 0) throw new require_errors.ValidationError("'models' entries must be non-empty strings");
|
|
197
|
+
const promptsArg = args.prompts;
|
|
198
|
+
let prompts;
|
|
199
|
+
if (promptsArg === "all") prompts = "all";
|
|
200
|
+
else if (Array.isArray(promptsArg)) {
|
|
201
|
+
if (promptsArg.length < 1 || promptsArg.length > MAX_PROMPTS_FULL) throw new require_errors.ValidationError(`'prompts' length must be in [1, ${MAX_PROMPTS_FULL}]`);
|
|
202
|
+
for (const p of promptsArg) if (typeof p !== "string" || p.length === 0) throw new require_errors.ValidationError("'prompts' entries must be non-empty strings");
|
|
203
|
+
prompts = promptsArg;
|
|
204
|
+
} else throw new require_errors.ValidationError("'prompts' must be 'all' or an array of prompt ids");
|
|
205
|
+
const samples = clampInt(args.samples, DEFAULT_SAMPLES, 1, MAX_SAMPLES);
|
|
206
|
+
const parallel = clampInt(args.parallel, 1, 1, MAX_PARALLEL);
|
|
207
|
+
let judgeFor;
|
|
208
|
+
if (args.judgeFor !== void 0) {
|
|
209
|
+
if (!Array.isArray(args.judgeFor)) throw new require_errors.ValidationError("'judgeFor' must be an array of dimension names");
|
|
210
|
+
const allowed = new Set(ALLOWED_JUDGE_DIMENSIONS);
|
|
211
|
+
for (const d of args.judgeFor) if (typeof d !== "string" || !allowed.has(d)) throw new require_errors.ValidationError(`'judgeFor' entries must be one of: ${ALLOWED_JUDGE_DIMENSIONS.join(", ")}`);
|
|
212
|
+
judgeFor = args.judgeFor;
|
|
213
|
+
}
|
|
214
|
+
let includeRaw = false;
|
|
215
|
+
if (args.includeRaw !== void 0) {
|
|
216
|
+
if (typeof args.includeRaw !== "boolean") throw new require_errors.ValidationError("'includeRaw' must be a boolean");
|
|
217
|
+
includeRaw = args.includeRaw;
|
|
218
|
+
}
|
|
219
|
+
let outlierThreshold;
|
|
220
|
+
if (args.outlierThreshold !== void 0) {
|
|
221
|
+
if (typeof args.outlierThreshold !== "number" || !Number.isFinite(args.outlierThreshold)) throw new require_errors.ValidationError("'outlierThreshold' must be a finite number");
|
|
222
|
+
outlierThreshold = Math.max(.01, Math.min(1, args.outlierThreshold));
|
|
223
|
+
}
|
|
224
|
+
let outlierRerun = false;
|
|
225
|
+
if (args.outlierRerun !== void 0) {
|
|
226
|
+
if (typeof args.outlierRerun !== "boolean") throw new require_errors.ValidationError("'outlierRerun' must be a boolean");
|
|
227
|
+
outlierRerun = args.outlierRerun;
|
|
228
|
+
}
|
|
229
|
+
let judges;
|
|
230
|
+
if (args.judges !== void 0) {
|
|
231
|
+
if (!Array.isArray(args.judges)) throw new require_errors.ValidationError("'judges' must be an array of judge model ids");
|
|
232
|
+
if (args.judges.length < 1 || args.judges.length > MAX_JUDGES) throw new require_errors.ValidationError(`'judges' length must be in [1, ${MAX_JUDGES}]`);
|
|
233
|
+
for (const j of args.judges) {
|
|
234
|
+
if (typeof j !== "string" || j.length === 0) throw new require_errors.ValidationError("'judges' entries must be non-empty strings");
|
|
235
|
+
if (!JUDGE_ID_PATTERN.test(j)) throw new require_errors.ValidationError(`'judges' entry "${j}" has invalid characters`);
|
|
236
|
+
}
|
|
237
|
+
judges = args.judges;
|
|
238
|
+
}
|
|
239
|
+
let hub;
|
|
240
|
+
if (args.hub !== void 0) {
|
|
241
|
+
if (typeof args.hub !== "string" || args.hub.length === 0) throw new require_errors.ValidationError("'hub' must be a non-empty string");
|
|
242
|
+
if (!/^[A-Za-z0-9._-]+$/.test(args.hub)) throw new require_errors.ValidationError(`'hub' has invalid characters: ${args.hub}`);
|
|
243
|
+
hub = args.hub;
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
models,
|
|
247
|
+
prompts,
|
|
248
|
+
samples,
|
|
249
|
+
parallel,
|
|
250
|
+
...hub !== void 0 ? { hub } : {},
|
|
251
|
+
judgeFor,
|
|
252
|
+
judges,
|
|
253
|
+
includeRaw,
|
|
254
|
+
outlierThreshold,
|
|
255
|
+
outlierRerun
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const DIMENSIONS = require_scoring.OBJECTIVE_DIMENSIONS;
|
|
259
|
+
function promptDomain(prompt) {
|
|
260
|
+
return prompt.frontmatter.domain ?? "english";
|
|
261
|
+
}
|
|
262
|
+
/** Mean of the populated dimension scores for a single sample. */
|
|
263
|
+
function sampleDimensionMean(s) {
|
|
264
|
+
const vals = DIMENSIONS.map((d) => s[d]);
|
|
265
|
+
if (typeof s.instruction_following === "number" && Number.isFinite(s.instruction_following)) vals.push(s.instruction_following);
|
|
266
|
+
return vals.reduce((a, b) => a + b, 0) / vals.length;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Population stddev (divides by N, not N-1) — matches the convention the
|
|
270
|
+
* benchmark spec uses. For N=1 this is 0 by definition; the existing path
|
|
271
|
+
* just returns 0 without dividing. N=0 short-circuits to 0 above the loop.
|
|
272
|
+
*/
|
|
273
|
+
function meanAndStddev(values) {
|
|
274
|
+
if (values.length === 0) return {
|
|
275
|
+
mean: 0,
|
|
276
|
+
stddev: 0
|
|
277
|
+
};
|
|
278
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
279
|
+
return {
|
|
280
|
+
mean,
|
|
281
|
+
stddev: Math.sqrt(values.reduce((a, b) => a + (b - mean) ** 2, 0) / values.length)
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function emptyDimensionScores() {
|
|
285
|
+
return {
|
|
286
|
+
exploration: 0,
|
|
287
|
+
tool_reliability: 0,
|
|
288
|
+
self_stop: 0,
|
|
289
|
+
parallelism: 0,
|
|
290
|
+
efficiency: 0
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function aggregateAcrossPrompts(samples, prompts) {
|
|
294
|
+
const scores = emptyDimensionScores();
|
|
295
|
+
const stddev = { ...scores };
|
|
296
|
+
if (samples.length === 0) return {
|
|
297
|
+
n_samples: 0,
|
|
298
|
+
scores,
|
|
299
|
+
stddev,
|
|
300
|
+
per_prompt: {}
|
|
301
|
+
};
|
|
302
|
+
for (const dim of DIMENSIONS) {
|
|
303
|
+
const { mean, stddev: sd } = meanAndStddev(samples.map((s) => s.scores[dim]));
|
|
304
|
+
scores[dim] = mean;
|
|
305
|
+
stddev[dim] = sd;
|
|
306
|
+
}
|
|
307
|
+
const ifValues = [];
|
|
308
|
+
for (const s of samples) {
|
|
309
|
+
const v = s.scores.instruction_following;
|
|
310
|
+
if (typeof v === "number" && Number.isFinite(v)) ifValues.push(v);
|
|
311
|
+
}
|
|
312
|
+
if (ifValues.length > 0) {
|
|
313
|
+
const { mean, stddev: sd } = meanAndStddev(ifValues);
|
|
314
|
+
scores.instruction_following = mean;
|
|
315
|
+
stddev.instruction_following = sd;
|
|
316
|
+
}
|
|
317
|
+
const byPrompt = /* @__PURE__ */ new Map();
|
|
318
|
+
for (const s of samples) {
|
|
319
|
+
const arr = byPrompt.get(s.prompt) ?? [];
|
|
320
|
+
arr.push(s);
|
|
321
|
+
byPrompt.set(s.prompt, arr);
|
|
322
|
+
}
|
|
323
|
+
const per_prompt = {};
|
|
324
|
+
for (const promptId of [...byPrompt.keys()].sort()) {
|
|
325
|
+
const promptSamples = byPrompt.get(promptId);
|
|
326
|
+
const dims = emptyDimensionScores();
|
|
327
|
+
for (const dim of DIMENSIONS) dims[dim] = meanAndStddev(promptSamples.map((s) => s.scores[dim])).mean;
|
|
328
|
+
const ifPerPrompt = [];
|
|
329
|
+
for (const s of promptSamples) {
|
|
330
|
+
const v = s.scores.instruction_following;
|
|
331
|
+
if (typeof v === "number" && Number.isFinite(v)) ifPerPrompt.push(v);
|
|
332
|
+
}
|
|
333
|
+
if (ifPerPrompt.length > 0) dims.instruction_following = meanAndStddev(ifPerPrompt).mean;
|
|
334
|
+
per_prompt[promptId] = dims;
|
|
335
|
+
}
|
|
336
|
+
const judgeScores = /* @__PURE__ */ new Map();
|
|
337
|
+
for (const s of samples) {
|
|
338
|
+
if (!s.judges_raw) continue;
|
|
339
|
+
for (const [id, score] of Object.entries(s.judges_raw)) {
|
|
340
|
+
const arr = judgeScores.get(id) ?? [];
|
|
341
|
+
if (typeof score === "number" && Number.isFinite(score)) arr.push(score);
|
|
342
|
+
judgeScores.set(id, arr);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const out = {
|
|
346
|
+
n_samples: samples.length,
|
|
347
|
+
scores,
|
|
348
|
+
stddev,
|
|
349
|
+
per_prompt
|
|
350
|
+
};
|
|
351
|
+
if (prompts && prompts.length > 0) {
|
|
352
|
+
const domainOf = /* @__PURE__ */ new Map();
|
|
353
|
+
for (const p of prompts) domainOf.set(p.id, promptDomain(p));
|
|
354
|
+
const samplesByDomain = /* @__PURE__ */ new Map();
|
|
355
|
+
for (const s of samples) {
|
|
356
|
+
const d = domainOf.get(s.prompt) ?? "english";
|
|
357
|
+
const arr = samplesByDomain.get(d) ?? [];
|
|
358
|
+
arr.push(s);
|
|
359
|
+
samplesByDomain.set(d, arr);
|
|
360
|
+
}
|
|
361
|
+
if (samplesByDomain.size > 0) {
|
|
362
|
+
const domains = {};
|
|
363
|
+
for (const d of [...samplesByDomain.keys()].sort()) {
|
|
364
|
+
const means = samplesByDomain.get(d).map((s) => sampleDimensionMean(s.scores));
|
|
365
|
+
domains[d] = means.reduce((a, b) => a + b, 0) / means.length;
|
|
366
|
+
}
|
|
367
|
+
out.domains = domains;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (judgeScores.size > 0) {
|
|
371
|
+
const judges_raw = {};
|
|
372
|
+
for (const id of [...judgeScores.keys()].sort()) {
|
|
373
|
+
const vs = judgeScores.get(id);
|
|
374
|
+
judges_raw[id] = vs.length === 0 ? null : meanAndStddev(vs).mean;
|
|
375
|
+
}
|
|
376
|
+
out.judges_raw = judges_raw;
|
|
377
|
+
}
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
380
|
+
async function runMatrix(input) {
|
|
381
|
+
const benchPromptCache = /* @__PURE__ */ new Map();
|
|
382
|
+
for (const p of input.prompts) benchPromptCache.set(p.id, promptToBenchPrompt(p));
|
|
383
|
+
const results = {};
|
|
384
|
+
for (const model of input.models) {
|
|
385
|
+
const samples = [];
|
|
386
|
+
for (const prompt of input.prompts) {
|
|
387
|
+
const batches = chunk(Array.from({ length: input.samples }, () => prompt), input.parallel);
|
|
388
|
+
for (const batch of batches) {
|
|
389
|
+
const out = await Promise.all(batch.map((p) => runOne({
|
|
390
|
+
deps: input.deps,
|
|
391
|
+
model,
|
|
392
|
+
hub: input.hub,
|
|
393
|
+
prompt: p,
|
|
394
|
+
benchPrompt: benchPromptCache.get(p.id)
|
|
395
|
+
})));
|
|
396
|
+
samples.push(...out);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
results[model] = aggregateAcrossPrompts(samples, input.prompts);
|
|
400
|
+
}
|
|
401
|
+
const promptIds = input.prompts.map((p) => p.id);
|
|
402
|
+
return {
|
|
403
|
+
results,
|
|
404
|
+
file: await writeResultsAtomic({
|
|
405
|
+
version: (/* @__PURE__ */ new Date()).toISOString(),
|
|
406
|
+
config: {
|
|
407
|
+
samples_per_pair: input.samples,
|
|
408
|
+
models_tested: input.models,
|
|
409
|
+
prompts_used: promptIds
|
|
410
|
+
},
|
|
411
|
+
results
|
|
412
|
+
}, input.resultsDir),
|
|
413
|
+
ranBatch: {
|
|
414
|
+
models: input.models,
|
|
415
|
+
prompts: promptIds,
|
|
416
|
+
samples: input.samples
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
function chunk(arr, size) {
|
|
421
|
+
const out = [];
|
|
422
|
+
for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size));
|
|
423
|
+
return out;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* `runMatrixFull` is the headless full-matrix runner used by `/bench/.actions/runFull`.
|
|
427
|
+
*
|
|
428
|
+
* Differences vs `runMatrix`:
|
|
429
|
+
* - Allows up to 200 models / 200 prompts (vs 5/5 for the fast `run` action).
|
|
430
|
+
* - Flushes the results JSON to disk after every (model, prompt) pair so a
|
|
431
|
+
* SIGKILL mid-run preserves the entries already completed.
|
|
432
|
+
* - Retries rate-limit errors with exponential backoff (2s/4s/8s, max 3
|
|
433
|
+
* retries). Auth errors (`INVALID_API_KEY`, `UNAUTHENTICATED`, etc.) fail
|
|
434
|
+
* immediately without retry.
|
|
435
|
+
* - Records per-sample failures on the model aggregate's `errors[]` instead
|
|
436
|
+
* of throwing — one bad sample doesn't block the rest of the matrix.
|
|
437
|
+
* - Emits per-sample progress via `onProgress` so callers can stream live
|
|
438
|
+
* output (`▸ <model> × <prompt> sample N/M: ...`).
|
|
439
|
+
*/
|
|
440
|
+
async function runMatrixFull(input) {
|
|
441
|
+
const benchPromptCache = /* @__PURE__ */ new Map();
|
|
442
|
+
for (const p of input.prompts) benchPromptCache.set(p.id, promptToBenchPrompt(p));
|
|
443
|
+
const sleep = input.sleep ?? defaultSleep;
|
|
444
|
+
const promptIds = input.prompts.map((p) => p.id);
|
|
445
|
+
const isoVersion = (/* @__PURE__ */ new Date()).toISOString();
|
|
446
|
+
const includeRaw = input.includeRaw ?? false;
|
|
447
|
+
const effectiveJudges = input.judges && input.judges.length > 0 ? [...input.judges] : [];
|
|
448
|
+
const judgeForRequested = input.judgeFor?.includes("instruction_following") ?? false;
|
|
449
|
+
let resultsFileJudgeModel = null;
|
|
450
|
+
if (judgeForRequested) if (effectiveJudges.length > 1) resultsFileJudgeModel = [...effectiveJudges];
|
|
451
|
+
else if (effectiveJudges.length === 1) resultsFileJudgeModel = effectiveJudges[0];
|
|
452
|
+
else resultsFileJudgeModel = input.judgeModel ?? require_judge.DEFAULT_JUDGE_MODEL;
|
|
453
|
+
const results = {};
|
|
454
|
+
const samplesByModel = {};
|
|
455
|
+
const errorsByModel = {};
|
|
456
|
+
const rawByModel = {};
|
|
457
|
+
let lastFile = "";
|
|
458
|
+
let lastRawFile = null;
|
|
459
|
+
const outlierThreshold = input.outlierThreshold ?? require_outlier.DEFAULT_OUTLIER_THRESHOLD;
|
|
460
|
+
const outlierRerun = input.outlierRerun ?? false;
|
|
461
|
+
const runSampleBatch = async (model, prompt, sampleIndices, totalSamples) => {
|
|
462
|
+
for (const batch of chunk(sampleIndices, input.parallel)) {
|
|
463
|
+
const settled = await Promise.all(batch.map(async (sampleIndex) => ({
|
|
464
|
+
sampleIndex,
|
|
465
|
+
outcome: await runOneWithRetry({
|
|
466
|
+
deps: input.deps,
|
|
467
|
+
model,
|
|
468
|
+
hub: input.hub,
|
|
469
|
+
prompt,
|
|
470
|
+
benchPrompt: benchPromptCache.get(prompt.id),
|
|
471
|
+
sleep
|
|
472
|
+
})
|
|
473
|
+
})));
|
|
474
|
+
for (const { sampleIndex, outcome } of settled) {
|
|
475
|
+
const event = {
|
|
476
|
+
model,
|
|
477
|
+
prompt: prompt.id,
|
|
478
|
+
sampleIndex,
|
|
479
|
+
totalSamples,
|
|
480
|
+
status: outcome.kind === "ok" ? "ok" : "error"
|
|
481
|
+
};
|
|
482
|
+
if (outcome.kind === "ok") {
|
|
483
|
+
if (judgeForRequested) if (effectiveJudges.length > 0) {
|
|
484
|
+
const perJudge = await Promise.all(effectiveJudges.map((j) => require_judge.judgeInstructionFollowing(input.deps, benchPromptCache.get(prompt.id), outcome.value.raw, j, input.judgeHub)));
|
|
485
|
+
const judgesRaw = {};
|
|
486
|
+
effectiveJudges.forEach((j, i) => {
|
|
487
|
+
judgesRaw[j] = perJudge[i];
|
|
488
|
+
});
|
|
489
|
+
outcome.value.judges_raw = judgesRaw;
|
|
490
|
+
const aggregate = require_judge.aggregateJudgeScores(perJudge);
|
|
491
|
+
if (aggregate !== null) outcome.value.scores.instruction_following = aggregate;
|
|
492
|
+
} else {
|
|
493
|
+
const score = await require_judge.judgeInstructionFollowing(input.deps, benchPromptCache.get(prompt.id), outcome.value.raw, input.judgeModel, input.judgeHub);
|
|
494
|
+
if (score !== null) outcome.value.scores.instruction_following = score;
|
|
495
|
+
}
|
|
496
|
+
samplesByModel[model].push(outcome.value);
|
|
497
|
+
event.scores = outcome.value.scores;
|
|
498
|
+
if (includeRaw) rawByModel[model][prompt.id].push({
|
|
499
|
+
sampleIndex,
|
|
500
|
+
raw: outcome.value.raw
|
|
501
|
+
});
|
|
502
|
+
} else {
|
|
503
|
+
errorsByModel[model].push(outcome.error);
|
|
504
|
+
event.error = outcome.error;
|
|
505
|
+
}
|
|
506
|
+
input.onProgress?.(event);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
const buildResultsFile = (outliers$1) => ({
|
|
511
|
+
version: isoVersion,
|
|
512
|
+
judge_model: resultsFileJudgeModel,
|
|
513
|
+
config: {
|
|
514
|
+
samples_per_pair: input.samples,
|
|
515
|
+
models_tested: input.models,
|
|
516
|
+
prompts_used: promptIds
|
|
517
|
+
},
|
|
518
|
+
results,
|
|
519
|
+
outliers: outliers$1
|
|
520
|
+
});
|
|
521
|
+
for (const model of input.models) {
|
|
522
|
+
samplesByModel[model] = [];
|
|
523
|
+
errorsByModel[model] = [];
|
|
524
|
+
if (includeRaw) rawByModel[model] = {};
|
|
525
|
+
for (const prompt of input.prompts) {
|
|
526
|
+
if (includeRaw) rawByModel[model][prompt.id] = [];
|
|
527
|
+
await runSampleBatch(model, prompt, Array.from({ length: input.samples }, (_, i) => i + 1), input.samples);
|
|
528
|
+
const agg = aggregateAcrossPrompts(samplesByModel[model], input.prompts);
|
|
529
|
+
const errs = errorsByModel[model];
|
|
530
|
+
results[model] = errs.length > 0 ? {
|
|
531
|
+
...agg,
|
|
532
|
+
errors: errs
|
|
533
|
+
} : agg;
|
|
534
|
+
lastFile = await writeResultsAtomic(buildResultsFile([]), input.resultsDir);
|
|
535
|
+
if (includeRaw) lastRawFile = await writeRawResultsAtomic({
|
|
536
|
+
version: isoVersion,
|
|
537
|
+
config: {
|
|
538
|
+
samples_per_pair: input.samples,
|
|
539
|
+
models_tested: input.models,
|
|
540
|
+
prompts_used: promptIds
|
|
541
|
+
},
|
|
542
|
+
raw: rawByModel
|
|
543
|
+
}, input.resultsDir);
|
|
544
|
+
await input.onPairComplete?.(model, prompt.id, lastFile);
|
|
545
|
+
}
|
|
546
|
+
await input.onModelComplete?.(model, lastFile);
|
|
547
|
+
}
|
|
548
|
+
let outliers = require_outlier.findOutliers(samplesByModel, outlierThreshold);
|
|
549
|
+
if (outlierRerun && outliers.length > 0) {
|
|
550
|
+
const promptById = new Map(input.prompts.map((p) => [p.id, p]));
|
|
551
|
+
const rerunByModel = /* @__PURE__ */ new Map();
|
|
552
|
+
for (const o of outliers) {
|
|
553
|
+
if (!promptById.has(o.prompt)) continue;
|
|
554
|
+
const set = rerunByModel.get(o.model) ?? /* @__PURE__ */ new Set();
|
|
555
|
+
set.add(o.prompt);
|
|
556
|
+
rerunByModel.set(o.model, set);
|
|
557
|
+
}
|
|
558
|
+
for (const [model, promptIds$1] of rerunByModel) {
|
|
559
|
+
const countByPrompt = /* @__PURE__ */ new Map();
|
|
560
|
+
for (const s of samplesByModel[model]) countByPrompt.set(s.prompt, (countByPrompt.get(s.prompt) ?? 0) + 1);
|
|
561
|
+
for (const promptId of promptIds$1) {
|
|
562
|
+
const prompt = promptById.get(promptId);
|
|
563
|
+
const existingForPair = countByPrompt.get(promptId) ?? 0;
|
|
564
|
+
const extra = Math.min(3, 2 * input.samples - existingForPair);
|
|
565
|
+
if (extra <= 0) continue;
|
|
566
|
+
await runSampleBatch(model, prompt, Array.from({ length: extra }, (_, i) => existingForPair + i + 1), existingForPair + extra);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
for (const model of rerunByModel.keys()) {
|
|
570
|
+
const agg = aggregateAcrossPrompts(samplesByModel[model], input.prompts);
|
|
571
|
+
const errs = errorsByModel[model];
|
|
572
|
+
results[model] = errs.length > 0 ? {
|
|
573
|
+
...agg,
|
|
574
|
+
errors: errs
|
|
575
|
+
} : agg;
|
|
576
|
+
}
|
|
577
|
+
outliers = require_outlier.findOutliers(samplesByModel, outlierThreshold);
|
|
578
|
+
}
|
|
579
|
+
lastFile = await writeResultsAtomic(buildResultsFile(outliers), input.resultsDir);
|
|
580
|
+
if (includeRaw) lastRawFile = await writeRawResultsAtomic({
|
|
581
|
+
version: isoVersion,
|
|
582
|
+
config: {
|
|
583
|
+
samples_per_pair: input.samples,
|
|
584
|
+
models_tested: input.models,
|
|
585
|
+
prompts_used: promptIds
|
|
586
|
+
},
|
|
587
|
+
raw: rawByModel
|
|
588
|
+
}, input.resultsDir);
|
|
589
|
+
return {
|
|
590
|
+
results,
|
|
591
|
+
file: lastFile,
|
|
592
|
+
rawFile: lastRawFile,
|
|
593
|
+
ranBatch: {
|
|
594
|
+
models: input.models,
|
|
595
|
+
prompts: promptIds,
|
|
596
|
+
samples: input.samples
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Wraps `runOne` with bounded exponential backoff for rate-limit errors and
|
|
602
|
+
* fast-fail for auth errors. Other errors are non-retryable but classified as
|
|
603
|
+
* "other" so they still surface in `errors[]`.
|
|
604
|
+
*/
|
|
605
|
+
async function runOneWithRetry(input) {
|
|
606
|
+
for (let attempts = 1;; attempts++) try {
|
|
607
|
+
return {
|
|
608
|
+
kind: "ok",
|
|
609
|
+
value: await runOne({
|
|
610
|
+
deps: input.deps,
|
|
611
|
+
model: input.model,
|
|
612
|
+
hub: input.hub,
|
|
613
|
+
prompt: input.prompt,
|
|
614
|
+
benchPrompt: input.benchPrompt
|
|
615
|
+
})
|
|
616
|
+
};
|
|
617
|
+
} catch (err) {
|
|
618
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
619
|
+
const cls = classifyError(message);
|
|
620
|
+
if (cls === "rate_limit" && attempts <= RATE_LIMIT_MAX_RETRIES) {
|
|
621
|
+
await input.sleep(RATE_LIMIT_BASE_BACKOFF_MS * 2 ** (attempts - 1));
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
return {
|
|
625
|
+
kind: "error",
|
|
626
|
+
error: {
|
|
627
|
+
kind: cls,
|
|
628
|
+
path: "/dev/agent/.actions/run",
|
|
629
|
+
message,
|
|
630
|
+
attempts
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
const RATE_LIMIT_PATTERNS = /(rate.*limit|429|too many requests)/i;
|
|
636
|
+
const AUTH_ERROR_PATTERNS = /(invalid[ _]api[ _]key|unauthenticated|unauthorized)/i;
|
|
637
|
+
function classifyError(message) {
|
|
638
|
+
if (RATE_LIMIT_PATTERNS.test(message)) return "rate_limit";
|
|
639
|
+
if (AUTH_ERROR_PATTERNS.test(message)) return "auth";
|
|
640
|
+
return "other";
|
|
641
|
+
}
|
|
642
|
+
function defaultSleep(ms) {
|
|
643
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Write `payload` to `<dir>/<ISO-date>.json` atomically (write to a randomised
|
|
647
|
+
* `.tmp-<id>.json` then rename). Best-effort redaction runs before
|
|
648
|
+
* serialisation: both sensitive labels (api_key / Authorization / …) and
|
|
649
|
+
* common credential VALUE shapes (Bearer tokens, `sk-`/`ya29.` keys, PEM
|
|
650
|
+
* blocks, AWS/Slack ids) are blanked so credentials that leak into agent-run
|
|
651
|
+
* traces are scrubbed from the on-disk record. This is a safety net, not a
|
|
652
|
+
* guarantee — the design keeps credentials out of traces. Aborts (without
|
|
653
|
+
* writing) when
|
|
654
|
+
* the serialised payload would exceed `MAX_RESULTS_SIZE_BYTES` — the failure
|
|
655
|
+
* mode is intentionally noisy so a bug that bloats the results dir never lands
|
|
656
|
+
* on disk silently.
|
|
657
|
+
*/
|
|
658
|
+
async function writeResultsAtomic(payload, dir) {
|
|
659
|
+
const safe = sanitiseForResults(payload);
|
|
660
|
+
const json = `${JSON.stringify(safe, null, 2)}\n`;
|
|
661
|
+
const bytes = Buffer.byteLength(json, "utf8");
|
|
662
|
+
if (bytes > MAX_RESULTS_SIZE_BYTES) throw new require_errors.ValidationError(`results JSON is ${bytes} bytes — exceeds ${MAX_RESULTS_SIZE_BYTES} byte cap. If raw traces are needed pass \`includeRaw: true\` (writes a separate \`<stamp>-raw.json\`).`);
|
|
663
|
+
return writeAtomic(dir, `${payload.version.replace(/[:.]/g, "-")}.json`, json);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Write the raw-trace sidecar to `<dir>/<ISO-date>-raw.json` atomically. Uses
|
|
667
|
+
* the same sensitive-string redaction as the main results file. Has no size
|
|
668
|
+
* cap — the maintainer opted in via `includeRaw: true` and a hard fail
|
|
669
|
+
* mid-run would discard the agent traces they explicitly asked to keep.
|
|
670
|
+
*/
|
|
671
|
+
async function writeRawResultsAtomic(payload, dir) {
|
|
672
|
+
const safe = sanitiseForResults(payload);
|
|
673
|
+
const json = `${JSON.stringify(safe, null, 2)}\n`;
|
|
674
|
+
return writeAtomic(dir, `${payload.version.replace(/[:.]/g, "-")}-raw.json`, json);
|
|
675
|
+
}
|
|
676
|
+
async function writeAtomic(dir, filename, json) {
|
|
677
|
+
await node_fs.promises.mkdir(dir, { recursive: true });
|
|
678
|
+
const finalPath = (0, node_path.join)(dir, filename);
|
|
679
|
+
const tmpPath = (0, node_path.join)(dir, `.tmp-${(0, node_crypto.randomBytes)(6).toString("hex")}.json`);
|
|
680
|
+
await node_fs.promises.writeFile(tmpPath, json, "utf8");
|
|
681
|
+
try {
|
|
682
|
+
await node_fs.promises.rename(tmpPath, finalPath);
|
|
683
|
+
} catch (err) {
|
|
684
|
+
await node_fs.promises.rm(tmpPath, { force: true });
|
|
685
|
+
throw err;
|
|
686
|
+
}
|
|
687
|
+
return finalPath;
|
|
688
|
+
}
|
|
689
|
+
function sanitiseForResults(value) {
|
|
690
|
+
let cleaned = JSON.stringify(value);
|
|
691
|
+
for (const re of SECRET_VALUE_PATTERNS) cleaned = cleaned.replace(re, "[REDACTED]");
|
|
692
|
+
for (const re of SENSITIVE_PATTERNS) cleaned = cleaned.replace(re, "[REDACTED]");
|
|
693
|
+
return JSON.parse(cleaned);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
//#endregion
|
|
697
|
+
exports.aggregateAcrossPrompts = aggregateAcrossPrompts;
|
|
698
|
+
exports.defaultBenchSystem = defaultBenchSystem;
|
|
699
|
+
exports.defaultBenchTools = defaultBenchTools;
|
|
700
|
+
exports.extractExpectedOutcomes = extractExpectedOutcomes;
|
|
701
|
+
exports.extractExpectedPaths = extractExpectedPaths;
|
|
702
|
+
exports.meanAndStddev = meanAndStddev;
|
|
703
|
+
exports.promptToBenchPrompt = promptToBenchPrompt;
|
|
704
|
+
exports.runMatrix = runMatrix;
|
|
705
|
+
exports.runMatrixFull = runMatrixFull;
|
|
706
|
+
exports.runOne = runOne;
|
|
707
|
+
exports.validateRunArgs = validateRunArgs;
|
|
708
|
+
exports.validateRunFullArgs = validateRunFullArgs;
|
|
709
|
+
exports.writeRawResultsAtomic = writeRawResultsAtomic;
|
|
710
|
+
exports.writeResultsAtomic = writeResultsAtomic;
|