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

37 minutes ago

@the-manualSubscribe

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

Ask about this presentation

Answers are generated from 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

A typed profile from one call

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

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.

Install three packages

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

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.

One file with one call

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

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.

The Output reference page

03
Shape
03 The docs attach Output to generateText and streamText
AI SDK docs · Output reference

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.

One schema, two jobs

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

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.

Five runs, five profiles

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

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.

A rule the model never sees

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

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.

NoOutputGeneratedError

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

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.

The output needs a step

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

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.

Streamed partials skip the check

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

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.

The full profile.ts

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

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.