Gemini API 如何生成结构化 JSON?开发者完整教程

从提示词 JSON、responseMimeType 到 responseSchema / JSON Schema,讲清 Gemini API 结构化输出原理、Python 与 REST 示例、与 Function Calling 的分工,以及落地前如何校验。

上一篇《AI Agent 为什么离不开 JSON》顺着一次调用拆了 Tool Calling 与 MCP 的数据流。本文换到模型对用户(或下游程序)的最终答复:如何让 Gemini 直接吐出可解析、可校验、可入库的 JSON,而不是「看起来像 JSON 的散文」。

这是 Gemini 文档里的 Structured Outputs(结构化输出 / 受控生成)。它和 Function Calling 共用同一套 Schema 思想,但目标不同:前者约束最终载荷,后者约束工具参数。把两者分清,Agent 才不会把「抽发票」和「调支付接口」写成同一种调用。

三种做法,一层比一层硬

团队里常见三种「让 Gemini 出 JSON」的办法,可靠性差一个数量级:

做法你控制什么适合什么
只在提示词里写「请输出 JSON」软约束,模型仍可能加 markdown 围栏或注释探索、一次性脚本
responseMimeType: application/json输出必须是合法 JSON 文本形状多变、先保证能 parse
MIME + responseSchema / responseJsonSchema字段、类型、枚举、必填都被约束生产抽取、填表、Agent 间传递

