Skip to content

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_schemajson_object 和 Function Calling 的支持程度不同。接入前,请用目标模型、目标 schema 和目标 prompt 在测试环境中验证一次。

以下表格列出本文示例请求的验证结果。

信息

该表格是一次能力快照,只覆盖本文测试过的部分模型,不包含全部 GenStudio LLM 模型。模型能力和服务行为可能随时间变化;接入前,请用目标模型、目标 schema 和目标 prompt 在测试环境中重新验证。表格中的 passfail 只表示本文示例请求当时的验证结果,不应替代您的业务 schema 验证。

模型json_schemajson_object工具参数备注
deepseek-r1passpasspass
deepseek-v3.2passpasspass
deepseek-v3.2-thinkingpasspasspass
deepseek-v4-flashpasspasspass
deepseek-v4-propasspassfail工具参数请求返回 HTTP 500。
deepseek-ocr-2passfailfail
glm-4.5-airfailpasspass
glm-4.6vfailfailpass
glm-5.1passpasspass
gpt-oss-120bfailpasspass
kimi-k2.6passpasspass
megrez-3b-instructpasspasspass
minimax-m2.7failfailpass
mimo-v2.5-profailpasspass
qwen3-235b-a22b-instruct-2507failpasspass
qwen3-coder-480b-a35b-instructfailpasspass
qwen3-next-80b-a3b-thinkingfailfailfail未稳定通过本文示例验证。
qwen3-vl-235b-a22b-thinkingpasspasspass

信息

如果模型返回 HTTP 200,但 choices[0].message.content 为空、不是 JSON,或字段不符合 schema,仍应视为本次结构化输出失败。

编写可校验的 schema

优先使用简单、明确、可在应用侧验证的 JSON Schema。schema 越复杂,越应该在调用后做本地校验。

建议优先使用的结构:

  • type
  • properties
  • required
  • additionalProperties
  • enum
  • items
  • description
  • 简单的 minimummaximumminLengthmaxLength

需要谨慎使用的结构:

  • 复杂 pattern
  • 多层 anyOfoneOfallOf
  • 深层嵌套对象
  • 很长的枚举列表
  • 依赖自然语言解释才能判断的字段约束

如果业务要求严格一致,请把 schema 作为应用代码的一部分维护,并在收到响应后再次校验。不要只依赖 prompt 描述字段。

以下示例使用发票解析任务。它要求模型从原始文本中提取供应商、发票号、日期、明细项、合计金额和币种。

language-json
{
  "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_formattoolstool_choice

建议按以下方式选择 SDK 用法:

  • 日常接入优先使用 SDK 的 chat.completions.create(...),并显式传入 response_formattools。这种方式最接近原始 HTTP 请求,也最容易排查。
  • 可以使用 Pydantic、Zod 等库维护 schema,但发送前应确认最终 schema 符合目标模型支持的请求结构。
  • 可以使用 SDK 提供的结构化输出辅助方法,例如自动从 Pydantic 或 Zod 生成 schema、自动解析响应等。使用前请确认该 SDK 版本最终发送的请求体仍是 GenStudio 支持的 response_formattools 结构。
  • 不要把 SDK 辅助方法的行为当成平台能力保证。平台侧可验证的是发送到 /chat/completions 的请求体,以及模型返回的响应结构。
  • 保留一份等价 curl 请求。遇到 SDK 报错、字段缺失或解析失败时,用等价请求确认问题来自模型/API 行为,还是 SDK 参数转换。

无论使用哪种 SDK,用于业务判断的最终数据都应经过应用侧 JSON 解析和 schema 校验。

使用 curl 请求 JSON Schema 输出

先用 curl 固定一份等价请求。这样可以确认模型和接口是否接受当前请求体;后续使用 SDK 时,也能用它对照 SDK 最终发送的参数。

以下 curl 命令适用于 bash/zsh 等 POSIX 风格 Shell。请先设置 GENSTUDIO_API_KEY 环境变量。

language-shell
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 字符串。解析后应类似:

language-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 请求排查问题。

language-python
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 只应在服务端环境中读取,不要写入浏览器代码。

language-javascript
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);

