Nano Banana 2.1 未返回图片时的排查思路:先检查API响应再重试

发布时间:2026/10/15 19:04:08
Nano Banana 2.1 未返回图片时的排查思路:先检查API响应再重试 HTTP 请求成功不等于响应中包含图片。使用 Gemini 生成图片时图片会包含在候选项的 content.parts[].inlineData或 inline_data中也可能与文本部分一同返回。如果应用只读取 response.text即使响应里实际包含图片也可能报告“没有图片”。本指南适用于 Ofox 模型 IDgoogle/gemini-nano-banana-2.1。本文由 Ofox 发布文中所述测试使用的网关也由 Ofox 运营。以下说明会区分该网关在这次测试中返回的内容与 Google 通常记录的文档信息不会假设每次请求失败的原因都相同。首先确认响应中是否包含图片对于原生 Gemini 请求请检查每个候选项的各个部分查找包含图片 MIME 类型且data非空的inlineData对象。不要把文本字段为空或缺失当作生成失败的证据。决定下一步操作前也要记录 HTTP 状态码、候选项的finishReason和promptFeedback。 在调用 Nano Banana 2.1 的图像生成端点后应优先检查响应体中是否存在images字段及其长度而非直接假设渲染失败部分聚合网关如 OpenRouter 或 ofox.io会在转发层对响应结构进行二次封装导致字段路径与原始 API 文档不一致。以下是 2026 年 10 月 13 日 Ofox 网关一次成功测试的脱敏结构摘录不是图片字节的副本{ 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 的图片生成文档介绍了包含图片的响应部分其模型文档则介绍了该模型的能力。发起一次便于检查的请求在环境中设置OFOX_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: ${OFOX_API_KEY:?Set OFOX_API_KEY first} \ -H Content-Type: application/json \ --data-binary request.json \ https://api.ofox.io/gemini/v1beta/models/google/gemini-nano-banana-2.1:generateContentcurl打印的 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_statusNone, timed_outFalse): # 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, typePath, nargs?) parser.add_argument(--http-status, typeint) parser.add_argument(--timeout, actionstore_true) args parser.parse_args() if args.timeout: print(json.dumps(diagnose({}, args.http_status, timed_outTrue))) 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(encodingutf-8)) except (OSError, UnicodeError, json.JSONDecodeError): print(Cannot read a valid JSON response, filesys.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(encodingutf-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(fInspect unsupported image MIME type: {mime}) image base64.b64decode(data, validateTrue) saved 1 Path(fimage-{saved}{extension}).write_bytes(image) print(fSaved {saved} image(s))此示例明确支持 PNG 和 JPEG不会为每种 MIME 类型都猜一个文件名。如果解码失败请保留 HTTP 状态码和响应元数据但不要把 base64 内容贴到支持工单或公开问题中。如何判断没有图片的响应下一步操作取决于请求在哪个环节失败。Google 的 API 故障排查指南提供了通用错误处理建议下表将这些建议整理为适用于此次网关调用的实际排查顺序不会把每种错误都视为可重试错误。 当响应状态码为 200 但data数组为空时需区分模型拒绝生成与网关截断两种情况建议通过直连端点与经由 OpenRouter 或 ofox.io 的转发请求分别抓包对比以确定空响应发生在哪一层。观察到的情况优先检查建议操作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 日通过 Ofox 的原生 Gemini 路由发出的六次实际请求全部返回 HTTP 200、STOP和一张 1024 × 1024 的 PNG 图片其中三次使用 Nano Banana 2.1分别生成海报、编辑标题以及后续修改碗的颜色另外三次使用较旧的 Nano Banana 2执行相同的三种提示词。2.1 的输出在编辑过程中明显保留了所要求的英文文字和物体。这些是有参考价值的成功响应示例并不代表复现了故障、测出了可靠性也不是生产质量排名测试没有重复进行而且两个模型可能使用不同默认设置。2.1 与 2 的对比讨论了这项有限的对比。 单次成功的图像生成测试仅能证明当前参数组合在该时刻可通过模型审核并返回有效载荷并不能排除在高并发或经由 OpenRouter、ofox.io 等中间层路由时出现的概率性空响应问题。检查时公开的 Ofox Nano Banana 2.1 模型页面列出了该模型。该公开页面只能作为模型可用性的证据不能证明需要登录的 Playground 曾成功生成结果也没有测试经过身份验证的 Agent 中的模型选择。反过来在该路由尚未开放之前出现的历史 404也不能推翻后来观察到的 2.1 响应。请检查当前模型 ID 和你自己的访问权限不要拿一种观察结果替代另一种。排查问题时最后还要注意费用测试中生成的 1K 2.1 海报报告了 1,120 个图片输出 token。按观察到的目录费率——每百万个图片输出 token 收费 $30——仅该图片部分的费用将为 $0.0336这不是经过核实的实际账单总额输入、文本、思考及其他适用费用还可能增加总额。调用超时或应用未能解析响应都不能证明没有产生可计费的工作。先保存状态码和诊断字段再判断是否值得发起新请求。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询