Vercel AI SDKAI SDK 7 · TypeScript · Node.js 20+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
17 small wins to finish your pathNext lesson

Typed output with Output.object and Zod

Output.object with a Zod schema asks the model for JSON matching the schema, parses it, validates it, and gives you a typed object.

Exampleticket.ts
import { z } from "zod";

export const Ticket = z.object({
  category: z.enum(["billing", "shipping", "account", "other"]),
  priority: z.number().int().min(1).max(5).describe("1 is low, 5 is urgent"),
});
Exampletriage.ts
import { generateText, Output } from "ai";
import { shopModel } from "./shop-model.ts";
import { Ticket } from "./ticket.ts";

const { output } = await generateText({
  model: shopModel,
  output: Output.object({ schema: Ticket }),
  prompt: "I was charged twice for one order",
});

console.log(output);
console.log(output.priority + 1);
console.log(JSON.stringify(shopModel.doGenerateCalls[0].responseFormat.schema.properties.priority));
Example
npx tsx triage.ts

output: Output.object({ schema: Ticket }) converts the Zod schema to JSON Schema and passes it to the model as responseFormat, where providers that support structured output enforce it. The last line shows what the model received for priority: the type, the limits and the describe text. output is typed from the schema, so output.priority + 1 type-checks.

When the answer does not fit

Exampleno-object.ts
import { generateText, Output, NoObjectGeneratedError } from "ai";
import { z } from "zod";
import { shopModel } from "./shop-model.ts";

const Reply = z.object({ category: z.string(), reply: z.string() });

try {
  await generateText({ model: shopModel, output: Output.object({ schema: Reply }), prompt: "I was charged twice" });
} catch (error) {
  console.log(NoObjectGeneratedError.isInstance(error));
  console.log(error.text);
  console.log(error.cause.message.split("\n")[0]);
}
Example
npx tsx no-object.ts

This schema requires reply, which the stand-in does not write. Validation failed, and the SDK threw NoObjectGeneratedError with the model's raw text and the validation error as cause. Catch it where your app can fall back, such as sending the ticket to a person.

OutputGives you
Output.object({ schema })One object matching a schema
Output.array({ element })A list of objects
Output.choice({ options })One of a list of strings, for classification
Output.text()Text, the default
Try it yourself
  • Make priority optional and remove it from the stand-in's answer.
  • Use Output.choice({ options: ["billing", "shipping"] }).
  • Stream the object with streamText and partialOutputStream.

You understood something today that you didn't yesterday.