第一层失败形态人人见过:```json 代码块、末尾多一段解释、单引号、尾逗号。第二层能 JSON.parse,但 price 可能变成字符串,items 可能缺。第三层才是本教程的重点——把 JSON Schema 交给 API,让解码器在生成每个 token 时就避开非法路径。

约束解码:为什么 Schema 比提示词更硬

提示词只能提高「模型愿意遵守」的概率。Structured Output 把 Schema 编译进生成过程:下一步若会破坏 JSON 语法或偏离 Schema(例如该输出 " 却要开始一个未声明字段),该 token 的概率会被压掉。所以你拿到的 response.text 通常已经是一份可解析对象,而不必先写正则剥围栏。

2025 年起 Gemini API 在原有 OpenAPI 3.0 风格 responseSchema 之外,补上了对标准 JSON Schema 的支持(请求里常对应 responseJsonSchema)。这意味着 Python 的 Pydantic model_json_schema()、TypeScript 的 Zod 导出,可以几乎原样送给 Gemini。Gemini 2.5 及之后还会尽量保持 Schema 里的字段顺序,方便和下游表格、CSV 列对齐。

分类任务还有一条旁路:responseMimeType: text/x.enum,模型只吐枚举字符串(如 Keyboard),连花括号都没有。需要对象时用 application/json;只需要一个标签时用 enum MIME。

Python:google-genai 完整示例

推荐官方新 SDK google-genai(from google import genai),不要和旧包 google-generativeai 混用。环境变量 GEMINI_API_KEY 配好后:

from google import genai
from pydantic import BaseModel, Field


class LineItem(BaseModel):
    name: str = Field(description="商品名称")
    qty: int = Field(description="数量,正整数")
    unit_price_cents: int = Field(description="单价,分")


class Invoice(BaseModel):
    vendor: str
    currency: str = Field(description="ISO 4217,如 CNY")
    items: list[LineItem]
    total_cents: int


client = genai.Client()
response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="从下面文本抽取发票:Acme 卖了 2 个键盘,每个 199 元人民币。",
    config={
        "response_mime_type": "application/json",
        "response_schema": Invoice,
    },
)

print(response.text)      # JSON 字符串
invoice = response.parsed  # Invoice 实例(Pydantic 路径)
print(invoice.total_cents)

response.parsed 只有在 response_schema 传入 Pydantic / SDK 类型时才有意义。若你只传原始 JSON Schema 字典(见下一节),请 json.loads(response.text) 后再自己校验。

多条记录用 list[Invoice] 或外层再包一个带 invoices: list[Invoice] 的对象。数组根类型在部分模型上不如「永远返回 object」稳,生产里更常见后者。

response_schema 与 JSON Schema

两条配置不要混着猜:

  • response_schema:Pydantic 模型、Python Enum、或 SDK 的 Schema 对象。SDK 会转换成线上的 OpenAPI 子集。
  • response_json_schema:一份 JSON Schema 对象(字典)。适合 Invoice.model_json_schema()、Zod 的 toJSONSchema(),以及 additionalProperties、minimum / maximum、prefixItems 等更完整的约束。
schema = {
  "type": "object",
  "properties": {
    "vendor": { "type": "string" },
    "currency": { "type": "string", "enum": ["CNY", "USD", "EUR"] },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "qty": { "type": "integer", "minimum": 1 },
          "unit_price_cents": { "type": "integer", "minimum": 0 }
        },
        "required": ["name", "qty", "unit_price_cents"],
        "additionalProperties": False
      }
    },
    "total_cents": { "type": "integer" }
  },
  "required": ["vendor", "currency", "items", "total_cents"],
  "additionalProperties": False
}

response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="抽取发票:……",
    config={
        "response_mime_type": "application/json",
        "response_json_schema": schema,
    },
)

REST 文档里旧式 responseSchema 常用大写类型名(OBJECT、STRING、ARRAY、INTEGER)。JSON Schema 路径则是小写 object / string。不要把两套关键字交叉粘贴。字段说明写进 description:它会进模型上下文,决定「qty 是件数还是箱数」这种语义,光靠类型挡不住。

REST 请求长什么样

Gemini Developer API 的 generateContent 把结构化输出放在 generationConfig。密钥走 x-goog-api-key 或查询参数,不要写进前端仓库。

POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent

{
  "contents": [
    {
      "role": "user",
      "parts": [{ "text": "从文本抽取发票:……" }]
    }
  ],
  "generationConfig": {
    "responseMimeType": "application/json",
    "responseJsonSchema": {
      "type": "object",
      "properties": {
        "vendor": { "type": "string" },
        "total_cents": { "type": "integer" }
      },
      "required": ["vendor", "total_cents"]
    }
  }
}

成功时候选文本在 candidates[0].content.parts[0].text,内容是 JSON 字符串。Vertex AI 上字段名相同,只是 endpoint 与鉴权换成 GCP。图片、PDF 一样可以当输入:Schema 约束的是输出,不限制多模态输入。

和 Function Calling 怎么分工

两者都是「用 Schema 管 JSON」,但发生在对话的不同跳:

Structured OutputFunction Calling / Tool Calling
约束对象最终答复 JSON工具参数 JSON
谁执行副作用没有;只是数据宿主 / MCP Server
典型配置responseMimeType + Schematools[].parameters / inputSchema
失败怎么回流校验失败则重试或人工把错误写成 tool 消息再问模型

发票抽取、内容审核标签、把会议纪要变成任务列表:Structured Output。查库存、创建工单、读仓库文件:工具调用,细节见《数据流解析》和《JSON Schema 与 MCP 演进》。不要用 Structured Output 假装成「已经调过支付 API」——模型没有真的调。

落地校验与常见坑

约束解码不是业务正确性保证。建议固定两道闸:

  1. 语法与 Schema:json.loads 后用同一份 JSON Schema 再校验(必填、enum、minimum)。
  2. 业务不变量:例如 sum(item.qty * item.unit_price_cents) == total_cents。这层 Schema 表达不了,必须自己写。

常见坑:

  • 关键字不被支持:把完整 Draft 2020-12 一股脑塞进去,部分关键字会被忽略,表现像「Schema 没生效」。先从 type / properties / required / enum / items 做起,再加 additionalProperties、min/max。
  • 根类型用 array:部分路径更稳的是 { "items": [ ... ] } 这种对象根。
  • 把 Markdown 说明和 JSON 混在同一段输出:开了 JSON MIME 后不要再要求「先解释再输出 JSON」。
  • 超长输出截断:提高 maxOutputTokens,或把任务拆成「先列表、再逐条补全」。
  • 密钥进前端:结构化输出常被放进浏览器演示,API Key 会泄漏。Schema 可以公开,密钥只能在服务端。

开发阶段把 Schema 与 2~3 组正/反例存进仓库,用 JSON 工具箱本地看结构、做 Diff。这和测 REST 契约同一思路,消费者换成了 Gemini。

常见问题 FAQ

只设 application/json 和同时给 Schema 有什么区别?

只设 responseMimeType 为 application/json,模型会尽量输出合法 JSON,但字段名、类型、是否缺字段不受约束。加上 responseSchema 或 responseJsonSchema 后,解码阶段会按蓝图约束 token,形状才稳定,才能直接入库或喂给下一跳 Agent。

response_schema 和 response_json_schema 怎么选?

Python 里用 Pydantic 模型或 SDK Schema 时走 response_schema,SDK 可把结果放到 response.parsed。需要完整 JSON Schema(additionalProperties、min/max、prefixItems 等)或把 Pydantic/Zod 的 model_json_schema() 直接送进 API 时,用 response_json_schema。二者都要配合 response_mime_type=application/json。

结构化输出能替代 Function Calling 吗?

不能互相替代。结构化输出约束的是模型对用户可见的最终 JSON;Function Calling / Tool Calling 约束的是工具参数 JSON,还要由宿主真正执行工具。抽取、分类、填表用 Structured Output;查天气、写文件、调 MCP 用工具调用。一条 Agent 链路常常两者都用。

模型能保证 100% 符合 Schema 吗?

约束解码大幅降低语法错误和类型漂移,但仍可能出现语义幻觉(字段类型对、值是编的)、截断、或个别不受支持的 Schema 关键字被忽略。生产环境应把同一份 Schema 再跑一遍校验器,失败则重试或降级。

支持嵌套对象、数组和枚举吗?

支持。对象、数组、字符串枚举是最常用的组合。分类任务也可以把 MIME 设为 text/x.enum,让模型只输出枚举值而不是 JSON 对象。嵌套层级过深或循环引用仍可能被拒绝,应把 Schema 写扁一些。

如何在本地校验 Schema 与样例输出?

把 responseJsonSchema 和两三组模型输出样例存成 JSON 文件,用 JSON 工具箱在浏览器本地做语法校验与结构对照,数据不上传服务器。上线后再用同一份 Schema 做运行时校验。

总结

让 Gemini 生成结构化 JSON,正确顺序是:先定 Schema,再开 JSON MIME,最后才写提示词。提示词负责语义(抽什么);Schema 负责形状(字段长什么样)。Pydantic / Zod 是作者友好的前端,线上走 response_schema 或 response_json_schema。

建议你用一张真实发票或一段客服对话跑通:写出 Schema → 打一次 generateContent → 把 response.text 贴进校验器对照。对得上再接数据库或下一跳 Agent。工具参数那一侧仍然走 Function Calling / MCP,不要混成一种 API。