@axlsdk/eval 0.13.3 → 0.13.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,7 +46,6 @@ export default defineEval({
46
46
  description: 'Is the answer relevant to the question?',
47
47
  model: 'openai:gpt-4o',
48
48
  system: 'Rate whether the answer is relevant to the question asked.',
49
- schema: z.object({ score: z.number(), reasoning: z.string() }),
50
49
  }),
51
50
  ],
52
51
  });
@@ -108,7 +107,7 @@ Scorers rate each output on a 0-1 scale. You can mix deterministic and LLM score
108
107
 
109
108
  ### Deterministic scorers
110
109
 
111
- Pure functions — fast, free, deterministic. The `score` callback receives `(output, input, annotations?)`:
110
+ Pure functions — fast, free, deterministic. The `score` callback receives `(output, input, annotations?)` and returns a number (0-1) or a `ScorerResult` with metadata:
112
111
 
113
112
  ```typescript
114
113
  import { scorer } from '@axlsdk/eval';
@@ -119,29 +118,54 @@ const containsAnswer = scorer({
119
118
  score: (output, _input, annotations) =>
120
119
  String(output).includes(String(annotations?.answer)) ? 1 : 0,
121
120
  });
121
+
122
+ // Returning rich metadata via ScorerResult
123
+ const lengthScore = scorer({
124
+ name: 'length',
125
+ description: 'Rates output by character length',
126
+ score: (output) => {
127
+ const len = String(output).length;
128
+ return { score: Math.min(len / 500, 1), metadata: { charCount: len } };
129
+ },
130
+ });
122
131
  ```
123
132
 
124
133
  ### LLM scorers
125
134
 
126
- Use an LLM as a judge. The scorer constructs a prompt from the input, output, and annotations, calls the LLM, and validates the response against your Zod schema:
135
+ Use an LLM as a judge. The scorer constructs a prompt from the input, output, and annotations, calls the LLM, and validates the response:
127
136
 
