@output.ai/cli 0.7.9 → 0.7.11-dev.pr263-8f8e94a
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/generated/sdk_versions.json +2 -2
- package/dist/services/messages.js +1 -1
- package/dist/templates/agent_instructions/dotoutputai/AGENTS.md.template +1 -1
- package/dist/templates/project/README.md.template +38 -1
- package/dist/templates/project/src/shared/clients/pokeapi.ts.template +43 -0
- package/dist/templates/project/src/shared/utils/string.ts.template +3 -0
- package/dist/templates/project/src/workflows/poke_battle/evaluators.ts.template +47 -0
- package/dist/templates/project/src/workflows/poke_battle/prompts/evaluate_realism@v1.prompt.template +26 -0
- package/dist/templates/project/src/workflows/poke_battle/prompts/generate_screenplay@v1.prompt.template +20 -0
- package/dist/templates/project/src/workflows/poke_battle/scenarios/pikachu_vs_charmander.json.template +4 -0
- package/dist/templates/project/src/workflows/poke_battle/steps.ts.template +58 -0
- package/dist/templates/project/src/workflows/poke_battle/utils.ts.template +7 -0
- package/dist/templates/project/src/workflows/poke_battle/workflow.ts.template +45 -0
- package/dist/templates/workflow/README.md.template +96 -75
- package/dist/templates/workflow/evaluators.ts.template +23 -0
- package/package.json +1 -1
- package/dist/templates/project/src/workflows/example_question/prompts/answer_question@v1.prompt.template +0 -13
- package/dist/templates/project/src/workflows/example_question/scenarios/question_ada_lovelace.json.template +0 -3
- package/dist/templates/project/src/workflows/example_question/steps.ts.template +0 -16
- package/dist/templates/project/src/workflows/example_question/workflow.ts.template +0 -22
|
@@ -177,7 +177,7 @@ export const getProjectSuccessMessage = (folderName, installSuccess, envConfigur
|
|
|
177
177
|
note: 'Launches Temporal, Redis, PostgreSQL, API, Worker, and UI'
|
|
178
178
|
}, {
|
|
179
179
|
step: 'Run example workflow',
|
|
180
|
-
command: 'npx output workflow run
|
|
180
|
+
command: 'npx output workflow run poke_battle --input src/workflows/poke_battle/scenarios/pikachu_vs_charmander.json',
|
|
181
181
|
note: 'Execute in a new terminal after services are running'
|
|
182
182
|
}, {
|
|
183
183
|
step: 'Monitor workflows',
|
|
@@ -186,7 +186,7 @@ export default workflow({
|
|
|
186
186
|
});
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
-
**Allowed imports**: steps.ts, evaluators.ts,
|
|
189
|
+
**Allowed imports**: steps.ts, evaluators.ts, ../../shared/steps/*.ts, types.ts, consts.ts, utils.ts
|
|
190
190
|
|
|
191
191
|
**Forbidden in workflows**: Direct API calls, Math.random(), Date.now(), dynamic imports
|
|
192
192
|
|
|
@@ -7,6 +7,43 @@
|
|
|
7
7
|
- Node.js >= 24.3
|
|
8
8
|
- Docker and Docker Compose (for local development)
|
|
9
9
|
|
|
10
|
+
## Project Structure
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
src/
|
|
14
|
+
├── shared/ # Shared code across workflows
|
|
15
|
+
│ ├── clients/ # API clients (e.g., pokeapi.ts)
|
|
16
|
+
│ └── utils/ # Utility functions (e.g., string.ts)
|
|
17
|
+
└── workflows/ # Workflow definitions
|
|
18
|
+
└── poke_battle/ # Example workflow
|
|
19
|
+
├── workflow.ts # Main workflow
|
|
20
|
+
├── steps.ts # Workflow steps
|
|
21
|
+
├── evaluators.ts # Quality evaluators
|
|
22
|
+
├── utils.ts # Local utilities
|
|
23
|
+
├── prompts/ # LLM prompts
|
|
24
|
+
└── scenarios/ # Test scenarios
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Shared Directory
|
|
28
|
+
|
|
29
|
+
The `src/shared/` directory contains code shared across multiple workflows:
|
|
30
|
+
|
|
31
|
+
- **`shared/clients/`** - API clients using `@output.ai/http` for external services
|
|
32
|
+
- **`shared/utils/`** - Helper functions and utilities
|
|
33
|
+
|
|
34
|
+
### Import Rules
|
|
35
|
+
|
|
36
|
+
**Workflows** can import from:
|
|
37
|
+
- Local steps, evaluators, and utilities
|
|
38
|
+
- Shared steps, evaluators, clients, and utilities
|
|
39
|
+
|
|
40
|
+
**Steps and Evaluators** can import from:
|
|
41
|
+
- Local utilities and clients
|
|
42
|
+
- Shared utilities and clients
|
|
43
|
+
|
|
44
|
+
**Steps and Evaluators cannot** import from:
|
|
45
|
+
- Other steps or evaluators (Temporal activity isolation)
|
|
46
|
+
|
|
10
47
|
## Getting Started
|
|
11
48
|
|
|
12
49
|
### 1. Install Dependencies
|
|
@@ -44,7 +81,7 @@ This starts:
|
|
|
44
81
|
In a new terminal:
|
|
45
82
|
|
|
46
83
|
```bash
|
|
47
|
-
npx output workflow run
|
|
84
|
+
npx output workflow run poke_battle --input src/workflows/poke_battle/scenarios/pikachu_vs_charmander.json
|
|
48
85
|
```
|
|
49
86
|
|
|
50
87
|
### 5. Stop Services
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { httpClient, HttpClientOptions } from '@output.ai/http';
|
|
2
|
+
|
|
3
|
+
export interface Pokemon {
|
|
4
|
+
name: string;
|
|
5
|
+
types: Array<{ type: { name: string } }>;
|
|
6
|
+
abilities: Array<{ ability: { name: string }; is_hidden: boolean }>;
|
|
7
|
+
stats: Array<{ stat: { name: string }; base_stat: number }>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const basePokeApiClient = httpClient( {
|
|
11
|
+
prefixUrl: 'https://pokeapi.co/api/v2'
|
|
12
|
+
} );
|
|
13
|
+
|
|
14
|
+
const pokemonClient = basePokeApiClient.extend( options => ( {
|
|
15
|
+
prefixUrl: `${options.prefixUrl}/pokemon`,
|
|
16
|
+
timeout: 10000
|
|
17
|
+
} ) as HttpClientOptions );
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fetch Pokemon data from PokeAPI, extracting only the relevant fields.
|
|
21
|
+
* The full API response is huge (sprites, all moves, etc.), so we filter
|
|
22
|
+
* to just what's needed for the battle screenplay.
|
|
23
|
+
*/
|
|
24
|
+
export async function getPokemon( name: string ): Promise<Pokemon> {
|
|
25
|
+
const response = await pokemonClient.get( name );
|
|
26
|
+
const data = await response.json() as Record<string, unknown>;
|
|
27
|
+
|
|
28
|
+
// Extract only the fields we need to avoid sending huge payloads to LLM
|
|
29
|
+
return {
|
|
30
|
+
name: data.name as string,
|
|
31
|
+
types: ( data.types as Array<{ type: { name: string } }> ).map( t => ( {
|
|
32
|
+
type: { name: t.type.name }
|
|
33
|
+
} ) ),
|
|
34
|
+
abilities: ( data.abilities as Array<{ ability: { name: string }; is_hidden: boolean }> ).map( a => ( {
|
|
35
|
+
ability: { name: a.ability.name },
|
|
36
|
+
is_hidden: a.is_hidden
|
|
37
|
+
} ) ),
|
|
38
|
+
stats: ( data.stats as Array<{ stat: { name: string }; base_stat: number }> ).map( s => ( {
|
|
39
|
+
stat: { name: s.stat.name },
|
|
40
|
+
base_stat: s.base_stat
|
|
41
|
+
} ) )
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { evaluator, z, EvaluationNumberResult } from '@output.ai/core';
|
|
2
|
+
import { generateObject } from '@output.ai/llm';
|
|
3
|
+
import type { Pokemon } from '../../shared/clients/pokeapi.js';
|
|
4
|
+
|
|
5
|
+
const pokemonSchema = z.object( {
|
|
6
|
+
name: z.string(),
|
|
7
|
+
types: z.array( z.object( {
|
|
8
|
+
type: z.object( { name: z.string() } )
|
|
9
|
+
} ) ),
|
|
10
|
+
abilities: z.array( z.object( {
|
|
11
|
+
ability: z.object( { name: z.string() } ),
|
|
12
|
+
is_hidden: z.boolean()
|
|
13
|
+
} ) ),
|
|
14
|
+
stats: z.array( z.object( {
|
|
15
|
+
stat: z.object( { name: z.string() } ),
|
|
16
|
+
base_stat: z.number()
|
|
17
|
+
} ) )
|
|
18
|
+
} );
|
|
19
|
+
|
|
20
|
+
export const evaluateBattleRealism = evaluator( {
|
|
21
|
+
name: 'evaluate_battle_realism',
|
|
22
|
+
description: 'Evaluate how realistic the Pokemon battle screenplay is',
|
|
23
|
+
inputSchema: z.object( {
|
|
24
|
+
screenplay: z.string(),
|
|
25
|
+
pokemon1: pokemonSchema,
|
|
26
|
+
pokemon2: pokemonSchema
|
|
27
|
+
} ),
|
|
28
|
+
fn: async ( input: { screenplay: string; pokemon1: Pokemon; pokemon2: Pokemon } ) => {
|
|
29
|
+
const { result } = await generateObject( {
|
|
30
|
+
prompt: 'evaluate_realism@v1',
|
|
31
|
+
variables: {
|
|
32
|
+
pokemon1Name: input.pokemon1.name,
|
|
33
|
+
pokemon1Description: JSON.stringify( input.pokemon1 ),
|
|
34
|
+
pokemon2Name: input.pokemon2.name,
|
|
35
|
+
pokemon2Description: JSON.stringify( input.pokemon2 ),
|
|
36
|
+
screenplay: input.screenplay
|
|
37
|
+
},
|
|
38
|
+
schema: z.object( {
|
|
39
|
+
score: z.number().min( 0 ).max( 100 ).describe( 'Realism score 0-100' )
|
|
40
|
+
} )
|
|
41
|
+
} );
|
|
42
|
+
return new EvaluationNumberResult( {
|
|
43
|
+
value: result.score,
|
|
44
|
+
confidence: 0.9
|
|
45
|
+
} );
|
|
46
|
+
}
|
|
47
|
+
} );
|
package/dist/templates/project/src/workflows/poke_battle/prompts/evaluate_realism@v1.prompt.template
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
provider: anthropic
|
|
3
|
+
model: claude-haiku-4-5
|
|
4
|
+
temperature: 0.7
|
|
5
|
+
maxTokens: 8192
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<user>
|
|
9
|
+
Evaluate this Pokemon battle screenplay for realism.
|
|
10
|
+
|
|
11
|
+
Consider:
|
|
12
|
+
- Are the moves/abilities used consistent with each Pokemon's actual abilities?
|
|
13
|
+
- Is the battle outcome realistic given their types and stats?
|
|
14
|
+
- Does the pacing feel like a real battle?
|
|
15
|
+
|
|
16
|
+
\{{ pokemon1Name }}'s Stat Sheet:
|
|
17
|
+
|
|
18
|
+
\{{ pokemon1Description }}
|
|
19
|
+
|
|
20
|
+
\{{ pokemon2Name }}'s Stat Sheet:
|
|
21
|
+
|
|
22
|
+
\{{ pokemon2Description }}
|
|
23
|
+
|
|
24
|
+
Screenplay:
|
|
25
|
+
\{{ screenplay }}
|
|
26
|
+
</user>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
provider: anthropic
|
|
3
|
+
model: claude-haiku-4-5
|
|
4
|
+
temperature: 0.7
|
|
5
|
+
maxTokens: 8192
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<user>
|
|
9
|
+
Write a dramatic 5-minute screenplay of a Pokemon battle between \{{ pokemon1Name }} and \{{ pokemon2Name }}.
|
|
10
|
+
|
|
11
|
+
\{{ pokemon1Name }}'s Stat Sheet:
|
|
12
|
+
|
|
13
|
+
\{{ pokemon1Description }}
|
|
14
|
+
|
|
15
|
+
\{{ pokemon2Name }}'s Stat Sheet:
|
|
16
|
+
|
|
17
|
+
\{{ pokemon2Description }}
|
|
18
|
+
|
|
19
|
+
Include dramatic moments, skill usage that matches their actual abilities, and a decisive outcome based on their type matchups. Format it as a proper screenplay with scene descriptions and dialogue.
|
|
20
|
+
</user>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { step, z } from '@output.ai/core';
|
|
2
|
+
import { generateText } from '@output.ai/llm';
|
|
3
|
+
import { getPokemon, type Pokemon } from '../../shared/clients/pokeapi.js';
|
|
4
|
+
|
|
5
|
+
const pokemonSchema = z.object( {
|
|
6
|
+
name: z.string(),
|
|
7
|
+
types: z.array( z.object( {
|
|
8
|
+
type: z.object( { name: z.string() } )
|
|
9
|
+
} ) ),
|
|
10
|
+
abilities: z.array( z.object( {
|
|
11
|
+
ability: z.object( { name: z.string() } ),
|
|
12
|
+
is_hidden: z.boolean()
|
|
13
|
+
} ) ),
|
|
14
|
+
stats: z.array( z.object( {
|
|
15
|
+
stat: z.object( { name: z.string() } ),
|
|
16
|
+
base_stat: z.number()
|
|
17
|
+
} ) )
|
|
18
|
+
} );
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Step to fetch Pokemon data and generate a battle screenplay.
|
|
22
|
+
* HTTP clients must be used inside steps (activities), not directly in workflows,
|
|
23
|
+
* because workflows run in a sandboxed environment without Node.js APIs.
|
|
24
|
+
*/
|
|
25
|
+
export const generateScreenplay = step( {
|
|
26
|
+
name: 'generate_screenplay',
|
|
27
|
+
description: 'Fetch Pokemon data and generate a dramatic 5-minute battle screenplay',
|
|
28
|
+
inputSchema: z.object( {
|
|
29
|
+
pokemon1Name: z.string().describe( 'Name of the first Pokemon' ),
|
|
30
|
+
pokemon2Name: z.string().describe( 'Name of the second Pokemon' )
|
|
31
|
+
} ),
|
|
32
|
+
outputSchema: z.object( {
|
|
33
|
+
screenplay: z.string(),
|
|
34
|
+
pokemon1: pokemonSchema,
|
|
35
|
+
pokemon2: pokemonSchema
|
|
36
|
+
} ),
|
|
37
|
+
fn: async ( input: { pokemon1Name: string; pokemon2Name: string } ) => {
|
|
38
|
+
// Fetch Pokemon data inside the step (activities can use HTTP clients)
|
|
39
|
+
const pokemon1 = await getPokemon( input.pokemon1Name );
|
|
40
|
+
const pokemon2 = await getPokemon( input.pokemon2Name );
|
|
41
|
+
|
|
42
|
+
const { result } = await generateText( {
|
|
43
|
+
prompt: 'generate_screenplay@v1',
|
|
44
|
+
variables: {
|
|
45
|
+
pokemon1Name: pokemon1.name,
|
|
46
|
+
pokemon2Name: pokemon2.name,
|
|
47
|
+
pokemon1Description: JSON.stringify( pokemon1 ),
|
|
48
|
+
pokemon2Description: JSON.stringify( pokemon2 )
|
|
49
|
+
}
|
|
50
|
+
} );
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
screenplay: result,
|
|
54
|
+
pokemon1: pokemon1 as Pokemon,
|
|
55
|
+
pokemon2: pokemon2 as Pokemon
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
} );
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { workflow, z } from '@output.ai/core';
|
|
2
|
+
import { lowercase } from '../../shared/utils/string.js';
|
|
3
|
+
import { generateScreenplay } from './steps.js';
|
|
4
|
+
import { evaluateBattleRealism } from './evaluators.js';
|
|
5
|
+
import { createWorkflowOutput } from './utils.js';
|
|
6
|
+
|
|
7
|
+
export default workflow( {
|
|
8
|
+
name: 'poke_battle',
|
|
9
|
+
description: '{{description}}',
|
|
10
|
+
inputSchema: z.object( {
|
|
11
|
+
pokemon1Name: z.string().describe( 'Name of the first Pokemon' ),
|
|
12
|
+
pokemon2Name: z.string().describe( 'Name of the second Pokemon' )
|
|
13
|
+
} ),
|
|
14
|
+
outputSchema: z.object( {
|
|
15
|
+
screenplay: z.string().describe( 'A 5-minute battle screenplay' ),
|
|
16
|
+
confidenceScore: z.number().describe( 'Realism confidence score 0-100' ),
|
|
17
|
+
summary: z.string().describe( 'Battle summary with confidence' )
|
|
18
|
+
} ),
|
|
19
|
+
fn: async input => {
|
|
20
|
+
// Use shared util to normalize names
|
|
21
|
+
const name1 = lowercase( input.pokemon1Name );
|
|
22
|
+
const name2 = lowercase( input.pokemon2Name );
|
|
23
|
+
|
|
24
|
+
// Generate the battle screenplay (fetches Pokemon data inside the step)
|
|
25
|
+
const { screenplay, pokemon1, pokemon2 } = await generateScreenplay( {
|
|
26
|
+
pokemon1Name: name1,
|
|
27
|
+
pokemon2Name: name2
|
|
28
|
+
} );
|
|
29
|
+
|
|
30
|
+
// Evaluate realism
|
|
31
|
+
const evaluation = await evaluateBattleRealism( {
|
|
32
|
+
screenplay,
|
|
33
|
+
pokemon1,
|
|
34
|
+
pokemon2
|
|
35
|
+
} );
|
|
36
|
+
|
|
37
|
+
// Use local util to format output
|
|
38
|
+
return createWorkflowOutput( screenplay, evaluation.value );
|
|
39
|
+
},
|
|
40
|
+
options: {
|
|
41
|
+
retry: {
|
|
42
|
+
maximumAttempts: 3
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} );
|
|
@@ -10,8 +10,50 @@ This workflow was generated using the Output SDK CLI. It provides a starting poi
|
|
|
10
10
|
|
|
11
11
|
- `workflow.ts` - Main workflow definition with input/output schemas
|
|
12
12
|
- `steps.ts` - Activity/step definitions with input/output schemas
|
|
13
|
-
- `
|
|
14
|
-
-
|
|
13
|
+
- `evaluators.ts` - Quality evaluators for workflow outputs
|
|
14
|
+
- `prompts/` - LLM prompt templates
|
|
15
|
+
|
|
16
|
+
## File Organization
|
|
17
|
+
|
|
18
|
+
You can organize your workflow files in two ways:
|
|
19
|
+
|
|
20
|
+
**Flat files:**
|
|
21
|
+
```
|
|
22
|
+
workflow/
|
|
23
|
+
├── workflow.ts
|
|
24
|
+
├── steps.ts
|
|
25
|
+
├── evaluators.ts
|
|
26
|
+
└── utils.ts
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Folder-based:**
|
|
30
|
+
```
|
|
31
|
+
workflow/
|
|
32
|
+
├── workflow.ts
|
|
33
|
+
├── steps/
|
|
34
|
+
│ ├── fetch_data.ts
|
|
35
|
+
│ └── process_data.ts
|
|
36
|
+
├── evaluators/
|
|
37
|
+
│ └── quality.ts
|
|
38
|
+
└── utils/
|
|
39
|
+
└── helpers.ts
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Import Rules
|
|
43
|
+
|
|
44
|
+
**Important:** Steps and evaluators are Temporal activities. Activities cannot call other activities.
|
|
45
|
+
|
|
46
|
+
**Steps can import from:**
|
|
47
|
+
- Local utilities (`./utils.ts`, `./utils/*.ts`)
|
|
48
|
+
- Shared utilities (`../../shared/utils/*.ts`)
|
|
49
|
+
- Shared clients (`../../shared/clients/*.ts`)
|
|
50
|
+
|
|
51
|
+
**Steps cannot import from:**
|
|
52
|
+
- Other steps or evaluators (activity isolation)
|
|
53
|
+
- Workflow files
|
|
54
|
+
|
|
55
|
+
**Workflows can import from:**
|
|
56
|
+
- Steps, evaluators, and utilities (local and shared)
|
|
15
57
|
|
|
16
58
|
## Setup
|
|
17
59
|
|
|
@@ -63,121 +105,109 @@ Example:
|
|
|
63
105
|
|
|
64
106
|
### Workflow Structure
|
|
65
107
|
|
|
66
|
-
The workflow follows the
|
|
108
|
+
The workflow follows the Output SDK conventions:
|
|
67
109
|
|
|
68
110
|
```typescript
|
|
69
|
-
import { workflow } from '@output.ai/core';
|
|
70
|
-
import { myStep
|
|
71
|
-
|
|
72
|
-
const inputSchema = {
|
|
73
|
-
type: 'object',
|
|
74
|
-
properties: {
|
|
75
|
-
// Define your input properties
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const outputSchema = {
|
|
80
|
-
type: 'object',
|
|
81
|
-
properties: {
|
|
82
|
-
// Define your output properties
|
|
83
|
-
}
|
|
84
|
-
};
|
|
111
|
+
import { workflow, z } from '@output.ai/core';
|
|
112
|
+
import { myStep } from './steps.js';
|
|
113
|
+
import { evaluateQuality } from './evaluators.js';
|
|
85
114
|
|
|
86
115
|
export default workflow( {
|
|
87
116
|
name: 'workflowName',
|
|
88
117
|
description: 'Workflow description',
|
|
89
|
-
inputSchema,
|
|
90
|
-
outputSchema,
|
|
118
|
+
inputSchema: z.object( { /* ... */ } ),
|
|
119
|
+
outputSchema: z.object( { /* ... */ } ),
|
|
91
120
|
fn: async ( input ) => {
|
|
92
|
-
// Call steps directly
|
|
93
121
|
const result = await myStep( input );
|
|
94
|
-
|
|
122
|
+
const { score } = await evaluateQuality( { input, output: result } );
|
|
123
|
+
return { result, qualityScore: score };
|
|
95
124
|
}
|
|
96
125
|
} );
|
|
97
126
|
```
|
|
98
127
|
|
|
99
128
|
### Adding New Steps
|
|
100
129
|
|
|
101
|
-
|
|
130
|
+
Define steps in `steps.ts` with schemas:
|
|
102
131
|
|
|
103
132
|
```typescript
|
|
104
|
-
import { step } from '@output.ai/core';
|
|
105
|
-
|
|
106
|
-
const inputSchema = {
|
|
107
|
-
type: 'object',
|
|
108
|
-
properties: {
|
|
109
|
-
value: { type: 'number' }
|
|
110
|
-
},
|
|
111
|
-
required: ['value']
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const outputSchema = {
|
|
115
|
-
type: 'object',
|
|
116
|
-
properties: {
|
|
117
|
-
result: { type: 'string' }
|
|
118
|
-
}
|
|
119
|
-
};
|
|
133
|
+
import { step, z } from '@output.ai/core';
|
|
120
134
|
|
|
121
135
|
export const myStep = step( {
|
|
122
136
|
name: 'myStep',
|
|
123
137
|
description: 'Description of what this step does',
|
|
124
|
-
inputSchema
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
138
|
+
inputSchema: z.object( {
|
|
139
|
+
value: z.number()
|
|
140
|
+
} ),
|
|
141
|
+
outputSchema: z.object( {
|
|
142
|
+
result: z.string()
|
|
143
|
+
} ),
|
|
144
|
+
fn: async ( input ) => {
|
|
128
145
|
return { result: `Processed ${input.value}` };
|
|
129
146
|
}
|
|
130
147
|
} );
|
|
131
148
|
```
|
|
132
149
|
|
|
133
|
-
|
|
150
|
+
### Adding Evaluators
|
|
151
|
+
|
|
152
|
+
Define evaluators in `evaluators.ts`:
|
|
134
153
|
|
|
135
154
|
```typescript
|
|
136
|
-
import {
|
|
155
|
+
import { evaluator, z } from '@output.ai/core';
|
|
156
|
+
import { generateText } from '@output.ai/llm';
|
|
137
157
|
|
|
138
|
-
|
|
139
|
-
|
|
158
|
+
export const evaluateQuality = evaluator( {
|
|
159
|
+
name: 'evaluate_quality',
|
|
160
|
+
description: 'Evaluate output quality',
|
|
161
|
+
inputSchema: z.object( {
|
|
162
|
+
input: z.any(),
|
|
163
|
+
output: z.any()
|
|
164
|
+
} ),
|
|
165
|
+
outputSchema: z.object( {
|
|
166
|
+
score: z.number().min( 0 ).max( 100 )
|
|
167
|
+
} ),
|
|
168
|
+
fn: async ( data ) => {
|
|
169
|
+
const { result } = await generateText( {
|
|
170
|
+
prompt: 'evaluate@v1',
|
|
171
|
+
variables: { input: data.input, output: data.output }
|
|
172
|
+
} );
|
|
173
|
+
return { score: parseInt( result, 10 ) };
|
|
174
|
+
}
|
|
175
|
+
} );
|
|
140
176
|
```
|
|
141
177
|
|
|
142
178
|
### Using LLM in Steps
|
|
143
179
|
|
|
144
|
-
The template includes an example of using LLM with prompts:
|
|
145
|
-
|
|
146
180
|
```typescript
|
|
147
181
|
import { generateText } from '@output.ai/llm';
|
|
148
182
|
|
|
149
183
|
export const llmStep = step( {
|
|
150
184
|
name: 'llmStep',
|
|
151
185
|
description: 'Generate text using LLM',
|
|
152
|
-
inputSchema: {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
outputSchema: { type: 'string' },
|
|
159
|
-
fn: async ( input: { userInput: string } ) => {
|
|
160
|
-
const response = await generateText( {
|
|
186
|
+
inputSchema: z.object( {
|
|
187
|
+
userInput: z.string()
|
|
188
|
+
} ),
|
|
189
|
+
outputSchema: z.string(),
|
|
190
|
+
fn: async ( input ) => {
|
|
191
|
+
const { result } = await generateText( {
|
|
161
192
|
prompt: 'prompt@v1',
|
|
162
193
|
variables: { userInput: input.userInput }
|
|
163
194
|
} );
|
|
164
|
-
return
|
|
195
|
+
return result;
|
|
165
196
|
}
|
|
166
197
|
} );
|
|
167
198
|
```
|
|
168
199
|
|
|
169
200
|
### Creating Prompt Templates
|
|
170
201
|
|
|
171
|
-
Create
|
|
202
|
+
Create prompt files in `prompts/` following the pattern:
|
|
172
203
|
- File naming: `promptName@v1.prompt`
|
|
173
|
-
- Include YAML frontmatter with
|
|
204
|
+
- Include YAML frontmatter with model
|
|
174
205
|
- Use LiquidJS syntax for variables: `{{ variableName }}`
|
|
175
206
|
|
|
176
207
|
Example prompt file:
|
|
177
208
|
```
|
|
178
209
|
---
|
|
179
|
-
|
|
180
|
-
model: claude-3-5-sonnet-latest
|
|
210
|
+
model: anthropic/claude-sonnet-4-20250514
|
|
181
211
|
---
|
|
182
212
|
|
|
183
213
|
{{ userInput }}
|
|
@@ -195,19 +225,10 @@ To test your workflow:
|
|
|
195
225
|
|
|
196
226
|
Example execution:
|
|
197
227
|
```bash
|
|
198
|
-
|
|
199
|
-
-H "Content-Type: application/json" \
|
|
200
|
-
-d '{
|
|
201
|
-
"workflowName": "{{workflowName}}",
|
|
202
|
-
"input": {
|
|
203
|
-
"prompt": "Tell me about workflows",
|
|
204
|
-
"data": { "value": 42, "type": "example" }
|
|
205
|
-
}
|
|
206
|
-
}'
|
|
228
|
+
npx output workflow run {{workflowName}} --input '{"prompt": "Hello"}'
|
|
207
229
|
```
|
|
208
230
|
|
|
209
231
|
## Resources
|
|
210
232
|
|
|
211
|
-
- [Output SDK Documentation](https://
|
|
233
|
+
- [Output SDK Documentation](https://docs.output.ai)
|
|
212
234
|
- [Temporal Documentation](https://docs.temporal.io)
|
|
213
|
-
- [AI SDK Documentation](https://sdk.vercel.ai/docs)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { evaluator, z } from '@output.ai/core';
|
|
2
|
+
|
|
3
|
+
// Example evaluator - customize for your workflow
|
|
4
|
+
export const evaluate{{WorkflowName}} = evaluator( {
|
|
5
|
+
name: 'evaluate_{{workflowName}}',
|
|
6
|
+
description: 'Evaluate the quality of {{workflowName}} output',
|
|
7
|
+
inputSchema: z.object( {
|
|
8
|
+
input: z.any(),
|
|
9
|
+
output: z.any()
|
|
10
|
+
} ),
|
|
11
|
+
outputSchema: z.object( {
|
|
12
|
+
score: z.number().min( 0 ).max( 100 ).describe( 'Quality score 0-100' ),
|
|
13
|
+
feedback: z.string().describe( 'Feedback on the output quality' )
|
|
14
|
+
} ),
|
|
15
|
+
fn: async () => {
|
|
16
|
+
// TODO: Implement evaluation logic
|
|
17
|
+
// Use LLM or custom logic to score the output quality
|
|
18
|
+
return {
|
|
19
|
+
score: 100,
|
|
20
|
+
feedback: 'Evaluation not yet implemented'
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
} );
|
package/package.json
CHANGED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
provider: anthropic
|
|
3
|
-
model: claude-opus-4-1-20250805
|
|
4
|
-
temperature: 0.7
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
<system>
|
|
8
|
-
You are a helpful assistant. Answer the user's question concisely and clearly.
|
|
9
|
-
</system>
|
|
10
|
-
|
|
11
|
-
<user>
|
|
12
|
-
Answer the following question: \{{ question }}
|
|
13
|
-
</user>
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { step, z } from '@output.ai/core';
|
|
2
|
-
import { generateText } from '@output.ai/llm';
|
|
3
|
-
|
|
4
|
-
export const answerQuestion = step( {
|
|
5
|
-
name: 'answerQuestion',
|
|
6
|
-
description: 'Answer a question using an LLM',
|
|
7
|
-
inputSchema: z.string(),
|
|
8
|
-
outputSchema: z.string(),
|
|
9
|
-
fn: async question => {
|
|
10
|
-
const { result } = await generateText( {
|
|
11
|
-
prompt: 'answer_question@v1',
|
|
12
|
-
variables: { question }
|
|
13
|
-
} );
|
|
14
|
-
return result;
|
|
15
|
-
}
|
|
16
|
-
} );
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { workflow, z } from '@output.ai/core';
|
|
2
|
-
import { answerQuestion } from './steps.js';
|
|
3
|
-
|
|
4
|
-
export default workflow( {
|
|
5
|
-
name: 'example_question',
|
|
6
|
-
description: '{{description}}',
|
|
7
|
-
inputSchema: z.object( {
|
|
8
|
-
question: z.string().describe( 'A question to answer' )
|
|
9
|
-
} ),
|
|
10
|
-
outputSchema: z.object( {
|
|
11
|
-
answer: z.string().describe( 'The answer to the question' )
|
|
12
|
-
} ),
|
|
13
|
-
fn: async input => {
|
|
14
|
-
const answer = await answerQuestion( input.question );
|
|
15
|
-
return { answer };
|
|
16
|
-
},
|
|
17
|
-
options: {
|
|
18
|
-
retry: {
|
|
19
|
-
maximumAttempts: 3
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
} );
|