@outputai/evals 0.8.2-next.2da7213.0 → 0.8.2-next.ad732b1.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/dist/eval_workflow.js +8 -5
- package/dist/eval_workflow.spec.d.ts +1 -0
- package/dist/eval_workflow.spec.js +92 -0
- package/dist/index_sandbox.js +4 -4
- package/dist/judge.js +5 -5
- package/dist/verify.spec.js +0 -8
- package/package.json +3 -3
package/dist/eval_workflow.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { workflow, executeInParallel, z } from '@outputai/core';
|
|
2
|
-
import {
|
|
2
|
+
import { ComponentMetadata } from '@outputai/core/sdk/helpers';
|
|
3
3
|
import { VERDICT, CRITICALITY, DatasetSchema, EvalOutputSchema } from './schemas.js';
|
|
4
4
|
import { interpretResult } from './interpret.js';
|
|
5
5
|
import { aggregateCaseVerdict } from './aggregate.js';
|
|
6
6
|
export function evalWorkflow({ name, evals, fn, config = {} }) {
|
|
7
7
|
const concurrency = config.concurrency ?? 10;
|
|
8
8
|
const evalDefs = evals.map(def => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
if (!ComponentMetadata.has(def.evaluator)) {
|
|
10
|
+
throw new Error('Evaluator passed to evalWorkflow was not created with evaluator().');
|
|
11
|
+
}
|
|
12
|
+
const name = ComponentMetadata.getName(def.evaluator);
|
|
13
|
+
if (!name) {
|
|
14
|
+
throw new Error('Evaluator component doesn\'t have a name.');
|
|
12
15
|
}
|
|
13
16
|
return {
|
|
14
|
-
name
|
|
17
|
+
name,
|
|
15
18
|
criticality: def.criticality ?? CRITICALITY.REQUIRED,
|
|
16
19
|
interpret: def.interpret
|
|
17
20
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { evalWorkflow } from './eval_workflow.js';
|
|
3
|
+
const workflowMock = vi.hoisted(() => vi.fn(({ fn }) => fn));
|
|
4
|
+
const executeInParallelMock = vi.hoisted(() => vi.fn(async ({ jobs }) => {
|
|
5
|
+
const results = await Promise.all(jobs.map(async (job) => ({ ok: true, result: await job() })));
|
|
6
|
+
return results;
|
|
7
|
+
}));
|
|
8
|
+
const hasMock = vi.hoisted(() => vi.fn());
|
|
9
|
+
const getNameMock = vi.hoisted(() => vi.fn());
|
|
10
|
+
vi.mock('@outputai/core', async (importOriginal) => {
|
|
11
|
+
const actual = await importOriginal();
|
|
12
|
+
return {
|
|
13
|
+
...actual,
|
|
14
|
+
workflow: workflowMock,
|
|
15
|
+
executeInParallel: executeInParallelMock
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
vi.mock('@outputai/core/sdk/helpers', () => ({
|
|
19
|
+
ComponentMetadata: {
|
|
20
|
+
has: hasMock,
|
|
21
|
+
getName: getNameMock
|
|
22
|
+
}
|
|
23
|
+
}));
|
|
24
|
+
describe('evalWorkflow', () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.clearAllMocks();
|
|
27
|
+
hasMock.mockReturnValue(true);
|
|
28
|
+
getNameMock.mockReturnValue('quality_eval');
|
|
29
|
+
});
|
|
30
|
+
it('resolves evaluator names with ComponentMetadata.getName', async () => {
|
|
31
|
+
const evaluator = async () => ({ value: true, confidence: 1, feedback: [], dimensions: [] });
|
|
32
|
+
const workflowFn = evalWorkflow({
|
|
33
|
+
name: 'quality',
|
|
34
|
+
evals: [{
|
|
35
|
+
evaluator,
|
|
36
|
+
interpret: { type: 'boolean' }
|
|
37
|
+
}]
|
|
38
|
+
});
|
|
39
|
+
const invokeEvaluator = vi.fn().mockResolvedValue({ value: true, confidence: 1, feedback: [], dimensions: [] });
|
|
40
|
+
const output = await workflowFn.call({ invokeEvaluator }, {
|
|
41
|
+
datasets: [{
|
|
42
|
+
name: 'case_1',
|
|
43
|
+
input: { prompt: 'hello' },
|
|
44
|
+
last_output: { output: { answer: 'hello' } },
|
|
45
|
+
ground_truth: {
|
|
46
|
+
shared: 'value',
|
|
47
|
+
evals: {
|
|
48
|
+
quality_eval: { expected: 'hello' }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}]
|
|
52
|
+
});
|
|
53
|
+
expect(hasMock).toHaveBeenCalledWith(evaluator);
|
|
54
|
+
expect(getNameMock).toHaveBeenCalledWith(evaluator);
|
|
55
|
+
expect(invokeEvaluator).toHaveBeenCalledWith('quality_eval', {
|
|
56
|
+
input: { prompt: 'hello' },
|
|
57
|
+
output: { answer: 'hello' },
|
|
58
|
+
ground_truth: { shared: 'value', expected: 'hello' }
|
|
59
|
+
});
|
|
60
|
+
expect(output.cases[0].evaluators[0]).toMatchObject({
|
|
61
|
+
name: 'quality_eval',
|
|
62
|
+
verdict: 'pass',
|
|
63
|
+
criticality: 'required'
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
it('rejects non-component evaluators', () => {
|
|
67
|
+
const evaluator = async () => ({ value: true, confidence: 1 });
|
|
68
|
+
hasMock.mockReturnValue(false);
|
|
69
|
+
expect(() => evalWorkflow({
|
|
70
|
+
name: 'quality',
|
|
71
|
+
evals: [{
|
|
72
|
+
evaluator,
|
|
73
|
+
interpret: { type: 'boolean' }
|
|
74
|
+
}]
|
|
75
|
+
})).toThrow('Evaluator passed to evalWorkflow was not created with evaluator().');
|
|
76
|
+
expect(hasMock).toHaveBeenCalledWith(evaluator);
|
|
77
|
+
expect(getNameMock).not.toHaveBeenCalled();
|
|
78
|
+
});
|
|
79
|
+
it('rejects component evaluators without a component name', () => {
|
|
80
|
+
const evaluator = async () => ({ value: true, confidence: 1 });
|
|
81
|
+
getNameMock.mockReturnValue(undefined);
|
|
82
|
+
expect(() => evalWorkflow({
|
|
83
|
+
name: 'quality',
|
|
84
|
+
evals: [{
|
|
85
|
+
evaluator,
|
|
86
|
+
interpret: { type: 'boolean' }
|
|
87
|
+
}]
|
|
88
|
+
})).toThrow('Evaluator component doesn\'t have a name.');
|
|
89
|
+
expect(hasMock).toHaveBeenCalledWith(evaluator);
|
|
90
|
+
expect(getNameMock).toHaveBeenCalledWith(evaluator);
|
|
91
|
+
});
|
|
92
|
+
});
|
package/dist/index_sandbox.js
CHANGED
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
* is fixed to generate output from the modified AST even when rewriteFnBodies() returns
|
|
30
30
|
* false — i.e. when collectTargetImports() stripped imports but no fn bodies needed
|
|
31
31
|
* rewriting. This also requires injecting metadata stubs for the stripped evaluator
|
|
32
|
-
* references, since evalWorkflow() calls
|
|
33
|
-
* and the identifiers would otherwise be undefined. Once the rewriter
|
|
34
|
-
* evaluator → judge → @outputai/llm chain, this file and the
|
|
35
|
-
* condition in package.json can be deleted.
|
|
32
|
+
* references, since evalWorkflow() calls ComponentMetadata.getName(def.evaluator) at
|
|
33
|
+
* module init time and the identifiers would otherwise be undefined. Once the rewriter
|
|
34
|
+
* properly strips the evaluator → judge → @outputai/llm chain, this file and the
|
|
35
|
+
* "output-workflow-bundle" export condition in package.json can be deleted.
|
|
36
36
|
*/
|
|
37
37
|
export { evalWorkflow } from './eval_workflow.js';
|
|
38
38
|
export { Verdict } from './verdict.js';
|
package/dist/judge.js
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
import { EvaluationVerdictResult, EvaluationNumberResult, EvaluationBooleanResult, EvaluationStringResult, z } from '@outputai/core';
|
|
2
|
-
import {
|
|
2
|
+
import { Path } from '@outputai/core/sdk/helpers';
|
|
3
3
|
import { generateText, Output } from '@outputai/llm';
|
|
4
4
|
const verdictSchema = z.object({ verdict: z.enum(['pass', 'partial', 'fail']), reasoning: z.string() });
|
|
5
5
|
const scoreSchema = z.object({ score: z.number(), reasoning: z.string() });
|
|
6
6
|
const booleanSchema = z.object({ result: z.boolean(), reasoning: z.string() });
|
|
7
7
|
const labelSchema = z.object({ label: z.string(), reasoning: z.string() });
|
|
8
8
|
export async function judgeVerdict({ prompt, variables, schema = verdictSchema }) {
|
|
9
|
-
const promptDir = resolveInvocationDir();
|
|
9
|
+
const promptDir = Path.resolveInvocationDir();
|
|
10
10
|
const response = await generateText({ prompt, variables, promptDir, output: Output.object({ schema }) });
|
|
11
11
|
const result = response.output;
|
|
12
12
|
return new EvaluationVerdictResult({ value: result.verdict, confidence: 0.9, reasoning: result.reasoning });
|
|
13
13
|
}
|
|
14
14
|
export async function judgeScore({ prompt, variables, schema = scoreSchema }) {
|
|
15
|
-
const promptDir = resolveInvocationDir();
|
|
15
|
+
const promptDir = Path.resolveInvocationDir();
|
|
16
16
|
const response = await generateText({ prompt, variables, promptDir, output: Output.object({ schema }) });
|
|
17
17
|
const result = response.output;
|
|
18
18
|
return new EvaluationNumberResult({ value: result.score, confidence: 0.9, reasoning: result.reasoning });
|
|
19
19
|
}
|
|
20
20
|
export async function judgeBoolean({ prompt, variables, schema = booleanSchema }) {
|
|
21
|
-
const promptDir = resolveInvocationDir();
|
|
21
|
+
const promptDir = Path.resolveInvocationDir();
|
|
22
22
|
const response = await generateText({ prompt, variables, promptDir, output: Output.object({ schema }) });
|
|
23
23
|
const result = response.output;
|
|
24
24
|
return new EvaluationBooleanResult({ value: result.result, confidence: 0.9, reasoning: result.reasoning });
|
|
25
25
|
}
|
|
26
26
|
export async function judgeLabel({ prompt, variables, schema = labelSchema }) {
|
|
27
|
-
const promptDir = resolveInvocationDir();
|
|
27
|
+
const promptDir = Path.resolveInvocationDir();
|
|
28
28
|
const response = await generateText({ prompt, variables, promptDir, output: Output.object({ schema }) });
|
|
29
29
|
const result = response.output;
|
|
30
30
|
return new EvaluationStringResult({ value: result.label, confidence: 0.9, reasoning: result.reasoning });
|
package/dist/verify.spec.js
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { verify } from './verify.js';
|
|
3
|
-
import { getMetadata } from '@outputai/core/sdk_utils';
|
|
4
3
|
import { EvaluationBooleanResult, z } from '@outputai/core';
|
|
5
4
|
import { Verdict } from './verdict.js';
|
|
6
5
|
describe('verify', () => {
|
|
7
|
-
it('returns a function with metadata matching the given name', () => {
|
|
8
|
-
const ev = verify({ name: 'my_eval' }, () => Verdict.isTrue(true));
|
|
9
|
-
const meta = getMetadata(ev);
|
|
10
|
-
expect(meta).not.toBeNull();
|
|
11
|
-
expect(meta.name).toBe('my_eval');
|
|
12
|
-
expect(meta.description).toBe('my_eval');
|
|
13
|
-
});
|
|
14
6
|
it('passes input and output to the user function', async () => {
|
|
15
7
|
const captured = [];
|
|
16
8
|
const ev = verify({ name: 'capture_test' }, ctx => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/evals",
|
|
3
|
-
"version": "0.8.2-next.
|
|
3
|
+
"version": "0.8.2-next.ad732b1.0",
|
|
4
4
|
"description": "Offline evaluation framework for Output.ai workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"./dist"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@outputai/core": "0.8.2-next.
|
|
20
|
-
"@outputai/llm": "0.8.2-next.
|
|
19
|
+
"@outputai/core": "0.8.2-next.ad732b1.0",
|
|
20
|
+
"@outputai/llm": "0.8.2-next.ad732b1.0"
|
|
21
21
|
},
|
|
22
22
|
"license": "Apache-2.0",
|
|
23
23
|
"publishConfig": {
|