128
137
  ```typescript
129
138
  import { llmScorer } from '@axlsdk/eval';
130
- import { z } from 'zod';
131
139
 
132
140
  const qualityJudge = llmScorer({
133
141
  name: 'quality',
134
142
  description: 'Rates overall output quality',
135
143
  model: 'openai:gpt-4o', // provider:model URI
136
144
  system: 'Rate the quality of the AI output.',
145
+ });
146
+ ```
147
+
148
+ The default schema is `z.object({ score: z.number().min(0).max(1), reasoning: z.string() })` — the LLM returns a 0-1 score with an explanation. The reasoning (and any other schema fields) are available on each item via `scoreDetails` — see [Understanding Results](#understanding-results). For custom scoring dimensions, provide your own schema:
149
+
150
+ ```typescript
151
+ import { z } from 'zod';
152
+
153
+ const detailedJudge = llmScorer({
154
+ name: 'detailed',
155
+ description: 'Rates quality with confidence',
156
+ model: 'openai:gpt-4o',
157
+ system: 'Rate quality and your confidence in the rating.',
137
158
  schema: z.object({
138
159
  score: z.number().min(0).max(1),
139
160
  reasoning: z.string(),
161
+ confidence: z.number().min(0).max(1),
140
162
  }),
141
163
  temperature: 0.2, // default: 0.2 (low for consistency)
142
164
  });
143
165
  ```
144
166
 
167
+ The schema is converted to JSON Schema and included in the LLM prompt, so the judge knows exactly what structure to produce.
168
+
145
169
  The `model` field uses a `provider:model` URI. The provider is resolved automatically at eval time — just set the right API key:
146
170
 
147
171
  | Provider | URI prefix | Env var |
@@ -170,9 +194,10 @@ npx axl-eval ./evals/qa.eval.ts # run a single file
170
194
  npx axl-eval ./evals/ # run all *.eval.* files in a directory
171
195
  npx axl-eval ./evals/ --output ./results/v1.json # save results to JSON
172
196
  npx axl-eval ./evals/ --config ./axl.config.ts # use a specific runtime config
197
+ npx axl-eval ./evals/ --conditions development # add Node.js import conditions (monorepo source exports)
173
198
  ```
174
199
 
175
- The CLI resolves a runtime automatically: `--config <path>` > auto-detect `axl.config.*` > bare `new AxlRuntime()` (providers from env vars).
200
+ The CLI resolves a runtime automatically: `--config <path>` > auto-detect `axl.config.*` > bare `new AxlRuntime()` (providers from env vars). Use `--conditions` when your eval file imports from monorepo packages that use conditional exports (e.g., `"development"` condition for source TypeScript instead of compiled dist).
176
201
 
177
202
  ### Programmatic
178
203
 
@@ -232,49 +257,97 @@ See the [@axlsdk/studio README](../axl-studio/README.md#lazy-eval-loading) for d
232
257
 
233
258
  ## Understanding Results
234
259
 
235
- Each eval run returns an `EvalResult` with per-item scores and aggregate statistics.
260
+ Each eval run returns an `EvalResult` with per-item scores and aggregate statistics. Every item captures timing and cost alongside scores.
261
+
262
+ Each item has two ways to access scores:
236
263
 
237
- **Scores** are `number | null` per scorer per item:
238
- - `0` to `1` — valid score from the scorer
239
- - `null` — the scorer failed (see `item.scorerErrors` for details)
264
+ - **`item.scores`** — quick numeric lookup: `Record<string, number | null>`. Use this for simple checks and aggregation. `null` means the scorer failed (see `item.scorerErrors`).
265
+ - **`item.scoreDetails`** — full context: `Record<string, ScorerDetail>`. Each detail has the numeric score plus `metadata` (e.g., LLM reasoning), per-scorer `duration`, and `cost`. Use this when you need to understand *why* a score is what it is.
240
266
 
241
267
  Summary statistics (mean, p50, p95, etc.) exclude `null` scores. If all scores for a scorer are `null`, the CLI shows `--` instead of misleading `0.00`.
242
268
 
243
269
  ```typescript
244
270
  const results = await runtime.eval({ ... });
245
271
 
246
- // Aggregate stats
272
+ // ── Aggregate stats ──────────────────────────────────
247
273
  console.log(results.summary.scorers['quality'].mean); // 0.85
248
274
  console.log(results.summary.count); // 50 items
249
275
  console.log(results.summary.failures); // 2 workflow errors
276
+ console.log(results.summary.timing); // { mean, min, max, p50, p95 } in ms
277
+ console.log(results.totalCost); // 0.42 (workflow + scorer LLM costs)
250
278
 
251
- // Per-item inspection
279
+ // ── Per-item inspection ──────────────────────────────
252
280
  for (const item of results.items) {
253
281
  if (item.error) continue; // workflow threw
282
+
283
+ // Timing and cost
284
+ console.log(item.duration); // workflow execution ms
285
+ console.log(item.cost); // workflow LLM cost
286
+ console.log(item.scorerCost); // total scorer cost for this item
287
+
288
+ // Quick score access
289
+ console.log(item.scores['quality']); // 0.85 or null
290
+
291
+ // Rich per-scorer detail — reasoning, timing, cost
292
+ const detail = item.scoreDetails?.['quality'];
293
+ if (detail) {
294
+ console.log(detail.score); // 0.85
295
+ console.log(detail.metadata?.reasoning); // "The answer is relevant..."
296
+ console.log(detail.duration); // scorer execution ms
297
+ console.log(detail.cost); // scorer LLM cost
298
+ }
299
+
300
+ // Error handling
254
301
  if (item.scores['quality'] === null) {
255
- console.log('Scorer failed:', item.scorerErrors); // e.g., ["Scorer "quality" threw: ..."]
302
+ console.log('Scorer failed:', item.scorerErrors); // ["Scorer "quality" threw: ..."]
256
303
  }
257
304
  }
258
-
259
- // Cost includes both workflow and LLM scorer calls
260
- console.log(results.totalCost); // 0.42
261
305
  ```
262
306
 
263
307
  ## Comparing Results
264
308
 
265
- Compare two runs to detect regressions and improvements:
309
+ Compare two runs to detect regressions and improvements. Runs must use the same dataset and scorers.
266
310
 
267
311
  ```bash
268
312
  npx axl-eval compare ./results/v1.json ./results/v2.json
269
313
  npx axl-eval compare v1.json v2.json --fail-on-regression # exit 1 if worse
270
314
  ```
271
315
 
316
+ ```
317
+ Compare: baseline (3f8a2b1c) -> candidate (9d4e7f6a)
318
+
319
+ Scorer Baseline Candidate Delta Change
320
+ ──────────────────────────────────────────────────
321
+ quality 0.750 0.850 +0.100 +13.3%
322
+ safety 0.900 0.900 +0.000 +0.0%
323
+
324
+ Timing: baseline 2.10s -> candidate 4.30s (+104.8%)
325
+ Cost: baseline $0.45 -> candidate $0.31 (-31.1%)
326
+
327
+ Regressions: 1 | Improvements: 3 | Stable: 16
328
+ ```
329
+
330
+ Programmatically:
331
+
272
332
  ```typescript
273
333
  import { evalCompare } from '@axlsdk/eval';
274
334
 
275
335
  const comparison = evalCompare(v1Results, v2Results);
276
- console.log(comparison.regressions); // items that got worse
277
- console.log(comparison.improvements); // items that got better
336
+
337
+ // Score changes
338
+ console.log(comparison.scorers.quality.delta); // +0.1
339
+ console.log(comparison.scorers.quality.deltaPercent); // +13.3
340
+
341
+ // Timing and cost tradeoffs
342
+ console.log(comparison.timing?.deltaPercent); // +104.8 (slower)
343
+ console.log(comparison.cost?.deltaPercent); // -31.1 (cheaper)
344
+
345
+ // Per-item regressions/improvements (delta > 0.1 threshold)
346
+ for (const r of comparison.regressions) {
347
+ console.log(`Item ${r.itemIndex}: ${r.scorer} dropped ${r.baselineScore} → ${r.candidateScore}`);
348
+ }
349
+
350
+ console.log(comparison.summary); // human-readable one-liner
278
351
  ```
279
352
 
280
353
  ## Eval Files in Detail
@@ -321,7 +394,26 @@ return { output, cost: ctx.totalCost };
321
394
 
322
395
  ### Common patterns
323
396
 
324
- **Budget** — cap cost per item with `runtime.createContext({ budget: '$0.50' })`.
397
+ **Concurrency** — process items in parallel (default: 5):
398
+
399
+ ```typescript
400
+ export default defineEval({
401
+ workflow: 'qa-eval',
402
+ dataset: ds,
403
+ scorers: [qualityJudge],
404
+ concurrency: 10, // run 10 items in parallel
405
+ budget: '$5.00', // stop if total cost exceeds $5
406
+ });
407
+ ```
408
+
409
+ **Per-item budget** — cap cost for a single workflow execution:
410
+
411
+ ```typescript
412
+ export async function executeWorkflow(input: { question: string }, runtime: AxlRuntime) {
413
+ const ctx = runtime.createContext({ budget: '$0.50' });
414
+ return { output: await ctx.ask(qaAgent, input.question) };
415
+ }
416
+ ```
325
417
 
326
418
  **Timeout** — abort slow items:
327
419
 
@@ -35,6 +35,7 @@ function evalCompare(baseline, candidate) {
35
35
  const delta = round(cScore - bScore);
36
36
  if (delta < -0.1)
37
37
  regressions.push({
38
+ itemIndex: i,
38
39
  input: bItem.input,
39
40
  scorer: name,
40
41
  baselineScore: bScore,
@@ -43,6 +44,7 @@ function evalCompare(baseline, candidate) {
43
44
  });
44
45
  else if (delta > 0.1)
45
46
  improvements.push({
47
+ itemIndex: i,
46
48
  input: bItem.input,
47
49
  scorer: name,
48
50
  baselineScore: bScore,
@@ -51,6 +53,25 @@ function evalCompare(baseline, candidate) {
51
53
  });
52
54
  }
53
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
+ }
54
75
  const parts = [];
55
76
  for (const name of baselineScorerNames) {
56
77
  const s = scorers[name];
@@ -62,17 +83,40 @@ function evalCompare(baseline, candidate) {
62
83
  );
63
84
  }
64
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
+ }
65
94
  const summaryStr = `candidate ${parts.length > 0 ? parts.join(", ") : "no significant changes"} with ${regressions.length} regressions and ${improvements.length} improvements`;
66
95
  return {
67
96
  baseline: { id: baseline.id, metadata: baseline.metadata },
68
97
  candidate: { id: candidate.id, metadata: candidate.metadata },
69
98
  scorers,
99
+ timing,
100
+ cost,
70
101
  regressions,
71
102
  improvements,
72
103
  summary: summaryStr
73
104
  };
74
105
  }
75
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
+
76
120
  // src/runner.ts
77
121
  import { randomUUID } from "crypto";
78
122
  function computeStats(scores) {
@@ -109,18 +153,18 @@ async function runEval(config, executeWorkflow, runtime) {
109
153
  return runtime.resolveProvider(uri);
110
154
  }
111
155
  };
112
- const evalItems = [];
156
+ const evalItems = new Array(items.length);
113
157
  let totalCost = 0;
114
158
  let budgetExceeded = false;
115
- async function processItem(item) {
159
+ async function processItem(item, itemIndex) {
116
160
  if (budgetExceeded) {
117
- evalItems.push({
161
+ evalItems[itemIndex] = {
118
162
  input: item.input,
119
163
  annotations: item.annotations,
120
164
  output: null,
121
165
  error: "Budget exceeded",
122
166
  scores: {}
123
- });
167
+ };
124
168
  return;
125
169
  }
126
170
  const evalItem = {
@@ -129,64 +173,88 @@ async function runEval(config, executeWorkflow, runtime) {
129
173
  output: null,
130
174
  scores: {}
131
175
  };
176
+ const itemStart = Date.now();
132
177
  try {
133
178
  const result = await executeWorkflow(item.input, runtime);
179
+ evalItem.duration = Date.now() - itemStart;
134
180
  evalItem.output = result.output;
181
+ evalItem.cost = result.cost;
135
182
  if (result.cost != null) {
136
183
  totalCost += result.cost;
137
184
  }
138
185
  } catch (err) {
186
+ evalItem.duration = Date.now() - itemStart;
139
187
  evalItem.error = err instanceof Error ? err.message : String(err);
140
- evalItems.push(evalItem);
188
+ evalItems[itemIndex] = evalItem;
141
189
  return;
142
190
  }
143
191
  if (budgetLimit != null && totalCost > budgetLimit) {
144
192
  budgetExceeded = true;
145
193
  }
146
- for (const scorer of config.scorers) {
194
+ evalItem.scoreDetails = {};
195
+ let itemScorerCost = 0;
196
+ for (const scorer2 of config.scorers) {
197
+ const scorerStart = Date.now();
147
198
  try {
148
- const score = await scorer.score(
199
+ const raw = await scorer2.score(
149
200
  evalItem.output,
150
201
  item.input,
151
202
  item.annotations,
152
203
  scorerContext
153
204
  );
154
- if (scorer.isLlm) {
155
- const lastCost = scorer._lastCost;
156
- if (lastCost != null) {
157
- totalCost += lastCost;
158
- }
205
+ const scorerResult = normalizeScorerResult(raw);
206
+ if (scorerResult.cost != null) {
207
+ itemScorerCost += scorerResult.cost;
208
+ totalCost += scorerResult.cost;
159
209
  }
160
- if (!Number.isFinite(score) || score < 0 || score > 1) {
210
+ const scorerDuration = Date.now() - scorerStart;
211
+ if (!Number.isFinite(scorerResult.score) || scorerResult.score < 0 || scorerResult.score > 1) {
161
212
  if (!evalItem.scorerErrors) evalItem.scorerErrors = [];
162
213
  evalItem.scorerErrors.push(
163
- `Scorer "${scorer.name}" returned out-of-range score ${score} for input ${JSON.stringify(item.input)}`
214
+ `Scorer "${scorer2.name}" returned out-of-range score ${scorerResult.score} for input ${JSON.stringify(item.input)}`
164
215
  );
165
- evalItem.scores[scorer.name] = null;
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
+ };
166
223
  } else {
167
- evalItem.scores[scorer.name] = round2(score);
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
+ };
168
231
  }
169
232
  } catch (err) {
170
- if (scorer.isLlm) {
171
- const lastCost = scorer._lastCost;
172
- if (lastCost != null) {
173
- totalCost += lastCost;
174
- }
233
+ const errCost = typeof err?.cost === "number" ? err.cost : void 0;
234
+ if (errCost != null) {
235
+ itemScorerCost += errCost;
236
+ totalCost += errCost;
175
237
  }
176
238
  if (!evalItem.scorerErrors) evalItem.scorerErrors = [];
177
239
  evalItem.scorerErrors.push(
178
- `Scorer "${scorer.name}" threw: ${err instanceof Error ? err.message : String(err)}`
240
+ `Scorer "${scorer2.name}" threw: ${err instanceof Error ? err.message : String(err)}`
179
241
  );
180
- evalItem.scores[scorer.name] = null;
242
+ evalItem.scores[scorer2.name] = null;
243
+ evalItem.scoreDetails[scorer2.name] = {
244
+ score: null,
245
+ duration: Date.now() - scorerStart,
246
+ cost: errCost
247
+ };
181
248
  }
182
249
  }
183
- evalItems.push(evalItem);
250
+ evalItem.scorerCost = itemScorerCost > 0 ? itemScorerCost : void 0;
251
+ evalItems[itemIndex] = evalItem;
184
252
  }
185
253
  let index = 0;
186
254
  async function runNext() {
187
255
  while (index < items.length) {
188
256
  const currentIndex = index++;
189
- await processItem(items[currentIndex]);
257
+ await processItem(items[currentIndex], currentIndex);
190
258
  }
191
259
  }
192
260
  const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => runNext());
@@ -198,6 +266,8 @@ async function runEval(config, executeWorkflow, runtime) {
198
266
  const scores = evalItems.filter((i) => !i.error && i.scores[name] != null).map((i) => i.scores[name]);
199
267
  scorerStats[name] = computeStats(scores);
200
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;
201
271
  return {
202
272
  id,
203
273
  workflow: config.workflow,
@@ -207,12 +277,14 @@ async function runEval(config, executeWorkflow, runtime) {
207
277
  totalCost,
208
278
  duration: Date.now() - startTime,
209
279
  items: evalItems,
210
- summary: { count: items.length, failures, scorers: scorerStats }
280
+ summary: { count: items.length, failures, scorers: scorerStats, timing }
211
281
  };
212
282
  }
213
283
 
214
284
  export {
215
285
  evalCompare,
286
+ normalizeScorerResult,
287
+ scorer,
216
288
  runEval
217
289
  };
218
- //# sourceMappingURL=chunk-OHZX3VFE.js.map
290
+ //# sourceMappingURL=chunk-F2AALK5U.js.map
@@ -0,0 +1 @@
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"]}