@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.
@@ -0,0 +1,2 @@
1
+ import type { EvaluatorResult, Verdict } from './schemas.js';
2
+ export declare function aggregateCaseVerdict(evaluatorResults: EvaluatorResult[]): Verdict;
@@ -0,0 +1,11 @@
1
+ import { VERDICT, CRITICALITY } from './schemas.js';
2
+ export function aggregateCaseVerdict(evaluatorResults) {
3
+ const required = evaluatorResults.filter(r => r.criticality === CRITICALITY.REQUIRED);
4
+ if (required.some(r => r.verdict === VERDICT.FAIL)) {
5
+ return VERDICT.FAIL;
6
+ }
7
+ if (required.some(r => r.verdict === VERDICT.PARTIAL)) {
8
+ return VERDICT.PARTIAL;
9
+ }
10
+ return VERDICT.PASS;
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,48 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { aggregateCaseVerdict } from './aggregate.js';
3
+ const makeResult = (verdict, criticality = 'required') => ({
4
+ name: 'test_eval',
5
+ verdict,
6
+ criticality
7
+ });
8
+ describe('aggregateCaseVerdict', () => {
9
+ it('returns pass when all required evaluators pass', () => {
10
+ expect(aggregateCaseVerdict([
11
+ makeResult('pass'),
12
+ makeResult('pass')
13
+ ])).toBe('pass');
14
+ });
15
+ it('returns fail when any required evaluator fails', () => {
16
+ expect(aggregateCaseVerdict([
17
+ makeResult('pass'),
18
+ makeResult('fail')
19
+ ])).toBe('fail');
20
+ });
21
+ it('returns partial when any required evaluator is partial and none fail', () => {
22
+ expect(aggregateCaseVerdict([
23
+ makeResult('pass'),
24
+ makeResult('partial')
25
+ ])).toBe('partial');
26
+ });
27
+ it('returns fail over partial when both exist', () => {
28
+ expect(aggregateCaseVerdict([
29
+ makeResult('partial'),
30
+ makeResult('fail')
31
+ ])).toBe('fail');
32
+ });
33
+ it('ignores informational evaluators for verdict', () => {
34
+ expect(aggregateCaseVerdict([
35
+ makeResult('pass'),
36
+ makeResult('fail', 'informational')
37
+ ])).toBe('pass');
38
+ });
39
+ it('returns pass when only informational evaluators fail', () => {
40
+ expect(aggregateCaseVerdict([
41
+ makeResult('fail', 'informational'),
42
+ makeResult('partial', 'informational')
43
+ ])).toBe('pass');
44
+ });
45
+ it('returns pass for empty evaluator list', () => {
46
+ expect(aggregateCaseVerdict([])).toBe('pass');
47
+ });
48
+ });
@@ -0,0 +1,20 @@
1
+ import type { InterpretConfig, EvalOutput, Dataset, Criticality } from './schemas.js';
2
+ export interface EvalDefinition {
3
+ evaluator: Function;
4
+ criticality?: Criticality;
5
+ interpret: InterpretConfig;
6
+ }
7
+ export interface EvalWorkflowConfig {
8
+ concurrency?: number;
9
+ }
10
+ export type EvalWorkflowInput = {
11
+ datasets: Dataset[];
12
+ };
13
+ export type EvalWorkflowFn = (input: EvalWorkflowInput, context: unknown) => Promise<EvalOutput>;
14
+ export interface EvalWorkflowOptions {
15
+ name: string;
16
+ evals: EvalDefinition[];
17
+ fn?: EvalWorkflowFn;
18
+ config?: EvalWorkflowConfig;
19
+ }
20
+ export declare function evalWorkflow({ name, evals, fn, config }: EvalWorkflowOptions): unknown;
@@ -0,0 +1,88 @@
1
+ import { workflow, executeInParallel, z } from '@outputai/core';
2
+ import { getMetadata } from '@outputai/core/sdk_utils';
3
+ import { VERDICT, CRITICALITY, DatasetSchema, EvalOutputSchema } from './schemas.js';
4
+ import { interpretResult } from './interpret.js';
5
+ import { aggregateCaseVerdict } from './aggregate.js';
6
+ export function evalWorkflow({ name, evals, fn, config = {} }) {
7
+ const concurrency = config.concurrency ?? 10;
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().');
12
+ }
13
+ return {
14
+ name: meta.name,
15
+ criticality: def.criticality ?? CRITICALITY.REQUIRED,
16
+ interpret: def.interpret
17
+ };
18
+ });
19
+ const defaultFn = async function (input) {
20
+ const { invokeEvaluator } = this;
21
+ const jobs = input.datasets.map((dataset) => async () => {
22
+ const evaluatorResults = [];
23
+ for (const evalDef of evalDefs) {
24
+ const { evals: perEvalTruth, ...globalTruth } = dataset.ground_truth ?? {};
25
+ const evalTruth = perEvalTruth?.[evalDef.name] ?? {};
26
+ const mergedTruth = { ...globalTruth, ...evalTruth };
27
+ const result = await invokeEvaluator(evalDef.name, {
28
+ input: dataset.input,
29
+ output: dataset.last_output?.output,
30
+ ground_truth: mergedTruth
31
+ });
32
+ const verdict = interpretResult(result, evalDef.interpret);
33
+ evaluatorResults.push({
34
+ name: evalDef.name,
35
+ verdict,
36
+ criticality: evalDef.criticality,
37
+ confidence: result.confidence,
38
+ reasoning: result.reasoning,
39
+ feedback: result.feedback?.length ? result.feedback : undefined
40
+ });
41
+ }
42
+ const caseVerdict = aggregateCaseVerdict(evaluatorResults);
43
+ return {
44
+ datasetName: dataset.name,
45
+ verdict: caseVerdict,
46
+ evaluators: evaluatorResults
47
+ };
48
+ });
49
+ const results = await executeInParallel({ jobs, concurrency });
50
+ const cases = results.map((r, i) => {
51
+ if (r.ok) {
52
+ return r.result;
53
+ }
54
+ const datasetName = input.datasets[i]?.name ?? `dataset_${i}`;
55
+ return {
56
+ datasetName,
57
+ verdict: VERDICT.FAIL,
58
+ evaluators: [{
59
+ name: '_error',
60
+ verdict: VERDICT.FAIL,
61
+ criticality: CRITICALITY.REQUIRED,
62
+ reasoning: r.error instanceof Error ? r.error.message : String(r.error)
63
+ }]
64
+ };
65
+ });
66
+ const passed = cases.filter(c => c.verdict === VERDICT.PASS).length;
67
+ const partial = cases.filter(c => c.verdict === VERDICT.PARTIAL).length;
68
+ const failed = cases.filter(c => c.verdict === VERDICT.FAIL).length;
69
+ const total = cases.length;
70
+ return {
71
+ cases,
72
+ summary: {
73
+ total,
74
+ passed,
75
+ partial,
76
+ failed,
77
+ acceptableRate: total > 0 ? (passed + partial) / total : 0
78
+ }
79
+ };
80
+ };
81
+ return workflow({
82
+ name,
83
+ description: `Eval workflow for ${name}`,
84
+ inputSchema: z.object({ datasets: z.array(DatasetSchema) }),
85
+ outputSchema: EvalOutputSchema,
86
+ fn: fn ?? defaultFn
87
+ });
88
+ }
@@ -0,0 +1,13 @@
1
+ export { evalWorkflow } from './eval_workflow.js';
2
+ export type { EvalDefinition, EvalWorkflowConfig, EvalWorkflowInput, EvalWorkflowFn, EvalWorkflowOptions } from './eval_workflow.js';
3
+ export { Verdict } from './verdict.js';
4
+ export { verify } from './verify.js';
5
+ export type { CheckContext } from './verify.js';
6
+ export { judgeVerdict, judgeScore, judgeBoolean, judgeLabel } from './judge.js';
7
+ export type { JudgeArgs } from './judge.js';
8
+ export { interpretResult } from './interpret.js';
9
+ export { aggregateCaseVerdict } from './aggregate.js';
10
+ export { renderEvalOutput, computeExitCode } from './render.js';
11
+ export { getEvalWorkflowName, isEvalWorkflow, getParentWorkflowName } from './naming.js';
12
+ export { VERDICT, CRITICALITY, VerdictSchema, CriticalitySchema, InterpretConfigSchema, EvaluatorResultSchema, EvalCaseSchema, EvalOutputSchema, LastOutputSchema, LastEvalSchema, DatasetSchema } from './schemas.js';
13
+ export type { Verdict as VerdictType, Criticality, InterpretConfig, EvaluatorResult, EvalCase, EvalSummary, EvalOutput, GroundTruth, LastOutput, LastEval, Dataset } from './schemas.js';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { evalWorkflow } from './eval_workflow.js';
2
+ export { Verdict } from './verdict.js';
3
+ export { verify } from './verify.js';
4
+ export { judgeVerdict, judgeScore, judgeBoolean, judgeLabel } from './judge.js';
5
+ export { interpretResult } from './interpret.js';
6
+ export { aggregateCaseVerdict } from './aggregate.js';
7
+ export { renderEvalOutput, computeExitCode } from './render.js';
8
+ export { getEvalWorkflowName, isEvalWorkflow, getParentWorkflowName } from './naming.js';
9
+ export { VERDICT, CRITICALITY, VerdictSchema, CriticalitySchema, InterpretConfigSchema, EvaluatorResultSchema, EvalCaseSchema, EvalOutputSchema, LastOutputSchema, LastEvalSchema, DatasetSchema } from './schemas.js';
@@ -0,0 +1,16 @@
1
+ export { evalWorkflow } from './eval_workflow.js';
2
+ export type { EvalDefinition, EvalWorkflowConfig, EvalWorkflowInput, EvalWorkflowFn, EvalWorkflowOptions } from './eval_workflow.js';
3
+ export { Verdict } from './verdict.js';
4
+ export { verify } from './verify.js';
5
+ export type { CheckContext } from './verify.js';
6
+ export type { JudgeArgs } from './judge.js';
7
+ export { interpretResult } from './interpret.js';
8
+ export { aggregateCaseVerdict } from './aggregate.js';
9
+ export { renderEvalOutput, computeExitCode } from './render.js';
10
+ export { getEvalWorkflowName, isEvalWorkflow, getParentWorkflowName } from './naming.js';
11
+ export { VERDICT, CRITICALITY, VerdictSchema, CriticalitySchema, InterpretConfigSchema, EvaluatorResultSchema, EvalCaseSchema, EvalOutputSchema, LastOutputSchema, LastEvalSchema, DatasetSchema } from './schemas.js';
12
+ export type { Verdict as VerdictType, Criticality, InterpretConfig, EvaluatorResult, EvalCase, EvalSummary, EvalOutput, GroundTruth, LastOutput, LastEval, Dataset } from './schemas.js';
13
+ export declare const judgeVerdict: (...args: unknown[]) => never;
14
+ export declare const judgeScore: (...args: unknown[]) => never;
15
+ export declare const judgeBoolean: (...args: unknown[]) => never;
16
+ export declare const judgeLabel: (...args: unknown[]) => never;
@@ -0,0 +1,51 @@
1
+ /*
2
+ * HACK: Export workflow-safe function signatures independently.
3
+ *
4
+ * Problem:
5
+ * Eval workflow files pass evaluator functions as config values to evalWorkflow():
6
+ *
7
+ * export default evalWorkflow({ evals: [{ evaluator: evaluateTopic, ... }] });
8
+ *
9
+ * The webpack rewriter (workflow_rewriter/index.mjs) is supposed to strip evaluator
10
+ * imports from the AST via collectTargetImports(), which calls path.remove() on each
11
+ * matched import declaration. However, after collecting imports, the rewriter checks
12
+ * if rewriteFnBodies() performed any rewrites. For eval workflows — which have no
13
+ * fn body, only a config object — rewriteFnBodies() returns false. When that happens,
14
+ * the rewriter returns the original source string unchanged (lines 46-48), discarding
15
+ * the import stripping that collectTargetImports() already applied to the AST.
16
+ *
17
+ * This means webpack follows the full import chain: evaluators.js → @outputai/evals
18
+ * → judge.js → @outputai/llm → node:zlib — which fails because Node.js built-ins
19
+ * can't be bundled into Temporal's deterministic workflow bundle.
20
+ *
21
+ * This file provides an alternative entry point for @outputai/evals that excludes
22
+ * the real judge functions (breaking the chain to @outputai/llm) and replaces them
23
+ * with no-op stubs. The stubs satisfy webpack's named-export resolution. Judge
24
+ * functions are never called inside the workflow bundle — they execute as Temporal
25
+ * activities at runtime where the real entry point (index.js) is used.
26
+ *
27
+ * Remove when:
28
+ * The workflow rewriter in sdk/core/src/worker/webpack_loaders/workflow_rewriter/index.mjs
29
+ * is fixed to generate output from the modified AST even when rewriteFnBodies() returns
30
+ * false — i.e. when collectTargetImports() stripped imports but no fn bodies needed
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.
36
+ */
37
+ export { evalWorkflow } from './eval_workflow.js';
38
+ export { Verdict } from './verdict.js';
39
+ export { verify } from './verify.js';
40
+ export { interpretResult } from './interpret.js';
41
+ export { aggregateCaseVerdict } from './aggregate.js';
42
+ export { renderEvalOutput, computeExitCode } from './render.js';
43
+ export { getEvalWorkflowName, isEvalWorkflow, getParentWorkflowName } from './naming.js';
44
+ export { VERDICT, CRITICALITY, VerdictSchema, CriticalitySchema, InterpretConfigSchema, EvaluatorResultSchema, EvalCaseSchema, EvalOutputSchema, LastOutputSchema, LastEvalSchema, DatasetSchema } from './schemas.js';
45
+ const sandboxStub = () => {
46
+ throw new Error('Judge functions are not available in the Temporal workflow bundle');
47
+ };
48
+ export const judgeVerdict = sandboxStub;
49
+ export const judgeScore = sandboxStub;
50
+ export const judgeBoolean = sandboxStub;
51
+ export const judgeLabel = sandboxStub;
@@ -0,0 +1,3 @@
1
+ import type { EvaluationResult } from '@outputai/core';
2
+ import type { InterpretConfig, Verdict } from './schemas.js';
3
+ export declare const interpretResult: (result: EvaluationResult, config: InterpretConfig) => Verdict;
@@ -0,0 +1,40 @@
1
+ import { VERDICT, VerdictSchema } from './schemas.js';
2
+ const VALID_VERDICTS = new Set(VerdictSchema.options);
3
+ const interpretVerdict = (value) => VALID_VERDICTS.has(value) ? value : VERDICT.FAIL;
4
+ const interpretBoolean = (value) => value === true ? VERDICT.PASS : VERDICT.FAIL;
5
+ const interpretNumber = (value, config) => {
6
+ if (typeof value !== 'number' || Number.isNaN(value)) {
7
+ return VERDICT.FAIL;
8
+ }
9
+ if (value >= config.pass) {
10
+ return VERDICT.PASS;
11
+ }
12
+ if (config.partial !== undefined && config.partial !== null && value >= config.partial) {
13
+ return VERDICT.PARTIAL;
14
+ }
15
+ return VERDICT.FAIL;
16
+ };
17
+ const interpretString = (value, config) => {
18
+ if (config.pass.includes(value)) {
19
+ return VERDICT.PASS;
20
+ }
21
+ if (config.partial?.includes(value)) {
22
+ return VERDICT.PARTIAL;
23
+ }
24
+ return VERDICT.FAIL;
25
+ };
26
+ export const interpretResult = (result, config) => {
27
+ const { value } = result;
28
+ switch (config.type) {
29
+ case 'verdict':
30
+ return interpretVerdict(value);
31
+ case 'boolean':
32
+ return interpretBoolean(value);
33
+ case 'number':
34
+ return interpretNumber(value, config);
35
+ case 'string':
36
+ return interpretString(value, config);
37
+ default:
38
+ return VERDICT.FAIL;
39
+ }
40
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,86 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { interpretResult } from './interpret.js';
3
+ const makeResult = (value) => ({
4
+ value,
5
+ confidence: 1.0,
6
+ feedback: [],
7
+ dimensions: []
8
+ });
9
+ describe('interpretResult', () => {
10
+ describe('verdict config', () => {
11
+ const config = { type: 'verdict' };
12
+ it('passes through valid verdict values', () => {
13
+ expect(interpretResult(makeResult('pass'), config)).toBe('pass');
14
+ expect(interpretResult(makeResult('partial'), config)).toBe('partial');
15
+ expect(interpretResult(makeResult('fail'), config)).toBe('fail');
16
+ });
17
+ it('returns fail for invalid verdict values', () => {
18
+ expect(interpretResult(makeResult('maybe'), config)).toBe('fail');
19
+ expect(interpretResult(makeResult(''), config)).toBe('fail');
20
+ expect(interpretResult(makeResult(null), config)).toBe('fail');
21
+ expect(interpretResult(makeResult(42), config)).toBe('fail');
22
+ });
23
+ });
24
+ describe('boolean config', () => {
25
+ const config = { type: 'boolean' };
26
+ it('returns pass for true', () => {
27
+ expect(interpretResult(makeResult(true), config)).toBe('pass');
28
+ });
29
+ it('returns fail for false', () => {
30
+ expect(interpretResult(makeResult(false), config)).toBe('fail');
31
+ });
32
+ it('returns fail for truthy non-boolean values', () => {
33
+ expect(interpretResult(makeResult(1), config)).toBe('fail');
34
+ expect(interpretResult(makeResult('true'), config)).toBe('fail');
35
+ });
36
+ });
37
+ describe('number config', () => {
38
+ it('returns pass when value >= pass threshold', () => {
39
+ const config = { type: 'number', pass: 0.8 };
40
+ expect(interpretResult(makeResult(0.9), config)).toBe('pass');
41
+ expect(interpretResult(makeResult(0.8), config)).toBe('pass');
42
+ expect(interpretResult(makeResult(1.0), config)).toBe('pass');
43
+ });
44
+ it('returns fail when value < pass threshold with no partial', () => {
45
+ const config = { type: 'number', pass: 0.8 };
46
+ expect(interpretResult(makeResult(0.7), config)).toBe('fail');
47
+ expect(interpretResult(makeResult(0), config)).toBe('fail');
48
+ });
49
+ it('returns partial when value between partial and pass thresholds', () => {
50
+ const config = { type: 'number', pass: 0.8, partial: 0.5 };
51
+ expect(interpretResult(makeResult(0.6), config)).toBe('partial');
52
+ expect(interpretResult(makeResult(0.5), config)).toBe('partial');
53
+ });
54
+ it('returns fail when value < partial threshold', () => {
55
+ const config = { type: 'number', pass: 0.8, partial: 0.5 };
56
+ expect(interpretResult(makeResult(0.4), config)).toBe('fail');
57
+ });
58
+ it('returns fail for non-number values', () => {
59
+ const config = { type: 'number', pass: 0.8 };
60
+ expect(interpretResult(makeResult(null), config)).toBe('fail');
61
+ expect(interpretResult(makeResult(undefined), config)).toBe('fail');
62
+ expect(interpretResult(makeResult('high'), config)).toBe('fail');
63
+ });
64
+ });
65
+ describe('string config', () => {
66
+ it('returns pass for matching pass values', () => {
67
+ const config = { type: 'string', pass: ['good', 'great'] };
68
+ expect(interpretResult(makeResult('good'), config)).toBe('pass');
69
+ expect(interpretResult(makeResult('great'), config)).toBe('pass');
70
+ });
71
+ it('returns partial for matching partial values', () => {
72
+ const config = { type: 'string', pass: ['good'], partial: ['ok', 'decent'] };
73
+ expect(interpretResult(makeResult('ok'), config)).toBe('partial');
74
+ expect(interpretResult(makeResult('decent'), config)).toBe('partial');
75
+ });
76
+ it('returns fail for non-matching values', () => {
77
+ const config = { type: 'string', pass: ['good'], partial: ['ok'] };
78
+ expect(interpretResult(makeResult('bad'), config)).toBe('fail');
79
+ expect(interpretResult(makeResult(''), config)).toBe('fail');
80
+ });
81
+ it('returns fail when no partial config and value not in pass', () => {
82
+ const config = { type: 'string', pass: ['good'] };
83
+ expect(interpretResult(makeResult('ok'), config)).toBe('fail');
84
+ });
85
+ });
86
+ });
@@ -0,0 +1,10 @@
1
+ import { EvaluationVerdictResult, EvaluationNumberResult, EvaluationBooleanResult, EvaluationStringResult, z } from '@outputai/core';
2
+ export type JudgeArgs = {
3
+ prompt: string;
4
+ variables?: Record<string, string | number | boolean>;
5
+ schema?: z.ZodType;
6
+ };
7
+ export declare function judgeVerdict({ prompt, variables, schema }: JudgeArgs): Promise<EvaluationVerdictResult>;
8
+ export declare function judgeScore({ prompt, variables, schema }: JudgeArgs): Promise<EvaluationNumberResult>;
9
+ export declare function judgeBoolean({ prompt, variables, schema }: JudgeArgs): Promise<EvaluationBooleanResult>;
10
+ export declare function judgeLabel({ prompt, variables, schema }: JudgeArgs): Promise<EvaluationStringResult>;
package/dist/judge.js ADDED
@@ -0,0 +1,31 @@
1
+ import { EvaluationVerdictResult, EvaluationNumberResult, EvaluationBooleanResult, EvaluationStringResult, z } from '@outputai/core';
2
+ import { resolveInvocationDir } from '@outputai/core/sdk_utils';
3
+ import { generateText, Output } from '@outputai/llm';
4
+ const verdictSchema = z.object({ verdict: z.enum(['pass', 'partial', 'fail']), reasoning: z.string() });
5
+ const scoreSchema = z.object({ score: z.number(), reasoning: z.string() });
6
+ const booleanSchema = z.object({ result: z.boolean(), reasoning: z.string() });
7
+ const labelSchema = z.object({ label: z.string(), reasoning: z.string() });
8
+ export async function judgeVerdict({ prompt, variables, schema = verdictSchema }) {
9
+ const promptDir = resolveInvocationDir();
10
+ const response = await generateText({ prompt, variables, promptDir, output: Output.object({ schema }) });
11
+ const result = response.output;
12
+ return new EvaluationVerdictResult({ value: result.verdict, confidence: 0.9, reasoning: result.reasoning });
13
+ }
14
+ export async function judgeScore({ prompt, variables, schema = scoreSchema }) {
15
+ const promptDir = resolveInvocationDir();
16
+ const response = await generateText({ prompt, variables, promptDir, output: Output.object({ schema }) });
17
+ const result = response.output;
18
+ return new EvaluationNumberResult({ value: result.score, confidence: 0.9, reasoning: result.reasoning });
19
+ }
20
+ export async function judgeBoolean({ prompt, variables, schema = booleanSchema }) {
21
+ const promptDir = resolveInvocationDir();
22
+ const response = await generateText({ prompt, variables, promptDir, output: Output.object({ schema }) });
23
+ const result = response.output;
24
+ return new EvaluationBooleanResult({ value: result.result, confidence: 0.9, reasoning: result.reasoning });
25
+ }
26
+ export async function judgeLabel({ prompt, variables, schema = labelSchema }) {
27
+ const promptDir = resolveInvocationDir();
28
+ const response = await generateText({ prompt, variables, promptDir, output: Output.object({ schema }) });
29
+ const result = response.output;
30
+ return new EvaluationStringResult({ value: result.label, confidence: 0.9, reasoning: result.reasoning });
31
+ }
@@ -0,0 +1,3 @@
1
+ export declare const getEvalWorkflowName: (workflowName: string) => string;
2
+ export declare const isEvalWorkflow: (name: string) => boolean;
3
+ export declare const getParentWorkflowName: (evalName: string) => string;
package/dist/naming.js ADDED
@@ -0,0 +1,4 @@
1
+ const EVAL_SUFFIX = '_eval';
2
+ export const getEvalWorkflowName = (workflowName) => `${workflowName}${EVAL_SUFFIX}`;
3
+ export const isEvalWorkflow = (name) => name.endsWith(EVAL_SUFFIX);
4
+ export const getParentWorkflowName = (evalName) => evalName.slice(0, -EVAL_SUFFIX.length);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { getEvalWorkflowName, isEvalWorkflow, getParentWorkflowName } from './naming.js';
3
+ describe('naming', () => {
4
+ describe('getEvalWorkflowName', () => {
5
+ it('appends _eval suffix', () => {
6
+ expect(getEvalWorkflowName('simple')).toBe('simple_eval');
7
+ expect(getEvalWorkflowName('my_workflow')).toBe('my_workflow_eval');
8
+ });
9
+ });
10
+ describe('isEvalWorkflow', () => {
11
+ it('returns true for names ending with _eval', () => {
12
+ expect(isEvalWorkflow('simple_eval')).toBe(true);
13
+ expect(isEvalWorkflow('my_workflow_eval')).toBe(true);
14
+ });
15
+ it('returns false for regular workflow names', () => {
16
+ expect(isEvalWorkflow('simple')).toBe(false);
17
+ expect(isEvalWorkflow('eval_runner')).toBe(false);
18
+ expect(isEvalWorkflow('evaluation')).toBe(false);
19
+ });
20
+ });
21
+ describe('getParentWorkflowName', () => {
22
+ it('strips the _eval suffix', () => {
23
+ expect(getParentWorkflowName('simple_eval')).toBe('simple');
24
+ expect(getParentWorkflowName('my_workflow_eval')).toBe('my_workflow');
25
+ });
26
+ it('returns empty string for bare _eval', () => {
27
+ expect(getParentWorkflowName('_eval')).toBe('');
28
+ });
29
+ });
30
+ });
@@ -0,0 +1,3 @@
1
+ import type { EvalOutput } from './schemas.js';
2
+ export declare function renderEvalOutput(evalOutput: EvalOutput, evalName?: string): string;
3
+ export declare function computeExitCode(evalOutput: EvalOutput): number;
package/dist/render.js ADDED
@@ -0,0 +1,75 @@
1
+ import { VERDICT, CRITICALITY } from './schemas.js';
2
+ const COLORS = {
3
+ reset: '\x1b[0m',
4
+ green: '\x1b[32m',
5
+ yellow: '\x1b[33m',
6
+ red: '\x1b[31m',
7
+ dim: '\x1b[2m',
8
+ bold: '\x1b[1m',
9
+ cyan: '\x1b[36m'
10
+ };
11
+ const VERDICT_STYLE = {
12
+ [VERDICT.PASS]: { label: 'PASS', color: COLORS.green },
13
+ [VERDICT.PARTIAL]: { label: 'PARTIAL', color: COLORS.yellow },
14
+ [VERDICT.FAIL]: { label: 'FAIL', color: COLORS.red }
15
+ };
16
+ function colorize(text, color) {
17
+ return `${color}${text}${COLORS.reset}`;
18
+ }
19
+ function padWithDots(label, width = 30) {
20
+ const dotCount = Math.max(2, width - label.length);
21
+ return ` ${'.'.repeat(dotCount)} `;
22
+ }
23
+ function renderEvaluator(evaluator) {
24
+ const lines = [];
25
+ const style = VERDICT_STYLE[evaluator.verdict];
26
+ const prefix = evaluator.criticality === CRITICALITY.INFORMATIONAL ?
27
+ `${COLORS.dim}(info)${COLORS.reset} ` :
28
+ '';
29
+ lines.push(` ${prefix}${evaluator.name}${padWithDots(evaluator.name, 24)}${colorize(evaluator.verdict, style.color)}`);
30
+ if (evaluator.reasoning && evaluator.verdict !== VERDICT.PASS) {
31
+ lines.push(` ${COLORS.dim}-> ${evaluator.reasoning}${COLORS.reset}`);
32
+ }
33
+ if (evaluator.feedback) {
34
+ for (const fb of evaluator.feedback) {
35
+ const item = fb;
36
+ if (item.issue) {
37
+ lines.push(` ${COLORS.dim}-> ${item.issue}${COLORS.reset}`);
38
+ }
39
+ }
40
+ }
41
+ return lines;
42
+ }
43
+ function renderCase(evalCase) {
44
+ const caseStyle = VERDICT_STYLE[evalCase.verdict];
45
+ const lines = [
46
+ ` ${evalCase.datasetName}${padWithDots(evalCase.datasetName)}${colorize(caseStyle.label, caseStyle.color)}`
47
+ ];
48
+ evalCase.evaluators.forEach(evaluator => lines.push(...renderEvaluator(evaluator)));
49
+ lines.push('');
50
+ return lines;
51
+ }
52
+ export function renderEvalOutput(evalOutput, evalName) {
53
+ const lines = [];
54
+ if (evalName) {
55
+ lines.push(colorize(evalName, COLORS.bold));
56
+ lines.push('');
57
+ }
58
+ evalOutput.cases.forEach(evalCase => lines.push(...renderCase(evalCase)));
59
+ const { summary } = evalOutput;
60
+ const parts = [];
61
+ if (summary.passed > 0) {
62
+ parts.push(colorize(`${summary.passed} passed`, COLORS.green));
63
+ }
64
+ if (summary.partial > 0) {
65
+ parts.push(colorize(`${summary.partial} partial`, COLORS.yellow));
66
+ }
67
+ if (summary.failed > 0) {
68
+ parts.push(colorize(`${summary.failed} failed`, COLORS.red));
69
+ }
70
+ lines.push(`${parts.join(', ')} (${Math.round(summary.acceptableRate * 100)}% acceptable)`);
71
+ return lines.join('\n');
72
+ }
73
+ export function computeExitCode(evalOutput) {
74
+ return evalOutput.cases.some(c => c.verdict === VERDICT.FAIL) ? 1 : 0;
75
+ }
@@ -0,0 +1 @@
1
+ export {};