ai-retry 0.7.0 → 0.8.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
CHANGED
|
@@ -95,29 +95,37 @@ console.log(result.embedding);
|
|
|
95
95
|
|
|
96
96
|
The objects passed to the `retries` are called retryables and control the retry behavior. We can distinguish between two types of retryables:
|
|
97
97
|
|
|
98
|
-
- **Static retryables** are simply models instances (language or embedding) that will always be used when an error occurs.
|
|
98
|
+
- **Static retryables** are simply models instances (language or embedding) that will always be used when an error occurs. They are also called fallback models.
|
|
99
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
100
|
|
|
101
|
-
You can think of `retries` as a big `if-else` block, where each dynamic retryable is an `if`
|
|
101
|
+
You can think of the `retries` array as a big `if-else` block, where each dynamic retryable is an `if` branch 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
102
|
|
|
103
103
|
```typescript
|
|
104
|
-
import { openai } from '@ai-sdk/openai';
|
|
105
104
|
import { generateText, streamText } from 'ai';
|
|
106
105
|
import { createRetryable } from 'ai-retry';
|
|
107
106
|
|
|
108
107
|
const retryableModel = createRetryable({
|
|
109
108
|
// Base model
|
|
110
|
-
model: openai('gpt-4
|
|
111
|
-
// Retryables are evaluated in order
|
|
109
|
+
model: openai('gpt-4'),
|
|
110
|
+
// Retryables are evaluated top-down in order
|
|
112
111
|
retries: [
|
|
113
|
-
// Dynamic
|
|
112
|
+
// Dynamic retryables act like if-branches:
|
|
113
|
+
// If error.code == 429 (too many requests) happens, retry with this model
|
|
114
114
|
(context) => {
|
|
115
115
|
return context.current.error.statusCode === 429
|
|
116
|
-
? { model:
|
|
117
|
-
: undefined;
|
|
116
|
+
? { model: azure('gpt-4-mini') } // Retry
|
|
117
|
+
: undefined; // Skip
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
// If error.message ~= "service overloaded", retry with this model
|
|
121
|
+
(context) => {
|
|
122
|
+
return context.current.error.message.includes("service overloaded")
|
|
123
|
+
? { model: azure('gpt-4-mini') } // Retry
|
|
124
|
+
: undefined; // Skip
|
|
118
125
|
},
|
|
119
126
|
|
|
120
|
-
// Static
|
|
127
|
+
// Static retryables act like else branches:
|
|
128
|
+
// Else, always fallback to this model
|
|
121
129
|
anthropic('claude-3-haiku-20240307'),
|
|
122
130
|
// Same as:
|
|
123
131
|
// { model: anthropic('claude-3-haiku-20240307'), maxAttempts: 1 }
|
|
@@ -125,7 +133,7 @@ const retryableModel = createRetryable({
|
|
|
125
133
|
});
|
|
126
134
|
```
|
|
127
135
|
|
|
128
|
-
In this example, if the base model fails with
|
|
136
|
+
In this example, if the base model fails with code 429 or a service overloaded error, it will retry with `gpt-4-mini` on Azure. In any other error case, it will fallback to `claude-3-haiku-20240307` on Anthropic. If the order would be reversed, the static retryable would catch all errors first, and the dynamic retryable would never be reached.
|
|
129
137
|
|
|
130
138
|
#### Fallbacks
|
|
131
139
|
|
|
@@ -275,20 +283,26 @@ Handle timeouts by switching to potentially faster models.
|
|
|
275
283
|
> [!NOTE]
|
|
276
284
|
> You need to use an `abortSignal` with a timeout on your request.
|
|
277
285
|
|
|
286
|
+
When a request times out, the `requestTimeout` retryable will automatically create a fresh abort signal for the retry attempt. This prevents the retry from immediately failing due to the already-aborted signal from the original request. If you do not provide a `timeout` value, a default of 60 seconds is used for the retry attempt.
|
|
287
|
+
|
|
278
288
|
```typescript
|
|
279
289
|
import { requestTimeout } from 'ai-retry/retryables';
|
|
280
290
|
|
|
281
291
|
const retryableModel = createRetryable({
|
|
282
292
|
model: azure('gpt-4'),
|
|
283
293
|
retries: [
|
|
284
|
-
|
|
294
|
+
// Defaults to 60 seconds timeout for the retry attempt
|
|
295
|
+
requestTimeout(azure('gpt-4-mini')),
|
|
296
|
+
|
|
297
|
+
// Or specify a custom timeout for the retry attempt
|
|
298
|
+
requestTimeout(azure('gpt-4-mini'), { timeout: 30_000 }),
|
|
285
299
|
],
|
|
286
300
|
});
|
|
287
301
|
|
|
288
302
|
const result = await generateText({
|
|
289
303
|
model: retryableModel,
|
|
290
304
|
prompt: 'Write a vegetarian lasagna recipe for 4 people.',
|
|
291
|
-
abortSignal: AbortSignal.timeout(60_000),
|
|
305
|
+
abortSignal: AbortSignal.timeout(60_000), // Original request timeout
|
|
292
306
|
});
|
|
293
307
|
```
|
|
294
308
|
|
|
@@ -343,10 +357,10 @@ const retryableModel = createRetryable({
|
|
|
343
357
|
model: openai('gpt-4'), // Base model
|
|
344
358
|
retries: [
|
|
345
359
|
// Retry base model 3 times with fixed 2s delay
|
|
346
|
-
retryAfterDelay({ delay:
|
|
360
|
+
retryAfterDelay({ delay: 2_000, maxAttempts: 3 }),
|
|
347
361
|
|
|
348
362
|
// Or retry with exponential backoff (2s, 4s, 8s)
|
|
349
|
-
retryAfterDelay({ delay:
|
|
363
|
+
retryAfterDelay({ delay: 2_000, backoffFactor: 2, maxAttempts: 3 }),
|
|
350
364
|
|
|
351
365
|
// Or retry only if the response contains a retry-after header
|
|
352
366
|
retryAfterDelay({ maxAttempts: 3 }),
|
|
@@ -367,10 +381,10 @@ const retryableModel = createRetryable({
|
|
|
367
381
|
model: openai('gpt-4'),
|
|
368
382
|
retries: [
|
|
369
383
|
// Retry model 3 times with fixed 2s delay
|
|
370
|
-
{ model: openai('gpt-4'), delay:
|
|
384
|
+
{ model: openai('gpt-4'), delay: 2_000, maxAttempts: 3 },
|
|
371
385
|
|
|
372
386
|
// Or retry with exponential backoff (2s, 4s, 8s)
|
|
373
|
-
{ model: openai('gpt-4'), delay:
|
|
387
|
+
{ model: openai('gpt-4'), delay: 2_000, backoffFactor: 2, maxAttempts: 3 },
|
|
374
388
|
],
|
|
375
389
|
});
|
|
376
390
|
|
|
@@ -395,6 +409,30 @@ const retryableModel = createRetryable({
|
|
|
395
409
|
],
|
|
396
410
|
});
|
|
397
411
|
```
|
|
412
|
+
#### Timeouts
|
|
413
|
+
|
|
414
|
+
When a retry specifies a `timeout` value, a fresh `AbortSignal.timeout()` is created for that retry attempt, replacing any existing abort signal. This is essential when retrying after timeout errors, as the original abort signal would already be in an aborted state.
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
const retryableModel = createRetryable({
|
|
418
|
+
model: openai('gpt-4'),
|
|
419
|
+
retries: [
|
|
420
|
+
// Provide a fresh 30 second timeout for the retry
|
|
421
|
+
{
|
|
422
|
+
model: openai('gpt-3.5-turbo'),
|
|
423
|
+
timeout: 30_000
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// Even if the original request times out, the retry gets a fresh signal
|
|
429
|
+
const result = await generateText({
|
|
430
|
+
model: retryableModel,
|
|
431
|
+
prompt: 'Write a story',
|
|
432
|
+
// Original request timeout
|
|
433
|
+
abortSignal: AbortSignal.timeout(60_000),
|
|
434
|
+
});
|
|
435
|
+
```
|
|
398
436
|
|
|
399
437
|
#### Max Attempts
|
|
400
438
|
|
|
@@ -509,7 +547,7 @@ type Retryable = (
|
|
|
509
547
|
|
|
510
548
|
#### `Retry`
|
|
511
549
|
|
|
512
|
-
A `Retry` specifies the model to retry and optional settings like `maxAttempts`, `delay`, `backoffFactor`, and `providerOptions`.
|
|
550
|
+
A `Retry` specifies the model to retry and optional settings like `maxAttempts`, `delay`, `backoffFactor`, `timeout`, and `providerOptions`.
|
|
513
551
|
|
|
514
552
|
```typescript
|
|
515
553
|
interface Retry {
|
|
@@ -517,6 +555,7 @@ interface Retry {
|
|
|
517
555
|
maxAttempts?: number; // Maximum retry attempts per model (default: 1)
|
|
518
556
|
delay?: number; // Delay in milliseconds before retrying
|
|
519
557
|
backoffFactor?: number; // Multiplier for exponential backoff
|
|
558
|
+
timeout?: number; // Timeout in milliseconds for the retry attempt
|
|
520
559
|
providerOptions?: ProviderOptions; // Provider-specific options for the retry
|
|
521
560
|
}
|
|
522
561
|
```
|
|
@@ -526,6 +565,7 @@ interface Retry {
|
|
|
526
565
|
- `maxAttempts`: Maximum number of times this model can be retried. Default is 1.
|
|
527
566
|
- `delay`: Delay in milliseconds to wait before retrying. The delay respects abort signals from the request.
|
|
528
567
|
- `backoffFactor`: Multiplier for exponential backoff (`delay × backoffFactor^attempt`). If not provided, uses fixed delay.
|
|
568
|
+
- `timeout`: Timeout in milliseconds for creating a fresh `AbortSignal.timeout()` for the retry attempt. This replaces any existing abort signal.
|
|
529
569
|
- `providerOptions`: Provider-specific options that override the original request's provider options during retry attempts.
|
|
530
570
|
|
|
531
571
|
#### `RetryContext`
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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-BuPozWMn.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
|
|
package/dist/index.js
CHANGED
|
@@ -201,7 +201,8 @@ var RetryableEmbeddingModel = class {
|
|
|
201
201
|
fn: async (currentRetry) => {
|
|
202
202
|
const callOptions = {
|
|
203
203
|
...options,
|
|
204
|
-
providerOptions: currentRetry?.providerOptions ?? options.providerOptions
|
|
204
|
+
providerOptions: currentRetry?.providerOptions ?? options.providerOptions,
|
|
205
|
+
abortSignal: currentRetry?.timeout ? AbortSignal.timeout(currentRetry.timeout) : options.abortSignal
|
|
205
206
|
};
|
|
206
207
|
return this.currentModel.doEmbed(callOptions);
|
|
207
208
|
},
|
|
@@ -374,7 +375,8 @@ var RetryableLanguageModel = class {
|
|
|
374
375
|
fn: async (currentRetry) => {
|
|
375
376
|
const callOptions = {
|
|
376
377
|
...options,
|
|
377
|
-
providerOptions: currentRetry?.providerOptions ?? options.providerOptions
|
|
378
|
+
providerOptions: currentRetry?.providerOptions ?? options.providerOptions,
|
|
379
|
+
abortSignal: currentRetry?.timeout ? AbortSignal.timeout(currentRetry.timeout) : options.abortSignal
|
|
378
380
|
};
|
|
379
381
|
return this.currentModel.doGenerate(callOptions);
|
|
380
382
|
},
|
|
@@ -394,7 +396,8 @@ var RetryableLanguageModel = class {
|
|
|
394
396
|
fn: async (currentRetry) => {
|
|
395
397
|
const callOptions = {
|
|
396
398
|
...options,
|
|
397
|
-
providerOptions: currentRetry?.providerOptions ?? options.providerOptions
|
|
399
|
+
providerOptions: currentRetry?.providerOptions ?? options.providerOptions,
|
|
400
|
+
abortSignal: currentRetry?.timeout ? AbortSignal.timeout(currentRetry.timeout) : options.abortSignal
|
|
398
401
|
};
|
|
399
402
|
return this.currentModel.doStream(callOptions);
|
|
400
403
|
},
|
|
@@ -456,7 +459,8 @@ var RetryableLanguageModel = class {
|
|
|
456
459
|
fn: async () => {
|
|
457
460
|
const callOptions = {
|
|
458
461
|
...options,
|
|
459
|
-
providerOptions: retryModel.providerOptions ?? options.providerOptions
|
|
462
|
+
providerOptions: retryModel.providerOptions ?? options.providerOptions,
|
|
463
|
+
abortSignal: retryModel.timeout ? AbortSignal.timeout(retryModel.timeout) : options.abortSignal
|
|
460
464
|
};
|
|
461
465
|
return this.currentModel.doStream(callOptions);
|
|
462
466
|
},
|
|
@@ -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-BuPozWMn.js";
|
|
2
2
|
|
|
3
3
|
//#region src/retryables/content-filter-triggered.d.ts
|
|
4
4
|
|
|
@@ -17,7 +17,7 @@ declare function requestNotRetryable<MODEL extends LanguageModelV2 | EmbeddingMo
|
|
|
17
17
|
/**
|
|
18
18
|
* Fallback to a different model after a timeout/abort error.
|
|
19
19
|
* Use in combination with the `abortSignal` option.
|
|
20
|
-
*
|
|
20
|
+
* If no timeout is specified, a default of 60 seconds is used.
|
|
21
21
|
*/
|
|
22
22
|
declare function requestTimeout<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
23
23
|
//#endregion
|
package/dist/retryables/index.js
CHANGED
|
@@ -51,18 +51,20 @@ function requestNotRetryable(model, options) {
|
|
|
51
51
|
/**
|
|
52
52
|
* Fallback to a different model after a timeout/abort error.
|
|
53
53
|
* Use in combination with the `abortSignal` option.
|
|
54
|
-
*
|
|
54
|
+
* If no timeout is specified, a default of 60 seconds is used.
|
|
55
55
|
*/
|
|
56
56
|
function requestTimeout(model, options) {
|
|
57
57
|
return (context) => {
|
|
58
58
|
const { current } = context;
|
|
59
59
|
if (isErrorAttempt(current)) {
|
|
60
60
|
/**
|
|
61
|
-
* Fallback to the specified model after
|
|
61
|
+
* Fallback to the specified model after a timeout/abort error.
|
|
62
|
+
* Provides a fresh timeout signal for the retry attempt.
|
|
62
63
|
*/
|
|
63
64
|
if (isAbortError(current.error)) return {
|
|
64
65
|
model,
|
|
65
66
|
maxAttempts: 1,
|
|
67
|
+
timeout: options?.timeout ?? 6e4,
|
|
66
68
|
...options
|
|
67
69
|
};
|
|
68
70
|
}
|
|
@@ -56,6 +56,7 @@ type Retry<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = {
|
|
|
56
56
|
delay?: number;
|
|
57
57
|
backoffFactor?: number;
|
|
58
58
|
providerOptions?: ProviderOptions;
|
|
59
|
+
timeout?: number;
|
|
59
60
|
};
|
|
60
61
|
/**
|
|
61
62
|
* A function that determines whether to retry with a different model based on the current attempt and all previous attempts.
|