Nano Banana 2.1 图片生成结果为空?排查响应内容再决定是否重试

简介: 本指南详解如何正确解析 Gemini(`gemini-nano-banana-2.1`)图像生成响应:HTTP 200 不代表含图;须检查 `candidates[].content.parts[].inlineData` 中 `image/*` 类型的非空 `data` 字段,而非仅读 `response.text`。附诊断脚本与提取工具,助你精准定位“无图”真因。

HTTP 请求成功,不等于响应中包含图片。使用 Gemini 生成图片时,图片会包含在候选项的 content.parts[].inlineData(或 inline_data)中,也可能与文本部分一同返回。如果应用只读取 response.text,即使响应里实际包含图片,也可能报告“没有图片”。

本指南适用于 模型 ID google/gemini-nano-banana-2.1。本文由 发布,文中所述测试使用的网关也由 运营。以下说明会区分该网关在这次测试中返回的内容与 Google 通常记录的文档信息;不会假设每次请求失败的原因都相同。

首先确认响应中是否包含图片

对于原生 Gemini 请求,请检查每个候选项的各个部分,查找包含图片 MIME 类型且 data 非空的 inlineData 对象。不要把文本字段为空或缺失,当作生成失败的证据。决定下一步操作前,也要记录 HTTP 状态码、候选项的 finishReason 和 promptFeedback。 在调用 Nano Banana 2.1 的图像生成端点后,应优先检查响应体中是否存在 images 字段及其长度,而非直接假设渲染失败;部分聚合网关(如 OpenRouter 或 )会在转发层对响应结构进行二次封装,导致字段路径与原始 API 文档不一致。

以下是 2026 年 10 月 13 日 网关一次成功测试的脱敏结构摘录,不是图片字节的副本:

{
 "candidates": [
 {
 "content": {
 "parts": [
 {
 "inlineData": {
 "mimeType": "image/png",
 "data": "[base64 image bytes omitted]"
 }
 }
 ]
 },
 "finishReason": "STOP"
 }
 ]
}

此次测试的请求返回 HTTP 200 和一张 1024 × 1024 的 PNG 图片。摘录省略了响应中的其他内容;测试记录的用量包括 54 个输入 token、用量元数据报告的 80 个文本输出 token、1,120 个图片输出 token,以及 639 个思考 token。这些数字描述的是这次请求,并不代表响应必须采用这种结构,也不代表固定费用。Google 的图片生成文档介绍了包含图片的响应部分;其模型文档则介绍了该模型的能力。

发起一次便于检查的请求

在环境中设置 API_KEY,然后发送测试中使用的完整海报提示词。模型 ID 很重要:google/gemini-nano-banana-2.1 不是旧版 google/gemini-3.1-flash-image,也不是“Lite”变体。

cat > request.json <<'JSON'
{
 "contents": [
 {
 "role": "user",
 "parts": [
 {
 "text": "Create a square minimalist event poster on warm cream paper. Large exact headline: ORCHARD MARKET. Smaller exact line: SATURDAY 10 AM. Draw three red apples in a blue ceramic bowl below the text. No other text, no logos, no border."
 }
 ]
 }
 ],
 "generationConfig": {
 "responseModalities": ["TEXT", "IMAGE"],
 "imageConfig": {
 "aspectRatio": "1:1",
 "imageSize": "1K"
 }
 }
}
JSON

curl --silent --show-error \
 --output response.json \
 --write-out 'HTTP %{http_code}\n' \
 -H "x-goog-api-key: ${API_KEY:?Set API_KEY first}" \
 -H 'Content-Type: application/json' \
 --data-binary @request.json \
 '

curl 打印的 HTTP 状态码很重要:请将它与响应一并保存,不要因为存在 response.json 就认定调用成功。请勿将包含机密提示词或图片数据的请求、响应文件放入公开日志或代码仓库。如需了解此示例以外的端点设置,请参阅 Nano Banana 2.1 API 指南。

