ai-retry 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -36
- package/dist/index.d.ts +6 -3
- package/dist/index.js +79 -35
- package/dist/retryables/index.d.ts +8 -13
- package/dist/retryables/index.js +14 -26
- package/dist/{types-BrJaHkFh.d.ts → types-DqwAmcZS.d.ts} +14 -12
- package/dist/{utils-Dojn0elD.js → utils-BlCGaP0E.js} +4 -10
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -21,9 +21,6 @@ It supports two types of retries:
|
|
|
21
21
|
|
|
22
22
|
This library only supports AI SDK v5.
|
|
23
23
|
|
|
24
|
-
> [!WARNING]
|
|
25
|
-
> `ai-retry` is in an early stage and the API may change in future releases.
|
|
26
|
-
|
|
27
24
|
```bash
|
|
28
25
|
npm install ai-retry
|
|
29
26
|
```
|
|
@@ -173,11 +170,10 @@ const retryable = createRetryable({
|
|
|
173
170
|
|
|
174
171
|
#### Retry After Delay
|
|
175
172
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
> [!NOTE]
|
|
179
|
-
> 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.
|
|
180
175
|
|
|
176
|
+
Note that this retryable does not accept a model parameter, it will always retry the model from the latest failed attempt.
|
|
181
177
|
|
|
182
178
|
```typescript
|
|
183
179
|
import { retryAfterDelay } from 'ai-retry/retryables';
|
|
@@ -190,19 +186,14 @@ const retryableModel = createRetryable({
|
|
|
190
186
|
|
|
191
187
|
// Or retry with exponential backoff (2s, 4s, 8s)
|
|
192
188
|
retryAfterDelay({ delay: 2000, backoffFactor: 2, maxAttempts: 3 }),
|
|
193
|
-
|
|
194
|
-
// Or
|
|
195
|
-
retryAfterDelay(
|
|
189
|
+
|
|
190
|
+
// Or retry only if the response contains a retry-after header
|
|
191
|
+
retryAfterDelay({ maxAttempts: 3 }),
|
|
196
192
|
],
|
|
197
193
|
});
|
|
198
194
|
```
|
|
199
195
|
|
|
200
|
-
|
|
201
|
-
- `delay` (required): Delay in milliseconds before retrying
|
|
202
|
-
- `backoffFactor` (optional): Multiplier for exponential backoff (delay × backoffFactor^attempt). If not provided, uses fixed delay.
|
|
203
|
-
- `maxAttempts` (optional): Maximum number of retry attempts for this model
|
|
204
|
-
|
|
205
|
-
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. If no headers are present, the configured delay or exponential backoff will be used.
|
|
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.
|
|
206
197
|
|
|
207
198
|
#### Fallbacks
|
|
208
199
|
|
|
@@ -284,22 +275,17 @@ try {
|
|
|
284
275
|
|
|
285
276
|
#### Retry Delays
|
|
286
277
|
|
|
287
|
-
You can
|
|
278
|
+
You can delay retries with an optional exponential backoff. The delay respects abort signals, so requests can still be cancelled during the delay period.
|
|
288
279
|
|
|
289
280
|
```typescript
|
|
290
281
|
const retryableModel = createRetryable({
|
|
291
282
|
model: openai('gpt-4'),
|
|
292
283
|
retries: [
|
|
293
|
-
//
|
|
294
|
-
() => ({
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}),
|
|
298
|
-
// Wait 2 seconds before trying a different provider
|
|
299
|
-
() => ({
|
|
300
|
-
model: anthropic('claude-3-haiku-20240307'),
|
|
301
|
-
delay: 2_000
|
|
302
|
-
}),
|
|
284
|
+
// Retry model 3 times with fixed 2s delay
|
|
285
|
+
() => ({ model: openai('gpt-4'), delay: 2000, maxAttempts: 3 }),
|
|
286
|
+
|
|
287
|
+
// Or retry with exponential backoff (2s, 4s, 8s)
|
|
288
|
+
() => ({ model: openai('gpt-4'), delay: 2000, backoffFactor: 2, maxAttempts: 3 }),
|
|
303
289
|
],
|
|
304
290
|
});
|
|
305
291
|
|
|
@@ -383,7 +369,7 @@ There are several built-in retryables:
|
|
|
383
369
|
- [`contentFilterTriggered`](./src/retryables/content-filter-triggered.ts): Content filter was triggered based on the prompt or completion.
|
|
384
370
|
- [`requestTimeout`](./src/retryables/request-timeout.ts): Request timeout occurred.
|
|
385
371
|
- [`requestNotRetryable`](./src/retryables/request-not-retryable.ts): Request failed with a non-retryable error.
|
|
386
|
-
- [`retryAfterDelay`](./src/retryables/retry-after-delay.ts): Retry with exponential backoff and respect `retry-after` headers
|
|
372
|
+
- [`retryAfterDelay`](./src/retryables/retry-after-delay.ts): Retry with delay and exponential backoff and respect `retry-after` headers.
|
|
387
373
|
- [`serviceOverloaded`](./src/retryables/service-overloaded.ts): Response with status code 529 (service overloaded).
|
|
388
374
|
- Use this retryable to handle Anthropic's overloaded errors.
|
|
389
375
|
|
|
@@ -405,29 +391,32 @@ interface RetryableModelOptions<MODEL extends LanguageModelV2 | EmbeddingModelV2
|
|
|
405
391
|
#### `Retryable`
|
|
406
392
|
|
|
407
393
|
A `Retryable` is a function that receives a `RetryContext` with the current error or result and model and all previous attempts.
|
|
408
|
-
It should evaluate the error/result and decide whether to retry by returning a `
|
|
394
|
+
It should evaluate the error/result and decide whether to retry by returning a `Retry` or to skip by returning `undefined`.
|
|
409
395
|
|
|
410
396
|
```ts
|
|
411
397
|
type Retryable = (
|
|
412
398
|
context: RetryContext
|
|
413
|
-
) =>
|
|
399
|
+
) => Retry | Promise<Retry> | undefined;
|
|
414
400
|
```
|
|
415
401
|
|
|
416
|
-
#### `
|
|
402
|
+
#### `Retry`
|
|
417
403
|
|
|
418
|
-
A `
|
|
404
|
+
A `Retry` specifies the model to retry and optional settings like `maxAttempts`, `delay` and `backoffFactor`.
|
|
419
405
|
|
|
420
406
|
```typescript
|
|
421
|
-
interface
|
|
407
|
+
interface Retry {
|
|
422
408
|
model: LanguageModelV2 | EmbeddingModelV2;
|
|
423
|
-
maxAttempts?: number;
|
|
424
|
-
delay?: number;
|
|
409
|
+
maxAttempts?: number; // Maximum retry attempts per model (default: 1)
|
|
410
|
+
delay?: number; // Delay in milliseconds before retrying
|
|
411
|
+
backoffFactor?: number; // Multiplier for exponential backoff
|
|
425
412
|
}
|
|
426
413
|
```
|
|
427
414
|
|
|
428
415
|
**Options:**
|
|
416
|
+
- `model`: The model to use for the retry attempt.
|
|
429
417
|
- `maxAttempts`: Maximum number of times this model can be retried. Default is 1.
|
|
430
|
-
- `delay`: Delay in milliseconds to wait before retrying.
|
|
418
|
+
- `delay`: Delay in milliseconds to wait before retrying. The delay respects abort signals from the request.
|
|
419
|
+
- `backoffFactor`: Multiplier for exponential backoff (`delay × backoffFactor^attempt`). If not provided, uses fixed delay.
|
|
431
420
|
|
|
432
421
|
#### `RetryContext`
|
|
433
422
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
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-DqwAmcZS.js";
|
|
2
2
|
import * as _ai_sdk_provider0 from "@ai-sdk/provider";
|
|
3
|
-
import { LanguageModelV2StreamPart } from "@ai-sdk/provider";
|
|
3
|
+
import { LanguageModelV2 as LanguageModelV2$1, LanguageModelV2StreamPart } from "@ai-sdk/provider";
|
|
4
4
|
|
|
5
5
|
//#region src/create-retryable-model.d.ts
|
|
6
6
|
declare function createRetryable<MODEL extends LanguageModelV2>(options: RetryableModelOptions<MODEL>): LanguageModelV2;
|
|
@@ -15,6 +15,9 @@ declare const getModelKey: (model: LanguageModelV2 | EmbeddingModelV2) => string
|
|
|
15
15
|
//#region src/utils.d.ts
|
|
16
16
|
declare const isObject: (value: unknown) => value is Record<string, unknown>;
|
|
17
17
|
declare const isString: (value: unknown) => value is string;
|
|
18
|
+
declare const isModelV2: (model: unknown) => model is LanguageModelV2$1 | EmbeddingModelV2;
|
|
19
|
+
declare const isLanguageModelV2: (model: unknown) => model is LanguageModelV2$1;
|
|
20
|
+
declare const isEmbeddingModelV2: (model: unknown) => model is EmbeddingModelV2;
|
|
18
21
|
declare const isStreamResult: (result: LanguageModelV2Generate | LanguageModelV2Stream) => result is LanguageModelV2Stream;
|
|
19
22
|
declare const isGenerateResult: (result: LanguageModelV2Generate | LanguageModelV2Stream) => result is LanguageModelV2Generate;
|
|
20
23
|
/**
|
|
@@ -64,4 +67,4 @@ declare const isStreamContentPart: (part: LanguageModelV2StreamPart) => part is
|
|
|
64
67
|
rawValue: unknown;
|
|
65
68
|
};
|
|
66
69
|
//#endregion
|
|
67
|
-
export { EmbeddingModelV2, EmbeddingModelV2CallOptions, EmbeddingModelV2Embed, LanguageModelV2, LanguageModelV2Generate, LanguageModelV2Stream, Retries, RetryAttempt, RetryContext, RetryErrorAttempt,
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as isModelV2, c as isStreamContentPart, i as isLanguageModelV2, l as isStreamResult, n as isErrorAttempt, o as isObject, r as isGenerateResult, s as isResultAttempt, t as isEmbeddingModelV2, u as isString } from "./utils-BlCGaP0E.js";
|
|
2
2
|
import { delay } from "@ai-sdk/provider-utils";
|
|
3
3
|
import { getErrorMessage } from "@ai-sdk/provider";
|
|
4
4
|
import { RetryError } from "ai";
|
|
5
5
|
|
|
6
|
+
//#region src/calculate-exponential-backoff.ts
|
|
7
|
+
/**
|
|
8
|
+
* Calculates the exponential backoff delay.
|
|
9
|
+
*/
|
|
10
|
+
function calculateExponentialBackoff(baseDelay, backoffFactor = 1, attempts) {
|
|
11
|
+
return baseDelay * Math.max(backoffFactor, 1) ** attempts;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/get-model-key.ts
|
|
16
|
+
/**
|
|
17
|
+
* Generate a unique key for a LanguageModelV2 instance.
|
|
18
|
+
*/
|
|
19
|
+
const getModelKey = (model) => {
|
|
20
|
+
return `${model.provider}/${model.modelId}`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/count-model-attempts.ts
|
|
25
|
+
function countModelAttempts(model, attempts) {
|
|
26
|
+
const modelKey = getModelKey(model);
|
|
27
|
+
return attempts.filter((a) => getModelKey(a.model) === modelKey).length;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
6
31
|
//#region src/find-retry-model.ts
|
|
7
32
|
/**
|
|
8
33
|
* Find the next model to retry with based on the retry context
|
|
@@ -18,10 +43,7 @@ async function findRetryModel(retries, context) {
|
|
|
18
43
|
* Iterate through the applicable retryables to find a model to retry with
|
|
19
44
|
*/
|
|
20
45
|
for (const retry of applicableRetries) {
|
|
21
|
-
const retryModel = typeof retry === "function" ? await retry(context) : {
|
|
22
|
-
model: retry,
|
|
23
|
-
maxAttempts: 1
|
|
24
|
-
};
|
|
46
|
+
const retryModel = typeof retry === "function" ? await retry(context) : { model: retry };
|
|
25
47
|
if (retryModel) {
|
|
26
48
|
/**
|
|
27
49
|
* The model key uniquely identifies a model instance (provider + modelId)
|
|
@@ -97,17 +119,12 @@ var RetryableEmbeddingModel = class {
|
|
|
97
119
|
* Skip on the first attempt since no previous attempt exists yet.
|
|
98
120
|
*/
|
|
99
121
|
if (previousAttempt) {
|
|
100
|
-
const currentAttempt = {
|
|
101
|
-
...previousAttempt,
|
|
102
|
-
model: this.currentModel
|
|
103
|
-
};
|
|
104
|
-
/**
|
|
105
|
-
* Create a shallow copy of the attempts for testing purposes
|
|
106
|
-
*/
|
|
107
|
-
const updatedAttempts = [...attempts];
|
|
108
122
|
const context = {
|
|
109
|
-
current:
|
|
110
|
-
|
|
123
|
+
current: {
|
|
124
|
+
...previousAttempt,
|
|
125
|
+
model: this.currentModel
|
|
126
|
+
},
|
|
127
|
+
attempts: [...attempts]
|
|
111
128
|
};
|
|
112
129
|
this.options.onRetry?.(context);
|
|
113
130
|
}
|
|
@@ -119,7 +136,18 @@ var RetryableEmbeddingModel = class {
|
|
|
119
136
|
} catch (error) {
|
|
120
137
|
const { retryModel, attempt } = await this.handleError(error, attempts);
|
|
121
138
|
attempts.push(attempt);
|
|
122
|
-
if (retryModel.delay)
|
|
139
|
+
if (retryModel.delay) {
|
|
140
|
+
/**
|
|
141
|
+
* Calculate exponential backoff delay based on the number of attempts for this specific model.
|
|
142
|
+
* The delay grows exponentially: baseDelay * backoffFactor^attempts
|
|
143
|
+
* Example: With delay=1000ms and backoffFactor=2:
|
|
144
|
+
* - Attempt 1: 1000ms
|
|
145
|
+
* - Attempt 2: 2000ms
|
|
146
|
+
* - Attempt 3: 4000ms
|
|
147
|
+
*/
|
|
148
|
+
const modelAttemptsCount = countModelAttempts(retryModel.model, attempts);
|
|
149
|
+
await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
|
|
150
|
+
}
|
|
123
151
|
this.currentModel = retryModel.model;
|
|
124
152
|
}
|
|
125
153
|
}
|
|
@@ -208,17 +236,12 @@ var RetryableLanguageModel = class {
|
|
|
208
236
|
* Skip on the first attempt since no previous attempt exists yet.
|
|
209
237
|
*/
|
|
210
238
|
if (previousAttempt) {
|
|
211
|
-
const currentAttempt = {
|
|
212
|
-
...previousAttempt,
|
|
213
|
-
model: this.currentModel
|
|
214
|
-
};
|
|
215
|
-
/**
|
|
216
|
-
* Create a shallow copy of the attempts for testing purposes
|
|
217
|
-
*/
|
|
218
|
-
const updatedAttempts = [...attempts];
|
|
219
239
|
const context = {
|
|
220
|
-
current:
|
|
221
|
-
|
|
240
|
+
current: {
|
|
241
|
+
...previousAttempt,
|
|
242
|
+
model: this.currentModel
|
|
243
|
+
},
|
|
244
|
+
attempts: [...attempts]
|
|
222
245
|
};
|
|
223
246
|
this.options.onRetry?.(context);
|
|
224
247
|
}
|
|
@@ -234,7 +257,18 @@ var RetryableLanguageModel = class {
|
|
|
234
257
|
const { retryModel, attempt } = await this.handleResult(result, attempts);
|
|
235
258
|
attempts.push(attempt);
|
|
236
259
|
if (retryModel) {
|
|
237
|
-
if (retryModel.delay)
|
|
260
|
+
if (retryModel.delay) {
|
|
261
|
+
/**
|
|
262
|
+
* Calculate exponential backoff delay based on the number of attempts for this specific model.
|
|
263
|
+
* The delay grows exponentially: baseDelay * backoffFactor^attempts
|
|
264
|
+
* Example: With delay=1000ms and backoffFactor=2:
|
|
265
|
+
* - Attempt 1: 1000ms
|
|
266
|
+
* - Attempt 2: 2000ms
|
|
267
|
+
* - Attempt 3: 4000ms
|
|
268
|
+
*/
|
|
269
|
+
const modelAttemptsCount = countModelAttempts(retryModel.model, attempts);
|
|
270
|
+
await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
|
|
271
|
+
}
|
|
238
272
|
this.currentModel = retryModel.model;
|
|
239
273
|
/**
|
|
240
274
|
* Continue to the next iteration to retry
|
|
@@ -249,7 +283,14 @@ var RetryableLanguageModel = class {
|
|
|
249
283
|
} catch (error) {
|
|
250
284
|
const { retryModel, attempt } = await this.handleError(error, attempts);
|
|
251
285
|
attempts.push(attempt);
|
|
252
|
-
if (retryModel.delay)
|
|
286
|
+
if (retryModel.delay) {
|
|
287
|
+
/**
|
|
288
|
+
* Calculate exponential backoff delay based on the number of attempts for this specific model.
|
|
289
|
+
* The delay grows exponentially: baseDelay * backoffFactor^attempts
|
|
290
|
+
*/
|
|
291
|
+
const modelAttemptsCount = countModelAttempts(retryModel.model, attempts);
|
|
292
|
+
await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: input.abortSignal });
|
|
293
|
+
}
|
|
253
294
|
this.currentModel = retryModel.model;
|
|
254
295
|
}
|
|
255
296
|
}
|
|
@@ -263,13 +304,9 @@ var RetryableLanguageModel = class {
|
|
|
263
304
|
result,
|
|
264
305
|
model: this.currentModel
|
|
265
306
|
};
|
|
266
|
-
/**
|
|
267
|
-
* Save the current attempt
|
|
268
|
-
*/
|
|
269
|
-
const updatedAttempts = [...attempts, resultAttempt];
|
|
270
307
|
const context = {
|
|
271
308
|
current: resultAttempt,
|
|
272
|
-
attempts:
|
|
309
|
+
attempts: [...attempts, resultAttempt]
|
|
273
310
|
};
|
|
274
311
|
return {
|
|
275
312
|
retryModel: await findRetryModel(this.options.retries, context),
|
|
@@ -370,7 +407,14 @@ var RetryableLanguageModel = class {
|
|
|
370
407
|
* Save the attempt
|
|
371
408
|
*/
|
|
372
409
|
attempts.push(attempt);
|
|
373
|
-
if (retryModel.delay)
|
|
410
|
+
if (retryModel.delay) {
|
|
411
|
+
/**
|
|
412
|
+
* Calculate exponential backoff delay based on the number of attempts for this specific model.
|
|
413
|
+
* The delay grows exponentially: baseDelay * backoffFactor^attempts
|
|
414
|
+
*/
|
|
415
|
+
const modelAttemptsCount = countModelAttempts(retryModel.model, attempts);
|
|
416
|
+
await delay(calculateExponentialBackoff(retryModel.delay, retryModel.backoffFactor, modelAttemptsCount), { abortSignal: options.abortSignal });
|
|
417
|
+
}
|
|
374
418
|
this.currentModel = retryModel.model;
|
|
375
419
|
/**
|
|
376
420
|
* Retry the request by calling doStream again.
|
|
@@ -406,4 +450,4 @@ function createRetryable(options) {
|
|
|
406
450
|
}
|
|
407
451
|
|
|
408
452
|
//#endregion
|
|
409
|
-
export { createRetryable, getModelKey, isErrorAttempt, isGenerateResult, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString };
|
|
453
|
+
export { createRetryable, getModelKey, isEmbeddingModelV2, isErrorAttempt, isGenerateResult, isLanguageModelV2, isModelV2, isObject, isResultAttempt, isStreamContentPart, isStreamResult, isString };
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { h as RetryableOptions, i as LanguageModelV2, p as Retryable, t as EmbeddingModelV2 } from "../types-DqwAmcZS.js";
|
|
2
2
|
|
|
3
3
|
//#region src/retryables/content-filter-triggered.d.ts
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Fallback to a different model if the content filter was triggered.
|
|
7
7
|
*/
|
|
8
|
-
declare function contentFilterTriggered<MODEL extends LanguageModelV2>(model: MODEL, options?:
|
|
8
|
+
declare function contentFilterTriggered<MODEL extends LanguageModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
9
9
|
//#endregion
|
|
10
10
|
//#region src/retryables/request-not-retryable.d.ts
|
|
11
11
|
/**
|
|
12
12
|
* Fallback to a different model if the error is non-retryable.
|
|
13
13
|
*/
|
|
14
|
-
declare function requestNotRetryable<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?:
|
|
14
|
+
declare function requestNotRetryable<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
15
15
|
//#endregion
|
|
16
16
|
//#region src/retryables/request-timeout.d.ts
|
|
17
17
|
/**
|
|
@@ -19,20 +19,15 @@ declare function requestNotRetryable<MODEL extends LanguageModelV2 | EmbeddingMo
|
|
|
19
19
|
* Use in combination with the `abortSignal` option.
|
|
20
20
|
* Works with both `LanguageModelV2` and `EmbeddingModelV2`.
|
|
21
21
|
*/
|
|
22
|
-
declare function requestTimeout<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?:
|
|
22
|
+
declare function requestTimeout<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
23
23
|
//#endregion
|
|
24
24
|
//#region src/retryables/retry-after-delay.d.ts
|
|
25
|
-
type RetryAfterDelayOptions<MODEL extends LanguageModelV2 | EmbeddingModelV2> = Omit<RetryModel<MODEL>, 'model' | 'delay'> & {
|
|
26
|
-
delay: number;
|
|
27
|
-
backoffFactor?: number;
|
|
28
|
-
};
|
|
29
25
|
/**
|
|
30
|
-
* Retry with the same
|
|
26
|
+
* Retry the current failed attempt with the same model, if the error is retryable.
|
|
31
27
|
* Uses the `Retry-After` or `Retry-After-Ms` headers if present.
|
|
32
|
-
* Otherwise uses the specified `delay
|
|
28
|
+
* Otherwise uses the specified `delay` and `backoffFactor` if provided.
|
|
33
29
|
*/
|
|
34
|
-
declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(
|
|
35
|
-
declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(options: RetryAfterDelayOptions<MODEL>): Retryable<MODEL>;
|
|
30
|
+
declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(options: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
36
31
|
//#endregion
|
|
37
32
|
//#region src/retryables/service-overloaded.d.ts
|
|
38
33
|
/**
|
|
@@ -42,6 +37,6 @@ declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV
|
|
|
42
37
|
* - Response with `type: "overloaded_error"`
|
|
43
38
|
* - Response with a `message` containing "overloaded"
|
|
44
39
|
*/
|
|
45
|
-
declare function serviceOverloaded<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?:
|
|
40
|
+
declare function serviceOverloaded<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
46
41
|
//#endregion
|
|
47
42
|
export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, serviceOverloaded };
|
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,15 +69,6 @@ function requestTimeout(model, options) {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
//#endregion
|
|
73
|
-
//#region src/calculate-exponential-backoff.ts
|
|
74
|
-
/**
|
|
75
|
-
* Calculates the exponential backoff delay.
|
|
76
|
-
*/
|
|
77
|
-
function calculateExponentialBackoff(baseDelay, backoffFactor, attempts) {
|
|
78
|
-
return baseDelay * backoffFactor ** attempts;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
72
|
//#endregion
|
|
82
73
|
//#region src/parse-retry-headers.ts
|
|
83
74
|
function parseRetryHeaders(headers) {
|
|
@@ -100,31 +91,28 @@ function parseRetryHeaders(headers) {
|
|
|
100
91
|
//#endregion
|
|
101
92
|
//#region src/retryables/retry-after-delay.ts
|
|
102
93
|
const MAX_RETRY_AFTER_MS = 6e4;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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) {
|
|
109
100
|
return (context) => {
|
|
110
|
-
const { current
|
|
101
|
+
const { current } = context;
|
|
111
102
|
if (isErrorAttempt(current)) {
|
|
112
103
|
const { error } = current;
|
|
113
104
|
if (APICallError.isInstance(error) && error.isRetryable === true) {
|
|
114
|
-
const
|
|
115
|
-
const modelKey = getModelKey(targetModel);
|
|
116
|
-
const modelAttempts = attempts.filter((a) => getModelKey(a.model) === modelKey);
|
|
105
|
+
const model = current.model;
|
|
117
106
|
const headerDelay = parseRetryHeaders(error.responseHeaders);
|
|
118
107
|
if (headerDelay !== null) return {
|
|
119
|
-
model
|
|
108
|
+
model,
|
|
109
|
+
...options,
|
|
120
110
|
delay: Math.min(headerDelay, MAX_RETRY_AFTER_MS),
|
|
121
|
-
|
|
111
|
+
backoffFactor: 1
|
|
122
112
|
};
|
|
123
|
-
const calculatedDelay = calculateExponentialBackoff(delay$1, backoffFactor, modelAttempts.length);
|
|
124
113
|
return {
|
|
125
|
-
model
|
|
126
|
-
|
|
127
|
-
maxAttempts: opts.maxAttempts
|
|
114
|
+
model,
|
|
115
|
+
...options
|
|
128
116
|
};
|
|
129
117
|
}
|
|
130
118
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { EmbeddingModelV2, LanguageModelV2 } from "@ai-sdk/provider";
|
|
1
|
+
import { EmbeddingModelV2, LanguageModelV2 as LanguageModelV2$1 } from "@ai-sdk/provider";
|
|
2
2
|
|
|
3
3
|
//#region src/types.d.ts
|
|
4
4
|
type EmbeddingModelV2$1<VALUE = any> = EmbeddingModelV2<VALUE>;
|
|
5
5
|
/**
|
|
6
6
|
* Options for creating a retryable model.
|
|
7
7
|
*/
|
|
8
|
-
interface RetryableModelOptions<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> {
|
|
8
|
+
interface RetryableModelOptions<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> {
|
|
9
9
|
model: MODEL;
|
|
10
10
|
retries: Retries<MODEL>;
|
|
11
11
|
onError?: (context: RetryContext<MODEL>) => void;
|
|
@@ -14,7 +14,7 @@ interface RetryableModelOptions<MODEL extends LanguageModelV2 | EmbeddingModelV2
|
|
|
14
14
|
/**
|
|
15
15
|
* The context provided to Retryables with the current attempt and all previous attempts.
|
|
16
16
|
*/
|
|
17
|
-
type RetryContext<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = {
|
|
17
|
+
type RetryContext<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = {
|
|
18
18
|
/**
|
|
19
19
|
* Current attempt that caused the retry
|
|
20
20
|
*/
|
|
@@ -27,7 +27,7 @@ type RetryContext<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = {
|
|
|
27
27
|
/**
|
|
28
28
|
* A retry attempt with an error
|
|
29
29
|
*/
|
|
30
|
-
type RetryErrorAttempt<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = {
|
|
30
|
+
type RetryErrorAttempt<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = {
|
|
31
31
|
type: 'error';
|
|
32
32
|
error: unknown;
|
|
33
33
|
result?: undefined;
|
|
@@ -40,28 +40,30 @@ type RetryResultAttempt = {
|
|
|
40
40
|
type: 'result';
|
|
41
41
|
result: LanguageModelV2Generate;
|
|
42
42
|
error?: undefined;
|
|
43
|
-
model: LanguageModelV2;
|
|
43
|
+
model: LanguageModelV2$1;
|
|
44
44
|
};
|
|
45
45
|
/**
|
|
46
46
|
* A retry attempt with either an error or a result and the model used
|
|
47
47
|
*/
|
|
48
|
-
type RetryAttempt<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = RetryErrorAttempt<MODEL> | RetryResultAttempt;
|
|
48
|
+
type RetryAttempt<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = RetryErrorAttempt<MODEL> | RetryResultAttempt;
|
|
49
49
|
/**
|
|
50
50
|
* A model to retry with and the maximum number of attempts for that model.
|
|
51
51
|
*/
|
|
52
|
-
type
|
|
52
|
+
type Retry<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = {
|
|
53
53
|
model: MODEL;
|
|
54
54
|
maxAttempts?: number;
|
|
55
55
|
delay?: number;
|
|
56
|
+
backoffFactor?: number;
|
|
56
57
|
};
|
|
57
58
|
/**
|
|
58
59
|
* A function that determines whether to retry with a different model based on the current attempt and all previous attempts.
|
|
59
60
|
*/
|
|
60
|
-
type Retryable<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = (context: RetryContext<MODEL>) =>
|
|
61
|
-
type Retries<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = Array<Retryable<MODEL> | MODEL>;
|
|
62
|
-
type
|
|
63
|
-
type
|
|
61
|
+
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>;
|
|
63
|
+
type RetryableOptions<MODEL extends LanguageModelV2$1 | EmbeddingModelV2$1> = Partial<Omit<Retry<MODEL>, 'model'>>;
|
|
64
|
+
type LanguageModelV2Generate = Awaited<ReturnType<LanguageModelV2$1['doGenerate']>>;
|
|
65
|
+
type LanguageModelV2Stream = Awaited<ReturnType<LanguageModelV2$1['doStream']>>;
|
|
64
66
|
type EmbeddingModelV2CallOptions<VALUE> = Parameters<EmbeddingModelV2$1<VALUE>['doEmbed']>[0];
|
|
65
67
|
type EmbeddingModelV2Embed<VALUE> = Awaited<ReturnType<EmbeddingModelV2$1<VALUE>['doEmbed']>>;
|
|
66
68
|
//#endregion
|
|
67
|
-
export {
|
|
69
|
+
export { LanguageModelV2Generate as a, Retry as c, RetryErrorAttempt as d, RetryResultAttempt as f, RetryableOptions as h, LanguageModelV2$1 as i, RetryAttempt as l, RetryableModelOptions as m, EmbeddingModelV2CallOptions as n, LanguageModelV2Stream as o, Retryable as p, EmbeddingModelV2Embed as r, Retries as s, EmbeddingModelV2$1 as t, RetryContext as u };
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
//#region src/get-model-key.ts
|
|
2
|
-
/**
|
|
3
|
-
* Generate a unique key for a LanguageModelV2 instance.
|
|
4
|
-
*/
|
|
5
|
-
const getModelKey = (model) => {
|
|
6
|
-
return `${model.provider}/${model.modelId}`;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
//#endregion
|
|
10
1
|
//#region src/utils.ts
|
|
11
2
|
const isObject = (value) => typeof value === "object" && value !== null;
|
|
12
3
|
const isString = (value) => typeof value === "string";
|
|
4
|
+
const isModelV2 = (model) => isLanguageModelV2(model) || isEmbeddingModelV2(model);
|
|
5
|
+
const isLanguageModelV2 = (model) => isObject(model) && "provider" in model && "modelId" in model && "specificationVersion" in model && model.specificationVersion === "v2";
|
|
6
|
+
const isEmbeddingModelV2 = (model) => isObject(model) && "provider" in model && "modelId" in model && "specificationVersion" in model && model.specificationVersion === "v2";
|
|
13
7
|
const isStreamResult = (result) => "stream" in result;
|
|
14
8
|
const isGenerateResult = (result) => "content" in result;
|
|
15
9
|
/**
|
|
@@ -34,4 +28,4 @@ const isStreamContentPart = (part) => {
|
|
|
34
28
|
};
|
|
35
29
|
|
|
36
30
|
//#endregion
|
|
37
|
-
export {
|
|
31
|
+
export { isModelV2 as a, isStreamContentPart as c, isLanguageModelV2 as i, isStreamResult as l, isErrorAttempt as n, isObject as o, isGenerateResult as r, isResultAttempt as s, isEmbeddingModelV2 as t, isString as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-retry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "AI SDK Retry",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@ai-sdk/anthropic": "^2.0.18",
|
|
37
37
|
"@ai-sdk/azure": "^2.0.30",
|
|
38
|
+
"@ai-sdk/groq": "^2.0.24",
|
|
38
39
|
"@ai-sdk/openai": "^2.0.30",
|
|
39
40
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
40
41
|
"@biomejs/biome": "^2.2.4",
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
"msw": "^2.11.2",
|
|
46
47
|
"pkg-pr-new": "^0.0.60",
|
|
47
48
|
"publint": "^0.3.13",
|
|
48
|
-
"tsdown": "^0.15.
|
|
49
|
+
"tsdown": "^0.15.9",
|
|
49
50
|
"tsx": "^4.20.5",
|
|
50
51
|
"typescript": "^5.9.2",
|
|
51
52
|
"unbuild": "^3.6.1",
|