@ai-sdk/xai 4.0.44 → 4.0.47
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 +26 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1112 -633
- package/dist/index.js.map +1 -1
- package/docs/01-xai.mdx +60 -0
- package/package.json +2 -2
- package/src/responses/xai-responses-api.ts +22 -18
- package/src/responses/xai-responses-batch.ts +558 -0
- package/src/responses/xai-responses-language-model.ts +109 -3
- package/src/xai-chat-language-model.ts +29 -22
- package/src/xai-provider.ts +6 -5
- package/src/xai-video-model.ts +12 -1
package/docs/01-xai.mdx
CHANGED
|
@@ -625,6 +625,66 @@ The following provider options are available:
|
|
|
625
625
|
tools with client-side function tools in the same request.
|
|
626
626
|
</Note>
|
|
627
627
|
|
|
628
|
+
## Text Batches
|
|
629
|
+
|
|
630
|
+
<Note type="warning">
|
|
631
|
+
Text batch support is experimental and the API may change in patch releases.
|
|
632
|
+
</Note>
|
|
633
|
+
|
|
634
|
+
The xAI provider supports asynchronous text generation through the
|
|
635
|
+
[Batch API](https://docs.x.ai/developers/advanced-api-usage/batch-api). Use the
|
|
636
|
+
experimental text batch APIs to start a batch, poll its status, and stream its
|
|
637
|
+
results:
|
|
638
|
+
|
|
639
|
+
```ts
|
|
640
|
+
import { xai } from '@ai-sdk/xai';
|
|
641
|
+
import {
|
|
642
|
+
experimental_getBatchResults as getBatchResults,
|
|
643
|
+
experimental_getBatchStatus as getBatchStatus,
|
|
644
|
+
experimental_startTextBatch as startTextBatch,
|
|
645
|
+
} from 'ai';
|
|
646
|
+
import { setTimeout } from 'node:timers/promises';
|
|
647
|
+
|
|
648
|
+
const model = xai('grok-4.3');
|
|
649
|
+
|
|
650
|
+
const batch = await startTextBatch({
|
|
651
|
+
model,
|
|
652
|
+
requests: [
|
|
653
|
+
{ id: 'capital-france', prompt: 'What is the capital of France?' },
|
|
654
|
+
{ id: 'capital-germany', prompt: 'What is the capital of Germany?' },
|
|
655
|
+
],
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
let status = batch.status;
|
|
659
|
+
while (status === 'pending') {
|
|
660
|
+
await setTimeout(60_000);
|
|
661
|
+
({ status } = await getBatchStatus({ model, batch }));
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
for await (const item of getBatchResults({ model, batch })) {
|
|
665
|
+
if (item.status === 'succeeded') {
|
|
666
|
+
console.log(item.id, item.text);
|
|
667
|
+
} else {
|
|
668
|
+
console.error(item.id, item.error);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
`startTextBatch` returns a serializable batch reference. Persist this reference
|
|
674
|
+
to check the batch status or retrieve its results from another process. Results
|
|
675
|
+
can arrive in a different order from the input requests, so match each result by
|
|
676
|
+
its `id`.
|
|
677
|
+
|
|
678
|
+
Text batches are available through `xai(modelId)` and
|
|
679
|
+
`xai.responses(modelId)`. The legacy `xai.chat(modelId)` models do not expose
|
|
680
|
+
the batch APIs.
|
|
681
|
+
|
|
682
|
+
<Note>
|
|
683
|
+
The xAI Batch API does not support per-batch webhooks. When you provide a
|
|
684
|
+
`webhookUrl`, the provider returns an unsupported warning and starts the batch
|
|
685
|
+
without a webhook.
|
|
686
|
+
</Note>
|
|
687
|
+
|
|
628
688
|
## Model Capabilities
|
|
629
689
|
|
|
630
690
|
| Model | Image Input | Object Generation | Tool Usage | Tool Streaming | Reasoning |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/xai",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.47",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@ai-sdk/provider": "4.0.8",
|
|
33
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
33
|
+
"@ai-sdk/provider-utils": "5.0.32"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@ai-sdk/test-server": "2.0.1",
|
|
@@ -260,24 +260,28 @@ const outputItemSchema = z.discriminatedUnion('type', [
|
|
|
260
260
|
}),
|
|
261
261
|
]);
|
|
262
262
|
|
|
263
|
-
export const xaiResponsesUsageSchema = z
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
263
|
+
export const xaiResponsesUsageSchema = z
|
|
264
|
+
.object({
|
|
265
|
+
input_tokens: z.number(),
|
|
266
|
+
output_tokens: z.number(),
|
|
267
|
+
total_tokens: z.number().optional(),
|
|
268
|
+
input_tokens_details: z
|
|
269
|
+
.object({
|
|
270
|
+
cached_tokens: z.number().optional(),
|
|
271
|
+
})
|
|
272
|
+
.catchall(z.json())
|
|
273
|
+
.optional(),
|
|
274
|
+
output_tokens_details: z
|
|
275
|
+
.object({
|
|
276
|
+
reasoning_tokens: z.number().optional(),
|
|
277
|
+
})
|
|
278
|
+
.catchall(z.json())
|
|
279
|
+
.optional(),
|
|
280
|
+
num_sources_used: z.number().optional(),
|
|
281
|
+
num_server_side_tools_used: z.number().optional(),
|
|
282
|
+
cost_in_usd_ticks: z.number().nullish(),
|
|
283
|
+
})
|
|
284
|
+
.catchall(z.json());
|
|
281
285
|
|
|
282
286
|
export const xaiResponsesResponseSchema = z.object({
|
|
283
287
|
id: z.string().nullish(),
|
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InvalidArgumentError,
|
|
3
|
+
type Experimental_BatchLanguageModelV4 as BatchLanguageModelV4,
|
|
4
|
+
type Experimental_BatchV4Error as BatchV4Error,
|
|
5
|
+
type Experimental_BatchV4ItemResult as BatchV4ItemResult,
|
|
6
|
+
type Experimental_BatchV4OperationOptions as BatchV4OperationOptions,
|
|
7
|
+
type Experimental_BatchV4StartOptions as BatchV4StartOptions,
|
|
8
|
+
type Experimental_BatchV4StartResult as BatchV4StartResult,
|
|
9
|
+
type Experimental_BatchV4Status as BatchV4Status,
|
|
10
|
+
type LanguageModelV4Content,
|
|
11
|
+
type LanguageModelV4GenerateResult,
|
|
12
|
+
type SharedV4ProviderMetadata,
|
|
13
|
+
type SharedV4Warning,
|
|
14
|
+
} from '@ai-sdk/provider';
|
|
15
|
+
import {
|
|
16
|
+
combineHeaders,
|
|
17
|
+
convertAsyncIteratorToReadableStream,
|
|
18
|
+
createJsonResponseHandler,
|
|
19
|
+
createNullLanguageModelUsage,
|
|
20
|
+
getFromApi,
|
|
21
|
+
lazySchema,
|
|
22
|
+
normalizeBatchRequestCounts,
|
|
23
|
+
postFormDataToApi,
|
|
24
|
+
postJsonToApi,
|
|
25
|
+
safeValidateTypes,
|
|
26
|
+
WORKFLOW_DESERIALIZE,
|
|
27
|
+
WORKFLOW_SERIALIZE,
|
|
28
|
+
zodSchema,
|
|
29
|
+
type InferSchema,
|
|
30
|
+
} from '@ai-sdk/provider-utils';
|
|
31
|
+
import { z } from 'zod/v4';
|
|
32
|
+
import { convertXaiChatUsage } from '../convert-xai-chat-usage';
|
|
33
|
+
import { getResponseMetadata } from '../get-response-metadata';
|
|
34
|
+
import { mapXaiFinishReason } from '../map-xai-finish-reason';
|
|
35
|
+
import {
|
|
36
|
+
xaiChatResponseSchema,
|
|
37
|
+
type XaiChatResponse,
|
|
38
|
+
} from '../xai-chat-language-model';
|
|
39
|
+
import { xaiFailedResponseHandler } from '../xai-error';
|
|
40
|
+
import { xaiFilesResponseSchema } from '../files/xai-files-api';
|
|
41
|
+
import {
|
|
42
|
+
XaiResponsesLanguageModel,
|
|
43
|
+
type XaiResponsesConfig,
|
|
44
|
+
} from './xai-responses-language-model';
|
|
45
|
+
import type { XaiResponsesModelId } from './xai-responses-language-model-options';
|
|
46
|
+
|
|
47
|
+
const xaiBatchEndpoint = '/v1/responses';
|
|
48
|
+
const xaiBatchName = 'ai-sdk-text-batch';
|
|
49
|
+
const xaiBatchResultsPageSize = 1000;
|
|
50
|
+
|
|
51
|
+
type XaiBatchRequest = Parameters<
|
|
52
|
+
BatchLanguageModelV4['experimental_doStartBatch']
|
|
53
|
+
>[0]['requests'][number];
|
|
54
|
+
|
|
55
|
+
type XaiBatchPreparedRequest = {
|
|
56
|
+
body: unknown;
|
|
57
|
+
warnings: SharedV4Warning[];
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type XaiBatchResponseConversion =
|
|
61
|
+
| { success: true; result: LanguageModelV4GenerateResult }
|
|
62
|
+
| { success: false; error: BatchV4Error };
|
|
63
|
+
|
|
64
|
+
const xaiBatchResponseSchema = lazySchema(() =>
|
|
65
|
+
zodSchema(
|
|
66
|
+
z.object({
|
|
67
|
+
batch_id: z.string(),
|
|
68
|
+
name: z.string().nullish(),
|
|
69
|
+
create_time: z.string().nullish(),
|
|
70
|
+
expire_time: z.string().nullish(),
|
|
71
|
+
cancel_time: z.string().nullish(),
|
|
72
|
+
cancel_by_xai_message: z.string().nullish(),
|
|
73
|
+
state: z
|
|
74
|
+
.object({
|
|
75
|
+
num_requests: z.number().nullish(),
|
|
76
|
+
num_pending: z.number().nullish(),
|
|
77
|
+
num_success: z.number().nullish(),
|
|
78
|
+
num_error: z.number().nullish(),
|
|
79
|
+
num_cancelled: z.number().nullish(),
|
|
80
|
+
})
|
|
81
|
+
.nullish(),
|
|
82
|
+
}),
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
type XaiBatchResponse = InferSchema<typeof xaiBatchResponseSchema>;
|
|
87
|
+
|
|
88
|
+
const xaiBatchErrorSchema = z.object({
|
|
89
|
+
code: z.union([z.string(), z.number()]).nullish(),
|
|
90
|
+
message: z.string().nullish(),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const xaiBatchResultSchema = z.object({
|
|
94
|
+
batch_request_id: z.string(),
|
|
95
|
+
batch_result: z
|
|
96
|
+
.object({
|
|
97
|
+
response: z
|
|
98
|
+
.object({
|
|
99
|
+
chat_get_completion: z.unknown().nullish(),
|
|
100
|
+
})
|
|
101
|
+
.nullish(),
|
|
102
|
+
error: xaiBatchErrorSchema.nullish(),
|
|
103
|
+
})
|
|
104
|
+
.nullish(),
|
|
105
|
+
error_message: z.string().nullish(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
type XaiBatchResult = z.infer<typeof xaiBatchResultSchema>;
|
|
109
|
+
|
|
110
|
+
const xaiBatchResultsPageSchema = lazySchema(() =>
|
|
111
|
+
zodSchema(
|
|
112
|
+
z.object({
|
|
113
|
+
results: z.array(xaiBatchResultSchema),
|
|
114
|
+
pagination_token: z.string().nullish(),
|
|
115
|
+
}),
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
class XaiResponsesBatch {
|
|
120
|
+
constructor(
|
|
121
|
+
private readonly options: {
|
|
122
|
+
config: XaiResponsesConfig;
|
|
123
|
+
prepareRequest: (
|
|
124
|
+
request: XaiBatchRequest,
|
|
125
|
+
) => PromiseLike<XaiBatchPreparedRequest>;
|
|
126
|
+
},
|
|
127
|
+
) {}
|
|
128
|
+
|
|
129
|
+
async startBatch(
|
|
130
|
+
options: BatchV4StartOptions<XaiBatchRequest>,
|
|
131
|
+
): Promise<BatchV4StartResult> {
|
|
132
|
+
const fileParts: string[] = [];
|
|
133
|
+
const warnings: BatchV4StartResult['warnings'] =
|
|
134
|
+
options.webhookUrl == null
|
|
135
|
+
? []
|
|
136
|
+
: [
|
|
137
|
+
{
|
|
138
|
+
warning: {
|
|
139
|
+
type: 'unsupported',
|
|
140
|
+
feature: 'webhookUrl',
|
|
141
|
+
details:
|
|
142
|
+
'The xAI Batch API does not support per-batch webhook URLs.',
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
for (const request of options.requests) {
|
|
148
|
+
const preparedRequest = await this.options.prepareRequest(request);
|
|
149
|
+
|
|
150
|
+
fileParts.push(
|
|
151
|
+
JSON.stringify({
|
|
152
|
+
custom_id: request.id,
|
|
153
|
+
method: 'POST',
|
|
154
|
+
url: xaiBatchEndpoint,
|
|
155
|
+
body: preparedRequest.body,
|
|
156
|
+
}),
|
|
157
|
+
'\n',
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
for (const warning of preparedRequest.warnings) {
|
|
161
|
+
warnings.push({ requestId: request.id, warning });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const filename = 'batch.jsonl';
|
|
166
|
+
const file = new Blob(fileParts, { type: 'application/jsonl' });
|
|
167
|
+
fileParts.length = 0;
|
|
168
|
+
const formData = new FormData();
|
|
169
|
+
formData.append('file', file, filename);
|
|
170
|
+
|
|
171
|
+
const headers = combineHeaders(
|
|
172
|
+
this.options.config.headers?.(),
|
|
173
|
+
options.headers,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const { value: uploadedFile } = await postFormDataToApi({
|
|
177
|
+
url: this.getUrl('/files'),
|
|
178
|
+
headers,
|
|
179
|
+
formData,
|
|
180
|
+
failedResponseHandler: xaiFailedResponseHandler,
|
|
181
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
182
|
+
xaiFilesResponseSchema,
|
|
183
|
+
),
|
|
184
|
+
abortSignal: options.abortSignal,
|
|
185
|
+
fetch: this.options.config.fetch,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const { value: batch } = await postJsonToApi({
|
|
189
|
+
url: this.getUrl('/batches'),
|
|
190
|
+
headers,
|
|
191
|
+
body: {
|
|
192
|
+
name: xaiBatchName,
|
|
193
|
+
input_file_id: uploadedFile.id,
|
|
194
|
+
},
|
|
195
|
+
failedResponseHandler: xaiFailedResponseHandler,
|
|
196
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
197
|
+
xaiBatchResponseSchema,
|
|
198
|
+
),
|
|
199
|
+
abortSignal: options.abortSignal,
|
|
200
|
+
fetch: this.options.config.fetch,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
batchId: batch.batch_id,
|
|
205
|
+
...convertXaiBatchStatus(batch),
|
|
206
|
+
warnings,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async getBatchStatus(
|
|
211
|
+
options: BatchV4OperationOptions,
|
|
212
|
+
): Promise<BatchV4Status> {
|
|
213
|
+
return convertXaiBatchStatus(await this.retrieveBatch(options));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async getBatchResults(
|
|
217
|
+
options: BatchV4OperationOptions,
|
|
218
|
+
): Promise<ReadableStream<BatchV4ItemResult<LanguageModelV4GenerateResult>>> {
|
|
219
|
+
const batch = await this.retrieveBatch(options);
|
|
220
|
+
if (convertXaiBatchStatus(batch).status === 'pending') {
|
|
221
|
+
throw new InvalidArgumentError({
|
|
222
|
+
argument: 'batchId',
|
|
223
|
+
message: `xAI batch "${options.batchId}" is not complete.`,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return convertAsyncIteratorToReadableStream(
|
|
228
|
+
this.iterateBatchResults(options),
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private async retrieveBatch(
|
|
233
|
+
options: BatchV4OperationOptions,
|
|
234
|
+
): Promise<XaiBatchResponse> {
|
|
235
|
+
const { value: batch } = await getFromApi({
|
|
236
|
+
url: this.getUrl(`/batches/${encodeURIComponent(options.batchId)}`),
|
|
237
|
+
headers: combineHeaders(this.options.config.headers?.(), options.headers),
|
|
238
|
+
failedResponseHandler: xaiFailedResponseHandler,
|
|
239
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
240
|
+
xaiBatchResponseSchema,
|
|
241
|
+
),
|
|
242
|
+
abortSignal: options.abortSignal,
|
|
243
|
+
fetch: this.options.config.fetch,
|
|
244
|
+
validateUrl: false,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return batch;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private async *iterateBatchResults(
|
|
251
|
+
options: BatchV4OperationOptions,
|
|
252
|
+
): AsyncGenerator<BatchV4ItemResult<LanguageModelV4GenerateResult>> {
|
|
253
|
+
let paginationToken: string | undefined;
|
|
254
|
+
|
|
255
|
+
do {
|
|
256
|
+
const query = new URLSearchParams({
|
|
257
|
+
limit: String(xaiBatchResultsPageSize),
|
|
258
|
+
});
|
|
259
|
+
if (paginationToken != null) {
|
|
260
|
+
query.set('pagination_token', paginationToken);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const { value: page } = await getFromApi({
|
|
264
|
+
url: this.getUrl(
|
|
265
|
+
`/batches/${encodeURIComponent(options.batchId)}/results?${query}`,
|
|
266
|
+
),
|
|
267
|
+
headers: combineHeaders(
|
|
268
|
+
this.options.config.headers?.(),
|
|
269
|
+
options.headers,
|
|
270
|
+
),
|
|
271
|
+
failedResponseHandler: xaiFailedResponseHandler,
|
|
272
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
273
|
+
xaiBatchResultsPageSchema,
|
|
274
|
+
),
|
|
275
|
+
abortSignal: options.abortSignal,
|
|
276
|
+
fetch: this.options.config.fetch,
|
|
277
|
+
validateUrl: false,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
for (const result of page.results) {
|
|
281
|
+
yield await this.convertBatchResult(result);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
paginationToken = page.pagination_token ?? undefined;
|
|
285
|
+
} while (paginationToken != null);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private async convertBatchResult(
|
|
289
|
+
result: XaiBatchResult,
|
|
290
|
+
): Promise<BatchV4ItemResult<LanguageModelV4GenerateResult>> {
|
|
291
|
+
const error = result.batch_result?.error;
|
|
292
|
+
if (
|
|
293
|
+
(result.error_message?.length ?? 0) > 0 ||
|
|
294
|
+
(error?.code != null && error.code !== 0 && error.code !== '0') ||
|
|
295
|
+
(error?.code == null && (error?.message?.length ?? 0) > 0)
|
|
296
|
+
) {
|
|
297
|
+
const convertedError = convertXaiBatchError(result);
|
|
298
|
+
return {
|
|
299
|
+
id: result.batch_request_id,
|
|
300
|
+
status: isXaiCancellationError(error?.code) ? 'cancelled' : 'failed',
|
|
301
|
+
error: convertedError,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// xAI returns text batch results in chat completion format, including for
|
|
306
|
+
// requests submitted to the Responses API endpoint.
|
|
307
|
+
const response = result.batch_result?.response;
|
|
308
|
+
if (response?.chat_get_completion != null) {
|
|
309
|
+
const validation = await safeValidateTypes({
|
|
310
|
+
value: response.chat_get_completion,
|
|
311
|
+
schema: xaiChatResponseSchema,
|
|
312
|
+
});
|
|
313
|
+
if (!validation.success) {
|
|
314
|
+
return invalidXaiBatchResult(result.batch_request_id);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const conversion = convertXaiChatBatchResponse(validation.value);
|
|
318
|
+
return conversion.success
|
|
319
|
+
? {
|
|
320
|
+
id: result.batch_request_id,
|
|
321
|
+
status: 'succeeded',
|
|
322
|
+
result: conversion.result,
|
|
323
|
+
}
|
|
324
|
+
: {
|
|
325
|
+
id: result.batch_request_id,
|
|
326
|
+
status: 'failed',
|
|
327
|
+
error: conversion.error,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return invalidXaiBatchResult(result.batch_request_id);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private getUrl(path: string) {
|
|
335
|
+
return `${this.options.config.baseURL ?? 'https://api.x.ai/v1'}${path}`;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export class XaiResponsesBatchLanguageModel
|
|
340
|
+
extends XaiResponsesLanguageModel
|
|
341
|
+
implements BatchLanguageModelV4
|
|
342
|
+
{
|
|
343
|
+
private readonly batch: XaiResponsesBatch;
|
|
344
|
+
|
|
345
|
+
static [WORKFLOW_SERIALIZE](model: XaiResponsesLanguageModel) {
|
|
346
|
+
return XaiResponsesLanguageModel[WORKFLOW_SERIALIZE](model);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
static [WORKFLOW_DESERIALIZE](options: {
|
|
350
|
+
modelId: XaiResponsesModelId;
|
|
351
|
+
config: XaiResponsesConfig;
|
|
352
|
+
}) {
|
|
353
|
+
return new XaiResponsesBatchLanguageModel(options.modelId, options.config);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
constructor(modelId: XaiResponsesModelId, config: XaiResponsesConfig) {
|
|
357
|
+
super(modelId, config);
|
|
358
|
+
this.batch = new XaiResponsesBatch({
|
|
359
|
+
config,
|
|
360
|
+
prepareRequest: async request => {
|
|
361
|
+
const { args: body, warnings } = await this.getArgs(request.options);
|
|
362
|
+
return { body, warnings };
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
experimental_doStartBatch(
|
|
368
|
+
options: Parameters<BatchLanguageModelV4['experimental_doStartBatch']>[0],
|
|
369
|
+
) {
|
|
370
|
+
return this.batch.startBatch(options);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
experimental_doGetBatchStatus(options: BatchV4OperationOptions) {
|
|
374
|
+
return this.batch.getBatchStatus(options);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
experimental_doGetBatchResults(options: BatchV4OperationOptions) {
|
|
378
|
+
return this.batch.getBatchResults(options);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function convertXaiBatchStatus(batch: XaiBatchResponse): BatchV4Status {
|
|
383
|
+
const requestCounts = normalizeBatchRequestCounts({
|
|
384
|
+
total: batch.state?.num_requests,
|
|
385
|
+
pending: batch.state?.num_pending,
|
|
386
|
+
completed: batch.state?.num_success,
|
|
387
|
+
failed:
|
|
388
|
+
batch.state?.num_error != null && batch.state?.num_cancelled != null
|
|
389
|
+
? batch.state.num_error + batch.state.num_cancelled
|
|
390
|
+
: undefined,
|
|
391
|
+
});
|
|
392
|
+
const isCancelled =
|
|
393
|
+
batch.cancel_time != null || batch.cancel_by_xai_message != null;
|
|
394
|
+
const isExpired = isPastDate(batch.expire_time);
|
|
395
|
+
const status: BatchV4Status['status'] =
|
|
396
|
+
isCancelled || isExpired
|
|
397
|
+
? 'failed'
|
|
398
|
+
: requestCounts != null &&
|
|
399
|
+
requestCounts.total > 0 &&
|
|
400
|
+
requestCounts.pending === 0
|
|
401
|
+
? 'completed'
|
|
402
|
+
: 'pending';
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
status,
|
|
406
|
+
...(requestCounts != null ? { requestCounts } : {}),
|
|
407
|
+
...(isCancelled
|
|
408
|
+
? {
|
|
409
|
+
error: {
|
|
410
|
+
message:
|
|
411
|
+
batch.cancel_by_xai_message ??
|
|
412
|
+
`xAI batch "${batch.batch_id}" was cancelled.`,
|
|
413
|
+
code: 'batch_cancelled',
|
|
414
|
+
},
|
|
415
|
+
}
|
|
416
|
+
: isExpired
|
|
417
|
+
? {
|
|
418
|
+
error: {
|
|
419
|
+
message: `xAI batch "${batch.batch_id}" expired.`,
|
|
420
|
+
code: 'batch_expired',
|
|
421
|
+
},
|
|
422
|
+
}
|
|
423
|
+
: {}),
|
|
424
|
+
...(batch.create_time != null ? { createdAt: batch.create_time } : {}),
|
|
425
|
+
...(batch.expire_time != null ? { expiresAt: batch.expire_time } : {}),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function isPastDate(value: string | null | undefined) {
|
|
430
|
+
if (value == null) {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
const timestamp = Date.parse(value);
|
|
434
|
+
return !Number.isNaN(timestamp) && timestamp <= Date.now();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function convertXaiBatchError(result: XaiBatchResult): BatchV4Error {
|
|
438
|
+
const error = result.batch_result?.error;
|
|
439
|
+
return {
|
|
440
|
+
message:
|
|
441
|
+
result.error_message || error?.message || 'xAI batch request failed.',
|
|
442
|
+
...(error?.code != null && error.code !== 0 && error.code !== '0'
|
|
443
|
+
? { code: String(error.code) }
|
|
444
|
+
: {}),
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function isXaiCancellationError(code: string | number | null | undefined) {
|
|
449
|
+
const normalizedCode = String(code).toLowerCase();
|
|
450
|
+
return (
|
|
451
|
+
code === 1 ||
|
|
452
|
+
normalizedCode === '1' ||
|
|
453
|
+
normalizedCode === 'cancelled' ||
|
|
454
|
+
normalizedCode === 'batch_cancelled'
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function invalidXaiBatchResult(
|
|
459
|
+
id: string,
|
|
460
|
+
): BatchV4ItemResult<LanguageModelV4GenerateResult> {
|
|
461
|
+
return {
|
|
462
|
+
id,
|
|
463
|
+
status: 'failed',
|
|
464
|
+
error: {
|
|
465
|
+
message: 'xAI returned an invalid Responses batch result.',
|
|
466
|
+
code: 'invalid_response',
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function convertXaiChatBatchResponse(
|
|
472
|
+
response: XaiChatResponse,
|
|
473
|
+
): XaiBatchResponseConversion {
|
|
474
|
+
if (response.error != null) {
|
|
475
|
+
return {
|
|
476
|
+
success: false,
|
|
477
|
+
error: {
|
|
478
|
+
message: response.error,
|
|
479
|
+
...(response.code != null ? { code: response.code } : {}),
|
|
480
|
+
},
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const choice = response.choices?.[0];
|
|
485
|
+
if (choice == null) {
|
|
486
|
+
return {
|
|
487
|
+
success: false,
|
|
488
|
+
error: {
|
|
489
|
+
message: 'xAI returned a batch response without any choices.',
|
|
490
|
+
code: 'invalid_response',
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (choice.message.tool_calls?.length) {
|
|
496
|
+
return unsupportedXaiBatchContent('tool_calls');
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const content: LanguageModelV4Content[] = [];
|
|
500
|
+
if (choice.message.content) {
|
|
501
|
+
content.push({ type: 'text', text: choice.message.content });
|
|
502
|
+
}
|
|
503
|
+
if (choice.message.reasoning_content) {
|
|
504
|
+
content.push({
|
|
505
|
+
type: 'reasoning',
|
|
506
|
+
text: choice.message.reasoning_content,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
for (const url of response.citations ?? []) {
|
|
510
|
+
content.push({
|
|
511
|
+
type: 'source',
|
|
512
|
+
sourceType: 'url',
|
|
513
|
+
id: url,
|
|
514
|
+
url,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return {
|
|
519
|
+
success: true,
|
|
520
|
+
result: {
|
|
521
|
+
content,
|
|
522
|
+
finishReason: {
|
|
523
|
+
unified: mapXaiFinishReason(choice.finish_reason),
|
|
524
|
+
raw: choice.finish_reason ?? undefined,
|
|
525
|
+
},
|
|
526
|
+
usage: response.usage
|
|
527
|
+
? convertXaiChatUsage(response.usage)
|
|
528
|
+
: createNullLanguageModelUsage(),
|
|
529
|
+
response: getResponseMetadata(response),
|
|
530
|
+
warnings: [],
|
|
531
|
+
...((response.usage?.cost_in_usd_ticks != null ||
|
|
532
|
+
response.service_tier != null) && {
|
|
533
|
+
providerMetadata: {
|
|
534
|
+
xai: {
|
|
535
|
+
...(response.usage?.cost_in_usd_ticks != null
|
|
536
|
+
? { costInUsdTicks: response.usage.cost_in_usd_ticks }
|
|
537
|
+
: {}),
|
|
538
|
+
...(response.service_tier != null
|
|
539
|
+
? { serviceTier: response.service_tier }
|
|
540
|
+
: {}),
|
|
541
|
+
},
|
|
542
|
+
} satisfies SharedV4ProviderMetadata,
|
|
543
|
+
}),
|
|
544
|
+
},
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function unsupportedXaiBatchContent(type: string): XaiBatchResponseConversion {
|
|
549
|
+
return {
|
|
550
|
+
success: false,
|
|
551
|
+
error: {
|
|
552
|
+
message:
|
|
553
|
+
`xAI returned "${type}" content, but tool content is not supported ` +
|
|
554
|
+
'in AI SDK text batches.',
|
|
555
|
+
code: 'unsupported_content',
|
|
556
|
+
},
|
|
557
|
+
};
|
|
558
|
+
}
|