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 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-JvcFQz93.mjs";
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-Vtk_GA9U.mjs";
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$1) => {
545
- return this.currentModel.doStream(retryCallOptions$1);
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-JvcFQz93.mjs";
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-Vtk_GA9U.mjs";
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.2",
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.9",
37
- "@ai-sdk/azure": "3.0.7",
38
- "@ai-sdk/gateway": "3.0.10",
39
- "@ai-sdk/groq": "3.0.4",
40
- "@ai-sdk/openai": "3.0.7",
41
- "@ai-sdk/test-server": "1.0.1",
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.10",
41
+ "@biomejs/biome": "^2.3.13",
44
42
  "@total-typescript/tsconfig": "^1.0.4",
45
- "@types/node": "^25.0.3",
46
- "ai": "6.0.20",
43
+ "@types/node": "^25.0.10",
44
+ "ai": "6.0.50",
47
45
  "husky": "^9.1.7",
48
- "msw": "^2.12.4",
46
+ "msw": "^2.12.7",
49
47
  "pkg-pr-new": "^0.0.62",
50
- "publint": "^0.3.16",
51
- "tsdown": "^0.18.2",
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.16",
55
- "zod": "^4.2.1"
52
+ "vitest": "^4.0.18"
56
53
  },
57
54
  "dependencies": {
58
- "@ai-sdk/provider": "3.0.2",
59
- "@ai-sdk/provider-utils": "4.0.4"
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