@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/chunk-F2AALK5U.js
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
// src/compare.ts
|
|
2
|
-
function round(n) {
|
|
3
|
-
return Math.round(n * 100) / 100;
|
|
4
|
-
}
|
|
5
|
-
function evalCompare(baseline, candidate) {
|
|
6
|
-
if (baseline.dataset !== candidate.dataset) {
|
|
7
|
-
throw new Error(
|
|
8
|
-
`Cannot compare evals from different datasets: "${baseline.dataset}" vs "${candidate.dataset}"`
|
|
9
|
-
);
|
|
10
|
-
}
|
|
11
|
-
const baselineScorerNames = Object.keys(baseline.summary.scorers).sort();
|
|
12
|
-
const candidateScorerNames = Object.keys(candidate.summary.scorers).sort();
|
|
13
|
-
if (JSON.stringify(baselineScorerNames) !== JSON.stringify(candidateScorerNames)) {
|
|
14
|
-
throw new Error(`Cannot compare evals with different scorers`);
|
|
15
|
-
}
|
|
16
|
-
const scorers = {};
|
|
17
|
-
for (const name of baselineScorerNames) {
|
|
18
|
-
const bMean = baseline.summary.scorers[name].mean;
|
|
19
|
-
const cMean = candidate.summary.scorers[name].mean;
|
|
20
|
-
const delta = round(cMean - bMean);
|
|
21
|
-
const deltaPercent = bMean > 0 ? round(delta / bMean * 100) : 0;
|
|
22
|
-
scorers[name] = { baselineMean: bMean, candidateMean: cMean, delta, deltaPercent };
|
|
23
|
-
}
|
|
24
|
-
const regressions = [];
|
|
25
|
-
const improvements = [];
|
|
26
|
-
const minLength = Math.min(baseline.items.length, candidate.items.length);
|
|
27
|
-
for (let i = 0; i < minLength; i++) {
|
|
28
|
-
const bItem = baseline.items[i];
|
|
29
|
-
const cItem = candidate.items[i];
|
|
30
|
-
if (bItem.error || cItem.error) continue;
|
|
31
|
-
for (const name of baselineScorerNames) {
|
|
32
|
-
const bScore = bItem.scores[name];
|
|
33
|
-
const cScore = cItem.scores[name];
|
|
34
|
-
if (bScore == null || cScore == null) continue;
|
|
35
|
-
const delta = round(cScore - bScore);
|
|
36
|
-
if (delta < -0.1)
|
|
37
|
-
regressions.push({
|
|
38
|
-
itemIndex: i,
|
|
39
|
-
input: bItem.input,
|
|
40
|
-
scorer: name,
|
|
41
|
-
baselineScore: bScore,
|
|
42
|
-
candidateScore: cScore,
|
|
43
|
-
delta
|
|
44
|
-
});
|
|
45
|
-
else if (delta > 0.1)
|
|
46
|
-
improvements.push({
|
|
47
|
-
itemIndex: i,
|
|
48
|
-
input: bItem.input,
|
|
49
|
-
scorer: name,
|
|
50
|
-
baselineScore: bScore,
|
|
51
|
-
candidateScore: cScore,
|
|
52
|
-
delta
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
let timing;
|
|
57
|
-
if (baseline.summary.timing && candidate.summary.timing) {
|
|
58
|
-
const bMean = baseline.summary.timing.mean;
|
|
59
|
-
const cMean = candidate.summary.timing.mean;
|
|
60
|
-
const delta = round(cMean - bMean);
|
|
61
|
-
const deltaPercent = bMean > 0 ? round(delta / bMean * 100) : 0;
|
|
62
|
-
timing = { baselineMean: bMean, candidateMean: cMean, delta, deltaPercent };
|
|
63
|
-
}
|
|
64
|
-
let cost;
|
|
65
|
-
if (baseline.totalCost > 0 || candidate.totalCost > 0) {
|
|
66
|
-
const delta = round(candidate.totalCost - baseline.totalCost);
|
|
67
|
-
const deltaPercent = baseline.totalCost > 0 ? round(delta / baseline.totalCost * 100) : 0;
|
|
68
|
-
cost = {
|
|
69
|
-
baselineTotal: baseline.totalCost,
|
|
70
|
-
candidateTotal: candidate.totalCost,
|
|
71
|
-
delta,
|
|
72
|
-
deltaPercent
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
const parts = [];
|
|
76
|
-
for (const name of baselineScorerNames) {
|
|
77
|
-
const s = scorers[name];
|
|
78
|
-
if (Math.abs(s.delta) > 1e-3) {
|
|
79
|
-
const direction = s.delta > 0 ? "improves" : "regresses";
|
|
80
|
-
const sign = s.delta > 0 ? "+" : "";
|
|
81
|
-
parts.push(
|
|
82
|
-
`'${name}' ${direction} by ${sign}${s.deltaPercent.toFixed(1)}% (${s.baselineMean.toFixed(2)} -> ${s.candidateMean.toFixed(2)})`
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
if (timing && Math.abs(timing.deltaPercent) > 1) {
|
|
87
|
-
const dir = timing.delta > 0 ? "slower" : "faster";
|
|
88
|
-
parts.push(`${Math.abs(timing.deltaPercent).toFixed(0)}% ${dir}`);
|
|
89
|
-
}
|
|
90
|
-
if (cost && Math.abs(cost.deltaPercent) > 1) {
|
|
91
|
-
const dir = cost.delta > 0 ? "more expensive" : "cheaper";
|
|
92
|
-
parts.push(`${Math.abs(cost.deltaPercent).toFixed(0)}% ${dir}`);
|
|
93
|
-
}
|
|
94
|
-
const summaryStr = `candidate ${parts.length > 0 ? parts.join(", ") : "no significant changes"} with ${regressions.length} regressions and ${improvements.length} improvements`;
|
|
95
|
-
return {
|
|
96
|
-
baseline: { id: baseline.id, metadata: baseline.metadata },
|
|
97
|
-
candidate: { id: candidate.id, metadata: candidate.metadata },
|
|
98
|
-
scorers,
|
|
99
|
-
timing,
|
|
100
|
-
cost,
|
|
101
|
-
regressions,
|
|
102
|
-
improvements,
|
|
103
|
-
summary: summaryStr
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// src/scorer.ts
|
|
108
|
-
function normalizeScorerResult(result) {
|
|
109
|
-
return typeof result === "number" ? { score: result } : result;
|
|
110
|
-
}
|
|
111
|
-
function scorer(config) {
|
|
112
|
-
return {
|
|
113
|
-
name: config.name,
|
|
114
|
-
description: config.description,
|
|
115
|
-
isLlm: false,
|
|
116
|
-
score: config.score
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// src/runner.ts
|
|
121
|
-
import { randomUUID } from "crypto";
|
|
122
|
-
function computeStats(scores) {
|
|
123
|
-
if (scores.length === 0) return { mean: 0, min: 0, max: 0, p50: 0, p95: 0 };
|
|
124
|
-
const sorted = [...scores].sort((a, b) => a - b);
|
|
125
|
-
const mean = sorted.reduce((a, b) => a + b, 0) / sorted.length;
|
|
126
|
-
const min = sorted[0];
|
|
127
|
-
const max = sorted[sorted.length - 1];
|
|
128
|
-
const p50 = sorted[Math.floor(sorted.length * 0.5)];
|
|
129
|
-
const p95 = sorted[Math.floor(sorted.length * 0.95)];
|
|
130
|
-
return { mean: round2(mean), min: round2(min), max: round2(max), p50: round2(p50), p95: round2(p95) };
|
|
131
|
-
}
|
|
132
|
-
function round2(n) {
|
|
133
|
-
return Math.round(n * 1e3) / 1e3;
|
|
134
|
-
}
|
|
135
|
-
function parseCost(cost) {
|
|
136
|
-
const match = cost.match(/^\$?([\d.]+)$/);
|
|
137
|
-
if (!match) throw new Error(`Invalid cost format: "${cost}"`);
|
|
138
|
-
return parseFloat(match[1]);
|
|
139
|
-
}
|
|
140
|
-
async function runEval(config, executeWorkflow, runtime) {
|
|
141
|
-
const startTime = Date.now();
|
|
142
|
-
const id = randomUUID();
|
|
143
|
-
const items = await config.dataset.getItems();
|
|
144
|
-
const concurrency = config.concurrency ?? 5;
|
|
145
|
-
const budgetLimit = config.budget ? parseCost(config.budget) : void 0;
|
|
146
|
-
const scorerContext = {
|
|
147
|
-
resolveProvider: (uri) => {
|
|
148
|
-
if (typeof runtime.resolveProvider !== "function") {
|
|
149
|
-
throw new Error(
|
|
150
|
-
`LLM scorers require a runtime with resolveProvider(). Ensure you are using a real AxlRuntime instance, not a mock.`
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
return runtime.resolveProvider(uri);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
const evalItems = new Array(items.length);
|
|
157
|
-
let totalCost = 0;
|
|
158
|
-
let budgetExceeded = false;
|
|
159
|
-
async function processItem(item, itemIndex) {
|
|
160
|
-
if (budgetExceeded) {
|
|
161
|
-
evalItems[itemIndex] = {
|
|
162
|
-
input: item.input,
|
|
163
|
-
annotations: item.annotations,
|
|
164
|
-
output: null,
|
|
165
|
-
error: "Budget exceeded",
|
|
166
|
-
scores: {}
|
|
167
|
-
};
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
const evalItem = {
|
|
171
|
-
input: item.input,
|
|
172
|
-
annotations: item.annotations,
|
|
173
|
-
output: null,
|
|
174
|
-
scores: {}
|
|
175
|
-
};
|
|
176
|
-
const itemStart = Date.now();
|
|
177
|
-
try {
|
|
178
|
-
const result = await executeWorkflow(item.input, runtime);
|
|
179
|
-
evalItem.duration = Date.now() - itemStart;
|
|
180
|
-
evalItem.output = result.output;
|
|
181
|
-
evalItem.cost = result.cost;
|
|
182
|
-
if (result.cost != null) {
|
|
183
|
-
totalCost += result.cost;
|
|
184
|
-
}
|
|
185
|
-
} catch (err) {
|
|
186
|
-
evalItem.duration = Date.now() - itemStart;
|
|
187
|
-
evalItem.error = err instanceof Error ? err.message : String(err);
|
|
188
|
-
evalItems[itemIndex] = evalItem;
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
if (budgetLimit != null && totalCost > budgetLimit) {
|
|
192
|
-
budgetExceeded = true;
|
|
193
|
-
}
|
|
194
|
-
evalItem.scoreDetails = {};
|
|
195
|
-
let itemScorerCost = 0;
|
|
196
|
-
for (const scorer2 of config.scorers) {
|
|
197
|
-
const scorerStart = Date.now();
|
|
198
|
-
try {
|
|
199
|
-
const raw = await scorer2.score(
|
|
200
|
-
evalItem.output,
|
|
201
|
-
item.input,
|
|
202
|
-
item.annotations,
|
|
203
|
-
scorerContext
|
|
204
|
-
);
|
|
205
|
-
const scorerResult = normalizeScorerResult(raw);
|
|
206
|
-
if (scorerResult.cost != null) {
|
|
207
|
-
itemScorerCost += scorerResult.cost;
|
|
208
|
-
totalCost += scorerResult.cost;
|
|
209
|
-
}
|
|
210
|
-
const scorerDuration = Date.now() - scorerStart;
|
|
211
|
-
if (!Number.isFinite(scorerResult.score) || scorerResult.score < 0 || scorerResult.score > 1) {
|
|
212
|
-
if (!evalItem.scorerErrors) evalItem.scorerErrors = [];
|
|
213
|
-
evalItem.scorerErrors.push(
|
|
214
|
-
`Scorer "${scorer2.name}" returned out-of-range score ${scorerResult.score} for input ${JSON.stringify(item.input)}`
|
|
215
|
-
);
|
|
216
|
-
evalItem.scores[scorer2.name] = null;
|
|
217
|
-
evalItem.scoreDetails[scorer2.name] = {
|
|
218
|
-
score: null,
|
|
219
|
-
metadata: scorerResult.metadata,
|
|
220
|
-
duration: scorerDuration,
|
|
221
|
-
cost: scorerResult.cost
|
|
222
|
-
};
|
|
223
|
-
} else {
|
|
224
|
-
evalItem.scores[scorer2.name] = round2(scorerResult.score);
|
|
225
|
-
evalItem.scoreDetails[scorer2.name] = {
|
|
226
|
-
score: round2(scorerResult.score),
|
|
227
|
-
metadata: scorerResult.metadata,
|
|
228
|
-
duration: scorerDuration,
|
|
229
|
-
cost: scorerResult.cost
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
} catch (err) {
|
|
233
|
-
const errCost = typeof err?.cost === "number" ? err.cost : void 0;
|
|
234
|
-
if (errCost != null) {
|
|
235
|
-
itemScorerCost += errCost;
|
|
236
|
-
totalCost += errCost;
|
|
237
|
-
}
|
|
238
|
-
if (!evalItem.scorerErrors) evalItem.scorerErrors = [];
|
|
239
|
-
evalItem.scorerErrors.push(
|
|
240
|
-
`Scorer "${scorer2.name}" threw: ${err instanceof Error ? err.message : String(err)}`
|
|
241
|
-
);
|
|
242
|
-
evalItem.scores[scorer2.name] = null;
|
|
243
|
-
evalItem.scoreDetails[scorer2.name] = {
|
|
244
|
-
score: null,
|
|
245
|
-
duration: Date.now() - scorerStart,
|
|
246
|
-
cost: errCost
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
evalItem.scorerCost = itemScorerCost > 0 ? itemScorerCost : void 0;
|
|
251
|
-
evalItems[itemIndex] = evalItem;
|
|
252
|
-
}
|
|
253
|
-
let index = 0;
|
|
254
|
-
async function runNext() {
|
|
255
|
-
while (index < items.length) {
|
|
256
|
-
const currentIndex = index++;
|
|
257
|
-
await processItem(items[currentIndex], currentIndex);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => runNext());
|
|
261
|
-
await Promise.all(workers);
|
|
262
|
-
const failures = evalItems.filter((i) => i.error).length;
|
|
263
|
-
const scorerNames = config.scorers.map((s) => s.name);
|
|
264
|
-
const scorerStats = {};
|
|
265
|
-
for (const name of scorerNames) {
|
|
266
|
-
const scores = evalItems.filter((i) => !i.error && i.scores[name] != null).map((i) => i.scores[name]);
|
|
267
|
-
scorerStats[name] = computeStats(scores);
|
|
268
|
-
}
|
|
269
|
-
const durations = evalItems.filter((i) => !i.error && i.duration != null).map((i) => i.duration);
|
|
270
|
-
const timing = durations.length > 0 ? computeStats(durations) : void 0;
|
|
271
|
-
return {
|
|
272
|
-
id,
|
|
273
|
-
workflow: config.workflow,
|
|
274
|
-
dataset: config.dataset.name,
|
|
275
|
-
metadata: config.metadata ?? {},
|
|
276
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
277
|
-
totalCost,
|
|
278
|
-
duration: Date.now() - startTime,
|
|
279
|
-
items: evalItems,
|
|
280
|
-
summary: { count: items.length, failures, scorers: scorerStats, timing }
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export {
|
|
285
|
-
evalCompare,
|
|
286
|
-
normalizeScorerResult,
|
|
287
|
-
scorer,
|
|
288
|
-
runEval
|
|
289
|
-
};
|
|
290
|
-
//# sourceMappingURL=chunk-F2AALK5U.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/compare.ts","../src/scorer.ts","../src/runner.ts"],"sourcesContent":["import type { EvalResult, EvalComparison, EvalRegression, EvalImprovement } from './types.js';\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n\nexport function evalCompare(baseline: EvalResult, candidate: EvalResult): EvalComparison {\n if (baseline.dataset !== candidate.dataset) {\n throw new Error(\n `Cannot compare evals from different datasets: \"${baseline.dataset}\" vs \"${candidate.dataset}\"`,\n );\n }\n\n const baselineScorerNames = Object.keys(baseline.summary.scorers).sort();\n const candidateScorerNames = Object.keys(candidate.summary.scorers).sort();\n if (JSON.stringify(baselineScorerNames) !== JSON.stringify(candidateScorerNames)) {\n throw new Error(`Cannot compare evals with different scorers`);\n }\n\n const scorers: EvalComparison['scorers'] = {};\n for (const name of baselineScorerNames) {\n const bMean = baseline.summary.scorers[name].mean;\n const cMean = candidate.summary.scorers[name].mean;\n const delta = round(cMean - bMean);\n const deltaPercent = bMean > 0 ? round((delta / bMean) * 100) : 0;\n scorers[name] = { baselineMean: bMean, candidateMean: cMean, delta, deltaPercent };\n }\n\n const regressions: EvalRegression[] = [];\n const improvements: EvalImprovement[] = [];\n\n const minLength = Math.min(baseline.items.length, candidate.items.length);\n for (let i = 0; i < minLength; i++) {\n const bItem = baseline.items[i];\n const cItem = candidate.items[i];\n if (bItem.error || cItem.error) continue;\n for (const name of baselineScorerNames) {\n const bScore = bItem.scores[name];\n const cScore = cItem.scores[name];\n if (bScore == null || cScore == null) continue;\n const delta = round(cScore - bScore);\n if (delta < -0.1)\n regressions.push({\n itemIndex: i,\n input: bItem.input,\n scorer: name,\n baselineScore: bScore,\n candidateScore: cScore,\n delta,\n });\n else if (delta > 0.1)\n improvements.push({\n itemIndex: i,\n input: bItem.input,\n scorer: name,\n baselineScore: bScore,\n candidateScore: cScore,\n delta,\n });\n }\n }\n\n // Timing comparison (use pre-computed summary.timing when available)\n let timing: EvalComparison['timing'];\n if (baseline.summary.timing && candidate.summary.timing) {\n const bMean = baseline.summary.timing.mean;\n const cMean = candidate.summary.timing.mean;\n const delta = round(cMean - bMean);\n const deltaPercent = bMean > 0 ? round((delta / bMean) * 100) : 0;\n timing = { baselineMean: bMean, candidateMean: cMean, delta, deltaPercent };\n }\n\n // Cost comparison\n let cost: EvalComparison['cost'];\n if (baseline.totalCost > 0 || candidate.totalCost > 0) {\n const delta = round(candidate.totalCost - baseline.totalCost);\n const deltaPercent = baseline.totalCost > 0 ? round((delta / baseline.totalCost) * 100) : 0;\n cost = {\n baselineTotal: baseline.totalCost,\n candidateTotal: candidate.totalCost,\n delta,\n deltaPercent,\n };\n }\n\n const parts: string[] = [];\n for (const name of baselineScorerNames) {\n const s = scorers[name];\n if (Math.abs(s.delta) > 0.001) {\n const direction = s.delta > 0 ? 'improves' : 'regresses';\n const sign = s.delta > 0 ? '+' : '';\n parts.push(\n `'${name}' ${direction} by ${sign}${s.deltaPercent.toFixed(1)}% (${s.baselineMean.toFixed(2)} -> ${s.candidateMean.toFixed(2)})`,\n );\n }\n }\n if (timing && Math.abs(timing.deltaPercent) > 1) {\n const dir = timing.delta > 0 ? 'slower' : 'faster';\n parts.push(`${Math.abs(timing.deltaPercent).toFixed(0)}% ${dir}`);\n }\n if (cost && Math.abs(cost.deltaPercent) > 1) {\n const dir = cost.delta > 0 ? 'more expensive' : 'cheaper';\n parts.push(`${Math.abs(cost.deltaPercent).toFixed(0)}% ${dir}`);\n }\n const summaryStr = `candidate ${parts.length > 0 ? parts.join(', ') : 'no significant changes'} with ${regressions.length} regressions and ${improvements.length} improvements`;\n\n return {\n baseline: { id: baseline.id, metadata: baseline.metadata },\n candidate: { id: candidate.id, metadata: candidate.metadata },\n scorers,\n timing,\n cost,\n regressions,\n improvements,\n summary: summaryStr,\n };\n}\n","/** Result from a scorer that includes metadata beyond the numeric score. */\nexport type ScorerResult = {\n score: number;\n metadata?: Record<string, unknown>;\n cost?: number;\n};\n\n/** Normalize a scorer return value to a ScorerResult. */\nexport function normalizeScorerResult(result: number | ScorerResult): ScorerResult {\n return typeof result === 'number' ? { score: result } : result;\n}\n\n/** Context passed to scorers by the eval runner. */\nexport type ScorerContext = {\n /** Resolve a provider:model URI to a provider instance and model name. */\n resolveProvider: (modelUri: string) => {\n provider: {\n chat(\n messages: { role: string; content: string }[],\n options: {\n model: string;\n temperature?: number;\n responseFormat?: { type: string; json_schema?: unknown };\n },\n ): Promise<{ content: string; cost?: number }>;\n };\n model: string;\n };\n};\n\nexport type ScorerFn<TOutput, TInput, TAnnotations> = (\n output: TOutput,\n input: TInput,\n annotations?: TAnnotations,\n) => number | ScorerResult;\n\nexport type ScorerConfig<TOutput = unknown, TInput = unknown, TAnnotations = unknown> = {\n name: string;\n description: string;\n score: ScorerFn<TOutput, TInput, TAnnotations>;\n};\n\nexport type Scorer<TOutput = unknown, TInput = unknown, TAnnotations = unknown> = {\n readonly name: string;\n readonly description: string;\n readonly isLlm: boolean;\n score(\n output: TOutput,\n input: TInput,\n annotations?: TAnnotations,\n context?: ScorerContext,\n ): number | ScorerResult | Promise<number | ScorerResult>;\n};\n\nexport function scorer<TOutput = unknown, TInput = unknown, TAnnotations = unknown>(\n config: ScorerConfig<TOutput, TInput, TAnnotations>,\n): Scorer<TOutput, TInput, TAnnotations> {\n return {\n name: config.name,\n description: config.description,\n isLlm: false,\n score: config.score,\n };\n}\n","import type { AxlRuntime } from '@axlsdk/axl';\nimport type { EvalConfig, EvalResult, EvalItem, EvalSummary } from './types.js';\nimport type { ScorerContext } from './scorer.js';\nimport { normalizeScorerResult } from './scorer.js';\nimport { randomUUID } from 'node:crypto';\n\nfunction computeStats(scores: number[]): {\n mean: number;\n min: number;\n max: number;\n p50: number;\n p95: number;\n} {\n if (scores.length === 0) return { mean: 0, min: 0, max: 0, p50: 0, p95: 0 };\n const sorted = [...scores].sort((a, b) => a - b);\n const mean = sorted.reduce((a, b) => a + b, 0) / sorted.length;\n const min = sorted[0];\n const max = sorted[sorted.length - 1];\n const p50 = sorted[Math.floor(sorted.length * 0.5)];\n const p95 = sorted[Math.floor(sorted.length * 0.95)];\n return { mean: round(mean), min: round(min), max: round(max), p50: round(p50), p95: round(p95) };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n\nfunction parseCost(cost: string): number {\n const match = cost.match(/^\\$?([\\d.]+)$/);\n if (!match) throw new Error(`Invalid cost format: \"${cost}\"`);\n return parseFloat(match[1]);\n}\n\nexport async function runEval(\n config: EvalConfig,\n executeWorkflow: (\n input: unknown,\n runtime: AxlRuntime,\n ) => Promise<{ output: unknown; cost?: number }>,\n runtime: AxlRuntime,\n): Promise<EvalResult> {\n const startTime = Date.now();\n const id = randomUUID();\n const items = await config.dataset.getItems();\n const concurrency = config.concurrency ?? 5;\n const budgetLimit = config.budget ? parseCost(config.budget) : undefined;\n\n // Create a scorer context that LLM scorers use to resolve providers.\n const scorerContext: ScorerContext = {\n resolveProvider: (uri: string) => {\n if (typeof runtime.resolveProvider !== 'function') {\n throw new Error(\n `LLM scorers require a runtime with resolveProvider(). ` +\n `Ensure you are using a real AxlRuntime instance, not a mock.`,\n );\n }\n return runtime.resolveProvider(uri);\n },\n };\n\n const evalItems: EvalItem[] = new Array(items.length);\n let totalCost = 0;\n let budgetExceeded = false;\n\n async function processItem(item: (typeof items)[0], itemIndex: number): Promise<void> {\n if (budgetExceeded) {\n evalItems[itemIndex] = {\n input: item.input,\n annotations: item.annotations,\n output: null,\n error: 'Budget exceeded',\n scores: {},\n };\n return;\n }\n\n const evalItem: EvalItem = {\n input: item.input,\n annotations: item.annotations,\n output: null,\n scores: {},\n };\n const itemStart = Date.now();\n try {\n const result = await executeWorkflow(item.input, runtime);\n evalItem.duration = Date.now() - itemStart;\n evalItem.output = result.output;\n evalItem.cost = result.cost;\n if (result.cost != null) {\n totalCost += result.cost;\n }\n } catch (err) {\n evalItem.duration = Date.now() - itemStart;\n evalItem.error = err instanceof Error ? err.message : String(err);\n evalItems[itemIndex] = evalItem;\n return;\n }\n\n if (budgetLimit != null && totalCost > budgetLimit) {\n budgetExceeded = true;\n }\n\n evalItem.scoreDetails = {};\n let itemScorerCost = 0;\n\n for (const scorer of config.scorers) {\n const scorerStart = Date.now();\n try {\n const raw = await scorer.score(\n evalItem.output,\n item.input,\n item.annotations,\n scorerContext,\n );\n const scorerResult = normalizeScorerResult(raw);\n\n if (scorerResult.cost != null) {\n itemScorerCost += scorerResult.cost;\n totalCost += scorerResult.cost;\n }\n\n const scorerDuration = Date.now() - scorerStart;\n\n if (\n !Number.isFinite(scorerResult.score) ||\n scorerResult.score < 0 ||\n scorerResult.score > 1\n ) {\n if (!evalItem.scorerErrors) evalItem.scorerErrors = [];\n evalItem.scorerErrors.push(\n `Scorer \"${scorer.name}\" returned out-of-range score ${scorerResult.score} for input ${JSON.stringify(item.input)}`,\n );\n evalItem.scores[scorer.name] = null;\n evalItem.scoreDetails[scorer.name] = {\n score: null,\n metadata: scorerResult.metadata,\n duration: scorerDuration,\n cost: scorerResult.cost,\n };\n } else {\n evalItem.scores[scorer.name] = round(scorerResult.score);\n evalItem.scoreDetails[scorer.name] = {\n score: round(scorerResult.score),\n metadata: scorerResult.metadata,\n duration: scorerDuration,\n cost: scorerResult.cost,\n };\n }\n } catch (err) {\n // Capture cost from error (LLM scorer attaches it)\n const errCost = typeof (err as any)?.cost === 'number' ? (err as any).cost : undefined;\n if (errCost != null) {\n itemScorerCost += errCost;\n totalCost += errCost;\n }\n if (!evalItem.scorerErrors) evalItem.scorerErrors = [];\n evalItem.scorerErrors.push(\n `Scorer \"${scorer.name}\" threw: ${err instanceof Error ? err.message : String(err)}`,\n );\n evalItem.scores[scorer.name] = null;\n evalItem.scoreDetails[scorer.name] = {\n score: null,\n duration: Date.now() - scorerStart,\n cost: errCost,\n };\n }\n }\n evalItem.scorerCost = itemScorerCost > 0 ? itemScorerCost : undefined;\n evalItems[itemIndex] = evalItem;\n }\n\n let index = 0;\n async function runNext(): Promise<void> {\n while (index < items.length) {\n const currentIndex = index++;\n await processItem(items[currentIndex], currentIndex);\n }\n }\n\n const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => runNext());\n await Promise.all(workers);\n\n const failures = evalItems.filter((i) => i.error).length;\n const scorerNames = config.scorers.map((s) => s.name);\n const scorerStats: EvalSummary['scorers'] = {};\n for (const name of scorerNames) {\n const scores = evalItems\n .filter((i) => !i.error && i.scores[name] != null)\n .map((i) => i.scores[name] as number);\n scorerStats[name] = computeStats(scores);\n }\n\n const durations = evalItems.filter((i) => !i.error && i.duration != null).map((i) => i.duration!);\n const timing = durations.length > 0 ? computeStats(durations) : undefined;\n\n return {\n id,\n workflow: config.workflow,\n dataset: config.dataset.name,\n metadata: config.metadata ?? {},\n timestamp: new Date().toISOString(),\n totalCost,\n duration: Date.now() - startTime,\n items: evalItems,\n summary: { count: items.length, failures, scorers: scorerStats, timing },\n };\n}\n"],"mappings":"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","names":["round","scorer"]}
|