前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
AI 面试
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
AI 热点
开发者导航
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
AI 面试
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
AI 热点
开发者导航
返回 AI 情报前线
All News · 全部资讯3415
  • AI 原生团队的瓶颈正在后移
  • 别迷信提示词,先写清任务简报
  • 谷歌开源气旋预测模型与权重
  • Agent 评测通过仍可能线上失效
  • AI 生成界面为何总漏掉异常状态
  • 用 AI SDK 稳定生成类型化 JSON
  • TensorRT-LLM 双显卡部署实战
  • 谷歌用 Gemma 打造树莓派离线翻译器
  • 微软开源多语言单测生成智能体
  • mcp2skill 统一管理并按需加载 MCP
  • KVM 客户机逃逸漏洞获修复
  • 用三路检索增强代码库问答
  • 从工作流而非代码评估编程 Agent
  • Neon开源4B智能体检索模型
  • AI 工作流不必按单动作拆技能
  • 备用模型不应自动继承工具权限
  • 用 LangGraph 构建可扩展 Agent 与 RAG
  • 避免 AI 加速制造技术债
  • 为 Claude Code 钩子增加信任校验
  • 十万条轨迹揭示 Agent 可观测性难点
  • 用分层流水线自动适配多平台发布
  • 长周期 Agent 更需要状态管理
  • DeepSeek V4 Flash 六项降本策略
  • AutoGPT:开源智能体工作流平台
  • 用视觉模型统一解析混合文档
  • Claude Code Skills 何时比提示词划算
  • Meta 发布 Muse Code 编程代理
  • 用事件队列构建可靠的智能体
  • 用交叉编码器提升 RAG 首位命中率
  • 先拆解问题再回答的认知验证提示法
  • 如何防止多智能体读取过期状态
  • 用代码图谱压缩 AI 审查上下文
  • 可组合的工程级 Agent 技能集
  • 为语音 AI 构建可控运行模式
  • 用结构化输出构建可迁移审核器
  • 四个实验验证 AI 防护栏效果
  • 已加载 36 / 3415
8.0
热点
AI SCORE
编程提效2026-08-07 14:01

用 AI SDK 稳定生成类型化 JSON

dev.to · AI#Vercel AI SDK#结构化输出#Zod
Editor brief · 编辑速览

文章演示通过 Vercel AI SDK 的 generateObject 与 Zod 获得经过模式校验的类型化对象,并用 streamObject 渐进更新界面。还区分对象、数组、枚举等输出模式,并建议显式处理生成失败异常。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

导语:Vercel AI SDK 的 generateObject 是从语言模型中获取类型明确、经过 schema 验证的 JSON 的可靠方式:我传入一个 Zod schema,SDK 会约束模型并验证结果,而我得到的是一个类型明确的对象,不必再手动解析那种“大多数时候是 JSON”的字符串。对我来说,真正发挥关键作用的有四点——用于一次性提取的 generateObject、用于渐进式 UI 的 streamObject、object/array/enum/无 schema 这几种输出模式,以及将 NoObjectGeneratedError 视为一条需要认真处理的一等代码路径。

generateObject({ model, schema, prompt }) 会返回一个经过 Zod(或 JSON)schema 验证的类型化对象;如果结果与 schema 不匹配,它会直接抛出异常,而不是把错误数据交给你。

streamObject 通过 partialObjectStream 流式输出部分对象,因此在模型完成生成之前,表单或表格就可以逐字段填充。

AI SDK 提供四种输出模式:object(默认)、array(通过 elementStream 流式输出元素)、enum(单标签分类),以及无 schema(自由格式 JSON)。

使用 generateObject 提取数据,使用 tool calling 执行操作;experimental_output 可以将 tool-calling 循环与最终的类型化对象结合起来。

当模型返回无效 JSON 时,SDK 会抛出 NoObjectGeneratedError,其中包含原始文本和用量信息;experimental_repairText 配合更明确的 schema 描述,可以修复其中的大多数问题。

generateObject 实际上做了什么?

generateObject 是 Vercel AI SDK 提供的一个函数,它会强制语言模型返回符合我所定义 schema 的 JSON,并在我的代码看到响应之前完成验证。我传入一个模型、一个 Zod schema 和一个 prompt;得到的则是一个类型明确的对象,其结构已经被 TypeScript 所知。

import { generateObject } from 'ai';
import { z } from 'zod';

const { object } = await generateObject({
  model: 'anthropic/claude-sonnet-5',
  schema: z.object({
    title: z.string(),
    tags: z.array(z.string()).max(5),
    sentiment: z.enum(['positive', 'neutral', 'negative']),
  }),
  prompt: `Summarise this support ticket: ${ticket}`,
});

object.sentiment; // typed as 'positive' | 'neutral' | 'negative'

schema 同时承担两项职责:引导模型生成正确的结构,以及验证输出。如果模型返回的某个字段无法解析,generateObject 会抛出异常,而不是返回一份半有效的数据。

为什么不直接解析 generateText 返回的 JSON?

因为对 generateText 返回的字符串调用 JSON.parse,恰恰就是 generateObject 想要消除的故障模式。原始 completion 是一个字符串,它在大多数时候都是格式正确的 JSON——直到模型用 Markdown 围栏包裹它、添加了一条尾随注释,或者漏掉某个必填字段,解析操作便会在线上环境中抛出异常。generateObject 会把 schema 注入请求,利用各家 provider 的 structured output 或 tool 机制约束生成过程,并在返回结果前使用你的 Zod schema 对其进行验证。

