Gemini 模型 JSON 输出截断排障指南:参数、协议、架构三层修复
【免费下载链接】generative-aiSample code and notebooks for Generative AI on Google Cloud, with Gemini Enterprise Agent Platform项目地址: https://gitcode.com/GitHub_Trending/ge/generative-ai
把 Gemini 模型返回的字符串丢给 json.loads,一个 JSONDecodeError 甩到脸上:JSON 在中间某个逗号处戛然而止。在 generative-ai 仓库里做 Gemini 结构化输出时,这种截半的 JSON 是最常踩的坑。这条排障路径分三步:先判断截断属于哪种表型,再按参数、协议、架构三层分别修复,最后过一遍上线前的加固清单。
先对号入座:三种 JSON 截断表型
动手改配置前,先看两处:响应文本的结尾,和 finish_reason 字段(SDK 用它解释模型为什么停笔)。
如果你看到的是输出在半条记录处停下,比如"price": 19后面没了,且 finish_reason 是 MAX_TOKENS——大概率是输出长度触顶。模型按"词元"(token,模型计量输出长度的最小单位)限制单次输出,大数组很容易超限。
如果你看到的是 JSON 本身完整,解析器却报 unexpected character——多半是模型在 JSON 外面裹了代码围栏,或补了一句"以上是查询结果"。这是自由文本模式的通病:你不约束格式,它就自由发挥。
如果你用函数调用(Function Calling,让模型按你声明的参数结构去"调函数")拿数据,而 function_call.args 少了字段或值被截断——大概率是参数体积超出生成能力,本质还是长度问题,只是换了个出口。
resp = client.models.generate_content(...) print(resp.candidates[0].finish_reason) # MAX_TOKENS = 长度触顶 print(resp.text[-80:]) # 确认结尾有没有闭合括号分层修复:参数、协议、架构各改什么
参数层:把输出上限抬到 8192
对应表型一。在请求里显式传 GenerateContentConfig,把 max_output_tokens 抬到所选模型的允许上限,temperature 压到 0 降低随机性:
from google import genai from google.genai.types import GenerateContentConfig client = genai.Client() resp = client.models.generate_content( model="gemini-2.5-flash", contents="生成 20 条产品记录,只输出 JSON 数组", config=GenerateContentConfig( max_output_tokens=8192, temperature=0, ), )⚠️ 局限:上限是模型写死的,8192 只是常见档位,数据体量再翻倍照样截断,治标不治本。
协议层:用 response_schema 锁死输出结构
对应表型二,也是多数固定业务结构的首选。Gemini 允许在请求里声明输出结构,模型只能按该结构产出 JSON,围栏和解释性文字从机制上消失。仓库的 控制生成示例 用的就是这条路:
from pydantic import BaseModel class Product(BaseModel): name: str price: float stock: int class ProductList(BaseModel): items: list[Product] resp = client.models.generate_content( model="gemini-2.5-flash", contents="生成 5 条产品记录", config=GenerateContentConfig( response_mime_type="application/json", response_schema=ProductList, # 可直接传 Pydantic / JSON Schema ), ) data = ProductList.model_validate_json(resp.text)等效的另一条路是强制函数调用:声明一个只接收 result 参数的函数,模式设为 ANY,逼模型按声明吐参数。forced_function_calling.ipynb 里有 ANY / AUTO / NONE 三种模式的完整对比。
⚠️ 局限:schema 约束结构不约束体量,单字段超长文本仍可能触顶;临时加字段要改代码重新发版。
架构层:大数组分片生成再拼装
对应"数据量本身大"的场景,比如几千条记录的数组。别指望一次生成完,切成每片 200~500 条逐片请求、客户端拼装:
import json # client 同前文 def generate_all(total=5000, chunk=500): items = [] for start in range(0, total, chunk): end = min(start + chunk, total) resp = client.models.generate_content( model="gemini-2.5-flash", contents=f"生成编号 {start} 至 {end - 1} 的产品记录," f"只返回 JSON 数组,禁止解释文字", config=GenerateContentConfig(max_output_tokens=8192), ) items.extend(json.loads(resp.text)) return {"total": len(items), "data": items}每片建议叠加协议层的 response_schema,拼装前逐片校验;某片失败只重跑该片,不用全部重来。
⚠️ 局限:请求数变成 N 片,延迟与成本同乘 N;分片前要先设计好编号或去重键,否则拼装时容易重复或丢数据。
上线前 checklist:校验、重试与降级
模型偶发抽风是常态,生产代码要把"解析失败"当正常分支处理:
- 解析前剥掉残留的代码围栏与首尾空白
- json.loads 包 try,失败后尝试补
}或]}二次解析 - 二次失败:用更小的分片重试一次,仍失败则落盘原始响应并走降级返回
- 解析前先读 finish_reason,MAX_TOKENS 直接跳过解析进入重试
- 监控解析失败率,超过 1% 告警,把它当提示词与模型回归的第一信号
兜底解析的最小版本:
import json def safe_parse(text: str): text = text.strip().removeprefix("```json").removesuffix("```").strip() try: return json.loads(text), None except json.JSONDecodeError as e: for tail in ("}", "]}"): # 补闭合,抢救差一个符号的半截输出 try: return json.loads(text + tail), f"repaired: {tail}" except json.JSONDecodeError: pass return None, str(e) # 交回调用方决定重试或降级补闭合符号只能救"差最后一个括号"的运气球,救不了值被截断的请求。它的定位是兜底,不是方案。
怎么选路径:场景对照与下一步
| 场景 | 推荐路径 | 关键参数 |
|---|---|---|
| 偶发截断,JSON 几 KB 量级 | 参数层抬上限 | max_output_tokens=8192, temperature=0 |
| 固定业务结构,字段类型明确 | 协议层 response_schema | response_mime_type="application/json" |
| 数据必须经函数调用回传 | 协议层强制函数调用 | mode=ANY, allowed_function_names |
| 千条以上大数组 | 架构层分片 + 每片 schema 校验 | chunk 200~500,逐片校验后拼装 |
延伸阅读按顺序来:先过一遍 function-calling 示例目录 建立手感,再看 intro_function_calling.ipynb 把基础流程跑通。
下一步:留一份线上截断的原始响应,按"先看 finish_reason、再看结尾有没有闭合括号"对出表型,只改对应那一层的配置。多数场景参数层加协议层的两行配置就覆盖了,剩下的才是大数组——那才轮到架构层。
【免费下载链接】generative-aiSample code and notebooks for Generative AI on Google Cloud, with Gemini Enterprise Agent Platform项目地址: https://gitcode.com/GitHub_Trending/ge/generative-ai
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考