ai icon indicating copy to clipboard operation
ai copied to clipboard

Zod v4: `z.describe()` not resulting in `"description"` JSON Schema key

Open johnboxall opened this issue 1 month ago • 9 comments

Description

I use JSON schema annotations to tell providers how to call my tools:

import { generateText, tool, stepCountIs, jsonSchema } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

const { text } = await generateText({
  model: anthropic("claude-haiku-4-5-20251001"),
  system:
    "You are an open source project assistant that identifies whether a bug is related to an existing issue. Always pass a description of the issue to `searchIssues` to find related issues.",
  prompt:
    "Is there a bug related to the serialization of Zod schema to JSON schema not including annotations?",
  stopWhen: stepCountIs(5),
  tools: {
    searchIssues: tool({
      description: "Search for issues",
      inputSchema: jsonSchema({
        type: "object",
        properties: {
          query: {
            type: "string",
            // THE MAGIC! ✨✨✨
            description: "Description of the issue. IN FRENCH!",
            maxLength: 100,
          },
        },
        required: ["query"],
        additionalProperties: false,
      }),
      execute: async ({ query }) => {
        console.log({ query });
        return [];
      },
    }),
  },
});

console.log(text);

Above, I tell Anthropic to call my tool in French.

Anthropic respects annotation present in tools' input_schema.

Running the script produces tool calls:

{ query: 'sérialisation schéma zod JSON schema annotations perdues' }
{ query: 'zod JSON schema annotations supprimées' }
{ query: 'zod schéma JSON métadonnées perdues' }

But, usually I provide my tool inputSchema using Zod rather than JSON schema:

import { generateText, tool, stepCountIs } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";

const { text } = await generateText({
  model: anthropic("claude-haiku-4-5-20251001"),
  system:
    "You are an open source project assistant that identifies whether a bug is related to an existing issue. Always pass a description of the issue to `searchIssues` to find related issues.",
  prompt:
    "Is there a bug related to the serialization of Zod schema to JSON schema not including annotations?",
  stopWhen: stepCountIs(5),
  tools: {
    searchIssues: tool({
      description: "Search for issues",
      // ‼️ Now a Zod schema rather than JSON schema
      inputSchema: z.object({
        query: z
          .string()
          .describe("Description of the issue you are looking for. IN FRENCH!")
          .max(100),
      }),
      execute: async ({ query }) => {
        console.log({ query });
        return [];
      },
    }),
  },
});

console.log(text);

I expect my Zod metadata to be converted to a JSON schema anntoation, but I can see it is not.

My annotation is lost in the actual call to Anthropic (eg. it does not appear in the input_schema param) and that it is not respected:

{ query: 'Zod schema serialization JSON schema annotations' }
{ query: 'Zod to JSON schema missing annotations' }
{ query: 'Zod JSON schema annotations not included' }

I'd love to see Zod metadata converted to JSON schema annotations!!!

❤️ ❤️ ❤️

AI SDK Version

  • ai: 5.0.87
  • @ai-sdk/anthropic: 2.0.41
  • zod: 4.1.12

Code of Conduct

  • [x] I agree to follow this project's Code of Conduct

johnboxall avatar Nov 04 '25 07:11 johnboxall

can you try with zod v3? this may be specific to zod 4

lgrammel avatar Nov 04 '25 17:11 lgrammel

@lgrammel – yes, it appears to be zod@4 specific issue.

Likely due to the change in how schema "metadata" is stored:

https://zod.dev/v4#metadata

The above example with zod@3 correctly includes the description correctly in the tools part of the API call.

🙏

johnboxall avatar Nov 04 '25 17:11 johnboxall

It seems that the order matters for zod

      inputSchema: z.object({
        query: z
          .string()
          .describe("Description of the issue you are looking for. IN FRENCH!")
          .max(100),
      }),

Can you try to move .describe() to the end?

      inputSchema: z.object({
        query: z
          .string()
          .max(100)
          .describe("Description of the issue you are looking for. IN FRENCH!"),
      }),

That worked for me. I'd say that is unexpected and might be a legit zod bug, it would be great if you could file an issue in the zod repository. Please feel free to ping me in there, too

gr2m avatar Nov 04 '25 19:11 gr2m

Hi, I'm facing the same issue and was able to resolve it by calling zodSchema manually.

import { zodSchema } from '@ai-sdk/provider-utils';
[...]
      inputSchema: zodSchema(z.object({
        query: z
          .string()
          .describe("Description of the issue you are looking for. IN FRENCH!")
          .max(100),
      })),

KilianU avatar Nov 06 '25 13:11 KilianU

@KilianU what zod version are you using? It will work with zod 3, the problem is with zod 4

gr2m avatar Nov 06 '25 15:11 gr2m

@gr2m I'm using Zod version 4.1.12

KilianU avatar Nov 11 '25 12:11 KilianU

Hi, I also noticed this behavior, I opened an issue to zod: https://github.com/colinhacks/zod/issues/5447

IdoBouskila avatar Nov 14 '25 11:11 IdoBouskila

thanks @IdoBouskila! It seems to be by design: https://github.com/colinhacks/zod/issues/5447

We should update our docs to let users of ai SDK know

gr2m avatar Nov 14 '25 16:11 gr2m

Im experiencing this in an expo driven app aswell. Swapping in chaining does not seem to resolve my "missing" description.

Checked the .ToJSONSchema() and zod schema logging, they were their.

Im using this: https://ai-sdk.dev/docs/announcing-ai-sdk-6-beta

The output structured change

Hooolm avatar Nov 14 '25 17:11 Hooolm

np @gr2m, where do you think is the right place to clarify that? Zod schema appears in many places in the docs, so maybe thats the right place: https://ai-sdk.dev/docs/reference/ai-sdk-core/zod-schema#zodschema

WDYT?

IdoBouskila avatar Nov 15 '25 16:11 IdoBouskila

ai-sdk.dev/docs/reference/ai-sdk-core/zod-schema#zodschema

WDYT?

yes I think that's a great place to add a note about making sure that .meta() / .describe() is at the end of the schema chain.

gr2m avatar Nov 15 '25 22:11 gr2m

Hi @gr2m, added (https://github.com/vercel/ai/pull/10452)

IdoBouskila avatar Nov 21 '25 14:11 IdoBouskila