更改提示词前,先运行离线诊断

将以下内容保存为 diagnose_gemini_image.py。它会读取已保存的 JSON 响应,不会调用 API。使用 --http-status 参数传入客户端记录的 HTTP 状态码。如果客户端超时,且没有可用响应,则改用 --timeout。输出内容有意限制为诊断字段:它不会打印密钥、原始错误消息、提示词或 base64 图片数据。

#!/usr/bin/env python3
import argparse
import json
import sys
from pathlib import Path

def field(obj, camel, snake):
 if not isinstance(obj, dict):
 return None
 return obj.get(camel, obj.get(snake))

def safe_feedback(obj):
 if not isinstance(obj, dict):
 return None
 ratings = field(obj, "safetyRatings", "safety_ratings")
 result = {"blockReason": field(obj, "blockReason", "block_reason")}
 if isinstance(ratings, list):
 result["safetyRatings"] = [
 {
 "category": r.get("category"),
 "probability": r.get("probability"),
 }
 for r in ratings if isinstance(r, dict)
 ]
 return result

def diagnose(body, http_status=None, timed_out=False):
 # Accept either a native response or {"body": <native response>}.
 if isinstance(body, dict) and isinstance(body.get("body"), dict):
 if http_status is None:
 http_status = body.get("status_code", body.get("httpStatus"))
 body = body["body"]

 result = {
 "status": "no_image",
 "httpStatus": http_status,
 "imagesCount": 0,
 "finishReasons": [],
 "promptFeedback": safe_feedback(
 field(body, "promptFeedback", "prompt_feedback")
 ),
 }

 if timed_out:
 result["status"] = "transport_uncertain"
 return result

 if http_status is not None and not 200 <= http_status < 300:
 result["status"] = (
 "http_error_check_retryability"
 if http_status == 429 or 500 <= http_status <= 599
 else "http_error"
 )
 return result

 candidates = body.get("candidates", []) if isinstance(body, dict) else []
 if not isinstance(candidates, list):
 candidates = []

 for candidate in candidates:
 if not isinstance(candidate, dict):
 continue
 reason = field(candidate, "finishReason", "finish_reason")
 if reason is not None:
 result["finishReasons"].append(reason)
 content = candidate.get("content")
 parts = content.get("parts", []) if isinstance(content, dict) else []
 if not isinstance(parts, list):
 continue
 for part in parts:
 inline = field(part, "inlineData", "inline_data")
 if not isinstance(inline, dict):
 continue
 mime = field(inline, "mimeType", "mime_type")
 if (isinstance(mime, str) and mime.startswith("image/")
 and isinstance(inline.get("data"), str)
 and inline["data"]):
 result["imagesCount"] += 1

 if result["imagesCount"]:
 result["status"] = "image_present"
 elif (result["promptFeedback"] or {}).get("blockReason"):
 result["status"] = "blocked"
 elif any(
 r in {"SAFETY", "PROHIBITED_CONTENT", "SPII", "RECITATION"}
 for r in result["finishReasons"]
 ):
 result["status"] = "blocked"
 elif "MAX_TOKENS" in result["finishReasons"]:
 result["status"] = "max_tokens_no_image"
 return result

def main:
 parser = argparse.ArgumentParser
 parser.add_argument("response_json", type=Path, nargs="?")
 parser.add_argument("--http-status", type=int)
 parser.add_argument("--timeout", action="store_true")
 args = parser.parse_args
 if args.timeout:
 print(json.dumps(diagnose({}, args.http_status, timed_out=True)))
 return 0
 if args.response_json is None:
 parser.error("Provide response_json unless using --timeout")
 try:
 body = json.loads(args.response_json.read_text(encoding="utf-8"))
 except (OSError, UnicodeError, json.JSONDecodeError):
 print("Cannot read a valid JSON response", file=sys.stderr)
 return 2
 print(json.dumps(
 diagnose(body, args.http_status, args.timeout),
 separators=(",", ":")
 ))
 return 0

