@outputai/llm 0.11.1-next.90d8cc0.0 → 0.11.1-next.a07f3e4.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/package.json +22 -17
- package/src/agent.js +103 -130
- package/src/ai_provider.js +2 -2
- package/src/ai_sdk_options.js +93 -39
- package/src/consts.js +6 -0
- package/src/generate.js +82 -0
- package/src/index.d.ts +302 -308
- package/src/index.js +3 -4
- package/src/prompt/content.js +70 -0
- package/src/prompt/{escape.js → interpolations.js} +11 -18
- package/src/prompt/loader.js +45 -39
- package/src/prompt/markup/attributes.js +52 -0
- package/src/prompt/markup/nodes.js +71 -0
- package/src/prompt/markup/tokenizer.js +3 -0
- package/src/prompt/markup/tokens.js +26 -0
- package/src/prompt/validations.js +100 -68
- package/src/utils/cost.js +137 -0
- package/src/{prompt/load_content.js → utils/file.js} +12 -15
- package/src/utils/legacy_cost_attribute.js +75 -0
- package/src/utils/models.js +15 -0
- package/src/utils/models_pricing.js +68 -0
- package/src/utils/skills.js +74 -0
- package/src/utils/sources.js +48 -0
- package/src/utils/stream.js +2 -2
- package/src/utils/tools.js +47 -0
- package/src/utils/usage.js +121 -0
- package/src/utils/wrap.js +198 -0
- package/src/validations.js +186 -35
- package/src/agent.spec.js +0 -606
- package/src/ai_model.js +0 -60
- package/src/ai_model.spec.js +0 -259
- package/src/ai_provider.spec.js +0 -217
- package/src/ai_sdk.js +0 -144
- package/src/ai_sdk.spec.js +0 -687
- package/src/ai_sdk_options.spec.js +0 -230
- package/src/cost/fetch_models_pricing.js +0 -60
- package/src/cost/fetch_models_pricing.spec.js +0 -168
- package/src/cost/fixtures/models_api_light.json +0 -675
- package/src/cost/index.js +0 -58
- package/src/cost/index.spec.js +0 -350
- package/src/prompt/block_options.js +0 -58
- package/src/prompt/block_options.spec.js +0 -71
- package/src/prompt/blocks.js +0 -47
- package/src/prompt/blocks.spec.js +0 -63
- package/src/prompt/escape.spec.js +0 -159
- package/src/prompt/load_content.spec.js +0 -83
- package/src/prompt/loader.spec.js +0 -361
- package/src/prompt/loader_validation.spec.js +0 -128
- package/src/prompt/parser.js +0 -16
- package/src/prompt/parser.spec.js +0 -186
- package/src/prompt/prepare_text.js +0 -27
- package/src/prompt/prepare_text.spec.js +0 -141
- package/src/prompt/skill.js +0 -128
- package/src/prompt/skill.spec.js +0 -172
- package/src/prompt/validations.spec.js +0 -641
- package/src/utils/__fixtures__/image_response.json +0 -38
- package/src/utils/__fixtures__/stream_response.json +0 -294
- package/src/utils/__fixtures__/text_response.json +0 -201
- package/src/utils/error_handler.spec.js +0 -230
- package/src/utils/image.spec.js +0 -20
- package/src/utils/message.js +0 -3
- package/src/utils/message.spec.js +0 -29
- package/src/utils/response_wrappers.js +0 -80
- package/src/utils/response_wrappers.spec.js +0 -172
- package/src/utils/source_extraction.js +0 -53
- package/src/utils/source_extraction.spec.js +0 -194
- package/src/utils/stream.spec.js +0 -55
- package/src/utils/trace.js +0 -19
- package/src/utils/trace.spec.js +0 -112
- package/src/validations.spec.js +0 -204
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/llm",
|
|
3
|
-
"version": "0.11.1-next.
|
|
3
|
+
"version": "0.11.1-next.a07f3e4.0",
|
|
4
4
|
"description": "Framework abstraction to interact with LLM models",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -9,32 +9,37 @@
|
|
|
9
9
|
"node": ">=24.15.0"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
|
-
"./src"
|
|
12
|
+
"./src/!(*.spec).js",
|
|
13
|
+
"./src/index.d.ts",
|
|
14
|
+
"./src/prompt/!(*.spec).js",
|
|
15
|
+
"./src/prompt/markup/!(*.spec).js",
|
|
16
|
+
"./src/utils/!(*.spec).js"
|
|
13
17
|
],
|
|
14
18
|
"dependencies": {
|
|
19
|
+
"decimal.js": "10.6.0",
|
|
15
20
|
"entities": "8.0.0",
|
|
16
21
|
"gray-matter": "4.0.3",
|
|
17
22
|
"liquidjs": "10.27.2",
|
|
18
23
|
"undici": "8.9.0",
|
|
19
|
-
"@outputai/core": "0.11.1-next.
|
|
24
|
+
"@outputai/core": "0.11.1-next.a07f3e4.0"
|
|
20
25
|
},
|
|
21
26
|
"devDependencies": {
|
|
22
|
-
"@ai-sdk/amazon-bedrock": "
|
|
23
|
-
"@ai-sdk/anthropic": "
|
|
24
|
-
"@ai-sdk/azure": "
|
|
25
|
-
"@ai-sdk/google-vertex": "
|
|
26
|
-
"@ai-sdk/openai": "
|
|
27
|
-
"@ai-sdk/perplexity": "
|
|
28
|
-
"ai": "
|
|
27
|
+
"@ai-sdk/amazon-bedrock": "5.0.57",
|
|
28
|
+
"@ai-sdk/anthropic": "4.0.39",
|
|
29
|
+
"@ai-sdk/azure": "4.0.43",
|
|
30
|
+
"@ai-sdk/google-vertex": "5.0.54",
|
|
31
|
+
"@ai-sdk/openai": "4.0.42",
|
|
32
|
+
"@ai-sdk/perplexity": "4.0.29",
|
|
33
|
+
"ai": "7.0.66"
|
|
29
34
|
},
|
|
30
35
|
"peerDependencies": {
|
|
31
|
-
"@ai-sdk/amazon-bedrock": ">=
|
|
32
|
-
"@ai-sdk/anthropic": ">=
|
|
33
|
-
"@ai-sdk/azure": ">=
|
|
34
|
-
"@ai-sdk/google-vertex": ">=
|
|
35
|
-
"@ai-sdk/openai": ">=
|
|
36
|
-
"@ai-sdk/perplexity": ">=
|
|
37
|
-
"ai": ">=
|
|
36
|
+
"@ai-sdk/amazon-bedrock": ">=5 <6",
|
|
37
|
+
"@ai-sdk/anthropic": ">=4 <5",
|
|
38
|
+
"@ai-sdk/azure": ">=4 <5",
|
|
39
|
+
"@ai-sdk/google-vertex": ">=5 <6",
|
|
40
|
+
"@ai-sdk/openai": ">=4 <5",
|
|
41
|
+
"@ai-sdk/perplexity": ">=4 <5",
|
|
42
|
+
"ai": ">=7 <8"
|
|
38
43
|
},
|
|
39
44
|
"license": "Apache-2.0",
|
|
40
45
|
"publishConfig": {
|
package/src/agent.js
CHANGED
|
@@ -1,165 +1,138 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Path } from '@outputai/core/sdk/helpers';
|
|
3
|
-
import { ToolLoopAgent as AIToolLoopAgent, stepCountIs } from 'ai';
|
|
1
|
+
import { ToolLoopAgent as AIToolLoopAgent } from 'ai';
|
|
4
2
|
import { loadAiSdkTextOptions } from './ai_sdk_options.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { wrapTextResponse } from './utils/response_wrappers.js';
|
|
8
|
-
import { mapAiError } from './utils/error_handler.js';
|
|
9
|
-
import { ROLE, isRole } from './utils/message.js';
|
|
3
|
+
import { wrapGeneration, wrapStream } from './utils/wrap.js';
|
|
4
|
+
import { Role } from './consts.js';
|
|
10
5
|
import { drainStream } from './utils/stream.js';
|
|
11
|
-
|
|
6
|
+
import { loadPrompt } from './prompt/loader.js';
|
|
7
|
+
import { loadSkills } from './utils/skills.js';
|
|
8
|
+
import * as Validator from './validations.js';
|
|
9
|
+
import { Logger } from '@outputai/core';
|
|
12
10
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
getMessages: () => messages,
|
|
17
|
-
addMessages: newMessages => messages.push( ...newMessages )
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export class Agent extends AIToolLoopAgent {
|
|
11
|
+
export class Agent {
|
|
12
|
+
#agent;
|
|
22
13
|
#prompt;
|
|
23
|
-
#modelId;
|
|
24
|
-
#providerId;
|
|
25
14
|
#initialMessages;
|
|
26
15
|
#store;
|
|
27
16
|
|
|
28
|
-
constructor( {
|
|
29
|
-
|
|
30
|
-
promptDir,
|
|
31
|
-
variables = {},
|
|
32
|
-
skills = [],
|
|
33
|
-
tools: toolsArg,
|
|
34
|
-
stopWhen,
|
|
35
|
-
maxSteps = 10,
|
|
36
|
-
conversationStore,
|
|
37
|
-
...rest
|
|
38
|
-
} ) {
|
|
39
|
-
if ( !prompt ) {
|
|
40
|
-
throw new ValidationError( 'Agent requires a prompt' );
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Must be captured synchronously — Temporal async activity execution
|
|
44
|
-
// breaks the call stack, so Path.resolveInvocationDir() fails if called lazily.
|
|
45
|
-
const resolvedPromptDir = promptDir ?? Path.resolveInvocationDir();
|
|
17
|
+
constructor( args ) {
|
|
18
|
+
const { promptFile, promptObject, promptDir, variables, tools, output, stopWhen, messageStore } = Validator.parseAgentArgs( args );
|
|
46
19
|
|
|
47
|
-
const
|
|
20
|
+
const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
|
|
21
|
+
const skills = loadSkills( prompt );
|
|
48
22
|
|
|
49
|
-
const {
|
|
23
|
+
const { instructions, messages, ...aiOptions } = loadAiSdkTextOptions( { prompt, skills, tools, output, stopWhen } );
|
|
50
24
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
...constructorOptions,
|
|
55
|
-
...( system.length > 0 ? { instructions: system } : {} ),
|
|
56
|
-
...( tools ? { tools } : {} ),
|
|
57
|
-
stopWhen: stopWhen ?? stepCountIs( maxSteps ),
|
|
58
|
-
...rest
|
|
25
|
+
this.#agent = new AIToolLoopAgent( {
|
|
26
|
+
...aiOptions,
|
|
27
|
+
...( instructions.length > 0 ? { instructions } : {} )
|
|
59
28
|
} );
|
|
60
29
|
|
|
61
30
|
this.#prompt = prompt;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this.#initialMessages = messages.filter( isRole( ROLE.USER ) );
|
|
67
|
-
this.#store = conversationStore ?? null;
|
|
31
|
+
// `messages` is system-free but may still hold authored <assistant> blocks;
|
|
32
|
+
// seed only <user> turns into each generate()/stream() call.
|
|
33
|
+
this.#initialMessages = messages.filter( m => m.role === Role.USER );
|
|
34
|
+
this.#store = messageStore ?? null;
|
|
68
35
|
}
|
|
69
36
|
|
|
70
|
-
async #
|
|
71
|
-
|
|
72
|
-
return [ ...this.#initialMessages, ...priorMessages, ...userMessages ];
|
|
37
|
+
async #combineWithPreviousMessages( messages ) {
|
|
38
|
+
return [ ...this.#initialMessages, ...( await this.#store?.getMessages() ?? [] ), ...messages ];
|
|
73
39
|
}
|
|
74
40
|
|
|
75
|
-
async #storeMessages(
|
|
41
|
+
async #storeMessages( messages ) {
|
|
76
42
|
if ( this.#store ) {
|
|
77
|
-
await this.#store.addMessages(
|
|
43
|
+
await this.#store.addMessages( messages );
|
|
78
44
|
}
|
|
79
45
|
}
|
|
80
46
|
|
|
81
|
-
async generate(
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
47
|
+
async generate( args ) {
|
|
48
|
+
const { messages, abortSignal, toolChoice } = Validator.parseAgentGenerateArgs( args );
|
|
49
|
+
const combinedMessages = await this.#combineWithPreviousMessages( messages );
|
|
50
|
+
|
|
51
|
+
return wrapGeneration( {
|
|
52
|
+
name: 'Agent.generate',
|
|
53
|
+
prompt: this.#prompt,
|
|
54
|
+
fn: async () => {
|
|
55
|
+
const response = await this.#agent.generate( {
|
|
56
|
+
messages: combinedMessages,
|
|
57
|
+
allowSystemInMessages: true,
|
|
58
|
+
...( abortSignal && { abortSignal } ),
|
|
59
|
+
...( toolChoice && { toolChoice } )
|
|
60
|
+
} );
|
|
61
|
+
if ( response.finishReason !== 'error' ) {
|
|
62
|
+
await this.#storeMessages( messages.concat( response.responseMessages ?? [] ) );
|
|
63
|
+
}
|
|
64
|
+
return response;
|
|
65
|
+
}
|
|
66
|
+
} );
|
|
94
67
|
}
|
|
95
68
|
|
|
96
69
|
/**
|
|
97
70
|
* Generates a completed agent response over streaming transport, invoking `onChunk` as parts arrive.
|
|
98
71
|
*/
|
|
99
|
-
async generateWithStreaming(
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
72
|
+
async generateWithStreaming( args ) {
|
|
73
|
+
const { messages, abortSignal, toolChoice, onChunk } = Validator.parseAgentGenerateWithStreamingArgs( args );
|
|
74
|
+
const combinedMessages = await this.#combineWithPreviousMessages( messages );
|
|
75
|
+
|
|
76
|
+
return wrapGeneration( {
|
|
77
|
+
name: 'Agent.generateWithStreaming',
|
|
78
|
+
prompt: this.#prompt,
|
|
79
|
+
fn: async () => {
|
|
80
|
+
const state = { response: null };
|
|
81
|
+
const stream = await this.#agent.stream( {
|
|
82
|
+
messages: combinedMessages,
|
|
83
|
+
allowSystemInMessages: true,
|
|
84
|
+
...( onChunk && { onChunk } ),
|
|
85
|
+
...( abortSignal && { abortSignal } ),
|
|
86
|
+
...( toolChoice && { toolChoice } ),
|
|
87
|
+
onEnd: res => {
|
|
88
|
+
state.response = res;
|
|
89
|
+
},
|
|
90
|
+
onError: _ => {} // Suppress AI-SDK console printing
|
|
91
|
+
} );
|
|
92
|
+
|
|
93
|
+
await drainStream( stream, abortSignal );
|
|
94
|
+
|
|
95
|
+
if ( !state.response ) {
|
|
96
|
+
throw new Error( 'Agent streaming generation completed without a response.' );
|
|
97
|
+
}
|
|
114
98
|
|
|
115
|
-
|
|
99
|
+
state.response.output = await stream.output;
|
|
100
|
+
if ( state.response.finishReason !== 'error' ) {
|
|
101
|
+
await this.#storeMessages( messages.concat( state.response.responseMessages ?? [] ) );
|
|
102
|
+
}
|
|
116
103
|
|
|
117
|
-
|
|
118
|
-
throw new Error( 'Agent streaming generation completed without a response.' );
|
|
104
|
+
return state.response;
|
|
119
105
|
}
|
|
120
|
-
|
|
121
|
-
state.response.output = await stream.output;
|
|
122
|
-
const wrappedResponse = await wrapTextResponse( {
|
|
123
|
-
traceId,
|
|
124
|
-
providerId: this.#providerId,
|
|
125
|
-
modelId: this.#modelId,
|
|
126
|
-
response: state.response
|
|
127
|
-
} );
|
|
128
|
-
await this.#storeMessages( userMessages, wrappedResponse );
|
|
129
|
-
|
|
130
|
-
return wrappedResponse;
|
|
131
|
-
|
|
132
|
-
} catch ( originalError ) {
|
|
133
|
-
const error = mapAiError( originalError );
|
|
134
|
-
endTraceWithError( { traceId, error } );
|
|
135
|
-
throw error;
|
|
136
|
-
}
|
|
137
|
-
|
|
106
|
+
} );
|
|
138
107
|
}
|
|
139
108
|
|
|
140
|
-
async stream(
|
|
141
|
-
const
|
|
109
|
+
async stream( args ) {
|
|
110
|
+
const { messages, abortSignal, toolChoice, onChunk, onEnd, onError } = Validator.parseAgentStreamArgs( args );
|
|
111
|
+
const combinedMessages = await this.#combineWithPreviousMessages( messages );
|
|
142
112
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
113
|
+
return wrapStream( {
|
|
114
|
+
name: 'Agent.stream',
|
|
115
|
+
prompt: this.#prompt,
|
|
116
|
+
abortSignal,
|
|
117
|
+
fn: ( { onEndHook, onErrorHook } ) => this.#agent.stream( {
|
|
118
|
+
messages: combinedMessages,
|
|
147
119
|
allowSystemInMessages: true,
|
|
148
|
-
...
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
120
|
+
...( onChunk && { onChunk } ),
|
|
121
|
+
...( abortSignal && { abortSignal } ),
|
|
122
|
+
...( toolChoice && { toolChoice } ),
|
|
123
|
+
onEnd: response =>
|
|
124
|
+
onEndHook( response, async parsedResponse => {
|
|
125
|
+
if ( response.finishReason !== 'error' ) {
|
|
126
|
+
await this.#storeMessages( messages.concat( response.responseMessages ?? [] ) )
|
|
127
|
+
.catch( error => Logger.error( 'Agent.stream message store persistence failed', {
|
|
128
|
+
namespace: 'LLM',
|
|
129
|
+
error: error instanceof Error ? error.message : String( error )
|
|
130
|
+
} ) );
|
|
131
|
+
}
|
|
132
|
+
await onEnd?.( parsedResponse );
|
|
133
|
+
} ),
|
|
134
|
+
onError: event => onErrorHook( event, error => onError?.( { ...event, error } ) )
|
|
135
|
+
} )
|
|
136
|
+
} );
|
|
164
137
|
}
|
|
165
138
|
}
|
package/src/ai_provider.js
CHANGED
|
@@ -53,7 +53,7 @@ const registerProviderSchema = z.object( {
|
|
|
53
53
|
* @param {Function} providerFn - Factory function that receives a model id
|
|
54
54
|
* @returns {void}
|
|
55
55
|
*/
|
|
56
|
-
export
|
|
56
|
+
export const registerProvider = ( name, providerFn ) => {
|
|
57
57
|
const result = registerProviderSchema.safeParse( { name, providerFn } );
|
|
58
58
|
if ( !result.success ) {
|
|
59
59
|
throw new ValidationError( `Invalid provider registration: ${z.prettifyError( result.error )}` );
|
|
@@ -65,7 +65,7 @@ export function registerProvider( name, providerFn ) {
|
|
|
65
65
|
);
|
|
66
66
|
}
|
|
67
67
|
registeredProviders[name] = providerFn;
|
|
68
|
-
}
|
|
68
|
+
};
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
71
|
* Return a provider by its name.
|
package/src/ai_sdk_options.js
CHANGED
|
@@ -1,72 +1,126 @@
|
|
|
1
|
-
import { loadImageModel, loadTextModel
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { loadImageModel, loadTextModel } from './utils/models.js';
|
|
2
|
+
import { buildLoadSkillTool, loadPromptTools } from './utils/tools.js';
|
|
3
|
+
import { Role } from './consts.js';
|
|
4
4
|
import { FatalError } from '@outputai/core';
|
|
5
|
+
import { isStepCount } from 'ai';
|
|
6
|
+
|
|
7
|
+
const buildSkillsMessageContent = skills =>
|
|
8
|
+
'Available skills (use load_skill to get full instructions):\n' +
|
|
9
|
+
skills.map( s => `- ${s.name}: ${s.description}` ).join( '\n' );
|
|
10
|
+
|
|
11
|
+
const textPromptConfigKeys = [
|
|
12
|
+
'frequencyPenalty',
|
|
13
|
+
'maxOutputTokens',
|
|
14
|
+
'presencePenalty',
|
|
15
|
+
'providerOptions',
|
|
16
|
+
'seed',
|
|
17
|
+
'stopSequences',
|
|
18
|
+
'temperature',
|
|
19
|
+
'topK',
|
|
20
|
+
'topP'
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const imagePromptConfigKeys = [
|
|
24
|
+
'aspectRatio',
|
|
25
|
+
'maxImagesPerCall',
|
|
26
|
+
'n',
|
|
27
|
+
'providerOptions',
|
|
28
|
+
'seed',
|
|
29
|
+
'size'
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const select = ( keys, target ) =>
|
|
33
|
+
Object.fromEntries( Object.entries( target ).filter( ( [ k, v ] ) => keys.includes( k ) && v !== undefined ) );
|
|
5
34
|
|
|
6
35
|
/**
|
|
7
|
-
*
|
|
36
|
+
* Build options for AI SDK text generation.
|
|
8
37
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* in `messages` — the AI SDK flags system roles inside `messages` as a prompt
|
|
12
|
-
* injection risk, and `system` is the provider-recommended slot.
|
|
38
|
+
* Splits system vs chat messages, applies prompt generation config, and merges tools
|
|
39
|
+
* with a step limit when any tools are present. Always sets `maxRetries` to 0.
|
|
13
40
|
*
|
|
14
|
-
* @param {object}
|
|
15
|
-
* @
|
|
41
|
+
* @param {object} args
|
|
42
|
+
* @param {object} args.prompt - Loaded prompt object
|
|
43
|
+
* @param {object} [args.tools] - Caller tools
|
|
44
|
+
* @param {Skill[]} args.skills - Resolved skills
|
|
45
|
+
* @param {unknown} [args.stopWhen] - Caller stop condition; otherwise prompt `maxSteps` when tools exist
|
|
46
|
+
* @param {unknown} [args.output] - Structured output spec
|
|
47
|
+
* @param {unknown} [args.toolChoice] - Tool choice, set only when tools exist
|
|
48
|
+
* @param {AbortSignal} [args.abortSignal] - Abort signal for the request
|
|
49
|
+
* @returns {object} AI SDK text options
|
|
16
50
|
*/
|
|
17
|
-
export const loadAiSdkTextOptions = prompt => {
|
|
51
|
+
export const loadAiSdkTextOptions = ( { prompt, tools, skills, stopWhen, output, toolChoice, abortSignal } ) => {
|
|
18
52
|
if ( prompt.messages.length === 0 ) {
|
|
19
53
|
throw new FatalError( `Prompt "${prompt.name}" has no chat-style messages. Add role-tagged blocks like <system> or <user>.` );
|
|
20
54
|
}
|
|
21
|
-
const isSystem = isRole( ROLE.SYSTEM );
|
|
22
|
-
const resolvedMessages = resolveMessageProviderOptions( prompt );
|
|
23
55
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
system: resolvedMessages.filter( isSystem ),
|
|
27
|
-
messages: resolvedMessages.filter( message => !isSystem( message ) ),
|
|
28
|
-
providerOptions: prompt.config.providerOptions
|
|
29
|
-
};
|
|
56
|
+
const instructions = prompt.messages.filter( m => m.role === Role.SYSTEM );
|
|
57
|
+
const messages = prompt.messages.filter( m => m.role !== Role.SYSTEM );
|
|
30
58
|
|
|
31
|
-
if (
|
|
32
|
-
|
|
59
|
+
if ( skills.length > 0 ) {
|
|
60
|
+
const skillsMessageContent = buildSkillsMessageContent( skills );
|
|
61
|
+
if ( instructions.length > 0 ) {
|
|
62
|
+
instructions[0] = { ...instructions[0], content: `${instructions[0].content}\n\n${skillsMessageContent}` };
|
|
63
|
+
} else {
|
|
64
|
+
instructions.push( { role: Role.SYSTEM, content: skillsMessageContent } );
|
|
65
|
+
}
|
|
33
66
|
}
|
|
34
67
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
68
|
+
const options = {
|
|
69
|
+
allowSystemInMessages: true,
|
|
70
|
+
maxRetries: 0,
|
|
71
|
+
model: loadTextModel( prompt ),
|
|
72
|
+
instructions,
|
|
73
|
+
messages,
|
|
74
|
+
...( output && { output } ),
|
|
75
|
+
...( abortSignal && { abortSignal } ),
|
|
76
|
+
...( stopWhen && { stopWhen } ),
|
|
77
|
+
...select( textPromptConfigKeys, prompt.config )
|
|
78
|
+
};
|
|
38
79
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
80
|
+
// Tools parsing
|
|
81
|
+
const promptTools = loadPromptTools( prompt );
|
|
82
|
+
const skillsTools = skills.length > 0 ? { load_skill: buildLoadSkillTool( skills ) } : {};
|
|
83
|
+
const mergedTools = { ...promptTools, ...tools, ...skillsTools };
|
|
84
|
+
if ( Object.keys( mergedTools ).length > 0 ) {
|
|
85
|
+
options.tools = mergedTools;
|
|
86
|
+
if ( toolChoice ) {
|
|
87
|
+
options.toolChoice = toolChoice;
|
|
88
|
+
}
|
|
89
|
+
if ( !options.stopWhen ) {
|
|
90
|
+
options.stopWhen = isStepCount( prompt.config.maxSteps );
|
|
91
|
+
}
|
|
42
92
|
}
|
|
43
93
|
|
|
44
94
|
return options;
|
|
45
95
|
};
|
|
46
96
|
|
|
47
97
|
/**
|
|
48
|
-
*
|
|
98
|
+
* Build options for AI SDK image generation.
|
|
99
|
+
*
|
|
100
|
+
* Uses prompt instructions (with optional source images and mask) and prompt-owned
|
|
101
|
+
* image config (`n`, `size`, `aspectRatio`, `seed`, `maxImagesPerCall`, `providerOptions`).
|
|
102
|
+
* Always sets `maxRetries` to 0.
|
|
49
103
|
*
|
|
50
|
-
* @param {object}
|
|
51
|
-
* @
|
|
104
|
+
* @param {object} args
|
|
105
|
+
* @param {object} args.prompt - Loaded prompt object
|
|
106
|
+
* @param {unknown} [args.images] - Source images for image-to-image
|
|
107
|
+
* @param {unknown} [args.mask] - Inpainting mask
|
|
108
|
+
* @param {AbortSignal} [args.abortSignal] - Abort signal for the request
|
|
109
|
+
* @returns {object} AI SDK image options
|
|
52
110
|
*/
|
|
53
|
-
export const loadAiSdkImageOptions = ( { prompt, images, mask } ) => {
|
|
111
|
+
export const loadAiSdkImageOptions = ( { prompt, images, mask, abortSignal } ) => {
|
|
54
112
|
if ( !prompt.instructions ) {
|
|
55
113
|
throw new FatalError( `Prompt "${prompt.name}" has no instructions. Image prompts must use plain instructions.` );
|
|
56
114
|
}
|
|
57
|
-
|
|
115
|
+
return {
|
|
116
|
+
maxRetries: 0,
|
|
58
117
|
model: loadImageModel( prompt ),
|
|
59
118
|
prompt: ( images || mask ) ? {
|
|
60
119
|
text: prompt.instructions,
|
|
61
120
|
...( images && { images } ),
|
|
62
121
|
...( mask && { mask } )
|
|
63
122
|
} : prompt.instructions,
|
|
64
|
-
|
|
123
|
+
...( abortSignal && { abortSignal } ),
|
|
124
|
+
...select( imagePromptConfigKeys, prompt.config )
|
|
65
125
|
};
|
|
66
|
-
for ( const key of [ 'n', 'maxImagesPerCall', 'size', 'aspectRatio', 'seed' ] ) {
|
|
67
|
-
if ( prompt.config[key] !== undefined ) {
|
|
68
|
-
options[key] = prompt.config[key];
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return options;
|
|
72
126
|
};
|
package/src/consts.js
ADDED
package/src/generate.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as AI from 'ai';
|
|
2
|
+
import { loadPrompt } from './prompt/loader.js';
|
|
3
|
+
import { wrapGeneration, wrapStream } from './utils/wrap.js';
|
|
4
|
+
import { loadAiSdkTextOptions, loadAiSdkImageOptions } from './ai_sdk_options.js';
|
|
5
|
+
import { drainStream } from './utils/stream.js';
|
|
6
|
+
import { loadSkills } from './utils/skills.js';
|
|
7
|
+
import * as Validator from './validations.js';
|
|
8
|
+
|
|
9
|
+
export const generateText = async args => {
|
|
10
|
+
const { promptFile, promptObject, variables, promptDir, ...aiOptions } = Validator.parseGenerateTextArgs( args );
|
|
11
|
+
const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
|
|
12
|
+
const skills = loadSkills( prompt );
|
|
13
|
+
|
|
14
|
+
return wrapGeneration( {
|
|
15
|
+
name: 'generateText',
|
|
16
|
+
prompt,
|
|
17
|
+
fn: () => AI.generateText( loadAiSdkTextOptions( { prompt, skills, ...aiOptions } ) )
|
|
18
|
+
} );
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const streamText = args => {
|
|
22
|
+
const { promptFile, promptObject, variables, promptDir, onEnd, onError, onChunk, ...aiOptions } = Validator.parseStreamTextArgs( args );
|
|
23
|
+
const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
|
|
24
|
+
const skills = loadSkills( prompt );
|
|
25
|
+
|
|
26
|
+
return wrapStream( {
|
|
27
|
+
name: 'streamText',
|
|
28
|
+
prompt,
|
|
29
|
+
abortSignal: aiOptions.abortSignal,
|
|
30
|
+
fn: ( { onEndHook, onErrorHook } ) => AI.streamText( {
|
|
31
|
+
...loadAiSdkTextOptions( { prompt, skills, ...aiOptions } ),
|
|
32
|
+
...( onChunk && { onChunk } ),
|
|
33
|
+
onEnd: response => onEndHook( response, onEnd ),
|
|
34
|
+
onError: event => onErrorHook( event, error => onError?.( { ...event, error } ) )
|
|
35
|
+
} )
|
|
36
|
+
} );
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generates a completed text response over streaming transport, invoking `onChunk` as parts arrive.
|
|
41
|
+
*/
|
|
42
|
+
export const generateTextWithStreaming = async args => {
|
|
43
|
+
const { promptFile, promptObject, variables, promptDir, onChunk, ...aiOptions } = Validator.parseGenerateTextWithStreamingArgs( args );
|
|
44
|
+
const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
|
|
45
|
+
const skills = loadSkills( prompt );
|
|
46
|
+
|
|
47
|
+
return wrapGeneration( {
|
|
48
|
+
name: 'generateTextWithStreaming',
|
|
49
|
+
prompt,
|
|
50
|
+
fn: async () => {
|
|
51
|
+
const state = { response: null };
|
|
52
|
+
const stream = AI.streamText( {
|
|
53
|
+
...loadAiSdkTextOptions( { prompt, skills, ...aiOptions } ),
|
|
54
|
+
...( onChunk && { onChunk } ),
|
|
55
|
+
onEnd: res => {
|
|
56
|
+
state.response = res;
|
|
57
|
+
},
|
|
58
|
+
onError: _ => {} // Suppress AI-SDK console printing
|
|
59
|
+
} );
|
|
60
|
+
|
|
61
|
+
await drainStream( stream, aiOptions.abortSignal );
|
|
62
|
+
|
|
63
|
+
if ( !state.response ) {
|
|
64
|
+
throw new Error( 'Streaming generation completed without a response.' );
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
state.response.output = await stream.output;
|
|
68
|
+
return state.response;
|
|
69
|
+
}
|
|
70
|
+
} );
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const generateImage = async args => {
|
|
74
|
+
const { promptFile, promptObject, promptDir, variables, ...aiOptions } = Validator.parseGenerateImageArgs( args );
|
|
75
|
+
const prompt = promptObject ?? loadPrompt( promptFile, variables, promptDir );
|
|
76
|
+
|
|
77
|
+
return wrapGeneration( {
|
|
78
|
+
name: 'generateImage',
|
|
79
|
+
prompt,
|
|
80
|
+
fn: () => AI.generateImage( loadAiSdkImageOptions( { prompt, ...aiOptions } ) )
|
|
81
|
+
} );
|
|
82
|
+
};
|