- 人工智能
- AI Agent
- MCP 服务
- 工具调用
- GUI 自动化
- 语音
- 数字人
- 交互助手
【免费下载链接】airi
💖🧸 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器,目标是接近 Neuro-sama 的高度;支持实时语音聊天、Minecraft 和 Factorio 游玩,支持 Web / macOS / Windows。
本文以 airi 仓库中的 xsAI 技能参考文档(.agents/skills/xsai/references/structured-output.md)为核心骨架,系统讲解在 OpenAI 兼容工作流中如何产出「带类型校验的结构化输出」:何时用generateObject、何时用streamObject、如何用tool()/rawTool()声明工具、以及 Zod / Valibot / ArkType / Effect 等 Standard Schema 库如何经xsschema统一转换为 JSON Schema。读完本文,你将能独立判断结构化输出 API 的选型、补齐 schema 转换依赖,并理解 airi 在core-agent与stage-ui中真实的工具与 schema 落地方式,直接复制到自己的项目中运行。
一、Structured Output 两大 API:generateObject 与 streamObject
xsAI 将结构化输出收敛为两个核心 API,两者都依赖xsschema完成「用户侧 schema 库 → JSON Schema → 供应商可消费格式」的转换链路:
generateObject:一元(unary)结构化输出,返回一个经过校验的最终对象;streamObject:增量(incremental)结构化输出,边生成边解析,适合需要渐进式渲染的 UI 或工作流。
选择依据很直接:用户要「一个确定、合法、校验过的对象」就用generateObject;用户要「随着 token 流入不断更新的部分结果」就用streamObject。两者的转换都发生在文本流开始之前,这也是streamObject被设计为 async 的原因——schema 必须先完成转换才能启动流。.agents/skills/xsai/SKILL.md中的 API 选择规则也印证了这一分工:generateObject对应「validated structured output」,streamObject对应「incremental object parsing」。
二、Schema 支持与转换:Standard Schema 生态 + xsschema
xsAI 通过xsschema支持Standard Schema风格的 schema 库,包括:
- Zod
- Valibot
- ArkType
- Effect
当用户需要「带类型的结构化输出」时,就使用这些库声明 schema。需要注意:部分 schema 厂商需要项目里额外安装一个「JSON Schema 转换器」包,否则xsschema无法把 schema 转成供应商能理解的 JSON Schema:
| Schema 库 | 需要的转换包 |
|---|---|
| Zod v3 | zod-to-json-schema |
| Valibot | @valibot/to-json-schema |
仓库内的真实依赖印证了这一要求:packages/stage-ui/package.json 与 packages/core-agent/package.json 都同时依赖xsschema、zod与valibot(stage-ui 还有valibot),而 tool 定义统一来自@xsai/tool。
xsschema 在 airi 中的两条关键能力
toJsonSchema:把 Zod / Valibot 等 schema 转成 provider-neutral 的 JSON Schema。spark-command 工具实现 中createSparkCommandTool先用toJsonSchema(sparkCommandToolSchema)生成参数 schema,注释明确写着「Keep the generated JSON Schema provider-neutral. Each provider adapter converts unsupported schema forms before it sends the request」——即各 provider 适配器在发请求前还会再做一次兜底转换。validate:在工具执行侧对输入做运行时校验。spark-notify 工具实现 同时导入toJsonSchema与validate,构建 notify 命令工具时对 schema 做双重处理。
三、generateObject:一次性返回校验过的最终对象
generateObject适用于「用户想要一个 validated final object」的场景。与让模型自由输出 JSON 相比,它最大的价值是schema 驱动 + 校验兜底:输出必须匹配 schema,否则无法通过校验。
重要选项
| 选项 | 作用 |
|---|---|
schema | 结构化输出必须满足的 schema(Zod / Valibot / ArkType / Effect 之一) |
schemaName | schema 的名称,用于供应商侧标识 |
schemaDescription | schema 的描述,帮助模型理解输出结构 |
strict | 是否启用严格模式(开启后模型输出必须严格符合 schema,禁止多余字段) |
output: 'array'(可选) | 让输出成为一个元素数组而非单个对象 |
最小可运行示例
以 Valibot 为例(需要项目里安装@valibot/to-json-schema):
import { env } from 'node:process' import { generateObject } from '@xsai/generate-object' import * as v from 'valibot' const { object } = await generateObject({ apiKey: env.OPENAI_API_KEY!, baseURL: 'https://api.openai.com/v1/', messages: [ { content: 'Extract the event information.', role: 'system', }, { content: 'Alice and Bob are going to a science fair on Friday.', role: 'user', }, ], model: 'gpt-4o', schema: v.object({ date: v.string(), name: v.string(), participants: v.array(v.string()), }), })返回的object已通过 schema 校验,可以直接当作类型安全的 JS 对象使用。可运行示例集 明确建议:凡是需要结构化数据的场景,优先用generateObject而不是让模型自由输出 JSON,因为后者无法保证字段齐全、类型正确。
四、streamObject:增量解析的结构化输出
streamObject适用于「UI 或工作流能从部分结构化输出中受益」的场景,例如打字机式的对象填充、边生成边校验的表单。它之所以是 async,是因为schema 转换发生在文本流开始之前——转换完成才会真正发起流式请求。
两种结果形状
| 模式 | 结果流 | 适用场景 |
|---|---|---|
| 对象模式(默认) | partialObjectStream | 需要渐进式更新的单个对象 |
数组模式(output: 'array') | elementStream | 需要逐元素消费的结果列表 |
最小可运行示例
import { env } from 'node:process' import { streamObject } from '@xsai/stream-object' import * as v from 'valibot' const { partialObjectStream } = await streamObject({ apiKey: env.OPENAI_API_KEY!, baseURL: 'https://api.openai.com/v1/', messages: [ { content: 'Extract the event information.', role: 'system', }, { content: 'Alice and Bob are going to a science fair on Friday.', role: 'user', }, ], model: 'gpt-4o', schema: v.object({ date: v.string(), name: v.string(), participants: v.array(v.string()), }), }) for await (const partialObject of partialObjectStream) { console.log(partialObject) }对象模式下,每次迭代拿到一个「部分填充」的对象,前端可以实时渲染;需要逐条结果时切换到output: 'array'并消费elementStream。
五、Tool helpers:tool() 与 rawTool() 的选择
除了输出对象,结构化 schema 还广泛用于「工具(function calling)」定义。xsAI 提供两个工具构造器:
tool():配合 Standard Schema 库(Zod、Valibot 等)使用。适用于项目里已经有 schema 库的情况,声明式地定义name、description、parameters与execute。rawTool():直接使用原始 JSON Schema。适用于项目里已经有一份 JSON Schema,或者希望与 schema 库零耦合的情况。
SKILL.md 补充了一条中间规则:只有仓库已经在用纯Tool对象形态、或者需要规避tool()的异步初始化时,才直接用普通Tool对象。
airi 中的 tool() 实战:MCP 与 Home Assistant
- MCP 桥接工具 用
tool()定义了builtIn_mcpListTools与builtIn_mcpCallTool两个 MCP 桥接工具:前者参数为z.object({}).strict()(空对象 + 严格模式),后者用z.object({ name: z.string(), arguments: z.string() })把 MCP 参数包成 JSON 字符串传入——注释解释了为何用z.string()而非z.unknown():z.unknown()会生成没有type键的{},部分供应商无法接受。 - Home Assistant 工具 同样用
tool()+ Zod 定义实体查询工具,并在execute内对service_data做JSON.parse前的字符串与对象形态校验(parseServiceData),形成「schema 声明 + 运行时双重校验」。
airi 中的 rawTool() 实战:spark 命令
spark-command 工具实现 展示了完整的rawTool()用法:
const parameters = await toJsonSchema(sparkCommandToolSchema) return [ rawTool({ name: 'builtIn_emitSparkCommand', description: 'Send a spark:command to one or more frontend-connected modules or sub-agents.', parameters, execute: async (rawPayload) => { const payload = rawPayload as z.infer<typeof sparkCommandToolSchema> // ... 归一化并转发为 WebSocketEvents['spark:command'] options.sendSparkCommand(command) }, }), ]流程是:先用 Zod 声明sparkCommandToolSchema(spark-notify schema 中是z.object({...})的复合 schema),再toJsonSchema转成 provider-neutral 的 JSON Schema,最后交给rawTool。spark-notify 工具实现 沿用了同一模式,并额外用validate做运行时校验。
strict-tool-schema:airi 对 tool schema 的强约束测试
airi 对工具 schema 的要求比默认更严格。strict-tool-schema 断言 提供 Vitest 自定义断言toSatisfyStrictToolSchema,强制以下规则:
- 只要出现
properties,就必须同时提供required; required必须覆盖properties中的每一个键(既不能缺、也不能多);- 出现
properties时additionalProperties必须为false; - 递归检查嵌套
items与anyOf/oneOf/allOf分支。
这直接呼应了structured-output.md中strict选项的意图——在 OpenAI 兼容的 function calling 场景里,schema 不严格会导致供应商 400 或模型自由发挥。web-search 工具实现 的注释也说明:严格 OpenAI 兼容的供应商会拒绝required中缺失properties键的 tool schema,所以可选参数被建模为「required 且允许 null」而非.optional()。
六、供应商兼容性兜底:anyOf 折叠与 nullable 归一化
schema 转换到 JSON Schema 后,不同供应商对anyOf/ nullable 的接受度不同。airi 的provider-inference为此实现了适配层:
- tool-schema 兼容性实现 的
collapseToolSchemaPrimitiveAnyOf(schema)会把「纯原始类型组成的 anyOf 分支」折叠成 JSON Schema 的type数组(例如anyOf: [{ type: 'string' }, { type: 'null' }]→{ type: ['string', 'null'] }),用于仅在供应商拒绝标准 anyOf 形式之后的兜底;对象与数组联合仍保留为anyOf。对应测试见 tool-schema 测试。 - spark-notify schema 的
normalizeNullableAnyOf则对 schema 做递归的 nullable 归一化,与collapseToolSchemaPrimitiveAnyOf配合,让同一个工具 schema 能安全穿越 azure-openai 适配器 等对 schema 形式敏感的自定义供应商适配器。
这条链路完整回答了「为什么说 schema 转换发生在请求之前、且各 provider 还要再兜底一次」:toJsonSchema负责标准转换,适配器负责供应商差异,validate负责运行时正确性。
七、推荐规则速查
把structured-output.md的三条推荐规则与 SKILL.md、可运行示例集、包选择指南 综合如下:
- 优先
generateObject,而不是让模型输出自由格式 JSON——校验保证结构正确,字段不会缺失或变形; - 当 UI 或工作流能从部分结构化输出中受益时,用
streamObject——增量解析、渐进渲染; - 除非用户明确需要原始 JSON Schema,否则优先
tool()——schema 库带来的类型安全与描述能力通常更有价值; - 包体敏感时优先
@xsai/generate-object、@xsai/stream-object、@xsai/tool等细粒度包,而不是xsai全家桶(@xsai/tool同时导出tool与rawTool); - 所有示例都应显式给出
baseURL与model;托管供应商的apiKey在 Node.js 用process.env、浏览器用localStorage注入,绝不硬编码。
八、扩展阅读路径
- xsAI 技能入口与总体选型规则:API 选择、约束与定位
- 结构化输出参考文档:本文核心骨架
- 可运行示例集:文本生成、流式工具、generateObject / streamObject 完整代码
- 包选择指南:细粒度包与 umbrella 包取舍
- 文本、流式与工具循环:
generateText/streamText与stopWhen工具循环 - spark-command 工具实现:
rawTool+toJsonSchema实战 - spark-notify 工具实现:
rawTool+toJsonSchema+validate实战 - web-search 工具实现:strict schema 与 required-nullable 建模
- MCP 桥接工具:
tool()定义实例 - tool schema 兼容性兜底:
anyOf折叠实现与其测试 - strict tool schema 断言:required / additionalProperties 强约束测试
- 人工智能
- AI Agent
- MCP 服务
- 工具调用
- GUI 自动化
- 语音
- 数字人
- 交互助手
【免费下载链接】airi
💖🧸 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器,目标是接近 Neuro-sama 的高度;支持实时语音聊天、Minecraft 和 Factorio 游玩,支持 Web / macOS / Windows。
相关推荐
Airi 中的 xsAI 扩展包选型指南:@xsai-ext/providers、responses 与 telemetry 的使用边界
Airi 中的 xsAI 扩展包选型指南:@xsai ext/providers、responses 与 telemetry 的使用边界 Airi 的 LLM
AI 应用人工智能大模型数字人AI Agent语音前端后端桌面应用移动开发即时通讯3D渲染AIRI 的 xsAI 包选型指南:细粒度 @xsai/* 包与 xsai 伞包如何取舍
AIRI 的 xsAI 包选型指南:细粒度 @xsai/ 包与 xsai 伞包如何取舍 AIRI 的 LLM、语音、工具调用等能力构建在 xsAI 这套 Ope
AI 应用人工智能大模型数字人AI Agent语音前端后端桌面应用移动开发即时通讯3D渲染Claude API 工具调用(Tool Use)完全指南:从 Tool Runner、服务端工具到结构化输出
Claude API 工具调用(Tool Use)完全指南:从 Tool Runner、服务端工具到结构化输出 本文是围绕开源仓库 skills3/skills
人工智能AI 技能AI 评测
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考