ai-retry 0.8.0 → 0.10.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 +53 -0
- package/dist/index.js +26 -0
- package/dist/retryables/index.d.ts +8 -1
- package/dist/retryables/index.js +21 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -251,12 +251,16 @@ Errors are tracked per unique model (provider + modelId). That means on the firs
|
|
|
251
251
|
|
|
252
252
|
There are several built-in dynamic retryables available for common use cases:
|
|
253
253
|
|
|
254
|
+
> [!TIP]
|
|
255
|
+
> You are missing a retryable for your use case? [Open an issue](https://github.com/zirkelc/ai-retry/issues/new) and let's discuss it!
|
|
256
|
+
|
|
254
257
|
- [`contentFilterTriggered`](./src/retryables/content-filter-triggered.ts): Content filter was triggered based on the prompt or completion.
|
|
255
258
|
- [`requestTimeout`](./src/retryables/request-timeout.ts): Request timeout occurred.
|
|
256
259
|
- [`requestNotRetryable`](./src/retryables/request-not-retryable.ts): Request failed with a non-retryable error.
|
|
257
260
|
- [`retryAfterDelay`](./src/retryables/retry-after-delay.ts): Retry with delay and exponential backoff and respect `retry-after` headers.
|
|
258
261
|
- [`serviceOverloaded`](./src/retryables/service-overloaded.ts): Response with status code 529 (service overloaded).
|
|
259
262
|
- Use this retryable to handle Anthropic's overloaded errors.
|
|
263
|
+
- [`serviceUnavailable`](./src/retryables/service-unavailable.ts): Response with status code 503 (service unavailable).
|
|
260
264
|
|
|
261
265
|
#### Content Filter
|
|
262
266
|
|
|
@@ -324,6 +328,21 @@ const retryableModel = createRetryable({
|
|
|
324
328
|
});
|
|
325
329
|
```
|
|
326
330
|
|
|
331
|
+
#### Service Unavailable
|
|
332
|
+
|
|
333
|
+
Handle service unavailable errors (status code 503) by switching to a different provider.
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { serviceUnavailable } from 'ai-retry/retryables';
|
|
337
|
+
|
|
338
|
+
const retryableModel = createRetryable({
|
|
339
|
+
model: azure('gpt-4'),
|
|
340
|
+
retries: [
|
|
341
|
+
serviceUnavailable(openai('gpt-4')), // Switch to OpenAI if Azure is unavailable
|
|
342
|
+
],
|
|
343
|
+
});
|
|
344
|
+
```
|
|
345
|
+
|
|
327
346
|
#### Request Not Retryable
|
|
328
347
|
|
|
329
348
|
Handle cases where the base model fails with a non-retryable error.
|
|
@@ -372,6 +391,32 @@ By default, if a [`retry-after-ms`](https://learn.microsoft.com/en-us/azure/ai-f
|
|
|
372
391
|
|
|
373
392
|
### Options
|
|
374
393
|
|
|
394
|
+
#### Disabling Retries
|
|
395
|
+
|
|
396
|
+
You can disable retries entirely, which is useful for testing or specific environments. When disabled, the base model will execute directly without any retry logic.
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
const retryableModel = createRetryable({
|
|
400
|
+
model: openai('gpt-4'), // Base model
|
|
401
|
+
retries: [/* ... */],
|
|
402
|
+
disabled: true, // Retries are completely disabled
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Or disable based on environment
|
|
406
|
+
const retryableModel = createRetryable({
|
|
407
|
+
model: openai('gpt-4'), // Base model
|
|
408
|
+
retries: [/* ... */],
|
|
409
|
+
disabled: process.env.NODE_ENV === 'test', // Disable in test environment
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// Or use a function for dynamic control
|
|
413
|
+
const retryableModel = createRetryable({
|
|
414
|
+
model: openai('gpt-4'), // Base model
|
|
415
|
+
retries: [/* ... */],
|
|
416
|
+
disabled: () => !featureFlags.isEnabled('ai-retries'), // Check feature flag
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
375
420
|
#### Retry Delays
|
|
376
421
|
|
|
377
422
|
You can delay retries with an optional exponential backoff. The delay respects abort signals, so requests can still be cancelled during the delay period.
|
|
@@ -529,11 +574,19 @@ Creates a retryable model that works with both language models and embedding mod
|
|
|
529
574
|
interface RetryableModelOptions<MODEL extends LanguageModelV2 | EmbeddingModelV2> {
|
|
530
575
|
model: MODEL;
|
|
531
576
|
retries: Array<Retryable<MODEL> | MODEL>;
|
|
577
|
+
disabled?: boolean | (() => boolean);
|
|
532
578
|
onError?: (context: RetryContext<MODEL>) => void;
|
|
533
579
|
onRetry?: (context: RetryContext<MODEL>) => void;
|
|
534
580
|
}
|
|
535
581
|
```
|
|
536
582
|
|
|
583
|
+
**Options:**
|
|
584
|
+
- `model`: The base model to use for the initial request.
|
|
585
|
+
- `retries`: Array of retryables (functions, models, or retry objects) to attempt on failure.
|
|
586
|
+
- `disabled`: Disable all retry logic. Can be a boolean or function returning boolean. Default: `false` (retries enabled).
|
|
587
|
+
- `onError`: Callback invoked when an error occurs.
|
|
588
|
+
- `onRetry`: Callback invoked before attempting a retry.
|
|
589
|
+
|
|
537
590
|
#### `Retryable`
|
|
538
591
|
|
|
539
592
|
A `Retryable` is a function that receives a `RetryContext` with the current error or result and model and all previous attempts.
|
package/dist/index.js
CHANGED
|
@@ -105,6 +105,13 @@ var RetryableEmbeddingModel = class {
|
|
|
105
105
|
this.currentModel = options.model;
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
108
|
+
* Check if retries are disabled
|
|
109
|
+
*/
|
|
110
|
+
isDisabled() {
|
|
111
|
+
if (this.options.disabled === void 0) return false;
|
|
112
|
+
return typeof this.options.disabled === "function" ? this.options.disabled() : this.options.disabled;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
108
115
|
* Execute a function with retry logic for handling errors
|
|
109
116
|
*/
|
|
110
117
|
async withRetry(input) {
|
|
@@ -197,6 +204,10 @@ var RetryableEmbeddingModel = class {
|
|
|
197
204
|
* Always start with the original model
|
|
198
205
|
*/
|
|
199
206
|
this.currentModel = this.baseModel;
|
|
207
|
+
/**
|
|
208
|
+
* If retries are disabled, bypass retry machinery entirely
|
|
209
|
+
*/
|
|
210
|
+
if (this.isDisabled()) return this.currentModel.doEmbed(options);
|
|
200
211
|
const { result } = await this.withRetry({
|
|
201
212
|
fn: async (currentRetry) => {
|
|
202
213
|
const callOptions = {
|
|
@@ -234,6 +245,13 @@ var RetryableLanguageModel = class {
|
|
|
234
245
|
this.currentModel = options.model;
|
|
235
246
|
}
|
|
236
247
|
/**
|
|
248
|
+
* Check if retries are disabled
|
|
249
|
+
*/
|
|
250
|
+
isDisabled() {
|
|
251
|
+
if (this.options.disabled === void 0) return false;
|
|
252
|
+
return typeof this.options.disabled === "function" ? this.options.disabled() : this.options.disabled;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
237
255
|
* Execute a function with retry logic for handling errors
|
|
238
256
|
*/
|
|
239
257
|
async withRetry(input) {
|
|
@@ -371,6 +389,10 @@ var RetryableLanguageModel = class {
|
|
|
371
389
|
* Always start with the original model
|
|
372
390
|
*/
|
|
373
391
|
this.currentModel = this.baseModel;
|
|
392
|
+
/**
|
|
393
|
+
* If retries are disabled, bypass retry machinery entirely
|
|
394
|
+
*/
|
|
395
|
+
if (this.isDisabled()) return this.currentModel.doGenerate(options);
|
|
374
396
|
const { result } = await this.withRetry({
|
|
375
397
|
fn: async (currentRetry) => {
|
|
376
398
|
const callOptions = {
|
|
@@ -390,6 +412,10 @@ var RetryableLanguageModel = class {
|
|
|
390
412
|
*/
|
|
391
413
|
this.currentModel = this.baseModel;
|
|
392
414
|
/**
|
|
415
|
+
* If retries are disabled, bypass retry machinery entirely
|
|
416
|
+
*/
|
|
417
|
+
if (this.isDisabled()) return this.currentModel.doStream(options);
|
|
418
|
+
/**
|
|
393
419
|
* Perform the initial call to doStream with retry logic to handle errors before any data is streamed.
|
|
394
420
|
*/
|
|
395
421
|
let { result, attempts } = await this.withRetry({
|
|
@@ -39,4 +39,11 @@ declare function retryAfterDelay<MODEL extends LanguageModelV2 | EmbeddingModelV
|
|
|
39
39
|
*/
|
|
40
40
|
declare function serviceOverloaded<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
41
41
|
//#endregion
|
|
42
|
-
|
|
42
|
+
//#region src/retryables/service-unavailable.d.ts
|
|
43
|
+
/**
|
|
44
|
+
* Fallback to a different model if the provider returns a service unavailable error.
|
|
45
|
+
* This retryable handles HTTP status code 503 (Service Unavailable).
|
|
46
|
+
*/
|
|
47
|
+
declare function serviceUnavailable<MODEL extends LanguageModelV2 | EmbeddingModelV2>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, serviceOverloaded, serviceUnavailable };
|
package/dist/retryables/index.js
CHANGED
|
@@ -152,4 +152,24 @@ function serviceOverloaded(model, options) {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
//#endregion
|
|
155
|
-
|
|
155
|
+
//#region src/retryables/service-unavailable.ts
|
|
156
|
+
/**
|
|
157
|
+
* Fallback to a different model if the provider returns a service unavailable error.
|
|
158
|
+
* This retryable handles HTTP status code 503 (Service Unavailable).
|
|
159
|
+
*/
|
|
160
|
+
function serviceUnavailable(model, options) {
|
|
161
|
+
return (context) => {
|
|
162
|
+
const { current } = context;
|
|
163
|
+
if (isErrorAttempt(current)) {
|
|
164
|
+
const { error } = current;
|
|
165
|
+
if (APICallError.isInstance(error) && error.statusCode === 503) return {
|
|
166
|
+
model,
|
|
167
|
+
maxAttempts: 1,
|
|
168
|
+
...options
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
//#endregion
|
|
175
|
+
export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, serviceOverloaded, serviceUnavailable };
|