ai-retry 0.5.1 → 0.7.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/README.md +207 -97
- package/dist/index.d.ts +8 -4
- package/dist/index.js +49 -11
- package/dist/retryables/index.d.ts +1 -1
- package/dist/retryables/index.js +1 -1
- package/dist/{types-DqwAmcZS.d.ts → types-CfE400mD.d.ts} +3 -1
- package/dist/{utils-BlCGaP0E.js → utils-DsvLGk6a.js} +5 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -40,7 +40,8 @@ import { createRetryable } from 'ai-retry';
|
|
|
40
40
|
|
|
41
41
|
// Create a retryable model
|
|
42
42
|
const retryableModel = createRetryable({
|
|
43
|
-
|
|
43
|
+
// Base model
|
|
44
|
+
model: openai('gpt-4-mini'),
|
|
44
45
|
retries: [
|
|
45
46
|
// Retry strategies and fallbacks...
|
|
46
47
|
],
|
|
@@ -74,7 +75,8 @@ import { createRetryable } from 'ai-retry';
|
|
|
74
75
|
|
|
75
76
|
// Create a retryable model
|
|
76
77
|
const retryableModel = createRetryable({
|
|
77
|
-
|
|
78
|
+
// Base model
|
|
79
|
+
model: openai.textEmbedding('text-embedding-3-large'),
|
|
78
80
|
retries: [
|
|
79
81
|
// Retry strategies and fallbacks...
|
|
80
82
|
],
|
|
@@ -89,6 +91,165 @@ const result = await embed({
|
|
|
89
91
|
console.log(result.embedding);
|
|
90
92
|
```
|
|
91
93
|
|
|
94
|
+
### Retryables
|
|
95
|
+
|
|
96
|
+
The objects passed to the `retries` are called retryables and control the retry behavior. We can distinguish between two types of retryables:
|
|
97
|
+
|
|
98
|
+
- **Static retryables** are simply models instances (language or embedding) that will always be used when an error occurs. This is also called a fallback model.
|
|
99
|
+
- **Dynamic retryables** are functions that receive the current attempt context (error/result and previous attempts) and decide whether to retry with a different model based on custom logic.
|
|
100
|
+
|
|
101
|
+
You can think of `retries` as a big `if-else` block, where each dynamic retryable is an `if` condition that can match a certain error/result condition, and static retryables are the `else` branches that match all other conditions. The analogy is not perfect, because the order of retryables matters because `retries` are evaluated in order until one matches:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { openai } from '@ai-sdk/openai';
|
|
105
|
+
import { generateText, streamText } from 'ai';
|
|
106
|
+
import { createRetryable } from 'ai-retry';
|
|
107
|
+
|
|
108
|
+
const retryableModel = createRetryable({
|
|
109
|
+
// Base model
|
|
110
|
+
model: openai('gpt-4-mini'),
|
|
111
|
+
// Retryables are evaluated in order
|
|
112
|
+
retries: [
|
|
113
|
+
// Dynamic retryable that matches only certain errors
|
|
114
|
+
(context) => {
|
|
115
|
+
return context.current.error.statusCode === 429
|
|
116
|
+
? { model: openai('gpt-3.5-turbo') } // Retry with this model
|
|
117
|
+
: undefined; // Skip to next retryable
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
// Static retryable that always matches (fallback)
|
|
121
|
+
anthropic('claude-3-haiku-20240307'),
|
|
122
|
+
// Same as:
|
|
123
|
+
// { model: anthropic('claude-3-haiku-20240307'), maxAttempts: 1 }
|
|
124
|
+
],
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
In this example, if the base model fails with a 429 error, it will retry with `gpt-4`. In any other error case, it will fallback to `gpt-3.5-turbo`. If the order would be reversed, the static retryable would catch all errors first, and the dynamic retryable would never be reached.
|
|
129
|
+
|
|
130
|
+
#### Fallbacks
|
|
131
|
+
|
|
132
|
+
If you don't need precise error matching with custom logic and just want to fallback to different models on any error, you can simply provide a list of models.
|
|
133
|
+
|
|
134
|
+
> [!NOTE]
|
|
135
|
+
> Use the object syntax `{ model: openai('gpt-4') }` if you need to provide additional options like `maxAttempts`, `delay`, etc.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { openai } from '@ai-sdk/openai';
|
|
139
|
+
import { generateText, streamText } from 'ai';
|
|
140
|
+
import { createRetryable } from 'ai-retry';
|
|
141
|
+
|
|
142
|
+
const retryableModel = createRetryable({
|
|
143
|
+
// Base model
|
|
144
|
+
model: openai('gpt-4-mini'),
|
|
145
|
+
// List of fallback models
|
|
146
|
+
retries: [
|
|
147
|
+
openai('gpt-3.5-turbo'), // Fallback for first error
|
|
148
|
+
// Same as:
|
|
149
|
+
// { model: openai('gpt-3.5-turbo'), maxAttempts: 1 },
|
|
150
|
+
|
|
151
|
+
anthropic('claude-3-haiku-20240307'), // Fallback for second error
|
|
152
|
+
// Same as:
|
|
153
|
+
// { model: anthropic('claude-3-haiku-20240307'), maxAttempts: 1 },
|
|
154
|
+
],
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
In this example, if the base model fails, it will retry with `gpt-3.5-turbo`. If that also fails, it will retry with `claude-3-haiku-20240307`. If that fails again, the whole retry process stops and a `RetryError` is thrown.
|
|
159
|
+
|
|
160
|
+
#### Custom
|
|
161
|
+
|
|
162
|
+
If you need more control over when to retry and which model to use, you can create your own custom retryable. This function is called with a context object containing the current attempt (error or result) and all previous attempts and needs to return a retry model or `undefined` to skip to the next retryable. The object you return from the retryable function is the same as the one you provide in the `retries` array.
|
|
163
|
+
|
|
164
|
+
> [!NOTE]
|
|
165
|
+
> You can return additional options like `maxAttempts`, `delay`, etc. along with the model.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { anthropic } from '@ai-sdk/anthropic';
|
|
169
|
+
import { openai } from '@ai-sdk/openai';
|
|
170
|
+
import { APICallError } from 'ai';
|
|
171
|
+
import { createRetryable, isErrorAttempt } from 'ai-retry';
|
|
172
|
+
import type { Retryable } from 'ai-retry';
|
|
173
|
+
|
|
174
|
+
// Custom retryable that retries on rate limit errors (429)
|
|
175
|
+
const rateLimitRetry: Retryable = (context) => {
|
|
176
|
+
// Only handle error attempts
|
|
177
|
+
if (isErrorAttempt(context.current)) {
|
|
178
|
+
// Get the error from the current attempt
|
|
179
|
+
const { error } = context.current;
|
|
180
|
+
|
|
181
|
+
// Check for rate limit error
|
|
182
|
+
if (APICallError.isInstance(error) && error.statusCode === 429) {
|
|
183
|
+
// Retry with a different model
|
|
184
|
+
return { model: anthropic('claude-3-haiku-20240307') };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Skip to next retryable
|
|
189
|
+
return undefined;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const retryableModel = createRetryable({
|
|
193
|
+
// Base model
|
|
194
|
+
model: openai('gpt-4-mini'),
|
|
195
|
+
retries: [
|
|
196
|
+
// Use custom rate limit retryable
|
|
197
|
+
rateLimitRetry
|
|
198
|
+
|
|
199
|
+
// Other retryables...
|
|
200
|
+
],
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
In this example, if the base model fails with a 429 error, it will retry with `claude-3-haiku-20240307`. For any other error, it will skip to the next retryable (if any) or throw the original error.
|
|
205
|
+
|
|
206
|
+
#### All Retries Failed
|
|
207
|
+
|
|
208
|
+
If all retry attempts failed, a `RetryError` is thrown containing all individual errors.
|
|
209
|
+
If no retry was attempted (e.g. because all retryables returned `undefined`), the original error is thrown directly.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { RetryError } from 'ai';
|
|
213
|
+
|
|
214
|
+
const retryableModel = createRetryable({
|
|
215
|
+
// Base model = first attempt
|
|
216
|
+
model: azure('gpt-4-mini'),
|
|
217
|
+
retries: [
|
|
218
|
+
// Fallback model 1 = Second attempt
|
|
219
|
+
openai('gpt-3.5-turbo'),
|
|
220
|
+
// Fallback model 2 = Third attempt
|
|
221
|
+
anthropic('claude-3-haiku-20240307')
|
|
222
|
+
],
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const result = await generateText({
|
|
227
|
+
model: retryableModel,
|
|
228
|
+
prompt: 'Hello world!',
|
|
229
|
+
});
|
|
230
|
+
} catch (error) {
|
|
231
|
+
// RetryError is an official AI SDK error
|
|
232
|
+
if (error instanceof RetryError) {
|
|
233
|
+
console.error('All retry attempts failed:', error.errors);
|
|
234
|
+
} else {
|
|
235
|
+
console.error('Request failed:', error);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Errors are tracked per unique model (provider + modelId). That means on the first error, it will retry with `gpt-3.5-turbo`. If that also fails, it will retry with `claude-3-haiku-20240307`. If that fails again, the whole retry process stops and a `RetryError` is thrown.
|
|
241
|
+
|
|
242
|
+
### Built-in Retryables
|
|
243
|
+
|
|
244
|
+
There are several built-in dynamic retryables available for common use cases:
|
|
245
|
+
|
|
246
|
+
- [`contentFilterTriggered`](./src/retryables/content-filter-triggered.ts): Content filter was triggered based on the prompt or completion.
|
|
247
|
+
- [`requestTimeout`](./src/retryables/request-timeout.ts): Request timeout occurred.
|
|
248
|
+
- [`requestNotRetryable`](./src/retryables/request-not-retryable.ts): Request failed with a non-retryable error.
|
|
249
|
+
- [`retryAfterDelay`](./src/retryables/retry-after-delay.ts): Retry with delay and exponential backoff and respect `retry-after` headers.
|
|
250
|
+
- [`serviceOverloaded`](./src/retryables/service-overloaded.ts): Response with status code 529 (service overloaded).
|
|
251
|
+
- Use this retryable to handle Anthropic's overloaded errors.
|
|
252
|
+
|
|
92
253
|
#### Content Filter
|
|
93
254
|
|
|
94
255
|
Automatically switch to a different model when content filtering blocks your request.
|
|
@@ -195,82 +356,6 @@ const retryableModel = createRetryable({
|
|
|
195
356
|
|
|
196
357
|
By default, if a [`retry-after-ms`](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/provisioned-get-started#what-should--i-do-when-i-receive-a-429-response) or [`retry-after`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After) header is present in the response, it will be prioritized over the configured delay. The delay from the header will be capped at 60 seconds for safety.
|
|
197
358
|
|
|
198
|
-
#### Fallbacks
|
|
199
|
-
|
|
200
|
-
If you always want to fallback to a different model on any error, you can simply provide a list of models.
|
|
201
|
-
|
|
202
|
-
```typescript
|
|
203
|
-
const retryableModel = createRetryable({
|
|
204
|
-
model: azure('gpt-4'),
|
|
205
|
-
retries: [
|
|
206
|
-
openai('gpt-4'),
|
|
207
|
-
anthropic('claude-3-haiku-20240307')
|
|
208
|
-
],
|
|
209
|
-
});
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
#### Custom
|
|
213
|
-
|
|
214
|
-
Create your own retryables for specific use cases:
|
|
215
|
-
|
|
216
|
-
```typescript
|
|
217
|
-
import { anthropic } from '@ai-sdk/anthropic';
|
|
218
|
-
import { openai } from '@ai-sdk/openai';
|
|
219
|
-
import { APICallError } from 'ai';
|
|
220
|
-
import { createRetryable, isErrorAttempt } from 'ai-retry';
|
|
221
|
-
import type { Retryable } from 'ai-retry';
|
|
222
|
-
|
|
223
|
-
const rateLimitRetry: Retryable = (context) => {
|
|
224
|
-
if (isErrorAttempt(context.current)) {
|
|
225
|
-
const { error } = context.current;
|
|
226
|
-
|
|
227
|
-
if (APICallError.isInstance(error) && error.statusCode === 429) {
|
|
228
|
-
return { model: anthropic('claude-3-haiku-20240307') };
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return undefined;
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
const retryableModel = createRetryable({
|
|
236
|
-
model: openai('gpt-4'),
|
|
237
|
-
retries: [
|
|
238
|
-
rateLimitRetry
|
|
239
|
-
],
|
|
240
|
-
});
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
#### All Retries Failed
|
|
244
|
-
|
|
245
|
-
If all retry attempts failed, a `RetryError` is thrown containing all individual errors.
|
|
246
|
-
If no retry was attempted (e.g. because all retryables returned `undefined`), the original error is thrown directly.
|
|
247
|
-
|
|
248
|
-
```typescript
|
|
249
|
-
import { RetryError } from 'ai';
|
|
250
|
-
|
|
251
|
-
const retryableModel = createRetryable({
|
|
252
|
-
model: azure('gpt-4'),
|
|
253
|
-
retries: [
|
|
254
|
-
openai('gpt-4'),
|
|
255
|
-
anthropic('claude-3-haiku-20240307')
|
|
256
|
-
],
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
const result = await generateText({
|
|
261
|
-
model: retryableModel,
|
|
262
|
-
prompt: 'Hello world!',
|
|
263
|
-
});
|
|
264
|
-
} catch (error) {
|
|
265
|
-
// RetryError is an official AI SDK error
|
|
266
|
-
if (error instanceof RetryError) {
|
|
267
|
-
console.error('All retry attempts failed:', error.errors);
|
|
268
|
-
} else {
|
|
269
|
-
console.error('Request failed:', error);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
359
|
### Options
|
|
275
360
|
|
|
276
361
|
#### Retry Delays
|
|
@@ -282,10 +367,10 @@ const retryableModel = createRetryable({
|
|
|
282
367
|
model: openai('gpt-4'),
|
|
283
368
|
retries: [
|
|
284
369
|
// Retry model 3 times with fixed 2s delay
|
|
285
|
-
|
|
370
|
+
{ model: openai('gpt-4'), delay: 2000, maxAttempts: 3 },
|
|
286
371
|
|
|
287
372
|
// Or retry with exponential backoff (2s, 4s, 8s)
|
|
288
|
-
|
|
373
|
+
{ model: openai('gpt-4'), delay: 2000, backoffFactor: 2, maxAttempts: 3 },
|
|
289
374
|
],
|
|
290
375
|
});
|
|
291
376
|
|
|
@@ -322,7 +407,7 @@ const retryableModel = createRetryable({
|
|
|
322
407
|
// Try this once
|
|
323
408
|
anthropic('claude-3-haiku-20240307'),
|
|
324
409
|
// Try this one more time (initial + 1 retry)
|
|
325
|
-
|
|
410
|
+
{ model: openai('gpt-4'), maxAttempts: 2 },
|
|
326
411
|
// Already tried, won't be retried again
|
|
327
412
|
anthropic('claude-3-haiku-20240307')
|
|
328
413
|
],
|
|
@@ -331,6 +416,41 @@ const retryableModel = createRetryable({
|
|
|
331
416
|
|
|
332
417
|
The attempts are counted per unique model (provider + modelId). That means if multiple retryables return the same model, it won't be retried again once the `maxAttempts` is reached.
|
|
333
418
|
|
|
419
|
+
#### Provider Options
|
|
420
|
+
|
|
421
|
+
You can override provider-specific options for each retry attempt. This is useful when you want to use different configurations for fallback models.
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
const retryableModel = createRetryable({
|
|
425
|
+
model: openai('gpt-5'),
|
|
426
|
+
retries: [
|
|
427
|
+
// Use different provider options for the retry
|
|
428
|
+
{
|
|
429
|
+
model: openai('gpt-4o-2024-08-06'),
|
|
430
|
+
providerOptions: {
|
|
431
|
+
openai: {
|
|
432
|
+
user: 'fallback-user',
|
|
433
|
+
structuredOutputs: false,
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Original provider options are used for the first attempt
|
|
441
|
+
const result = await generateText({
|
|
442
|
+
model: retryableModel,
|
|
443
|
+
prompt: 'Write a story',
|
|
444
|
+
providerOptions: {
|
|
445
|
+
openai: {
|
|
446
|
+
user: 'primary-user',
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
The retry's `providerOptions` will completely replace the original ones during retry attempts. This works for all model types (language and embedding) and all operations (generate, stream, embed).
|
|
453
|
+
|
|
334
454
|
#### Logging
|
|
335
455
|
|
|
336
456
|
You can use the following callbacks to log retry attempts and errors:
|
|
@@ -361,18 +481,6 @@ Errors during streaming requests can occur in two ways:
|
|
|
361
481
|
|
|
362
482
|
In the second case, errors during stream processing will not always be retried, because the stream might have already emitted some actual content and the consumer might have processed it. Retrying will be stopped as soon as the first content chunk (e.g. types of `text-delta`, `tool-call`, etc.) is emitted. The type of chunks considered as content are the same as the ones that are passed to [onChunk()](https://github.com/vercel/ai/blob/1fe4bd4144bff927f5319d9d206e782a73979ccb/packages/ai/src/generate-text/stream-text.ts#L684-L697).
|
|
363
483
|
|
|
364
|
-
### Retryables
|
|
365
|
-
|
|
366
|
-
A retryable is a function that receives the current attempt and determines whether to retry with a different model based on the error/result and any previous attempts.
|
|
367
|
-
There are several built-in retryables:
|
|
368
|
-
|
|
369
|
-
- [`contentFilterTriggered`](./src/retryables/content-filter-triggered.ts): Content filter was triggered based on the prompt or completion.
|
|
370
|
-
- [`requestTimeout`](./src/retryables/request-timeout.ts): Request timeout occurred.
|
|
371
|
-
- [`requestNotRetryable`](./src/retryables/request-not-retryable.ts): Request failed with a non-retryable error.
|
|
372
|
-
- [`retryAfterDelay`](./src/retryables/retry-after-delay.ts): Retry with delay and exponential backoff and respect `retry-after` headers.
|
|
373
|
-
- [`serviceOverloaded`](./src/retryables/service-overloaded.ts): Response with status code 529 (service overloaded).
|
|
374
|
-
- Use this retryable to handle Anthropic's overloaded errors.
|
|
375
|
-
|
|
376
484
|
### API Reference
|
|
377
485
|
|
|
378
486
|
#### `createRetryable(options: RetryableModelOptions): LanguageModelV2 | EmbeddingModelV2`
|
|
@@ -401,14 +509,15 @@ type Retryable = (
|
|
|
401
509
|
|
|
402
510
|
#### `Retry`
|
|
403
511
|
|
|
404
|
-
A `Retry` specifies the model to retry and optional settings like `maxAttempts`, `delay` and `
|
|
512
|
+
A `Retry` specifies the model to retry and optional settings like `maxAttempts`, `delay`, `backoffFactor`, and `providerOptions`.
|
|
405
513
|
|
|
406
514
|
```typescript
|
|
407
515
|
interface Retry {
|
|
408
516
|
model: LanguageModelV2 | EmbeddingModelV2;
|
|
409
|
-
maxAttempts?: number;
|
|
410
|
-
delay?: number;
|
|
411
|
-
backoffFactor?: number;
|
|
517
|
+
maxAttempts?: number; // Maximum retry attempts per model (default: 1)
|
|
518
|
+
delay?: number; // Delay in milliseconds before retrying
|
|
519
|
+
backoffFactor?: number; // Multiplier for exponential backoff
|
|
520
|
+
providerOptions?: ProviderOptions; // Provider-specific options for the retry
|
|
412
521
|
}
|
|
413
522
|
```
|
|
414
523
|
|
|
@@ -417,6 +526,7 @@ interface Retry {
|
|
|
417
526
|
- `maxAttempts`: Maximum number of times this model can be retried. Default is 1.
|
|
418
527
|
- `delay`: Delay in milliseconds to wait before retrying. The delay respects abort signals from the request.
|
|
419
528
|
- `backoffFactor`: Multiplier for exponential backoff (`delay × backoffFactor^attempt`). If not provided, uses fixed delay.
|
|
529
|
+
- `providerOptions`: Provider-specific options that override the original request's provider options during retry attempts.
|
|
420
530
|
|
|
421
531
|
#### `RetryContext`
|
|
422
532
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { a as LanguageModelV2Generate, c as Retry, d as RetryErrorAttempt, f as RetryResultAttempt, h as RetryableOptions, i as LanguageModelV2, l as RetryAttempt, m as RetryableModelOptions, n as EmbeddingModelV2CallOptions, o as LanguageModelV2Stream, p as Retryable, r as EmbeddingModelV2Embed, s as Retries, t as EmbeddingModelV2, u as RetryContext } from "./types-
|
|
1
|
+
import { a as LanguageModelV2Generate, c as Retry, d as RetryErrorAttempt, f as RetryResultAttempt, h as RetryableOptions, i as LanguageModelV2, l as RetryAttempt, m as RetryableModelOptions, n as EmbeddingModelV2CallOptions, o as LanguageModelV2Stream, p as Retryable, r as EmbeddingModelV2Embed, s as Retries, t as EmbeddingModelV2, u as RetryContext } from "./types-CfE400mD.js";
|
|
2
2
|
import * as _ai_sdk_provider0 from "@ai-sdk/provider";
|
|
3
3
|
import { LanguageModelV2 as LanguageModelV2$1, LanguageModelV2StreamPart } from "@ai-sdk/provider";
|
|
4
4
|
|
|
5
5
|
//#region src/create-retryable-model.d.ts
|
|
6
|
-
declare function createRetryable<MODEL extends LanguageModelV2>(options: RetryableModelOptions<MODEL>): LanguageModelV2;
|
|
7
|
-
declare function createRetryable<MODEL extends EmbeddingModelV2>(options: RetryableModelOptions<MODEL>): EmbeddingModelV2;
|
|
6
|
+
declare function createRetryable<MODEL$1 extends LanguageModelV2>(options: RetryableModelOptions<MODEL$1>): LanguageModelV2;
|
|
7
|
+
declare function createRetryable<MODEL$1 extends EmbeddingModelV2>(options: RetryableModelOptions<MODEL$1>): EmbeddingModelV2;
|
|
8
8
|
//#endregion
|
|
9
9
|
//#region src/get-model-key.d.ts
|
|
10
10
|
/**
|
|
@@ -66,5 +66,9 @@ declare const isStreamContentPart: (part: LanguageModelV2StreamPart) => part is
|
|
|
66
66
|
type: "raw";
|
|
67
67
|
rawValue: unknown;
|
|
68
68
|
};
|
|
69
|
+
/**
|
|
70
|
+
* Type guard to check if a value is a Retry object (has a model property with a MODEL)
|
|
71
|
+
*/
|
|
72
|
+
declare const isRetry: <MODEL extends LanguageModelV2$1 | EmbeddingModelV2>(value: unknown) => value is Retry<MODEL>;
|
|
69
73
|
//#endregion
|
|
70
|
-
export { EmbeddingModelV2, EmbeddingModelV2CallOptions, EmbeddingModelV2Embed, LanguageModelV2, LanguageModelV2Generate, LanguageModelV2Stream, Retries, Retry, RetryAttempt, RetryContext, RetryErrorAttempt, RetryResultAttempt, Retryable, RetryableModelOptions, RetryableOptions, createRetryable, getModelKey, isEmbeddingModelV2, isErrorAttempt, isGenerateResult, isLanguageModelV2, isModelV2, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString };
|
|
74
|
+
export { EmbeddingModelV2, EmbeddingModelV2CallOptions, EmbeddingModelV2Embed, LanguageModelV2, LanguageModelV2Generate, LanguageModelV2Stream, Retries, Retry, RetryAttempt, RetryContext, RetryErrorAttempt, RetryResultAttempt, Retryable, RetryableModelOptions, RetryableOptions, createRetryable, getModelKey, isEmbeddingModelV2, isErrorAttempt, isGenerateResult, isLanguageModelV2, isModelV2, isObject, isResultAttempt, isRetry, isStreamContentPart, isStreamResult, isString };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as isModelV2, c as
|
|
1
|
+
import { a as isModelV2, c as isRetry, d as isString, i as isLanguageModelV2, l as isStreamContentPart, n as isErrorAttempt, o as isObject, r as isGenerateResult, s as isResultAttempt, t as isEmbeddingModelV2, u as isStreamResult } from "./utils-DsvLGk6a.js";
|
|
2
2
|
import { delay } from "@ai-sdk/provider-utils";
|
|
3
3
|
import { getErrorMessage } from "@ai-sdk/provider";
|
|
4
4
|
import { RetryError } from "ai";
|
|
@@ -35,15 +35,18 @@ function countModelAttempts(model, attempts) {
|
|
|
35
35
|
async function findRetryModel(retries, context) {
|
|
36
36
|
/**
|
|
37
37
|
* Filter retryables based on attempt type:
|
|
38
|
-
* - Result-based attempts: Only consider function retryables (skip plain models)
|
|
39
|
-
* - Error-based attempts: Consider all retryables (functions + plain models)
|
|
38
|
+
* - Result-based attempts: Only consider function retryables (skip plain models and static Retry objects)
|
|
39
|
+
* - Error-based attempts: Consider all retryables (functions + plain models + static Retry objects)
|
|
40
40
|
*/
|
|
41
41
|
const applicableRetries = isResultAttempt(context.current) ? retries.filter((retry) => typeof retry === "function") : retries;
|
|
42
42
|
/**
|
|
43
43
|
* Iterate through the applicable retryables to find a model to retry with
|
|
44
44
|
*/
|
|
45
45
|
for (const retry of applicableRetries) {
|
|
46
|
-
|
|
46
|
+
let retryModel;
|
|
47
|
+
if (typeof retry === "function") retryModel = await retry(context);
|
|
48
|
+
else if (isRetry(retry)) retryModel = retry;
|
|
49
|
+
else retryModel = { model: retry };
|
|
47
50
|
if (retryModel) {
|
|
48
51
|
/**
|
|
49
52
|
* The model key uniquely identifies a model instance (provider + modelId)
|
|
@@ -109,6 +112,10 @@ var RetryableEmbeddingModel = class {
|
|
|
109
112
|
* Track all attempts.
|
|
110
113
|
*/
|
|
111
114
|
const attempts = input.attempts ?? [];
|
|
115
|
+
/**
|
|
116
|
+
* Track current retry configuration.
|
|
117
|
+
*/
|
|
118
|
+
let currentRetry;
|
|
112
119
|
while (true) {
|
|
113
120
|
/**
|
|
114
121
|
* The previous attempt that triggered a retry, or undefined if this is the first attempt
|
|
@@ -130,7 +137,7 @@ var RetryableEmbeddingModel = class {
|
|
|
130
137
|
}
|
|
131
138
|
try {
|
|
132
139
|
return {
|
|
133
|
-
result: await input.fn(),
|
|
140
|
+
result: await input.fn(currentRetry),
|
|
134
141
|
attempts
|
|
135
142
|
};
|
|
136
143
|
} catch (error) {
|
|
@@ -149,6 +156,7 @@ var RetryableEmbeddingModel = class {
|
|
|
149
156
|
await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
|
|
150
157
|
}
|
|
151
158
|
this.currentModel = retryModel.model;
|
|
159
|
+
currentRetry = retryModel;
|
|
152
160
|
}
|
|
153
161
|
}
|
|
154
162
|
}
|
|
@@ -190,7 +198,13 @@ var RetryableEmbeddingModel = class {
|
|
|
190
198
|
*/
|
|
191
199
|
this.currentModel = this.baseModel;
|
|
192
200
|
const { result } = await this.withRetry({
|
|
193
|
-
fn: async () =>
|
|
201
|
+
fn: async (currentRetry) => {
|
|
202
|
+
const callOptions = {
|
|
203
|
+
...options,
|
|
204
|
+
providerOptions: currentRetry?.providerOptions ?? options.providerOptions
|
|
205
|
+
};
|
|
206
|
+
return this.currentModel.doEmbed(callOptions);
|
|
207
|
+
},
|
|
194
208
|
abortSignal: options.abortSignal
|
|
195
209
|
});
|
|
196
210
|
return result;
|
|
@@ -226,6 +240,10 @@ var RetryableLanguageModel = class {
|
|
|
226
240
|
* Track all attempts.
|
|
227
241
|
*/
|
|
228
242
|
const attempts = input.attempts ?? [];
|
|
243
|
+
/**
|
|
244
|
+
* Track current retry configuration.
|
|
245
|
+
*/
|
|
246
|
+
let currentRetry;
|
|
229
247
|
while (true) {
|
|
230
248
|
/**
|
|
231
249
|
* The previous attempt that triggered a retry, or undefined if this is the first attempt
|
|
@@ -249,7 +267,7 @@ var RetryableLanguageModel = class {
|
|
|
249
267
|
/**
|
|
250
268
|
* Call the function that may need to be retried
|
|
251
269
|
*/
|
|
252
|
-
const result = await input.fn();
|
|
270
|
+
const result = await input.fn(currentRetry);
|
|
253
271
|
/**
|
|
254
272
|
* Check if the result should trigger a retry (only for generate results, not streams)
|
|
255
273
|
*/
|
|
@@ -270,6 +288,7 @@ var RetryableLanguageModel = class {
|
|
|
270
288
|
await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
|
|
271
289
|
}
|
|
272
290
|
this.currentModel = retryModel.model;
|
|
291
|
+
currentRetry = retryModel;
|
|
273
292
|
/**
|
|
274
293
|
* Continue to the next iteration to retry
|
|
275
294
|
*/
|
|
@@ -292,6 +311,7 @@ var RetryableLanguageModel = class {
|
|
|
292
311
|
await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
|
|
293
312
|
}
|
|
294
313
|
this.currentModel = retryModel.model;
|
|
314
|
+
currentRetry = retryModel;
|
|
295
315
|
}
|
|
296
316
|
}
|
|
297
317
|
}
|
|
@@ -351,7 +371,13 @@ var RetryableLanguageModel = class {
|
|
|
351
371
|
*/
|
|
352
372
|
this.currentModel = this.baseModel;
|
|
353
373
|
const { result } = await this.withRetry({
|
|
354
|
-
fn: async () =>
|
|
374
|
+
fn: async (currentRetry) => {
|
|
375
|
+
const callOptions = {
|
|
376
|
+
...options,
|
|
377
|
+
providerOptions: currentRetry?.providerOptions ?? options.providerOptions
|
|
378
|
+
};
|
|
379
|
+
return this.currentModel.doGenerate(callOptions);
|
|
380
|
+
},
|
|
355
381
|
abortSignal: options.abortSignal
|
|
356
382
|
});
|
|
357
383
|
return result;
|
|
@@ -365,7 +391,13 @@ var RetryableLanguageModel = class {
|
|
|
365
391
|
* Perform the initial call to doStream with retry logic to handle errors before any data is streamed.
|
|
366
392
|
*/
|
|
367
393
|
let { result, attempts } = await this.withRetry({
|
|
368
|
-
fn: async () =>
|
|
394
|
+
fn: async (currentRetry) => {
|
|
395
|
+
const callOptions = {
|
|
396
|
+
...options,
|
|
397
|
+
providerOptions: currentRetry?.providerOptions ?? options.providerOptions
|
|
398
|
+
};
|
|
399
|
+
return this.currentModel.doStream(callOptions);
|
|
400
|
+
},
|
|
369
401
|
abortSignal: options.abortSignal
|
|
370
402
|
});
|
|
371
403
|
/**
|
|
@@ -421,7 +453,13 @@ var RetryableLanguageModel = class {
|
|
|
421
453
|
* This will create a new stream.
|
|
422
454
|
*/
|
|
423
455
|
const retriedResult = await this.withRetry({
|
|
424
|
-
fn: async () =>
|
|
456
|
+
fn: async () => {
|
|
457
|
+
const callOptions = {
|
|
458
|
+
...options,
|
|
459
|
+
providerOptions: retryModel.providerOptions ?? options.providerOptions
|
|
460
|
+
};
|
|
461
|
+
return this.currentModel.doStream(callOptions);
|
|
462
|
+
},
|
|
425
463
|
attempts,
|
|
426
464
|
abortSignal: options.abortSignal
|
|
427
465
|
});
|
|
@@ -450,4 +488,4 @@ function createRetryable(options) {
|
|
|
450
488
|
}
|
|
451
489
|
|
|
452
490
|
//#endregion
|
|
453
|
-
export { createRetryable, getModelKey, isEmbeddingModelV2, isErrorAttempt, isGenerateResult, isLanguageModelV2, isModelV2, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString };
|
|
491
|
+
export { createRetryable, getModelKey, isEmbeddingModelV2, isErrorAttempt, isGenerateResult, isLanguageModelV2, isModelV2, isObject, isResultAttempt, isRetry, isStreamContentPart, isStreamResult, isString };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { h as RetryableOptions, i as LanguageModelV2, p as Retryable, t as EmbeddingModelV2 } from "../types-
|
|
1
|
+
import { h as RetryableOptions, i as LanguageModelV2, p as Retryable, t as EmbeddingModelV2 } from "../types-CfE400mD.js";
|
|
2
2
|
|
|
3
3
|
//#region src/retryables/content-filter-triggered.d.ts
|
|
4
4
|
|
package/dist/retryables/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as isErrorAttempt, o as isObject, s as isResultAttempt
|
|
1
|
+
import { d as isString, n as isErrorAttempt, o as isObject, s as isResultAttempt } from "../utils-DsvLGk6a.js";
|
|
2
2
|
import { isAbortError } from "@ai-sdk/provider-utils";
|
|
3
3
|
import { APICallError } from "ai";
|
|
4
4
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ProviderOptions } from "@ai-sdk/provider-utils";
|
|
1
2
|
import { EmbeddingModelV2, LanguageModelV2 as LanguageModelV2$1 } from "@ai-sdk/provider";
|
|
2
3
|
|
|
3
4
|
//#region src/types.d.ts
|
|
@@ -54,12 +55,13 @@ type Retry<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = {
|
|
|
54
55
|
maxAttempts?: number;
|
|
55
56
|
delay?: number;
|
|
56
57
|
backoffFactor?: number;
|
|
58
|
+
providerOptions?: ProviderOptions;
|
|
57
59
|
};
|
|
58
60
|
/**
|
|
59
61
|
* A function that determines whether to retry with a different model based on the current attempt and all previous attempts.
|
|
60
62
|
*/
|
|
61
63
|
type Retryable<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = (context: RetryContext<MODEL>) => Retry<MODEL> | Promise<Retry<MODEL>> | undefined;
|
|
62
|
-
type Retries<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = Array<Retryable<MODEL> | MODEL>;
|
|
64
|
+
type Retries<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = Array<Retryable<MODEL> | Retry<MODEL> | MODEL>;
|
|
63
65
|
type RetryableOptions<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = Partial<Omit<Retry<MODEL>, 'model'>>;
|
|
64
66
|
type LanguageModelV2Generate = Awaited<ReturnType<LanguageModelV2$1['doGenerate']>>;
|
|
65
67
|
type LanguageModelV2Stream = Awaited<ReturnType<LanguageModelV2$1['doStream']>>;
|
|
@@ -26,6 +26,10 @@ function isResultAttempt(attempt) {
|
|
|
26
26
|
const isStreamContentPart = (part) => {
|
|
27
27
|
return part.type === "text-delta" || part.type === "reasoning-delta" || part.type === "source" || part.type === "tool-call" || part.type === "tool-result" || part.type === "tool-input-start" || part.type === "tool-input-delta" || part.type === "raw";
|
|
28
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* Type guard to check if a value is a Retry object (has a model property with a MODEL)
|
|
31
|
+
*/
|
|
32
|
+
const isRetry = (value) => isObject(value) && "model" in value && isModelV2(value.model);
|
|
29
33
|
|
|
30
34
|
//#endregion
|
|
31
|
-
export { isModelV2 as a,
|
|
35
|
+
export { isModelV2 as a, isRetry as c, isString as d, isLanguageModelV2 as i, isStreamContentPart as l, isErrorAttempt as n, isObject as o, isGenerateResult as r, isResultAttempt as s, isEmbeddingModelV2 as t, isStreamResult as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-retry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "AI SDK Retry",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@ai-sdk/anthropic": "^2.0.18",
|
|
37
37
|
"@ai-sdk/azure": "^2.0.30",
|
|
38
38
|
"@ai-sdk/groq": "^2.0.24",
|
|
39
|
-
"@ai-sdk/openai": "^2.0.
|
|
39
|
+
"@ai-sdk/openai": "^2.0.53",
|
|
40
40
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
41
41
|
"@biomejs/biome": "^2.2.4",
|
|
42
42
|
"@total-typescript/tsconfig": "^1.0.4",
|