Structured output
Structured output 用于让模型返回可被程序直接解析的 JSON,而不是自由文本。它适合信息抽取、单据解析、分类结果返回、表单填充、配置生成、工具参数生成等场景。
使用 structured output 时,不要把模型输出直接当成可信业务数据。请在应用侧解析 JSON、校验 schema、检查关键字段,再写入数据库或触发后续动作。
选择输出方式
先根据业务结果需要的稳定程度选择请求方式。选择错了方式,即使模型返回 200,也可能得到无法稳定解析的内容。
- 使用
response_format.type = "json_schema":当结果必须包含固定字段、必填项、枚举值、嵌套对象或数组时使用。典型任务包括单据解析、实体抽取、结构化表单生成。 - 使用
response_format.type = "json_object":当只需要返回合法 JSON 对象,但字段结构可以由 prompt 约束时使用。典型任务包括轻量分类、状态返回、简单配置生成。 - 使用 Function Calling:当模型需要生成工具参数,或结构化结果会直接驱动函数、任务、查询、工作流时使用。工具参数也需要作为 JSON 解析和校验。
- 使用普通文本:当输出面向人阅读,且后续代码不需要稳定解析字段时使用。
如果某个流程既要调用工具,又要返回最终结构化结果,可以分两步处理:先使用 Function Calling 生成并执行工具参数,再使用 json_schema 让模型基于工具结果返回最终对象。
确认模型支持请求形态
不同模型对 json_schema、json_object 和 Function Calling 的支持程度不同。接入前,请用目标模型、目标 schema 和目标 prompt 在测试环境中验证一次。
以下表格列出本文示例请求的验证结果。
信息
该表格是一次能力快照,只覆盖本文测试过的部分模型,不包含全部 GenStudio LLM 模型。模型能力和服务行为可能随时间变化;接入前,请用目标模型、目标 schema 和目标 prompt 在测试环境中重新验证。表格中的 pass 或 fail 只表示本文示例请求当时的验证结果,不应替代您的业务 schema 验证。
| 模型 | json_schema | json_object | 工具参数 | 备注 |
|---|---|---|---|---|
deepseek-r1 | pass | pass | pass | 无 |
deepseek-v3.2 | pass | pass | pass | 无 |
deepseek-v3.2-thinking | pass | pass | pass | 无 |
deepseek-v4-flash | pass | pass | pass | 无 |
deepseek-v4-pro | pass | pass | fail | 工具参数请求返回 HTTP 500。 |
deepseek-ocr-2 | pass | fail | fail | 无 |
glm-4.5-air | fail | pass | pass | 无 |
glm-4.6v | fail | fail | pass | 无 |
glm-5.1 | pass | pass | pass | 无 |
gpt-oss-120b | fail | pass | pass | 无 |
kimi-k2.6 | pass | pass | pass | 无 |
megrez-3b-instruct | pass | pass | pass | 无 |
minimax-m2.7 | fail | fail | pass | 无 |
mimo-v2.5-pro | fail | pass | pass | 无 |
qwen3-235b-a22b-instruct-2507 | fail | pass | pass | 无 |
qwen3-coder-480b-a35b-instruct | fail | pass | pass | 无 |
qwen3-next-80b-a3b-thinking | fail | fail | fail | 未稳定通过本文示例验证。 |
qwen3-vl-235b-a22b-thinking | pass | pass | pass | 无 |
信息
如果模型返回 HTTP 200,但 choices[0].message.content 为空、不是 JSON,或字段不符合 schema,仍应视为本次结构化输出失败。
编写可校验的 schema
优先使用简单、明确、可在应用侧验证的 JSON Schema。schema 越复杂,越应该在调用后做本地校验。
建议优先使用的结构:
typepropertiesrequiredadditionalPropertiesenumitemsdescription- 简单的
minimum、maximum、minLength、maxLength
需要谨慎使用的结构:
- 复杂
pattern - 多层
anyOf、oneOf、allOf - 深层嵌套对象
- 很长的枚举列表
- 依赖自然语言解释才能判断的字段约束
如果业务要求严格一致,请把 schema 作为应用代码的一部分维护,并在收到响应后再次校验。不要只依赖 prompt 描述字段。
以下示例使用发票解析任务。它要求模型从原始文本中提取供应商、发票号、日期、明细项、合计金额和币种。
{
"type": "object",
"additionalProperties": false,
"properties": {
"vendor_name": { "type": "string" },
"vendor_address": {
"type": "object",
"additionalProperties": false,
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"postal_code": { "type": "string" },
"country": { "type": "string" }
},
"required": ["street", "city", "postal_code", "country"]
},
"invoice_number": { "type": "string" },
"invoice_date": { "type": "string" },
"line_items": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"description": { "type": "string" },
"quantity": { "type": "integer" },
"unit_price": { "type": "number" }
},
"required": ["description", "quantity", "unit_price"]
}
},
"total_amount": { "type": "number" },
"currency": { "type": "string", "enum": ["USD", "EUR", "GBP", "CNY"] }
},
"required": [
"vendor_name",
"vendor_address",
"invoice_number",
"invoice_date",
"line_items",
"total_amount",
"currency"
]
}选择 SDK 接入方式
生产接入通常会使用 SDK。GenStudio 提供 OpenAI 兼容 Chat Completions 接口,因此可以使用 OpenAI 兼容客户端发送 response_format、tools 和 tool_choice。
建议按以下方式选择 SDK 用法:
- 日常接入优先使用 SDK 的
chat.completions.create(...),并显式传入response_format或tools。这种方式最接近原始 HTTP 请求,也最容易排查。 - 可以使用 Pydantic、Zod 等库维护 schema,但发送前应确认最终 schema 符合目标模型支持的请求结构。
- 可以使用 SDK 提供的结构化输出辅助方法,例如自动从 Pydantic 或 Zod 生成 schema、自动解析响应等。使用前请确认该 SDK 版本最终发送的请求体仍是 GenStudio 支持的
response_format或tools结构。 - 不要把 SDK 辅助方法的行为当成平台能力保证。平台侧可验证的是发送到
/chat/completions的请求体,以及模型返回的响应结构。 - 保留一份等价 curl 请求。遇到 SDK 报错、字段缺失或解析失败时,用等价请求确认问题来自模型/API 行为,还是 SDK 参数转换。
无论使用哪种 SDK,用于业务判断的最终数据都应经过应用侧 JSON 解析和 schema 校验。
使用 curl 请求 JSON Schema 输出
先用 curl 固定一份等价请求。这样可以确认模型和接口是否接受当前请求体;后续使用 SDK 时,也能用它对照 SDK 最终发送的参数。
以下 curl 命令适用于 bash/zsh 等 POSIX 风格 Shell。请先设置 GENSTUDIO_API_KEY 环境变量。
curl --request POST \
--url "https://cloud.infini-ai.com/maas/v1/chat/completions" \
--header "Authorization: Bearer $GENSTUDIO_API_KEY" \
--header "Content-Type: application/json" \
--data-raw '{
"model": "kimi-k2.6",
"messages": [
{
"role": "system",
"content": "Extract invoice data. Return only JSON data that matches the requested schema."
},
{
"role": "user",
"content": "Vendor: Acme Corp, 123 Main St, Springfield, IL 62704\nInvoice Number: INV-2025-001\nDate: 2025-02-10\nItems:\n- Widget A, 5 units, $10.00 each\n- Widget B, 2 units, $15.00 each\nTotal: $80.00 USD"
}
],
"temperature": 0,
"max_tokens": 2048,
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "invoice",
"strict": true,
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"vendor_name": { "type": "string" },
"vendor_address": {
"type": "object",
"additionalProperties": false,
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"postal_code": { "type": "string" },
"country": { "type": "string" }
},
"required": ["street", "city", "postal_code", "country"]
},
"invoice_number": { "type": "string" },
"invoice_date": { "type": "string" },
"line_items": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"description": { "type": "string" },
"quantity": { "type": "integer" },
"unit_price": { "type": "number" }
},
"required": ["description", "quantity", "unit_price"]
}
},
"total_amount": { "type": "number" },
"currency": { "type": "string", "enum": ["USD", "EUR", "GBP", "CNY"] }
},
"required": [
"vendor_name",
"vendor_address",
"invoice_number",
"invoice_date",
"line_items",
"total_amount",
"currency"
]
}
}
}
}'预期返回中,choices[0].message.content 是一个 JSON 字符串。解析后应类似:
{
"vendor_name": "Acme Corp",
"vendor_address": {
"street": "123 Main St",
"city": "Springfield",
"postal_code": "62704",
"country": "US"
},
"invoice_number": "INV-2025-001",
"invoice_date": "2025-02-10",
"line_items": [
{
"description": "Widget A",
"quantity": 5,
"unit_price": 10
},
{
"description": "Widget B",
"quantity": 2,
"unit_price": 15
}
],
"total_amount": 80,
"currency": "USD"
}警告
部分后端在使用 response_format 时要求消息内容明确包含 JSON 字样。建议在 system prompt 中写明 Return only JSON data...,避免请求被拒绝或输出解释性文本。
使用 Python SDK 显式传入 response_format
Python 示例使用 OpenAI 客户端发送请求,并显式传入 response_format。这种写法不会依赖 SDK 的自动解析能力,便于对照 curl 请求排查问题。
import json
import os
from enum import Enum
from typing import List
from openai import OpenAI
from pydantic import BaseModel, Field, ValidationError
class Currency(str, Enum):
USD = "USD"
EUR = "EUR"
GBP = "GBP"
CNY = "CNY"
class Address(BaseModel):
street: str
city: str
postal_code: str
country: str
class LineItem(BaseModel):
description: str
quantity: int = Field(ge=1)
unit_price: float = Field(ge=0)
class Invoice(BaseModel):
vendor_name: str
vendor_address: Address
invoice_number: str
invoice_date: str
line_items: List[LineItem]
total_amount: float = Field(ge=0)
currency: Currency
invoice_schema = Invoice.model_json_schema()
invoice_schema["additionalProperties"] = False
client = OpenAI(
api_key=os.environ["GENSTUDIO_API_KEY"],
base_url=os.environ.get("GENSTUDIO_BASE_URL", "https://cloud.infini-ai.com/maas/v1"),
)
completion = client.chat.completions.create(
model="kimi-k2.6",
messages=[
{
"role": "system",
"content": "Extract invoice data. Return only JSON data that matches the requested schema.",
},
{
"role": "user",
"content": (
"Vendor: Acme Corp, 123 Main St, Springfield, IL 62704\n"
"Invoice Number: INV-2025-001\n"
"Date: 2025-02-10\n"
"Items:\n"
"- Widget A, 5 units, $10.00 each\n"
"- Widget B, 2 units, $15.00 each\n"
"Total: $80.00 USD"
),
},
],
temperature=0,
max_tokens=2048,
response_format={
"type": "json_schema",
"json_schema": {
"name": "invoice",
"strict": True,
"schema": invoice_schema,
},
},
)
content = completion.choices[0].message.content
try:
invoice = Invoice.model_validate_json(content)
except (TypeError, ValidationError, json.JSONDecodeError) as exc:
raise RuntimeError(f"Structured output validation failed: {exc}") from exc
print(invoice.invoice_number)
print(invoice.total_amount)如果 Pydantic 生成的 schema 中包含业务暂不需要的复杂关键字,请在发送前简化 schema。生产代码中也可以使用固定 JSON Schema 文件,避免客户端库升级改变请求结构。
如果希望使用 SDK 的自动解析辅助方法,请先在测试环境中确认它最终发送的请求体。只要最终请求仍是 GenStudio 支持的 response_format.type = "json_schema",并且返回内容通过应用侧校验,就可以在业务代码中使用该辅助方法。
使用 JavaScript SDK 显式传入 response_format
JavaScript 示例使用 OpenAI 客户端发送请求,并显式传入 response_format。API Key 只应在服务端环境中读取,不要写入浏览器代码。
import OpenAI from "openai";
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
const InvoiceSchema = z.object({
vendor_name: z.string(),
vendor_address: z.object({
street: z.string(),
city: z.string(),
postal_code: z.string(),
country: z.string(),
}),
invoice_number: z.string(),
invoice_date: z.string(),
line_items: z.array(
z.object({
description: z.string(),
quantity: z.number().int().min(1),
unit_price: z.number().min(0),
})
),
total_amount: z.number().min(0),
currency: z.enum(["USD", "EUR", "GBP", "CNY"]),
});
const client = new OpenAI({
apiKey: process.env.GENSTUDIO_API_KEY,
baseURL: process.env.GENSTUDIO_BASE_URL || "https://cloud.infini-ai.com/maas/v1",
});
const completion = await client.chat.completions.create({
model: "kimi-k2.6",
messages: [
{
role: "system",
content: "Extract invoice data. Return only JSON data that matches the requested schema.",
},
{
role: "user",
content: [
"Vendor: Acme Corp, 123 Main St, Springfield, IL 62704",
"Invoice Number: INV-2025-001",
"Date: 2025-02-10",
"Items:",
"- Widget A, 5 units, $10.00 each",
"- Widget B, 2 units, $15.00 each",
"Total: $80.00 USD",
].join("\n"),
},
],
temperature: 0,
max_tokens: 2048,
response_format: {
type: "json_schema",
json_schema: {
name: "invoice",
strict: true,
schema: zodToJsonSchema(InvoiceSchema, "invoice"),
},
},
});
const content = completion.choices[0]?.message?.content;
if (!content) {
throw new Error("Structured output content is empty.");
}
const parsed = InvoiceSchema.safeParse(JSON.parse(content));
if (!parsed.success) {
throw new Error(`Structured output validation failed: ${parsed.error.message}`);
}
console.log(parsed.data.invoice_number);
console.log(parsed.data.total_amount);如果使用 zodResponseFormat、zodToJsonSchema 或其他 schema 转换辅助方法,请把转换后的 JSON Schema 记录到测试日志或单元测试快照中。SDK 升级后,先确认 schema 形状没有意外变化,再发布到生产环境。
使用 JSON object 返回宽松对象
如果只需要合法 JSON 对象,不需要服务端按完整 schema 限制字段,可以使用 json_object。这种方式适合轻量状态、简单分类、开关配置等任务。
curl --request POST \
--url "https://cloud.infini-ai.com/maas/v1/chat/completions" \
--header "Authorization: Bearer $GENSTUDIO_API_KEY" \
--header "Content-Type: application/json" \
--data-raw '{
"model": "kimi-k2.6",
"messages": [
{
"role": "system",
"content": "Return a valid JSON object. Do not include Markdown fences."
},
{
"role": "user",
"content": "Return a JSON object with keys status, model_task, and score. Use status=ok, model_task=structured_output_probe, score=1."
}
],
"temperature": 0,
"max_tokens": 512,
"response_format": {
"type": "json_object"
}
}'预期解析结果:
{
"status": "ok",
"model_task": "structured_output_probe",
"score": 1
}json_object 只约束输出是 JSON 对象,不等于字段一定完整或类型一定正确。请继续用应用侧 schema 检查字段。
使用 Function Calling 生成工具参数
当模型输出会触发外部函数、数据库查询、工作流或任务队列时,优先把目标动作定义为工具。工具参数 schema 可以限制模型生成的参数结构。
以下示例要求模型调用 create_order,并返回订单参数。
curl --request POST \
--url "https://cloud.infini-ai.com/maas/v1/chat/completions" \
--header "Authorization: Bearer $GENSTUDIO_API_KEY" \
--header "Content-Type: application/json" \
--data-raw '{
"model": "deepseek-v4-flash",
"messages": [
{
"role": "user",
"content": "Create an order for Alice buying 3 notebooks for 12.50 CNY on 2026-05-14. Use the tool."
}
],
"temperature": 0,
"max_tokens": 2048,
"tools": [
{
"type": "function",
"function": {
"name": "create_order",
"description": "Create an order record.",
"strict": true,
"parameters": {
"type": "object",
"additionalProperties": false,
"properties": {
"order_id": { "type": "string" },
"buyer": { "type": "string" },
"item": { "type": "string" },
"quantity": { "type": "integer" },
"total": { "type": "number" },
"currency": { "type": "string", "enum": ["CNY", "USD"] },
"order_date": { "type": "string" }
},
"required": ["buyer", "item", "quantity", "total", "currency", "order_date"]
}
}
}
],
"tool_choice": {
"type": "function",
"function": {
"name": "create_order"
}
}
}'预期返回中,工具参数位于 choices[0].message.tool_calls[0].function.arguments。该字段通常是 JSON 字符串,解析后应类似:
{
"order_id": "SO-20260514-001",
"buyer": "Alice",
"item": "notebooks",
"quantity": 3,
"total": 12.5,
"currency": "CNY",
"order_date": "2026-05-14"
}不要直接执行工具调用。请先解析 arguments,校验参数 schema,再检查金额、数量、用户权限和幂等键。
执行工具后返回最终结构化结果
如果业务流程需要先调用工具,再把工具结果整理成最终 JSON,请拆成两个请求,降低单次响应的不确定性。
- 第一次请求使用 Function Calling,让模型生成工具参数。
- 应用校验参数并执行工具。
- 第二次请求把工具结果作为上下文传给模型,并使用
response_format.type = "json_schema"约束最终返回对象。
第二次请求示例:
{
"model": "kimi-k2.6",
"messages": [
{
"role": "system",
"content": "Return only JSON data that matches the requested schema."
},
{
"role": "user",
"content": "The order creation tool returned success=true, order_id=SO-20260514-001, buyer=Alice, total=12.50, currency=CNY. Return the final order summary."
}
],
"temperature": 0,
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "order_summary",
"strict": true,
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"success": { "type": "boolean" },
"order_id": { "type": "string" },
"buyer": { "type": "string" },
"total": { "type": "number" },
"currency": { "type": "string", "enum": ["CNY", "USD"] }
},
"required": ["success", "order_id", "buyer", "total", "currency"]
}
}
}
}这种拆分方式更容易定位失败原因:第一步失败通常是工具参数问题,第二步失败通常是最终结果 schema 或 prompt 问题。
解析流式结构化输出
如果使用流式响应,请先累积完整 content,再解析 JSON。不要在半截 chunk 上做最终校验。
Python 解析示例:
content_parts = []
stream = client.chat.completions.create(
model="kimi-k2.6",
messages=messages,
temperature=0,
response_format=response_format,
stream=True,
)
for chunk in stream:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
if getattr(delta, "content", None):
content_parts.append(delta.content)
content = "".join(content_parts)
invoice = Invoice.model_validate_json(content)JavaScript 解析示例:
let content = "";
const stream = await client.chat.completions.create({
model: "kimi-k2.6",
messages,
temperature: 0,
response_format,
stream: true,
});
for await (const chunk of stream) {
const delta = chunk.choices?.[0]?.delta;
if (delta?.content) {
content += delta.content;
}
}
const invoice = InvoiceSchema.parse(JSON.parse(content));流式输出的中间片段可能不是合法 JSON。只有完整响应结束后,才适合做最终 JSON 解析和 schema 校验。
固定失败处理路径
结构化输出失败时,请让应用进入明确的失败路径。不要让失败内容继续流入后续业务动作。
建议按以下顺序处理:
- 检查 HTTP 状态码。非 2xx 响应先按请求错误或服务错误处理。
- 检查
choices[0].message.content或tool_calls[0].function.arguments是否存在。 - 尝试解析 JSON。解析失败时记录原始响应摘要,不要执行后续动作。
- 使用应用侧 schema 校验字段、类型、枚举和必填项。
- 校验金额、数量、日期、用户权限、幂等键等业务规则。
- 记录响应头中的
traceresponse、响应体中的id、模型 ID、schema 版本和校验错误。 - 根据业务风险选择重试、降级、人工复核或直接失败。
如果重试,请使用相同输入、相同 schema 和较低随机性参数。不要用无限重试掩盖模型或 schema 不兼容。
排查请求被拒绝或返回异常
遇到失败时,先判断是请求被拒绝,还是模型返回内容未通过应用校验。
让 prompt 明确要求 JSON
部分后端在使用 response_format 时会检查消息中是否出现 json。如果错误提示类似 messages must contain the word json,请在 system prompt 中明确写入:
Return only JSON data that matches the requested schema.调整 token 上限
如果 content 为空或不完整,检查 max_tokens 是否过低。结构化输出需要足够 token 返回完整 JSON。复杂 schema、推理模型和嵌套数组尤其容易受影响。
降低 schema 复杂度
如果返回 JSON 能解析但不满足 schema,先用更小的 schema 验证模型能力,再逐步增加嵌套对象、数组、枚举和业务字段。
更换请求形态
如果 json_schema 不稳定,但 json_object 通过,可以先用 json_object 获取 JSON,再把严格校验放在应用侧。如果结果会触发外部动作,请优先测试 Function Calling。
识别工具调用限制
如果工具参数请求返回 400 或 500,先去掉强制 tool_choice 进行验证,或更换同系列模型测试。本文验证中,deepseek-v4-pro 能通过 json_schema 和 json_object,但工具参数请求返回 HTTP 500;deepseek-v4-flash 三种请求形态均通过。
记录可复现信息
每次结构化输出失败,都应记录足够信息,方便复现和排查。
建议记录:
- 模型 ID。
- 请求路径。
response_format或工具 schema 的版本。- HTTP 状态码。
- 响应头中的
traceresponse。 - 响应体中的
id或request_id。 - JSON 解析错误或 schema 校验错误。
- 是否开启流式响应。
max_tokens、temperature等关键参数。
关于如何获取 traceresponse 和响应体 id,参见 请求标识。
接入前完成验证
把 structured output 接入生产流程前,请至少完成以下验证:
- 使用目标模型和目标 schema 跑通非流式请求。
- 用真实业务样本覆盖正常、缺字段、歧义文本、超长文本和非法输入。
- 确认应用侧 JSON 解析和 schema 校验会拦截坏响应。
- 确认校验失败不会触发数据库写入、外部工具执行或用户可见的错误动作。
- 记录请求标识,确保线上问题能回溯到具体请求。
- 如果使用流式响应,确认完整内容拼接后再解析 JSON。
- 如果使用 Function Calling,确认工具参数校验通过后才执行工具。
完成这些检查后,再把结构化输出接入需要稳定字段的业务流程。