ai icon indicating copy to clipboard operation
ai copied to clipboard

support derived/unknown zod schema in generateObject.schema

Open benjick opened this issue 1 year ago • 8 comments

Description

Hello!

I'm migrating from zod-gpt to use ai instead. It's working in every single case, except one where I do this:

  const functionCall = functionCalls[prompt.functionCall];
  const { object, usage } = await generateObject({
    model: openai(prompt.model),
    schema: functionCall,
    prompt: content,
  });

(reproduction below)

It gives me the following error:

No overload matches this call.
  The last overload gave the following error.
    Object literal may only specify known properties, and 'schema' does not exist in type 'Omit<CallSettings, "stopSequences"> & Prompt & { output: "no-schema"; model: LanguageModelV1; mode?: "json" | undefined; experimental_telemetry?: TelemetrySettings | undefined; experimental_providerMetadata?: LanguageModelV1ProviderMetadata | undefined; _internal?: { ...; } | undefined; }'.ts(2769)

What do I need to do to solve this?

Code example

import { createOpenAI } from "@ai-sdk/openai";
import { generateObject } from "ai";
import { z } from "zod";

const openai = createOpenAI({
  apiKey: process.env.OPENAI_API_KEY!,
  compatibility: "strict", // strict mode, enable when using the OpenAI API
});

export const dynamic = "force-dynamic";

type FunctionCall = "test1" | "test2";

const test1 = z.object({
  type: z.literal("test1").default("test1"),
  test: z.string().describe("test1"),
});

const test2 = z.object({
  type: z.literal("test2").default("test2"),
  test: z.string().describe("test2"),
});

const functionCalls = {
  test1,
  test2,
} satisfies Record<FunctionCall, z.ZodTypeAny>;

export const GET = async () => {
  const prompt: {
    functionCall: FunctionCall;
    model: string;
  } = {
    functionCall: "test1",
    model: "gpt-4o-mini",
  };
  const content = "test";

  const functionCall = functionCalls[prompt.functionCall];
  const { object, usage } = await generateObject({
    model: openai(prompt.model),
    schema: functionCall,
    prompt: content,
  });
  return Response.json({ object, usage });
};

Additional context

I've also tried as const Instead of satisfies and not having anything at all. Same issue

"ai": "3.4.9",

benjick avatar Oct 04 '24 15:10 benjick

Try updating your @ai-sdk/openai package - mine seemed to be stuck at 0.0.36 and this resolved it for me.

jnimmo avatar Oct 09 '24 07:10 jnimmo

Thanks for the comment, I'm on 0.0.66 though

benjick avatar Oct 10 '24 17:10 benjick

In the code snippet, the functionCall variable you are passing into generateObject appears to be a z.Object. The versions of the overloaded generateObject function whose argument has a schema property, requires that it be a schema type (e.g. z.Schema<ELEMENT, z.ZodTypeDef, any> | Schema<ELEMENT>). I think that's the reason your call doesn't match any of the overloads.

The error message comes from the attempt to match the one declared last in the library, which doesn't have a schema property on its argument at all.

jonriegel avatar Oct 11 '24 02:10 jonriegel

@jonriegel thanks for the comment. I looked into the definition files after your reply and I see it too. But I don't know how to translate it to code. Any suggestions?

benjick avatar Oct 11 '24 21:10 benjick

I ran into a similar issue caused by generateObject's ordering of overload definitions. Eventually found a workaround using the Overloads type from this SO answer. https://stackoverflow.com/a/64107995 Copy-pasted below:

type Overloads<T> =
  T extends {
    (...args: infer A1): infer R1; (...args: infer A2): infer R2;
    (...args: infer A3): infer R3; (...args: infer A4): infer R4
  } ? [
    (...args: A1) => R1, (...args: A2) => R2,
    (...args: A3) => R3, (...args: A4) => R4
  ] : T extends {
    (...args: infer A1): infer R1; (...args: infer A2): infer R2;
    (...args: infer A3): infer R3
  } ? [
    (...args: A1) => R1, (...args: A2) => R2,
    (...args: A3) => R3
  ] : T extends {
    (...args: infer A1): infer R1; (...args: infer A2): infer R2
  } ? [
    (...args: A1) => R1, (...args: A2) => R2
  ] : T extends {
    (...args: infer A1): infer R1
  } ? [
    (...args: A1) => R1
  ] : any

@benjick for your example, looks like the error can be removed by defining the argument for generateObject as an outside variable typed with Parameters and Overloads. Code:

  const functionCall = functionCalls[prompt.functionCall];
  const params: Parameters<Overloads<typeof generateObject>[number]>[0] = {
    model: openai(prompt.model),
    schema: functionCall,
    prompt: content,
  };
  const { object, usage } = await generateObject(params);

Now it matches the correct overload based on the properties of params. the 'schema' does not exist in type error only shows up again if you explicitly set output: 'no-schema', as we'd expect.

leothorp avatar Jan 04 '25 20:01 leothorp

The issue here is that generateObject (in with object output, the overload error is a red herring) expects a fixed schema for correct output inference, and you pass a schema with |:

CleanShot 2025-01-05 at 11 49 11

This fails to infer the type of the result. I'll think about how to pass through union types; ideally it should be possible.

