Gemini API 怎么返回可反序列化 JSON
Gemini API 的结构化输出用 response_format 承载:type 固定为 text,mime_type 设为 application/json,schema 传 JSON Schema;三者齐备才进入结构化生成通道,取值仍需应用侧校验。(核验 2026-11-05,F11、F12、F17)
一、Gemini API 怎么返回可反序列化 JSON:问题不是格式,而是字段契约
工单系统接模型,第一版几乎都这么写:提示词末尾加一句「请以 JSON 格式输出」,然后拿返回的字符串做 json.loads。上线第三天开始报错,报错内容还各不相同——有时是开头多了「好的,以下是提取结果:」,有时是字段从 title 变成了 subject,有时是 priority 从数字变成了「高」。
写代码的人通常会做两件事:加正则把代码围栏剥掉,再加一层 if "title" not in data 的兜底。这两步都在处理症状,没碰到病根。
病根是:你把字段契约交给了模型的自由发挥。 提示词里的「JSON 格式」只是建议,模型可以给字段改名、加解释、把数字写成中文字符串,全都算「遵守了你的要求」。
Gemini API 把这件事从提示词层挪到了协议层:请求里有一个 response_format 字段,用三个键把格式说死——type、mime_type、schema(F11)。官方给的固定组合是 type: text + mime_type: application/json + schema(F12)。
注意 type 填的是 text,不是 json。这点反直觉,但它是官方写法;决定「输出是 JSON」的是 mime_type,不是 type。
二、Gemini API 结构化输出:最小可运行示例
真实调用要密钥,本机跑不了。但「schema 长什么样、返回的字符串怎么校验」完全可以离线练——下面这段只用标准库,把一次返回从字符串变成可信对象。
## EXPECT: 解析成功=是 | 缺失必填=['assignee'] | 类型不符=['priority'] | 可入库=False
import json
SCHEMA = {
"type": "object",
"properties": {
"ticket_id": {"type": "string"},
"title": {"type": "string"},
"priority": {"type": "integer"},
"assignee": {"type": "string"},
},
"required": ["ticket_id", "title", "priority", "assignee"],
}
RAW = '{"ticket_id": "TKT-A", "title": "导出报表超时", "priority": "高"}'
TYPES = {"string": str, "integer": int, "number": (int, float), "boolean": bool}
def missing_required(schema, data):
return [k for k in schema.get("required", []) if k not in data]
def wrong_type(schema, data):
bad = []
for key, rule in schema["properties"].items():
if key not in data:
continue
expect = TYPES[rule["type"]]
# bool 是 int 的子类,isinstance(True, int) 为 True;
# 不单独挡的话,priority: true 会被判成合格整数。
if rule["type"] == "integer" and isinstance(data[key], bool):
bad.append(key)
elif not isinstance(data[key], expect):
bad.append(key)
return bad
data = json.loads(RAW)
miss = missing_required(SCHEMA, data)
bad = wrong_type(SCHEMA, data)
print(f"解析成功=是 | 缺失必填={miss} | 类型不符={bad} | 可入库={not miss and not bad}")
assert miss == ["assignee"] and bad == ["priority"]
这段代码的价值不在校验器写得多好,而在它把两件事拆开了:能 json.loads 通过(语法合法),和字段齐全且类型对(值合格)。示例里 RAW 语法完全合法,但它缺 assignee、priority 还是中文字符串——两个问题都不是 JSON 解析器能发现的。
真实请求里,同一份 SCHEMA 直接传给 response_format 的 schema 键即可;用 Pydantic 的话,model_json_schema() 生成的就是这坨东西,model_validate_json() 干的就是上面 json.loads + 校验这两步。官方明确说 SDK 支持用 Pydantic(Python)和 Zod(JS)来定义 schema,但协议层认的始终是 JSON Schema 本身。
三、解释与边界:三个键各管什么
type 管输出形态。 目前结构化输出场景固定写 text(F12)。它不是「JSON 的类型」,而是「这一路输出是文本」;真正的类型约束来自 schema。
mime_type 管解析器。 写 application/json,服务端才会按 JSON 生成。这个键写错通常不会报错,只是你拿到的仍是普通文本——这种静默失效见 5.2。
schema 管字段。 它是一份 JSON Schema 子集,支持的类型是 string、number、integer、boolean、object、array、null 七种(F13)。想让某个字段允许为空,官方写法是把 null 并进类型数组,例如 {"type": ["string", "null"]},而不是另设一个 nullable 开关。
一个容易被忽略的事实:schema 只能约束形状,不能保证语义。官方在最佳实践里写得很直接——输出是「语法正确的 JSON」,但取值永远要应用侧校验(F17)。也就是说,priority 被约束成 integer 之后,模型仍可能给你 99,而你的业务只认 1 到 4。这一层没有协议能替你挡。
示例里那个 isinstance(data[key], bool) 特判值得多说一句:Python 里 bool 是 int 的子类,isinstance(True, int) 返回 True。少了这一行,priority: true 会被判成「合格的整数」——偏偏这个字段在工单场景里语义上最不该容错。
边界。 本节只解决「怎么把返回变成对象」。schema 写复杂了会被服务端拒绝、部分约束会被静默剥离,见 5.3;拿到了坏对象之后重试几次、怎么降级,见 5.4。本节也不比较 Pydantic 与 Zod 的写法差异——两者生成的 schema 都能用,选你项目语言顺手的那个。
四、预期输出与异常分支
真跑输出:
解析成功=是 | 缺失必填=['assignee'] | 类型不符=['priority'] | 可入库=False
四类分支,按发生频率排。返回里带解释文字:没有 response_format 时很常见,模型会在 JSON 前后各加一句话;这类文本不是官方承诺的行为,只是工程上反复出现的现象。有了 response_format 之后大概率消失,但解析器仍不该假设「第一个字符一定是 {」。字段名漂移:title 写成 subject,schema 里没这个键,模型只能自己发明;描述写清字段含义比缩写更重要。数字变字符串:"priority": "3" 这类最坑,json.loads 不报错,入库时才炸;这也是为什么类型校验必须放在解析之后、入库之前。嵌套数组缺字段:required 只对所在的那一层 object 生效,数组里每个元素自己的必填项要在元素的 schema 里单独写。漏了这层,外层校验全过,里层是空的。
五、判据卡:自由文本解析什么时候必须换
| 该这么做 | 不该这么做 |
|---|---|
字段要被代码直接读,就上 response_format |
用正则从自由文本里抠字段 |
| 先定义 schema,再写提示词 | 先写提示词,再从输出反推字段 |
| 解析后做必填与类型校验,再入库 | 认为 json.loads 通过就可信 |
允许为空就用 ["string", "null"] |
给字段加一个自定义的 nullable 键 |
六、来源与核验
https://aistudio.google.com/docs/structured-output.md(核验 2026-11-05,F11、F12、F13、F17)https://aistudio.google.com/docs/migrate-to-interactions.md(核验 2026-11-05,F78)
结构化输出的三字段属于「API 结构」档事实,复核周期三个月,见 1.3。若官方后续调整 type 的取值或 response_format 的位置,以结构化输出页与迁移页原文为准。
七、动手试
- 把
RAW里的priority改成3(整数),重跑。预期类型不符=[]、可入库=False(因为assignee还缺);验收标准是你能说清「为什么类型修好了仍不能入库」。 - 给
SCHEMA加一个tags字段,类型array,元素是string,并把它写进required。重跑。预期缺失必填里多出tags;验收标准是你能指出「数组元素的必填」与「数组本身的必填」分别写在哪一层。 - 打开你项目里解析模型输出的那段代码,数一下有几处
if "xxx" in data。验收标准:给出这些兜底分别对应哪个字段;超过三个,就说明字段契约目前靠猜,该换成 schema 了。