@outputai/evals 0.8.2-next.42a0ddf.0 → 0.8.2-next.4b5c049.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.
@@ -1,17 +1,20 @@
1
1
  import { workflow, executeInParallel, z } from '@outputai/core';
2
- import { getMetadata } from '@outputai/core/sdk_utils';
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
- const meta = getMetadata(def.evaluator);
10
- if (!meta) {
11
- throw new Error('Evaluator passed to evalWorkflow is missing metadata. Ensure it was created with evaluator().');
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: meta.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
+ });
@@ -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 getMetadata(def.evaluator) at module init time
33
- * and the identifiers would otherwise be undefined. Once the rewriter properly strips the
34
- * evaluator → judge → @outputai/llm chain, this file and the "output-workflow-bundle" export
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 { resolveInvocationDir } from '@outputai/core/sdk_utils';
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 });
@@ -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.42a0ddf.0",
3
+ "version": "0.8.2-next.4b5c049.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.42a0ddf.0",
20
- "@outputai/llm": "0.8.2-next.42a0ddf.0"
19
+ "@outputai/core": "0.8.2-next.4b5c049.0",
20
+ "@outputai/llm": "0.8.2-next.4b5c049.0"
21
21
  },
22
22
  "license": "Apache-2.0",
23
23
  "publishConfig": {