@ai-sdk/baseten 2.0.21 → 2.1.1

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 CHANGED
@@ -1,5 +1,49 @@
1
1
  # @ai-sdk/baseten
2
2
 
3
+ ## 2.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [1bec07d]
8
+ - @ai-sdk/provider-utils@5.0.21
9
+ - @ai-sdk/openai-compatible@3.0.23
10
+
11
+ ## 2.1.0
12
+
13
+ ### Minor Changes
14
+
15
+ - 11f00aa: Make the native performance client opt-in for embeddings.
16
+
17
+ `@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.
18
+
19
+ 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:
20
+
21
+ ```ts
22
+ import { createBaseten } from "@ai-sdk/baseten";
23
+ import { PerformanceClient } from "@basetenlabs/performance-client";
24
+
25
+ const baseten = createBaseten({
26
+ modelURL,
27
+ performanceClient: PerformanceClient,
28
+ });
29
+ ```
30
+
31
+ 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.
32
+
33
+ `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.
34
+
35
+ 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.
36
+
37
+ 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.
38
+
39
+ 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.
40
+
41
+ ### Patch Changes
42
+
43
+ - Updated dependencies [160ccdb]
44
+ - @ai-sdk/provider-utils@5.0.20
45
+ - @ai-sdk/openai-compatible@3.0.22
46
+
3
47
  ## 2.0.21
4
48
 
5
49
  ### 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.21" : "0.0.0-test";
17
+ var VERSION = true ? "2.1.1" : "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.string()
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
- const isOpenAICompatible = customURL.includes("/sync/v1");
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
- const isOpenAICompatible = customURL.includes("/sync");
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":"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","names":["_a"]}
@@ -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&lt;Response&gt;_
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. The Baseten provider uses the high-performance `@basetenlabs/performance-client` for optimal embedding performance.
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 (Performance Client automatically adds `/v1/embeddings`)
155
- - `/sync/v1` endpoints (automatically strips `/v1` before passing to Performance Client)
166
+ - `/sync` endpoints (`/v1/embeddings` is appended for you)
167
+ - `/sync/v1` endpoints
156
168
 
157
169
  **Not Supported:**
158
170
 
159
- - `/predict` endpoints (not compatible with Performance Client)
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
- ### Performance Features
187
+ ```ts
188
+ import { createBaseten } from '@ai-sdk/baseten';
189
+ import { PerformanceClient } from '@basetenlabs/performance-client';
162
190
 
163
- The embedding implementation includes:
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
- - **High-performance client**: Uses `@basetenlabs/performance-client` for optimal performance
166
- - **Automatic batching**: Efficiently handles multiple texts in a single request
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.21",
3
+ "version": "2.1.1",
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
- "@basetenlabs/performance-client": "^0.0.10",
33
- "@ai-sdk/openai-compatible": "3.0.21",
32
+ "@ai-sdk/openai-compatible": "3.0.23",
34
33
  "@ai-sdk/provider": "4.0.5",
35
- "@ai-sdk/provider-utils": "5.0.19"
34
+ "@ai-sdk/provider-utils": "5.0.21"
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": {
@@ -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.string(),
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 => data.error,
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
- // 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
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
- // 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
- },
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
- // Strip /v1 from URL if present before passing to Performance Client to avoid double /v1
190
- const performanceClientURL = customURL.replace('/sync/v1', '/sync');
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
- // 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
- );
248
+ if (!options.performanceClient) {
249
+ return model;
250
+ }
201
251
 
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
- }
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
- // 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);
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
- return {
217
- embeddings,
218
- usage: response.usage
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
- response: { headers: {}, body: response },
222
- warnings: [],
223
- };
282
+ response: { headers: {}, body: response },
283
+ warnings: [],
224
284
  };
285
+ };
225
286
 
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
- }
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';