@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.
@@ -0,0 +1,653 @@
1
+ // src/bootstrap.ts
2
+ function pairedBootstrapCI(differences, options) {
3
+ const n = differences.length;
4
+ if (n === 0) return { lower: 0, upper: 0, mean: 0, pRegression: 0, pImprovement: 0 };
5
+ const nResamples = options?.nResamples ?? 1e3;
6
+ const alpha = options?.alpha ?? 0.05;
7
+ const mean = differences.reduce((a, b) => a + b, 0) / n;
8
+ if (n === 1)
9
+ return {
10
+ lower: differences[0],
11
+ upper: differences[0],
12
+ mean,
13
+ pRegression: mean < 0 ? 1 : 0,
14
+ pImprovement: mean > 0 ? 1 : 0
15
+ };
16
+ if (nResamples <= 0)
17
+ return {
18
+ lower: round(mean),
19
+ upper: round(mean),
20
+ mean: round(mean),
21
+ pRegression: mean < 0 ? 1 : 0,
22
+ pImprovement: mean > 0 ? 1 : 0
23
+ };
24
+ const rng = options?.seed != null ? xorshift32(options.seed) : () => Math.random();
25
+ const resampleMeans = new Array(nResamples);
26
+ for (let r = 0; r < nResamples; r++) {
27
+ let sum = 0;
28
+ for (let i = 0; i < n; i++) {
29
+ sum += differences[Math.floor(rng() * n)];
30
+ }
31
+ resampleMeans[r] = sum / n;
32
+ }
33
+ resampleMeans.sort((a, b) => a - b);
34
+ const lowerIdx = Math.floor(alpha / 2 * nResamples);
35
+ const upperIdx = Math.floor((1 - alpha / 2) * nResamples) - 1;
36
+ let belowZero = 0;
37
+ for (let i = 0; i < nResamples; i++) {
38
+ if (resampleMeans[i] < 0) belowZero++;
39
+ else break;
40
+ }
41
+ let aboveZero = 0;
42
+ for (let i = nResamples - 1; i >= 0; i--) {
43
+ if (resampleMeans[i] > 0) aboveZero++;
44
+ else break;
45
+ }
46
+ return {
47
+ lower: round(resampleMeans[Math.max(0, lowerIdx)]),
48
+ upper: round(resampleMeans[Math.min(nResamples - 1, upperIdx)]),
49
+ mean: round(mean),
50
+ pRegression: round(belowZero / nResamples),
51
+ pImprovement: round(aboveZero / nResamples)
52
+ };
53
+ }
54
+ function round(n) {
55
+ return Math.round(n * 1e4) / 1e4;
56
+ }
57
+ function xorshift32(seed) {
58
+ let state = seed | 0 || 1;
59
+ return () => {
60
+ state ^= state << 13;
61
+ state ^= state >> 17;
62
+ state ^= state << 5;
63
+ return (state >>> 0) / 4294967296;
64
+ };
65
+ }
66
+
67
+ // src/compare.ts
68
+ function round2(n) {
69
+ return Math.round(n * 1e3) / 1e3;
70
+ }
71
+ var DEFAULT_LLM_THRESHOLD = 0.05;
72
+ var DEFAULT_DETERMINISTIC_THRESHOLD = 0;
73
+ var LEGACY_THRESHOLD = 0.1;
74
+ function resolveThreshold(scorerName, options, metadata) {
75
+ if (typeof options?.thresholds === "number") return options.thresholds;
76
+ if (options?.thresholds && typeof options.thresholds === "object") {
77
+ const perScorer = options.thresholds[scorerName];
78
+ if (perScorer != null) return perScorer;
79
+ }
80
+ const scorerTypes = metadata.scorerTypes;
81
+ if (scorerTypes && scorerName in scorerTypes) {
82
+ return scorerTypes[scorerName] === "llm" ? DEFAULT_LLM_THRESHOLD : DEFAULT_DETERMINISTIC_THRESHOLD;
83
+ }
84
+ return LEGACY_THRESHOLD;
85
+ }
86
+ function evalCompare(baseline, candidate, options) {
87
+ const baselineRuns = Array.isArray(baseline) ? baseline : [baseline];
88
+ const candidateRuns = Array.isArray(candidate) ? candidate : [candidate];
89
+ if (baselineRuns.length === 0 || candidateRuns.length === 0) {
90
+ throw new Error("Cannot compare empty eval result arrays");
91
+ }
92
+ const baselineRef = baselineRuns[0];
93
+ const candidateRef = candidateRuns[0];
94
+ if (baselineRef.dataset !== candidateRef.dataset) {
95
+ throw new Error(
96
+ `Cannot compare evals from different datasets: "${baselineRef.dataset}" vs "${candidateRef.dataset}"`
97
+ );
98
+ }
99
+ const baselineScorerNames = Object.keys(baselineRef.summary.scorers).sort();
100
+ const candidateScorerNames = Object.keys(candidateRef.summary.scorers).sort();
101
+ if (JSON.stringify(baselineScorerNames) !== JSON.stringify(candidateScorerNames)) {
102
+ throw new Error(`Cannot compare evals with different scorers`);
103
+ }
104
+ const pairedDiffs = {};
105
+ for (const name of baselineScorerNames) {
106
+ pairedDiffs[name] = [];
107
+ }
108
+ const runCount = Math.min(baselineRuns.length, candidateRuns.length);
109
+ for (let r = 0; r < runCount; r++) {
110
+ const bRun = baselineRuns[r];
111
+ const cRun = candidateRuns[r];
112
+ const minLength = Math.min(bRun.items.length, cRun.items.length);
113
+ for (let i = 0; i < minLength; i++) {
114
+ const bItem = bRun.items[i];
115
+ const cItem = cRun.items[i];
116
+ if (bItem.error || cItem.error) continue;
117
+ for (const name of baselineScorerNames) {
118
+ const bScore = bItem.scores[name];
119
+ const cScore = cItem.scores[name];
120
+ if (bScore != null && cScore != null) {
121
+ pairedDiffs[name].push(cScore - bScore);
122
+ }
123
+ }
124
+ }
125
+ }
126
+ const baselineMeans = {};
127
+ const candidateMeans = {};
128
+ for (const name of baselineScorerNames) {
129
+ baselineMeans[name] = baselineRuns.reduce((sum, r) => sum + (r.summary.scorers[name]?.mean ?? 0), 0) / baselineRuns.length;
130
+ candidateMeans[name] = candidateRuns.reduce((sum, r) => sum + (r.summary.scorers[name]?.mean ?? 0), 0) / candidateRuns.length;
131
+ }
132
+ const scorers = {};
133
+ for (const name of baselineScorerNames) {
134
+ const bMeanRaw = baselineMeans[name];
135
+ const cMeanRaw = candidateMeans[name];
136
+ const deltaRaw = cMeanRaw - bMeanRaw;
137
+ const bMean = round2(bMeanRaw);
138
+ const cMean = round2(cMeanRaw);
139
+ const delta = round2(deltaRaw);
140
+ const deltaPercent = bMeanRaw > 0 ? round2(deltaRaw / bMeanRaw * 100) : 0;
141
+ const threshold = resolveThreshold(name, options, baselineRef.metadata);
142
+ const entry = {
143
+ baselineMean: bMean,
144
+ candidateMean: cMean,
145
+ delta,
146
+ deltaPercent
147
+ };
148
+ const diffs = pairedDiffs[name];
149
+ if (diffs.length >= 2) {
150
+ const ci = pairedBootstrapCI(diffs);
151
+ entry.ci = { lower: ci.lower, upper: ci.upper };
152
+ entry.pRegression = ci.pRegression;
153
+ entry.pImprovement = ci.pImprovement;
154
+ entry.n = diffs.length;
155
+ const ciExcludesZero = ci.lower > 0 || ci.upper < 0;
156
+ entry.significant = ciExcludesZero && Math.abs(ci.mean) >= threshold;
157
+ }
158
+ scorers[name] = entry;
159
+ }
160
+ const regressions = [];
161
+ const improvements = [];
162
+ const itemCount = Math.min(
163
+ ...baselineRuns.map((r) => r.items.length),
164
+ ...candidateRuns.map((r) => r.items.length)
165
+ );
166
+ for (let i = 0; i < itemCount; i++) {
167
+ const hasBaselineError = baselineRuns.some((r) => r.items[i]?.error);
168
+ const hasCandidateError = candidateRuns.some((r) => r.items[i]?.error);
169
+ if (hasBaselineError || hasCandidateError) continue;
170
+ for (const name of baselineScorerNames) {
171
+ const bScores = baselineRuns.map((r) => r.items[i]?.scores[name]).filter((s) => s != null);
172
+ const cScores = candidateRuns.map((r) => r.items[i]?.scores[name]).filter((s) => s != null);
173
+ if (bScores.length === 0 || cScores.length === 0) continue;
174
+ const bAvg = bScores.reduce((a, b) => a + b, 0) / bScores.length;
175
+ const cAvg = cScores.reduce((a, b) => a + b, 0) / cScores.length;
176
+ const delta = round2(cAvg - bAvg);
177
+ const threshold = resolveThreshold(name, options, baselineRef.metadata);
178
+ if (delta < -threshold)
179
+ regressions.push({
180
+ itemIndex: i,
181
+ input: baselineRef.items[i]?.input,
182
+ scorer: name,
183
+ baselineScore: round2(bAvg),
184
+ candidateScore: round2(cAvg),
185
+ delta
186
+ });
187
+ else if (delta > threshold)
188
+ improvements.push({
189
+ itemIndex: i,
190
+ input: baselineRef.items[i]?.input,
191
+ scorer: name,
192
+ baselineScore: round2(bAvg),
193
+ candidateScore: round2(cAvg),
194
+ delta
195
+ });
196
+ }
197
+ }
198
+ let timing;
199
+ const baselineTimings = baselineRuns.filter((r) => r.summary.timing).map((r) => r.summary.timing.mean);
200
+ const candidateTimings = candidateRuns.filter((r) => r.summary.timing).map((r) => r.summary.timing.mean);
201
+ if (baselineTimings.length > 0 && candidateTimings.length > 0) {
202
+ const bMean = baselineTimings.reduce((a, b) => a + b, 0) / baselineTimings.length;
203
+ const cMean = candidateTimings.reduce((a, b) => a + b, 0) / candidateTimings.length;
204
+ const delta = round2(cMean - bMean);
205
+ const deltaPercent = bMean > 0 ? round2((cMean - bMean) / bMean * 100) : 0;
206
+ timing = { baselineMean: round2(bMean), candidateMean: round2(cMean), delta, deltaPercent };
207
+ }
208
+ let cost;
209
+ const baselineAvgCost = baselineRuns.reduce((sum, r) => sum + r.totalCost, 0) / baselineRuns.length;
210
+ const candidateAvgCost = candidateRuns.reduce((sum, r) => sum + r.totalCost, 0) / candidateRuns.length;
211
+ if (baselineAvgCost > 0 || candidateAvgCost > 0) {
212
+ const deltaRaw = candidateAvgCost - baselineAvgCost;
213
+ const delta = round2(deltaRaw);
214
+ const deltaPercent = baselineAvgCost > 0 ? round2(deltaRaw / baselineAvgCost * 100) : 0;
215
+ cost = {
216
+ baselineTotal: round2(baselineAvgCost),
217
+ candidateTotal: round2(candidateAvgCost),
218
+ delta,
219
+ deltaPercent
220
+ };
221
+ }
222
+ const parts = [];
223
+ for (const name of baselineScorerNames) {
224
+ const s = scorers[name];
225
+ if (Math.abs(s.delta) > 1e-3) {
226
+ const direction = s.delta > 0 ? "improves" : "regresses";
227
+ const sign = s.delta > 0 ? "+" : "";
228
+ let sigLabel = "";
229
+ if (s.significant === true) sigLabel = " (significant)";
230
+ else if (s.significant === false) sigLabel = " (not significant)";
231
+ parts.push(
232
+ `'${name}' ${direction} by ${sign}${s.deltaPercent.toFixed(1)}% (${s.baselineMean.toFixed(2)} -> ${s.candidateMean.toFixed(2)})${sigLabel}`
233
+ );
234
+ }
235
+ }
236
+ if (timing && Math.abs(timing.deltaPercent) > 1) {
237
+ const dir = timing.delta > 0 ? "slower" : "faster";
238
+ parts.push(`${Math.abs(timing.deltaPercent).toFixed(0)}% ${dir}`);
239
+ }
240
+ if (cost && Math.abs(cost.deltaPercent) > 1) {
241
+ const dir = cost.delta > 0 ? "more expensive" : "cheaper";
242
+ parts.push(`${Math.abs(cost.deltaPercent).toFixed(0)}% ${dir}`);
243
+ }
244
+ const summaryStr = `candidate ${parts.length > 0 ? parts.join(", ") : "no meaningful changes"} with ${regressions.length} regressions and ${improvements.length} improvements`;
245
+ return {
246
+ baseline: { id: baselineRef.id, metadata: baselineRef.metadata },
247
+ candidate: { id: candidateRef.id, metadata: candidateRef.metadata },
248
+ scorers,
249
+ timing,
250
+ cost,
251
+ regressions,
252
+ improvements,
253
+ summary: summaryStr
254
+ };
255
+ }
256
+
257
+ // src/scorer.ts
258
+ function normalizeScorerResult(result) {
259
+ return typeof result === "number" ? { score: result } : result;
260
+ }
261
+ function scorer(config) {
262
+ return {
263
+ name: config.name,
264
+ description: config.description,
265
+ isLlm: false,
266
+ score: config.score
267
+ };
268
+ }
269
+
270
+ // src/utils.ts
271
+ function computeStats(scores) {
272
+ if (scores.length === 0) return { mean: 0, min: 0, max: 0, p50: 0, p95: 0 };
273
+ const sorted = [...scores].sort((a, b) => a - b);
274
+ const mean = sorted.reduce((a, b) => a + b, 0) / sorted.length;
275
+ const min = sorted[0];
276
+ const max = sorted[sorted.length - 1];
277
+ const p50 = sorted[Math.floor(sorted.length * 0.5)];
278
+ const p95 = sorted[Math.floor(sorted.length * 0.95)];
279
+ return { mean: round3(mean), min: round3(min), max: round3(max), p50: round3(p50), p95: round3(p95) };
280
+ }
281
+ function round3(n) {
282
+ return Math.round(n * 1e3) / 1e3;
283
+ }
284
+
285
+ // src/runner.ts
286
+ import { randomUUID } from "crypto";
287
+ function parseCost(cost) {
288
+ const match = cost.match(/^\$?([\d.]+)$/);
289
+ if (!match) throw new Error(`Invalid cost format: "${cost}"`);
290
+ return parseFloat(match[1]);
291
+ }
292
+ async function runEval(config, executeWorkflow, runtime) {
293
+ const startTime = Date.now();
294
+ const id = randomUUID();
295
+ const items = await config.dataset.getItems();
296
+ const concurrency = config.concurrency ?? 5;
297
+ const budgetLimit = config.budget ? parseCost(config.budget) : void 0;
298
+ const scorerContext = {
299
+ resolveProvider: (uri) => {
300
+ if (typeof runtime.resolveProvider !== "function") {
301
+ throw new Error(
302
+ `LLM scorers require a runtime with resolveProvider(). Ensure you are using a real AxlRuntime instance, not a mock.`
303
+ );
304
+ }
305
+ return runtime.resolveProvider(uri);
306
+ }
307
+ };
308
+ const evalItems = new Array(items.length);
309
+ let totalCost = 0;
310
+ let budgetExceeded = false;
311
+ async function processItem(item, itemIndex) {
312
+ if (budgetExceeded) {
313
+ evalItems[itemIndex] = {
314
+ input: item.input,
315
+ annotations: item.annotations,
316
+ output: null,
317
+ error: "Budget exceeded",
318
+ scores: {}
319
+ };
320
+ return;
321
+ }
322
+ const evalItem = {
323
+ input: item.input,
324
+ annotations: item.annotations,
325
+ output: null,
326
+ scores: {}
327
+ };
328
+ const itemStart = Date.now();
329
+ try {
330
+ const result = await executeWorkflow(item.input, runtime);
331
+ evalItem.duration = Date.now() - itemStart;
332
+ evalItem.output = result.output;
333
+ evalItem.cost = result.cost;
334
+ if (result.metadata) evalItem.metadata = result.metadata;
335
+ if (result.cost != null) {
336
+ totalCost += result.cost;
337
+ }
338
+ } catch (err) {
339
+ evalItem.duration = Date.now() - itemStart;
340
+ evalItem.error = err instanceof Error ? err.message : String(err);
341
+ evalItems[itemIndex] = evalItem;
342
+ return;
343
+ }
344
+ if (budgetLimit != null && totalCost > budgetLimit) {
345
+ budgetExceeded = true;
346
+ }
347
+ evalItem.scoreDetails = {};
348
+ let itemScorerCost = 0;
349
+ for (const scorer2 of config.scorers) {
350
+ const scorerStart = Date.now();
351
+ try {
352
+ const raw = await scorer2.score(
353
+ evalItem.output,
354
+ item.input,
355
+ item.annotations,
356
+ scorerContext
357
+ );
358
+ const scorerResult = normalizeScorerResult(raw);
359
+ if (scorerResult.cost != null) {
360
+ itemScorerCost += scorerResult.cost;
361
+ totalCost += scorerResult.cost;
362
+ }
363
+ const scorerDuration = Date.now() - scorerStart;
364
+ if (!Number.isFinite(scorerResult.score) || scorerResult.score < 0 || scorerResult.score > 1) {
365
+ if (!evalItem.scorerErrors) evalItem.scorerErrors = [];
366
+ evalItem.scorerErrors.push(
367
+ `Scorer "${scorer2.name}" returned out-of-range score ${scorerResult.score} for input ${JSON.stringify(item.input)}`
368
+ );
369
+ evalItem.scores[scorer2.name] = null;
370
+ evalItem.scoreDetails[scorer2.name] = {
371
+ score: null,
372
+ metadata: scorerResult.metadata,
373
+ duration: scorerDuration,
374
+ cost: scorerResult.cost
375
+ };
376
+ } else {
377
+ evalItem.scores[scorer2.name] = round3(scorerResult.score);
378
+ evalItem.scoreDetails[scorer2.name] = {
379
+ score: round3(scorerResult.score),
380
+ metadata: scorerResult.metadata,
381
+ duration: scorerDuration,
382
+ cost: scorerResult.cost
383
+ };
384
+ }
385
+ } catch (err) {
386
+ const errCost = typeof err?.cost === "number" ? err.cost : void 0;
387
+ if (errCost != null) {
388
+ itemScorerCost += errCost;
389
+ totalCost += errCost;
390
+ }
391
+ if (!evalItem.scorerErrors) evalItem.scorerErrors = [];
392
+ evalItem.scorerErrors.push(
393
+ `Scorer "${scorer2.name}" threw: ${err instanceof Error ? err.message : String(err)}`
394
+ );
395
+ evalItem.scores[scorer2.name] = null;
396
+ evalItem.scoreDetails[scorer2.name] = {
397
+ score: null,
398
+ duration: Date.now() - scorerStart,
399
+ cost: errCost
400
+ };
401
+ }
402
+ }
403
+ evalItem.scorerCost = itemScorerCost > 0 ? itemScorerCost : void 0;
404
+ evalItems[itemIndex] = evalItem;
405
+ }
406
+ let index = 0;
407
+ async function runNext() {
408
+ while (index < items.length) {
409
+ const currentIndex = index++;
410
+ await processItem(items[currentIndex], currentIndex);
411
+ }
412
+ }
413
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => runNext());
414
+ await Promise.all(workers);
415
+ const failures = evalItems.filter((i) => i.error).length;
416
+ const scorerNames = config.scorers.map((s) => s.name);
417
+ const scorerStats = {};
418
+ for (const name of scorerNames) {
419
+ const scores = evalItems.filter((i) => !i.error && i.scores[name] != null).map((i) => i.scores[name]);
420
+ scorerStats[name] = computeStats(scores);
421
+ }
422
+ const durations = evalItems.filter((i) => !i.error && i.duration != null).map((i) => i.duration);
423
+ const timing = durations.length > 0 ? computeStats(durations) : void 0;
424
+ const scorerTypes = {};
425
+ for (const s of config.scorers) {
426
+ scorerTypes[s.name] = s.isLlm ? "llm" : "deterministic";
427
+ }
428
+ const totalModelCalls = /* @__PURE__ */ new Map();
429
+ for (const item of evalItems) {
430
+ const itemCounts = item.metadata?.modelCallCounts;
431
+ if (itemCounts && typeof itemCounts === "object") {
432
+ for (const [m, count] of Object.entries(itemCounts)) {
433
+ if (typeof count === "number")
434
+ totalModelCalls.set(m, (totalModelCalls.get(m) ?? 0) + count);
435
+ }
436
+ } else {
437
+ const itemModels = item.metadata?.models;
438
+ if (Array.isArray(itemModels)) {
439
+ for (const m of itemModels) {
440
+ if (typeof m === "string") totalModelCalls.set(m, (totalModelCalls.get(m) ?? 0) + 1);
441
+ }
442
+ }
443
+ }
444
+ }
445
+ const modelsMeta = {};
446
+ if (totalModelCalls.size > 0) {
447
+ const sorted = [...totalModelCalls.entries()].sort((a, b) => b[1] - a[1]);
448
+ modelsMeta.models = sorted.map(([m]) => m);
449
+ modelsMeta.modelCounts = Object.fromEntries(sorted);
450
+ }
451
+ return {
452
+ id,
453
+ workflow: config.workflow,
454
+ dataset: config.dataset.name,
455
+ metadata: {
456
+ ...config.metadata,
457
+ scorerTypes,
458
+ ...modelsMeta
459
+ },
460
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
461
+ totalCost,
462
+ duration: Date.now() - startTime,
463
+ items: evalItems,
464
+ summary: { count: items.length, failures, scorers: scorerStats, timing }
465
+ };
466
+ }
467
+
468
+ // src/rescore.ts
469
+ import { randomUUID as randomUUID2 } from "crypto";
470
+ async function rescore(result, scorers, runtime, options) {
471
+ const startTime = Date.now();
472
+ const concurrency = options?.concurrency ?? 5;
473
+ const scorerContext = {
474
+ resolveProvider: (uri) => {
475
+ if (typeof runtime.resolveProvider !== "function") {
476
+ throw new Error(
477
+ `LLM scorers require a runtime with resolveProvider(). Ensure you are using a real AxlRuntime instance, not a mock.`
478
+ );
479
+ }
480
+ return runtime.resolveProvider(uri);
481
+ }
482
+ };
483
+ const rescored = new Array(result.items.length);
484
+ let totalCost = 0;
485
+ async function rescoreItem(original, itemIndex) {
486
+ if (original.error) {
487
+ rescored[itemIndex] = {
488
+ input: original.input,
489
+ annotations: original.annotations,
490
+ output: original.output,
491
+ error: original.error,
492
+ scores: {}
493
+ };
494
+ return;
495
+ }
496
+ const item = {
497
+ input: original.input,
498
+ annotations: original.annotations,
499
+ output: original.output,
500
+ metadata: original.metadata,
501
+ scores: {},
502
+ scoreDetails: {}
503
+ };
504
+ let itemScorerCost = 0;
505
+ for (const scorer2 of scorers) {
506
+ const scorerStart = Date.now();
507
+ try {
508
+ const raw = await scorer2.score(item.output, item.input, item.annotations, scorerContext);
509
+ const scorerResult = normalizeScorerResult(raw);
510
+ if (scorerResult.cost != null) {
511
+ itemScorerCost += scorerResult.cost;
512
+ totalCost += scorerResult.cost;
513
+ }
514
+ const scorerDuration = Date.now() - scorerStart;
515
+ if (!Number.isFinite(scorerResult.score) || scorerResult.score < 0 || scorerResult.score > 1) {
516
+ if (!item.scorerErrors) item.scorerErrors = [];
517
+ item.scorerErrors.push(
518
+ `Scorer "${scorer2.name}" returned out-of-range score ${scorerResult.score} for input ${JSON.stringify(item.input)}`
519
+ );
520
+ item.scores[scorer2.name] = null;
521
+ item.scoreDetails[scorer2.name] = {
522
+ score: null,
523
+ metadata: scorerResult.metadata,
524
+ duration: scorerDuration,
525
+ cost: scorerResult.cost
526
+ };
527
+ } else {
528
+ item.scores[scorer2.name] = round3(scorerResult.score);
529
+ item.scoreDetails[scorer2.name] = {
530
+ score: round3(scorerResult.score),
531
+ metadata: scorerResult.metadata,
532
+ duration: scorerDuration,
533
+ cost: scorerResult.cost
534
+ };
535
+ }
536
+ } catch (err) {
537
+ const errCost = typeof err?.cost === "number" ? err.cost : void 0;
538
+ if (errCost != null) {
539
+ itemScorerCost += errCost;
540
+ totalCost += errCost;
541
+ }
542
+ if (!item.scorerErrors) item.scorerErrors = [];
543
+ item.scorerErrors.push(
544
+ `Scorer "${scorer2.name}" threw: ${err instanceof Error ? err.message : String(err)}`
545
+ );
546
+ item.scores[scorer2.name] = null;
547
+ item.scoreDetails[scorer2.name] = {
548
+ score: null,
549
+ duration: Date.now() - scorerStart,
550
+ cost: errCost
551
+ };
552
+ }
553
+ }
554
+ item.scorerCost = itemScorerCost > 0 ? itemScorerCost : void 0;
555
+ rescored[itemIndex] = item;
556
+ }
557
+ let index = 0;
558
+ async function runNext() {
559
+ while (index < result.items.length) {
560
+ const currentIndex = index++;
561
+ await rescoreItem(result.items[currentIndex], currentIndex);
562
+ }
563
+ }
564
+ const workers = Array.from(
565
+ { length: Math.min(concurrency, result.items.length) },
566
+ () => runNext()
567
+ );
568
+ await Promise.all(workers);
569
+ const failures = rescored.filter((i) => i.error).length;
570
+ const scorerNames = scorers.map((s) => s.name);
571
+ const scorerStats = {};
572
+ for (const name of scorerNames) {
573
+ const scores = rescored.filter((i) => !i.error && i.scores[name] != null).map((i) => i.scores[name]);
574
+ scorerStats[name] = computeStats(scores);
575
+ }
576
+ const scorerTypes = {};
577
+ for (const s of scorers) {
578
+ scorerTypes[s.name] = s.isLlm ? "llm" : "deterministic";
579
+ }
580
+ return {
581
+ id: randomUUID2(),
582
+ workflow: result.workflow,
583
+ dataset: result.dataset,
584
+ metadata: (() => {
585
+ const { runGroupId: _, runIndex: __, ...rest } = result.metadata;
586
+ return { ...rest, rescored: true, originalId: result.id, scorerTypes };
587
+ })(),
588
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
589
+ totalCost,
590
+ duration: Date.now() - startTime,
591
+ items: rescored,
592
+ summary: { count: result.items.length, failures, scorers: scorerStats }
593
+ };
594
+ }
595
+
596
+ // src/multi-run.ts
597
+ import { randomUUID as randomUUID3 } from "crypto";
598
+ function std(values, mean) {
599
+ if (values.length <= 1) return 0;
600
+ const sumSqDiffs = values.reduce((sum, v) => sum + (v - mean) ** 2, 0);
601
+ return Math.sqrt(sumSqDiffs / (values.length - 1));
602
+ }
603
+ function aggregateRuns(runs) {
604
+ if (runs.length === 0) throw new Error("Cannot aggregate zero runs");
605
+ const runGroupId = runs[0].metadata.runGroupId ?? randomUUID3();
606
+ const { workflow, dataset } = runs[0];
607
+ const totalCost = runs.reduce((sum, r) => sum + r.totalCost, 0);
608
+ const totalDuration = runs.reduce((sum, r) => sum + r.duration, 0);
609
+ const scorerNames = Object.keys(runs[0].summary.scorers);
610
+ const scorers = {};
611
+ for (const name of scorerNames) {
612
+ const means = runs.map((r) => r.summary.scorers[name]?.mean ?? 0);
613
+ const meanOfMeans = means.reduce((a, b) => a + b, 0) / means.length;
614
+ const minMean = Math.min(...means);
615
+ const maxMean = Math.max(...means);
616
+ scorers[name] = {
617
+ mean: round3(meanOfMeans),
618
+ std: round3(std(means, meanOfMeans)),
619
+ min: round3(minMean),
620
+ max: round3(maxMean)
621
+ };
622
+ }
623
+ let timing;
624
+ const timingMeans = runs.filter((r) => r.summary.timing).map((r) => r.summary.timing.mean);
625
+ if (timingMeans.length === runs.length && timingMeans.length > 0) {
626
+ const meanTiming = timingMeans.reduce((a, b) => a + b, 0) / timingMeans.length;
627
+ timing = {
628
+ mean: round3(meanTiming),
629
+ std: round3(std(timingMeans, meanTiming))
630
+ };
631
+ }
632
+ return {
633
+ runGroupId,
634
+ runCount: runs.length,
635
+ workflow,
636
+ dataset,
637
+ totalCost: round3(totalCost),
638
+ totalDuration: round3(totalDuration),
639
+ scorers,
640
+ timing
641
+ };
642
+ }
643
+
644
+ export {
645
+ pairedBootstrapCI,
646
+ evalCompare,
647
+ normalizeScorerResult,
648
+ scorer,
649
+ runEval,
650
+ rescore,
651
+ aggregateRuns
652
+ };
653
+ //# sourceMappingURL=chunk-CMWRFU3P.js.map