@outputai/cli 0.15.1-next.4a437bf.0 → 0.15.1-next.5db73d3.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/assets/docker/docker-compose-dev.yml +1 -1
- package/dist/commands/workflow/test.d.ts +1 -1
- package/dist/commands/workflow/test.js +3 -1
- package/dist/commands/workflow/test.spec.js +2 -1
- package/dist/generated/framework_version.json +1 -1
- package/dist/services/cost_calculator.js +1 -1
- package/dist/services/datasets.d.ts +1 -1
- package/dist/services/datasets.js +1 -1
- package/dist/services/workflow_planner.js +1 -1
- package/dist/services/workflow_planner.spec.js +2 -2
- package/dist/templates/agent_instructions/CLAUDE.md.template +3 -3
- package/dist/templates/project/README.md.template +1 -1
- package/dist/templates/project/package.json.template +2 -0
- package/dist/templates/project/src/clients/jina.ts.template +1 -1
- package/dist/templates/project/src/workflows/blog_evaluator/evaluators.ts.template +1 -1
- package/dist/templates/workflow/README.md.template +2 -2
- package/dist/templates/workflow/evaluators.ts.template +1 -1
- package/dist/templates/workflow/steps.ts.template +1 -1
- package/dist/test_helpers/mocks.d.ts +1 -1
- package/dist/test_helpers/mocks.js +1 -1
- package/dist/types/cost.d.ts +1 -1
- package/dist/utils/eval_diagnostics.js +1 -1
- package/dist/utils/eval_naming.d.ts +1 -0
- package/dist/utils/eval_naming.js +2 -0
- package/dist/utils/eval_naming.spec.d.ts +1 -0
- package/dist/utils/eval_naming.spec.js +10 -0
- package/dist/utils/eval_render.d.ts +3 -0
- package/dist/utils/eval_render.js +75 -0
- package/dist/utils/eval_render.spec.d.ts +1 -0
- package/dist/utils/eval_render.spec.js +171 -0
- package/oclif.manifest.json +1 -1
- package/package.json +4 -5
|
@@ -5,7 +5,9 @@ import { readAllDatasets, writeDataset } from '#services/datasets.js';
|
|
|
5
5
|
import { fetchWorkflowCatalog } from '#api/workflow_catalog.js';
|
|
6
6
|
import { diagnoseMissingEvalWorkflow } from '#utils/eval_diagnostics.js';
|
|
7
7
|
import { handleApiError } from '#utils/error_handler.js';
|
|
8
|
-
import { getEvalWorkflowName
|
|
8
|
+
import { getEvalWorkflowName } from '#utils/eval_naming.js';
|
|
9
|
+
import { renderEvalOutput, computeExitCode } from '#utils/eval_render.js';
|
|
10
|
+
import { EvalOutputSchema } from '@outputai/core/evals';
|
|
9
11
|
export default class WorkflowTest extends Command {
|
|
10
12
|
static description = 'Run evaluations against a workflow using its datasets';
|
|
11
13
|
static enableJsonFlag = true;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
3
|
-
import { getEvalWorkflowName
|
|
3
|
+
import { getEvalWorkflowName } from '#utils/eval_naming.js';
|
|
4
|
+
import { renderEvalOutput } from '#utils/eval_render.js';
|
|
4
5
|
vi.mock('#api/generated/api.js', () => ({
|
|
5
6
|
postWorkflowRun: vi.fn()
|
|
6
7
|
}));
|
|
@@ -32,7 +32,7 @@ function priceLines(lines, pricing) {
|
|
|
32
32
|
}, 0);
|
|
33
33
|
}
|
|
34
34
|
// Token counts for display. The producer's 'input' line excludes cached tokens
|
|
35
|
-
// (
|
|
35
|
+
// (core/llm emits input − cached), while AI SDK reports inputTokens as the
|
|
36
36
|
// total — add cached back so the columns match what providers report.
|
|
37
37
|
function eventTokenUsage(lines) {
|
|
38
38
|
const sumOf = (type) => lines.filter(l => l.type === type).reduce((s, l) => s + l.amount, 0);
|
|
@@ -2,7 +2,7 @@ import { readFile, writeFile, readdir, mkdir } from 'node:fs/promises';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { resolve, join } from 'node:path';
|
|
4
4
|
import { load as parseYaml, dump as stringifyYaml } from 'js-yaml';
|
|
5
|
-
import { DatasetSchema } from '@outputai/evals';
|
|
5
|
+
import { DatasetSchema } from '@outputai/core/evals';
|
|
6
6
|
import { getTrace } from '#services/trace_reader.js';
|
|
7
7
|
import { sanitizeSecrets } from '#utils/secret_sanitizer.js';
|
|
8
8
|
import { resolveWorkflowDir, WORKFLOWS_PATHS } from '#utils/workflow_dir.js';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
2
|
import { generatePlanName, writePlanFile, updateAgentTemplates } from './workflow_planner.js';
|
|
3
3
|
import { initializeAgentConfig } from './coding_agents.js';
|
|
4
|
-
import { generateText } from '@outputai/llm';
|
|
4
|
+
import { generateText } from '@outputai/core/llm';
|
|
5
5
|
import fs from 'node:fs/promises';
|
|
6
6
|
vi.mock('./coding_agents.js');
|
|
7
|
-
vi.mock('@outputai/llm');
|
|
7
|
+
vi.mock('@outputai/core/llm');
|
|
8
8
|
vi.mock('node:fs/promises');
|
|
9
9
|
const mockGenerateTextResult = (text) => ({
|
|
10
10
|
text,
|
|
@@ -35,7 +35,7 @@ src/
|
|
|
35
35
|
types.ts # Zod schemas and TypeScript types
|
|
36
36
|
prompts/ # .prompt files (Liquid.js templates with YAML frontmatter)
|
|
37
37
|
scenarios/ # Test scenario JSON files
|
|
38
|
-
clients/ # Shared HTTP clients (use @outputai/http, not fetch/axios)
|
|
38
|
+
clients/ # Shared HTTP clients (use @outputai/core/http, not fetch/axios)
|
|
39
39
|
shared/ # Shared utilities across workflows
|
|
40
40
|
config/
|
|
41
41
|
costs.yml # Token/API pricing overrides
|
|
@@ -46,8 +46,8 @@ config/
|
|
|
46
46
|
## Key Conventions
|
|
47
47
|
|
|
48
48
|
- **Workflows are deterministic**: No I/O, no `Date.now()`, no `Math.random()` in `workflow.ts`. All side effects go in steps or evaluators.
|
|
49
|
-
- **HTTP clients**: Use `outputFetch` or `createKyClient` from `@outputai/http` instead of raw `fetch` or `axios`. Requests are automatically traced; for paid APIs, attach spend via `addRequestCost` in an `afterResponse` hook (`output-dev-http-client-create`) and forward cost events with `output-dev-cost-hooks`.
|
|
50
|
-
- **LLM calls**: Use `generateText` from `@outputai/llm` with `.prompt` files. Never call LLM APIs directly.
|
|
49
|
+
- **HTTP clients**: Use `outputFetch` or `createKyClient` from `@outputai/core/http` instead of raw `fetch` or `axios`. Requests are automatically traced; for paid APIs, attach spend via `addRequestCost` in an `afterResponse` hook (`output-dev-http-client-create`) and forward cost events with `output-dev-cost-hooks`.
|
|
50
|
+
- **LLM calls**: Use `generateText` from `@outputai/core/llm` with `.prompt` files. Never call LLM APIs directly.
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
@@ -27,7 +27,7 @@ src/
|
|
|
27
27
|
|
|
28
28
|
### Clients Directory
|
|
29
29
|
|
|
30
|
-
The `src/clients/` directory contains API clients using `@outputai/http` for external services.
|
|
30
|
+
The `src/clients/` directory contains API clients using `@outputai/core/http` for external services.
|
|
31
31
|
|
|
32
32
|
### Shared Directory
|
|
33
33
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { evaluator, EvaluationNumberResult } from '@outputai/core';
|
|
2
2
|
import type { EvaluationResultArgs } from '@outputai/core';
|
|
3
|
-
import { generateText, aiSdk } from '@outputai/llm';
|
|
3
|
+
import { generateText, aiSdk } from '@outputai/core/llm';
|
|
4
4
|
import { blogContentSchema } from './types.js';
|
|
5
5
|
import type { BlogContent } from './types.js';
|
|
6
6
|
|
|
@@ -123,7 +123,7 @@ Define steps in `steps.ts` with schemas:
|
|
|
123
123
|
|
|
124
124
|
```typescript
|
|
125
125
|
import { step, z } from '@outputai/core';
|
|
126
|
-
import { generateText, aiSdk } from '@outputai/llm';
|
|
126
|
+
import { generateText, aiSdk } from '@outputai/core/llm';
|
|
127
127
|
|
|
128
128
|
export const myStep = step( {
|
|
129
129
|
name: 'my_step',
|
|
@@ -153,7 +153,7 @@ Define evaluators in `evaluators.ts`:
|
|
|
153
153
|
|
|
154
154
|
```typescript
|
|
155
155
|
import { evaluator, z, EvaluationVerdictResult } from '@outputai/core';
|
|
156
|
-
import { generateText, aiSdk } from '@outputai/llm';
|
|
156
|
+
import { generateText, aiSdk } from '@outputai/core/llm';
|
|
157
157
|
|
|
158
158
|
export const evaluateQuality = evaluator( {
|
|
159
159
|
name: 'evaluate_quality',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { evaluator, z, EvaluationVerdictResult } from '@outputai/core';
|
|
2
|
-
import { generateText, aiSdk } from '@outputai/llm';
|
|
2
|
+
import { generateText, aiSdk } from '@outputai/core/llm';
|
|
3
3
|
|
|
4
4
|
// TODO: Update the evaluator to assess your workflow's output quality
|
|
5
5
|
export const evaluate{{WorkflowName}} = evaluator( {
|
package/dist/types/cost.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface TokenUsage {
|
|
|
13
13
|
cachedInputTokens?: number;
|
|
14
14
|
reasoningTokens?: number;
|
|
15
15
|
}
|
|
16
|
-
import type { LLMGenerationCost, LLMGenerationUsage, LLMUsageEvent } from '@outputai/llm';
|
|
16
|
+
import type { LLMGenerationCost, LLMGenerationUsage, LLMUsageEvent } from '@outputai/core/llm';
|
|
17
17
|
export type LLMUsageLine = LLMUsageEvent['usage'][number];
|
|
18
18
|
export interface HTTPCostEvent {
|
|
19
19
|
type: 'http:request:cost';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import { getEvalWorkflowName } from '@outputai/evals';
|
|
4
3
|
import { resolveWorkflowDir } from '#utils/workflow_dir.js';
|
|
4
|
+
import { getEvalWorkflowName } from '#utils/eval_naming.js';
|
|
5
5
|
const EVAL_WORKFLOW_FILES = ['tests/evals/workflow.ts', 'tests/evals/workflow.js'];
|
|
6
6
|
/**
|
|
7
7
|
* Explain why a `<wf>_eval` workflow isn't registered. When the eval source
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getEvalWorkflowName: (workflowName: string) => string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getEvalWorkflowName } from './eval_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
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { VERDICT, CRITICALITY } from '@outputai/core/evals';
|
|
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 {};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { renderEvalOutput, computeExitCode } from './eval_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
|
+
});
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/cli",
|
|
3
|
-
"version": "0.15.1-next.
|
|
3
|
+
"version": "0.15.1-next.5db73d3.0",
|
|
4
4
|
"description": "CLI for Output.ai workflow generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"output": "./bin/run.js"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"@ai-sdk/anthropic": "4.0.61",
|
|
20
21
|
"@anthropic-ai/claude-agent-sdk": "0.3.281",
|
|
21
22
|
"@hackylabs/deep-redact": "3.0.5",
|
|
22
23
|
"@inquirer/prompts": "8.7.2",
|
|
@@ -35,10 +36,8 @@
|
|
|
35
36
|
"ky": "2.0.2",
|
|
36
37
|
"react": "19.3.0",
|
|
37
38
|
"semver": "7.8.5",
|
|
38
|
-
"undici": "8.
|
|
39
|
-
"@outputai/
|
|
40
|
-
"@outputai/core": "0.15.1-next.4a437bf.0",
|
|
41
|
-
"@outputai/llm": "0.15.1-next.4a437bf.0"
|
|
39
|
+
"undici": "8.10.2",
|
|
40
|
+
"@outputai/core": "0.15.1-next.5db73d3.0"
|
|
42
41
|
},
|
|
43
42
|
"devDependencies": {
|
|
44
43
|
"@types/cli-progress": "3.11.6",
|