@output.ai/cli 0.7.11-dev.pr263-8f8e94a → 0.7.12-dev.pr263-5d2eaa9
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/README.md +1 -1
- package/dist/api/generated/api.d.ts +1 -1
- package/dist/api/generated/api.js +1 -1
- package/dist/generated/sdk_versions.json +2 -2
- package/dist/services/messages.js +2 -2
- package/dist/templates/project/README.md.template +3 -3
- package/dist/templates/project/src/shared/clients/jina.ts.template +30 -0
- package/dist/templates/project/src/shared/utils/url.ts.template +15 -0
- package/dist/templates/project/src/workflows/blog_evaluator/evaluators.ts.template +33 -0
- package/dist/templates/project/src/workflows/blog_evaluator/prompts/signal_noise@v1.prompt.template +28 -0
- package/dist/templates/project/src/workflows/blog_evaluator/scenarios/paulgraham_hwh.json.template +3 -0
- package/dist/templates/project/src/workflows/blog_evaluator/steps.ts.template +27 -0
- package/dist/templates/project/src/workflows/blog_evaluator/types.ts.template +24 -0
- package/dist/templates/project/src/workflows/blog_evaluator/utils.ts.template +12 -0
- package/dist/templates/project/src/workflows/blog_evaluator/workflow.ts.template +25 -0
- package/package.json +5 -7
- package/dist/templates/project/src/shared/clients/pokeapi.ts.template +0 -43
- package/dist/templates/project/src/workflows/poke_battle/evaluators.ts.template +0 -47
- package/dist/templates/project/src/workflows/poke_battle/prompts/evaluate_realism@v1.prompt.template +0 -26
- package/dist/templates/project/src/workflows/poke_battle/prompts/generate_screenplay@v1.prompt.template +0 -20
- package/dist/templates/project/src/workflows/poke_battle/scenarios/pikachu_vs_charmander.json.template +0 -4
- package/dist/templates/project/src/workflows/poke_battle/steps.ts.template +0 -58
- package/dist/templates/project/src/workflows/poke_battle/utils.ts.template +0 -7
- package/dist/templates/project/src/workflows/poke_battle/workflow.ts.template +0 -45
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ cd <project-name>
|
|
|
18
18
|
npx output dev
|
|
19
19
|
|
|
20
20
|
# Run a workflow
|
|
21
|
-
npx output workflow run
|
|
21
|
+
npx output workflow run blog_evaluator --input src/workflows/blog_evaluator/scenarios/paulgraham_hwh.json
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
## Environment Configuration
|
|
@@ -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 blog_evaluator --input src/workflows/blog_evaluator/scenarios/paulgraham_hwh.json',
|
|
181
181
|
note: 'Execute in a new terminal after services are running'
|
|
182
182
|
}, {
|
|
183
183
|
step: 'Monitor workflows',
|
|
@@ -321,7 +321,7 @@ ${createSectionHeader('RUN A WORKFLOW', '🚀')}
|
|
|
321
321
|
|
|
322
322
|
${ux.colorize('white', 'In a new terminal, execute:')}
|
|
323
323
|
|
|
324
|
-
${formatCommand('npx output workflow run
|
|
324
|
+
${formatCommand('npx output workflow run blog_evaluator --input src/workflows/blog_evaluator/scenarios/paulgraham_hwh.json')}
|
|
325
325
|
|
|
326
326
|
${divider}
|
|
327
327
|
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
```
|
|
13
13
|
src/
|
|
14
14
|
├── shared/ # Shared code across workflows
|
|
15
|
-
│ ├── clients/ # API clients (e.g.,
|
|
15
|
+
│ ├── clients/ # API clients (e.g., jina.ts)
|
|
16
16
|
│ └── utils/ # Utility functions (e.g., string.ts)
|
|
17
17
|
└── workflows/ # Workflow definitions
|
|
18
|
-
└──
|
|
18
|
+
└── blog_evaluator/ # Example workflow
|
|
19
19
|
├── workflow.ts # Main workflow
|
|
20
20
|
├── steps.ts # Workflow steps
|
|
21
21
|
├── evaluators.ts # Quality evaluators
|
|
@@ -81,7 +81,7 @@ This starts:
|
|
|
81
81
|
In a new terminal:
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
|
-
npx output workflow run
|
|
84
|
+
npx output workflow run blog_evaluator --input src/workflows/blog_evaluator/scenarios/paulgraham_hwh.json
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
### 5. Stop Services
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { httpClient } from '@output.ai/http';
|
|
2
|
+
|
|
3
|
+
export interface JinaReaderResponse {
|
|
4
|
+
code: number;
|
|
5
|
+
status: number;
|
|
6
|
+
data: {
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
url: string;
|
|
10
|
+
content: string;
|
|
11
|
+
usage: { tokens: number };
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const jinaClient = httpClient( {
|
|
16
|
+
prefixUrl: 'https://r.jina.ai',
|
|
17
|
+
timeout: 30000
|
|
18
|
+
} );
|
|
19
|
+
|
|
20
|
+
export async function fetchBlogContent( url: string ): Promise<JinaReaderResponse> {
|
|
21
|
+
const response = await jinaClient.post( '', {
|
|
22
|
+
json: { url },
|
|
23
|
+
headers: {
|
|
24
|
+
'Accept': 'application/json',
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
'X-Return-Format': 'markdown'
|
|
27
|
+
}
|
|
28
|
+
} );
|
|
29
|
+
return response.json() as Promise<JinaReaderResponse>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function isValidUrl( urlString: string ): boolean {
|
|
2
|
+
try {
|
|
3
|
+
const url = new URL( urlString );
|
|
4
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
5
|
+
} catch {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function validateUrl( urlString: string ): string {
|
|
11
|
+
if ( !isValidUrl( urlString ) ) {
|
|
12
|
+
throw new Error( `Invalid URL: ${urlString}` );
|
|
13
|
+
}
|
|
14
|
+
return urlString;
|
|
15
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { evaluator, z, EvaluationNumberResult } from '@output.ai/core';
|
|
2
|
+
import { generateObject } from '@output.ai/llm';
|
|
3
|
+
import type { BlogContent } from './types.js';
|
|
4
|
+
|
|
5
|
+
const blogContentSchema = z.object( {
|
|
6
|
+
title: z.string(),
|
|
7
|
+
url: z.string(),
|
|
8
|
+
content: z.string(),
|
|
9
|
+
tokenCount: z.number()
|
|
10
|
+
} );
|
|
11
|
+
|
|
12
|
+
export const evaluateSignalToNoise = evaluator( {
|
|
13
|
+
name: 'evaluate_signal_to_noise',
|
|
14
|
+
description: 'Evaluate the signal-to-noise ratio of blog content',
|
|
15
|
+
inputSchema: blogContentSchema,
|
|
16
|
+
fn: async ( input: BlogContent ) => {
|
|
17
|
+
const { result } = await generateObject( {
|
|
18
|
+
prompt: 'signal_noise@v1',
|
|
19
|
+
variables: {
|
|
20
|
+
title: input.title,
|
|
21
|
+
content: input.content
|
|
22
|
+
},
|
|
23
|
+
schema: z.object( {
|
|
24
|
+
score: z.number().min( 0 ).max( 100 ).describe( 'Signal-to-noise score 0-100' )
|
|
25
|
+
} )
|
|
26
|
+
} );
|
|
27
|
+
|
|
28
|
+
return new EvaluationNumberResult( {
|
|
29
|
+
value: result.score,
|
|
30
|
+
confidence: 0.85
|
|
31
|
+
} );
|
|
32
|
+
}
|
|
33
|
+
} );
|
package/dist/templates/project/src/workflows/blog_evaluator/prompts/signal_noise@v1.prompt.template
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
provider: anthropic
|
|
3
|
+
model: claude-haiku-4-5
|
|
4
|
+
temperature: 0.3
|
|
5
|
+
maxTokens: 256
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<system>
|
|
9
|
+
You are an expert content analyst. Evaluate blog posts for their signal-to-noise ratio.
|
|
10
|
+
</system>
|
|
11
|
+
|
|
12
|
+
<user>
|
|
13
|
+
Analyze this blog post for signal-to-noise ratio.
|
|
14
|
+
|
|
15
|
+
Title: \{{ title }}
|
|
16
|
+
|
|
17
|
+
Content:
|
|
18
|
+
\{{ content }}
|
|
19
|
+
|
|
20
|
+
Score 0-100 where:
|
|
21
|
+
- 0-20: Mostly filler/noise
|
|
22
|
+
- 21-40: More noise than signal
|
|
23
|
+
- 41-60: Balanced
|
|
24
|
+
- 61-80: Good signal, minimal noise
|
|
25
|
+
- 81-100: Exceptional, dense valuable content
|
|
26
|
+
|
|
27
|
+
Return only the score.
|
|
28
|
+
</user>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { step, z } from '@output.ai/core';
|
|
2
|
+
import { fetchBlogContent } from '../../shared/clients/jina.js';
|
|
3
|
+
|
|
4
|
+
const blogContentSchema = z.object( {
|
|
5
|
+
title: z.string(),
|
|
6
|
+
url: z.string(),
|
|
7
|
+
content: z.string(),
|
|
8
|
+
tokenCount: z.number()
|
|
9
|
+
} );
|
|
10
|
+
|
|
11
|
+
export const fetchContent = step( {
|
|
12
|
+
name: 'fetch_blog_content',
|
|
13
|
+
description: 'Fetch blog content from URL using Jina Reader API',
|
|
14
|
+
inputSchema: z.object( {
|
|
15
|
+
url: z.string().url()
|
|
16
|
+
} ),
|
|
17
|
+
outputSchema: blogContentSchema,
|
|
18
|
+
fn: async ( { url } ) => {
|
|
19
|
+
const response = await fetchBlogContent( url );
|
|
20
|
+
return {
|
|
21
|
+
title: response.data.title,
|
|
22
|
+
url: response.data.url,
|
|
23
|
+
content: response.data.content,
|
|
24
|
+
tokenCount: response.data.usage.tokens
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
} );
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from '@output.ai/core';
|
|
2
|
+
|
|
3
|
+
export const blogContentSchema = z.object( {
|
|
4
|
+
title: z.string(),
|
|
5
|
+
url: z.string(),
|
|
6
|
+
content: z.string(),
|
|
7
|
+
tokenCount: z.number()
|
|
8
|
+
} );
|
|
9
|
+
|
|
10
|
+
export const workflowInputSchema = z.object( {
|
|
11
|
+
url: z.string().url().describe( 'URL of the blog post to evaluate' )
|
|
12
|
+
} );
|
|
13
|
+
|
|
14
|
+
export const workflowOutputSchema = z.object( {
|
|
15
|
+
url: z.string(),
|
|
16
|
+
title: z.string(),
|
|
17
|
+
signalToNoiseScore: z.number().min( 0 ).max( 100 ),
|
|
18
|
+
confidence: z.number().min( 0 ).max( 1 ),
|
|
19
|
+
summary: z.string()
|
|
20
|
+
} );
|
|
21
|
+
|
|
22
|
+
export type BlogContent = z.infer<typeof blogContentSchema>;
|
|
23
|
+
export type WorkflowInput = z.infer<typeof workflowInputSchema>;
|
|
24
|
+
export type WorkflowOutput = z.infer<typeof workflowOutputSchema>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function createWorkflowOutput(
|
|
2
|
+
blogContent: { url: string; title: string },
|
|
3
|
+
score: number
|
|
4
|
+
) {
|
|
5
|
+
return {
|
|
6
|
+
url: blogContent.url,
|
|
7
|
+
title: blogContent.title,
|
|
8
|
+
signalToNoiseScore: score,
|
|
9
|
+
confidence: 0.85,
|
|
10
|
+
summary: `Signal-to-noise score: ${score}/100`
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { workflow, z } from '@output.ai/core';
|
|
2
|
+
import { validateUrl } from '../../shared/utils/url.js';
|
|
3
|
+
import { fetchContent } from './steps.js';
|
|
4
|
+
import { evaluateSignalToNoise } from './evaluators.js';
|
|
5
|
+
import { createWorkflowOutput } from './utils.js';
|
|
6
|
+
import { workflowInputSchema, workflowOutputSchema } from './types.js';
|
|
7
|
+
|
|
8
|
+
export default workflow( {
|
|
9
|
+
name: 'blog_evaluator',
|
|
10
|
+
description: '{{description}}',
|
|
11
|
+
inputSchema: workflowInputSchema,
|
|
12
|
+
outputSchema: workflowOutputSchema,
|
|
13
|
+
fn: async ( input ) => {
|
|
14
|
+
const validatedUrl = validateUrl( input.url );
|
|
15
|
+
const blogContent = await fetchContent( { url: validatedUrl } );
|
|
16
|
+
const evaluation = await evaluateSignalToNoise( blogContent );
|
|
17
|
+
|
|
18
|
+
return createWorkflowOutput( blogContent, evaluation.value );
|
|
19
|
+
},
|
|
20
|
+
options: {
|
|
21
|
+
retry: {
|
|
22
|
+
maximumAttempts: 3
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
} );
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@output.ai/cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.12-dev.pr263-5d2eaa9",
|
|
4
4
|
"description": "CLI for Output.ai workflow generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,9 +24,8 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@anthropic-ai/claude-agent-sdk": "0.1.71",
|
|
26
26
|
"@inquirer/prompts": "7.9.0",
|
|
27
|
-
"@oclif/core": "4.
|
|
28
|
-
"@oclif/plugin-help": "6.2.
|
|
29
|
-
"@oclif/plugin-plugins": "5.4.50",
|
|
27
|
+
"@oclif/core": "4.8.0",
|
|
28
|
+
"@oclif/plugin-help": "6.2.37",
|
|
30
29
|
"@output.ai/http": ">=0.0.1",
|
|
31
30
|
"@output.ai/llm": ">=0.0.1",
|
|
32
31
|
"change-case": "5.4.4",
|
|
@@ -46,7 +45,7 @@
|
|
|
46
45
|
"@types/debug": "4.1.12",
|
|
47
46
|
"@types/handlebars": "4.1.0",
|
|
48
47
|
"copyfiles": "2.4.1",
|
|
49
|
-
"orval": "7.
|
|
48
|
+
"orval": "7.20.0"
|
|
50
49
|
},
|
|
51
50
|
"license": "Apache-2.0",
|
|
52
51
|
"publishConfig": {
|
|
@@ -60,8 +59,7 @@
|
|
|
60
59
|
"dirname": "output",
|
|
61
60
|
"commands": "./dist/commands",
|
|
62
61
|
"plugins": [
|
|
63
|
-
"@oclif/plugin-help"
|
|
64
|
-
"@oclif/plugin-plugins"
|
|
62
|
+
"@oclif/plugin-help"
|
|
65
63
|
],
|
|
66
64
|
"topicSeparator": " ",
|
|
67
65
|
"topics": {
|
|
@@ -1,43 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
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>
|
|
@@ -1,20 +0,0 @@
|
|
|
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>
|
|
@@ -1,58 +0,0 @@
|
|
|
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
|
-
} );
|
|
@@ -1,45 +0,0 @@
|
|
|
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
|
-
} );
|