@outputai/evals 0.1.4 → 0.1.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/dist/aggregate.d.ts +2 -0
- package/dist/aggregate.js +11 -0
- package/dist/aggregate.spec.d.ts +1 -0
- package/dist/aggregate.spec.js +48 -0
- package/dist/eval_workflow.d.ts +20 -0
- package/dist/eval_workflow.js +88 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +9 -0
- package/dist/index_sandbox.d.ts +16 -0
- package/dist/index_sandbox.js +51 -0
- package/dist/interpret.d.ts +3 -0
- package/dist/interpret.js +40 -0
- package/dist/interpret.spec.d.ts +1 -0
- package/dist/interpret.spec.js +86 -0
- package/dist/judge.d.ts +10 -0
- package/dist/judge.js +31 -0
- package/dist/naming.d.ts +3 -0
- package/dist/naming.js +4 -0
- package/dist/naming.spec.d.ts +1 -0
- package/dist/naming.spec.js +30 -0
- package/dist/render.d.ts +3 -0
- package/dist/render.js +75 -0
- package/dist/render.spec.d.ts +1 -0
- package/dist/render.spec.js +171 -0
- package/dist/schemas.d.ts +95 -0
- package/dist/schemas.js +60 -0
- package/dist/verdict.d.ts +27 -0
- package/dist/verdict.js +109 -0
- package/dist/verdict.spec.d.ts +1 -0
- package/dist/verdict.spec.js +181 -0
- package/dist/verify.d.ts +41 -0
- package/dist/verify.js +18 -0
- package/dist/verify.spec.d.ts +1 -0
- package/dist/verify.spec.js +70 -0
- package/package.json +3 -3
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { renderEvalOutput, computeExitCode } from './render.js';
|
|
3
|
+
const makeOutput = (cases, overrides) => {
|
|
4
|
+
const passed = cases.filter(c => c.verdict === 'pass').length;
|
|
5
|
+
const partial = cases.filter(c => c.verdict === 'partial').length;
|
|
6
|
+
const failed = cases.filter(c => c.verdict === 'fail').length;
|
|
7
|
+
const total = cases.length;
|
|
8
|
+
return {
|
|
9
|
+
cases,
|
|
10
|
+
summary: {
|
|
11
|
+
total,
|
|
12
|
+
passed,
|
|
13
|
+
partial,
|
|
14
|
+
failed,
|
|
15
|
+
acceptableRate: total > 0 ? (passed + partial) / total : 0,
|
|
16
|
+
...overrides
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
describe('computeExitCode', () => {
|
|
21
|
+
it('returns 0 when all cases pass', () => {
|
|
22
|
+
const output = makeOutput([
|
|
23
|
+
{ datasetName: 'a', verdict: 'pass', evaluators: [] }
|
|
24
|
+
]);
|
|
25
|
+
expect(computeExitCode(output)).toBe(0);
|
|
26
|
+
});
|
|
27
|
+
it('returns 0 when cases are partial but none fail', () => {
|
|
28
|
+
const output = makeOutput([
|
|
29
|
+
{ datasetName: 'a', verdict: 'partial', evaluators: [] }
|
|
30
|
+
]);
|
|
31
|
+
expect(computeExitCode(output)).toBe(0);
|
|
32
|
+
});
|
|
33
|
+
it('returns 1 when any case fails', () => {
|
|
34
|
+
const output = makeOutput([
|
|
35
|
+
{ datasetName: 'a', verdict: 'pass', evaluators: [] },
|
|
36
|
+
{ datasetName: 'b', verdict: 'fail', evaluators: [] }
|
|
37
|
+
]);
|
|
38
|
+
expect(computeExitCode(output)).toBe(1);
|
|
39
|
+
});
|
|
40
|
+
it('returns 0 for empty cases', () => {
|
|
41
|
+
const output = makeOutput([]);
|
|
42
|
+
expect(computeExitCode(output)).toBe(0);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('renderEvalOutput', () => {
|
|
46
|
+
it('includes eval name when provided', () => {
|
|
47
|
+
const output = makeOutput([
|
|
48
|
+
{ datasetName: 'test', verdict: 'pass', evaluators: [] }
|
|
49
|
+
]);
|
|
50
|
+
const rendered = renderEvalOutput(output, 'my_eval');
|
|
51
|
+
expect(rendered).toContain('my_eval');
|
|
52
|
+
});
|
|
53
|
+
it('includes dataset names', () => {
|
|
54
|
+
const output = makeOutput([
|
|
55
|
+
{ datasetName: 'basic_input', verdict: 'pass', evaluators: [] },
|
|
56
|
+
{ datasetName: 'edge_case', verdict: 'fail', evaluators: [] }
|
|
57
|
+
]);
|
|
58
|
+
const rendered = renderEvalOutput(output);
|
|
59
|
+
expect(rendered).toContain('basic_input');
|
|
60
|
+
expect(rendered).toContain('edge_case');
|
|
61
|
+
});
|
|
62
|
+
it('includes verdict labels', () => {
|
|
63
|
+
const output = makeOutput([
|
|
64
|
+
{ datasetName: 'a', verdict: 'pass', evaluators: [] },
|
|
65
|
+
{ datasetName: 'b', verdict: 'fail', evaluators: [] }
|
|
66
|
+
]);
|
|
67
|
+
const rendered = renderEvalOutput(output);
|
|
68
|
+
expect(rendered).toContain('PASS');
|
|
69
|
+
expect(rendered).toContain('FAIL');
|
|
70
|
+
});
|
|
71
|
+
it('shows reasoning for failed evaluators', () => {
|
|
72
|
+
const output = makeOutput([
|
|
73
|
+
{
|
|
74
|
+
datasetName: 'test',
|
|
75
|
+
verdict: 'fail',
|
|
76
|
+
evaluators: [{
|
|
77
|
+
name: 'check',
|
|
78
|
+
verdict: 'fail',
|
|
79
|
+
criticality: 'required',
|
|
80
|
+
reasoning: 'Expected 15, got null'
|
|
81
|
+
}]
|
|
82
|
+
}
|
|
83
|
+
]);
|
|
84
|
+
const rendered = renderEvalOutput(output);
|
|
85
|
+
expect(rendered).toContain('Expected 15, got null');
|
|
86
|
+
});
|
|
87
|
+
it('hides reasoning for passing evaluators', () => {
|
|
88
|
+
const output = makeOutput([
|
|
89
|
+
{
|
|
90
|
+
datasetName: 'test',
|
|
91
|
+
verdict: 'pass',
|
|
92
|
+
evaluators: [{
|
|
93
|
+
name: 'check',
|
|
94
|
+
verdict: 'pass',
|
|
95
|
+
criticality: 'required',
|
|
96
|
+
reasoning: 'All good'
|
|
97
|
+
}]
|
|
98
|
+
}
|
|
99
|
+
]);
|
|
100
|
+
const rendered = renderEvalOutput(output);
|
|
101
|
+
expect(rendered).not.toContain('All good');
|
|
102
|
+
});
|
|
103
|
+
it('shows summary with acceptable rate', () => {
|
|
104
|
+
const output = makeOutput([
|
|
105
|
+
{ datasetName: 'a', verdict: 'pass', evaluators: [] },
|
|
106
|
+
{ datasetName: 'b', verdict: 'fail', evaluators: [] }
|
|
107
|
+
]);
|
|
108
|
+
const rendered = renderEvalOutput(output);
|
|
109
|
+
expect(rendered).toContain('1 passed');
|
|
110
|
+
expect(rendered).toContain('1 failed');
|
|
111
|
+
expect(rendered).toContain('50% acceptable');
|
|
112
|
+
});
|
|
113
|
+
it('includes PARTIAL verdict label', () => {
|
|
114
|
+
const output = makeOutput([
|
|
115
|
+
{ datasetName: 'a', verdict: 'partial', evaluators: [] }
|
|
116
|
+
]);
|
|
117
|
+
const rendered = renderEvalOutput(output);
|
|
118
|
+
expect(rendered).toContain('PARTIAL');
|
|
119
|
+
});
|
|
120
|
+
it('shows reasoning for partial evaluators', () => {
|
|
121
|
+
const output = makeOutput([
|
|
122
|
+
{
|
|
123
|
+
datasetName: 'test',
|
|
124
|
+
verdict: 'partial',
|
|
125
|
+
evaluators: [{
|
|
126
|
+
name: 'check',
|
|
127
|
+
verdict: 'partial',
|
|
128
|
+
criticality: 'required',
|
|
129
|
+
reasoning: 'Close but not quite'
|
|
130
|
+
}]
|
|
131
|
+
}
|
|
132
|
+
]);
|
|
133
|
+
const rendered = renderEvalOutput(output);
|
|
134
|
+
expect(rendered).toContain('Close but not quite');
|
|
135
|
+
});
|
|
136
|
+
it('renders feedback issues', () => {
|
|
137
|
+
const output = makeOutput([
|
|
138
|
+
{
|
|
139
|
+
datasetName: 'test',
|
|
140
|
+
verdict: 'fail',
|
|
141
|
+
evaluators: [{
|
|
142
|
+
name: 'check',
|
|
143
|
+
verdict: 'fail',
|
|
144
|
+
criticality: 'required',
|
|
145
|
+
feedback: [
|
|
146
|
+
{ issue: 'Missing required field' },
|
|
147
|
+
{ issue: 'Invalid format' }
|
|
148
|
+
]
|
|
149
|
+
}]
|
|
150
|
+
}
|
|
151
|
+
]);
|
|
152
|
+
const rendered = renderEvalOutput(output);
|
|
153
|
+
expect(rendered).toContain('Missing required field');
|
|
154
|
+
expect(rendered).toContain('Invalid format');
|
|
155
|
+
});
|
|
156
|
+
it('marks informational evaluators with (info) prefix', () => {
|
|
157
|
+
const output = makeOutput([
|
|
158
|
+
{
|
|
159
|
+
datasetName: 'test',
|
|
160
|
+
verdict: 'pass',
|
|
161
|
+
evaluators: [{
|
|
162
|
+
name: 'info_check',
|
|
163
|
+
verdict: 'fail',
|
|
164
|
+
criticality: 'informational'
|
|
165
|
+
}]
|
|
166
|
+
}
|
|
167
|
+
]);
|
|
168
|
+
const rendered = renderEvalOutput(output);
|
|
169
|
+
expect(rendered).toContain('(info)');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from '@outputai/core';
|
|
2
|
+
export declare const VERDICT: {
|
|
3
|
+
readonly PASS: "pass";
|
|
4
|
+
readonly PARTIAL: "partial";
|
|
5
|
+
readonly FAIL: "fail";
|
|
6
|
+
};
|
|
7
|
+
export declare const CRITICALITY: {
|
|
8
|
+
readonly REQUIRED: "required";
|
|
9
|
+
readonly INFORMATIONAL: "informational";
|
|
10
|
+
};
|
|
11
|
+
export declare const VerdictSchema: z.ZodEnum<{
|
|
12
|
+
pass: "pass";
|
|
13
|
+
partial: "partial";
|
|
14
|
+
fail: "fail";
|
|
15
|
+
}>;
|
|
16
|
+
export type Verdict = z.infer<typeof VerdictSchema>;
|
|
17
|
+
export declare const CriticalitySchema: z.ZodEnum<{
|
|
18
|
+
required: "required";
|
|
19
|
+
informational: "informational";
|
|
20
|
+
}>;
|
|
21
|
+
export type Criticality = z.infer<typeof CriticalitySchema>;
|
|
22
|
+
export type InterpretConfig = {
|
|
23
|
+
type: 'verdict';
|
|
24
|
+
} | {
|
|
25
|
+
type: 'boolean';
|
|
26
|
+
} | {
|
|
27
|
+
type: 'number';
|
|
28
|
+
pass: number;
|
|
29
|
+
partial?: number;
|
|
30
|
+
} | {
|
|
31
|
+
type: 'string';
|
|
32
|
+
pass: string[];
|
|
33
|
+
partial?: string[];
|
|
34
|
+
};
|
|
35
|
+
export declare const InterpretConfigSchema: z.ZodType<InterpretConfig>;
|
|
36
|
+
export interface EvaluatorResult {
|
|
37
|
+
name: string;
|
|
38
|
+
verdict: Verdict;
|
|
39
|
+
criticality: Criticality;
|
|
40
|
+
confidence?: number;
|
|
41
|
+
reasoning?: string;
|
|
42
|
+
feedback?: unknown[];
|
|
43
|
+
}
|
|
44
|
+
export declare const EvaluatorResultSchema: z.ZodType<EvaluatorResult>;
|
|
45
|
+
export interface EvalCase {
|
|
46
|
+
datasetName: string;
|
|
47
|
+
verdict: Verdict;
|
|
48
|
+
evaluators: EvaluatorResult[];
|
|
49
|
+
}
|
|
50
|
+
export declare const EvalCaseSchema: z.ZodType<EvalCase>;
|
|
51
|
+
export interface EvalSummary {
|
|
52
|
+
total: number;
|
|
53
|
+
passed: number;
|
|
54
|
+
partial: number;
|
|
55
|
+
failed: number;
|
|
56
|
+
acceptableRate: number;
|
|
57
|
+
}
|
|
58
|
+
export interface EvalOutput {
|
|
59
|
+
cases: EvalCase[];
|
|
60
|
+
summary: EvalSummary;
|
|
61
|
+
}
|
|
62
|
+
export declare const EvalOutputSchema: z.ZodType<EvalOutput>;
|
|
63
|
+
export interface GroundTruth {
|
|
64
|
+
evals?: Record<string, Record<string, unknown>>;
|
|
65
|
+
[key: string]: unknown;
|
|
66
|
+
}
|
|
67
|
+
export interface LastOutput {
|
|
68
|
+
output: unknown;
|
|
69
|
+
executionTimeMs?: number;
|
|
70
|
+
date: string;
|
|
71
|
+
}
|
|
72
|
+
export interface LastEval {
|
|
73
|
+
output: EvalCase;
|
|
74
|
+
executionTimeMs?: number;
|
|
75
|
+
date: string;
|
|
76
|
+
}
|
|
77
|
+
export declare const LastOutputSchema: z.ZodType<LastOutput>;
|
|
78
|
+
export declare const LastEvalSchema: z.ZodType<LastEval>;
|
|
79
|
+
export interface Dataset {
|
|
80
|
+
name: string;
|
|
81
|
+
input: Record<string, unknown>;
|
|
82
|
+
ground_truth?: GroundTruth;
|
|
83
|
+
last_output?: LastOutput;
|
|
84
|
+
last_eval?: LastEval;
|
|
85
|
+
[key: string]: unknown;
|
|
86
|
+
}
|
|
87
|
+
export declare const DatasetSchema: z.ZodObject<{
|
|
88
|
+
name: z.ZodString;
|
|
89
|
+
input: z.ZodRecord<z.ZodString, z.ZodAny>;
|
|
90
|
+
ground_truth: z.ZodOptional<z.ZodObject<{
|
|
91
|
+
evals: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodAny>>>;
|
|
92
|
+
}, z.core.$loose>>;
|
|
93
|
+
last_output: z.ZodOptional<z.ZodType<LastOutput, unknown, z.core.$ZodTypeInternals<LastOutput, unknown>>>;
|
|
94
|
+
last_eval: z.ZodOptional<z.ZodType<LastEval, unknown, z.core.$ZodTypeInternals<LastEval, unknown>>>;
|
|
95
|
+
}, z.core.$loose>;
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from '@outputai/core';
|
|
2
|
+
export const VERDICT = {
|
|
3
|
+
PASS: 'pass',
|
|
4
|
+
PARTIAL: 'partial',
|
|
5
|
+
FAIL: 'fail'
|
|
6
|
+
};
|
|
7
|
+
export const CRITICALITY = {
|
|
8
|
+
REQUIRED: 'required',
|
|
9
|
+
INFORMATIONAL: 'informational'
|
|
10
|
+
};
|
|
11
|
+
export const VerdictSchema = z.enum([VERDICT.PASS, VERDICT.PARTIAL, VERDICT.FAIL]);
|
|
12
|
+
export const CriticalitySchema = z.enum([CRITICALITY.REQUIRED, CRITICALITY.INFORMATIONAL]);
|
|
13
|
+
export const InterpretConfigSchema = z.discriminatedUnion('type', [
|
|
14
|
+
z.object({ type: z.literal('verdict') }),
|
|
15
|
+
z.object({ type: z.literal('boolean') }),
|
|
16
|
+
z.object({ type: z.literal('number'), pass: z.number(), partial: z.number().optional() }),
|
|
17
|
+
z.object({ type: z.literal('string'), pass: z.array(z.string()), partial: z.array(z.string()).optional() })
|
|
18
|
+
]);
|
|
19
|
+
export const EvaluatorResultSchema = z.object({
|
|
20
|
+
name: z.string(),
|
|
21
|
+
verdict: VerdictSchema,
|
|
22
|
+
criticality: CriticalitySchema,
|
|
23
|
+
confidence: z.number().optional(),
|
|
24
|
+
reasoning: z.string().optional(),
|
|
25
|
+
feedback: z.array(z.any()).optional()
|
|
26
|
+
});
|
|
27
|
+
export const EvalCaseSchema = z.object({
|
|
28
|
+
datasetName: z.string(),
|
|
29
|
+
verdict: VerdictSchema,
|
|
30
|
+
evaluators: z.array(EvaluatorResultSchema)
|
|
31
|
+
});
|
|
32
|
+
export const EvalOutputSchema = z.object({
|
|
33
|
+
cases: z.array(EvalCaseSchema),
|
|
34
|
+
summary: z.object({
|
|
35
|
+
total: z.number(),
|
|
36
|
+
passed: z.number(),
|
|
37
|
+
partial: z.number(),
|
|
38
|
+
failed: z.number(),
|
|
39
|
+
acceptableRate: z.number()
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
export const LastOutputSchema = z.object({
|
|
43
|
+
output: z.any(),
|
|
44
|
+
executionTimeMs: z.number().optional(),
|
|
45
|
+
date: z.string()
|
|
46
|
+
});
|
|
47
|
+
export const LastEvalSchema = z.object({
|
|
48
|
+
output: EvalCaseSchema,
|
|
49
|
+
executionTimeMs: z.number().optional(),
|
|
50
|
+
date: z.string()
|
|
51
|
+
});
|
|
52
|
+
export const DatasetSchema = z.object({
|
|
53
|
+
name: z.string(),
|
|
54
|
+
input: z.record(z.string(), z.any()),
|
|
55
|
+
ground_truth: z.object({
|
|
56
|
+
evals: z.record(z.string(), z.record(z.string(), z.any())).optional()
|
|
57
|
+
}).passthrough().optional(),
|
|
58
|
+
last_output: LastOutputSchema.optional(),
|
|
59
|
+
last_eval: LastEvalSchema.optional()
|
|
60
|
+
}).passthrough();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { EvaluationVerdictResult, EvaluationBooleanResult, EvaluationNumberResult, EvaluationStringResult, EvaluationFeedback } from '@outputai/core';
|
|
2
|
+
type FeedbackArg = ConstructorParameters<typeof EvaluationFeedback>[0];
|
|
3
|
+
export declare const Verdict: {
|
|
4
|
+
pass(reasoning?: string): EvaluationVerdictResult;
|
|
5
|
+
partial(confidence: number, reasoning?: string, feedback?: FeedbackArg[]): EvaluationVerdictResult;
|
|
6
|
+
fail(reasoning: string, feedback?: FeedbackArg[]): EvaluationVerdictResult;
|
|
7
|
+
equals(actual: unknown, expected: unknown): EvaluationBooleanResult;
|
|
8
|
+
closeTo(actual: number, expected: number, tolerance: number): EvaluationBooleanResult;
|
|
9
|
+
gt(actual: number, threshold: number): EvaluationBooleanResult;
|
|
10
|
+
gte(actual: number, threshold: number): EvaluationBooleanResult;
|
|
11
|
+
lt(actual: number, threshold: number): EvaluationBooleanResult;
|
|
12
|
+
lte(actual: number, threshold: number): EvaluationBooleanResult;
|
|
13
|
+
inRange(actual: number, min: number, max: number): EvaluationBooleanResult;
|
|
14
|
+
contains(haystack: string, needle: string): EvaluationBooleanResult;
|
|
15
|
+
matches(value: string, pattern: RegExp): EvaluationBooleanResult;
|
|
16
|
+
includesAll(actual: unknown[], expected: unknown[]): EvaluationBooleanResult;
|
|
17
|
+
includesAny(actual: unknown[], expected: unknown[]): EvaluationBooleanResult;
|
|
18
|
+
isTrue(value: boolean): EvaluationBooleanResult;
|
|
19
|
+
isFalse(value: boolean): EvaluationBooleanResult;
|
|
20
|
+
fromJudge({ verdict, reasoning }: {
|
|
21
|
+
verdict: "pass" | "partial" | "fail";
|
|
22
|
+
reasoning: string;
|
|
23
|
+
}): EvaluationVerdictResult;
|
|
24
|
+
score(value: number, reasoning?: string): EvaluationNumberResult;
|
|
25
|
+
label(value: string, reasoning?: string): EvaluationStringResult;
|
|
26
|
+
};
|
|
27
|
+
export {};
|
package/dist/verdict.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { EvaluationVerdictResult, EvaluationBooleanResult, EvaluationNumberResult, EvaluationStringResult, EvaluationFeedback } from '@outputai/core';
|
|
2
|
+
const boolResult = (value, reasoning) => new EvaluationBooleanResult({ value, confidence: 1.0, reasoning });
|
|
3
|
+
export const Verdict = {
|
|
4
|
+
// --- Original helpers ---
|
|
5
|
+
pass(reasoning) {
|
|
6
|
+
return new EvaluationVerdictResult({ value: 'pass', confidence: 1.0, reasoning });
|
|
7
|
+
},
|
|
8
|
+
partial(confidence, reasoning, feedback) {
|
|
9
|
+
return new EvaluationVerdictResult({
|
|
10
|
+
value: 'partial',
|
|
11
|
+
confidence,
|
|
12
|
+
reasoning,
|
|
13
|
+
feedback: feedback ? feedback.map(f => new EvaluationFeedback(f)) : undefined
|
|
14
|
+
});
|
|
15
|
+
},
|
|
16
|
+
fail(reasoning, feedback) {
|
|
17
|
+
return new EvaluationVerdictResult({
|
|
18
|
+
value: 'fail',
|
|
19
|
+
confidence: 0.0,
|
|
20
|
+
reasoning,
|
|
21
|
+
feedback: feedback ? feedback.map(f => new EvaluationFeedback(f)) : undefined
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
// --- Deterministic assertions (confidence 1.0 for both pass and fail) ---
|
|
25
|
+
equals(actual, expected) {
|
|
26
|
+
const passed = actual === expected;
|
|
27
|
+
return boolResult(passed, passed ?
|
|
28
|
+
`Value equals expected: ${JSON.stringify(expected)}` :
|
|
29
|
+
`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
|
30
|
+
},
|
|
31
|
+
closeTo(actual, expected, tolerance) {
|
|
32
|
+
const passed = Math.abs(actual - expected) <= tolerance;
|
|
33
|
+
return boolResult(passed, passed ?
|
|
34
|
+
`${actual} is within ${tolerance} of ${expected}` :
|
|
35
|
+
`${actual} is not within ${tolerance} of ${expected} (diff: ${Math.abs(actual - expected)})`);
|
|
36
|
+
},
|
|
37
|
+
gt(actual, threshold) {
|
|
38
|
+
const passed = actual > threshold;
|
|
39
|
+
return boolResult(passed, passed ?
|
|
40
|
+
`${actual} > ${threshold}` :
|
|
41
|
+
`${actual} is not greater than ${threshold}`);
|
|
42
|
+
},
|
|
43
|
+
gte(actual, threshold) {
|
|
44
|
+
const passed = actual >= threshold;
|
|
45
|
+
return boolResult(passed, passed ?
|
|
46
|
+
`${actual} >= ${threshold}` :
|
|
47
|
+
`${actual} is not greater than or equal to ${threshold}`);
|
|
48
|
+
},
|
|
49
|
+
lt(actual, threshold) {
|
|
50
|
+
const passed = actual < threshold;
|
|
51
|
+
return boolResult(passed, passed ?
|
|
52
|
+
`${actual} < ${threshold}` :
|
|
53
|
+
`${actual} is not less than ${threshold}`);
|
|
54
|
+
},
|
|
55
|
+
lte(actual, threshold) {
|
|
56
|
+
const passed = actual <= threshold;
|
|
57
|
+
return boolResult(passed, passed ?
|
|
58
|
+
`${actual} <= ${threshold}` :
|
|
59
|
+
`${actual} is not less than or equal to ${threshold}`);
|
|
60
|
+
},
|
|
61
|
+
inRange(actual, min, max) {
|
|
62
|
+
const passed = actual >= min && actual <= max;
|
|
63
|
+
return boolResult(passed, passed ?
|
|
64
|
+
`${actual} is in range [${min}, ${max}]` :
|
|
65
|
+
`${actual} is not in range [${min}, ${max}]`);
|
|
66
|
+
},
|
|
67
|
+
contains(haystack, needle) {
|
|
68
|
+
const passed = haystack.includes(needle);
|
|
69
|
+
return boolResult(passed, passed ?
|
|
70
|
+
`String contains "${needle}"` :
|
|
71
|
+
`String does not contain "${needle}"`);
|
|
72
|
+
},
|
|
73
|
+
matches(value, pattern) {
|
|
74
|
+
const passed = pattern.test(value);
|
|
75
|
+
return boolResult(passed, passed ?
|
|
76
|
+
`Value matches ${pattern}` :
|
|
77
|
+
`Value does not match ${pattern}`);
|
|
78
|
+
},
|
|
79
|
+
includesAll(actual, expected) {
|
|
80
|
+
const missing = expected.filter(e => !actual.includes(e));
|
|
81
|
+
const passed = missing.length === 0;
|
|
82
|
+
return boolResult(passed, passed ?
|
|
83
|
+
'Array includes all expected values' :
|
|
84
|
+
`Array is missing: ${JSON.stringify(missing)}`);
|
|
85
|
+
},
|
|
86
|
+
includesAny(actual, expected) {
|
|
87
|
+
const found = expected.filter(e => actual.includes(e));
|
|
88
|
+
const passed = found.length > 0;
|
|
89
|
+
return boolResult(passed, passed ?
|
|
90
|
+
`Array includes: ${JSON.stringify(found)}` :
|
|
91
|
+
`Array includes none of: ${JSON.stringify(expected)}`);
|
|
92
|
+
},
|
|
93
|
+
isTrue(value) {
|
|
94
|
+
return boolResult(value === true, value === true ? 'Value is true' : `Expected true, got ${value}`);
|
|
95
|
+
},
|
|
96
|
+
isFalse(value) {
|
|
97
|
+
return boolResult(value === false, value === false ? 'Value is false' : `Expected false, got ${value}`);
|
|
98
|
+
},
|
|
99
|
+
// --- LLM judge helpers ---
|
|
100
|
+
fromJudge({ verdict, reasoning }) {
|
|
101
|
+
return new EvaluationVerdictResult({ value: verdict, confidence: 0.9, reasoning });
|
|
102
|
+
},
|
|
103
|
+
score(value, reasoning) {
|
|
104
|
+
return new EvaluationNumberResult({ value, confidence: 0.9, reasoning });
|
|
105
|
+
},
|
|
106
|
+
label(value, reasoning) {
|
|
107
|
+
return new EvaluationStringResult({ value, confidence: 0.9, reasoning });
|
|
108
|
+
}
|
|
109
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Verdict } from './verdict.js';
|
|
3
|
+
import { EvaluationBooleanResult, EvaluationVerdictResult, EvaluationNumberResult, EvaluationStringResult } from '@outputai/core';
|
|
4
|
+
describe('Verdict', () => {
|
|
5
|
+
// --- Original helpers regression ---
|
|
6
|
+
describe('pass', () => {
|
|
7
|
+
it('returns EvaluationVerdictResult with value "pass"', () => {
|
|
8
|
+
const r = Verdict.pass('looks good');
|
|
9
|
+
expect(r).toBeInstanceOf(EvaluationVerdictResult);
|
|
10
|
+
expect(r.value).toBe('pass');
|
|
11
|
+
expect(r.confidence).toBe(1.0);
|
|
12
|
+
expect(r.reasoning).toBe('looks good');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
describe('partial', () => {
|
|
16
|
+
it('returns EvaluationVerdictResult with value "partial"', () => {
|
|
17
|
+
const r = Verdict.partial(0.7, 'mostly ok');
|
|
18
|
+
expect(r).toBeInstanceOf(EvaluationVerdictResult);
|
|
19
|
+
expect(r.value).toBe('partial');
|
|
20
|
+
expect(r.confidence).toBe(0.7);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe('fail', () => {
|
|
24
|
+
it('returns EvaluationVerdictResult with value "fail"', () => {
|
|
25
|
+
const r = Verdict.fail('wrong');
|
|
26
|
+
expect(r).toBeInstanceOf(EvaluationVerdictResult);
|
|
27
|
+
expect(r.value).toBe('fail');
|
|
28
|
+
expect(r.confidence).toBe(0.0);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
// --- Deterministic assertions ---
|
|
32
|
+
describe('equals', () => {
|
|
33
|
+
it('passes when values are strictly equal', () => {
|
|
34
|
+
const r = Verdict.equals(42, 42);
|
|
35
|
+
expect(r).toBeInstanceOf(EvaluationBooleanResult);
|
|
36
|
+
expect(r.value).toBe(true);
|
|
37
|
+
expect(r.confidence).toBe(1.0);
|
|
38
|
+
});
|
|
39
|
+
it('fails when values differ', () => {
|
|
40
|
+
const r = Verdict.equals(42, 43);
|
|
41
|
+
expect(r.value).toBe(false);
|
|
42
|
+
expect(r.confidence).toBe(1.0);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('closeTo', () => {
|
|
46
|
+
it('passes within tolerance', () => {
|
|
47
|
+
const r = Verdict.closeTo(3.14, 3.15, 0.02);
|
|
48
|
+
expect(r.value).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
it('fails outside tolerance', () => {
|
|
51
|
+
const r = Verdict.closeTo(3.14, 3.20, 0.02);
|
|
52
|
+
expect(r.value).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('gt', () => {
|
|
56
|
+
it('passes when actual > threshold', () => {
|
|
57
|
+
expect(Verdict.gt(5, 3).value).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
it('fails when actual <= threshold', () => {
|
|
60
|
+
expect(Verdict.gt(3, 3).value).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('gte', () => {
|
|
64
|
+
it('passes when actual >= threshold', () => {
|
|
65
|
+
expect(Verdict.gte(3, 3).value).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
it('fails when actual < threshold', () => {
|
|
68
|
+
expect(Verdict.gte(2, 3).value).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('lt', () => {
|
|
72
|
+
it('passes when actual < threshold', () => {
|
|
73
|
+
expect(Verdict.lt(2, 3).value).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
it('fails when actual >= threshold', () => {
|
|
76
|
+
expect(Verdict.lt(3, 3).value).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('lte', () => {
|
|
80
|
+
it('passes when actual <= threshold', () => {
|
|
81
|
+
expect(Verdict.lte(3, 3).value).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
it('fails when actual > threshold', () => {
|
|
84
|
+
expect(Verdict.lte(4, 3).value).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe('inRange', () => {
|
|
88
|
+
it('passes when value is in range', () => {
|
|
89
|
+
expect(Verdict.inRange(5, 1, 10).value).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
it('passes at boundaries', () => {
|
|
92
|
+
expect(Verdict.inRange(1, 1, 10).value).toBe(true);
|
|
93
|
+
expect(Verdict.inRange(10, 1, 10).value).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
it('fails when value is out of range', () => {
|
|
96
|
+
expect(Verdict.inRange(11, 1, 10).value).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe('contains', () => {
|
|
100
|
+
it('passes when haystack includes needle', () => {
|
|
101
|
+
expect(Verdict.contains('hello world', 'world').value).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
it('fails when haystack does not include needle', () => {
|
|
104
|
+
expect(Verdict.contains('hello world', 'xyz').value).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
describe('matches', () => {
|
|
108
|
+
it('passes when value matches pattern', () => {
|
|
109
|
+
expect(Verdict.matches('abc123', /\d+/).value).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
it('fails when value does not match', () => {
|
|
112
|
+
expect(Verdict.matches('abc', /\d+/).value).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe('includesAll', () => {
|
|
116
|
+
it('passes when all expected values are present', () => {
|
|
117
|
+
expect(Verdict.includesAll([1, 2, 3], [1, 3]).value).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
it('fails when some expected values are missing', () => {
|
|
120
|
+
const r = Verdict.includesAll([1, 2], [2, 3]);
|
|
121
|
+
expect(r.value).toBe(false);
|
|
122
|
+
expect(r.reasoning).toContain('3');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe('includesAny', () => {
|
|
126
|
+
it('passes when at least one expected value is present', () => {
|
|
127
|
+
expect(Verdict.includesAny([1, 2, 3], [5, 2]).value).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
it('fails when no expected values are present', () => {
|
|
130
|
+
expect(Verdict.includesAny([1, 2], [5, 6]).value).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
describe('isTrue', () => {
|
|
134
|
+
it('passes for true', () => {
|
|
135
|
+
expect(Verdict.isTrue(true).value).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
it('fails for false', () => {
|
|
138
|
+
expect(Verdict.isTrue(false).value).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
describe('isFalse', () => {
|
|
142
|
+
it('passes for false', () => {
|
|
143
|
+
expect(Verdict.isFalse(false).value).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
it('fails for true', () => {
|
|
146
|
+
expect(Verdict.isFalse(true).value).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
// --- LLM judge helpers ---
|
|
150
|
+
describe('fromJudge', () => {
|
|
151
|
+
it('returns EvaluationVerdictResult with 0.9 confidence', () => {
|
|
152
|
+
const r = Verdict.fromJudge({ verdict: 'pass', reasoning: 'on topic' });
|
|
153
|
+
expect(r).toBeInstanceOf(EvaluationVerdictResult);
|
|
154
|
+
expect(r.value).toBe('pass');
|
|
155
|
+
expect(r.confidence).toBe(0.9);
|
|
156
|
+
expect(r.reasoning).toBe('on topic');
|
|
157
|
+
});
|
|
158
|
+
it('handles all verdict values', () => {
|
|
159
|
+
expect(Verdict.fromJudge({ verdict: 'partial', reasoning: 'ok' }).value).toBe('partial');
|
|
160
|
+
expect(Verdict.fromJudge({ verdict: 'fail', reasoning: 'bad' }).value).toBe('fail');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
describe('score', () => {
|
|
164
|
+
it('returns EvaluationNumberResult with 0.9 confidence', () => {
|
|
165
|
+
const r = Verdict.score(0.85, 'good quality');
|
|
166
|
+
expect(r).toBeInstanceOf(EvaluationNumberResult);
|
|
167
|
+
expect(r.value).toBe(0.85);
|
|
168
|
+
expect(r.confidence).toBe(0.9);
|
|
169
|
+
expect(r.reasoning).toBe('good quality');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
describe('label', () => {
|
|
173
|
+
it('returns EvaluationStringResult with 0.9 confidence', () => {
|
|
174
|
+
const r = Verdict.label('formal', 'professional writing');
|
|
175
|
+
expect(r).toBeInstanceOf(EvaluationStringResult);
|
|
176
|
+
expect(r.value).toBe('formal');
|
|
177
|
+
expect(r.confidence).toBe(0.9);
|
|
178
|
+
expect(r.reasoning).toBe('professional writing');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from '@outputai/core';
|
|
2
|
+
import type { EvaluationResult } from '@outputai/core';
|
|
3
|
+
export type CheckContext<TInput = unknown, TOutput = unknown> = {
|
|
4
|
+
input: TInput;
|
|
5
|
+
output: TOutput;
|
|
6
|
+
context: {
|
|
7
|
+
ground_truth: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
type CheckFn<TInput, TOutput> = (ctx: CheckContext<TInput, TOutput>) => EvaluationResult | Promise<EvaluationResult>;
|
|
11
|
+
type VerifyOptions<I extends z.ZodType = z.ZodType<unknown>, O extends z.ZodType = z.ZodType<unknown>> = {
|
|
12
|
+
name: string;
|
|
13
|
+
input?: I;
|
|
14
|
+
output?: O;
|
|
15
|
+
};
|
|
16
|
+
export declare const verify: <I extends z.ZodType, O extends z.ZodType>(options: VerifyOptions<I, O>, fn: CheckFn<z.infer<I>, z.infer<O>>) => (input: ({
|
|
17
|
+
input: z.ZodAny | I;
|
|
18
|
+
output: z.ZodAny | O;
|
|
19
|
+
ground_truth: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
20
|
+
} extends infer T_1 extends z.core.$ZodLooseShape ? { -readonly [k in keyof T_1 as {
|
|
21
|
+
input: z.ZodAny | I;
|
|
22
|
+
output: z.ZodAny | O;
|
|
23
|
+
ground_truth: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
24
|
+
}[k] extends {
|
|
25
|
+
_zod: {
|
|
26
|
+
optout: "optional";
|
|
27
|
+
};
|
|
28
|
+
} ? never : k]: T_1[k]["_zod"]["output"]; } : never) & ({
|
|
29
|
+
input: z.ZodAny | I;
|
|
30
|
+
output: z.ZodAny | O;
|
|
31
|
+
ground_truth: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
32
|
+
} extends infer T_2 extends z.core.$ZodLooseShape ? { -readonly [k_1 in keyof T_2 as {
|
|
33
|
+
input: z.ZodAny | I;
|
|
34
|
+
output: z.ZodAny | O;
|
|
35
|
+
ground_truth: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
36
|
+
}[k_1] extends {
|
|
37
|
+
_zod: {
|
|
38
|
+
optout: "optional";
|
|
39
|
+
};
|
|
40
|
+
} ? k_1 : never]?: T_2[k_1]["_zod"]["output"] | undefined; } : never) extends infer T ? { [K in keyof T]: T[K]; } : never) => Promise<EvaluationResult>;
|
|
41
|
+
export {};
|