AI Engineering Tips
    Delivered to your inbox

    Structured Outputs with Vercel's AI SDK

    Matt PocockMatt Pocock

    Often the thing that you want to get back from your LLM is not text but some kind of object.

    You might want to scan a bank statement for multiple properties, like the account number and the balance.

    The most efficient way to do this is with structured outputs.

    This lets you ask the LLM a question, tell it what format you want the answer in, and then it will send you that information in that format.

    In this example, we're going to ask the LLM for a recipe. We want the recipe name, an array of ingredients, and an array of steps that the chef needs to take to make the recipe.

    {
    "recipe": {
    "name": "Chocolate Cake",
    "ingredients": [
    {
    "name": "flour",
    "amount": "2 cups"
    },
    {
    "name": "sugar",
    "amount": "2 cups"
    }
    // ...
    ],
    "steps": [
    "Preheat the oven to 350 degrees F.",
    "Mix the flour, sugar, cocoa powder, baking powder, baking soda, and salt in a large bowl."
    // ...
    ]
    }
    }

    Scrollycoding

    The first step is to create a zod schema that describes the data type that we want to get back from the LLM.

    If you've never seen zod before, I have a free tutorial on my sister site, Total TypeScript.

    To describe the recipe shape that we have here the zod schema would look like this:

    We can pass this schema directly to the generateObject function from the AI SDK.

    I've also added a simple system prompt here to give the AI some context as to what we're doing.

    The result that comes back contains a property called object which contains our recipe.

    Thanks to TypeScript being clever, we also get type-safe access to the name, ingredients, and steps.

    import { z } from "zod";
    const schema = z.object({
    recipe: z.object({
    name: z.string(),
    ingredients: z.array(
    z.object({
    name: z.string(),
    amount: z.string(),
    }),
    ),
    steps: z.array(z.string()),
    }),
    });

    Describing Properties

    But we're not quite done here. We should provide the AI with more information about what each individual property means.

    Currently, all it's got to go on are name, ingredients, and steps.

    We can do this by adding zod's describe function on each property.

    const schema = z.object({
    recipe: z.object({
    name: z
    .string()
    .describe("The title of the recipe"),
    ingredients: z
    .array(
    z.object({
    name: z.string(),
    amount: z.string(),
    }),
    )
    .describe(
    "The ingredients needed for the recipe",
    ),
    steps: z
    .array(z.string())
    .describe("The steps to make the recipe"),
    }),
    });

    Now it's clear to the AI what we're asking for from each property. This is especially useful when the property names are not that descriptive.

    And finally, we can pass a schemaName property to the generateObject function:

    const { object } = await generateObject({
    model,
    system:
    `You are helping a user create a recipe. ` +
    `Use British English variants of ingredient names, like Coriander over Cilantro.`,
    schemaName: "Recipe",
    schema,
    prompt,
    });

    Let's give this a go and see what outputs we get. Let's ask how to make Baba Ganoush.

    const recipe = await createRecipe(
    "How to make baba ganoush?",
    );
    console.dir(recipe, { depth: null });

    When we run this, we're going to get back a recipe for Baba Ganoush.

    {
    "name": "Baba Ganoush",
    "ingredients": [
    { "name": "Aubergine", "amount": "2 large" },
    { "name": "Tahini", "amount": "3 tablespoons" }
    // ...
    ],
    "steps": [
    "Preheat the oven to 200°C (400°F).",
    "Pierce the aubergines several times with a fork."
    // ...
    ]
    }

    And there we go, that's how you get structured outputs from the AI SDK.

    Share