ai-retry 0.5.0 → 0.6.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 +49 -13
- package/dist/index.d.ts +1 -1
- package/dist/index.js +41 -6
- package/dist/retryables/index.d.ts +3 -4
- package/dist/retryables/index.js +12 -24
- package/dist/{types-DqwAmcZS.d.ts → types-DhGbwiB4.d.ts} +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -170,11 +170,10 @@ const retryable = createRetryable({
|
|
|
170
170
|
|
|
171
171
|
#### Retry After Delay
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
> [!NOTE]
|
|
176
|
-
> If the response contains a [`retry-after`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After) header, it will be prioritized over the configured delay.
|
|
173
|
+
If an error is retryable, such as 429 (Too Many Requests) or 503 (Service Unavailable) errors, it will be retried after a delay.
|
|
174
|
+
The delay and exponential backoff can be configured. If the response contains a [`retry-after`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After) header, it will be prioritized over the configured delay.
|
|
177
175
|
|
|
176
|
+
Note that this retryable does not accept a model parameter, it will always retry the model from the latest failed attempt.
|
|
178
177
|
|
|
179
178
|
```typescript
|
|
180
179
|
import { retryAfterDelay } from 'ai-retry/retryables';
|
|
@@ -187,14 +186,14 @@ const retryableModel = createRetryable({
|
|
|
187
186
|
|
|
188
187
|
// Or retry with exponential backoff (2s, 4s, 8s)
|
|
189
188
|
retryAfterDelay({ delay: 2000, backoffFactor: 2, maxAttempts: 3 }),
|
|
190
|
-
|
|
191
|
-
// Or
|
|
192
|
-
retryAfterDelay(
|
|
189
|
+
|
|
190
|
+
// Or retry only if the response contains a retry-after header
|
|
191
|
+
retryAfterDelay({ maxAttempts: 3 }),
|
|
193
192
|
],
|
|
194
193
|
});
|
|
195
194
|
```
|
|
196
195
|
|
|
197
|
-
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` 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.
|
|
196
|
+
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.
|
|
198
197
|
|
|
199
198
|
#### Fallbacks
|
|
200
199
|
|
|
@@ -332,6 +331,41 @@ const retryableModel = createRetryable({
|
|
|
332
331
|
|
|
333
332
|
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.
|
|
334
333
|
|
|
334
|
+
#### Provider Options
|
|
335
|
+
|
|
336
|
+
You can override provider-specific options for each retry attempt. This is useful when you want to use different configurations for fallback models.
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
const retryableModel = createRetryable({
|
|
340
|
+
model: openai('gpt-5'),
|
|
341
|
+
retries: [
|
|
342
|
+
// Use different provider options for the retry
|
|
343
|
+
() => ({
|
|
344
|
+
model: openai('gpt-4o-2024-08-06'),
|
|
345
|
+
providerOptions: {
|
|
346
|
+
openai: {
|
|
347
|
+
user: 'fallback-user',
|
|
348
|
+
structuredOutputs: false,
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
}),
|
|
352
|
+
],
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Original provider options are used for the first attempt
|
|
356
|
+
const result = await generateText({
|
|
357
|
+
model: retryableModel,
|
|
358
|
+
prompt: 'Write a story',
|
|
359
|
+
providerOptions: {
|
|
360
|
+
openai: {
|
|
361
|
+
user: 'primary-user',
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
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).
|
|
368
|
+
|
|
335
369
|
#### Logging
|
|
336
370
|
|
|
337
371
|
You can use the following callbacks to log retry attempts and errors:
|
|
@@ -370,7 +404,7 @@ There are several built-in retryables:
|
|
|
370
404
|
- [`contentFilterTriggered`](./src/retryables/content-filter-triggered.ts): Content filter was triggered based on the prompt or completion.
|
|
371
405
|
- [`requestTimeout`](./src/retryables/request-timeout.ts): Request timeout occurred.
|
|
372
406
|
- [`requestNotRetryable`](./src/retryables/request-not-retryable.ts): Request failed with a non-retryable error.
|
|
373
|
-
- [`retryAfterDelay`](./src/retryables/retry-after-delay.ts): Retry with exponential backoff and respect `retry-after` headers
|
|
407
|
+
- [`retryAfterDelay`](./src/retryables/retry-after-delay.ts): Retry with delay and exponential backoff and respect `retry-after` headers.
|
|
374
408
|
- [`serviceOverloaded`](./src/retryables/service-overloaded.ts): Response with status code 529 (service overloaded).
|
|
375
409
|
- Use this retryable to handle Anthropic's overloaded errors.
|
|
376
410
|
|
|
@@ -402,14 +436,15 @@ type Retryable = (
|
|
|
402
436
|
|
|
403
437
|
#### `Retry`
|
|
404
438
|
|
|
405
|
-
A `Retry` specifies the model to retry and optional settings like `maxAttempts`, `delay` and `
|
|
439
|
+
A `Retry` specifies the model to retry and optional settings like `maxAttempts`, `delay`, `backoffFactor`, and `providerOptions`.
|
|
406
440
|
|
|
407
441
|
```typescript
|
|
408
442
|
interface Retry {
|
|
409
443
|
model: LanguageModelV2 | EmbeddingModelV2;
|
|
410
|
-
maxAttempts?: number;
|
|
411
|
-
delay?: number;
|
|
412
|
-
backoffFactor?: number;
|
|
444
|
+
maxAttempts?: number; // Maximum retry attempts per model (default: 1)
|
|
445
|
+
delay?: number; // Delay in milliseconds before retrying
|
|
446
|
+
backoffFactor?: number; // Multiplier for exponential backoff
|
|
447
|
+
providerOptions?: ProviderOptions; // Provider-specific options for the retry
|
|
413
448
|
}
|
|
414
449
|
```
|
|
415
450
|
|
|
@@ -418,6 +453,7 @@ interface Retry {
|
|
|
418
453
|
- `maxAttempts`: Maximum number of times this model can be retried. Default is 1.
|
|
419
454
|
- `delay`: Delay in milliseconds to wait before retrying. The delay respects abort signals from the request.
|
|
420
455
|
- `backoffFactor`: Multiplier for exponential backoff (`delay × backoffFactor^attempt`). If not provided, uses fixed delay.
|
|
456
|
+
- `providerOptions`: Provider-specific options that override the original request's provider options during retry attempts.
|
|
421
457
|
|
|
422
458
|
#### `RetryContext`
|
|
423
459
|
|
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-DhGbwiB4.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
|
@@ -109,6 +109,10 @@ var RetryableEmbeddingModel = class {
|
|
|
109
109
|
* Track all attempts.
|
|
110
110
|
*/
|
|
111
111
|
const attempts = input.attempts ?? [];
|
|
112
|
+
/**
|
|
113
|
+
* Track current retry configuration.
|
|
114
|
+
*/
|
|
115
|
+
let currentRetry;
|
|
112
116
|
while (true) {
|
|
113
117
|
/**
|
|
114
118
|
* The previous attempt that triggered a retry, or undefined if this is the first attempt
|
|
@@ -130,7 +134,7 @@ var RetryableEmbeddingModel = class {
|
|
|
130
134
|
}
|
|
131
135
|
try {
|
|
132
136
|
return {
|
|
133
|
-
result: await input.fn(),
|
|
137
|
+
result: await input.fn(currentRetry),
|
|
134
138
|
attempts
|
|
135
139
|
};
|
|
136
140
|
} catch (error) {
|
|
@@ -149,6 +153,7 @@ var RetryableEmbeddingModel = class {
|
|
|
149
153
|
await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
|
|
150
154
|
}
|
|
151
155
|
this.currentModel = retryModel.model;
|
|
156
|
+
currentRetry = retryModel;
|
|
152
157
|
}
|
|
153
158
|
}
|
|
154
159
|
}
|
|
@@ -190,7 +195,13 @@ var RetryableEmbeddingModel = class {
|
|
|
190
195
|
*/
|
|
191
196
|
this.currentModel = this.baseModel;
|
|
192
197
|
const { result } = await this.withRetry({
|
|
193
|
-
fn: async () =>
|
|
198
|
+
fn: async (currentRetry) => {
|
|
199
|
+
const callOptions = {
|
|
200
|
+
...options,
|
|
201
|
+
providerOptions: currentRetry?.providerOptions ?? options.providerOptions
|
|
202
|
+
};
|
|
203
|
+
return this.currentModel.doEmbed(callOptions);
|
|
204
|
+
},
|
|
194
205
|
abortSignal: options.abortSignal
|
|
195
206
|
});
|
|
196
207
|
return result;
|
|
@@ -226,6 +237,10 @@ var RetryableLanguageModel = class {
|
|
|
226
237
|
* Track all attempts.
|
|
227
238
|
*/
|
|
228
239
|
const attempts = input.attempts ?? [];
|
|
240
|
+
/**
|
|
241
|
+
* Track current retry configuration.
|
|
242
|
+
*/
|
|
243
|
+
let currentRetry;
|
|
229
244
|
while (true) {
|
|
230
245
|
/**
|
|
231
246
|
* The previous attempt that triggered a retry, or undefined if this is the first attempt
|
|
@@ -249,7 +264,7 @@ var RetryableLanguageModel = class {
|
|
|
249
264
|
/**
|
|
250
265
|
* Call the function that may need to be retried
|
|
251
266
|
*/
|
|
252
|
-
const result = await input.fn();
|
|
267
|
+
const result = await input.fn(currentRetry);
|
|
253
268
|
/**
|
|
254
269
|
* Check if the result should trigger a retry (only for generate results, not streams)
|
|
255
270
|
*/
|
|
@@ -270,6 +285,7 @@ var RetryableLanguageModel = class {
|
|
|
270
285
|
await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
|
|
271
286
|
}
|
|
272
287
|
this.currentModel = retryModel.model;
|
|
288
|
+
currentRetry = retryModel;
|
|
273
289
|
/**
|
|
274
290
|
* Continue to the next iteration to retry
|
|
275
291
|
*/
|
|
@@ -292,6 +308,7 @@ var RetryableLanguageModel = class {
|
|
|
292
308
|
await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
|
|
293
309
|
}
|
|
294
310
|
this.currentModel = retryModel.model;
|
|
311
|
+
currentRetry = retryModel;
|
|
295
312
|
}
|
|
296
313
|
}
|
|
297
314
|
}
|
|
@@ -351,7 +368,13 @@ var RetryableLanguageModel = class {
|
|
|
351
368
|
*/
|
|
352
369
|
this.currentModel = this.baseModel;
|
|
353
370
|
const { result } = await this.withRetry({
|
|
354
|
-
fn: async () =>
|
|
371
|
+
fn: async (currentRetry) => {
|
|
372
|
+
const callOptions = {
|
|
373
|
+
...options,
|
|
374
|
+
providerOptions: currentRetry?.providerOptions ?? options.providerOptions
|
|
375
|
+
};
|
|
376
|
+
return this.currentModel.doGenerate(callOptions);
|
|
377
|
+
},
|
|
355
378
|
abortSignal: options.abortSignal
|
|
356
379
|
});
|
|
357
380
|
return result;
|
|
@@ -365,7 +388,13 @@ var RetryableLanguageModel = class {
|
|
|
365
388
|
* Perform the initial call to doStream with retry logic to handle errors before any data is streamed.
|
|
366
389
|
*/
|
|
367
390
|
let { result, attempts } = await this.withRetry({
|
|
368
|
-
fn: async () =>
|
|
391
|
+
fn: async (currentRetry) => {
|
|
392
|
+
const callOptions = {
|
|
393
|
+
...options,
|
|
394
|
+
providerOptions: currentRetry?.providerOptions ?? options.providerOptions
|
|
395
|
+
};
|
|
396
|
+
return this.currentModel.doStream(callOptions);
|
|
397
|
+
},
|
|
369
398
|
abortSignal: options.abortSignal
|
|
370
399
|
});
|
|
371
400
|
/**
|
|
@@ -421,7 +450,13 @@ var RetryableLanguageModel = class {
|
|
|
421
450
|
* This will create a new stream.
|
|
422
451
|
*/
|
|
423
452
|
const retriedResult = await this.withRetry({
|
|
424
|
-
fn: async () =>
|
|
453
|
+
fn: async () => {
|
|
454
|
+
const callOptions = {
|
|
455
|
+
...options,
|
|
456
|
+
providerOptions: retryModel.providerOptions ?? options.providerOptions
|
|
457
|
+
};
|
|
458
|
+
return this.currentModel.doStream(callOptions);
|
|
459
|
+
},
|
|
425
460
|
attempts,
|
|
426
461
|
abortSignal: options.abortSignal
|
|
427
462
|
});
|
|
@@ -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-DhGbwiB4.js";
|
|
2
2
|
|
|
3
3
|
//#region src/retryables/content-filter-triggered.d.ts
|
|
4
4
|
|
|
@@ -23,11 +23,10 @@ declare function requestTimeout<MODEL extends LanguageModelV2 | EmbeddingModelV2
|
|
|
23
23
|
//#endregion
|
|
24
24
|
//#region src/retryables/retry-after-delay.d.ts
|
|
25
25
|
/**
|
|
26
|
-
* Retry with the same
|
|
26
|
+
* Retry the current failed attempt with the same model, if the error is retryable.
|
|
27
27
|
* Uses the `Retry-After` or `Retry-After-Ms` headers if present.
|
|
28
|
-
* Otherwise uses the specified `delay`
|
|
28
|
+
* Otherwise uses the specified `delay` and `backoffFactor` if provided.
|
|
29
29
|
*/
|
|
30
|
-
declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
31
30
|
declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(options: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
32
31
|
//#endregion
|
|
33
32
|
//#region src/retryables/service-overloaded.d.ts
|
package/dist/retryables/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as isErrorAttempt, o as isObject, s as isResultAttempt, u as isString } from "../utils-BlCGaP0E.js";
|
|
2
2
|
import { isAbortError } from "@ai-sdk/provider-utils";
|
|
3
3
|
import { APICallError } from "ai";
|
|
4
4
|
|
|
@@ -69,23 +69,6 @@ function requestTimeout(model, options) {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
//#endregion
|
|
73
|
-
//#region src/internal/resolve-retryable-options.ts
|
|
74
|
-
/**
|
|
75
|
-
* Helper to resolve `RetryableOptions` from either a model and/or options object.
|
|
76
|
-
* Used to support function overloads in retryables:
|
|
77
|
-
* - `retryable(model)`
|
|
78
|
-
* - `retryable(model, options)`
|
|
79
|
-
* - `retryable(options)`
|
|
80
|
-
*/
|
|
81
|
-
function resolveRetryableOptions(modelOrOptions, options) {
|
|
82
|
-
if (isModelV2(modelOrOptions)) return {
|
|
83
|
-
...options,
|
|
84
|
-
model: modelOrOptions
|
|
85
|
-
};
|
|
86
|
-
return modelOrOptions;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
72
|
//#endregion
|
|
90
73
|
//#region src/parse-retry-headers.ts
|
|
91
74
|
function parseRetryHeaders(headers) {
|
|
@@ -108,23 +91,28 @@ function parseRetryHeaders(headers) {
|
|
|
108
91
|
//#endregion
|
|
109
92
|
//#region src/retryables/retry-after-delay.ts
|
|
110
93
|
const MAX_RETRY_AFTER_MS = 6e4;
|
|
111
|
-
|
|
112
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Retry the current failed attempt with the same model, if the error is retryable.
|
|
96
|
+
* Uses the `Retry-After` or `Retry-After-Ms` headers if present.
|
|
97
|
+
* Otherwise uses the specified `delay` and `backoffFactor` if provided.
|
|
98
|
+
*/
|
|
99
|
+
function retryAfterDelay(options) {
|
|
113
100
|
return (context) => {
|
|
114
101
|
const { current } = context;
|
|
115
102
|
if (isErrorAttempt(current)) {
|
|
116
103
|
const { error } = current;
|
|
117
104
|
if (APICallError.isInstance(error) && error.isRetryable === true) {
|
|
118
|
-
const model =
|
|
105
|
+
const model = current.model;
|
|
119
106
|
const headerDelay = parseRetryHeaders(error.responseHeaders);
|
|
120
107
|
if (headerDelay !== null) return {
|
|
121
108
|
model,
|
|
122
|
-
...
|
|
123
|
-
delay: Math.min(headerDelay, MAX_RETRY_AFTER_MS)
|
|
109
|
+
...options,
|
|
110
|
+
delay: Math.min(headerDelay, MAX_RETRY_AFTER_MS),
|
|
111
|
+
backoffFactor: 1
|
|
124
112
|
};
|
|
125
113
|
return {
|
|
126
114
|
model,
|
|
127
|
-
...
|
|
115
|
+
...options
|
|
128
116
|
};
|
|
129
117
|
}
|
|
130
118
|
}
|
|
@@ -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,6 +55,7 @@ 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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-retry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|