ai-retry 0.2.0 → 0.3.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 +43 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +37 -23
- package/dist/retryables/index.d.ts +1 -1
- package/dist/{types-CHhEGL5x.d.ts → types-BrJaHkFh.d.ts} +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -247,6 +247,42 @@ try {
|
|
|
247
247
|
}
|
|
248
248
|
```
|
|
249
249
|
|
|
250
|
+
#### Retry Delays
|
|
251
|
+
|
|
252
|
+
You can add delays before retrying to handle rate limiting or give services time to recover. The delay respects abort signals, so requests can still be cancelled during the delay period.
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
const retryableModel = createRetryable({
|
|
256
|
+
model: openai('gpt-4'),
|
|
257
|
+
retries: [
|
|
258
|
+
// Wait 1 second before retrying
|
|
259
|
+
() => ({
|
|
260
|
+
model: openai('gpt-4'),
|
|
261
|
+
delay: 1_000
|
|
262
|
+
}),
|
|
263
|
+
// Wait 2 seconds before trying a different provider
|
|
264
|
+
() => ({
|
|
265
|
+
model: anthropic('claude-3-haiku-20240307'),
|
|
266
|
+
delay: 2_000
|
|
267
|
+
}),
|
|
268
|
+
],
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
You can also use delays with built-in retryables:
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import { serviceOverloaded } from 'ai-retry/retryables';
|
|
276
|
+
|
|
277
|
+
const retryableModel = createRetryable({
|
|
278
|
+
model: openai('gpt-4'),
|
|
279
|
+
retries: [
|
|
280
|
+
// Wait 5 seconds before retrying on service overload
|
|
281
|
+
serviceOverloaded(openai('gpt-4'), { maxAttempts: 3, delay: 5_000 }),
|
|
282
|
+
],
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
250
286
|
#### Logging
|
|
251
287
|
|
|
252
288
|
You can use the following callbacks to log retry attempts and errors:
|
|
@@ -318,16 +354,20 @@ type Retryable = (
|
|
|
318
354
|
|
|
319
355
|
#### `RetryModel`
|
|
320
356
|
|
|
321
|
-
A `RetryModel` specifies the model to retry and
|
|
322
|
-
By default, each retryable will only attempt to retry once per model. This can be customized by setting the `maxAttempts` property.
|
|
357
|
+
A `RetryModel` specifies the model to retry and optional settings like `maxAttempts` and `delay`.
|
|
323
358
|
|
|
324
359
|
```typescript
|
|
325
360
|
interface RetryModel {
|
|
326
361
|
model: LanguageModelV2 | EmbeddingModelV2;
|
|
327
|
-
maxAttempts?: number;
|
|
362
|
+
maxAttempts?: number; // Maximum retry attempts per model (default: 1)
|
|
363
|
+
delay?: number; // Delay in milliseconds before retrying
|
|
328
364
|
}
|
|
329
365
|
```
|
|
330
366
|
|
|
367
|
+
**Options:**
|
|
368
|
+
- `maxAttempts`: Maximum number of times this model can be retried. Default is 1.
|
|
369
|
+
- `delay`: Delay in milliseconds to wait before retrying. Useful for rate limiting or giving services time to recover. The delay respects abort signals from the request.
|
|
370
|
+
|
|
331
371
|
#### `RetryContext`
|
|
332
372
|
|
|
333
373
|
The `RetryContext` object contains information about the current attempt and all previous attempts.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EmbeddingModelV2, LanguageModelV2, RetryableModelOptions } from "./types-
|
|
1
|
+
import { EmbeddingModelV2, EmbeddingModelV2CallOptions, EmbeddingModelV2Embed, LanguageModelV2, LanguageModelV2Generate, LanguageModelV2Stream, Retries, RetryAttempt, RetryContext, RetryErrorAttempt, RetryModel, RetryResultAttempt, Retryable, RetryableModelOptions } from "./types-BrJaHkFh.js";
|
|
2
2
|
|
|
3
3
|
//#region src/create-retryable-model.d.ts
|
|
4
4
|
declare function createRetryable<MODEL extends LanguageModelV2>(options: RetryableModelOptions<MODEL>): LanguageModelV2;
|
|
@@ -10,4 +10,4 @@ declare function createRetryable<MODEL extends EmbeddingModelV2>(options: Retrya
|
|
|
10
10
|
*/
|
|
11
11
|
declare const getModelKey: (model: LanguageModelV2 | EmbeddingModelV2) => string;
|
|
12
12
|
//#endregion
|
|
13
|
-
export { createRetryable, getModelKey };
|
|
13
|
+
export { EmbeddingModelV2, EmbeddingModelV2CallOptions, EmbeddingModelV2Embed, LanguageModelV2, LanguageModelV2Generate, LanguageModelV2Stream, Retries, RetryAttempt, RetryContext, RetryErrorAttempt, RetryModel, RetryResultAttempt, Retryable, RetryableModelOptions, createRetryable, getModelKey };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isErrorAttempt, isGenerateResult, isResultAttempt, isStreamContentPart } from "./utils-lRsC105f.js";
|
|
2
|
-
import "@ai-sdk/provider-utils";
|
|
3
|
-
import { RetryError } from "ai";
|
|
2
|
+
import { delay } from "@ai-sdk/provider-utils";
|
|
4
3
|
import { getErrorMessage } from "@ai-sdk/provider";
|
|
4
|
+
import { RetryError } from "ai";
|
|
5
5
|
|
|
6
6
|
//#region src/get-model-key.ts
|
|
7
7
|
/**
|
|
@@ -44,7 +44,7 @@ async function findRetryModel(retries, context) {
|
|
|
44
44
|
/**
|
|
45
45
|
* Check if the model can still be retried based on maxAttempts
|
|
46
46
|
*/
|
|
47
|
-
if (retryAttempts.length < maxAttempts) return retryModel
|
|
47
|
+
if (retryAttempts.length < maxAttempts) return retryModel;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -126,9 +126,10 @@ var RetryableEmbeddingModel = class {
|
|
|
126
126
|
attempts
|
|
127
127
|
};
|
|
128
128
|
} catch (error) {
|
|
129
|
-
const {
|
|
129
|
+
const { retryModel, attempt } = await this.handleError(error, attempts);
|
|
130
130
|
attempts.push(attempt);
|
|
131
|
-
|
|
131
|
+
if (retryModel.delay) await delay(retryModel.delay, { abortSignal: input.abortSignal });
|
|
132
|
+
this.currentModel = retryModel.model;
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
}
|
|
@@ -150,17 +151,17 @@ var RetryableEmbeddingModel = class {
|
|
|
150
151
|
attempts: updatedAttempts
|
|
151
152
|
};
|
|
152
153
|
this.options.onError?.(context);
|
|
153
|
-
const
|
|
154
|
+
const retryModel = await findRetryModel(this.options.retries, context);
|
|
154
155
|
/**
|
|
155
156
|
* Handler didn't return any models to try next, rethrow the error.
|
|
156
157
|
* If we retried the request, wrap the error into a `RetryError` for better visibility.
|
|
157
158
|
*/
|
|
158
|
-
if (!
|
|
159
|
+
if (!retryModel) {
|
|
159
160
|
if (updatedAttempts.length > 1) throw prepareRetryError(error, updatedAttempts);
|
|
160
161
|
throw error;
|
|
161
162
|
}
|
|
162
163
|
return {
|
|
163
|
-
|
|
164
|
+
retryModel,
|
|
164
165
|
attempt: errorAttempt
|
|
165
166
|
};
|
|
166
167
|
}
|
|
@@ -169,7 +170,10 @@ var RetryableEmbeddingModel = class {
|
|
|
169
170
|
* Always start with the original model
|
|
170
171
|
*/
|
|
171
172
|
this.currentModel = this.baseModel;
|
|
172
|
-
const { result } = await this.withRetry({
|
|
173
|
+
const { result } = await this.withRetry({
|
|
174
|
+
fn: async () => await this.currentModel.doEmbed(options),
|
|
175
|
+
abortSignal: options.abortSignal
|
|
176
|
+
});
|
|
173
177
|
return result;
|
|
174
178
|
}
|
|
175
179
|
};
|
|
@@ -236,10 +240,11 @@ var RetryableLanguageModel = class {
|
|
|
236
240
|
* Check if the result should trigger a retry (only for generate results, not streams)
|
|
237
241
|
*/
|
|
238
242
|
if (isGenerateResult(result)) {
|
|
239
|
-
const {
|
|
243
|
+
const { retryModel, attempt } = await this.handleResult(result, attempts);
|
|
240
244
|
attempts.push(attempt);
|
|
241
|
-
if (
|
|
242
|
-
|
|
245
|
+
if (retryModel) {
|
|
246
|
+
if (retryModel.delay) await delay(retryModel.delay, { abortSignal: input.abortSignal });
|
|
247
|
+
this.currentModel = retryModel.model;
|
|
243
248
|
/**
|
|
244
249
|
* Continue to the next iteration to retry
|
|
245
250
|
*/
|
|
@@ -251,9 +256,10 @@ var RetryableLanguageModel = class {
|
|
|
251
256
|
attempts
|
|
252
257
|
};
|
|
253
258
|
} catch (error) {
|
|
254
|
-
const {
|
|
259
|
+
const { retryModel, attempt } = await this.handleError(error, attempts);
|
|
255
260
|
attempts.push(attempt);
|
|
256
|
-
|
|
261
|
+
if (retryModel.delay) await delay(retryModel.delay, { abortSignal: input.abortSignal });
|
|
262
|
+
this.currentModel = retryModel.model;
|
|
257
263
|
}
|
|
258
264
|
}
|
|
259
265
|
}
|
|
@@ -275,7 +281,7 @@ var RetryableLanguageModel = class {
|
|
|
275
281
|
attempts: updatedAttempts
|
|
276
282
|
};
|
|
277
283
|
return {
|
|
278
|
-
|
|
284
|
+
retryModel: await findRetryModel(this.options.retries, context),
|
|
279
285
|
attempt: resultAttempt
|
|
280
286
|
};
|
|
281
287
|
}
|
|
@@ -297,17 +303,17 @@ var RetryableLanguageModel = class {
|
|
|
297
303
|
attempts: updatedAttempts
|
|
298
304
|
};
|
|
299
305
|
this.options.onError?.(context);
|
|
300
|
-
const
|
|
306
|
+
const retryModel = await findRetryModel(this.options.retries, context);
|
|
301
307
|
/**
|
|
302
308
|
* Handler didn't return any models to try next, rethrow the error.
|
|
303
309
|
* If we retried the request, wrap the error into a `RetryError` for better visibility.
|
|
304
310
|
*/
|
|
305
|
-
if (!
|
|
311
|
+
if (!retryModel) {
|
|
306
312
|
if (updatedAttempts.length > 1) throw prepareRetryError(error, updatedAttempts);
|
|
307
313
|
throw error;
|
|
308
314
|
}
|
|
309
315
|
return {
|
|
310
|
-
|
|
316
|
+
retryModel,
|
|
311
317
|
attempt: errorAttempt
|
|
312
318
|
};
|
|
313
319
|
}
|
|
@@ -316,7 +322,10 @@ var RetryableLanguageModel = class {
|
|
|
316
322
|
* Always start with the original model
|
|
317
323
|
*/
|
|
318
324
|
this.currentModel = this.baseModel;
|
|
319
|
-
const { result } = await this.withRetry({
|
|
325
|
+
const { result } = await this.withRetry({
|
|
326
|
+
fn: async () => await this.currentModel.doGenerate(options),
|
|
327
|
+
abortSignal: options.abortSignal
|
|
328
|
+
});
|
|
320
329
|
return result;
|
|
321
330
|
}
|
|
322
331
|
async doStream(options) {
|
|
@@ -327,7 +336,10 @@ var RetryableLanguageModel = class {
|
|
|
327
336
|
/**
|
|
328
337
|
* Perform the initial call to doStream with retry logic to handle errors before any data is streamed.
|
|
329
338
|
*/
|
|
330
|
-
let { result, attempts } = await this.withRetry({
|
|
339
|
+
let { result, attempts } = await this.withRetry({
|
|
340
|
+
fn: async () => await this.currentModel.doStream(options),
|
|
341
|
+
abortSignal: options.abortSignal
|
|
342
|
+
});
|
|
331
343
|
/**
|
|
332
344
|
* Wrap the original stream to handle retries if an error occurs during streaming.
|
|
333
345
|
*/
|
|
@@ -362,19 +374,21 @@ var RetryableLanguageModel = class {
|
|
|
362
374
|
* Check if the error from the stream can be retried.
|
|
363
375
|
* Otherwise it will rethrow the error.
|
|
364
376
|
*/
|
|
365
|
-
const {
|
|
366
|
-
this.currentModel = nextModel;
|
|
377
|
+
const { retryModel, attempt } = await this.handleError(error, attempts);
|
|
367
378
|
/**
|
|
368
379
|
* Save the attempt
|
|
369
380
|
*/
|
|
370
381
|
attempts.push(attempt);
|
|
382
|
+
if (retryModel.delay) await delay(retryModel.delay, { abortSignal: options.abortSignal });
|
|
383
|
+
this.currentModel = retryModel.model;
|
|
371
384
|
/**
|
|
372
385
|
* Retry the request by calling doStream again.
|
|
373
386
|
* This will create a new stream.
|
|
374
387
|
*/
|
|
375
388
|
const retriedResult = await this.withRetry({
|
|
376
389
|
fn: async () => await this.currentModel.doStream(options),
|
|
377
|
-
attempts
|
|
390
|
+
attempts,
|
|
391
|
+
abortSignal: options.abortSignal
|
|
378
392
|
});
|
|
379
393
|
/**
|
|
380
394
|
* Cancel the previous reader and stream if we are retrying
|
|
@@ -52,6 +52,7 @@ type RetryAttempt<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = RetryErr
|
|
|
52
52
|
type RetryModel<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = {
|
|
53
53
|
model: MODEL;
|
|
54
54
|
maxAttempts?: number;
|
|
55
|
+
delay?: number;
|
|
55
56
|
};
|
|
56
57
|
/**
|
|
57
58
|
* A function that determines whether to retry with a different model based on the current attempt and all previous attempts.
|
|
@@ -59,5 +60,8 @@ type RetryModel<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = {
|
|
|
59
60
|
type Retryable<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = (context: RetryContext<MODEL>) => RetryModel<MODEL> | Promise<RetryModel<MODEL>> | undefined;
|
|
60
61
|
type Retries<MODEL extends LanguageModelV2 | EmbeddingModelV2$1> = Array<Retryable<MODEL> | MODEL>;
|
|
61
62
|
type LanguageModelV2Generate = Awaited<ReturnType<LanguageModelV2['doGenerate']>>;
|
|
63
|
+
type LanguageModelV2Stream = Awaited<ReturnType<LanguageModelV2['doStream']>>;
|
|
64
|
+
type EmbeddingModelV2CallOptions<VALUE> = Parameters<EmbeddingModelV2$1<VALUE>['doEmbed']>[0];
|
|
65
|
+
type EmbeddingModelV2Embed<VALUE> = Awaited<ReturnType<EmbeddingModelV2$1<VALUE>['doEmbed']>>;
|
|
62
66
|
//#endregion
|
|
63
|
-
export { EmbeddingModelV2$1 as EmbeddingModelV2, type LanguageModelV2, RetryModel, Retryable, RetryableModelOptions };
|
|
67
|
+
export { EmbeddingModelV2$1 as EmbeddingModelV2, EmbeddingModelV2CallOptions, EmbeddingModelV2Embed, type LanguageModelV2, LanguageModelV2Generate, LanguageModelV2Stream, Retries, RetryAttempt, RetryContext, RetryErrorAttempt, RetryModel, RetryResultAttempt, Retryable, RetryableModelOptions };
|