ai-retry 1.0.2 → 1.1.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 +41 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +3 -3
- package/dist/retryables/index.d.mts +24 -3
- package/dist/retryables/index.mjs +50 -2
- package/package.json +17 -19
- /package/dist/{types-JvcFQz93.d.mts → types-Bty5BU37.d.mts} +0 -0
- /package/dist/{utils-Vtk_GA9U.mjs → utils-fYGELi1C.mjs} +0 -0
package/README.md
CHANGED
|
@@ -358,6 +358,7 @@ There are several built-in dynamic retryables available for common use cases:
|
|
|
358
358
|
- [`serviceOverloaded`](./src/retryables/service-overloaded.ts): Response with status code 529 (service overloaded).
|
|
359
359
|
- Use this retryable to handle Anthropic's overloaded errors.
|
|
360
360
|
- [`serviceUnavailable`](./src/retryables/service-unavailable.ts): Response with status code 503 (service unavailable).
|
|
361
|
+
- [`schemaMismatch`](./src/retryables/schema-mismatch.ts): Response JSON doesn't match the expected schema from structured output modes (`Output.object()`, `Output.array()`, `Output.choice()`).
|
|
361
362
|
|
|
362
363
|
#### Content Filter
|
|
363
364
|
|
|
@@ -498,6 +499,46 @@ const retryableModel = createRetryable({
|
|
|
498
499
|
|
|
499
500
|
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`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/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.
|
|
500
501
|
|
|
502
|
+
#### Schema Mismatch
|
|
503
|
+
|
|
504
|
+
Automatically retry with a different model when the response JSON doesn't match the expected schema.
|
|
505
|
+
|
|
506
|
+
This is a result-based retryable that validates the model's JSON output against the schema set by structured output modes like `Output.object()`, `Output.array()`, and `Output.choice()`.
|
|
507
|
+
Normally, schema validation happens outside the model in `generateText`, so a schema validation error would not be seen by the retryable model. This retryable catches it early and retries with a fallback model.
|
|
508
|
+
|
|
509
|
+
> [!NOTE]
|
|
510
|
+
> This retryable works with `generateText` and any structured output mode that provides a schema: `Output.object()`, `Output.array()`, and `Output.choice()`.
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
import { openai } from '@ai-sdk/openai';
|
|
514
|
+
import { anthropic } from '@ai-sdk/anthropic';
|
|
515
|
+
import { generateText, Output } from 'ai';
|
|
516
|
+
import { createRetryable } from 'ai-retry';
|
|
517
|
+
import { schemaMismatch } from 'ai-retry/retryables';
|
|
518
|
+
import { z } from 'zod';
|
|
519
|
+
|
|
520
|
+
const retryableModel = createRetryable({
|
|
521
|
+
model: openai('gpt-4-mini'), // Weak base model
|
|
522
|
+
retries: [
|
|
523
|
+
// Retry with stronger model on schema mismatch
|
|
524
|
+
schemaMismatch(openai('gpt-5')),
|
|
525
|
+
],
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
const result = await generateText({
|
|
529
|
+
model: retryableModel,
|
|
530
|
+
output: Output.object({
|
|
531
|
+
schema: z.object({
|
|
532
|
+
name: z.string(),
|
|
533
|
+
age: z.number(),
|
|
534
|
+
}),
|
|
535
|
+
}),
|
|
536
|
+
prompt: 'Generate a person with name and age.',
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
console.log(result.object); // { name: "Alice", age: 30 }
|
|
540
|
+
```
|
|
541
|
+
|
|
501
542
|
### Options
|
|
502
543
|
|
|
503
544
|
#### Disabling Retries
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as RetryableModelOptions, S as Retryable, _ as Retry, a as GatewayLanguageModelId, b as RetryErrorAttempt, c as LanguageModelGenerate, d as LanguageModelStreamPart, f as ProviderOptions, g as Retries, h as ResolvedModel, i as EmbeddingModelRetryCallOptions, l as LanguageModelRetryCallOptions, m as ResolvableModel, n as EmbeddingModelCallOptions, o as LanguageModel, p as ResolvableLanguageModel, r as EmbeddingModelEmbed, s as LanguageModelCallOptions, t as EmbeddingModel, u as LanguageModelStream, v as RetryAttempt, w as RetryableOptions, x as RetryResultAttempt, y as RetryContext } from "./types-
|
|
1
|
+
import { C as RetryableModelOptions, S as Retryable, _ as Retry, a as GatewayLanguageModelId, b as RetryErrorAttempt, c as LanguageModelGenerate, d as LanguageModelStreamPart, f as ProviderOptions, g as Retries, h as ResolvedModel, i as EmbeddingModelRetryCallOptions, l as LanguageModelRetryCallOptions, m as ResolvableModel, n as EmbeddingModelCallOptions, o as LanguageModel, p as ResolvableLanguageModel, r as EmbeddingModelEmbed, s as LanguageModelCallOptions, t as EmbeddingModel, u as LanguageModelStream, v as RetryAttempt, w as RetryableOptions, x as RetryResultAttempt, y as RetryContext } from "./types-Bty5BU37.mjs";
|
|
2
2
|
import * as _ai_sdk_provider0 from "@ai-sdk/provider";
|
|
3
3
|
|
|
4
4
|
//#region src/create-retryable-model.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as isLanguageModel, c as isResultAttempt, d as isString, f as isTimeoutError, i as isGenerateResult, l as isStreamContentPart, n as isEmbeddingModel, o as isModel, r as isErrorAttempt, s as isObject, t as isAbortError, u as isStreamResult } from "./utils-
|
|
1
|
+
import { a as isLanguageModel, c as isResultAttempt, d as isString, f as isTimeoutError, i as isGenerateResult, l as isStreamContentPart, n as isEmbeddingModel, o as isModel, r as isErrorAttempt, s as isObject, t as isAbortError, u as isStreamResult } from "./utils-fYGELi1C.mjs";
|
|
2
2
|
import { RetryError, gateway } from "ai";
|
|
3
3
|
import { delay } from "@ai-sdk/provider-utils";
|
|
4
4
|
import { getErrorMessage } from "@ai-sdk/provider";
|
|
@@ -541,8 +541,8 @@ var RetryableLanguageModel = class {
|
|
|
541
541
|
* This will create a new stream.
|
|
542
542
|
*/
|
|
543
543
|
const retriedResult = await this.withRetry({
|
|
544
|
-
fn: async (retryCallOptions
|
|
545
|
-
return this.currentModel.doStream(retryCallOptions
|
|
544
|
+
fn: async (retryCallOptions) => {
|
|
545
|
+
return this.currentModel.doStream(retryCallOptions);
|
|
546
546
|
},
|
|
547
547
|
callOptions,
|
|
548
548
|
attempts,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { S as Retryable, p as ResolvableLanguageModel, t as EmbeddingModel, w as RetryableOptions } from "../types-
|
|
1
|
+
import { S as Retryable, p as ResolvableLanguageModel, t as EmbeddingModel, w as RetryableOptions } from "../types-Bty5BU37.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/retryables/content-filter-triggered.d.ts
|
|
4
|
-
|
|
5
4
|
/**
|
|
6
5
|
* Fallback to a different model if the content filter was triggered.
|
|
7
6
|
*/
|
|
@@ -29,6 +28,28 @@ declare function requestTimeout<MODEL extends ResolvableLanguageModel | Embeddin
|
|
|
29
28
|
*/
|
|
30
29
|
declare function retryAfterDelay<MODEL extends ResolvableLanguageModel | EmbeddingModel>(options: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
31
30
|
//#endregion
|
|
31
|
+
//#region src/retryables/schema-mismatch.d.ts
|
|
32
|
+
/**
|
|
33
|
+
* Fallback to a different model if the response does not match the expected JSON schema.
|
|
34
|
+
*
|
|
35
|
+
* Validates the response text against the JSON schema from `responseFormat.schema`
|
|
36
|
+
* (set automatically when using `Output.object()`) and retries with a different model
|
|
37
|
+
* if validation fails.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const result = await generateText({
|
|
42
|
+
* model: createRetryable({
|
|
43
|
+
* model: primaryModel,
|
|
44
|
+
* retries: [schemaMismatch(fallbackModel)],
|
|
45
|
+
* }),
|
|
46
|
+
* output: Output.object({ schema: z.object({ name: z.string() }) }),
|
|
47
|
+
* prompt: `Generate a person`,
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
declare function schemaMismatch<MODEL extends ResolvableLanguageModel>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
52
|
+
//#endregion
|
|
32
53
|
//#region src/retryables/service-overloaded.d.ts
|
|
33
54
|
/**
|
|
34
55
|
* Fallback to a different model if the provider returns an overloaded error.
|
|
@@ -46,4 +67,4 @@ declare function serviceOverloaded<MODEL extends ResolvableLanguageModel | Embed
|
|
|
46
67
|
*/
|
|
47
68
|
declare function serviceUnavailable<MODEL extends ResolvableLanguageModel | EmbeddingModel>(model: MODEL, options?: RetryableOptions<MODEL>): Retryable<MODEL>;
|
|
48
69
|
//#endregion
|
|
49
|
-
export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, serviceOverloaded, serviceUnavailable };
|
|
70
|
+
export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, schemaMismatch, serviceOverloaded, serviceUnavailable };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { c as isResultAttempt, d as isString, f as isTimeoutError, r as isErrorAttempt, s as isObject } from "../utils-
|
|
1
|
+
import { c as isResultAttempt, d as isString, f as isTimeoutError, r as isErrorAttempt, s as isObject } from "../utils-fYGELi1C.mjs";
|
|
2
2
|
import { APICallError } from "ai";
|
|
3
|
+
import { safeParseJSON } from "@ai-sdk/provider-utils";
|
|
4
|
+
import { fromJSONSchema } from "zod";
|
|
3
5
|
|
|
4
6
|
//#region src/retryables/content-filter-triggered.ts
|
|
5
7
|
/**
|
|
@@ -120,6 +122,52 @@ function retryAfterDelay(options) {
|
|
|
120
122
|
};
|
|
121
123
|
}
|
|
122
124
|
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/retryables/schema-mismatch.ts
|
|
127
|
+
/**
|
|
128
|
+
* Fallback to a different model if the response does not match the expected JSON schema.
|
|
129
|
+
*
|
|
130
|
+
* Validates the response text against the JSON schema from `responseFormat.schema`
|
|
131
|
+
* (set automatically when using `Output.object()`) and retries with a different model
|
|
132
|
+
* if validation fails.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* const result = await generateText({
|
|
137
|
+
* model: createRetryable({
|
|
138
|
+
* model: primaryModel,
|
|
139
|
+
* retries: [schemaMismatch(fallbackModel)],
|
|
140
|
+
* }),
|
|
141
|
+
* output: Output.object({ schema: z.object({ name: z.string() }) }),
|
|
142
|
+
* prompt: `Generate a person`,
|
|
143
|
+
* });
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
function schemaMismatch(model, options) {
|
|
147
|
+
return async (context) => {
|
|
148
|
+
/**
|
|
149
|
+
* Only handle result attempts
|
|
150
|
+
*/
|
|
151
|
+
if (!isResultAttempt(context.current)) return void 0;
|
|
152
|
+
const { result, options: callOptions } = context.current;
|
|
153
|
+
/** Extract text from content */
|
|
154
|
+
const text = result.content.filter((part) => part.type === `text`).map((part) => part.text).join(``);
|
|
155
|
+
if (!text) return void 0;
|
|
156
|
+
/** Auto-detect schema from responseFormat (set by Output.object()) */
|
|
157
|
+
const responseFormat = callOptions.responseFormat;
|
|
158
|
+
if (responseFormat?.type !== `json` || !responseFormat.schema) return;
|
|
159
|
+
if ((await safeParseJSON({
|
|
160
|
+
text,
|
|
161
|
+
schema: fromJSONSchema(responseFormat.schema)
|
|
162
|
+
})).success) return void 0;
|
|
163
|
+
return {
|
|
164
|
+
model,
|
|
165
|
+
maxAttempts: 1,
|
|
166
|
+
...options
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
123
171
|
//#endregion
|
|
124
172
|
//#region src/retryables/service-overloaded.ts
|
|
125
173
|
/**
|
|
@@ -171,4 +219,4 @@ function serviceUnavailable(model, options) {
|
|
|
171
219
|
}
|
|
172
220
|
|
|
173
221
|
//#endregion
|
|
174
|
-
export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, serviceOverloaded, serviceUnavailable };
|
|
222
|
+
export { contentFilterTriggered, requestNotRetryable, requestTimeout, retryAfterDelay, schemaMismatch, serviceOverloaded, serviceUnavailable };
|
package/package.json
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-retry",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "AI SDK Retry",
|
|
5
|
-
"main": "./dist/index.mjs",
|
|
6
|
-
"module": "./dist/index.mjs",
|
|
7
5
|
"types": "./dist/index.d.mts",
|
|
8
6
|
"type": "module",
|
|
9
7
|
"files": [
|
|
@@ -33,30 +31,30 @@
|
|
|
33
31
|
"ai": "6.x"
|
|
34
32
|
},
|
|
35
33
|
"devDependencies": {
|
|
36
|
-
"@ai-sdk/anthropic": "3.0.
|
|
37
|
-
"@ai-sdk/azure": "3.0.
|
|
38
|
-
"@ai-sdk/gateway": "3.0.
|
|
39
|
-
"@ai-sdk/groq": "3.0.
|
|
40
|
-
"@ai-sdk/openai": "3.0.
|
|
41
|
-
"@ai-sdk/test-server": "1.0.
|
|
34
|
+
"@ai-sdk/anthropic": "3.0.23",
|
|
35
|
+
"@ai-sdk/azure": "3.0.19",
|
|
36
|
+
"@ai-sdk/gateway": "3.0.23",
|
|
37
|
+
"@ai-sdk/groq": "3.0.15",
|
|
38
|
+
"@ai-sdk/openai": "3.0.19",
|
|
39
|
+
"@ai-sdk/test-server": "1.0.3",
|
|
42
40
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
43
|
-
"@biomejs/biome": "^2.3.
|
|
41
|
+
"@biomejs/biome": "^2.3.13",
|
|
44
42
|
"@total-typescript/tsconfig": "^1.0.4",
|
|
45
|
-
"@types/node": "^25.0.
|
|
46
|
-
"ai": "6.0.
|
|
43
|
+
"@types/node": "^25.0.10",
|
|
44
|
+
"ai": "6.0.50",
|
|
47
45
|
"husky": "^9.1.7",
|
|
48
|
-
"msw": "^2.12.
|
|
46
|
+
"msw": "^2.12.7",
|
|
49
47
|
"pkg-pr-new": "^0.0.62",
|
|
50
|
-
"publint": "^0.3.
|
|
51
|
-
"tsdown": "^0.
|
|
48
|
+
"publint": "^0.3.17",
|
|
49
|
+
"tsdown": "^0.20.1",
|
|
52
50
|
"tsx": "^4.21.0",
|
|
53
51
|
"typescript": "^5.9.3",
|
|
54
|
-
"vitest": "^4.0.
|
|
55
|
-
"zod": "^4.2.1"
|
|
52
|
+
"vitest": "^4.0.18"
|
|
56
53
|
},
|
|
57
54
|
"dependencies": {
|
|
58
|
-
"@ai-sdk/provider": "3.0.
|
|
59
|
-
"@ai-sdk/provider-utils": "4.0.
|
|
55
|
+
"@ai-sdk/provider": "3.0.5",
|
|
56
|
+
"@ai-sdk/provider-utils": "4.0.9",
|
|
57
|
+
"zod": "^4.3.6"
|
|
60
58
|
},
|
|
61
59
|
"scripts": {
|
|
62
60
|
"build": "tsdown",
|
|
File without changes
|
|
File without changes
|