ai-retry 1.6.1 → 1.7.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 +110 -69
- package/dist/create-retryable-model-HLobeUXU.mjs +1026 -0
- package/dist/error-CPbAtI-h.d.mts +95 -0
- package/dist/error-_63RHJTp.mjs +247 -0
- package/dist/experimental/embedding-model/index.d.mts +8 -0
- package/dist/experimental/embedding-model/index.mjs +19 -0
- package/dist/experimental/embedding-model/retryables/index.d.mts +20 -0
- package/dist/experimental/embedding-model/retryables/index.mjs +7 -0
- package/dist/experimental/image-model/index.d.mts +8 -0
- package/dist/experimental/image-model/index.mjs +19 -0
- package/dist/experimental/image-model/retryables/index.d.mts +4 -0
- package/dist/experimental/image-model/retryables/index.mjs +4 -0
- package/dist/experimental/language-model/index.d.mts +11 -0
- package/dist/experimental/language-model/index.mjs +19 -0
- package/dist/experimental/language-model/retryables/index.d.mts +4 -0
- package/dist/experimental/language-model/retryables/index.mjs +4 -0
- package/dist/{utils-CfnsSGrw.mjs → guards-D8UJtxDK.mjs} +2 -8
- package/dist/index-DfqLMa2f.d.mts +60 -0
- package/dist/index-DmNbfl6t.d.mts +30 -0
- package/dist/index.d.mts +4 -55
- package/dist/index.mjs +3 -913
- package/dist/{parse-retry-headers-DIPVbwW5.mjs → parse-retry-headers-CRxgluhe.mjs} +1 -1
- package/dist/retryables/index.d.mts +1 -1
- package/dist/retryables/index.mjs +2 -2
- package/dist/retryables-D0wMy6Qt.mjs +25 -0
- package/dist/retryables-nm5-elvB.mjs +76 -0
- package/dist/{types-CRKV-hdW.d.mts → types-DYMm5YMu.d.mts} +8 -4
- package/package.json +7 -2
- package/dist/retryables/experimental/index.d.mts +0 -248
- package/dist/retryables/experimental/index.mjs +0 -310
package/README.md
CHANGED
|
@@ -249,7 +249,7 @@ const retryableModel = createRetryable({
|
|
|
249
249
|
});
|
|
250
250
|
```
|
|
251
251
|
|
|
252
|
-
Result-based retryables
|
|
252
|
+
Result-based retryables apply to language models for both generate (`generateText`, `generateObject`) and streaming (`streamText`, `streamObject`) calls. For streams, the retry decision happens when the upstream `finish` part arrives and only fires if no content has been emitted yet, so behavior like `finishReason: 'content-filter'` on an otherwise empty response can still trigger a fallback. Once any content chunk has been forwarded, the stream is committed and result-based retries are skipped.
|
|
253
253
|
|
|
254
254
|
#### Fallbacks
|
|
255
255
|
|
|
@@ -389,8 +389,8 @@ There are several built-in dynamic retryables available for common use cases:
|
|
|
389
389
|
|
|
390
390
|
Automatically switch to a different model when content filtering blocks your request.
|
|
391
391
|
|
|
392
|
-
> [!
|
|
393
|
-
>
|
|
392
|
+
> [!NOTE]
|
|
393
|
+
> For streaming requests this retryable can only fire if the content filter trips before any content has been emitted. Once a text chunk flows through, the stream is committed and the fallback is skipped.
|
|
394
394
|
|
|
395
395
|
```typescript
|
|
396
396
|
import { contentFilterTriggered } from 'ai-retry/retryables';
|
|
@@ -587,24 +587,47 @@ console.log(result.object); // { name: "Alice", age: 30 }
|
|
|
587
587
|
### Experimental: Composable Conditions
|
|
588
588
|
|
|
589
589
|
> [!WARNING]
|
|
590
|
-
> This API is experimental and may change. It is not exported from the package root; opt in via the deep
|
|
590
|
+
> This API is experimental and may change. It is not exported from the package root; opt in via one of the per-model deep imports:
|
|
591
|
+
>
|
|
592
|
+
> ```ts
|
|
593
|
+
> import { ... } from 'ai-retry/experimental/language-model';
|
|
594
|
+
> import { ... } from 'ai-retry/experimental/image-model';
|
|
595
|
+
> import { ... } from 'ai-retry/experimental/embedding-model';
|
|
596
|
+
> ```
|
|
597
|
+
>
|
|
598
|
+
> Each entry point also re-exports `createRetryable` already typed for that model family, so you can either import everything from one path:
|
|
591
599
|
>
|
|
592
600
|
> ```ts
|
|
593
|
-
> import {
|
|
601
|
+
> import {
|
|
602
|
+
> createRetryable,
|
|
603
|
+
> error,
|
|
604
|
+
> httpStatus,
|
|
605
|
+
> } from 'ai-retry/experimental/language-model';
|
|
606
|
+
> ```
|
|
607
|
+
>
|
|
608
|
+
> or pull retryables from the dedicated `/retryables` subpath:
|
|
609
|
+
>
|
|
610
|
+
> ```ts
|
|
611
|
+
> import {
|
|
612
|
+
> error,
|
|
613
|
+
> httpStatus,
|
|
614
|
+
> } from 'ai-retry/experimental/language-model/retryables';
|
|
615
|
+
> // or
|
|
616
|
+
> import * as retryables from 'ai-retry/experimental/language-model/retryables';
|
|
594
617
|
> ```
|
|
595
618
|
|
|
596
|
-
A `condition().action()` API for retryables. Conditions are built from small primitives (`error(fn)`, `result(fn)`), composed with
|
|
619
|
+
A `condition().action()` API for retryables. Conditions are built from small primitives (`error(fn)`, `result(fn)`), composed with `.and` / `.or` / `.not`, and turned into a `Retryable` by one of two terminal actions: `.switch({ model })` or `.retry({ delay })`. The result drops into the same `retries: [...]` array as the stable helpers, so you can mix the two styles freely.
|
|
597
620
|
|
|
598
621
|
```typescript
|
|
599
622
|
import { anthropic } from '@ai-sdk/anthropic';
|
|
600
623
|
import { openai } from '@ai-sdk/openai';
|
|
601
624
|
import { generateText } from 'ai';
|
|
602
|
-
import { createRetryable } from 'ai-retry';
|
|
603
625
|
import {
|
|
626
|
+
createRetryable,
|
|
604
627
|
error,
|
|
605
628
|
finishReason,
|
|
606
629
|
httpStatus,
|
|
607
|
-
} from 'ai-retry/
|
|
630
|
+
} from 'ai-retry/experimental/language-model';
|
|
608
631
|
|
|
609
632
|
const retryableModel = createRetryable({
|
|
610
633
|
model: openai('gpt-4'),
|
|
@@ -623,87 +646,105 @@ const retryableModel = createRetryable({
|
|
|
623
646
|
});
|
|
624
647
|
```
|
|
625
648
|
|
|
626
|
-
####
|
|
649
|
+
#### Picking an entry point
|
|
627
650
|
|
|
628
|
-
|
|
651
|
+
Pick the entry point that matches the model you pass to `createRetryable`. Each module exposes the helpers that make sense for that model family already typed for it, so you don't need to add type annotations yourself.
|
|
629
652
|
|
|
630
|
-
|
|
631
|
-
| ------------------------------ | -------------------------------------------------------------------------------------------------- |
|
|
632
|
-
| `httpStatus(...patterns)` | Numbers match the status code; strings match the message (substring); regex matches either |
|
|
633
|
-
| `timeout()` | `Error.name === 'TimeoutError'` (`AbortSignal.timeout()` fired) |
|
|
634
|
-
| `aborted()` | `Error.name === 'AbortError'` (manual `controller.abort()`) |
|
|
635
|
-
| `noImage()` | The image model threw `NoImageGeneratedError` |
|
|
636
|
-
| `finishReason(...reasons)` | The result's `finishReason.unified` matches one of the given values |
|
|
637
|
-
| `schemaInvalid()` | The result text fails JSON-schema validation against the call's `responseFormat` |
|
|
653
|
+
#### Low-level conditions
|
|
638
654
|
|
|
639
|
-
|
|
655
|
+
The primitive builders `error(...)` and `result(...)` take a predicate and turn it into a condition; their namespaces bundle the most common field matchers on top.
|
|
640
656
|
|
|
641
|
-
|
|
657
|
+
| Helper | Matches when | Available in |
|
|
658
|
+
| --------------------------------- | ------------------------------------------------------------------------------------ | ---------------------- |
|
|
659
|
+
| `error(predicate)` | The current attempt failed and `predicate(err, ctx)` returns true | all three entry points |
|
|
660
|
+
| `error.isRetryable(flag)` | `APICallError.isRetryable === flag` (default `true`) | all three entry points |
|
|
661
|
+
| `error.statusCode(...patterns)` | Numbers match exactly; regex matches the stringified code (e.g. `/^5\d\d$/` for 5xx) | all three entry points |
|
|
662
|
+
| `error.message(...patterns)` | Substring (case-insensitive) or regex match against the error message | all three entry points |
|
|
663
|
+
| `result(predicate)` | The current attempt succeeded and `predicate(res, ctx)` returns true | `language-model` only |
|
|
664
|
+
| `result.finishReason(...reasons)` | The result's `finishReason.unified` matches one of the given values | `language-model` only |
|
|
642
665
|
|
|
643
|
-
|
|
644
|
-
|
|
666
|
+
```typescript
|
|
667
|
+
import { APICallError } from 'ai';
|
|
668
|
+
import { error } from 'ai-retry/experimental/language-model';
|
|
645
669
|
|
|
646
|
-
|
|
670
|
+
error((e) => APICallError.isInstance(e) && e.statusCode === 418).switch({
|
|
671
|
+
model: fallback,
|
|
672
|
+
});
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
#### High-level conditions
|
|
676
|
+
|
|
677
|
+
Convenience matchers built on top of the low-level ones for the common cases. Each returns a condition that you finalize with `.switch(...)` or `.retry(...)`.
|
|
678
|
+
|
|
679
|
+
| Helper | language-model | image-model | embedding-model |
|
|
680
|
+
| -------------------------- | :------------: | :---------: | :-------------: |
|
|
681
|
+
| `httpStatus(...patterns)` | ✓ | ✓ | ✓ |
|
|
682
|
+
| `timeout()` | ✓ | ✓ | ✓ |
|
|
683
|
+
| `aborted()` | ✓ | ✓ | ✓ |
|
|
684
|
+
| `finishReason(...reasons)` | ✓ | — | — |
|
|
685
|
+
| `schemaInvalid()` | ✓ | — | — |
|
|
686
|
+
| `noImage()` | — | ✓ | — |
|
|
647
687
|
|
|
648
|
-
|
|
688
|
+
What each one matches:
|
|
689
|
+
|
|
690
|
+
| Helper | Matches when |
|
|
691
|
+
| -------------------------- | ------------------------------------------------------------------------------------------ |
|
|
692
|
+
| `httpStatus(...patterns)` | Numbers match the status code; strings match the message (substring); regex matches either |
|
|
693
|
+
| `timeout()` | `Error.name === 'TimeoutError'` (`AbortSignal.timeout()` fired) |
|
|
694
|
+
| `aborted()` | `Error.name === 'AbortError'` (manual `controller.abort()`) |
|
|
695
|
+
| `finishReason(...reasons)` | The result's `finishReason.unified` matches one of the given values |
|
|
696
|
+
| `schemaInvalid()` | The result text fails JSON-schema validation against the call's `responseFormat` |
|
|
697
|
+
| `noImage()` | The image model threw `NoImageGeneratedError` |
|
|
698
|
+
|
|
699
|
+
Each high-level helper is a thin wrapper around the low-level ones. For example, `timeout()` is roughly:
|
|
649
700
|
|
|
650
701
|
```typescript
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
not,
|
|
656
|
-
or,
|
|
657
|
-
} from 'ai-retry/retryables/experimental';
|
|
702
|
+
function timeout() {
|
|
703
|
+
return error((err) => err instanceof Error && err.name === 'TimeoutError');
|
|
704
|
+
}
|
|
705
|
+
```
|
|
658
706
|
|
|
659
|
-
|
|
660
|
-
and(httpStatus(503), error.message('temporary'));
|
|
661
|
-
not(error.isRetryable(true));
|
|
707
|
+
and `finishReason(...)` just delegates to `result.finishReason(...)`:
|
|
662
708
|
|
|
663
|
-
|
|
664
|
-
|
|
709
|
+
```typescript
|
|
710
|
+
function finishReason(...reasons: Array<string>) {
|
|
711
|
+
return result.finishReason(...reasons);
|
|
712
|
+
}
|
|
665
713
|
```
|
|
666
714
|
|
|
667
|
-
####
|
|
715
|
+
#### Actions
|
|
668
716
|
|
|
669
|
-
|
|
717
|
+
Every condition exposes two terminal actions that turn it into a `Retryable`:
|
|
670
718
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
| `error(predicate)` | The current attempt failed and `predicate(err, ctx)` returns true |
|
|
674
|
-
| `result(predicate)`| The current attempt succeeded and `predicate(res, ctx)` returns true (language models only) |
|
|
719
|
+
- **`.switch({ model, ...options })`** falls back to a different model when the condition matches. Optional fields (`maxAttempts`, `delay`, `backoffFactor`, `timeout`, `options`) are the same as on a normal `Retry` object. `maxAttempts` defaults to `1`.
|
|
720
|
+
- **`.retry({ delay?, backoffFactor?, maxAttempts?, ... })`** retries the current model when the condition matches. Honors `Retry-After` and `Retry-After-Ms` response headers when present, capped at 60 seconds. `maxAttempts` defaults to `2` (one original attempt + one retry); values below `2` throw, since the retry budget is consumed by the original failure.
|
|
675
721
|
|
|
676
|
-
|
|
677
|
-
import { APICallError } from 'ai';
|
|
678
|
-
import { error } from 'ai-retry/retryables/experimental';
|
|
722
|
+
#### Combinators
|
|
679
723
|
|
|
680
|
-
|
|
681
|
-
(e) => APICallError.isInstance(e) && e.statusCode === 418,
|
|
682
|
-
).switch({ model: fallback });
|
|
683
|
-
```
|
|
724
|
+
Compose conditions with `.and`, `.or`, `.not`:
|
|
684
725
|
|
|
685
|
-
|
|
726
|
+
```typescript
|
|
727
|
+
import { error, httpStatus } from 'ai-retry/experimental/language-model';
|
|
686
728
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
| `error.message(...patterns)` | Substring (case-insensitive) or regex match against the error message |
|
|
729
|
+
httpStatus(429).or(error.message('overloaded'));
|
|
730
|
+
httpStatus(503).and(error.message('temporary'));
|
|
731
|
+
error.isRetryable(true).not();
|
|
732
|
+
```
|
|
692
733
|
|
|
693
734
|
#### Mapping from Built-in retryables
|
|
694
735
|
|
|
695
|
-
Each stable retryable has an equivalent in the new shape:
|
|
736
|
+
Each stable retryable has an equivalent in the new shape (imports from `ai-retry/experimental/language-model` unless noted):
|
|
696
737
|
|
|
697
|
-
| Built-in
|
|
698
|
-
|
|
|
699
|
-
| `contentFilterTriggered(m)`
|
|
700
|
-
| `requestTimeout(m)`
|
|
701
|
-
| `requestNotRetryable(m)`
|
|
702
|
-
| `schemaMismatch(m)`
|
|
703
|
-
| `serviceOverloaded(m)`
|
|
704
|
-
| `serviceUnavailable(m)`
|
|
705
|
-
| `noImageGenerated(m)`
|
|
706
|
-
| `retryAfterDelay({ delay, backoffFactor })`
|
|
738
|
+
| Built-in | Composable form |
|
|
739
|
+
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
740
|
+
| `contentFilterTriggered(m)` | `error(/* check e.data.error.code === 'content_filter' */).or(finishReason('content-filter')).switch({ model: m })` |
|
|
741
|
+
| `requestTimeout(m)` | `timeout().switch({ model: m, timeout: 60_000 })` |
|
|
742
|
+
| `requestNotRetryable(m)` | `error.isRetryable(false).switch({ model: m })` |
|
|
743
|
+
| `schemaMismatch(m)` | `schemaInvalid().switch({ model: m })` |
|
|
744
|
+
| `serviceOverloaded(m)` | `httpStatus(529, 'overloaded').switch({ model: m })` |
|
|
745
|
+
| `serviceUnavailable(m)` | `error.statusCode(503).switch({ model: m })` |
|
|
746
|
+
| `noImageGenerated(m)` | `noImage().switch({ model: m })` (from `image-model`) |
|
|
747
|
+
| `retryAfterDelay({ delay, backoffFactor })` | `error.isRetryable(true).retry({ delay, backoffFactor })` |
|
|
707
748
|
|
|
708
749
|
> [!NOTE]
|
|
709
750
|
> `error.isRetryable(true)` matches whatever the AI SDK's `APICallError` marks retryable. By default that's status codes 408, 409, 429, and any 5xx, plus network errors and provider-specific overrides (e.g. Anthropic flips it on `error.type === 'overloaded_error'`). It picks up more cases than a manual status-code list.
|
|
@@ -1169,7 +1210,7 @@ interface SuccessAttempt {
|
|
|
1169
1210
|
type: 'success';
|
|
1170
1211
|
model: LanguageModelV3 | EmbeddingModelV3 | ImageModelV3;
|
|
1171
1212
|
result:
|
|
1172
|
-
|
|
|
1213
|
+
| LanguageModelResult
|
|
1173
1214
|
| LanguageModelStream
|
|
1174
1215
|
| EmbeddingModelEmbed
|
|
1175
1216
|
| ImageModelGenerate;
|
|
@@ -1198,12 +1239,12 @@ type RetryAttempt =
|
|
|
1198
1239
|
}
|
|
1199
1240
|
| {
|
|
1200
1241
|
type: 'result';
|
|
1201
|
-
result:
|
|
1242
|
+
result: LanguageModelResult;
|
|
1202
1243
|
model: LanguageModelV3;
|
|
1203
1244
|
options: LanguageModelV3CallOptions;
|
|
1204
1245
|
};
|
|
1205
1246
|
|
|
1206
|
-
// Note: Result-based retries only apply to language models
|
|
1247
|
+
// Note: Result-based retries only apply to language models (both generate and stream paths). They do not apply to embedding or image models. For streaming, retries are only possible before any content has been emitted; once a text-delta flows through, the stream is committed.
|
|
1207
1248
|
|
|
1208
1249
|
// Type guards for discriminating attempts
|
|
1209
1250
|
function isErrorAttempt(attempt: RetryAttempt): attempt is RetryErrorAttempt;
|