ai-retry 0.4.0 → 0.5.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 +17 -27
- package/dist/index.d.ts +59 -2
- package/dist/index.js +79 -35
- package/dist/retryables/index.d.ts +8 -12
- package/dist/retryables/index.js +21 -21
- package/dist/{types-BrJaHkFh.d.ts → types-DqwAmcZS.d.ts} +14 -12
- package/dist/{utils-DNoBKkQe.js → utils-BlCGaP0E.js} +5 -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
|
```
|
|
@@ -197,11 +194,6 @@ const retryableModel = createRetryable({
|
|
|
197
194
|
});
|
|
198
195
|
```
|
|
199
196
|
|
|
200
|
-
**Options:**
|
|
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
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. If no headers are present, the configured delay or exponential backoff will be used.
|
|
206
198
|
|
|
207
199
|
#### Fallbacks
|
|
@@ -284,22 +276,17 @@ try {
|
|
|
284
276
|
|
|
285
277
|
#### Retry Delays
|
|
286
278
|
|
|
287
|
-
You can
|
|
279
|
+
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
280
|
|
|
289
281
|
```typescript
|
|
290
282
|
const retryableModel = createRetryable({
|
|
291
283
|
model: openai('gpt-4'),
|
|
292
284
|
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
|
-
}),
|
|
285
|
+
// Retry model 3 times with fixed 2s delay
|
|
286
|
+
() => ({ model: openai('gpt-4'), delay: 2000, maxAttempts: 3 }),
|
|
287
|
+
|
|
288
|
+
// Or retry with exponential backoff (2s, 4s, 8s)
|
|
289
|
+
() => ({ model: openai('gpt-4'), delay: 2000, backoffFactor: 2, maxAttempts: 3 }),
|
|
303
290
|
],
|
|
304
291
|
});
|
|
305
292
|
|
|
@@ -405,29 +392,32 @@ interface RetryableModelOptions<MODEL extends LanguageModelV2 | EmbeddingModelV2
|
|
|
405
392
|
#### `Retryable`
|
|
406
393
|
|
|
407
394
|
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 `
|
|
395
|
+
It should evaluate the error/result and decide whether to retry by returning a `Retry` or to skip by returning `undefined`.
|
|
409
396
|
|
|
410
397
|
```ts
|
|
411
398
|
type Retryable = (
|
|
412
399
|
context: RetryContext
|
|
413
|
-
) =>
|
|
400
|
+
) => Retry | Promise<Retry> | undefined;
|
|
414
401
|
```
|
|
415
402
|
|
|
416
|
-
#### `
|
|
403
|
+
#### `Retry`
|
|
417
404
|
|
|
418
|
-
A `
|
|
405
|
+
A `Retry` specifies the model to retry and optional settings like `maxAttempts`, `delay` and `backoffFactor`.
|
|
419
406
|
|
|
420
407
|
```typescript
|
|
421
|
-
interface
|
|
408
|
+
interface Retry {
|
|
422
409
|
model: LanguageModelV2 | EmbeddingModelV2;
|
|
423
|
-
maxAttempts?: number;
|
|
424
|
-
delay?: number;
|
|
410
|
+
maxAttempts?: number; // Maximum retry attempts per model (default: 1)
|
|
411
|
+
delay?: number; // Delay in milliseconds before retrying
|
|
412
|
+
backoffFactor?: number; // Multiplier for exponential backoff
|
|
425
413
|
}
|
|
426
414
|
```
|
|
427
415
|
|
|
428
416
|
**Options:**
|
|
417
|
+
- `model`: The model to use for the retry attempt.
|
|
429
418
|
- `maxAttempts`: Maximum number of times this model can be retried. Default is 1.
|
|
430
|
-
- `delay`: Delay in milliseconds to wait before retrying.
|
|
419
|
+
- `delay`: Delay in milliseconds to wait before retrying. The delay respects abort signals from the request.
|
|
420
|
+
- `backoffFactor`: Multiplier for exponential backoff (`delay × backoffFactor^attempt`). If not provided, uses fixed delay.
|
|
431
421
|
|
|
432
422
|
#### `RetryContext`
|
|
433
423
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +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
|
+
import * as _ai_sdk_provider0 from "@ai-sdk/provider";
|
|
3
|
+
import { LanguageModelV2 as LanguageModelV2$1, LanguageModelV2StreamPart } from "@ai-sdk/provider";
|
|
2
4
|
|
|
3
5
|
//#region src/create-retryable-model.d.ts
|
|
4
6
|
declare function createRetryable<MODEL extends LanguageModelV2>(options: RetryableModelOptions<MODEL>): LanguageModelV2;
|
|
@@ -10,4 +12,59 @@ declare function createRetryable<MODEL extends EmbeddingModelV2>(options: Retrya
|
|
|
10
12
|
*/
|
|
11
13
|
declare const getModelKey: (model: LanguageModelV2 | EmbeddingModelV2) => string;
|
|
12
14
|
//#endregion
|
|
13
|
-
|
|
15
|
+
//#region src/utils.d.ts
|
|
16
|
+
declare const isObject: (value: unknown) => value is Record<string, unknown>;
|
|
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;
|
|
21
|
+
declare const isStreamResult: (result: LanguageModelV2Generate | LanguageModelV2Stream) => result is LanguageModelV2Stream;
|
|
22
|
+
declare const isGenerateResult: (result: LanguageModelV2Generate | LanguageModelV2Stream) => result is LanguageModelV2Generate;
|
|
23
|
+
/**
|
|
24
|
+
* Type guard to check if a retry attempt is an error attempt
|
|
25
|
+
*/
|
|
26
|
+
declare function isErrorAttempt(attempt: RetryAttempt<any>): attempt is RetryErrorAttempt<any>;
|
|
27
|
+
/**
|
|
28
|
+
* Type guard to check if a retry attempt is a result attempt
|
|
29
|
+
*/
|
|
30
|
+
declare function isResultAttempt(attempt: RetryAttempt<any>): attempt is RetryResultAttempt;
|
|
31
|
+
/**
|
|
32
|
+
* Check if a stream part is a content part (e.g., text delta, reasoning delta, source, tool call, tool result).
|
|
33
|
+
* These types are also emitted by `onChunk` callbacks.
|
|
34
|
+
* @see https://github.com/vercel/ai/blob/1fe4bd4144bff927f5319d9d206e782a73979ccb/packages/ai/src/generate-text/stream-text.ts#L686-L697
|
|
35
|
+
*/
|
|
36
|
+
declare const isStreamContentPart: (part: LanguageModelV2StreamPart) => part is _ai_sdk_provider0.LanguageModelV2Source | _ai_sdk_provider0.LanguageModelV2ToolCall | {
|
|
37
|
+
type: "tool-result";
|
|
38
|
+
toolCallId: string;
|
|
39
|
+
toolName: string;
|
|
40
|
+
result: unknown;
|
|
41
|
+
isError?: boolean;
|
|
42
|
+
providerExecuted?: boolean;
|
|
43
|
+
providerMetadata?: _ai_sdk_provider0.SharedV2ProviderMetadata;
|
|
44
|
+
} | {
|
|
45
|
+
type: "text-delta";
|
|
46
|
+
id: string;
|
|
47
|
+
providerMetadata?: _ai_sdk_provider0.SharedV2ProviderMetadata;
|
|
48
|
+
delta: string;
|
|
49
|
+
} | {
|
|
50
|
+
type: "reasoning-delta";
|
|
51
|
+
id: string;
|
|
52
|
+
providerMetadata?: _ai_sdk_provider0.SharedV2ProviderMetadata;
|
|
53
|
+
delta: string;
|
|
54
|
+
} | {
|
|
55
|
+
type: "tool-input-start";
|
|
56
|
+
id: string;
|
|
57
|
+
toolName: string;
|
|
58
|
+
providerMetadata?: _ai_sdk_provider0.SharedV2ProviderMetadata;
|
|
59
|
+
providerExecuted?: boolean;
|
|
60
|
+
} | {
|
|
61
|
+
type: "tool-input-delta";
|
|
62
|
+
id: string;
|
|
63
|
+
delta: string;
|
|
64
|
+
providerMetadata?: _ai_sdk_provider0.SharedV2ProviderMetadata;
|
|
65
|
+
} | {
|
|
66
|
+
type: "raw";
|
|
67
|
+
rawValue: unknown;
|
|
68
|
+
};
|
|
69
|
+
//#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 };
|
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 };
|
|
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,16 @@ 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
26
|
* Retry with the same or a different model if the error is retryable with a delay.
|
|
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` with exponential backoff if `backoffFactor` is provided.
|
|
33
29
|
*/
|
|
34
|
-
declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?:
|
|
35
|
-
declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(options:
|
|
30
|
+
declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
31
|
+
declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV2>(options: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
36
32
|
//#endregion
|
|
37
33
|
//#region src/retryables/service-overloaded.d.ts
|
|
38
34
|
/**
|
|
@@ -42,6 +38,6 @@ declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV
|
|
|
42
38
|
* - Response with `type: "overloaded_error"`
|
|
43
39
|
* - Response with a `message` containing "overloaded"
|
|
44
40
|
*/
|
|
45
|
-
declare function serviceOverloaded<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?:
|
|
41
|
+
declare function serviceOverloaded<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
46
42
|
//#endregion
|
|
47
43
|
export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, serviceOverloaded };
|
package/dist/retryables/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as isModelV2, 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
|
|
|
@@ -70,12 +70,20 @@ function requestTimeout(model, options) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
//#endregion
|
|
73
|
-
//#region src/
|
|
73
|
+
//#region src/internal/resolve-retryable-options.ts
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
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)`
|
|
76
80
|
*/
|
|
77
|
-
function
|
|
78
|
-
|
|
81
|
+
function resolveRetryableOptions(modelOrOptions, options) {
|
|
82
|
+
if (isModelV2(modelOrOptions)) return {
|
|
83
|
+
...options,
|
|
84
|
+
model: modelOrOptions
|
|
85
|
+
};
|
|
86
|
+
return modelOrOptions;
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
//#endregion
|
|
@@ -101,30 +109,22 @@ function parseRetryHeaders(headers) {
|
|
|
101
109
|
//#region src/retryables/retry-after-delay.ts
|
|
102
110
|
const MAX_RETRY_AFTER_MS = 6e4;
|
|
103
111
|
function retryAfterDelay(modelOrOptions, options) {
|
|
104
|
-
const
|
|
105
|
-
const opts = modelOrOptions && "delay" in modelOrOptions ? modelOrOptions : options;
|
|
106
|
-
if (!opts?.delay) throw new Error("retryAfterDelay: delay is required");
|
|
107
|
-
const delay$1 = opts.delay;
|
|
108
|
-
const backoffFactor = Math.max(opts.backoffFactor ?? 1, 1);
|
|
112
|
+
const resolvedOptions = resolveRetryableOptions(modelOrOptions, options);
|
|
109
113
|
return (context) => {
|
|
110
|
-
const { current
|
|
114
|
+
const { current } = context;
|
|
111
115
|
if (isErrorAttempt(current)) {
|
|
112
116
|
const { error } = current;
|
|
113
117
|
if (APICallError.isInstance(error) && error.isRetryable === true) {
|
|
114
|
-
const
|
|
115
|
-
const modelKey = getModelKey(targetModel);
|
|
116
|
-
const modelAttempts = attempts.filter((a) => getModelKey(a.model) === modelKey);
|
|
118
|
+
const model = resolvedOptions.model ?? current.model;
|
|
117
119
|
const headerDelay = parseRetryHeaders(error.responseHeaders);
|
|
118
120
|
if (headerDelay !== null) return {
|
|
119
|
-
model
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
model,
|
|
122
|
+
...resolvedOptions,
|
|
123
|
+
delay: Math.min(headerDelay, MAX_RETRY_AFTER_MS)
|
|
122
124
|
};
|
|
123
|
-
const calculatedDelay = calculateExponentialBackoff(delay$1, backoffFactor, modelAttempts.length);
|
|
124
125
|
return {
|
|
125
|
-
model
|
|
126
|
-
|
|
127
|
-
maxAttempts: opts.maxAttempts
|
|
126
|
+
model,
|
|
127
|
+
...resolvedOptions
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
130
|
}
|
|
@@ -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,10 @@
|
|
|
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";
|
|
7
|
+
const isStreamResult = (result) => "stream" in result;
|
|
13
8
|
const isGenerateResult = (result) => "content" in result;
|
|
14
9
|
/**
|
|
15
10
|
* Type guard to check if a retry attempt is an error attempt
|
|
@@ -33,4 +28,4 @@ const isStreamContentPart = (part) => {
|
|
|
33
28
|
};
|
|
34
29
|
|
|
35
30
|
//#endregion
|
|
36
|
-
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.0",
|
|
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",
|