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 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 an optional `maxAttempts` to limit how many times this model can be retried.
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-CHhEGL5x.js";
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.model;
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 { nextModel, attempt } = await this.handleError(error, attempts);
129
+ const { retryModel, attempt } = await this.handleError(error, attempts);
130
130
  attempts.push(attempt);
131
- this.currentModel = nextModel;
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 nextModel = await findRetryModel(this.options.retries, context);
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 (!nextModel) {
159
+ if (!retryModel) {
159
160
  if (updatedAttempts.length > 1) throw prepareRetryError(error, updatedAttempts);
160
161
  throw error;
161
162
  }
162
163
  return {
163
- nextModel,
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({ fn: async () => await this.currentModel.doEmbed(options) });
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 { nextModel, attempt } = await this.handleResult(result, attempts);
243
+ const { retryModel, attempt } = await this.handleResult(result, attempts);
240
244
  attempts.push(attempt);
241
- if (nextModel) {
242
- this.currentModel = nextModel;
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 { nextModel, attempt } = await this.handleError(error, attempts);
259
+ const { retryModel, attempt } = await this.handleError(error, attempts);
255
260
  attempts.push(attempt);
256
- this.currentModel = nextModel;
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
- nextModel: await findRetryModel(this.options.retries, context),
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 nextModel = await findRetryModel(this.options.retries, context);
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 (!nextModel) {
311
+ if (!retryModel) {
306
312
  if (updatedAttempts.length > 1) throw prepareRetryError(error, updatedAttempts);
307
313
  throw error;
308
314
  }
309
315
  return {
310
- nextModel,
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({ fn: async () => await this.currentModel.doGenerate(options) });
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({ fn: async () => await this.currentModel.doStream(options) });
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 { nextModel, attempt } = await this.handleError(error, attempts);
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
@@ -1,4 +1,4 @@
1
- import { EmbeddingModelV2, LanguageModelV2, RetryModel, Retryable } from "../types-CHhEGL5x.js";
1
+ import { EmbeddingModelV2, LanguageModelV2, RetryModel, Retryable } from "../types-BrJaHkFh.js";
2
2
 
3
3
  //#region src/retryables/content-filter-triggered.d.ts
4
4
 
@@ -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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-retry",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "AI SDK Retry",
5
5
  "packageManager": "pnpm@9.0.0",
6
6
  "main": "./dist/index.js",