它的优势不在于少写几个字符的代码,而在于模型输出与类型化值之间的边界只有一个负责人——schema——而不是分散在 prompt、正则表达式和 try/catch 之中。

什么时候应该使用 streamObject,而不是 generateObject?

当对象大到等待完整结果会让人感觉迟缓时,就该使用 streamObject。streamObject 会返回一个 partialObjectStream,在对象逐步构建的过程中不断产出结果,因此 UI 可以在字段到达的那一刻就将其渲染出来。

import { streamObject } from 'ai';

const { partialObjectStream } = streamObject({
  model: 'anthropic/claude-sonnet-5',
  schema: reportSchema,
  prompt,
});

for await (const partial of partialObjectStream) {
  render(partial); // partial is a deep-partial of Report
}

每次产出的值都是该 schema 的 deep-partial,因此在模型填充某个字段之前,它都可能是 undefined。对于一次性的服务端任务——例如 cron job,或只返回一次结果的 route handler——generateObject 更简单,也一直是我的默认选择。

AI SDK 支持哪些输出模式?

对于 enum,我会传入 output: 'enum' 和一个 enum: ['spam', 'not_spam'] 列表;模型只能返回其中某个完全一致的字符串。相比仅包含一个 enum 字段的对象,这种方式约束更严格,成本也更低。

什么时候应该使用 tool calling,而不是 generateObject?

使用 generateObject 提取一个值,使用 tool calling 执行某项操作。generateObject 没有副作用:它把非结构化输入转换为一个类型化对象,然后停止。Tool calling——即为 generateText 或 streamText 提供一个 tools map——则允许模型决定调用函数,并且可能在循环中调用多次,然后再给出答案。

import { generateText, Output } from 'ai';

const { experimental_output } = await generateText({
  model: 'anthropic/claude-sonnet-5',
  tools: { searchOrders },
  experimental_output: Output.object({ schema: answerSchema }),
  prompt,
});

experimental_output——目前仍被标记为 experimental——正是连接二者的桥梁:模型先运行 tool-calling 循环,然后返回一个经过 schema 验证的最终答案,因此我可以通过一次调用同时获得操作过程和类型化结果。

如何处理返回无效 JSON 的模型?

当生成结果未通过 schema 验证时,AI SDK 会抛出 NoObjectGeneratedError。显式捕获它,决定了程序能够优雅降级,还是直接返回 500。该错误包含模型生成的原始文本,以及用量和响应信息。

import { generateObject, NoObjectGeneratedError } from 'ai';

try {
  const { object } = await generateObject({ model, schema, prompt });
  return object;
} catch (err) {
  if (NoObjectGeneratedError.isInstance(err)) {
    logger.warn({ text: err.text, usage: err.usage }, 'invalid object from model');
    return fallback;
  }
  throw err;
}

在代码运行到 catch 块之前,有三件事显著降低了我的失败率。第一,为每个含义并不直观的字段添加 .describe()——描述会被发送给模型,因此使用 z.string().describe('ISO 8601 date') 要胜过寄希望于模型自己猜对。第二,对于模型可能不知道的字段,使用 .nullable() 而不是 .optional(),因为很多模型能够比“省略一个 key”更可靠地输出 null。第三,使用 experimental_repairText:这是一个 callback,可以在 SDK 重新解析之前移除代码围栏或尾随逗号。只有在这三种措施都失败后,我才会重试,因为重试会让延迟和成本翻倍。

问:generateObject 和 generateText 有什么区别?答:generateText 返回自由格式的字符串;generateObject 返回一个经过 schema 验证的类型化对象,如果输出不匹配则会抛出异常。生成自然语言文本时使用 generateText;只要需要机器可读的数据,就使用 generateObject。

问:generateObject 适用于所有模型吗?答:它适用于 AI SDK 所支持的任何 provider,但具体实现机制有所不同——有些在底层使用原生 structured-output JSON 模式,另一些则使用 tool calling。无论使用哪种模型,你都传入相同的 Zod schema;支持原生 structured output 的模型最可靠。

问:可以把结构化对象流式传输到浏览器吗?答:可以。streamObject 会返回由 deep-partial 对象组成的 partialObjectStream。每个字段到达时就将其渲染出来,并且在流结束之前,应当把所有字段都视为可能是 undefined。

问:如何在不使用对象包装的情况下对文本进行分类?答:使用 output: 'enum',并通过一个 enum 数组提供允许的标签。模型必须返回其中一个完全一致的字符串,相比只有一个 enum 字段的对象,这种方式约束更严格,成本也更低。

问:模型输出格式错误时会抛出什么异常?答:NoObjectGeneratedError。它会暴露原始文本、用量和响应信息,方便你记录日志并执行降级方案。可以通过字段的 .describe() 提示、使用 .nullable() 代替 .optional(),以及提供 experimental_repairText callback 来降低该异常的发生频率。

最初发布于 devya.dev,同时发布于 eng-ahmed.com。由 Devya Solutions 构建。

如需采取进一步行动,你可以考虑屏蔽此人和/或举报滥用行为。

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
AI 生成界面为何总漏掉异常状态
下一篇
TensorRT-LLM 双显卡部署实战