@outputai/llm 0.4.1-dev.c0b98d8.0 → 0.4.1-next.6bc541c.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 +3 -2
- package/src/ai_model.js +44 -36
- package/src/ai_model.spec.js +53 -14
- package/src/ai_sdk.js +8 -6
- package/src/ai_sdk.spec.js +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/llm",
|
|
3
|
-
"version": "0.4.1-
|
|
3
|
+
"version": "0.4.1-next.6bc541c.0",
|
|
4
4
|
"description": "Framework abstraction to interact with LLM models",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"entities": "8.0.0",
|
|
24
24
|
"gray-matter": "4.0.3",
|
|
25
25
|
"liquidjs": "10.25.7",
|
|
26
|
-
"
|
|
26
|
+
"undici": "8.1.0",
|
|
27
|
+
"@outputai/core": "0.4.1-next.6bc541c.0"
|
|
27
28
|
},
|
|
28
29
|
"license": "Apache-2.0",
|
|
29
30
|
"publishConfig": {
|
package/src/ai_model.js
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';
|
|
2
|
+
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
3
|
+
import { createAzure } from '@ai-sdk/azure';
|
|
4
|
+
import { createVertex } from '@ai-sdk/google-vertex';
|
|
5
|
+
import { createOpenAI } from '@ai-sdk/openai';
|
|
6
|
+
import { createPerplexity } from '@ai-sdk/perplexity';
|
|
7
7
|
import { ValidationError, z } from '@outputai/core';
|
|
8
|
+
import { Agent, fetch } from 'undici';
|
|
9
|
+
|
|
10
|
+
const dispatcher = new Agent( {
|
|
11
|
+
headersTimeout: 15 * 60 * 1000, // 15 min
|
|
12
|
+
bodyTimeout: 15 * 60 * 1000
|
|
13
|
+
} );
|
|
14
|
+
|
|
15
|
+
const customFetch = ( input, init ) => fetch( input, { dispatcher, ...init } );
|
|
16
|
+
const initProvider = factory => factory( { fetch: customFetch } );
|
|
17
|
+
|
|
18
|
+
export const builtInProviders = {
|
|
19
|
+
azure: initProvider( createAzure ),
|
|
20
|
+
anthropic: initProvider( createAnthropic ),
|
|
21
|
+
openai: initProvider( createOpenAI ),
|
|
22
|
+
vertex: initProvider( createVertex ),
|
|
23
|
+
bedrock: initProvider( createAmazonBedrock ),
|
|
24
|
+
perplexity: initProvider( createPerplexity )
|
|
25
|
+
};
|
|
8
26
|
|
|
9
|
-
export const builtInProviders = { azure, anthropic, openai, vertex, bedrock, perplexity };
|
|
10
27
|
export const providers = { ...builtInProviders };
|
|
11
28
|
|
|
12
29
|
const registerProviderSchema = z.object( {
|
|
@@ -14,20 +31,17 @@ const registerProviderSchema = z.object( {
|
|
|
14
31
|
providerFn: z.function()
|
|
15
32
|
} );
|
|
16
33
|
|
|
34
|
+
const toolConfigSchema = z.record( z.string(), z.unknown() );
|
|
35
|
+
|
|
17
36
|
export function registerProvider( name, providerFn ) {
|
|
18
37
|
const result = registerProviderSchema.safeParse( { name, providerFn } );
|
|
19
38
|
if ( !result.success ) {
|
|
20
|
-
throw new ValidationError(
|
|
21
|
-
`Invalid provider registration: ${z.prettifyError( result.error )}`,
|
|
22
|
-
{ cause: result.error }
|
|
23
|
-
);
|
|
39
|
+
throw new ValidationError( `Invalid provider registration: ${z.prettifyError( result.error )}` );
|
|
24
40
|
}
|
|
25
41
|
providers[name] = providerFn;
|
|
26
42
|
}
|
|
27
43
|
|
|
28
|
-
export
|
|
29
|
-
return Object.keys( providers );
|
|
30
|
-
}
|
|
44
|
+
export const getRegisteredProviders = () => Object.keys( providers );
|
|
31
45
|
|
|
32
46
|
export function loadModel( prompt ) {
|
|
33
47
|
const config = prompt?.config;
|
|
@@ -49,10 +63,8 @@ export function loadModel( prompt ) {
|
|
|
49
63
|
const provider = providers[providerName];
|
|
50
64
|
|
|
51
65
|
if ( !provider ) {
|
|
52
|
-
const
|
|
53
|
-
throw new Error(
|
|
54
|
-
`Invalid provider "${providerName}". Valid providers: ${validProviders}`
|
|
55
|
-
);
|
|
66
|
+
const availableProviders = Object.keys( providers ).join( ', ' );
|
|
67
|
+
throw new Error( `Invalid provider "${providerName}". Valid providers: ${availableProviders}` );
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
return provider( modelName );
|
|
@@ -60,7 +72,12 @@ export function loadModel( prompt ) {
|
|
|
60
72
|
|
|
61
73
|
export function loadTools( prompt ) {
|
|
62
74
|
const config = prompt?.config;
|
|
63
|
-
|
|
75
|
+
|
|
76
|
+
if ( !config ) {
|
|
77
|
+
throw new Error( 'Prompt is missing config object' );
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const { tools: toolsConfig, provider: providerName } = config;
|
|
64
81
|
|
|
65
82
|
if ( !toolsConfig ) {
|
|
66
83
|
return null;
|
|
@@ -84,20 +101,15 @@ export function loadTools( prompt ) {
|
|
|
84
101
|
return null;
|
|
85
102
|
}
|
|
86
103
|
|
|
87
|
-
const providerName = config.provider;
|
|
88
104
|
const provider = providers[providerName];
|
|
89
105
|
|
|
90
106
|
if ( !provider ) {
|
|
91
|
-
const
|
|
92
|
-
throw new Error(
|
|
93
|
-
`Invalid provider "${providerName}". Valid providers: ${validProviders}`
|
|
94
|
-
);
|
|
107
|
+
const availableProviders = Object.keys( providers ).join( ', ' );
|
|
108
|
+
throw new Error( `Invalid provider "${providerName}". Valid providers: ${availableProviders}` );
|
|
95
109
|
}
|
|
96
110
|
|
|
97
111
|
if ( !provider.tools || typeof provider.tools !== 'object' ) {
|
|
98
|
-
throw new Error(
|
|
99
|
-
`Provider "${providerName}" does not support provider-specific tools.`
|
|
100
|
-
);
|
|
112
|
+
throw new Error( `Provider "${providerName}" does not support provider-specific tools.` );
|
|
101
113
|
}
|
|
102
114
|
|
|
103
115
|
const tools = {};
|
|
@@ -109,18 +121,14 @@ export function loadTools( prompt ) {
|
|
|
109
121
|
const availableTools = Object.keys( provider.tools )
|
|
110
122
|
.filter( key => typeof provider.tools[key] === 'function' )
|
|
111
123
|
.join( ', ' );
|
|
124
|
+
const toolsMessage = availableTools ? `Available tools: ${availableTools}` : 'No tools are available';
|
|
112
125
|
|
|
113
|
-
throw new Error(
|
|
114
|
-
`Unknown tool "${toolName}" for provider "${providerName}".` +
|
|
115
|
-
( availableTools ? ` Available tools: ${availableTools}` : '' )
|
|
116
|
-
);
|
|
126
|
+
throw new Error( `Unknown tool "${toolName}" for provider "${providerName}". ${toolsMessage}` );
|
|
117
127
|
}
|
|
118
128
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
`Use "${toolName}: {}" for tools without configuration.`
|
|
123
|
-
);
|
|
129
|
+
const result = toolConfigSchema.safeParse( toolConfig );
|
|
130
|
+
if ( !result.success ) {
|
|
131
|
+
throw new ValidationError( `Invalid config for tool "${toolName}": ${z.prettifyError( result.error )}` );
|
|
124
132
|
}
|
|
125
133
|
|
|
126
134
|
tools[toolName] = toolFactory( toolConfig );
|
package/src/ai_model.spec.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { it, expect, vi, afterEach, describe } from 'vitest';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
3
|
+
const providerFactoryOptions = vi.hoisted( () => ( {} ) );
|
|
4
|
+
const openaiImpl = vi.hoisted( () => vi.fn( model => `openai:${model}` ) );
|
|
5
|
+
const azureImpl = vi.hoisted( () => vi.fn( model => `azure:${model}` ) );
|
|
6
|
+
const anthropicImpl = vi.hoisted( () => vi.fn( model => `anthropic:${model}` ) );
|
|
7
|
+
const bedrockImpl = vi.hoisted( () => vi.fn( model => `bedrock:${model}` ) );
|
|
8
|
+
const perplexityImpl = vi.hoisted( () => vi.fn( model => `perplexity:${model}` ) );
|
|
9
|
+
const vertexImpl = vi.hoisted( () => vi.fn( model => `vertex:${model}` ) );
|
|
8
10
|
|
|
9
11
|
// OpenAI mock with tools support
|
|
10
12
|
vi.mock( '@ai-sdk/openai', () => {
|
|
@@ -12,12 +14,20 @@ vi.mock( '@ai-sdk/openai', () => {
|
|
|
12
14
|
openaiMock.tools = {
|
|
13
15
|
webSearch: ( config = {} ) => ( { type: 'webSearch', config } )
|
|
14
16
|
};
|
|
15
|
-
return {
|
|
17
|
+
return {
|
|
18
|
+
createOpenAI: options => {
|
|
19
|
+
providerFactoryOptions.openai = options;
|
|
20
|
+
return openaiMock;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
16
23
|
} );
|
|
17
24
|
|
|
18
25
|
// Azure mock without tools support
|
|
19
26
|
vi.mock( '@ai-sdk/azure', () => ( {
|
|
20
|
-
|
|
27
|
+
createAzure: options => {
|
|
28
|
+
providerFactoryOptions.azure = options;
|
|
29
|
+
return ( ...values ) => azureImpl( ...values );
|
|
30
|
+
}
|
|
21
31
|
} ) );
|
|
22
32
|
|
|
23
33
|
// Anthropic mock with tools support
|
|
@@ -30,7 +40,12 @@ vi.mock( '@ai-sdk/anthropic', () => {
|
|
|
30
40
|
codeExecution_20250522: ( config = {} ) => ( { type: 'codeExecution_20250522', config } ),
|
|
31
41
|
codeExecution_20250825: ( config = {} ) => ( { type: 'codeExecution_20250825', config } )
|
|
32
42
|
};
|
|
33
|
-
return {
|
|
43
|
+
return {
|
|
44
|
+
createAnthropic: options => {
|
|
45
|
+
providerFactoryOptions.anthropic = options;
|
|
46
|
+
return anthropicMock;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
34
49
|
} );
|
|
35
50
|
|
|
36
51
|
// Bedrock mock with tools support
|
|
@@ -42,17 +57,25 @@ vi.mock( '@ai-sdk/amazon-bedrock', () => {
|
|
|
42
57
|
textEditor_20250429: ( config = {} ) => ( { type: 'textEditor_20250429', config } ),
|
|
43
58
|
computer_20241022: ( config = {} ) => ( { type: 'computer_20241022', config } )
|
|
44
59
|
};
|
|
45
|
-
return {
|
|
60
|
+
return {
|
|
61
|
+
createAmazonBedrock: options => {
|
|
62
|
+
providerFactoryOptions.bedrock = options;
|
|
63
|
+
return bedrockMock;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
46
66
|
} );
|
|
47
67
|
|
|
48
68
|
// Perplexity mock
|
|
49
69
|
vi.mock( '@ai-sdk/perplexity', () => ( {
|
|
50
|
-
|
|
70
|
+
createPerplexity: options => {
|
|
71
|
+
providerFactoryOptions.perplexity = options;
|
|
72
|
+
return ( ...values ) => perplexityImpl( ...values );
|
|
73
|
+
}
|
|
51
74
|
} ) );
|
|
52
75
|
|
|
53
76
|
// Vertex mock with tools support
|
|
54
77
|
vi.mock( '@ai-sdk/google-vertex', () => {
|
|
55
|
-
const vertexFn =
|
|
78
|
+
const vertexFn = ( ...values ) => vertexImpl( ...values );
|
|
56
79
|
vertexFn.tools = {
|
|
57
80
|
googleSearch: ( config = {} ) => ( { type: 'googleSearch', config } ),
|
|
58
81
|
fileSearch: ( config = {} ) => ( { type: 'fileSearch', config } ),
|
|
@@ -62,7 +85,12 @@ vi.mock( '@ai-sdk/google-vertex', () => {
|
|
|
62
85
|
codeExecution: ( config = {} ) => ( { type: 'codeExecution', config } ),
|
|
63
86
|
vertexRagStore: ( config = {} ) => ( { type: 'vertexRagStore', config } )
|
|
64
87
|
};
|
|
65
|
-
return {
|
|
88
|
+
return {
|
|
89
|
+
createVertex: options => {
|
|
90
|
+
providerFactoryOptions.vertex = options;
|
|
91
|
+
return vertexFn;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
66
94
|
} );
|
|
67
95
|
|
|
68
96
|
import { loadModel, loadTools, registerProvider, getRegisteredProviders, providers, builtInProviders } from './ai_model.js';
|
|
@@ -73,6 +101,17 @@ afterEach( async () => {
|
|
|
73
101
|
} );
|
|
74
102
|
|
|
75
103
|
describe( 'loadModel', () => {
|
|
104
|
+
it( 'initializes built-in providers with custom fetch', () => {
|
|
105
|
+
expect( providerFactoryOptions ).toMatchObject( {
|
|
106
|
+
azure: { fetch: expect.any( Function ) },
|
|
107
|
+
anthropic: { fetch: expect.any( Function ) },
|
|
108
|
+
openai: { fetch: expect.any( Function ) },
|
|
109
|
+
vertex: { fetch: expect.any( Function ) },
|
|
110
|
+
bedrock: { fetch: expect.any( Function ) },
|
|
111
|
+
perplexity: { fetch: expect.any( Function ) }
|
|
112
|
+
} );
|
|
113
|
+
} );
|
|
114
|
+
|
|
76
115
|
it( 'loads model using selected provider', () => {
|
|
77
116
|
const result = loadModel( { config: { provider: 'openai', model: 'gpt-4o-mini' } } );
|
|
78
117
|
|
|
@@ -617,7 +656,7 @@ describe( 'loadTools', () => {
|
|
|
617
656
|
provider: 'vertex',
|
|
618
657
|
tools: { googleSearch: null }
|
|
619
658
|
}
|
|
620
|
-
} ) ).toThrow(
|
|
659
|
+
} ) ).toThrow( /Invalid config for tool "googleSearch".*expected record, received null/s );
|
|
621
660
|
} );
|
|
622
661
|
|
|
623
662
|
it( 'throws error when tool config is a string', () => {
|
|
@@ -626,7 +665,7 @@ describe( 'loadTools', () => {
|
|
|
626
665
|
provider: 'vertex',
|
|
627
666
|
tools: { googleSearch: 'MODE_DYNAMIC' }
|
|
628
667
|
}
|
|
629
|
-
} ) ).toThrow(
|
|
668
|
+
} ) ).toThrow( /Invalid config for tool "googleSearch".*expected record, received string/s );
|
|
630
669
|
} );
|
|
631
670
|
|
|
632
671
|
it( 'throws error for unknown tool on Bedrock with dynamic tool listing', () => {
|
package/src/ai_sdk.js
CHANGED
|
@@ -64,10 +64,10 @@ export const hydratePromptTemplate = ( prompt, variables, promptDir, callerSkill
|
|
|
64
64
|
return { loadedPrompt: meta, allVariables: variables, tools };
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
export async function generateText( { prompt, variables, promptDir, skills = [], maxSteps = 10, ...
|
|
67
|
+
export async function generateText( { prompt, variables, promptDir, skills = [], maxSteps = 10, ...aiSdkArgs } ) {
|
|
68
68
|
const callerSkills = typeof skills === 'function' ? await skills( variables ) : skills;
|
|
69
69
|
const { loadedPrompt, allVariables, tools } =
|
|
70
|
-
hydratePromptTemplate( prompt, variables, promptDir, callerSkills,
|
|
70
|
+
hydratePromptTemplate( prompt, variables, promptDir, callerSkills, aiSdkArgs.tools );
|
|
71
71
|
const hasTools = Object.keys( tools ).length > 0;
|
|
72
72
|
|
|
73
73
|
validateGenerateTextArgs( { prompt, variables: allVariables } );
|
|
@@ -78,9 +78,10 @@ export async function generateText( { prompt, variables, promptDir, skills = [],
|
|
|
78
78
|
try {
|
|
79
79
|
const response = await AI.generateText( {
|
|
80
80
|
...loadAiSdkOptionsFromPrompt( loadedPrompt ),
|
|
81
|
-
|
|
81
|
+
maxRetries: 0,
|
|
82
|
+
...aiSdkArgs,
|
|
82
83
|
...( hasTools ? { tools } : {} ),
|
|
83
|
-
...( hasTools && !
|
|
84
|
+
...( hasTools && !aiSdkArgs.stopWhen ? { stopWhen: stepCountIs( maxSteps ) } : {} )
|
|
84
85
|
} );
|
|
85
86
|
return wrapTextResponse( { traceId, modelId, response } );
|
|
86
87
|
} catch ( error ) {
|
|
@@ -89,7 +90,7 @@ export async function generateText( { prompt, variables, promptDir, skills = [],
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
export function streamText( { prompt, variables, onFinish, onError, ...
|
|
93
|
+
export function streamText( { prompt, variables, onFinish, onError, ...aiSdkArgs } ) {
|
|
93
94
|
validateStreamTextArgs( { prompt, variables } );
|
|
94
95
|
const loadedPrompt = loadPrompt( prompt, variables );
|
|
95
96
|
const traceId = startTrace( { name: 'streamText', prompt, variables, loadedPrompt } );
|
|
@@ -98,7 +99,8 @@ export function streamText( { prompt, variables, onFinish, onError, ...restOptio
|
|
|
98
99
|
try {
|
|
99
100
|
return AI.streamText( {
|
|
100
101
|
...loadAiSdkOptionsFromPrompt( loadedPrompt ),
|
|
101
|
-
|
|
102
|
+
maxRetries: 0,
|
|
103
|
+
...aiSdkArgs,
|
|
102
104
|
...wrapStreamOnFinishResponse( { traceId, modelId, onFinish } ),
|
|
103
105
|
onError( event ) {
|
|
104
106
|
endTraceWithError( { traceId, error: event.error } );
|
package/src/ai_sdk.spec.js
CHANGED
|
@@ -149,6 +149,7 @@ describe( 'ai_sdk', () => {
|
|
|
149
149
|
model: 'MODEL',
|
|
150
150
|
messages: basePrompt.messages,
|
|
151
151
|
temperature: 0.3,
|
|
152
|
+
maxRetries: 0,
|
|
152
153
|
providerOptions: basePrompt.config.providerOptions
|
|
153
154
|
} );
|
|
154
155
|
expect( result ).toBe( generateTextAiFixture.response );
|
|
@@ -182,6 +183,7 @@ describe( 'ai_sdk', () => {
|
|
|
182
183
|
expect( aiFns.generateText ).toHaveBeenCalledWith( {
|
|
183
184
|
model: 'MODEL',
|
|
184
185
|
messages: promptWithProviderOptions.messages,
|
|
186
|
+
maxRetries: 0,
|
|
185
187
|
providerOptions: {
|
|
186
188
|
thinking: {
|
|
187
189
|
type: 'enabled',
|
|
@@ -362,6 +364,7 @@ describe( 'ai_sdk', () => {
|
|
|
362
364
|
model: 'MODEL',
|
|
363
365
|
messages: basePrompt.messages,
|
|
364
366
|
temperature: 0.3,
|
|
367
|
+
maxRetries: 0,
|
|
365
368
|
providerOptions: basePrompt.config.providerOptions,
|
|
366
369
|
onFinish: expect.any( Function ),
|
|
367
370
|
onError: expect.any( Function )
|