@axlsdk/eval 0.13.6 → 0.13.8
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/README.md +170 -6
- package/dist/chunk-CMWRFU3P.js +653 -0
- package/dist/chunk-CMWRFU3P.js.map +1 -0
- package/dist/cli.cjs +577 -73
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +169 -23
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +413 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +74 -2
- package/dist/index.d.ts +74 -2
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-F2AALK5U.js +0 -290
- package/dist/chunk-F2AALK5U.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -30,11 +30,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
aggregateRuns: () => aggregateRuns,
|
|
33
34
|
dataset: () => dataset,
|
|
34
35
|
defineEval: () => defineEval,
|
|
35
36
|
evalCompare: () => evalCompare,
|
|
36
37
|
llmScorer: () => llmScorer,
|
|
37
38
|
normalizeScorerResult: () => normalizeScorerResult,
|
|
39
|
+
pairedBootstrapCI: () => pairedBootstrapCI,
|
|
40
|
+
rescore: () => rescore,
|
|
38
41
|
runEval: () => runEval,
|
|
39
42
|
scorer: () => scorer
|
|
40
43
|
});
|
|
@@ -159,8 +162,7 @@ function defineEval(config) {
|
|
|
159
162
|
return config;
|
|
160
163
|
}
|
|
161
164
|
|
|
162
|
-
// src/
|
|
163
|
-
var import_node_crypto = require("crypto");
|
|
165
|
+
// src/utils.ts
|
|
164
166
|
function computeStats(scores) {
|
|
165
167
|
if (scores.length === 0) return { mean: 0, min: 0, max: 0, p50: 0, p95: 0 };
|
|
166
168
|
const sorted = [...scores].sort((a, b) => a - b);
|
|
@@ -174,6 +176,9 @@ function computeStats(scores) {
|
|
|
174
176
|
function round(n) {
|
|
175
177
|
return Math.round(n * 1e3) / 1e3;
|
|
176
178
|
}
|
|
179
|
+
|
|
180
|
+
// src/runner.ts
|
|
181
|
+
var import_node_crypto = require("crypto");
|
|
177
182
|
function parseCost(cost) {
|
|
178
183
|
const match = cost.match(/^\$?([\d.]+)$/);
|
|
179
184
|
if (!match) throw new Error(`Invalid cost format: "${cost}"`);
|
|
@@ -221,6 +226,7 @@ async function runEval(config, executeWorkflow, runtime) {
|
|
|
221
226
|
evalItem.duration = Date.now() - itemStart;
|
|
222
227
|
evalItem.output = result.output;
|
|
223
228
|
evalItem.cost = result.cost;
|
|
229
|
+
if (result.metadata) evalItem.metadata = result.metadata;
|
|
224
230
|
if (result.cost != null) {
|
|
225
231
|
totalCost += result.cost;
|
|
226
232
|
}
|
|
@@ -310,11 +316,42 @@ async function runEval(config, executeWorkflow, runtime) {
|
|
|
310
316
|
}
|
|
311
317
|
const durations = evalItems.filter((i) => !i.error && i.duration != null).map((i) => i.duration);
|
|
312
318
|
const timing = durations.length > 0 ? computeStats(durations) : void 0;
|
|
319
|
+
const scorerTypes = {};
|
|
320
|
+
for (const s of config.scorers) {
|
|
321
|
+
scorerTypes[s.name] = s.isLlm ? "llm" : "deterministic";
|
|
322
|
+
}
|
|
323
|
+
const totalModelCalls = /* @__PURE__ */ new Map();
|
|
324
|
+
for (const item of evalItems) {
|
|
325
|
+
const itemCounts = item.metadata?.modelCallCounts;
|
|
326
|
+
if (itemCounts && typeof itemCounts === "object") {
|
|
327
|
+
for (const [m, count] of Object.entries(itemCounts)) {
|
|
328
|
+
if (typeof count === "number")
|
|
329
|
+
totalModelCalls.set(m, (totalModelCalls.get(m) ?? 0) + count);
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
const itemModels = item.metadata?.models;
|
|
333
|
+
if (Array.isArray(itemModels)) {
|
|
334
|
+
for (const m of itemModels) {
|
|
335
|
+
if (typeof m === "string") totalModelCalls.set(m, (totalModelCalls.get(m) ?? 0) + 1);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const modelsMeta = {};
|
|
341
|
+
if (totalModelCalls.size > 0) {
|
|
342
|
+
const sorted = [...totalModelCalls.entries()].sort((a, b) => b[1] - a[1]);
|
|
343
|
+
modelsMeta.models = sorted.map(([m]) => m);
|
|
344
|
+
modelsMeta.modelCounts = Object.fromEntries(sorted);
|
|
345
|
+
}
|
|
313
346
|
return {
|
|
314
347
|
id,
|
|
315
348
|
workflow: config.workflow,
|
|
316
349
|
dataset: config.dataset.name,
|
|
317
|
-
metadata:
|
|
350
|
+
metadata: {
|
|
351
|
+
...config.metadata,
|
|
352
|
+
scorerTypes,
|
|
353
|
+
...modelsMeta
|
|
354
|
+
},
|
|
318
355
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
319
356
|
totalCost,
|
|
320
357
|
duration: Date.now() - startTime,
|
|
@@ -323,76 +360,223 @@ async function runEval(config, executeWorkflow, runtime) {
|
|
|
323
360
|
};
|
|
324
361
|
}
|
|
325
362
|
|
|
326
|
-
// src/
|
|
363
|
+
// src/bootstrap.ts
|
|
364
|
+
function pairedBootstrapCI(differences, options) {
|
|
365
|
+
const n = differences.length;
|
|
366
|
+
if (n === 0) return { lower: 0, upper: 0, mean: 0, pRegression: 0, pImprovement: 0 };
|
|
367
|
+
const nResamples = options?.nResamples ?? 1e3;
|
|
368
|
+
const alpha = options?.alpha ?? 0.05;
|
|
369
|
+
const mean = differences.reduce((a, b) => a + b, 0) / n;
|
|
370
|
+
if (n === 1)
|
|
371
|
+
return {
|
|
372
|
+
lower: differences[0],
|
|
373
|
+
upper: differences[0],
|
|
374
|
+
mean,
|
|
375
|
+
pRegression: mean < 0 ? 1 : 0,
|
|
376
|
+
pImprovement: mean > 0 ? 1 : 0
|
|
377
|
+
};
|
|
378
|
+
if (nResamples <= 0)
|
|
379
|
+
return {
|
|
380
|
+
lower: round2(mean),
|
|
381
|
+
upper: round2(mean),
|
|
382
|
+
mean: round2(mean),
|
|
383
|
+
pRegression: mean < 0 ? 1 : 0,
|
|
384
|
+
pImprovement: mean > 0 ? 1 : 0
|
|
385
|
+
};
|
|
386
|
+
const rng = options?.seed != null ? xorshift32(options.seed) : () => Math.random();
|
|
387
|
+
const resampleMeans = new Array(nResamples);
|
|
388
|
+
for (let r = 0; r < nResamples; r++) {
|
|
389
|
+
let sum = 0;
|
|
390
|
+
for (let i = 0; i < n; i++) {
|
|
391
|
+
sum += differences[Math.floor(rng() * n)];
|
|
392
|
+
}
|
|
393
|
+
resampleMeans[r] = sum / n;
|
|
394
|
+
}
|
|
395
|
+
resampleMeans.sort((a, b) => a - b);
|
|
396
|
+
const lowerIdx = Math.floor(alpha / 2 * nResamples);
|
|
397
|
+
const upperIdx = Math.floor((1 - alpha / 2) * nResamples) - 1;
|
|
398
|
+
let belowZero = 0;
|
|
399
|
+
for (let i = 0; i < nResamples; i++) {
|
|
400
|
+
if (resampleMeans[i] < 0) belowZero++;
|
|
401
|
+
else break;
|
|
402
|
+
}
|
|
403
|
+
let aboveZero = 0;
|
|
404
|
+
for (let i = nResamples - 1; i >= 0; i--) {
|
|
405
|
+
if (resampleMeans[i] > 0) aboveZero++;
|
|
406
|
+
else break;
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
lower: round2(resampleMeans[Math.max(0, lowerIdx)]),
|
|
410
|
+
upper: round2(resampleMeans[Math.min(nResamples - 1, upperIdx)]),
|
|
411
|
+
mean: round2(mean),
|
|
412
|
+
pRegression: round2(belowZero / nResamples),
|
|
413
|
+
pImprovement: round2(aboveZero / nResamples)
|
|
414
|
+
};
|
|
415
|
+
}
|
|
327
416
|
function round2(n) {
|
|
328
|
-
return Math.round(n *
|
|
417
|
+
return Math.round(n * 1e4) / 1e4;
|
|
418
|
+
}
|
|
419
|
+
function xorshift32(seed) {
|
|
420
|
+
let state = seed | 0 || 1;
|
|
421
|
+
return () => {
|
|
422
|
+
state ^= state << 13;
|
|
423
|
+
state ^= state >> 17;
|
|
424
|
+
state ^= state << 5;
|
|
425
|
+
return (state >>> 0) / 4294967296;
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/compare.ts
|
|
430
|
+
function round3(n) {
|
|
431
|
+
return Math.round(n * 1e3) / 1e3;
|
|
432
|
+
}
|
|
433
|
+
var DEFAULT_LLM_THRESHOLD = 0.05;
|
|
434
|
+
var DEFAULT_DETERMINISTIC_THRESHOLD = 0;
|
|
435
|
+
var LEGACY_THRESHOLD = 0.1;
|
|
436
|
+
function resolveThreshold(scorerName, options, metadata) {
|
|
437
|
+
if (typeof options?.thresholds === "number") return options.thresholds;
|
|
438
|
+
if (options?.thresholds && typeof options.thresholds === "object") {
|
|
439
|
+
const perScorer = options.thresholds[scorerName];
|
|
440
|
+
if (perScorer != null) return perScorer;
|
|
441
|
+
}
|
|
442
|
+
const scorerTypes = metadata.scorerTypes;
|
|
443
|
+
if (scorerTypes && scorerName in scorerTypes) {
|
|
444
|
+
return scorerTypes[scorerName] === "llm" ? DEFAULT_LLM_THRESHOLD : DEFAULT_DETERMINISTIC_THRESHOLD;
|
|
445
|
+
}
|
|
446
|
+
return LEGACY_THRESHOLD;
|
|
329
447
|
}
|
|
330
|
-
function evalCompare(baseline, candidate) {
|
|
331
|
-
|
|
448
|
+
function evalCompare(baseline, candidate, options) {
|
|
449
|
+
const baselineRuns = Array.isArray(baseline) ? baseline : [baseline];
|
|
450
|
+
const candidateRuns = Array.isArray(candidate) ? candidate : [candidate];
|
|
451
|
+
if (baselineRuns.length === 0 || candidateRuns.length === 0) {
|
|
452
|
+
throw new Error("Cannot compare empty eval result arrays");
|
|
453
|
+
}
|
|
454
|
+
const baselineRef = baselineRuns[0];
|
|
455
|
+
const candidateRef = candidateRuns[0];
|
|
456
|
+
if (baselineRef.dataset !== candidateRef.dataset) {
|
|
332
457
|
throw new Error(
|
|
333
|
-
`Cannot compare evals from different datasets: "${
|
|
458
|
+
`Cannot compare evals from different datasets: "${baselineRef.dataset}" vs "${candidateRef.dataset}"`
|
|
334
459
|
);
|
|
335
460
|
}
|
|
336
|
-
const baselineScorerNames = Object.keys(
|
|
337
|
-
const candidateScorerNames = Object.keys(
|
|
461
|
+
const baselineScorerNames = Object.keys(baselineRef.summary.scorers).sort();
|
|
462
|
+
const candidateScorerNames = Object.keys(candidateRef.summary.scorers).sort();
|
|
338
463
|
if (JSON.stringify(baselineScorerNames) !== JSON.stringify(candidateScorerNames)) {
|
|
339
464
|
throw new Error(`Cannot compare evals with different scorers`);
|
|
340
465
|
}
|
|
466
|
+
const pairedDiffs = {};
|
|
467
|
+
for (const name of baselineScorerNames) {
|
|
468
|
+
pairedDiffs[name] = [];
|
|
469
|
+
}
|
|
470
|
+
const runCount = Math.min(baselineRuns.length, candidateRuns.length);
|
|
471
|
+
for (let r = 0; r < runCount; r++) {
|
|
472
|
+
const bRun = baselineRuns[r];
|
|
473
|
+
const cRun = candidateRuns[r];
|
|
474
|
+
const minLength = Math.min(bRun.items.length, cRun.items.length);
|
|
475
|
+
for (let i = 0; i < minLength; i++) {
|
|
476
|
+
const bItem = bRun.items[i];
|
|
477
|
+
const cItem = cRun.items[i];
|
|
478
|
+
if (bItem.error || cItem.error) continue;
|
|
479
|
+
for (const name of baselineScorerNames) {
|
|
480
|
+
const bScore = bItem.scores[name];
|
|
481
|
+
const cScore = cItem.scores[name];
|
|
482
|
+
if (bScore != null && cScore != null) {
|
|
483
|
+
pairedDiffs[name].push(cScore - bScore);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
const baselineMeans = {};
|
|
489
|
+
const candidateMeans = {};
|
|
490
|
+
for (const name of baselineScorerNames) {
|
|
491
|
+
baselineMeans[name] = baselineRuns.reduce((sum, r) => sum + (r.summary.scorers[name]?.mean ?? 0), 0) / baselineRuns.length;
|
|
492
|
+
candidateMeans[name] = candidateRuns.reduce((sum, r) => sum + (r.summary.scorers[name]?.mean ?? 0), 0) / candidateRuns.length;
|
|
493
|
+
}
|
|
341
494
|
const scorers = {};
|
|
342
495
|
for (const name of baselineScorerNames) {
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
const
|
|
346
|
-
const
|
|
347
|
-
|
|
496
|
+
const bMeanRaw = baselineMeans[name];
|
|
497
|
+
const cMeanRaw = candidateMeans[name];
|
|
498
|
+
const deltaRaw = cMeanRaw - bMeanRaw;
|
|
499
|
+
const bMean = round3(bMeanRaw);
|
|
500
|
+
const cMean = round3(cMeanRaw);
|
|
501
|
+
const delta = round3(deltaRaw);
|
|
502
|
+
const deltaPercent = bMeanRaw > 0 ? round3(deltaRaw / bMeanRaw * 100) : 0;
|
|
503
|
+
const threshold = resolveThreshold(name, options, baselineRef.metadata);
|
|
504
|
+
const entry = {
|
|
505
|
+
baselineMean: bMean,
|
|
506
|
+
candidateMean: cMean,
|
|
507
|
+
delta,
|
|
508
|
+
deltaPercent
|
|
509
|
+
};
|
|
510
|
+
const diffs = pairedDiffs[name];
|
|
511
|
+
if (diffs.length >= 2) {
|
|
512
|
+
const ci = pairedBootstrapCI(diffs);
|
|
513
|
+
entry.ci = { lower: ci.lower, upper: ci.upper };
|
|
514
|
+
entry.pRegression = ci.pRegression;
|
|
515
|
+
entry.pImprovement = ci.pImprovement;
|
|
516
|
+
entry.n = diffs.length;
|
|
517
|
+
const ciExcludesZero = ci.lower > 0 || ci.upper < 0;
|
|
518
|
+
entry.significant = ciExcludesZero && Math.abs(ci.mean) >= threshold;
|
|
519
|
+
}
|
|
520
|
+
scorers[name] = entry;
|
|
348
521
|
}
|
|
349
522
|
const regressions = [];
|
|
350
523
|
const improvements = [];
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
524
|
+
const itemCount = Math.min(
|
|
525
|
+
...baselineRuns.map((r) => r.items.length),
|
|
526
|
+
...candidateRuns.map((r) => r.items.length)
|
|
527
|
+
);
|
|
528
|
+
for (let i = 0; i < itemCount; i++) {
|
|
529
|
+
const hasBaselineError = baselineRuns.some((r) => r.items[i]?.error);
|
|
530
|
+
const hasCandidateError = candidateRuns.some((r) => r.items[i]?.error);
|
|
531
|
+
if (hasBaselineError || hasCandidateError) continue;
|
|
356
532
|
for (const name of baselineScorerNames) {
|
|
357
|
-
const
|
|
358
|
-
const
|
|
359
|
-
if (
|
|
360
|
-
const
|
|
361
|
-
|
|
533
|
+
const bScores = baselineRuns.map((r) => r.items[i]?.scores[name]).filter((s) => s != null);
|
|
534
|
+
const cScores = candidateRuns.map((r) => r.items[i]?.scores[name]).filter((s) => s != null);
|
|
535
|
+
if (bScores.length === 0 || cScores.length === 0) continue;
|
|
536
|
+
const bAvg = bScores.reduce((a, b) => a + b, 0) / bScores.length;
|
|
537
|
+
const cAvg = cScores.reduce((a, b) => a + b, 0) / cScores.length;
|
|
538
|
+
const delta = round3(cAvg - bAvg);
|
|
539
|
+
const threshold = resolveThreshold(name, options, baselineRef.metadata);
|
|
540
|
+
if (delta < -threshold)
|
|
362
541
|
regressions.push({
|
|
363
542
|
itemIndex: i,
|
|
364
|
-
input:
|
|
543
|
+
input: baselineRef.items[i]?.input,
|
|
365
544
|
scorer: name,
|
|
366
|
-
baselineScore:
|
|
367
|
-
candidateScore:
|
|
545
|
+
baselineScore: round3(bAvg),
|
|
546
|
+
candidateScore: round3(cAvg),
|
|
368
547
|
delta
|
|
369
548
|
});
|
|
370
|
-
else if (delta >
|
|
549
|
+
else if (delta > threshold)
|
|
371
550
|
improvements.push({
|
|
372
551
|
itemIndex: i,
|
|
373
|
-
input:
|
|
552
|
+
input: baselineRef.items[i]?.input,
|
|
374
553
|
scorer: name,
|
|
375
|
-
baselineScore:
|
|
376
|
-
candidateScore:
|
|
554
|
+
baselineScore: round3(bAvg),
|
|
555
|
+
candidateScore: round3(cAvg),
|
|
377
556
|
delta
|
|
378
557
|
});
|
|
379
558
|
}
|
|
380
559
|
}
|
|
381
560
|
let timing;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
const
|
|
387
|
-
|
|
561
|
+
const baselineTimings = baselineRuns.filter((r) => r.summary.timing).map((r) => r.summary.timing.mean);
|
|
562
|
+
const candidateTimings = candidateRuns.filter((r) => r.summary.timing).map((r) => r.summary.timing.mean);
|
|
563
|
+
if (baselineTimings.length > 0 && candidateTimings.length > 0) {
|
|
564
|
+
const bMean = baselineTimings.reduce((a, b) => a + b, 0) / baselineTimings.length;
|
|
565
|
+
const cMean = candidateTimings.reduce((a, b) => a + b, 0) / candidateTimings.length;
|
|
566
|
+
const delta = round3(cMean - bMean);
|
|
567
|
+
const deltaPercent = bMean > 0 ? round3((cMean - bMean) / bMean * 100) : 0;
|
|
568
|
+
timing = { baselineMean: round3(bMean), candidateMean: round3(cMean), delta, deltaPercent };
|
|
388
569
|
}
|
|
389
570
|
let cost;
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
571
|
+
const baselineAvgCost = baselineRuns.reduce((sum, r) => sum + r.totalCost, 0) / baselineRuns.length;
|
|
572
|
+
const candidateAvgCost = candidateRuns.reduce((sum, r) => sum + r.totalCost, 0) / candidateRuns.length;
|
|
573
|
+
if (baselineAvgCost > 0 || candidateAvgCost > 0) {
|
|
574
|
+
const deltaRaw = candidateAvgCost - baselineAvgCost;
|
|
575
|
+
const delta = round3(deltaRaw);
|
|
576
|
+
const deltaPercent = baselineAvgCost > 0 ? round3(deltaRaw / baselineAvgCost * 100) : 0;
|
|
393
577
|
cost = {
|
|
394
|
-
baselineTotal:
|
|
395
|
-
candidateTotal:
|
|
578
|
+
baselineTotal: round3(baselineAvgCost),
|
|
579
|
+
candidateTotal: round3(candidateAvgCost),
|
|
396
580
|
delta,
|
|
397
581
|
deltaPercent
|
|
398
582
|
};
|
|
@@ -403,8 +587,11 @@ function evalCompare(baseline, candidate) {
|
|
|
403
587
|
if (Math.abs(s.delta) > 1e-3) {
|
|
404
588
|
const direction = s.delta > 0 ? "improves" : "regresses";
|
|
405
589
|
const sign = s.delta > 0 ? "+" : "";
|
|
590
|
+
let sigLabel = "";
|
|
591
|
+
if (s.significant === true) sigLabel = " (significant)";
|
|
592
|
+
else if (s.significant === false) sigLabel = " (not significant)";
|
|
406
593
|
parts.push(
|
|
407
|
-
`'${name}' ${direction} by ${sign}${s.deltaPercent.toFixed(1)}% (${s.baselineMean.toFixed(2)} -> ${s.candidateMean.toFixed(2)})`
|
|
594
|
+
`'${name}' ${direction} by ${sign}${s.deltaPercent.toFixed(1)}% (${s.baselineMean.toFixed(2)} -> ${s.candidateMean.toFixed(2)})${sigLabel}`
|
|
408
595
|
);
|
|
409
596
|
}
|
|
410
597
|
}
|
|
@@ -416,10 +603,10 @@ function evalCompare(baseline, candidate) {
|
|
|
416
603
|
const dir = cost.delta > 0 ? "more expensive" : "cheaper";
|
|
417
604
|
parts.push(`${Math.abs(cost.deltaPercent).toFixed(0)}% ${dir}`);
|
|
418
605
|
}
|
|
419
|
-
const summaryStr = `candidate ${parts.length > 0 ? parts.join(", ") : "no
|
|
606
|
+
const summaryStr = `candidate ${parts.length > 0 ? parts.join(", ") : "no meaningful changes"} with ${regressions.length} regressions and ${improvements.length} improvements`;
|
|
420
607
|
return {
|
|
421
|
-
baseline: { id:
|
|
422
|
-
candidate: { id:
|
|
608
|
+
baseline: { id: baselineRef.id, metadata: baselineRef.metadata },
|
|
609
|
+
candidate: { id: candidateRef.id, metadata: candidateRef.metadata },
|
|
423
610
|
scorers,
|
|
424
611
|
timing,
|
|
425
612
|
cost,
|
|
@@ -428,13 +615,192 @@ function evalCompare(baseline, candidate) {
|
|
|
428
615
|
summary: summaryStr
|
|
429
616
|
};
|
|
430
617
|
}
|
|
618
|
+
|
|
619
|
+
// src/rescore.ts
|
|
620
|
+
var import_node_crypto2 = require("crypto");
|
|
621
|
+
async function rescore(result, scorers, runtime, options) {
|
|
622
|
+
const startTime = Date.now();
|
|
623
|
+
const concurrency = options?.concurrency ?? 5;
|
|
624
|
+
const scorerContext = {
|
|
625
|
+
resolveProvider: (uri) => {
|
|
626
|
+
if (typeof runtime.resolveProvider !== "function") {
|
|
627
|
+
throw new Error(
|
|
628
|
+
`LLM scorers require a runtime with resolveProvider(). Ensure you are using a real AxlRuntime instance, not a mock.`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
return runtime.resolveProvider(uri);
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
const rescored = new Array(result.items.length);
|
|
635
|
+
let totalCost = 0;
|
|
636
|
+
async function rescoreItem(original, itemIndex) {
|
|
637
|
+
if (original.error) {
|
|
638
|
+
rescored[itemIndex] = {
|
|
639
|
+
input: original.input,
|
|
640
|
+
annotations: original.annotations,
|
|
641
|
+
output: original.output,
|
|
642
|
+
error: original.error,
|
|
643
|
+
scores: {}
|
|
644
|
+
};
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const item = {
|
|
648
|
+
input: original.input,
|
|
649
|
+
annotations: original.annotations,
|
|
650
|
+
output: original.output,
|
|
651
|
+
metadata: original.metadata,
|
|
652
|
+
scores: {},
|
|
653
|
+
scoreDetails: {}
|
|
654
|
+
};
|
|
655
|
+
let itemScorerCost = 0;
|
|
656
|
+
for (const scorer2 of scorers) {
|
|
657
|
+
const scorerStart = Date.now();
|
|
658
|
+
try {
|
|
659
|
+
const raw = await scorer2.score(item.output, item.input, item.annotations, scorerContext);
|
|
660
|
+
const scorerResult = normalizeScorerResult(raw);
|
|
661
|
+
if (scorerResult.cost != null) {
|
|
662
|
+
itemScorerCost += scorerResult.cost;
|
|
663
|
+
totalCost += scorerResult.cost;
|
|
664
|
+
}
|
|
665
|
+
const scorerDuration = Date.now() - scorerStart;
|
|
666
|
+
if (!Number.isFinite(scorerResult.score) || scorerResult.score < 0 || scorerResult.score > 1) {
|
|
667
|
+
if (!item.scorerErrors) item.scorerErrors = [];
|
|
668
|
+
item.scorerErrors.push(
|
|
669
|
+
`Scorer "${scorer2.name}" returned out-of-range score ${scorerResult.score} for input ${JSON.stringify(item.input)}`
|
|
670
|
+
);
|
|
671
|
+
item.scores[scorer2.name] = null;
|
|
672
|
+
item.scoreDetails[scorer2.name] = {
|
|
673
|
+
score: null,
|
|
674
|
+
metadata: scorerResult.metadata,
|
|
675
|
+
duration: scorerDuration,
|
|
676
|
+
cost: scorerResult.cost
|
|
677
|
+
};
|
|
678
|
+
} else {
|
|
679
|
+
item.scores[scorer2.name] = round(scorerResult.score);
|
|
680
|
+
item.scoreDetails[scorer2.name] = {
|
|
681
|
+
score: round(scorerResult.score),
|
|
682
|
+
metadata: scorerResult.metadata,
|
|
683
|
+
duration: scorerDuration,
|
|
684
|
+
cost: scorerResult.cost
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
} catch (err) {
|
|
688
|
+
const errCost = typeof err?.cost === "number" ? err.cost : void 0;
|
|
689
|
+
if (errCost != null) {
|
|
690
|
+
itemScorerCost += errCost;
|
|
691
|
+
totalCost += errCost;
|
|
692
|
+
}
|
|
693
|
+
if (!item.scorerErrors) item.scorerErrors = [];
|
|
694
|
+
item.scorerErrors.push(
|
|
695
|
+
`Scorer "${scorer2.name}" threw: ${err instanceof Error ? err.message : String(err)}`
|
|
696
|
+
);
|
|
697
|
+
item.scores[scorer2.name] = null;
|
|
698
|
+
item.scoreDetails[scorer2.name] = {
|
|
699
|
+
score: null,
|
|
700
|
+
duration: Date.now() - scorerStart,
|
|
701
|
+
cost: errCost
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
item.scorerCost = itemScorerCost > 0 ? itemScorerCost : void 0;
|
|
706
|
+
rescored[itemIndex] = item;
|
|
707
|
+
}
|
|
708
|
+
let index = 0;
|
|
709
|
+
async function runNext() {
|
|
710
|
+
while (index < result.items.length) {
|
|
711
|
+
const currentIndex = index++;
|
|
712
|
+
await rescoreItem(result.items[currentIndex], currentIndex);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
const workers = Array.from(
|
|
716
|
+
{ length: Math.min(concurrency, result.items.length) },
|
|
717
|
+
() => runNext()
|
|
718
|
+
);
|
|
719
|
+
await Promise.all(workers);
|
|
720
|
+
const failures = rescored.filter((i) => i.error).length;
|
|
721
|
+
const scorerNames = scorers.map((s) => s.name);
|
|
722
|
+
const scorerStats = {};
|
|
723
|
+
for (const name of scorerNames) {
|
|
724
|
+
const scores = rescored.filter((i) => !i.error && i.scores[name] != null).map((i) => i.scores[name]);
|
|
725
|
+
scorerStats[name] = computeStats(scores);
|
|
726
|
+
}
|
|
727
|
+
const scorerTypes = {};
|
|
728
|
+
for (const s of scorers) {
|
|
729
|
+
scorerTypes[s.name] = s.isLlm ? "llm" : "deterministic";
|
|
730
|
+
}
|
|
731
|
+
return {
|
|
732
|
+
id: (0, import_node_crypto2.randomUUID)(),
|
|
733
|
+
workflow: result.workflow,
|
|
734
|
+
dataset: result.dataset,
|
|
735
|
+
metadata: (() => {
|
|
736
|
+
const { runGroupId: _, runIndex: __, ...rest } = result.metadata;
|
|
737
|
+
return { ...rest, rescored: true, originalId: result.id, scorerTypes };
|
|
738
|
+
})(),
|
|
739
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
740
|
+
totalCost,
|
|
741
|
+
duration: Date.now() - startTime,
|
|
742
|
+
items: rescored,
|
|
743
|
+
summary: { count: result.items.length, failures, scorers: scorerStats }
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// src/multi-run.ts
|
|
748
|
+
var import_node_crypto3 = require("crypto");
|
|
749
|
+
function std(values, mean) {
|
|
750
|
+
if (values.length <= 1) return 0;
|
|
751
|
+
const sumSqDiffs = values.reduce((sum, v) => sum + (v - mean) ** 2, 0);
|
|
752
|
+
return Math.sqrt(sumSqDiffs / (values.length - 1));
|
|
753
|
+
}
|
|
754
|
+
function aggregateRuns(runs) {
|
|
755
|
+
if (runs.length === 0) throw new Error("Cannot aggregate zero runs");
|
|
756
|
+
const runGroupId = runs[0].metadata.runGroupId ?? (0, import_node_crypto3.randomUUID)();
|
|
757
|
+
const { workflow, dataset: dataset2 } = runs[0];
|
|
758
|
+
const totalCost = runs.reduce((sum, r) => sum + r.totalCost, 0);
|
|
759
|
+
const totalDuration = runs.reduce((sum, r) => sum + r.duration, 0);
|
|
760
|
+
const scorerNames = Object.keys(runs[0].summary.scorers);
|
|
761
|
+
const scorers = {};
|
|
762
|
+
for (const name of scorerNames) {
|
|
763
|
+
const means = runs.map((r) => r.summary.scorers[name]?.mean ?? 0);
|
|
764
|
+
const meanOfMeans = means.reduce((a, b) => a + b, 0) / means.length;
|
|
765
|
+
const minMean = Math.min(...means);
|
|
766
|
+
const maxMean = Math.max(...means);
|
|
767
|
+
scorers[name] = {
|
|
768
|
+
mean: round(meanOfMeans),
|
|
769
|
+
std: round(std(means, meanOfMeans)),
|
|
770
|
+
min: round(minMean),
|
|
771
|
+
max: round(maxMean)
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
let timing;
|
|
775
|
+
const timingMeans = runs.filter((r) => r.summary.timing).map((r) => r.summary.timing.mean);
|
|
776
|
+
if (timingMeans.length === runs.length && timingMeans.length > 0) {
|
|
777
|
+
const meanTiming = timingMeans.reduce((a, b) => a + b, 0) / timingMeans.length;
|
|
778
|
+
timing = {
|
|
779
|
+
mean: round(meanTiming),
|
|
780
|
+
std: round(std(timingMeans, meanTiming))
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
return {
|
|
784
|
+
runGroupId,
|
|
785
|
+
runCount: runs.length,
|
|
786
|
+
workflow,
|
|
787
|
+
dataset: dataset2,
|
|
788
|
+
totalCost: round(totalCost),
|
|
789
|
+
totalDuration: round(totalDuration),
|
|
790
|
+
scorers,
|
|
791
|
+
timing
|
|
792
|
+
};
|
|
793
|
+
}
|
|
431
794
|
// Annotate the CommonJS export names for ESM import in node:
|
|
432
795
|
0 && (module.exports = {
|
|
796
|
+
aggregateRuns,
|
|
433
797
|
dataset,
|
|
434
798
|
defineEval,
|
|
435
799
|
evalCompare,
|
|
436
800
|
llmScorer,
|
|
437
801
|
normalizeScorerResult,
|
|
802
|
+
pairedBootstrapCI,
|
|
803
|
+
rescore,
|
|
438
804
|
runEval,
|
|
439
805
|
scorer
|
|
440
806
|
});
|