@axlsdk/eval 0.1.0
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 +169 -0
- package/dist/chunk-F5UZX6FP.js +198 -0
- package/dist/chunk-F5UZX6FP.js.map +1 -0
- package/dist/cli.cjs +375 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +161 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +334 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +126 -0
- package/dist/index.d.ts +126 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# axl-eval
|
|
2
|
+
|
|
3
|
+
Evaluation framework for Axl agentic workflows. Define datasets, scoring functions, and run evaluations to measure and compare agent performance.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install axl-eval
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
### `dataset(config)`
|
|
14
|
+
|
|
15
|
+
Define evaluation datasets:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { dataset } from 'axl-eval';
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
|
|
21
|
+
// Inline items with schema and annotations
|
|
22
|
+
const ds = dataset({
|
|
23
|
+
name: 'math-basics',
|
|
24
|
+
schema: z.object({ question: z.string() }),
|
|
25
|
+
annotations: z.object({ answer: z.number() }),
|
|
26
|
+
items: [
|
|
27
|
+
{ input: { question: '2+2' }, annotations: { answer: 4 } },
|
|
28
|
+
{ input: { question: '3*5' }, annotations: { answer: 15 } },
|
|
29
|
+
{ input: { question: '10/2' }, annotations: { answer: 5 } },
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Load from file
|
|
34
|
+
const ds = dataset({
|
|
35
|
+
name: 'math-advanced',
|
|
36
|
+
schema: z.object({ question: z.string() }),
|
|
37
|
+
file: './datasets/math.json',
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `scorer(config)`
|
|
42
|
+
|
|
43
|
+
Define deterministic scoring functions. The `score` callback receives `(output, input, annotations?)`:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { scorer } from 'axl-eval';
|
|
47
|
+
|
|
48
|
+
const exactMatch = scorer({
|
|
49
|
+
name: 'exact-match',
|
|
50
|
+
description: 'Checks if the output answer exactly matches the expected answer',
|
|
51
|
+
score: (output, input, annotations) => output.answer === annotations?.answer ? 1 : 0,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const partialMatch = scorer({
|
|
55
|
+
name: 'partial',
|
|
56
|
+
description: 'Checks if the output contains the expected answer',
|
|
57
|
+
score: (output, input, annotations) => {
|
|
58
|
+
const outputStr = String(output).toLowerCase();
|
|
59
|
+
const expected = String(annotations?.answer).toLowerCase();
|
|
60
|
+
return outputStr.includes(expected) ? 1 : 0;
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### `llmScorer(config)`
|
|
66
|
+
|
|
67
|
+
Use an LLM as a judge. The scorer automatically constructs a prompt from the input, output, and annotations, then validates the response against your Zod schema:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { llmScorer } from 'axl-eval';
|
|
71
|
+
import { z } from 'zod';
|
|
72
|
+
|
|
73
|
+
const qualityJudge = llmScorer({
|
|
74
|
+
name: 'quality',
|
|
75
|
+
description: 'Rates output quality on a 0-1 scale',
|
|
76
|
+
model: 'openai:gpt-4o',
|
|
77
|
+
system: 'You are an expert evaluator. Rate the quality of AI outputs.',
|
|
78
|
+
schema: z.object({
|
|
79
|
+
score: z.number().min(0).max(1),
|
|
80
|
+
reasoning: z.string(),
|
|
81
|
+
}),
|
|
82
|
+
temperature: 0.2,
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `runEval(config, executeFn)`
|
|
87
|
+
|
|
88
|
+
Run an evaluation:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { runEval } from 'axl-eval';
|
|
92
|
+
|
|
93
|
+
const results = await runEval(
|
|
94
|
+
{
|
|
95
|
+
dataset: ds,
|
|
96
|
+
scorers: [exactMatch, qualityJudge],
|
|
97
|
+
concurrency: 5,
|
|
98
|
+
},
|
|
99
|
+
async (input) => {
|
|
100
|
+
const output = await runtime.execute('my-workflow', input);
|
|
101
|
+
return { output };
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
console.log(results.summary);
|
|
106
|
+
// { exact-match: { mean: 0.85, min: 0, max: 1 }, quality: { mean: 0.92, ... } }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `evalCompare(baseline, candidate)`
|
|
110
|
+
|
|
111
|
+
Compare two evaluation runs:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { evalCompare } from 'axl-eval';
|
|
115
|
+
|
|
116
|
+
const comparison = evalCompare(baselineResults, candidateResults);
|
|
117
|
+
|
|
118
|
+
console.log(comparison.regressions);
|
|
119
|
+
// [{ item: ..., scorer: 'exact-match', baseline: 1, candidate: 0 }]
|
|
120
|
+
|
|
121
|
+
console.log(comparison.improvements);
|
|
122
|
+
// [{ item: ..., scorer: 'quality', baseline: 0.5, candidate: 0.9 }]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `defineEval(config)`
|
|
126
|
+
|
|
127
|
+
Register an eval for CLI discovery:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { defineEval } from 'axl-eval';
|
|
131
|
+
|
|
132
|
+
export default defineEval({
|
|
133
|
+
name: 'math-eval',
|
|
134
|
+
dataset: ds,
|
|
135
|
+
scorers: [exactMatch],
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## CLI
|
|
140
|
+
|
|
141
|
+
Run evaluations from the command line:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Run an eval file
|
|
145
|
+
npx axl-eval run ./evals/math.ts
|
|
146
|
+
|
|
147
|
+
# Compare two results
|
|
148
|
+
npx axl-eval compare ./results/baseline.json ./results/candidate.json
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Integration with AxlRuntime
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { AxlRuntime } from 'axl';
|
|
155
|
+
|
|
156
|
+
const runtime = new AxlRuntime();
|
|
157
|
+
runtime.register(myWorkflow);
|
|
158
|
+
|
|
159
|
+
const results = await runtime.eval({
|
|
160
|
+
workflow: 'my-workflow',
|
|
161
|
+
dataset: ds,
|
|
162
|
+
scorers: [exactMatch],
|
|
163
|
+
concurrency: 3,
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
[Apache 2.0](../../LICENSE)
|
|
@@ -0,0 +1,198 @@
|
|
|
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 === void 0 || cScore === void 0 || bScore < 0 || cScore < 0) continue;
|
|
35
|
+
const delta = round(cScore - bScore);
|
|
36
|
+
if (delta < -0.1)
|
|
37
|
+
regressions.push({
|
|
38
|
+
input: bItem.input,
|
|
39
|
+
scorer: name,
|
|
40
|
+
baselineScore: bScore,
|
|
41
|
+
candidateScore: cScore,
|
|
42
|
+
delta
|
|
43
|
+
});
|
|
44
|
+
else if (delta > 0.1)
|
|
45
|
+
improvements.push({
|
|
46
|
+
input: bItem.input,
|
|
47
|
+
scorer: name,
|
|
48
|
+
baselineScore: bScore,
|
|
49
|
+
candidateScore: cScore,
|
|
50
|
+
delta
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const parts = [];
|
|
55
|
+
for (const name of baselineScorerNames) {
|
|
56
|
+
const s = scorers[name];
|
|
57
|
+
if (Math.abs(s.delta) > 1e-3) {
|
|
58
|
+
const direction = s.delta > 0 ? "improves" : "regresses";
|
|
59
|
+
const sign = s.delta > 0 ? "+" : "";
|
|
60
|
+
parts.push(
|
|
61
|
+
`'${name}' ${direction} by ${sign}${s.deltaPercent.toFixed(1)}% (${s.baselineMean.toFixed(2)} -> ${s.candidateMean.toFixed(2)})`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const summaryStr = `candidate ${parts.length > 0 ? parts.join(", ") : "no significant changes"} with ${regressions.length} regressions and ${improvements.length} improvements`;
|
|
66
|
+
return {
|
|
67
|
+
baseline: { id: baseline.id, metadata: baseline.metadata },
|
|
68
|
+
candidate: { id: candidate.id, metadata: candidate.metadata },
|
|
69
|
+
scorers,
|
|
70
|
+
regressions,
|
|
71
|
+
improvements,
|
|
72
|
+
summary: summaryStr
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/runner.ts
|
|
77
|
+
import { randomUUID } from "crypto";
|
|
78
|
+
function computeStats(scores) {
|
|
79
|
+
if (scores.length === 0) return { mean: 0, min: 0, max: 0, p50: 0, p95: 0 };
|
|
80
|
+
const sorted = [...scores].sort((a, b) => a - b);
|
|
81
|
+
const mean = sorted.reduce((a, b) => a + b, 0) / sorted.length;
|
|
82
|
+
const min = sorted[0];
|
|
83
|
+
const max = sorted[sorted.length - 1];
|
|
84
|
+
const p50 = sorted[Math.floor(sorted.length * 0.5)];
|
|
85
|
+
const p95 = sorted[Math.floor(sorted.length * 0.95)];
|
|
86
|
+
return { mean: round2(mean), min: round2(min), max: round2(max), p50: round2(p50), p95: round2(p95) };
|
|
87
|
+
}
|
|
88
|
+
function round2(n) {
|
|
89
|
+
return Math.round(n * 1e3) / 1e3;
|
|
90
|
+
}
|
|
91
|
+
function parseCost(cost) {
|
|
92
|
+
const match = cost.match(/^\$?([\d.]+)$/);
|
|
93
|
+
if (!match) throw new Error(`Invalid cost format: "${cost}"`);
|
|
94
|
+
return parseFloat(match[1]);
|
|
95
|
+
}
|
|
96
|
+
async function runEval(config, executeWorkflow, provider) {
|
|
97
|
+
const startTime = Date.now();
|
|
98
|
+
const id = randomUUID();
|
|
99
|
+
const items = await config.dataset.getItems();
|
|
100
|
+
const concurrency = config.concurrency ?? 5;
|
|
101
|
+
const budgetLimit = config.budget ? parseCost(config.budget) : void 0;
|
|
102
|
+
if (provider) {
|
|
103
|
+
for (const scorer of config.scorers) {
|
|
104
|
+
if (scorer.isLlm) {
|
|
105
|
+
scorer._provider = provider;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const evalItems = [];
|
|
110
|
+
let totalCost = 0;
|
|
111
|
+
let budgetExceeded = false;
|
|
112
|
+
async function processItem(item) {
|
|
113
|
+
if (budgetExceeded) {
|
|
114
|
+
evalItems.push({
|
|
115
|
+
input: item.input,
|
|
116
|
+
annotations: item.annotations,
|
|
117
|
+
output: null,
|
|
118
|
+
error: "Budget exceeded",
|
|
119
|
+
scores: {}
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const evalItem = {
|
|
124
|
+
input: item.input,
|
|
125
|
+
annotations: item.annotations,
|
|
126
|
+
output: null,
|
|
127
|
+
scores: {}
|
|
128
|
+
};
|
|
129
|
+
try {
|
|
130
|
+
const result = await executeWorkflow(item.input);
|
|
131
|
+
evalItem.output = result.output;
|
|
132
|
+
if (result.cost != null) {
|
|
133
|
+
totalCost += result.cost;
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
evalItem.error = err instanceof Error ? err.message : String(err);
|
|
137
|
+
evalItems.push(evalItem);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (budgetLimit != null && totalCost > budgetLimit) {
|
|
141
|
+
budgetExceeded = true;
|
|
142
|
+
}
|
|
143
|
+
for (const scorer of config.scorers) {
|
|
144
|
+
try {
|
|
145
|
+
const score = await scorer.score(evalItem.output, item.input, item.annotations);
|
|
146
|
+
if (score < 0 || score > 1) {
|
|
147
|
+
if (!evalItem.errors) evalItem.errors = [];
|
|
148
|
+
evalItem.errors.push(
|
|
149
|
+
`Scorer "${scorer.name}" returned out-of-range score ${score} for input ${JSON.stringify(item.input)}`
|
|
150
|
+
);
|
|
151
|
+
evalItem.scores[scorer.name] = -1;
|
|
152
|
+
} else {
|
|
153
|
+
evalItem.scores[scorer.name] = round2(score);
|
|
154
|
+
}
|
|
155
|
+
} catch (err) {
|
|
156
|
+
if (!evalItem.errors) evalItem.errors = [];
|
|
157
|
+
evalItem.errors.push(
|
|
158
|
+
`Scorer "${scorer.name}" threw: ${err instanceof Error ? err.message : String(err)}`
|
|
159
|
+
);
|
|
160
|
+
evalItem.scores[scorer.name] = -1;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
evalItems.push(evalItem);
|
|
164
|
+
}
|
|
165
|
+
let index = 0;
|
|
166
|
+
async function runNext() {
|
|
167
|
+
while (index < items.length) {
|
|
168
|
+
const currentIndex = index++;
|
|
169
|
+
await processItem(items[currentIndex]);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => runNext());
|
|
173
|
+
await Promise.all(workers);
|
|
174
|
+
const failures = evalItems.filter((i) => i.error).length;
|
|
175
|
+
const scorerNames = config.scorers.map((s) => s.name);
|
|
176
|
+
const scorerStats = {};
|
|
177
|
+
for (const name of scorerNames) {
|
|
178
|
+
const scores = evalItems.filter((i) => !i.error && i.scores[name] !== void 0 && i.scores[name] >= 0).map((i) => i.scores[name]);
|
|
179
|
+
scorerStats[name] = computeStats(scores);
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
id,
|
|
183
|
+
workflow: config.workflow,
|
|
184
|
+
dataset: config.dataset.name,
|
|
185
|
+
metadata: config.metadata ?? {},
|
|
186
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
187
|
+
totalCost,
|
|
188
|
+
duration: Date.now() - startTime,
|
|
189
|
+
items: evalItems,
|
|
190
|
+
summary: { count: items.length, failures, scorers: scorerStats }
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export {
|
|
195
|
+
evalCompare,
|
|
196
|
+
runEval
|
|
197
|
+
};
|
|
198
|
+
//# sourceMappingURL=chunk-F5UZX6FP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/compare.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 === undefined || cScore === undefined || bScore < 0 || cScore < 0) continue;\n const delta = round(cScore - bScore);\n if (delta < -0.1)\n regressions.push({\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 input: bItem.input,\n scorer: name,\n baselineScore: bScore,\n candidateScore: cScore,\n delta,\n });\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 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 regressions,\n improvements,\n summary: summaryStr,\n };\n}\n","import type { EvalConfig, EvalResult, EvalItem, EvalSummary } from './types.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: (input: unknown) => Promise<{ output: unknown; cost?: number }>,\n provider?: any,\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 if (provider) {\n for (const scorer of config.scorers) {\n if (scorer.isLlm) {\n (scorer as any)._provider = provider;\n }\n }\n }\n\n const evalItems: EvalItem[] = [];\n let totalCost = 0;\n let budgetExceeded = false;\n\n async function processItem(item: (typeof items)[0]): Promise<void> {\n if (budgetExceeded) {\n evalItems.push({\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 try {\n const result = await executeWorkflow(item.input);\n evalItem.output = result.output;\n if (result.cost != null) {\n totalCost += result.cost;\n }\n } catch (err) {\n evalItem.error = err instanceof Error ? err.message : String(err);\n evalItems.push(evalItem);\n return;\n }\n\n if (budgetLimit != null && totalCost > budgetLimit) {\n budgetExceeded = true;\n }\n\n for (const scorer of config.scorers) {\n try {\n const score = await scorer.score(evalItem.output, item.input, item.annotations);\n if (score < 0 || score > 1) {\n if (!evalItem.errors) evalItem.errors = [];\n evalItem.errors.push(\n `Scorer \"${scorer.name}\" returned out-of-range score ${score} for input ${JSON.stringify(item.input)}`,\n );\n evalItem.scores[scorer.name] = -1;\n } else {\n evalItem.scores[scorer.name] = round(score);\n }\n } catch (err) {\n if (!evalItem.errors) evalItem.errors = [];\n evalItem.errors.push(\n `Scorer \"${scorer.name}\" threw: ${err instanceof Error ? err.message : String(err)}`,\n );\n evalItem.scores[scorer.name] = -1;\n }\n }\n evalItems.push(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]);\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] !== undefined && i.scores[name] >= 0)\n .map((i) => i.scores[name]);\n scorerStats[name] = computeStats(scores);\n }\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 },\n };\n}\n"],"mappings":"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","names":["round"]}
|