AI SDK 7 Output.object returns a typed object or throws

1 month ago

One `generateText` call handed back this profile, a name and an age. The editor already knew the age was a number before the script ever ran. This is AI SDK 7, Vercel's TypeScript toolkit for calling AI models from an app. `generateText` is its call that runs a model and returns the finished result. The option on that call, Output dot object, asks the model for an object in a shape you describe. Then the AI SDK checks the reply against that shape, before your code gets the object.

Ask about this presentation

Chapters

  1. 0:00A typed profile from one call
  2. 0:32Install three packages
  3. 1:21One file with one call
  4. 1:55The Output reference page
  5. 2:22One schema, two jobs
  6. 3:11Five runs, five profiles
  7. 3:37A rule the model never sees
  8. 4:22NoOutputGeneratedError
  9. 5:09The output needs a step
  10. 5:50Streamed partials skip the check
  11. 6:34The full profile.ts
Show transcript
  1. One `generateText` call handed back this profile, a name and an age. The editor already knew the age was a number before the script ever ran. This is AI SDK 7, Vercel's TypeScript toolkit for calling AI models from an app. `generateText` is its call that runs a model and returns the finished result. The option on that call, Output dot object, asks the model for an object in a shape you describe. Then the AI SDK checks the reply against that shape, before your code gets the object.

    Slide text
    AI SDK 7 hands back a typed profile from one generateText call
    terminal · bun profile.ts
    $ bun profile.ts   # 5 runs, qwen2.5:3b on local Ollama
    --- run 1
    {
      name: "Evelyn Chen",
      age: 28,
    }
    real 0.89
    Local run · AI SDK 7 · Qwen 2.5 on Ollama
  2. This was run on AI SDK 7. One Bun command installs three packages. The `ai` package is the AI SDK itself. The second, the AI SDK's OpenAI provider, turns AI SDK calls into requests a model server understands. The third is Zod, a TypeScript library that describes the shape of data and checks real data against that shape. The model is Qwen 2.5, a small open model, running on this machine in Ollama, a local app that serves open models in OpenAI's API format. Older tutorials teach `generateObject` for this job. AI SDK 6 deprecated `generateObject` in favor of this output setting, and AI SDK 7 still ships it marked deprecated.

    Slide text
    01
    Install
    01 Install adds three packages
    ai
    @ai-sdk/openai
    zod
    terminal · bun add
    $ bun add ai @ai-sdk/openai zod
    lines omitted
    12 packages installed [640.00ms]
    Model Qwen 2.5
    Server Ollama on localhost
    Local bun output · AI SDK 6 migration guide
  3. The whole program is one file, profile dot T S. At the top, `createOpenAI` builds a provider whose base URL points at Ollama on localhost. Below that sits one `generateText` call with three settings, the model, the output and the prompt. The output setting is Output dot object, wrapped around a Zod object with two fields. The name field is a string, and the age field is a number. Each field also carries a dot describe call, a short hint for the model in plain English. The prompt says, generate a user profile. The last line prints result dot output.

    Slide text
    02
    First run
    02 First run is one file with one call
    createOpenAIbase URL is Ollama on localhost
    generateTextmodel, output, prompt
    console.logprints result.output
    Runnable source: profile.ts
  4. The official Output reference page opens with this same example, a name and an age. The page says Output works with `generateText` and `streamText`, and handles validation automatically. Output dot object takes one required setting, the schema. The schema can be a Zod schema or a JSON Schema, a JSON format for describing the shape of data. Two optional settings, a name and a description, give some providers extra guidance for the model.

    Slide text
    03
    Shape
    03 The docs attach Output to generateText and streamText
    AI SDK docs · Output reference
  5. The one Zod schema does two jobs. First, the AI SDK turns the schema into JSON Schema and sends it with the request. This is the request body the script sent to Ollama. The schema sits in a field called response format. The two describe hints ride along as each field's description, full name of the user, and age in years. So the model gets the shape and the hints before it writes a word. Second, the reply comes back as text. The AI SDK parses that text and checks it against the same Zod schema. Only a reply that passes becomes result dot output. TypeScript reads the output's type from that same Zod object. The hover says name is a string and age is a number because the schema says so. The check and the type come from one object, so a change to the schema changes both.

    Slide text
    03
    Shape
    One schema goes out with the request and checks the reply
    request body · POST /v1/chat/completions
      "response_format": {
        "type": "json_schema",
        "json_schema": {
          "schema": {
            "$schema": "http://json-schema.org/draft-07/schema#",
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "description": "Full name of the user"
              },
              "age": {
                "type": "number",
                "description": "Age in years"
              }
            },
            "required": [
              "name",
              "age"
            ],
            "additionalProperties": false
          },
          "strict": true,
          "name": "response"
    Sent
    the schema, in the request body
    Checked
    the finished reply, before result.output exists
    Local request capture · AI SDK 7 · Ollama
  6. The script ran five times in a row. All five runs printed a profile that matched the schema. The names were Evelyn Chen, Alice, Evelyn Brown, Alice again, and Evelyn. The ages ran from twenty-eight to thirty-five. Each run took under a second on this small local model. Every profile arrived as a real object, with age as a number. The script got there with zero calls to JSON dot parse and zero hand-written types.

    Slide text
    04
    Output
    04 Five local runs returned five valid profiles
    terminal · bun profile.ts
    $ bun profile.ts   # 5 runs, qwen2.5:3b on local Ollama
    --- run 1
    {
      name: "Evelyn Chen",
      age: 28,
    }
    real 0.89
    --- run 2
    {
      name: "Alice",
      age: 28,
    }
    real 0.81
    --- run 3
    {
      name: "Evelyn Brown",
      age: 35,
    }
    real 0.85
    terminal · continued
    --- run 4
    {
      name: "Alice",
      age: 35,
    }
    real 0.79
    --- run 5
    {
      name: "Evelyn",
      age: 29,
    }
    real 0.83
    0.79 to 0.89 seconds per run
    Local run · Qwen 2.5 on Ollama
  7. Zod's refine method adds a custom check to the schema, written as a function. This check says the profile must be a retiree, sixty-five or older. A function can't travel inside a JSON Schema. The request with the rule matches the request without it, byte for byte, so the model never sees the rule. Across three runs, the model wrote ages of twenty-seven, thirty and thirty-two. The AI SDK's check caught all three. Each time, `generateText` threw a No Object Generated Error. The error carries the raw text the model wrote, the response details, the token usage, and a cause. The cause says type validation failed, profile must be a retiree. A reply that breaks your schema fails at the call, where your code can catch and log it.

    Slide text
    04
    Output
    A refine rule the model never sees still rejects the reply
    terminal · bun profile-refine.ts · run 1
    NoObjectGeneratedError
    Cause: Type validation failed: Value: {"name":"Alice","age":27}.
    Error message: [
      {
        "code": "custom",
        "path": [],
        "message": "Profile must be a retiree"
      }
    ]
    Text: { "name": "Alice", "age": 27 }
    Local run · AI SDK docs · Generating Structured Data
  8. This is the part people get wrong. The docs name a second error, No Output Generated Error. It fires when you read result dot output from a run that ended without an output. One example is a run whose last step stopped on a tool call. A tool call is the model asking your app to run a named function. The output property is a getter, a property that runs code when you read it. So even destructuring output from the result throws. Issue eleven three forty-eight hit this error when a gateway dropped the finish reason, the model's label for why it stopped. The AI SDK threw with valid JSON sitting in the reply text. That issue closed in August, and the fix ships in AI SDK 7. Read result dot output inside the same try block as the call.

    Slide text
    05
    Gotchas
    05 Reading output throws when the run ended without one
    Closed · fixed in AI SDK 7
    AI SDK docs · vercel/ai issue #11348
  9. This is the part people get wrong once tools join the call. `generateText` runs as a loop of steps. Each model call or tool run is one step. The docs say the structured output counts as a step too. The stop when setting decides when the loop ends, and its default is one step. So with a tool and no stop when setting, the run can end right after the tool call, with no output to read. The docs example allows five steps. Issue thirteen oh seven five is open. The reporter's agent had two tools and a six-step limit. The agent kept calling tools until the limit, and reading output threw. Give stop when enough steps for every tool call, plus one more for the output.

    Slide text
    05
    Gotchas
    Gotcha: with tools, the structured output needs a step of its own
    stopWhen default · isStepCount(1)
    Open
    AI SDK docs · generateText reference · vercel/ai issue #13075
  10. This is the part people get wrong when streaming. `streamText` with the same output setting gives you a partial output stream. Each partial is a half-built object, where any field may still be missing. The docs say those partials can't be validated, because incomplete data may not fit the schema yet. This run used the retiree rule. The partials went from an empty object, to an empty name, to Alice, to Alice aged two, then Alice aged twenty-eight. Then awaiting the finished output threw No Object Generated Error. Issue two oh nine six two found a different gap. The partial stream dropped null and empty-string values, and the finished output kept them. That issue closed as fixed in September. Save only the awaited output.

    Slide text
    05
    Gotchas
    Gotcha: streamed partials skip the check, so act on the awaited output
    terminal · bun stream-partials.ts
    $ bun stream-partials.ts
    partial {}
    partial {"name":""}
    partial {"name":"Alice"}
    partial {"name":"Alice","age":2}
    partial {"name":"Alice","age":28}
    output  threw AI_NoObjectGeneratedError
    Closed · fixed in AI SDK 7
    Local run · AI SDK docs · vercel/ai issue #20962
  11. The provider points at Ollama on localhost. One `generateText` call carries Output dot object, with the Zod profile schema inside it. The last line prints result dot output, typed as a name string and an age number.

    Slide text
    The full profile.ts runs on AI SDK 7 with Ollama
    Runnable source: profile.ts

What happened in AI today, in your inbox

One short email each evening. Read it and you’re caught up.

Built with ❤️ in New York