@axlsdk/eval 0.13.6 → 0.13.7

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/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/runner.ts
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: config.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/compare.ts
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 * 100) / 100;
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
- if (baseline.dataset !== candidate.dataset) {
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: "${baseline.dataset}" vs "${candidate.dataset}"`
458
+ `Cannot compare evals from different datasets: "${baselineRef.dataset}" vs "${candidateRef.dataset}"`
334
459
  );
335
460
  }
336
- const baselineScorerNames = Object.keys(baseline.summary.scorers).sort();
337
- const candidateScorerNames = Object.keys(candidate.summary.scorers).sort();
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 bMean = baseline.summary.scorers[name].mean;
344
- const cMean = candidate.summary.scorers[name].mean;
345
- const delta = round2(cMean - bMean);
346
- const deltaPercent = bMean > 0 ? round2(delta / bMean * 100) : 0;
347
- scorers[name] = { baselineMean: bMean, candidateMean: cMean, delta, deltaPercent };
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 minLength = Math.min(baseline.items.length, candidate.items.length);
352
- for (let i = 0; i < minLength; i++) {
353
- const bItem = baseline.items[i];
354
- const cItem = candidate.items[i];
355
- if (bItem.error || cItem.error) continue;
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 bScore = bItem.scores[name];
358
- const cScore = cItem.scores[name];
359
- if (bScore == null || cScore == null) continue;
360
- const delta = round2(cScore - bScore);
361
- if (delta < -0.1)
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: bItem.input,
543
+ input: baselineRef.items[i]?.input,
365
544
  scorer: name,
366
- baselineScore: bScore,
367
- candidateScore: cScore,
545
+ baselineScore: round3(bAvg),
546
+ candidateScore: round3(cAvg),
368
547
  delta
369
548
  });
370
- else if (delta > 0.1)
549
+ else if (delta > threshold)
371
550
  improvements.push({
372
551
  itemIndex: i,
373
- input: bItem.input,
552
+ input: baselineRef.items[i]?.input,
374
553
  scorer: name,
375
- baselineScore: bScore,
376
- candidateScore: cScore,
554
+ baselineScore: round3(bAvg),
555
+ candidateScore: round3(cAvg),
377
556
  delta
378
557
  });
379
558
  }
380
559
  }
381
560
  let timing;
382
- if (baseline.summary.timing && candidate.summary.timing) {
383
- const bMean = baseline.summary.timing.mean;
384
- const cMean = candidate.summary.timing.mean;
385
- const delta = round2(cMean - bMean);
386
- const deltaPercent = bMean > 0 ? round2(delta / bMean * 100) : 0;
387
- timing = { baselineMean: bMean, candidateMean: cMean, delta, deltaPercent };
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
- if (baseline.totalCost > 0 || candidate.totalCost > 0) {
391
- const delta = round2(candidate.totalCost - baseline.totalCost);
392
- const deltaPercent = baseline.totalCost > 0 ? round2(delta / baseline.totalCost * 100) : 0;
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: baseline.totalCost,
395
- candidateTotal: candidate.totalCost,
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 significant changes"} with ${regressions.length} regressions and ${improvements.length} improvements`;
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: baseline.id, metadata: baseline.metadata },
422
- candidate: { id: candidate.id, metadata: candidate.metadata },
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
  });