if __name__ == "__main__":
 sys.exit(main)

例如,运行 python3 diagnose_gemini_image.py response.json --http-status 200,即可查看是否找到了图片部分。imagesCount 统计的是声明存在且内容非空的图片部分;它不会解码数据,也不能确认图片文件有效。如果提取失败,请保留原始响应,以便在本地进一步检查。

你可以在不消耗 API 额度的情况下检查诊断脚本的分支逻辑。以下是人工构造的测试数据,不是对模型的模拟测试:

printf '%s\n' \
 '{"candidates":[{"content":{"parts":[{"inline_data":{"mime_type":"image/png","data":"aGVsbG8="}}]},"finish_reason":"STOP"}]}' \
 > fixture-image.json
python3 diagnose_gemini_image.py fixture-image.json --http-status 200
# {"status":"image_present","httpStatus":200,"imagesCount":1,"finishReasons":["STOP"],"promptFeedback":null}

printf '%s\n' \
 '{"candidates":[{"finishReason":"MAX_TOKENS","content":{"parts":[]}}]}' \
 > fixture-limit.json
python3 diagnose_gemini_image.py fixture-limit.json --http-status 200
# {"status":"max_tokens_no_image","httpStatus":200,"imagesCount":0,"finishReasons":["MAX_TOKENS"],"promptFeedback":null}

测试数据中的 aGVsbG8= 只是非空的 base64 文本,不是 PNG。这一区别很重要:检测到图片部分只是第一步;接下来还要解码并验证文件。

如果诊断结果发现了图片,请提取它,而不是重试生成。这个小型本地提取脚本会保存所有图片部分,并且不会打印图片内容:

import base64
import json
from pathlib import Path

response = json.loads(Path("response.json").read_text(encoding="utf-8"))
saved = 0

for candidate in response.get("candidates", []):
 for part in candidate.get("content", {}).get("parts", []):
 inline = part.get("inlineData", part.get("inline_data", {}))
 mime = inline.get("mimeType", inline.get("mime_type", ""))
 data = inline.get("data")
 if not (mime.startswith("image/") and data):
 continue
 extension = {"image/png": ".png", "image/jpeg": ".jpg"}.get(mime)
 if extension is None:
 raise ValueError(f"Inspect unsupported image MIME type: {mime}")
 image = base64.b64decode(data, validate=True)
 saved += 1
 Path(f"image-{saved}{extension}").write_bytes(image)

print(f"Saved {saved} image(s)")

此示例明确支持 PNG 和 JPEG,不会为每种 MIME 类型都猜一个文件名。如果解码失败,请保留 HTTP 状态码和响应元数据,但不要把 base64 内容贴到支持工单或公开问题中。

如何判断没有图片的响应

下一步操作取决于请求在哪个环节失败。Google 的 API 故障排查指南提供了通用错误处理建议;下表将这些建议整理为适用于此次网关调用的实际排查顺序,不会把每种错误都视为可重试错误。 当响应状态码为 200 但 data 数组为空时,需区分模型拒绝生成与网关截断两种情况,建议直连端点与经由 OpenRouter 或 的转发请求分别抓包对比,以确定空响应发生在哪一层。

