@ai-sdk/baseten 1.0.16 → 1.0.18
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/CHANGELOG.md +15 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +3 -2
- package/src/baseten-chat-options.ts +15 -0
- package/src/baseten-embedding-options.ts +12 -0
- package/src/baseten-provider.ts +247 -0
- package/src/baseten-provider.unit.test.ts +446 -0
- package/src/index.ts +8 -0
- package/src/version.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @ai-sdk/baseten
|
|
2
2
|
|
|
3
|
+
## 1.0.18
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8dc54db: chore: add src folders to package bundle
|
|
8
|
+
- Updated dependencies [8dc54db]
|
|
9
|
+
- @ai-sdk/openai-compatible@2.0.17
|
|
10
|
+
|
|
11
|
+
## 1.0.17
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [78555ad]
|
|
16
|
+
- @ai-sdk/openai-compatible@2.0.16
|
|
17
|
+
|
|
3
18
|
## 1.0.16
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var import_v4 = require("zod/v4");
|
|
|
34
34
|
var import_performance_client = require("@basetenlabs/performance-client");
|
|
35
35
|
|
|
36
36
|
// src/version.ts
|
|
37
|
-
var VERSION = true ? "1.0.
|
|
37
|
+
var VERSION = true ? "1.0.18" : "0.0.0-test";
|
|
38
38
|
|
|
39
39
|
// src/baseten-provider.ts
|
|
40
40
|
var basetenErrorSchema = import_v4.z.object({
|
package/dist/index.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { z } from "zod/v4";
|
|
|
15
15
|
import { PerformanceClient } from "@basetenlabs/performance-client";
|
|
16
16
|
|
|
17
17
|
// src/version.ts
|
|
18
|
-
var VERSION = true ? "1.0.
|
|
18
|
+
var VERSION = true ? "1.0.18" : "0.0.0-test";
|
|
19
19
|
|
|
20
20
|
// src/baseten-provider.ts
|
|
21
21
|
var basetenErrorSchema = z.object({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/baseten",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/**/*",
|
|
11
|
+
"src",
|
|
11
12
|
"CHANGELOG.md",
|
|
12
13
|
"README.md"
|
|
13
14
|
],
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
23
24
|
"@basetenlabs/performance-client": "^0.0.10",
|
|
24
|
-
"@ai-sdk/openai-compatible": "2.0.
|
|
25
|
+
"@ai-sdk/openai-compatible": "2.0.17",
|
|
25
26
|
"@ai-sdk/provider": "3.0.4",
|
|
26
27
|
"@ai-sdk/provider-utils": "4.0.8"
|
|
27
28
|
},
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// https://docs.baseten.co/development/model-apis/overview#supported-models
|
|
2
|
+
// Below is the current list of models supported by Baseten model APIs.
|
|
3
|
+
// Ohter dedicated models are also supported, but not listed here.
|
|
4
|
+
export type BasetenChatModelId =
|
|
5
|
+
| 'deepseek-ai/DeepSeek-R1-0528'
|
|
6
|
+
| 'deepseek-ai/DeepSeek-V3-0324'
|
|
7
|
+
| 'deepseek-ai/DeepSeek-V3.1'
|
|
8
|
+
| 'moonshotai/Kimi-K2-Instruct-0905'
|
|
9
|
+
| 'moonshotai/Kimi-K2-Thinking'
|
|
10
|
+
| 'Qwen/Qwen3-235B-A22B-Instruct-2507'
|
|
11
|
+
| 'Qwen/Qwen3-Coder-480B-A35B-Instruct'
|
|
12
|
+
| 'openai/gpt-oss-120b'
|
|
13
|
+
| 'zai-org/GLM-4.6'
|
|
14
|
+
| 'zai-org/GLM-4.7'
|
|
15
|
+
| (string & {});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
|
|
3
|
+
// https://www.baseten.co/library/tag/embedding/
|
|
4
|
+
// Pass in the model URL directly, we won't be using the model ID
|
|
5
|
+
|
|
6
|
+
export type BasetenEmbeddingModelId = string & {};
|
|
7
|
+
|
|
8
|
+
export const basetenEmbeddingProviderOptions = z.object({});
|
|
9
|
+
|
|
10
|
+
export type BasetenEmbeddingProviderOptions = z.infer<
|
|
11
|
+
typeof basetenEmbeddingProviderOptions
|
|
12
|
+
>;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OpenAICompatibleChatLanguageModel,
|
|
3
|
+
OpenAICompatibleEmbeddingModel,
|
|
4
|
+
ProviderErrorStructure,
|
|
5
|
+
} from '@ai-sdk/openai-compatible';
|
|
6
|
+
import {
|
|
7
|
+
EmbeddingModelV3,
|
|
8
|
+
LanguageModelV3,
|
|
9
|
+
NoSuchModelError,
|
|
10
|
+
ProviderV3,
|
|
11
|
+
} from '@ai-sdk/provider';
|
|
12
|
+
import {
|
|
13
|
+
FetchFunction,
|
|
14
|
+
loadApiKey,
|
|
15
|
+
withoutTrailingSlash,
|
|
16
|
+
withUserAgentSuffix,
|
|
17
|
+
} from '@ai-sdk/provider-utils';
|
|
18
|
+
import { z } from 'zod/v4';
|
|
19
|
+
import { BasetenChatModelId } from './baseten-chat-options';
|
|
20
|
+
import { BasetenEmbeddingModelId } from './baseten-embedding-options';
|
|
21
|
+
import { PerformanceClient } from '@basetenlabs/performance-client';
|
|
22
|
+
import { VERSION } from './version';
|
|
23
|
+
|
|
24
|
+
export type BasetenErrorData = z.infer<typeof basetenErrorSchema>;
|
|
25
|
+
|
|
26
|
+
const basetenErrorSchema = z.object({
|
|
27
|
+
error: z.string(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const basetenErrorStructure: ProviderErrorStructure<BasetenErrorData> = {
|
|
31
|
+
errorSchema: basetenErrorSchema,
|
|
32
|
+
errorToMessage: data => data.error,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export interface BasetenProviderSettings {
|
|
36
|
+
/**
|
|
37
|
+
* Baseten API key. Default value is taken from the `BASETEN_API_KEY`
|
|
38
|
+
* environment variable.
|
|
39
|
+
*/
|
|
40
|
+
apiKey?: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Base URL for the Model APIs. Default: 'https://inference.baseten.co/v1'
|
|
44
|
+
*/
|
|
45
|
+
baseURL?: string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Model URL for custom models (chat or embeddings).
|
|
49
|
+
* If not supplied, the default Model APIs will be used.
|
|
50
|
+
*/
|
|
51
|
+
modelURL?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Custom headers to include in the requests.
|
|
54
|
+
*/
|
|
55
|
+
headers?: Record<string, string>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Custom fetch implementation. You can use it as a middleware to intercept requests,
|
|
59
|
+
* or to provide a custom fetch implementation for e.g. testing.
|
|
60
|
+
*/
|
|
61
|
+
fetch?: FetchFunction;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface BasetenProvider extends ProviderV3 {
|
|
65
|
+
/**
|
|
66
|
+
Creates a chat model for text generation.
|
|
67
|
+
*/
|
|
68
|
+
(modelId?: BasetenChatModelId): LanguageModelV3;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
Creates a chat model for text generation.
|
|
72
|
+
*/
|
|
73
|
+
chatModel(modelId?: BasetenChatModelId): LanguageModelV3;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
Creates a language model for text generation. Alias for chatModel.
|
|
77
|
+
*/
|
|
78
|
+
languageModel(modelId?: BasetenChatModelId): LanguageModelV3;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
Creates a embedding model for text generation.
|
|
82
|
+
*/
|
|
83
|
+
embeddingModel(modelId?: BasetenEmbeddingModelId): EmbeddingModelV3;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @deprecated Use `embeddingModel` instead.
|
|
87
|
+
*/
|
|
88
|
+
textEmbeddingModel(modelId?: BasetenEmbeddingModelId): EmbeddingModelV3;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// by default, we use the Model APIs
|
|
92
|
+
const defaultBaseURL = 'https://inference.baseten.co/v1';
|
|
93
|
+
|
|
94
|
+
export function createBaseten(
|
|
95
|
+
options: BasetenProviderSettings = {},
|
|
96
|
+
): BasetenProvider {
|
|
97
|
+
const baseURL = withoutTrailingSlash(options.baseURL ?? defaultBaseURL);
|
|
98
|
+
const getHeaders = () =>
|
|
99
|
+
withUserAgentSuffix(
|
|
100
|
+
{
|
|
101
|
+
Authorization: `Bearer ${loadApiKey({
|
|
102
|
+
apiKey: options.apiKey,
|
|
103
|
+
environmentVariableName: 'BASETEN_API_KEY',
|
|
104
|
+
description: 'Baseten API key',
|
|
105
|
+
})}`,
|
|
106
|
+
...options.headers,
|
|
107
|
+
},
|
|
108
|
+
`ai-sdk/baseten/${VERSION}`,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
interface CommonModelConfig {
|
|
112
|
+
provider: string;
|
|
113
|
+
url: ({ path }: { path: string }) => string;
|
|
114
|
+
headers: () => Record<string, string>;
|
|
115
|
+
fetch?: FetchFunction;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const getCommonModelConfig = (
|
|
119
|
+
modelType: string,
|
|
120
|
+
customURL?: string,
|
|
121
|
+
): CommonModelConfig => ({
|
|
122
|
+
provider: `baseten.${modelType}`,
|
|
123
|
+
url: ({ path }) => {
|
|
124
|
+
// For embeddings with /sync URLs (but not /sync/v1), we need to add /v1
|
|
125
|
+
if (
|
|
126
|
+
modelType === 'embedding' &&
|
|
127
|
+
customURL?.includes('/sync') &&
|
|
128
|
+
!customURL?.includes('/sync/v1')
|
|
129
|
+
) {
|
|
130
|
+
return `${customURL}/v1${path}`;
|
|
131
|
+
}
|
|
132
|
+
return `${customURL || baseURL}${path}`;
|
|
133
|
+
},
|
|
134
|
+
headers: getHeaders,
|
|
135
|
+
fetch: options.fetch,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const createChatModel = (modelId?: BasetenChatModelId) => {
|
|
139
|
+
// Use modelURL if provided, otherwise use default Model APIs
|
|
140
|
+
const customURL = options.modelURL;
|
|
141
|
+
|
|
142
|
+
if (customURL) {
|
|
143
|
+
// Check if this is a /sync/v1 endpoint (OpenAI-compatible) or /predict endpoint (custom)
|
|
144
|
+
const isOpenAICompatible = customURL.includes('/sync/v1');
|
|
145
|
+
|
|
146
|
+
if (isOpenAICompatible) {
|
|
147
|
+
// For /sync/v1 endpoints, use standard OpenAI-compatible format
|
|
148
|
+
return new OpenAICompatibleChatLanguageModel(modelId ?? 'placeholder', {
|
|
149
|
+
...getCommonModelConfig('chat', customURL),
|
|
150
|
+
errorStructure: basetenErrorStructure,
|
|
151
|
+
});
|
|
152
|
+
} else if (customURL.includes('/predict')) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
'Not supported. You must use a /sync/v1 endpoint for chat models.',
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Use default OpenAI-compatible format for Model APIs
|
|
160
|
+
return new OpenAICompatibleChatLanguageModel(modelId ?? 'chat', {
|
|
161
|
+
...getCommonModelConfig('chat'),
|
|
162
|
+
errorStructure: basetenErrorStructure,
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const createEmbeddingModel = (modelId?: BasetenEmbeddingModelId) => {
|
|
167
|
+
// Use modelURL if provided
|
|
168
|
+
const customURL = options.modelURL;
|
|
169
|
+
if (!customURL) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
'No model URL provided for embeddings. Please set modelURL option for embeddings.',
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check if this is a /sync or /sync/v1 endpoint (OpenAI-compatible)
|
|
176
|
+
// We support both /sync and /sync/v1, stripping /v1 before passing to Performance Client, as Performance Client adds /v1 itself
|
|
177
|
+
const isOpenAICompatible = customURL.includes('/sync');
|
|
178
|
+
|
|
179
|
+
if (isOpenAICompatible) {
|
|
180
|
+
// Create the model using OpenAICompatibleEmbeddingModel and override doEmbed
|
|
181
|
+
const model = new OpenAICompatibleEmbeddingModel(
|
|
182
|
+
modelId ?? 'embeddings',
|
|
183
|
+
{
|
|
184
|
+
...getCommonModelConfig('embedding', customURL),
|
|
185
|
+
errorStructure: basetenErrorStructure,
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// Strip /v1 from URL if present before passing to Performance Client to avoid double /v1
|
|
190
|
+
const performanceClientURL = customURL.replace('/sync/v1', '/sync');
|
|
191
|
+
|
|
192
|
+
// Initialize the B10 Performance Client once for reuse
|
|
193
|
+
const performanceClient = new PerformanceClient(
|
|
194
|
+
performanceClientURL,
|
|
195
|
+
loadApiKey({
|
|
196
|
+
apiKey: options.apiKey,
|
|
197
|
+
environmentVariableName: 'BASETEN_API_KEY',
|
|
198
|
+
description: 'Baseten API key',
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// Override the doEmbed method to use the pre-created Performance Client
|
|
203
|
+
model.doEmbed = async params => {
|
|
204
|
+
if (!params.values || !Array.isArray(params.values)) {
|
|
205
|
+
throw new Error('params.values must be an array of strings');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Performance Client handles batching internally, so we don't need to limit in 128 here
|
|
209
|
+
const response = await performanceClient.embed(
|
|
210
|
+
params.values,
|
|
211
|
+
modelId ?? 'embeddings', // model_id is for Model APIs, we don't use it here for dedicated
|
|
212
|
+
);
|
|
213
|
+
// Transform the response to match the expected format
|
|
214
|
+
const embeddings = response.data.map((item: any) => item.embedding);
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
embeddings,
|
|
218
|
+
usage: response.usage
|
|
219
|
+
? { tokens: response.usage.total_tokens }
|
|
220
|
+
: undefined,
|
|
221
|
+
response: { headers: {}, body: response },
|
|
222
|
+
warnings: [],
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return model;
|
|
227
|
+
} else {
|
|
228
|
+
throw new Error(
|
|
229
|
+
'Not supported. You must use a /sync or /sync/v1 endpoint for embeddings.',
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const provider = (modelId?: BasetenChatModelId) => createChatModel(modelId);
|
|
235
|
+
|
|
236
|
+
provider.specificationVersion = 'v3' as const;
|
|
237
|
+
provider.chatModel = createChatModel;
|
|
238
|
+
provider.languageModel = createChatModel;
|
|
239
|
+
provider.imageModel = (modelId: string) => {
|
|
240
|
+
throw new NoSuchModelError({ modelId, modelType: 'imageModel' });
|
|
241
|
+
};
|
|
242
|
+
provider.embeddingModel = createEmbeddingModel;
|
|
243
|
+
provider.textEmbeddingModel = createEmbeddingModel;
|
|
244
|
+
return provider;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export const baseten = createBaseten();
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
|
|
2
|
+
import { createBaseten } from './baseten-provider';
|
|
3
|
+
import {
|
|
4
|
+
LanguageModelV3,
|
|
5
|
+
EmbeddingModelV3,
|
|
6
|
+
NoSuchModelError,
|
|
7
|
+
} from '@ai-sdk/provider';
|
|
8
|
+
import { loadApiKey } from '@ai-sdk/provider-utils';
|
|
9
|
+
import {
|
|
10
|
+
OpenAICompatibleChatLanguageModel,
|
|
11
|
+
OpenAICompatibleEmbeddingModel,
|
|
12
|
+
} from '@ai-sdk/openai-compatible';
|
|
13
|
+
|
|
14
|
+
// Mock the OpenAI-compatible classes
|
|
15
|
+
const OpenAICompatibleChatLanguageModelMock =
|
|
16
|
+
OpenAICompatibleChatLanguageModel as unknown as Mock;
|
|
17
|
+
const OpenAICompatibleEmbeddingModelMock =
|
|
18
|
+
OpenAICompatibleEmbeddingModel as unknown as Mock;
|
|
19
|
+
|
|
20
|
+
vi.mock('@ai-sdk/openai-compatible', () => {
|
|
21
|
+
const createMockConstructor = (providerName: string) => {
|
|
22
|
+
const mockConstructor = vi.fn().mockImplementation(function (
|
|
23
|
+
this: any,
|
|
24
|
+
modelId: string,
|
|
25
|
+
settings: any,
|
|
26
|
+
) {
|
|
27
|
+
this.provider = providerName;
|
|
28
|
+
this.modelId = modelId;
|
|
29
|
+
this.settings = settings;
|
|
30
|
+
this.doGenerate = vi.fn();
|
|
31
|
+
this.doEmbed = vi.fn();
|
|
32
|
+
});
|
|
33
|
+
return mockConstructor;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
OpenAICompatibleChatLanguageModel: createMockConstructor('baseten.chat'),
|
|
38
|
+
OpenAICompatibleEmbeddingModel: createMockConstructor('baseten.embedding'),
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
vi.mock('@ai-sdk/provider-utils', async () => {
|
|
43
|
+
const actual = await vi.importActual('@ai-sdk/provider-utils');
|
|
44
|
+
return {
|
|
45
|
+
...actual,
|
|
46
|
+
loadApiKey: vi.fn().mockReturnValue('mock-api-key'),
|
|
47
|
+
withoutTrailingSlash: vi.fn(url => url),
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
vi.mock('@basetenlabs/performance-client', () => ({
|
|
52
|
+
PerformanceClient: vi.fn().mockImplementation(() => ({
|
|
53
|
+
embed: vi.fn(),
|
|
54
|
+
embedBatch: vi.fn(),
|
|
55
|
+
})),
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
vi.mock('./version', () => ({
|
|
59
|
+
VERSION: '0.0.0-test',
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
describe('BasetenProvider', () => {
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
vi.clearAllMocks();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('createBaseten', () => {
|
|
68
|
+
it('should create a BasetenProvider instance with default options', () => {
|
|
69
|
+
const provider = createBaseten();
|
|
70
|
+
const model = provider.chatModel('deepseek-ai/DeepSeek-V3-0324');
|
|
71
|
+
|
|
72
|
+
const constructorCall =
|
|
73
|
+
OpenAICompatibleChatLanguageModelMock.mock.calls[0];
|
|
74
|
+
const config = constructorCall[1];
|
|
75
|
+
const headers = config.headers();
|
|
76
|
+
|
|
77
|
+
expect(loadApiKey).toHaveBeenCalledWith({
|
|
78
|
+
apiKey: undefined,
|
|
79
|
+
environmentVariableName: 'BASETEN_API_KEY',
|
|
80
|
+
description: 'Baseten API key',
|
|
81
|
+
});
|
|
82
|
+
expect(headers.authorization).toBe('Bearer mock-api-key');
|
|
83
|
+
expect(config.provider).toBe('baseten.chat');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should create a BasetenProvider instance with custom options', () => {
|
|
87
|
+
const options = {
|
|
88
|
+
apiKey: 'custom-key',
|
|
89
|
+
baseURL: 'https://custom.url',
|
|
90
|
+
headers: { 'Custom-Header': 'value' },
|
|
91
|
+
};
|
|
92
|
+
const provider = createBaseten(options);
|
|
93
|
+
const model = provider.chatModel('deepseek-ai/DeepSeek-V3-0324');
|
|
94
|
+
|
|
95
|
+
const constructorCall =
|
|
96
|
+
OpenAICompatibleChatLanguageModelMock.mock.calls[0];
|
|
97
|
+
const config = constructorCall[1];
|
|
98
|
+
const headers = config.headers();
|
|
99
|
+
|
|
100
|
+
expect(loadApiKey).toHaveBeenCalledWith({
|
|
101
|
+
apiKey: 'custom-key',
|
|
102
|
+
environmentVariableName: 'BASETEN_API_KEY',
|
|
103
|
+
description: 'Baseten API key',
|
|
104
|
+
});
|
|
105
|
+
expect(headers['custom-header']).toBe('value');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should support optional modelId parameter', () => {
|
|
109
|
+
const provider = createBaseten();
|
|
110
|
+
|
|
111
|
+
// Should work without modelId
|
|
112
|
+
const model1 = provider();
|
|
113
|
+
expect(model1).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
|
|
114
|
+
|
|
115
|
+
// Should work with modelId
|
|
116
|
+
const model2 = provider('deepseek-ai/DeepSeek-V3-0324');
|
|
117
|
+
expect(model2).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('chatModel', () => {
|
|
122
|
+
it('should construct a chat model with correct configuration for default Model APIs', () => {
|
|
123
|
+
const provider = createBaseten();
|
|
124
|
+
const modelId = 'deepseek-ai/DeepSeek-V3-0324';
|
|
125
|
+
|
|
126
|
+
const model = provider.chatModel(modelId);
|
|
127
|
+
|
|
128
|
+
expect(model).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
|
|
129
|
+
expect(OpenAICompatibleChatLanguageModelMock).toHaveBeenCalledWith(
|
|
130
|
+
modelId,
|
|
131
|
+
expect.objectContaining({
|
|
132
|
+
provider: 'baseten.chat',
|
|
133
|
+
errorStructure: expect.any(Object),
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should construct a chat model with optional modelId', () => {
|
|
139
|
+
const provider = createBaseten();
|
|
140
|
+
|
|
141
|
+
// Should work without modelId
|
|
142
|
+
const model1 = provider.chatModel();
|
|
143
|
+
expect(model1).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
|
|
144
|
+
expect(OpenAICompatibleChatLanguageModelMock).toHaveBeenCalledWith(
|
|
145
|
+
'chat',
|
|
146
|
+
expect.any(Object),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// Should work with modelId
|
|
150
|
+
const model2 = provider.chatModel('deepseek-ai/DeepSeek-V3-0324');
|
|
151
|
+
expect(model2).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle /sync/v1 endpoints correctly', () => {
|
|
155
|
+
const provider = createBaseten({
|
|
156
|
+
modelURL:
|
|
157
|
+
'https://model-123.api.baseten.co/environments/production/sync/v1',
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const model = provider.chatModel();
|
|
161
|
+
|
|
162
|
+
expect(model).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
|
|
163
|
+
expect(OpenAICompatibleChatLanguageModelMock).toHaveBeenCalledWith(
|
|
164
|
+
'placeholder',
|
|
165
|
+
expect.objectContaining({
|
|
166
|
+
provider: 'baseten.chat',
|
|
167
|
+
url: expect.any(Function),
|
|
168
|
+
errorStructure: expect.any(Object),
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Test URL construction
|
|
173
|
+
const constructorCall =
|
|
174
|
+
OpenAICompatibleChatLanguageModelMock.mock.calls[0];
|
|
175
|
+
const config = constructorCall[1];
|
|
176
|
+
const url = config.url({ path: '/chat/completions' });
|
|
177
|
+
expect(url).toBe(
|
|
178
|
+
'https://model-123.api.baseten.co/environments/production/sync/v1/chat/completions',
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should throw error for /predict endpoints with chat models', () => {
|
|
183
|
+
const provider = createBaseten({
|
|
184
|
+
modelURL:
|
|
185
|
+
'https://model-123.api.baseten.co/environments/production/predict',
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(() => {
|
|
189
|
+
provider.chatModel();
|
|
190
|
+
}).toThrow(
|
|
191
|
+
'Not supported. You must use a /sync/v1 endpoint for chat models.',
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('languageModel', () => {
|
|
197
|
+
it('should be an alias for chatModel', () => {
|
|
198
|
+
const provider = createBaseten();
|
|
199
|
+
const modelId = 'deepseek-ai/DeepSeek-V3-0324';
|
|
200
|
+
|
|
201
|
+
const chatModel = provider.chatModel(modelId);
|
|
202
|
+
const languageModel = provider.languageModel(modelId);
|
|
203
|
+
|
|
204
|
+
expect(chatModel).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
|
|
205
|
+
expect(languageModel).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should support optional modelId parameter', () => {
|
|
209
|
+
const provider = createBaseten();
|
|
210
|
+
|
|
211
|
+
const model1 = provider.languageModel();
|
|
212
|
+
expect(model1).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
|
|
213
|
+
|
|
214
|
+
const model2 = provider.languageModel('deepseek-ai/DeepSeek-V3-0324');
|
|
215
|
+
expect(model2).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('textEmbeddingModel', () => {
|
|
220
|
+
it('should throw error when no modelURL is provided', () => {
|
|
221
|
+
const provider = createBaseten();
|
|
222
|
+
|
|
223
|
+
expect(() => {
|
|
224
|
+
provider.embeddingModel();
|
|
225
|
+
}).toThrow(
|
|
226
|
+
'No model URL provided for embeddings. Please set modelURL option for embeddings.',
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should construct embedding model for /sync endpoints', () => {
|
|
231
|
+
const provider = createBaseten({
|
|
232
|
+
modelURL:
|
|
233
|
+
'https://model-123.api.baseten.co/environments/production/sync',
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const model = provider.embeddingModel();
|
|
237
|
+
|
|
238
|
+
expect(model).toBeInstanceOf(OpenAICompatibleEmbeddingModel);
|
|
239
|
+
expect(OpenAICompatibleEmbeddingModelMock).toHaveBeenCalledWith(
|
|
240
|
+
'embeddings',
|
|
241
|
+
expect.objectContaining({
|
|
242
|
+
provider: 'baseten.embedding',
|
|
243
|
+
url: expect.any(Function),
|
|
244
|
+
errorStructure: expect.any(Object),
|
|
245
|
+
}),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// Test URL construction for embeddings (Performance Client adds /v1/embeddings)
|
|
249
|
+
const constructorCall = OpenAICompatibleEmbeddingModelMock.mock.calls[0];
|
|
250
|
+
const config = constructorCall[1];
|
|
251
|
+
const url = config.url({ path: '/embeddings' });
|
|
252
|
+
expect(url).toBe(
|
|
253
|
+
'https://model-123.api.baseten.co/environments/production/sync/v1/embeddings',
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should throw error for /predict endpoints (not supported with Performance Client)', () => {
|
|
258
|
+
const provider = createBaseten({
|
|
259
|
+
modelURL:
|
|
260
|
+
'https://model-123.api.baseten.co/environments/production/predict',
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
expect(() => {
|
|
264
|
+
provider.embeddingModel();
|
|
265
|
+
}).toThrow(
|
|
266
|
+
'Not supported. You must use a /sync or /sync/v1 endpoint for embeddings.',
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should support /sync/v1 endpoints (strips /v1 before passing to Performance Client)', () => {
|
|
271
|
+
const provider = createBaseten({
|
|
272
|
+
modelURL:
|
|
273
|
+
'https://model-123.api.baseten.co/environments/production/sync/v1',
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const model = provider.embeddingModel();
|
|
277
|
+
|
|
278
|
+
expect(model).toBeInstanceOf(OpenAICompatibleEmbeddingModel);
|
|
279
|
+
expect(OpenAICompatibleEmbeddingModelMock).toHaveBeenCalledWith(
|
|
280
|
+
'embeddings',
|
|
281
|
+
expect.any(Object),
|
|
282
|
+
);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should support custom modelId for embeddings', () => {
|
|
286
|
+
const provider = createBaseten({
|
|
287
|
+
modelURL:
|
|
288
|
+
'https://model-123.api.baseten.co/environments/production/sync',
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const model = provider.embeddingModel();
|
|
292
|
+
|
|
293
|
+
expect(model).toBeInstanceOf(OpenAICompatibleEmbeddingModel);
|
|
294
|
+
expect(OpenAICompatibleEmbeddingModelMock).toHaveBeenCalledWith(
|
|
295
|
+
'embeddings',
|
|
296
|
+
expect.any(Object),
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe('imageModel', () => {
|
|
302
|
+
it('should throw NoSuchModelError for unsupported image models', () => {
|
|
303
|
+
const provider = createBaseten();
|
|
304
|
+
|
|
305
|
+
expect(() => {
|
|
306
|
+
provider.imageModel('test-model');
|
|
307
|
+
}).toThrow(NoSuchModelError);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe('URL construction', () => {
|
|
312
|
+
it('should use default baseURL when no modelURL is provided', () => {
|
|
313
|
+
const provider = createBaseten();
|
|
314
|
+
const model = provider.chatModel('test-model');
|
|
315
|
+
|
|
316
|
+
const constructorCall =
|
|
317
|
+
OpenAICompatibleChatLanguageModelMock.mock.calls[0];
|
|
318
|
+
const config = constructorCall[1];
|
|
319
|
+
const url = config.url({ path: '/chat/completions' });
|
|
320
|
+
expect(url).toBe('https://inference.baseten.co/v1/chat/completions');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should use custom baseURL when provided', () => {
|
|
324
|
+
const provider = createBaseten({
|
|
325
|
+
baseURL: 'https://custom.baseten.co/v1',
|
|
326
|
+
});
|
|
327
|
+
const model = provider.chatModel('test-model');
|
|
328
|
+
|
|
329
|
+
const constructorCall =
|
|
330
|
+
OpenAICompatibleChatLanguageModelMock.mock.calls[0];
|
|
331
|
+
const config = constructorCall[1];
|
|
332
|
+
const url = config.url({ path: '/chat/completions' });
|
|
333
|
+
expect(url).toBe('https://custom.baseten.co/v1/chat/completions');
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('should use modelURL for custom endpoints', () => {
|
|
337
|
+
const provider = createBaseten({
|
|
338
|
+
modelURL:
|
|
339
|
+
'https://model-123.api.baseten.co/environments/production/sync/v1',
|
|
340
|
+
});
|
|
341
|
+
const model = provider.chatModel();
|
|
342
|
+
|
|
343
|
+
const constructorCall =
|
|
344
|
+
OpenAICompatibleChatLanguageModelMock.mock.calls[0];
|
|
345
|
+
const config = constructorCall[1];
|
|
346
|
+
const url = config.url({ path: '/chat/completions' });
|
|
347
|
+
expect(url).toBe(
|
|
348
|
+
'https://model-123.api.baseten.co/environments/production/sync/v1/chat/completions',
|
|
349
|
+
);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
describe('Headers', () => {
|
|
354
|
+
it('should include Authorization header with API key', () => {
|
|
355
|
+
const provider = createBaseten();
|
|
356
|
+
const model = provider.chatModel('test-model');
|
|
357
|
+
|
|
358
|
+
const constructorCall =
|
|
359
|
+
OpenAICompatibleChatLanguageModelMock.mock.calls[0];
|
|
360
|
+
const config = constructorCall[1];
|
|
361
|
+
const headers = config.headers();
|
|
362
|
+
|
|
363
|
+
expect(headers.authorization).toBe('Bearer mock-api-key');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should include custom headers when provided', () => {
|
|
367
|
+
const provider = createBaseten({
|
|
368
|
+
headers: { 'Custom-Header': 'custom-value' },
|
|
369
|
+
});
|
|
370
|
+
const model = provider.chatModel('test-model');
|
|
371
|
+
|
|
372
|
+
const constructorCall =
|
|
373
|
+
OpenAICompatibleChatLanguageModelMock.mock.calls[0];
|
|
374
|
+
const config = constructorCall[1];
|
|
375
|
+
const headers = config.headers();
|
|
376
|
+
|
|
377
|
+
expect(headers.authorization).toBe('Bearer mock-api-key');
|
|
378
|
+
expect(headers['custom-header']).toBe('custom-value');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('should include user-agent with version', async () => {
|
|
382
|
+
const fetchMock = vi
|
|
383
|
+
.fn()
|
|
384
|
+
.mockResolvedValue(new Response('{}', { status: 200 }));
|
|
385
|
+
|
|
386
|
+
const provider = createBaseten({ fetch: fetchMock });
|
|
387
|
+
const model = provider.chatModel('test-model');
|
|
388
|
+
|
|
389
|
+
const constructorCall =
|
|
390
|
+
OpenAICompatibleChatLanguageModelMock.mock.calls[0];
|
|
391
|
+
const config = constructorCall[1];
|
|
392
|
+
const headers = config.headers();
|
|
393
|
+
|
|
394
|
+
await fetchMock('https://example.com/test', {
|
|
395
|
+
method: 'POST',
|
|
396
|
+
headers,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
expect(fetchMock.mock.calls[0][1].headers['user-agent']).toContain(
|
|
400
|
+
'ai-sdk/baseten/0.0.0-test',
|
|
401
|
+
);
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
describe('Error handling', () => {
|
|
406
|
+
it('should handle missing modelURL for embeddings gracefully', () => {
|
|
407
|
+
const provider = createBaseten();
|
|
408
|
+
|
|
409
|
+
expect(() => {
|
|
410
|
+
provider.embeddingModel();
|
|
411
|
+
}).toThrow(
|
|
412
|
+
'No model URL provided for embeddings. Please set modelURL option for embeddings.',
|
|
413
|
+
);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('should handle unsupported image models', () => {
|
|
417
|
+
const provider = createBaseten();
|
|
418
|
+
|
|
419
|
+
expect(() => {
|
|
420
|
+
provider.imageModel('unsupported-model');
|
|
421
|
+
}).toThrow(NoSuchModelError);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
describe('Provider interface', () => {
|
|
426
|
+
it('should implement all required provider methods', () => {
|
|
427
|
+
const provider = createBaseten();
|
|
428
|
+
|
|
429
|
+
expect(typeof provider).toBe('function');
|
|
430
|
+
expect(typeof provider.chatModel).toBe('function');
|
|
431
|
+
expect(typeof provider.languageModel).toBe('function');
|
|
432
|
+
expect(typeof provider.embeddingModel).toBe('function');
|
|
433
|
+
expect(typeof provider.imageModel).toBe('function');
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('should allow calling provider as function', () => {
|
|
437
|
+
const provider = createBaseten();
|
|
438
|
+
|
|
439
|
+
const model1 = provider();
|
|
440
|
+
expect(model1).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
|
|
441
|
+
|
|
442
|
+
const model2 = provider('test-model');
|
|
443
|
+
expect(model2).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type { BasetenChatModelId } from './baseten-chat-options';
|
|
2
|
+
export { baseten, createBaseten } from './baseten-provider';
|
|
3
|
+
export type {
|
|
4
|
+
BasetenProvider,
|
|
5
|
+
BasetenProviderSettings,
|
|
6
|
+
BasetenErrorData,
|
|
7
|
+
} from './baseten-provider';
|
|
8
|
+
export { VERSION } from './version';
|