@ai-sdk/baseten 2.0.21 → 2.1.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/CHANGELOG.md +36 -0
- package/dist/index.d.ts +40 -2
- package/dist/index.js +59 -45
- package/dist/index.js.map +1 -1
- package/docs/170-baseten.mdx +41 -10
- package/package.json +4 -4
- package/src/baseten-provider.ts +115 -59
- package/src/index.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# @ai-sdk/baseten
|
|
2
2
|
|
|
3
|
+
## 2.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 11f00aa: Make the native performance client opt-in for embeddings.
|
|
8
|
+
|
|
9
|
+
`@basetenlabs/performance-client` is no longer a dependency. It is a NAPI addon — 16 platform binary packages, ~5-16 MB installed — that could not load in edge runtimes and whose platform binaries bundlers could not resolve, and it was imported at module top level, so every consumer paid for it even though only embeddings use it.
|
|
10
|
+
|
|
11
|
+
Embeddings now go over plain HTTP to the deployment's OpenAI-compatible endpoint, which is what Baseten Embeddings Inference serves with no additional settings. To keep the native client's client-side batching and request hedging, install it yourself and pass the constructor:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createBaseten } from "@ai-sdk/baseten";
|
|
15
|
+
import { PerformanceClient } from "@basetenlabs/performance-client";
|
|
16
|
+
|
|
17
|
+
const baseten = createBaseten({
|
|
18
|
+
modelURL,
|
|
19
|
+
performanceClient: PerformanceClient,
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The default path now supports things the previous implementation silently dropped: `abortSignal`, per-call `headers`, the `dimensions` and `user` provider options, and the provider's `fetch` option — `createBaseten({ fetch })` previously had no effect on embeddings. Response headers and warnings are now real rather than empty.
|
|
24
|
+
|
|
25
|
+
`usage.tokens` now comes from `prompt_tokens` rather than `total_tokens`, matching the `EmbeddingModelV4` contract ("we only have input tokens for embeddings") and the other providers. The values are normally identical for embeddings.
|
|
26
|
+
|
|
27
|
+
One behaviour change to be aware of: each request now sends at most 128 values. `embedMany` splits and parallelises above that, so only a direct `doEmbed` call with more than 128 values is affected — it throws `TooManyEmbeddingValuesForCallError`. The opt-in native path is unchanged and still receives everything in one call.
|
|
28
|
+
|
|
29
|
+
Separately, report token usage for streamed chat completions. The provider never set `includeUsage`, so `stream_options.include_usage` was omitted from requests and OpenAI-compatible servers returned no usage at all for streams — `streamText` reported `inputTokens`/`outputTokens`/`totalTokens` as `undefined` while `generateText` on the same model reported them correctly. This affected both the Model APIs and dedicated-deployment paths.
|
|
30
|
+
|
|
31
|
+
Also parse the error envelope dedicated deployments return. Baseten sends two different shapes: the Model APIs send `error` as a bare string (`{"error":"please check the model you provided"}`), while a dedicated deployment passes through its server's OpenAI-shaped `{"error":{"message":…,"code":…,"param":…,"type":…}}` object. The schema only accepted the string, so the object failed to parse and the message degraded to the HTTP reason phrase — a real `The model \`x\` does not exist.`surfaced as`Not Found`, or as the empty string over HTTP/2, which has no reason phrase. The schema now accepts both. This affects embeddings especially, since they require a `modelURL` and so always talk to a dedicated deployment.
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- Updated dependencies [160ccdb]
|
|
36
|
+
- @ai-sdk/provider-utils@5.0.20
|
|
37
|
+
- @ai-sdk/openai-compatible@3.0.22
|
|
38
|
+
|
|
3
39
|
## 2.0.21
|
|
4
40
|
|
|
5
41
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -8,9 +8,30 @@ type BasetenEmbeddingModelId = string & {};
|
|
|
8
8
|
declare const basetenEmbeddingModelOptions: z.ZodObject<{}, z.core.$strip>;
|
|
9
9
|
type BasetenEmbeddingModelOptions = z.infer<typeof basetenEmbeddingModelOptions>;
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* The part of `@basetenlabs/performance-client` we use, declared structurally to
|
|
13
|
+
* keep that native addon out of our dependency and type graph.
|
|
14
|
+
*/
|
|
15
|
+
type BasetenPerformanceClient = {
|
|
16
|
+
embed(input: string[], model: string): Promise<{
|
|
17
|
+
data: {
|
|
18
|
+
embedding: number[];
|
|
19
|
+
}[];
|
|
20
|
+
usage?: {
|
|
21
|
+
total_tokens?: number;
|
|
22
|
+
};
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
25
|
+
type BasetenPerformanceClientConstructor = new (baseUrl: string, apiKey?: string) => BasetenPerformanceClient;
|
|
11
26
|
type BasetenErrorData = z.infer<typeof basetenErrorSchema>;
|
|
12
27
|
declare const basetenErrorSchema: z.ZodObject<{
|
|
13
|
-
error: z.ZodString
|
|
28
|
+
error: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
29
|
+
message: z.ZodString;
|
|
30
|
+
object: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
31
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
32
|
+
param: z.ZodOptional<z.ZodNullable<z.ZodAny>>;
|
|
33
|
+
code: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
34
|
+
}, z.core.$strip>]>;
|
|
14
35
|
}, z.core.$strip>;
|
|
15
36
|
interface BasetenProviderSettings {
|
|
16
37
|
/**
|
|
@@ -36,6 +57,23 @@ interface BasetenProviderSettings {
|
|
|
36
57
|
* or to provide a custom fetch implementation for e.g. testing.
|
|
37
58
|
*/
|
|
38
59
|
fetch?: FetchFunction;
|
|
60
|
+
/**
|
|
61
|
+
* Opt in to Baseten's native performance client for embeddings, for
|
|
62
|
+
* client-side batching and request hedging. Pass the `PerformanceClient`
|
|
63
|
+
* constructor from `@basetenlabs/performance-client`, which you install
|
|
64
|
+
* yourself:
|
|
65
|
+
*
|
|
66
|
+
* ```ts
|
|
67
|
+
* import { PerformanceClient } from '@basetenlabs/performance-client';
|
|
68
|
+
*
|
|
69
|
+
* const baseten = createBaseten({ modelURL, performanceClient: PerformanceClient });
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* When omitted, embeddings go over plain HTTP to Baseten's OpenAI-compatible
|
|
73
|
+
* endpoint — the default, since this NAPI addon cannot load in edge runtimes
|
|
74
|
+
* and bundlers cannot resolve its platform binaries.
|
|
75
|
+
*/
|
|
76
|
+
performanceClient?: BasetenPerformanceClientConstructor;
|
|
39
77
|
}
|
|
40
78
|
interface BasetenProvider extends ProviderV4 {
|
|
41
79
|
/**
|
|
@@ -64,4 +102,4 @@ declare const baseten: BasetenProvider;
|
|
|
64
102
|
|
|
65
103
|
declare const VERSION: string;
|
|
66
104
|
|
|
67
|
-
export { type BasetenChatModelId, type BasetenEmbeddingModelOptions, type BasetenErrorData, type BasetenProvider, type BasetenProviderSettings, VERSION, baseten, createBaseten };
|
|
105
|
+
export { type BasetenChatModelId, type BasetenEmbeddingModelOptions, type BasetenErrorData, type BasetenPerformanceClient, type BasetenPerformanceClientConstructor, type BasetenProvider, type BasetenProviderSettings, VERSION, baseten, createBaseten };
|
package/dist/index.js
CHANGED
|
@@ -12,18 +12,27 @@ import {
|
|
|
12
12
|
withUserAgentSuffix
|
|
13
13
|
} from "@ai-sdk/provider-utils";
|
|
14
14
|
import { z } from "zod/v4";
|
|
15
|
-
import { PerformanceClient } from "@basetenlabs/performance-client";
|
|
16
15
|
|
|
17
16
|
// src/version.ts
|
|
18
|
-
var VERSION = true ? "2.0
|
|
17
|
+
var VERSION = true ? "2.1.0" : "0.0.0-test";
|
|
19
18
|
|
|
20
19
|
// src/baseten-provider.ts
|
|
20
|
+
var MAX_EMBEDDINGS_PER_CALL = 128;
|
|
21
21
|
var basetenErrorSchema = z.object({
|
|
22
|
-
error: z.
|
|
22
|
+
error: z.union([
|
|
23
|
+
z.string(),
|
|
24
|
+
z.object({
|
|
25
|
+
message: z.string(),
|
|
26
|
+
object: z.string().nullish(),
|
|
27
|
+
type: z.string().nullish(),
|
|
28
|
+
param: z.any().nullish(),
|
|
29
|
+
code: z.union([z.string(), z.number()]).nullish()
|
|
30
|
+
})
|
|
31
|
+
])
|
|
23
32
|
});
|
|
24
33
|
var basetenErrorStructure = {
|
|
25
34
|
errorSchema: basetenErrorSchema,
|
|
26
|
-
errorToMessage: (data) => data.error
|
|
35
|
+
errorToMessage: (data) => typeof data.error === "string" ? data.error : data.error.message
|
|
27
36
|
};
|
|
28
37
|
var defaultBaseURL = "https://inference.baseten.co/v1";
|
|
29
38
|
function createBaseten(options = {}) {
|
|
@@ -54,11 +63,12 @@ function createBaseten(options = {}) {
|
|
|
54
63
|
const createChatModel = (modelId) => {
|
|
55
64
|
const customURL = options.modelURL;
|
|
56
65
|
if (customURL) {
|
|
57
|
-
|
|
58
|
-
if (isOpenAICompatible) {
|
|
66
|
+
if (customURL.includes("/sync/v1")) {
|
|
59
67
|
return new OpenAICompatibleChatLanguageModel(modelId != null ? modelId : "placeholder", {
|
|
60
68
|
...getCommonModelConfig("chat", customURL),
|
|
61
|
-
errorStructure: basetenErrorStructure
|
|
69
|
+
errorStructure: basetenErrorStructure,
|
|
70
|
+
// Or stream_options.include_usage is omitted and streams report no usage.
|
|
71
|
+
includeUsage: true
|
|
62
72
|
});
|
|
63
73
|
} else if (customURL.includes("/predict")) {
|
|
64
74
|
throw new Error(
|
|
@@ -68,7 +78,8 @@ function createBaseten(options = {}) {
|
|
|
68
78
|
}
|
|
69
79
|
return new OpenAICompatibleChatLanguageModel(modelId != null ? modelId : "chat", {
|
|
70
80
|
...getCommonModelConfig("chat"),
|
|
71
|
-
errorStructure: basetenErrorStructure
|
|
81
|
+
errorStructure: basetenErrorStructure,
|
|
82
|
+
includeUsage: true
|
|
72
83
|
});
|
|
73
84
|
};
|
|
74
85
|
const createEmbeddingModel = (modelId) => {
|
|
@@ -78,47 +89,50 @@ function createBaseten(options = {}) {
|
|
|
78
89
|
"No model URL provided for embeddings. Please set modelURL option for embeddings."
|
|
79
90
|
);
|
|
80
91
|
}
|
|
81
|
-
|
|
82
|
-
if (isOpenAICompatible) {
|
|
83
|
-
const model = new OpenAICompatibleEmbeddingModel(
|
|
84
|
-
modelId != null ? modelId : "embeddings",
|
|
85
|
-
{
|
|
86
|
-
...getCommonModelConfig("embedding", customURL),
|
|
87
|
-
errorStructure: basetenErrorStructure
|
|
88
|
-
}
|
|
89
|
-
);
|
|
90
|
-
const performanceClientURL = customURL.replace("/sync/v1", "/sync");
|
|
91
|
-
const performanceClient = new PerformanceClient(
|
|
92
|
-
performanceClientURL,
|
|
93
|
-
loadApiKey({
|
|
94
|
-
apiKey: options.apiKey,
|
|
95
|
-
environmentVariableName: "BASETEN_API_KEY",
|
|
96
|
-
description: "Baseten API key"
|
|
97
|
-
})
|
|
98
|
-
);
|
|
99
|
-
model.doEmbed = async (params) => {
|
|
100
|
-
if (!params.values || !Array.isArray(params.values)) {
|
|
101
|
-
throw new Error("params.values must be an array of strings");
|
|
102
|
-
}
|
|
103
|
-
const response = await performanceClient.embed(
|
|
104
|
-
params.values,
|
|
105
|
-
modelId != null ? modelId : "embeddings"
|
|
106
|
-
// model_id is for Model APIs, we don't use it here for dedicated
|
|
107
|
-
);
|
|
108
|
-
const embeddings = response.data.map((item) => item.embedding);
|
|
109
|
-
return {
|
|
110
|
-
embeddings,
|
|
111
|
-
usage: response.usage ? { tokens: response.usage.total_tokens } : void 0,
|
|
112
|
-
response: { headers: {}, body: response },
|
|
113
|
-
warnings: []
|
|
114
|
-
};
|
|
115
|
-
};
|
|
116
|
-
return model;
|
|
117
|
-
} else {
|
|
92
|
+
if (!customURL.includes("/sync")) {
|
|
118
93
|
throw new Error(
|
|
119
94
|
"Not supported. You must use a /sync or /sync/v1 endpoint for embeddings."
|
|
120
95
|
);
|
|
121
96
|
}
|
|
97
|
+
const model = new OpenAICompatibleEmbeddingModel(modelId != null ? modelId : "embeddings", {
|
|
98
|
+
...getCommonModelConfig("embedding", customURL),
|
|
99
|
+
errorStructure: basetenErrorStructure,
|
|
100
|
+
// Over HTTP, cap each request and let `embedMany` split and parallelise.
|
|
101
|
+
// The native client does its own batching, so let it take everything at
|
|
102
|
+
// once — `embedMany` treats Infinity as "one call".
|
|
103
|
+
maxEmbeddingsPerCall: options.performanceClient ? Number.POSITIVE_INFINITY : MAX_EMBEDDINGS_PER_CALL
|
|
104
|
+
});
|
|
105
|
+
if (!options.performanceClient) {
|
|
106
|
+
return model;
|
|
107
|
+
}
|
|
108
|
+
const performanceClient = new options.performanceClient(
|
|
109
|
+
customURL.replace("/sync/v1", "/sync"),
|
|
110
|
+
loadApiKey({
|
|
111
|
+
apiKey: options.apiKey,
|
|
112
|
+
environmentVariableName: "BASETEN_API_KEY",
|
|
113
|
+
description: "Baseten API key"
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
model.doEmbed = async (params) => {
|
|
117
|
+
var _a2;
|
|
118
|
+
if (!params.values || !Array.isArray(params.values)) {
|
|
119
|
+
throw new Error("params.values must be an array of strings");
|
|
120
|
+
}
|
|
121
|
+
const response = await performanceClient.embed(
|
|
122
|
+
params.values,
|
|
123
|
+
// model_id is for Model APIs; dedicated deployments ignore it.
|
|
124
|
+
modelId != null ? modelId : "embeddings"
|
|
125
|
+
);
|
|
126
|
+
return {
|
|
127
|
+
embeddings: response.data.map((item) => item.embedding),
|
|
128
|
+
// The native client types its response as `any`; only report usage when
|
|
129
|
+
// a token count is actually present rather than `{ tokens: undefined }`.
|
|
130
|
+
usage: typeof ((_a2 = response.usage) == null ? void 0 : _a2.total_tokens) === "number" ? { tokens: response.usage.total_tokens } : void 0,
|
|
131
|
+
response: { headers: {}, body: response },
|
|
132
|
+
warnings: []
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
return model;
|
|
122
136
|
};
|
|
123
137
|
const provider = (modelId) => createChatModel(modelId);
|
|
124
138
|
provider.specificationVersion = "v4";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/baseten-provider.ts","../src/version.ts"],"sourcesContent":["import {\n OpenAICompatibleChatLanguageModel,\n OpenAICompatibleEmbeddingModel,\n type ProviderErrorStructure,\n} from '@ai-sdk/openai-compatible';\nimport {\n NoSuchModelError,\n type EmbeddingModelV4,\n type LanguageModelV4,\n type ProviderV4,\n} from '@ai-sdk/provider';\nimport {\n loadApiKey,\n withoutTrailingSlash,\n withUserAgentSuffix,\n type FetchFunction,\n} from '@ai-sdk/provider-utils';\nimport { z } from 'zod/v4';\nimport type { BasetenChatModelId } from './baseten-chat-options';\nimport type { BasetenEmbeddingModelId } from './baseten-embedding-options';\nimport { PerformanceClient } from '@basetenlabs/performance-client';\nimport { VERSION } from './version';\n\nexport type BasetenErrorData = z.infer<typeof basetenErrorSchema>;\n\nconst basetenErrorSchema = z.object({\n error: z.string(),\n});\n\nconst basetenErrorStructure: ProviderErrorStructure<BasetenErrorData> = {\n errorSchema: basetenErrorSchema,\n errorToMessage: data => data.error,\n};\n\nexport interface BasetenProviderSettings {\n /**\n * Baseten API key. Default value is taken from the `BASETEN_API_KEY`\n * environment variable.\n */\n apiKey?: string;\n\n /**\n * Base URL for the Model APIs. Default: 'https://inference.baseten.co/v1'\n */\n baseURL?: string;\n\n /**\n * Model URL for custom models (chat or embeddings).\n * If not supplied, the default Model APIs will be used.\n */\n modelURL?: string;\n /**\n * Custom headers to include in the requests.\n */\n headers?: Record<string, string>;\n\n /**\n * Custom fetch implementation. You can use it as a middleware to intercept requests,\n * or to provide a custom fetch implementation for e.g. testing.\n */\n fetch?: FetchFunction;\n}\n\nexport interface BasetenProvider extends ProviderV4 {\n /**\n * Creates a chat model for text generation.\n */\n (modelId?: BasetenChatModelId): LanguageModelV4;\n\n /**\n * Creates a chat model for text generation.\n */\n chatModel(modelId?: BasetenChatModelId): LanguageModelV4;\n\n /**\n * Creates a language model for text generation. Alias for chatModel.\n */\n languageModel(modelId?: BasetenChatModelId): LanguageModelV4;\n\n /**\n * Creates a embedding model for text generation.\n */\n embeddingModel(modelId?: BasetenEmbeddingModelId): EmbeddingModelV4;\n\n /**\n * @deprecated Use `embeddingModel` instead.\n */\n textEmbeddingModel(modelId?: BasetenEmbeddingModelId): EmbeddingModelV4;\n}\n\n// by default, we use the Model APIs\nconst defaultBaseURL = 'https://inference.baseten.co/v1';\n\nexport function createBaseten(\n options: BasetenProviderSettings = {},\n): BasetenProvider {\n const baseURL = withoutTrailingSlash(options.baseURL ?? defaultBaseURL);\n const getHeaders = () =>\n withUserAgentSuffix(\n {\n Authorization: `Bearer ${loadApiKey({\n apiKey: options.apiKey,\n environmentVariableName: 'BASETEN_API_KEY',\n description: 'Baseten API key',\n })}`,\n ...options.headers,\n },\n `ai-sdk/baseten/${VERSION}`,\n );\n\n interface CommonModelConfig {\n provider: string;\n url: ({ path }: { path: string }) => string;\n headers: () => Record<string, string>;\n fetch?: FetchFunction;\n }\n\n const getCommonModelConfig = (\n modelType: string,\n customURL?: string,\n ): CommonModelConfig => ({\n provider: `baseten.${modelType}`,\n url: ({ path }) => {\n // For embeddings with /sync URLs (but not /sync/v1), we need to add /v1\n if (\n modelType === 'embedding' &&\n customURL?.includes('/sync') &&\n !customURL?.includes('/sync/v1')\n ) {\n return `${customURL}/v1${path}`;\n }\n return `${customURL || baseURL}${path}`;\n },\n headers: getHeaders,\n fetch: options.fetch,\n });\n\n const createChatModel = (modelId?: BasetenChatModelId) => {\n // Use modelURL if provided, otherwise use default Model APIs\n const customURL = options.modelURL;\n\n if (customURL) {\n // Check if this is a /sync/v1 endpoint (OpenAI-compatible) or /predict endpoint (custom)\n const isOpenAICompatible = customURL.includes('/sync/v1');\n\n if (isOpenAICompatible) {\n // For /sync/v1 endpoints, use standard OpenAI-compatible format\n return new OpenAICompatibleChatLanguageModel(modelId ?? 'placeholder', {\n ...getCommonModelConfig('chat', customURL),\n errorStructure: basetenErrorStructure,\n });\n } else if (customURL.includes('/predict')) {\n throw new Error(\n 'Not supported. You must use a /sync/v1 endpoint for chat models.',\n );\n }\n }\n\n // Use default OpenAI-compatible format for Model APIs\n return new OpenAICompatibleChatLanguageModel(modelId ?? 'chat', {\n ...getCommonModelConfig('chat'),\n errorStructure: basetenErrorStructure,\n });\n };\n\n const createEmbeddingModel = (modelId?: BasetenEmbeddingModelId) => {\n // Use modelURL if provided\n const customURL = options.modelURL;\n if (!customURL) {\n throw new Error(\n 'No model URL provided for embeddings. Please set modelURL option for embeddings.',\n );\n }\n\n // Check if this is a /sync or /sync/v1 endpoint (OpenAI-compatible)\n // We support both /sync and /sync/v1, stripping /v1 before passing to Performance Client, as Performance Client adds /v1 itself\n const isOpenAICompatible = customURL.includes('/sync');\n\n if (isOpenAICompatible) {\n // Create the model using OpenAICompatibleEmbeddingModel and override doEmbed\n const model = new OpenAICompatibleEmbeddingModel(\n modelId ?? 'embeddings',\n {\n ...getCommonModelConfig('embedding', customURL),\n errorStructure: basetenErrorStructure,\n },\n );\n\n // Strip /v1 from URL if present before passing to Performance Client to avoid double /v1\n const performanceClientURL = customURL.replace('/sync/v1', '/sync');\n\n // Initialize the B10 Performance Client once for reuse\n const performanceClient = new PerformanceClient(\n performanceClientURL,\n loadApiKey({\n apiKey: options.apiKey,\n environmentVariableName: 'BASETEN_API_KEY',\n description: 'Baseten API key',\n }),\n );\n\n // Override the doEmbed method to use the pre-created Performance Client\n model.doEmbed = async params => {\n if (!params.values || !Array.isArray(params.values)) {\n throw new Error('params.values must be an array of strings');\n }\n\n // Performance Client handles batching internally, so we don't need to limit in 128 here\n const response = await performanceClient.embed(\n params.values,\n modelId ?? 'embeddings', // model_id is for Model APIs, we don't use it here for dedicated\n );\n // Transform the response to match the expected format\n const embeddings = response.data.map((item: any) => item.embedding);\n\n return {\n embeddings,\n usage: response.usage\n ? { tokens: response.usage.total_tokens }\n : undefined,\n response: { headers: {}, body: response },\n warnings: [],\n };\n };\n\n return model;\n } else {\n throw new Error(\n 'Not supported. You must use a /sync or /sync/v1 endpoint for embeddings.',\n );\n }\n };\n\n const provider = (modelId?: BasetenChatModelId) => createChatModel(modelId);\n\n provider.specificationVersion = 'v4' as const;\n provider.chatModel = createChatModel;\n provider.languageModel = createChatModel;\n provider.imageModel = (modelId: string) => {\n throw new NoSuchModelError({ modelId, modelType: 'imageModel' });\n };\n provider.embeddingModel = createEmbeddingModel;\n provider.textEmbeddingModel = createEmbeddingModel;\n return provider;\n}\n\nexport const baseten = createBaseten();\n","// Version string of this package injected at build time.\ndeclare const __PACKAGE_VERSION__: string | undefined;\nexport const VERSION: string =\n typeof __PACKAGE_VERSION__ !== 'undefined'\n ? __PACKAGE_VERSION__\n : '0.0.0-test';\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/baseten-provider.ts","../src/version.ts"],"sourcesContent":["import {\n OpenAICompatibleChatLanguageModel,\n OpenAICompatibleEmbeddingModel,\n type ProviderErrorStructure,\n} from '@ai-sdk/openai-compatible';\nimport {\n NoSuchModelError,\n type EmbeddingModelV4,\n type LanguageModelV4,\n type ProviderV4,\n} from '@ai-sdk/provider';\nimport {\n loadApiKey,\n withoutTrailingSlash,\n withUserAgentSuffix,\n type FetchFunction,\n} from '@ai-sdk/provider-utils';\nimport { z } from 'zod/v4';\nimport type { BasetenChatModelId } from './baseten-chat-options';\nimport type { BasetenEmbeddingModelId } from './baseten-embedding-options';\nimport { VERSION } from './version';\n\n/**\n * Baseten's per-request embedding input limit: larger batches are rejected with\n * `413 batch size N > maximum allowed batch size 128`. It is also the native\n * performance client's own default `batchSize`. `embedMany` splits larger\n * inputs into chunks of this size and runs them in parallel.\n */\nconst MAX_EMBEDDINGS_PER_CALL = 128;\n\n/**\n * The part of `@basetenlabs/performance-client` we use, declared structurally to\n * keep that native addon out of our dependency and type graph.\n */\nexport type BasetenPerformanceClient = {\n embed(\n input: string[],\n model: string,\n ): Promise<{\n data: { embedding: number[] }[];\n usage?: { total_tokens?: number };\n }>;\n};\n\nexport type BasetenPerformanceClientConstructor = new (\n baseUrl: string,\n apiKey?: string,\n) => BasetenPerformanceClient;\n\nexport type BasetenErrorData = z.infer<typeof basetenErrorSchema>;\n\n// Baseten returns two different envelopes. The Model APIs send a bare string\n// (`{\"error\":\"please check the model you provided\"}`), while dedicated\n// deployments pass through their server's OpenAI-shaped object. Parsing only\n// the string form left dedicated-deployment errors falling back to the HTTP\n// reason phrase — \"Not Found\", or nothing at all over HTTP/2.\nconst basetenErrorSchema = z.object({\n error: z.union([\n z.string(),\n z.object({\n message: z.string(),\n object: z.string().nullish(),\n type: z.string().nullish(),\n param: z.any().nullish(),\n code: z.union([z.string(), z.number()]).nullish(),\n }),\n ]),\n});\n\nconst basetenErrorStructure: ProviderErrorStructure<BasetenErrorData> = {\n errorSchema: basetenErrorSchema,\n errorToMessage: data =>\n typeof data.error === 'string' ? data.error : data.error.message,\n};\n\nexport interface BasetenProviderSettings {\n /**\n * Baseten API key. Default value is taken from the `BASETEN_API_KEY`\n * environment variable.\n */\n apiKey?: string;\n\n /**\n * Base URL for the Model APIs. Default: 'https://inference.baseten.co/v1'\n */\n baseURL?: string;\n\n /**\n * Model URL for custom models (chat or embeddings).\n * If not supplied, the default Model APIs will be used.\n */\n modelURL?: string;\n /**\n * Custom headers to include in the requests.\n */\n headers?: Record<string, string>;\n\n /**\n * Custom fetch implementation. You can use it as a middleware to intercept requests,\n * or to provide a custom fetch implementation for e.g. testing.\n */\n fetch?: FetchFunction;\n\n /**\n * Opt in to Baseten's native performance client for embeddings, for\n * client-side batching and request hedging. Pass the `PerformanceClient`\n * constructor from `@basetenlabs/performance-client`, which you install\n * yourself:\n *\n * ```ts\n * import { PerformanceClient } from '@basetenlabs/performance-client';\n *\n * const baseten = createBaseten({ modelURL, performanceClient: PerformanceClient });\n * ```\n *\n * When omitted, embeddings go over plain HTTP to Baseten's OpenAI-compatible\n * endpoint — the default, since this NAPI addon cannot load in edge runtimes\n * and bundlers cannot resolve its platform binaries.\n */\n performanceClient?: BasetenPerformanceClientConstructor;\n}\n\nexport interface BasetenProvider extends ProviderV4 {\n /**\n * Creates a chat model for text generation.\n */\n (modelId?: BasetenChatModelId): LanguageModelV4;\n\n /**\n * Creates a chat model for text generation.\n */\n chatModel(modelId?: BasetenChatModelId): LanguageModelV4;\n\n /**\n * Creates a language model for text generation. Alias for chatModel.\n */\n languageModel(modelId?: BasetenChatModelId): LanguageModelV4;\n\n /**\n * Creates a embedding model for text generation.\n */\n embeddingModel(modelId?: BasetenEmbeddingModelId): EmbeddingModelV4;\n\n /**\n * @deprecated Use `embeddingModel` instead.\n */\n textEmbeddingModel(modelId?: BasetenEmbeddingModelId): EmbeddingModelV4;\n}\n\n// by default, we use the Model APIs\nconst defaultBaseURL = 'https://inference.baseten.co/v1';\n\nexport function createBaseten(\n options: BasetenProviderSettings = {},\n): BasetenProvider {\n const baseURL = withoutTrailingSlash(options.baseURL ?? defaultBaseURL);\n const getHeaders = () =>\n withUserAgentSuffix(\n {\n Authorization: `Bearer ${loadApiKey({\n apiKey: options.apiKey,\n environmentVariableName: 'BASETEN_API_KEY',\n description: 'Baseten API key',\n })}`,\n ...options.headers,\n },\n `ai-sdk/baseten/${VERSION}`,\n );\n\n interface CommonModelConfig {\n provider: string;\n url: ({ path }: { path: string }) => string;\n headers: () => Record<string, string>;\n fetch?: FetchFunction;\n }\n\n const getCommonModelConfig = (\n modelType: string,\n customURL?: string,\n ): CommonModelConfig => ({\n provider: `baseten.${modelType}`,\n url: ({ path }) => {\n // For embeddings with /sync URLs (but not /sync/v1), we need to add /v1\n if (\n modelType === 'embedding' &&\n customURL?.includes('/sync') &&\n !customURL?.includes('/sync/v1')\n ) {\n return `${customURL}/v1${path}`;\n }\n return `${customURL || baseURL}${path}`;\n },\n headers: getHeaders,\n fetch: options.fetch,\n });\n\n const createChatModel = (modelId?: BasetenChatModelId) => {\n const customURL = options.modelURL;\n if (customURL) {\n if (customURL.includes('/sync/v1')) {\n return new OpenAICompatibleChatLanguageModel(modelId ?? 'placeholder', {\n ...getCommonModelConfig('chat', customURL),\n errorStructure: basetenErrorStructure,\n // Or stream_options.include_usage is omitted and streams report no usage.\n includeUsage: true,\n });\n } else if (customURL.includes('/predict')) {\n throw new Error(\n 'Not supported. You must use a /sync/v1 endpoint for chat models.',\n );\n }\n }\n\n return new OpenAICompatibleChatLanguageModel(modelId ?? 'chat', {\n ...getCommonModelConfig('chat'),\n errorStructure: basetenErrorStructure,\n includeUsage: true,\n });\n };\n\n const createEmbeddingModel = (modelId?: BasetenEmbeddingModelId) => {\n const customURL = options.modelURL;\n if (!customURL) {\n throw new Error(\n 'No model URL provided for embeddings. Please set modelURL option for embeddings.',\n );\n }\n\n if (!customURL.includes('/sync')) {\n throw new Error(\n 'Not supported. You must use a /sync or /sync/v1 endpoint for embeddings.',\n );\n }\n\n // BEI embedding deployments are OpenAI-compatible with no extra settings, so\n // plain HTTP is the default and needs no override.\n const model = new OpenAICompatibleEmbeddingModel(modelId ?? 'embeddings', {\n ...getCommonModelConfig('embedding', customURL),\n errorStructure: basetenErrorStructure,\n // Over HTTP, cap each request and let `embedMany` split and parallelise.\n // The native client does its own batching, so let it take everything at\n // once — `embedMany` treats Infinity as \"one call\".\n maxEmbeddingsPerCall: options.performanceClient\n ? Number.POSITIVE_INFINITY\n : MAX_EMBEDDINGS_PER_CALL,\n });\n\n if (!options.performanceClient) {\n return model;\n }\n\n // Opted in to the native client. It appends /v1 itself, so hand it the bare\n // /sync form.\n const performanceClient = new options.performanceClient(\n customURL.replace('/sync/v1', '/sync'),\n loadApiKey({\n apiKey: options.apiKey,\n environmentVariableName: 'BASETEN_API_KEY',\n description: 'Baseten API key',\n }),\n );\n\n model.doEmbed = async params => {\n if (!params.values || !Array.isArray(params.values)) {\n throw new Error('params.values must be an array of strings');\n }\n\n const response = await performanceClient.embed(\n params.values,\n // model_id is for Model APIs; dedicated deployments ignore it.\n modelId ?? 'embeddings',\n );\n\n return {\n embeddings: response.data.map(item => item.embedding),\n // The native client types its response as `any`; only report usage when\n // a token count is actually present rather than `{ tokens: undefined }`.\n usage:\n typeof response.usage?.total_tokens === 'number'\n ? { tokens: response.usage.total_tokens }\n : undefined,\n response: { headers: {}, body: response },\n warnings: [],\n };\n };\n\n return model;\n };\n\n const provider = (modelId?: BasetenChatModelId) => createChatModel(modelId);\n\n provider.specificationVersion = 'v4' as const;\n provider.chatModel = createChatModel;\n provider.languageModel = createChatModel;\n provider.imageModel = (modelId: string) => {\n throw new NoSuchModelError({ modelId, modelType: 'imageModel' });\n };\n provider.embeddingModel = createEmbeddingModel;\n provider.textEmbeddingModel = createEmbeddingModel;\n return provider;\n}\n\nexport const baseten = createBaseten();\n","// Version string of this package injected at build time.\ndeclare const __PACKAGE_VERSION__: string | undefined;\nexport const VERSION: string =\n typeof __PACKAGE_VERSION__ !== 'undefined'\n ? __PACKAGE_VERSION__\n : '0.0.0-test';\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,SAAS;;;ACfX,IAAM,UACX,OACI,UACA;;;ADuBN,IAAM,0BAA0B;AA4BhC,IAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,OAAO,EAAE,MAAM;AAAA,IACb,EAAE,OAAO;AAAA,IACT,EAAE,OAAO;AAAA,MACP,SAAS,EAAE,OAAO;AAAA,MAClB,QAAQ,EAAE,OAAO,EAAE,QAAQ;AAAA,MAC3B,MAAM,EAAE,OAAO,EAAE,QAAQ;AAAA,MACzB,OAAO,EAAE,IAAI,EAAE,QAAQ;AAAA,MACvB,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,QAAQ;AAAA,IAClD,CAAC;AAAA,EACH,CAAC;AACH,CAAC;AAED,IAAM,wBAAkE;AAAA,EACtE,aAAa;AAAA,EACb,gBAAgB,UACd,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAK,MAAM;AAC7D;AA6EA,IAAM,iBAAiB;AAEhB,SAAS,cACd,UAAmC,CAAC,GACnB;AA1JnB;AA2JE,QAAM,UAAU,sBAAqB,aAAQ,YAAR,YAAmB,cAAc;AACtE,QAAM,aAAa,MACjB;AAAA,IACE;AAAA,MACE,eAAe,UAAU,WAAW;AAAA,QAClC,QAAQ,QAAQ;AAAA,QAChB,yBAAyB;AAAA,QACzB,aAAa;AAAA,MACf,CAAC,CAAC;AAAA,MACF,GAAG,QAAQ;AAAA,IACb;AAAA,IACA,kBAAkB,OAAO;AAAA,EAC3B;AASF,QAAM,uBAAuB,CAC3B,WACA,eACuB;AAAA,IACvB,UAAU,WAAW,SAAS;AAAA,IAC9B,KAAK,CAAC,EAAE,KAAK,MAAM;AAEjB,UACE,cAAc,gBACd,uCAAW,SAAS,aACpB,EAAC,uCAAW,SAAS,cACrB;AACA,eAAO,GAAG,SAAS,MAAM,IAAI;AAAA,MAC/B;AACA,aAAO,GAAG,aAAa,OAAO,GAAG,IAAI;AAAA,IACvC;AAAA,IACA,SAAS;AAAA,IACT,OAAO,QAAQ;AAAA,EACjB;AAEA,QAAM,kBAAkB,CAAC,YAAiC;AACxD,UAAM,YAAY,QAAQ;AAC1B,QAAI,WAAW;AACb,UAAI,UAAU,SAAS,UAAU,GAAG;AAClC,eAAO,IAAI,kCAAkC,4BAAW,eAAe;AAAA,UACrE,GAAG,qBAAqB,QAAQ,SAAS;AAAA,UACzC,gBAAgB;AAAA;AAAA,UAEhB,cAAc;AAAA,QAChB,CAAC;AAAA,MACH,WAAW,UAAU,SAAS,UAAU,GAAG;AACzC,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO,IAAI,kCAAkC,4BAAW,QAAQ;AAAA,MAC9D,GAAG,qBAAqB,MAAM;AAAA,MAC9B,gBAAgB;AAAA,MAChB,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AAEA,QAAM,uBAAuB,CAAC,YAAsC;AAClE,UAAM,YAAY,QAAQ;AAC1B,QAAI,CAAC,WAAW;AACd,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,UAAU,SAAS,OAAO,GAAG;AAChC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAIA,UAAM,QAAQ,IAAI,+BAA+B,4BAAW,cAAc;AAAA,MACxE,GAAG,qBAAqB,aAAa,SAAS;AAAA,MAC9C,gBAAgB;AAAA;AAAA;AAAA;AAAA,MAIhB,sBAAsB,QAAQ,oBAC1B,OAAO,oBACP;AAAA,IACN,CAAC;AAED,QAAI,CAAC,QAAQ,mBAAmB;AAC9B,aAAO;AAAA,IACT;AAIA,UAAM,oBAAoB,IAAI,QAAQ;AAAA,MACpC,UAAU,QAAQ,YAAY,OAAO;AAAA,MACrC,WAAW;AAAA,QACT,QAAQ,QAAQ;AAAA,QAChB,yBAAyB;AAAA,QACzB,aAAa;AAAA,MACf,CAAC;AAAA,IACH;AAEA,UAAM,UAAU,OAAM,WAAU;AAtQpC,UAAAA;AAuQM,UAAI,CAAC,OAAO,UAAU,CAAC,MAAM,QAAQ,OAAO,MAAM,GAAG;AACnD,cAAM,IAAI,MAAM,2CAA2C;AAAA,MAC7D;AAEA,YAAM,WAAW,MAAM,kBAAkB;AAAA,QACvC,OAAO;AAAA;AAAA,QAEP,4BAAW;AAAA,MACb;AAEA,aAAO;AAAA,QACL,YAAY,SAAS,KAAK,IAAI,UAAQ,KAAK,SAAS;AAAA;AAAA;AAAA,QAGpD,OACE,SAAOA,MAAA,SAAS,UAAT,gBAAAA,IAAgB,kBAAiB,WACpC,EAAE,QAAQ,SAAS,MAAM,aAAa,IACtC;AAAA,QACN,UAAU,EAAE,SAAS,CAAC,GAAG,MAAM,SAAS;AAAA,QACxC,UAAU,CAAC;AAAA,MACb;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,CAAC,YAAiC,gBAAgB,OAAO;AAE1E,WAAS,uBAAuB;AAChC,WAAS,YAAY;AACrB,WAAS,gBAAgB;AACzB,WAAS,aAAa,CAAC,YAAoB;AACzC,UAAM,IAAI,iBAAiB,EAAE,SAAS,WAAW,aAAa,CAAC;AAAA,EACjE;AACA,WAAS,iBAAiB;AAC1B,WAAS,qBAAqB;AAC9B,SAAO;AACT;AAEO,IAAM,UAAU,cAAc;","names":["_a"]}
|
package/docs/170-baseten.mdx
CHANGED
|
@@ -54,6 +54,14 @@ You can use the following optional settings to customize the Baseten provider in
|
|
|
54
54
|
|
|
55
55
|
Custom headers to include in the requests.
|
|
56
56
|
|
|
57
|
+
- **performanceClient** _PerformanceClient constructor_
|
|
58
|
+
|
|
59
|
+
Opt in to Baseten's native performance client for embeddings, for client-side
|
|
60
|
+
batching and request hedging. Pass the `PerformanceClient` constructor from
|
|
61
|
+
`@basetenlabs/performance-client`, which you install yourself. When omitted,
|
|
62
|
+
embeddings use plain HTTP. See
|
|
63
|
+
[Native performance client](#native-performance-client-optional).
|
|
64
|
+
|
|
57
65
|
- **fetch** _(input: RequestInfo, init?: RequestInit) => Promise<Response>_
|
|
58
66
|
|
|
59
67
|
Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
|
|
@@ -112,7 +120,7 @@ const { text } = await generateText({
|
|
|
112
120
|
|
|
113
121
|
## Embedding Models
|
|
114
122
|
|
|
115
|
-
You can create models that call the Baseten embeddings API using the `.embeddingModel()` factory method.
|
|
123
|
+
You can create models that call the Baseten embeddings API using the `.embeddingModel()` factory method. Baseten Embeddings Inference deployments are OpenAI-compatible, so embeddings use plain HTTP by default with no extra dependencies.
|
|
116
124
|
|
|
117
125
|
<Note>
|
|
118
126
|
**Important:** Embedding models require a dedicated deployment with a custom
|
|
@@ -147,25 +155,48 @@ const { embeddings } = await embedMany({
|
|
|
147
155
|
});
|
|
148
156
|
```
|
|
149
157
|
|
|
158
|
+
Each request sends at most 128 values. `embedMany` splits larger inputs into
|
|
159
|
+
chunks of that size and runs them in parallel, so you can pass as many values as
|
|
160
|
+
you like.
|
|
161
|
+
|
|
150
162
|
### Endpoint Support for Embeddings
|
|
151
163
|
|
|
152
164
|
**Supported:**
|
|
153
165
|
|
|
154
|
-
- `/sync` endpoints (
|
|
155
|
-
- `/sync/v1` endpoints
|
|
166
|
+
- `/sync` endpoints (`/v1/embeddings` is appended for you)
|
|
167
|
+
- `/sync/v1` endpoints
|
|
156
168
|
|
|
157
169
|
**Not Supported:**
|
|
158
170
|
|
|
159
|
-
- `/predict` endpoints
|
|
171
|
+
- `/predict` endpoints
|
|
172
|
+
|
|
173
|
+
### Native performance client (optional)
|
|
174
|
+
|
|
175
|
+
Baseten also publishes `@basetenlabs/performance-client`, a native client that
|
|
176
|
+
adds client-side batching and request hedging on top of the server-side dynamic
|
|
177
|
+
batching your deployment already does. It is **not** installed by default: it is
|
|
178
|
+
a native addon, so it cannot load in edge runtimes and bundlers cannot resolve
|
|
179
|
+
its platform binaries.
|
|
180
|
+
|
|
181
|
+
To use it, install it yourself and pass the constructor:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
npm i @basetenlabs/performance-client
|
|
185
|
+
```
|
|
160
186
|
|
|
161
|
-
|
|
187
|
+
```ts
|
|
188
|
+
import { createBaseten } from '@ai-sdk/baseten';
|
|
189
|
+
import { PerformanceClient } from '@basetenlabs/performance-client';
|
|
162
190
|
|
|
163
|
-
|
|
191
|
+
const baseten = createBaseten({
|
|
192
|
+
modelURL:
|
|
193
|
+
'https://model-{MODEL_ID}.api.baseten.co/environments/production/sync',
|
|
194
|
+
performanceClient: PerformanceClient,
|
|
195
|
+
});
|
|
196
|
+
```
|
|
164
197
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
- **Connection reuse**: Performance Client is created once and reused for all requests
|
|
168
|
-
- **Built-in retries**: Automatic retry logic for failed requests
|
|
198
|
+
When you opt in, the client handles batching itself, so values are sent in a
|
|
199
|
+
single call rather than being split at 128.
|
|
169
200
|
|
|
170
201
|
## Error Handling
|
|
171
202
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/baseten",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -29,16 +29,16 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@
|
|
33
|
-
"@ai-sdk/openai-compatible": "3.0.21",
|
|
32
|
+
"@ai-sdk/openai-compatible": "3.0.22",
|
|
34
33
|
"@ai-sdk/provider": "4.0.5",
|
|
35
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
34
|
+
"@ai-sdk/provider-utils": "5.0.20"
|
|
36
35
|
},
|
|
37
36
|
"devDependencies": {
|
|
38
37
|
"@types/node": "22.19.19",
|
|
39
38
|
"tsup": "^8.5.1",
|
|
40
39
|
"typescript": "5.8.3",
|
|
41
40
|
"zod": "3.25.76",
|
|
41
|
+
"@ai-sdk/test-server": "2.0.1",
|
|
42
42
|
"@vercel/ai-tsconfig": "0.0.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
package/src/baseten-provider.ts
CHANGED
|
@@ -18,18 +18,59 @@ import {
|
|
|
18
18
|
import { z } from 'zod/v4';
|
|
19
19
|
import type { BasetenChatModelId } from './baseten-chat-options';
|
|
20
20
|
import type { BasetenEmbeddingModelId } from './baseten-embedding-options';
|
|
21
|
-
import { PerformanceClient } from '@basetenlabs/performance-client';
|
|
22
21
|
import { VERSION } from './version';
|
|
23
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Baseten's per-request embedding input limit: larger batches are rejected with
|
|
25
|
+
* `413 batch size N > maximum allowed batch size 128`. It is also the native
|
|
26
|
+
* performance client's own default `batchSize`. `embedMany` splits larger
|
|
27
|
+
* inputs into chunks of this size and runs them in parallel.
|
|
28
|
+
*/
|
|
29
|
+
const MAX_EMBEDDINGS_PER_CALL = 128;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The part of `@basetenlabs/performance-client` we use, declared structurally to
|
|
33
|
+
* keep that native addon out of our dependency and type graph.
|
|
34
|
+
*/
|
|
35
|
+
export type BasetenPerformanceClient = {
|
|
36
|
+
embed(
|
|
37
|
+
input: string[],
|
|
38
|
+
model: string,
|
|
39
|
+
): Promise<{
|
|
40
|
+
data: { embedding: number[] }[];
|
|
41
|
+
usage?: { total_tokens?: number };
|
|
42
|
+
}>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type BasetenPerformanceClientConstructor = new (
|
|
46
|
+
baseUrl: string,
|
|
47
|
+
apiKey?: string,
|
|
48
|
+
) => BasetenPerformanceClient;
|
|
49
|
+
|
|
24
50
|
export type BasetenErrorData = z.infer<typeof basetenErrorSchema>;
|
|
25
51
|
|
|
52
|
+
// Baseten returns two different envelopes. The Model APIs send a bare string
|
|
53
|
+
// (`{"error":"please check the model you provided"}`), while dedicated
|
|
54
|
+
// deployments pass through their server's OpenAI-shaped object. Parsing only
|
|
55
|
+
// the string form left dedicated-deployment errors falling back to the HTTP
|
|
56
|
+
// reason phrase — "Not Found", or nothing at all over HTTP/2.
|
|
26
57
|
const basetenErrorSchema = z.object({
|
|
27
|
-
error: z.
|
|
58
|
+
error: z.union([
|
|
59
|
+
z.string(),
|
|
60
|
+
z.object({
|
|
61
|
+
message: z.string(),
|
|
62
|
+
object: z.string().nullish(),
|
|
63
|
+
type: z.string().nullish(),
|
|
64
|
+
param: z.any().nullish(),
|
|
65
|
+
code: z.union([z.string(), z.number()]).nullish(),
|
|
66
|
+
}),
|
|
67
|
+
]),
|
|
28
68
|
});
|
|
29
69
|
|
|
30
70
|
const basetenErrorStructure: ProviderErrorStructure<BasetenErrorData> = {
|
|
31
71
|
errorSchema: basetenErrorSchema,
|
|
32
|
-
errorToMessage: data =>
|
|
72
|
+
errorToMessage: data =>
|
|
73
|
+
typeof data.error === 'string' ? data.error : data.error.message,
|
|
33
74
|
};
|
|
34
75
|
|
|
35
76
|
export interface BasetenProviderSettings {
|
|
@@ -59,6 +100,24 @@ export interface BasetenProviderSettings {
|
|
|
59
100
|
* or to provide a custom fetch implementation for e.g. testing.
|
|
60
101
|
*/
|
|
61
102
|
fetch?: FetchFunction;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Opt in to Baseten's native performance client for embeddings, for
|
|
106
|
+
* client-side batching and request hedging. Pass the `PerformanceClient`
|
|
107
|
+
* constructor from `@basetenlabs/performance-client`, which you install
|
|
108
|
+
* yourself:
|
|
109
|
+
*
|
|
110
|
+
* ```ts
|
|
111
|
+
* import { PerformanceClient } from '@basetenlabs/performance-client';
|
|
112
|
+
*
|
|
113
|
+
* const baseten = createBaseten({ modelURL, performanceClient: PerformanceClient });
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* When omitted, embeddings go over plain HTTP to Baseten's OpenAI-compatible
|
|
117
|
+
* endpoint — the default, since this NAPI addon cannot load in edge runtimes
|
|
118
|
+
* and bundlers cannot resolve its platform binaries.
|
|
119
|
+
*/
|
|
120
|
+
performanceClient?: BasetenPerformanceClientConstructor;
|
|
62
121
|
}
|
|
63
122
|
|
|
64
123
|
export interface BasetenProvider extends ProviderV4 {
|
|
@@ -136,18 +195,14 @@ export function createBaseten(
|
|
|
136
195
|
});
|
|
137
196
|
|
|
138
197
|
const createChatModel = (modelId?: BasetenChatModelId) => {
|
|
139
|
-
// Use modelURL if provided, otherwise use default Model APIs
|
|
140
198
|
const customURL = options.modelURL;
|
|
141
|
-
|
|
142
199
|
if (customURL) {
|
|
143
|
-
|
|
144
|
-
const isOpenAICompatible = customURL.includes('/sync/v1');
|
|
145
|
-
|
|
146
|
-
if (isOpenAICompatible) {
|
|
147
|
-
// For /sync/v1 endpoints, use standard OpenAI-compatible format
|
|
200
|
+
if (customURL.includes('/sync/v1')) {
|
|
148
201
|
return new OpenAICompatibleChatLanguageModel(modelId ?? 'placeholder', {
|
|
149
202
|
...getCommonModelConfig('chat', customURL),
|
|
150
203
|
errorStructure: basetenErrorStructure,
|
|
204
|
+
// Or stream_options.include_usage is omitted and streams report no usage.
|
|
205
|
+
includeUsage: true,
|
|
151
206
|
});
|
|
152
207
|
} else if (customURL.includes('/predict')) {
|
|
153
208
|
throw new Error(
|
|
@@ -156,15 +211,14 @@ export function createBaseten(
|
|
|
156
211
|
}
|
|
157
212
|
}
|
|
158
213
|
|
|
159
|
-
// Use default OpenAI-compatible format for Model APIs
|
|
160
214
|
return new OpenAICompatibleChatLanguageModel(modelId ?? 'chat', {
|
|
161
215
|
...getCommonModelConfig('chat'),
|
|
162
216
|
errorStructure: basetenErrorStructure,
|
|
217
|
+
includeUsage: true,
|
|
163
218
|
});
|
|
164
219
|
};
|
|
165
220
|
|
|
166
221
|
const createEmbeddingModel = (modelId?: BasetenEmbeddingModelId) => {
|
|
167
|
-
// Use modelURL if provided
|
|
168
222
|
const customURL = options.modelURL;
|
|
169
223
|
if (!customURL) {
|
|
170
224
|
throw new Error(
|
|
@@ -172,63 +226,65 @@ export function createBaseten(
|
|
|
172
226
|
);
|
|
173
227
|
}
|
|
174
228
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
},
|
|
229
|
+
if (!customURL.includes('/sync')) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
'Not supported. You must use a /sync or /sync/v1 endpoint for embeddings.',
|
|
187
232
|
);
|
|
233
|
+
}
|
|
188
234
|
|
|
189
|
-
|
|
190
|
-
|
|
235
|
+
// BEI embedding deployments are OpenAI-compatible with no extra settings, so
|
|
236
|
+
// plain HTTP is the default and needs no override.
|
|
237
|
+
const model = new OpenAICompatibleEmbeddingModel(modelId ?? 'embeddings', {
|
|
238
|
+
...getCommonModelConfig('embedding', customURL),
|
|
239
|
+
errorStructure: basetenErrorStructure,
|
|
240
|
+
// Over HTTP, cap each request and let `embedMany` split and parallelise.
|
|
241
|
+
// The native client does its own batching, so let it take everything at
|
|
242
|
+
// once — `embedMany` treats Infinity as "one call".
|
|
243
|
+
maxEmbeddingsPerCall: options.performanceClient
|
|
244
|
+
? Number.POSITIVE_INFINITY
|
|
245
|
+
: MAX_EMBEDDINGS_PER_CALL,
|
|
246
|
+
});
|
|
191
247
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
loadApiKey({
|
|
196
|
-
apiKey: options.apiKey,
|
|
197
|
-
environmentVariableName: 'BASETEN_API_KEY',
|
|
198
|
-
description: 'Baseten API key',
|
|
199
|
-
}),
|
|
200
|
-
);
|
|
248
|
+
if (!options.performanceClient) {
|
|
249
|
+
return model;
|
|
250
|
+
}
|
|
201
251
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
252
|
+
// Opted in to the native client. It appends /v1 itself, so hand it the bare
|
|
253
|
+
// /sync form.
|
|
254
|
+
const performanceClient = new options.performanceClient(
|
|
255
|
+
customURL.replace('/sync/v1', '/sync'),
|
|
256
|
+
loadApiKey({
|
|
257
|
+
apiKey: options.apiKey,
|
|
258
|
+
environmentVariableName: 'BASETEN_API_KEY',
|
|
259
|
+
description: 'Baseten API key',
|
|
260
|
+
}),
|
|
261
|
+
);
|
|
207
262
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
);
|
|
213
|
-
// Transform the response to match the expected format
|
|
214
|
-
const embeddings = response.data.map((item: any) => item.embedding);
|
|
263
|
+
model.doEmbed = async params => {
|
|
264
|
+
if (!params.values || !Array.isArray(params.values)) {
|
|
265
|
+
throw new Error('params.values must be an array of strings');
|
|
266
|
+
}
|
|
215
267
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
268
|
+
const response = await performanceClient.embed(
|
|
269
|
+
params.values,
|
|
270
|
+
// model_id is for Model APIs; dedicated deployments ignore it.
|
|
271
|
+
modelId ?? 'embeddings',
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
embeddings: response.data.map(item => item.embedding),
|
|
276
|
+
// The native client types its response as `any`; only report usage when
|
|
277
|
+
// a token count is actually present rather than `{ tokens: undefined }`.
|
|
278
|
+
usage:
|
|
279
|
+
typeof response.usage?.total_tokens === 'number'
|
|
219
280
|
? { tokens: response.usage.total_tokens }
|
|
220
281
|
: undefined,
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
};
|
|
282
|
+
response: { headers: {}, body: response },
|
|
283
|
+
warnings: [],
|
|
224
284
|
};
|
|
285
|
+
};
|
|
225
286
|
|
|
226
|
-
|
|
227
|
-
} else {
|
|
228
|
-
throw new Error(
|
|
229
|
-
'Not supported. You must use a /sync or /sync/v1 endpoint for embeddings.',
|
|
230
|
-
);
|
|
231
|
-
}
|
|
287
|
+
return model;
|
|
232
288
|
};
|
|
233
289
|
|
|
234
290
|
const provider = (modelId?: BasetenChatModelId) => createChatModel(modelId);
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,8 @@ export type {
|
|
|
4
4
|
BasetenProvider,
|
|
5
5
|
BasetenProviderSettings,
|
|
6
6
|
BasetenErrorData,
|
|
7
|
+
BasetenPerformanceClient,
|
|
8
|
+
BasetenPerformanceClientConstructor,
|
|
7
9
|
} from './baseten-provider';
|
|
8
10
|
export type { BasetenEmbeddingModelOptions } from './baseten-embedding-options';
|
|
9
11
|
export { VERSION } from './version';
|