观察到的情况 优先检查 建议操作
400 请求 JSON、responseModalities、imageConfig 和字段名称 修正无效参数或请求内容。原样重复相同请求很可能无济于事。
401 网关密钥是否已传入 x-goog-api-key,以及密钥是否有效 修复身份验证问题,同时避免在日志中暴露密钥。
403 账户或模型权限,以及返回的错误分类 解决访问权限或政策限制;不要把它归类为暂时性图片生成失败。
404 准确的端点和模型 ID 确认路径使用 google/gemini-nano-banana-2.1。过去某个日期针对其他 ID 的 404 报告,不能证明该模型目前不可用。
429 错误信息和账户状态中显示的是速率限制,还是额度/配额问题 只有属于可重试的速率限制时才退避重试。额度耗尽或需要处理的账户限制,不会因增加重试次数而解决。
5xx 网关/服务器状态,以及是否存在请求记录或处理结果 可以考虑有限次数的重试,但首先要判断请求是否可能已经处理。
客户端超时或连接中断 本地请求 ID、可用时的网关请求记录,以及是否保存了响应 结果尚不确定。不能仅仅因为客户端没有收到响应,就认定“没有生成图片”或“没有计费”。

如果状态码为 200,但 finishReason 为 STOP 且没有图片部分,应检查所有候选项和各部分、请求的模态,以及保存的 JSON。如果状态码为 200,但 finishReason 为 MAX_TOKENS 且没有图片,则是另一种线索:输出可能在图片送达前就已结束。Google 开发者论坛上的一则用户报告描述了这一现象;这是一则未经验证的用户报告,并非我们在这里的六次成功请求中复现的故障。报告的 token 数低于配置上限,本身并不能证明模型存在 bug;在得出结论前,需要核查各项限制、响应计量方式,以及相关计数器的具体含义。

如果 promptFeedback 显示拦截原因,或候选项因安全相关原因结束,请将其视为政策判定。请检查请求是否符合适用的 Gemini API 条款;不要用重试或改写提示词的方式规避限制。如需了解设置细节,请参阅 API 指南。

谨慎重试,并在后续轮次中保留上下文

不要把所有未返回图片的结果都放进自动重试循环。先解决 400、401、403 和 404 的原因。对于已确认可重试的 429 或 5xx,应将尝试次数限制在较小范围内,逐次增加延迟并加入抖动,同时遵循客户端所支持的服务器重试指引。发生超时后,如果可能,应先检查请求记录再重新提交:响应丢失会让生成和计费状态都变得不确定。定价指南解释了为什么额外生成一张图片并非免费,即使第一张并非你想要的结果。

在编辑场景中,“重试”和“后续轮次”也是两种不同操作。后续轮次必须包含先前的对话内容,包括模型返回的内容部分。不要只根据可见文本重建上一条模型消息:这样可能会丢失图片和思考签名。经过测试的三步链路,在每次新的用户轮次之前,都原样传入了此前的 candidates[0].content。后续提示词如下:

Change only the headline from ORCHARD MARKET to ORCHARD FAIR. Keep SATURDAY 10 AM exactly unchanged. Preserve the three red apples, blue bowl, cream background, composition and all other pixels as closely as possible.

Now change only the bowl from blue to dark green. Preserve ORCHARD FAIR, SATURDAY 10 AM, the three red apples and the layout.

在 Python 中,关键的历史记录操作如下:

# previous_response is the complete parsed response from the preceding turn.
model_content = previous_response["candidates"][0]["content"]
contents.append(model_content) # Keep its parts and signatures intact.
contents.append({
 "role": "user",
 "parts": [{"text": next_prompt}]
})

添加下一轮对话前,请先保存更新后的响应。如果先前的响应遭到拦截、不完整,或缺少编辑所需的内容,就不要把它说成等同于正常的对话延续。有关编辑预期的说明,包括“保留”能保证和不能保证的内容,请参阅海报编辑操作指南和较早的产品保留指南。多参考图工作流的取舍另见单张还是两张产品参考图。

成功测试说明了什么,又没有说明什么

