-
Notifications
You must be signed in to change notification settings - Fork 2.3k
feat: support for zod v4 schema validation #6421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v5
Are you sure you want to change the base?
Conversation
SCHEMA extends z.Schema | Schema = z.Schema<JSONValue>, | ||
? Array< | ||
// @ts-expect-error - TODO: Type instantiation is excessively deep and possibly infinite | ||
z3.infer<SCHEMA> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make sure SCHEMA
is not an instance of zod 4
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
Hey - Super keen to start using this so I've been poking around, figure I'll offer my 2 cents You appear to have two causes for the recursive type error, the first ai/packages/ai/core/tool/mcp/types.ts Line 20 in bca63f9
Is because when I think this is as simple as switching both tool schema records to use unknown? I'm not so sure about the second one:
I think it's because you are defining Regardless, I have a fix. By pulling out some of the heavy lifting into helper types: type InferSchema<S> =
S extends z3.Schema ? z3.infer<S> :
S extends z4.$ZodType ? z4.infer<S> :
S extends Schema<infer T> ? T :
never;
type InferFor<S, O extends 'object' | 'array'> =
O extends 'array' ? Array<InferSchema<S>> : InferSchema<S>; You can write the signature like: ...
export async function generateObject<
SCHEMA extends z3.Schema | z4.$ZodType | Schema = z4.$ZodType<JSONValue>,
Output extends 'object' | 'array' | 'enum' | 'no-schema' =
InferSchema<SCHEMA> extends string ? 'enum' : 'object',
RESULT = Output extends 'array' ? InferFor<SCHEMA, 'array'> : InferFor<SCHEMA, 'object'>
>(
options: Omit<CallSettings, 'stopSequences'> &
... So that the generics are in order Hopefully this is both clear & helpful, apologies that the explanation of the second issue is a bit fuzzy |
This comment was marked as resolved.
This comment was marked as resolved.
: never, | ||
SCHEMA extends z.Schema | Schema = z.Schema<JSONValue>, | ||
Output extends | ||
SCHEMA extends z3.Schema | z4.$ZodType | Schema = z4.$ZodType<JSONValue>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some reviews. It's extremely thoughtful to try to ensure compatibility with Zod 3.24.x, to put it nicely. Based on a quick scan, the current implementation will not work well for 3.24.x - I explain more in my reviews. You're going to have a much easier time if you bump your Zod peer dep to 3.25. The timing with Arguably, a minor version bump in a peer dependency does not qualify as a breaking change at all since (assuming the peer dependency library didn't break anything between minors) it requires no code changes on the part of your users. Note also I plan to publish PS Gregor - feel free to DM me about this stuff. I'm not on mastodon but I am on X/Bsky 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot @colinhacks for reviewing the PR, much appreciated!
I've been playing around with the alpha a bit and noticed that providers (tried it with google gen ai) may reject Google expects So either the generated JSON schema has to be post-processed or zod's JSONschema generation has to be adapted. Relevant issues in zod: Here's a test that illustrates error: import type { JSONSchema7 } from "ai@alpha";
import { jsonSchema } from "@ai-sdk/provider-utils@alpha";
import { generateObject } from "ai@alpha";
import { z } from "zod/v4";
import { providers } from "~/alpha/providers";
describe("zod v4 with literals & enum -> generateObject", () => {
it("should work without patch", async () => {
const schema = z.strictObject({
food: z.literal(["pizza", "burger", "pasta"]),
animal: z.enum(["cats", "dogs"]),
});
try {
const JSONschema = z.toJSONSchema(schema, {
io: "output",
target: "draft-7",
}) as JSONSchema7;
const manual = jsonSchema(JSONschema, {
validate(value) {
const result = schema.safeParse(value);
return result.success
? { success: true, value: result.data }
: { success: false, error: result.error };
},
});
// throws AI_APICallError
const generated = await generateObject({
schema: manual,
model: providers.languageModel("google:gemini-1.5-flash"),
system: "Generate an object based on the provided json schema",
prompt: "Pick your favorite options among the available ones",
});
console.log(generated.object);
} catch (e) {
if (e instanceof Error) {
console.log(`[API ERROR ${e.name}]\n${e.message}`);
}
}
try {
const JSONschema = z.toJSONSchema(schema, {
io: "output",
target: "draft-7",
}) as JSONSchema7;
// @ts-expect-error necessary patch
JSONschema.properties.food.type = "string";
// @ts-expect-error necessary patch
JSONschema.properties.animal.type = "string";
const manual = jsonSchema(JSONschema, {
validate(value) {
const result = schema.safeParse(value);
return result.success
? { success: true, value: result.data }
: { success: false, error: result.error };
},
});
// doesn't throw
const generated = await generateObject({
schema: manual,
model: providers.languageModel("google:gemini-1.5-flash"),
system: "Generate an object based on the provided json schema",
prompt: "Pick your favorite options among the available ones",
});
console.log(generated.object);
} catch (e) {
console.error(e);
throw e;
}
});
}); Here's the output of the above test (I've intercepted the api calls in
|
target: 'draft-7', | ||
io: 'output', | ||
reused: useReferences ? 'ref' : 'inline', | ||
}) as JSONSchema7; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To post process the generated zod schema something like this would be needed
}) as JSONSchema7; | |
override({ zodSchema, jsonSchema }) { | |
if (zodSchema instanceof z.ZodLiteral) { | |
jsonSchema.type = "string"; | |
} | |
if (zodSchema instanceof z.ZodEnum) { | |
jsonSchema.type = "string"; | |
} | |
}) as JSONSchema7; |
Thanks for the detailed comment! Looks like the fix for
For literals, I'll look into implementing it using an override if the change does land in zod |
| z4.$ZodType<OBJECT, any> | ||
| z3.Schema<OBJECT, z3.ZodTypeDef, any> | ||
| Schema<OBJECT> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wondering if it makes sense to extract this into a dedicated type e.g. extended schema
Background
zod 4 has now been released: https://zod.dev/v4
We want to support both zod 3 and zod 4 schemas for validation.
Summary
zod
to latest (3.25.32
as of May 28)generateObject()
,streamObject()
,generateText()
,experimental_useObject()
from@ai-sdk/react
, andstreamUI()
from@ai-sdk/rsc
to accept both zod v3 and zod v4 (mini) schemasVerification
generateObject()
:examples/ai-core/src/generate-object/openai.ts
streamObject()
:examples/ai-core/src/stream-object/openai.ts
generateText()
:examples/ai-core/src/generate-text/openai-output-object.ts
experimental_useObject()
:examples/next-openai/app/use-obj/page.tsx
streamUI()
:examples/next-openai/app/stream-ui/actions.tsx
, update: commentTasks
Tests have been added / updated (for bug fixes / features)
Documentation has been added / updated (for bug fixes / features)
All the docs use
import { z } from 'zod';
which is zod v3. Should we update it tozod/v4
to align with the recommendation on https://zod.dev/?A patch changeset for relevant packages has been added (for bug fixes / features - run
pnpm changeset
in the project root)Formatting issues have been fixed (run
pnpm prettier-fix
in the project root)Future Work
When using a zod 4 schema in
examples/next-openai/app/api/use-object/schema.ts
, thenuseObject({ api: '/api/use-object', schema: notificationSchema })
inexamples/next-openai/app/use-object/page.tsx
sets the type of{ object }
toany
, and I couldn't figure out why that is. For now, I explicitly used a zod 3 schema fornotificationSchema
.packages/svelte/src/structured-object.svelte.ts
packages/svelte/src/tests/structured-object-synchronization.svelte
Upgrade all internal uses of
zod
to z4 (~102 files), try usingz4-mini
.Related Issues
#5682