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.
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"),
});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));npx tsx triage.tsoutput: 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
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]);
}npx tsx no-object.tsThis 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.
| Output | Gives 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 |
- Make
priorityoptional and remove it from the stand-in's answer. - Use
Output.choice({ options: ["billing", "shipping"] }). - Stream the object with
streamTextandpartialOutputStream.
You understood something today that you didn't yesterday.