2026 年 10 月 13 日,的原生 Gemini 路由发出的六次实际请求,全部返回 HTTP 200、STOP 和一张 1024 × 1024 的 PNG 图片:其中三次使用 Nano Banana 2.1,分别生成海报、编辑标题,以及后续修改碗的颜色;另外三次使用较旧的 Nano Banana 2,执行相同的三种提示词。2.1 的输出在编辑过程中明显保留了所要求的英文文字和物体。这些是有参考价值的成功响应示例,并不代表复现了故障、测出了可靠性,也不是生产质量排名;测试没有重复进行,而且两个模型可能使用不同默认设置。2.1 与 2 的对比讨论了这项有限的对比。 单次成功的图像生成测试仅能证明当前参数组合在该时刻可模型审核并返回有效载荷,并不能排除在高并发或经由 OpenRouter、 等中间层路由时出现的概率性空响应问题。

检查时,公开的 Nano Banana 2.1 模型页面列出了该模型。该公开页面只能作为模型可用性的证据,不能证明需要登录的 Playground 曾成功生成结果;也没有测试经过身份验证的 Agent 中的模型选择。反过来,在该路由尚未开放之前出现的历史 404,也不能推翻后来观察到的 2.1 响应。请检查当前模型 ID 和你自己的访问权限,不要拿一种观察结果替代另一种。

排查问题时,最后还要注意费用:测试中生成的 1K 2.1 海报报告了 1,120 个图片输出 token。按观察到的目录费率——每百万个图片输出 token 收费 $30——仅该图片部分的费用将为 $0.0336,这不是经过核实的实际账单总额;输入、文本、思考及其他适用费用还可能增加总额。调用超时,或应用未能解析响应,都不能证明没有产生可计费的工作。先保存状态码和诊断字段,再判断是否值得发起新请求。


参考来源

相关文章
|
23天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
9365 30
|
9天前
|
存储 人工智能 并行计算
大模型本地部署终端选型方法论:以 Qwen3.8-27B 为例的四档分层完整流程
本文提出一套大模型本地部署终端选型方法论:定约束、定档位、定框架、定参数四步决策法,配合入门、主力、质量、无损四档分层模型。以 Qwen3.8-27B 实测数据为例,逐环节解读显存、带宽、存储、散热、系统、预算等要素,给出面向不同预算的优选方案、决策自查清单与市场观察框架。文末前瞻 AI 笔记本的 CPU+GPU 与统一内存两条路线,论证四步决策法在新品类上的延续性。
|
8天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型深度拆解:原理解读、实战测评与保姆级落地教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为**System One Model(系统一模型)**,对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
638 1
|
21天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
2478 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
4天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型全解析:核心原理拆解,性能实战测评与落地部署教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为System One Model(系统一模型),对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
413 0
|
3天前
|
存储 缓存 测试技术
QWT 3D 模块重构记:旧渲染管线改进介绍及实时瀑布图示例
QWT 3D模块完成重大重构:解耦窗口与图形(Plot+Item),摒弃OpenGL 1.x固定管线,迁至Core Profile着色器渲染;支持多图元同场景叠加,并以实时3D瀑布图验证——滑动窗口、固定色域、64帧×256点达61fps。兼容Qt 5.12+/6,LGPL协议。
157 120
|
15天前
|
Windows
【保姆级】.NET Framework 3.5离线安装教程(2026最新实测)
.NET Framework 3.5是微软经典运行库,专为兼容老软件、老游戏设计。Win7自带,Win10/11需手动启用(在线或离线安装)。它与4.x系列并存不冲突,缺它会导致“找不到组件”报错。本文详解两种安装方式及常见问题解决,小白也能轻松上手。(239字)
1874 0
|
10天前
|
人工智能 Linux Windows
千问办公(QwenWork)官网入口:其实有2个,一个是网页端千问办公,一个是介绍指南页面
千问办公(QwenWork)是阿里云推出的AI智能办公平台,支持网页端直接使用及Windows/Mac/Linux客户端下载。提供PPT生成、财报分析、网页搭建等AI功能,个人版免费,企业版198元/席/月。详情见官网qwenwork.cn或阿里云产品页。
1194 0
千问办公(QwenWork)官网入口:其实有2个,一个是网页端千问办公,一个是介绍指南页面