如果使用 zodResponseFormatzodToJsonSchema 或其他 schema 转换辅助方法,请把转换后的 JSON Schema 记录到测试日志或单元测试快照中。SDK 升级后,先确认 schema 形状没有意外变化,再发布到生产环境。

使用 JSON object 返回宽松对象

如果只需要合法 JSON 对象,不需要服务端按完整 schema 限制字段,可以使用 json_object。这种方式适合轻量状态、简单分类、开关配置等任务。

language-shell
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"
    }
  }'

预期解析结果:

language-json
{
  "status": "ok",
  "model_task": "structured_output_probe",
  "score": 1
}

json_object 只约束输出是 JSON 对象,不等于字段一定完整或类型一定正确。请继续用应用侧 schema 检查字段。

使用 Function Calling 生成工具参数

当模型输出会触发外部函数、数据库查询、工作流或任务队列时,优先把目标动作定义为工具。工具参数 schema 可以限制模型生成的参数结构。

以下示例要求模型调用 create_order,并返回订单参数。

language-shell
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 字符串,解析后应类似:

language-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,请拆成两个请求,降低单次响应的不确定性。

  1. 第一次请求使用 Function Calling,让模型生成工具参数。
  2. 应用校验参数并执行工具。
  3. 第二次请求把工具结果作为上下文传给模型,并使用 response_format.type = "json_schema" 约束最终返回对象。

第二次请求示例:

language-json
{
  "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 解析示例:

language-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 解析示例:

language-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 校验。

固定失败处理路径

结构化输出失败时,请让应用进入明确的失败路径。不要让失败内容继续流入后续业务动作。

建议按以下顺序处理:

  1. 检查 HTTP 状态码。非 2xx 响应先按请求错误或服务错误处理。
  2. 检查 choices[0].message.contenttool_calls[0].function.arguments 是否存在。
  3. 尝试解析 JSON。解析失败时记录原始响应摘要,不要执行后续动作。
  4. 使用应用侧 schema 校验字段、类型、枚举和必填项。
  5. 校验金额、数量、日期、用户权限、幂等键等业务规则。
  6. 记录响应头中的 traceresponse、响应体中的 id、模型 ID、schema 版本和校验错误。
  7. 根据业务风险选择重试、降级、人工复核或直接失败。

如果重试,请使用相同输入、相同 schema 和较低随机性参数。不要用无限重试掩盖模型或 schema 不兼容。

排查请求被拒绝或返回异常

遇到失败时,先判断是请求被拒绝,还是模型返回内容未通过应用校验。

让 prompt 明确要求 JSON

部分后端在使用 response_format 时会检查消息中是否出现 json。如果错误提示类似 messages must contain the word json,请在 system prompt 中明确写入:

language-text
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_schemajson_object,但工具参数请求返回 HTTP 500;deepseek-v4-flash 三种请求形态均通过。

记录可复现信息

每次结构化输出失败,都应记录足够信息,方便复现和排查。

建议记录:

  • 模型 ID。
  • 请求路径。
  • response_format 或工具 schema 的版本。
  • HTTP 状态码。
  • 响应头中的 traceresponse
  • 响应体中的 idrequest_id
  • JSON 解析错误或 schema 校验错误。
  • 是否开启流式响应。
  • max_tokenstemperature 等关键参数。

关于如何获取 traceresponse 和响应体 id,参见 请求标识

接入前完成验证

把 structured output 接入生产流程前,请至少完成以下验证:

  1. 使用目标模型和目标 schema 跑通非流式请求。
  2. 用真实业务样本覆盖正常、缺字段、歧义文本、超长文本和非法输入。
  3. 确认应用侧 JSON 解析和 schema 校验会拦截坏响应。
  4. 确认校验失败不会触发数据库写入、外部工具执行或用户可见的错误动作。
  5. 记录请求标识,确保线上问题能回溯到具体请求。
  6. 如果使用流式响应,确认完整内容拼接后再解析 JSON。
  7. 如果使用 Function Calling,确认工具参数校验通过后才执行工具。

完成这些检查后,再把结构化输出接入需要稳定字段的业务流程。