lgrammel avatar Jan 05 '25 10:01 lgrammel

I'm having the exact same issue here with the overload warnings. Even though the code actually works fine

import type { SchemaName } from "@/types/schema-types";
import { recipeSchema } from "./recipeSchema";
import { articleSchema } from "./quizSchema";

export const schemas = {
  recipe: recipeSchema,
  article: articleSchema,
} as const;

and then I try to use it here

import { schemas } from "@/app/schemas/schemas";
import type { SchemaName } from "@/types/schema-types";
import { google } from "@ai-sdk/google";
import { generateObject, zodSchema } from "ai";

import { NextResponse } from "next/server";

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export async function POST(req: Request) {
  const request = await req.json();
  const schemaName = request.schemaName as SchemaName;

  const schema = schemas[schemaName];

  if (!schema) {
    return new NextResponse("Schema not found", { status: 404 });
  }

  if (!request.prompt) {
    return new NextResponse("Prompt is required", { status: 400 });
  }
  try {
    const { object } = await generateObject({
      model: google("gemini-2.0-flash-001"),
      prompt: request.prompt,
      schema: zodSchema(schema),
    });
    return new NextResponse(JSON.stringify(object));
  } catch (error) {
    console.log("error generated is", error);
    return new NextResponse("Error generated", { status: 500 });
  }
}

utlandingur avatar Mar 09 '25 23:03 utlandingur

Any plans to fix this on library end? I'm also having this and I really would like to avoid workarounds here :)

1ron avatar Apr 17 '25 04:04 1ron

👍

ronnyandre avatar Jun 13 '25 05:06 ronnyandre

I think generateObject is unusable with this right now...

garrettlove8 avatar Jun 14 '25 20:06 garrettlove8

Using the code verbatim with documentation also cause some of this type error.

https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-object#example-generate-an-array-using-a-schema

  Overload 1 of 3, '(options: Omit<CallSettings, "stopSequences"> & Prompt & { output?: "object" | undefined; model: LanguageModelV1; schema: ZodType<unknown, ZodTypeDef, any> | Schema<...>; ... 8 more ...; _internal?: { ...; } | undefined; }): StreamObjectResult<...>', gave the following error.
    Type 'ZodObject<{ recipe: ZodObject<{ name: ZodString; ingredients: ZodArray<ZodString>; steps: ZodArray<ZodString>; }, $strip>; }, $strip>' is not assignable to type 'ZodType<unknown, ZodTypeDef, any> | Schema<unknown>'.
      Type 'ZodObject<{ recipe: ZodObject<{ name: ZodString; ingredients: ZodArray<ZodString>; steps: ZodArray<ZodString>; }, $strip>; }, $strip>' is missing the following properties from type 'ZodType<unknown, ZodTypeDef, any>': _type, _parse, _getType, _getOrReturnCtx, and 7 more.
  Overload 2 of 3, '(options: Omit<CallSettings, "stopSequences"> & Prompt & { output: "array"; model: LanguageModelV1; schema: ZodType<unknown, ZodTypeDef, any> | Schema<...>; ... 8 more ...; _internal?: { ...; } | undefined; }): StreamObjectResult<...>', gave the following error.
    Type 'ZodObject<{ recipe: ZodObject<{ name: ZodString; ingredients: ZodArray<ZodString>; steps: ZodArray<ZodString>; }, $strip>; }, $strip>' is not assignable to type 'ZodType<unknown, ZodTypeDef, any> | Schema<unknown>'.
      Type 'ZodObject<{ recipe: ZodObject<{ name: ZodString; ingredients: ZodArray<ZodString>; steps: ZodArray<ZodString>; }, $strip>; }, $strip>' is missing the following properties from type 'ZodType<unknown, ZodTypeDef, any>': _type, _parse, _getType, _getOrReturnCtx, and 7 more.
  Overload 3 of 3, '(options: Omit<CallSettings, "stopSequences"> & Prompt & { output: "no-schema"; model: LanguageModelV1; mode?: "json" | undefined; experimental_telemetry?: TelemetrySettings | undefined; ... 4 more ...; _internal?: { ...; } | undefined; }): StreamObjectResult<...>', gave the following error.
    Object literal may only specify known properties, and 'schema' does not exist in type 'Omit<CallSettings, "stopSequences"> & Prompt & { output: "no-schema"; model: LanguageModelV1; mode?: "json" | undefined; experimental_telemetry?: TelemetrySettings | undefined; ... 4 more ...; _internal?: { ...; } | undefined; }'.

27   schema: z.object({

Abdillah avatar Jul 06 '25 14:07 Abdillah

Downgrading zod from v4 to v3 fixed the issue.

    "zod": "^3.25.28"

acro5piano avatar Jul 10 '25 03:07 acro5piano

seems to be an issue with zod v4

instead of downgrading to v3 for zod

✅ you could do

import { z } from "zod/v3";

❌ instead of

import { z } from "zod";

Just-Moh-it avatar Jul 14 '25 00:07 Just-Moh-it

i don;t w

seems to be an issue with zod v4

instead of downgrading to v3 for zod

✅ you could do

import { z } from "zod/v3"; ❌ instead of

import { z } from "zod";

This worked. Thanks

ajay9415 avatar Nov 01 '25 08:11 ajay9415