跳转到主要内容

Nano Banana API 不出图:按返回字段逐项排查

Nano Banana API 没拿到图,先看 finishReason 或 status:只回文字、NO_IMAGE、安全拦截、模型下线各有对应处理,拦截类原样重试无效。

LaoZhang AI Team发布于更新于 17 分钟阅读
文章目录
同一次 Nano Banana API 原始响应分成两种判断方式:generateContent 看 finishReason 与 blockReason,Interactions API 看 status 与 steps

Nano Banana 的 API 调用返回了 200,却没有图片,原因要到返回的 JSON 里找:用 generateContent 就看 promptFeedback.blockReason 和每个候选的 finishReason,用 Interactions API 就看 status 和 steps。只回了文字、finishReason 是 NO_IMAGE、被 IMAGE_SAFETY 一类规则拦下、模型 ID 已经下线,这几种情况的处理办法各不相同;其中拦截类结果,原样重试多少次都一样。下面的对照表和检查脚本对应截至 2026 年 9 月 24 日的 Gemini API 文档。

在 Gemini 网页版或 App 里直接生成图片、不写代码的情况,请看 Nano Banana 无法生成图片?Gemini 图片错误的 12 个修复分支。这里只处理 Gemini API、AI Studio、官方 SDK,以及基于 API 的客户端和工具。

先排除模型 ID 已下线

模型 ID 停止服务后,请求会直接报错,而不是“返回了但没图”。如果你的代码、教程或客户端预设是今年 6 月以前写的,先看一眼模型名:

名称模型 ID状态(截至 2026 年 9 月 24 日)
Nano Banana 2gemini-3.1-flash-image2026 年 5 月 28 日正式发布,未公布下线日期
Nano Banana Progemini-3-pro-image2026 年 5 月 28 日正式发布,未公布下线日期
Nano Banana 2 Litegemini-3.1-flash-lite-image2026 年 6 月 30 日正式发布
Nano Banana(原版)gemini-2.5-flash-image2026 年 10 月 2 日下线
已停止服务gemini-3.1-flash-image-preview、gemini-3-pro-image-preview2026 年 6 月 25 日下线,改用上面对应的正式版 ID
已停止服务gemini-2.5-flash-image-preview2026 年 1 月 15 日下线
已停止服务gemini-2.0-flash-preview-image-generation2025 年 11 月 14 日下线

来源:Google 的 Gemini API 弃用时间表 与 图片生成文档。

从 -preview 版迁到正式版,只需要换模型 ID,请求和解析代码不用改。原版 gemini-2.5-flash-image 在 2026 年 10 月 2 日下线,还在用的要尽快迁移:Google 在图片生成文档里建议换到 Nano Banana 2 Lite(gemini-3.1-flash-lite-image);弃用表给原版列的替代 ID 却是 gemini-3.1-flash-image-preview,这个 ID 本身已经停止服务,照抄会再失败一次。迁移时选哪个模型,见 Nano Banana 2、2.1、Pro 和 Lite 区别怎么选:2 已弃用。用 generateContent 时,响应里的 modelStatus 字段如果带有 retirementTime,就是当前模型的退役时间,可以顺手记进日志。

存下一次失败的原始响应

SDK 的便捷属性(Python 的 response.parts、Interactions 的 interaction.output_image)会替你挑内容,排查时要看完整的 JSON。Gemini API 现在有两套接口,结构不同,不能混着判断:

  • generateContent:图片在 candidates[].content.parts[] 的 inlineData 里,每个候选有自己的 finishReason;提示词被拦时,candidates 为空,原因写在 promptFeedback.blockReason。Google 已把它标为旧版接口,但仍然完整支持。
  • Interactions API:2026 年 6 月正式发布,Google 建议新项目使用。结果看顶层 status,图片是 steps[] 中 type 为 model_output 的那一步里、type 为 image 的内容块。

最直接的办法是用 curl 按原参数重发一次,把 HTTP 状态码和响应体分开保存:

bash
# generateContent
curl -s -X POST \
  "https://generativelanguage.googleapis.com/v1/models/gemini-3.1-flash-image:generateContent" \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contents": [{"parts": [{"text": "生成一张图片:雨后的老街,暖色路灯,电影感构图"}]}],
    "generationConfig": {"responseModalities": ["TEXT", "IMAGE"]}
  }' \
  -o resp.json -w "HTTP %{http_code}\n"

# Interactions API
curl -s -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
  -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemini-3.1-flash-image",
    "input": [{"type": "text", "text": "生成一张图片:雨后的老街,暖色路灯,电影感构图"}]
  }' \
  -o interaction.json -w "HTTP %{http_code}\n"

用 Python SDK 调 generateContent 的,可以把响应对象直接写成文件:open("resp.json", "w").write(response.model_dump_json(exclude_none=True)),导出的字段名是 finish_reason 这类下划线写法,后面的脚本两种写法都认。

HTTP 状态码不是 200 时,问题出在请求本身,不属于“没图”。429 是额度或速率限制,见 Nano Banana 2 限制与每日额度:按算力还是按张;Nano Banana 2 和 Pro 在价格表的免费层一栏都标为不可用。其他 4xx 先核对模型 ID 和参数。

按响应形态对照下一步

没出图时的判断流程:HTTP 报错先查模型 ID,已出图就修解析代码,被拦截要改内容且原样重试没用,只回文字或 NO_IMAGE 就明确要求出图

generateContent

你看到的含义下一步原样重试
candidates 为空,promptFeedback.blockReason 有值提示词在生成前就被拦下按 blockReason 改写提示词没用
finishReason: STOP,parts 里只有文字模型把请求当成了对话明确写出“生成一张图片”,确认请求了图片输出改了再发
NO_IMAGE按请求本该出图,但模型没生成同上,并把请求拆简单可以再试一次
IMAGE_SAFETY、IMAGE_PROHIBITED_CONTENT生成的图片触犯安全或禁止内容规则换题材或描述方式没用
SAFETY内容被安全过滤器标记,类别看 safetyRatings自己设过阈值就核对设置,否则改写内容没用
PROHIBITED_CONTENT、BLOCKLIST、SPII禁止内容、屏蔽词、敏感个人信息删掉相关内容没用
IMAGE_RECITATION、RECITATION输出与已有素材过于相似让描述更原创,少复刻具体作品改了再发
IMAGE_OTHER、OTHER文档只写“其他原因”保存响应,重试一次;再出现就改提示词最多一次
MAX_TOKENS触到输出 token 上限去掉或调高 maxOutputTokens改参数后再发
MISSING_THOUGHT_SIGNATURE多轮编辑时漏传了 thought signature修正多轮历史的传法改代码后再发
既没有 candidates 也没有 blockReason响应里没有给出原因记下 responseId,重试一次最多一次

枚举值的定义见 generateContent API 参考。finishReason 有值时,候选里还可能带一条 finishMessage,把它和 responseId 一起记进日志。枚举表以后还会增加新值,代码里遇到不认识的值要原样保留,不要一律当成“安全拦截”。

提示词级拦截里最难懂的是 blockReason: OTHER,它的排查见 Nano Banana 2 blockReason OTHER:官方只写「原因未知」,该查什么。多轮编辑中签名丢失的写法问题,见 Nano Banana 2 Thought Signature 错误:全场景修复指南(2026);用官方 SDK 的 chat 功能,或把上一轮的完整响应对象放回历史,签名会自动处理。

Interactions API

status含义下一步
completed,model_output 里有 image 块已出图程序仍说没图,就是解析代码的问题(见下一节)
completed,只有 text 块只回了文字同 generateContent 的“只有文字”
incomplete已结束但结果不完整,例如触到 max_tokens调高或去掉 generation_config.max_output_tokens
failed调用失败保存完整 interaction,看 steps 原文,不要循环重试
in_progress、queued还没做完(常见于后台运行)稍后按 id 取回再判断
requires_action在等你提交工具调用结果按工具调用流程补齐
cancelled已取消重新发起

状态定义见 Interactions API 参考。图片安全拦截在 Interactions API 里具体落在哪个字段,现有文档没有写明;遇到 failed 或只有文字时,以保存下来的 status 与 steps 原文为准,不要照搬 generateContent 的字段名去找。

只回文字:先确认请求了图片

Nano Banana 默认同时返回文字和图片,所以“先回一段话、没附图”并不说明代码坏了,更可能是模型没把这句话当成出图请求。两个接口限定输出的方式不同:

  • generateContent 用 generationConfig.responseModalities,官方示例写 ["TEXT", "IMAGE"],只要图可以写 ["IMAGE"]。API 参考说明这个列表要与响应的模态完全一致,传空列表等于只要文字,传了模型不支持的组合会直接报错。
  • Interactions API 用 response_format:写 {"type": "image"} 只返回图片,写成 [{"type": "text"}, {"type": "image"}] 就是图文都要。

这两项限定的是“可以返回什么”,并不保证每次都出图,同一个请求仍可能以 NO_IMAGE 或拦截结束。配合提示词一起改:把“生成一张图片”写在开头,说清楚要画什么,不要只写一个名词或只问“能不能画”。

简体中文(zh-CN)在 Google 列出的最佳效果语言里。有用户反映非英文提示词更容易只回文字,这属于个人经验,没有官方说明;想验证的话,把同一需求改成明确的出图指令再发一次,成本最低。另外,文档写明模型不一定严格按你要求的张数出图,要 4 张只来 1 张也属于正常结果。

被拦截:改提示词还是改安全设置

安全设置能管的范围很窄。按 安全设置文档,可调的只有四个类别:骚扰、仇恨言论、露骨色情、危险内容;对 Gemini 2.5 和 3 系列模型,这些过滤器在你不设置时默认就是 OFF。所以没设过阈值的人再加一遍 BLOCK_NONE,不会多出任何放行空间。

另外两条限制:

  • 危害儿童安全这类核心危害始终拦截,无法调整。IMAGE_SAFETY、IMAGE_PROHIBITED_CONTENT、PROHIBITED_CONTENT 都不属于那四个可调类别,文档里也没有任何能关掉它们的参数。
  • Interactions API 不支持自定义安全设置。

所以碰到 SAFETY,先看 safetyRatings 里标记的类别:如果那是你自己调严过的类别,放宽阈值会有效果;其余拦截只能改内容:把可能触及规则的元素从描述里拿掉,或者换一个题材。官方 SDK 会对超时、网络问题、429 和 5xx 自动重试(Python SDK 默认最多重试四次),拦截结果却是一份正常返回的响应,原因写在响应体里,不会触发这层重试;如果你自己写了重试循环,要把拦截类的 finishReason 排除在外。

IMAGE_RECITATION 表示生成结果与某些已有内容过于接近。Google 对 RECITATION 的建议是让提示词和上下文尽量独特;照这个思路,出图时少用“画一个和某部作品一模一样的角色”这类要求,改成描述你自己的设定。

有图却被代码漏掉

检查脚本报告“有最终图片”、你的程序却说没图,问题通常在解析方式。下面几种情况来自文档描述的响应结构,属于推断,排查时值得逐一对照:

  1. 只取第一个图片部分。 Gemini 3 系列图片模型默认开启思考,而且无法关闭;思考过程中最多生成两张中间图。在 generateContent 里打开 includeThoughts 后,这些中间内容会以 thought: true 的部分出现在 parts 里。只拿第一个 inlineData,拿到的可能是草稿;最终图应取 thought 不为 true 的图片部分。
  2. 只看便捷属性。 Interactions 的 interaction.output_image 只返回最后一个图片块。Nano Banana Pro 这类模型可以输出图文交错的内容(例如带插图的故事),这时便捷属性抓不全,要遍历 steps,在 model_output 步骤的 content 里逐块处理;thought 步骤的 summary 里也可能有图片,那是中间图。
  3. 按旧写法读字段。 官方文档现在的示例都基于 google-genai(from google import genai)和 @google/genai。沿用旧 SDK 或旧教程的字段路径,可能拿到空值而误判为没图。
  4. 没检查 MIME 类型。 图片部分可能是 image/png,也可能是 image/jpeg(Interactions 的 response_format 可以指定 mime_type)。写死扩展名不影响数据本身,但写死“只认 PNG”的判断会把 JPEG 当成没图。

用脚本判断一次响应

下面的脚本只用 Python 标准库,读入上一步保存的 JSON,自动识别是 generateContent 还是 Interactions 的结构,输出:属于哪种形态、最终图片几张、中间图几张、token 用量、按公开单价估算的费用和下一步动作。有最终图片时退出码为 0,没有为 1,无法识别为 2,方便接进 CI 或批处理脚本。

python
#!/usr/bin/env python3
"""检查一次 Nano Banana 调用的原始 JSON:有没有最终图片、属于哪种“没图”、下一步做什么。
用法:python3 nb_inspect.py resp.json [--save DIR]   只用标准库;退出码 0=有图 1=没图 2=无法识别"""
import base64, json, pathlib, sys

PRICE = {  # 美元 / 100 万 token:输入、文字与思考输出、图片输出(Gemini API 付费层 Standard)
    "gemini-3.1-flash-image": (0.50, 3.0, 60.0),
    "gemini-3-pro-image": (2.0, 12.0, 120.0),
}
POLICY = {"PROHIBITED_CONTENT", "BLOCKLIST", "SPII", "IMAGE_SAFETY", "IMAGE_PROHIBITED_CONTENT"}
ACTION = {
    "prompt_blocked": "提示词在生成前被拦截:看 blockReason,改写内容后再发,不要原样重试",
    "policy_block": "输出被图片安全或政策规则拦截:换题材或描述方式;原样重试没用,四个可调安全类别也不包括它",
    "safety_filter": "可调安全过滤器拦截(SAFETY):看 safetyRatings;若是你自己设的阈值,放宽它或改写内容",
    "no_image": "模型该出图却没出(NO_IMAGE):把“生成一张图片”写明,确认请求了图片模态,可再试一次",
    "text_only": "正常结束但只有文字:明确要求出图,并在请求里指定图片输出",
    "recitation": "输出与已有素材过于相似:去掉对具体作品、角色的复刻要求,描述写得更原创",
    "other": "原因未说明(OTHER / IMAGE_OTHER):保存原始响应,可重试一次;重复出现再改提示词",
    "max_tokens": "输出不完整,常见原因是触到输出上限:去掉或调高 maxOutputTokens / max_output_tokens",
    "thought_signature": "多轮编辑缺少 thought signature:把上一轮响应完整放回历史,或改用 SDK 的 chat",
    "not_finished": "interaction 还没结束:稍后按 id 取回结果再判断",
    "failed": "interaction 失败:保存完整 JSON,查看 steps 与错误信息,不要盲目循环重试",
    "empty": "没有候选也没有拦截原因:保存 responseId 与原始响应,重试一次,重复出现就改提示词",
    "http_error": "请求本身报错(不是“没图”):429 看额度,其他先核对模型 ID 与参数",
    "ok": "拿到了最终图片;如果你的程序仍说没图,问题在解析代码",
    "unknown": "未知的结束原因:保留原始值,按文档最新枚举核对",
}

def g(d, *keys):
    """同时兼容 REST 的 camelCase 和 SDK 导出的 snake_case。"""
    for k in keys:
        if isinstance(d, dict) and d.get(k) is not None:
            return d[k]
    return None

def by_modality(items):
    return {str(g(i, "modality")).upper(): g(i, "tokenCount", "token_count", "tokens") or 0
            for i in items or []}

def inspect_generate_content(r):
    finals, thoughts, texts, reasons = [], 0, 0, []
    for c in g(r, "candidates") or []:
        reasons.append(g(c, "finishReason", "finish_reason"))
        for p in g(g(c, "content") or {}, "parts") or []:
            blob = g(p, "inlineData", "inline_data")
            is_img = blob and str(g(blob, "mimeType", "mime_type")).startswith("image/")
            if is_img and g(p, "thought"):
                thoughts += 1
            elif is_img:
                finals.append(blob)
            elif g(p, "text") and not g(p, "thought"):
                texts += 1
    fb = g(r, "promptFeedback", "prompt_feedback") or {}
    block = g(fb, "blockReason", "block_reason")
    reason = next((x for x in reasons if x and x != "STOP"), reasons[0] if reasons else None)
    if finals:
        shape = "ok"
    elif block:
        shape = "prompt_blocked"
    elif not reasons:
        shape = "empty"
    elif reason in POLICY:
        shape = "policy_block"
    else:
        shape = {"STOP": "text_only" if texts else "empty", "SAFETY": "safety_filter", "NO_IMAGE": "no_image",
                 "RECITATION": "recitation", "IMAGE_RECITATION": "recitation",
                 "OTHER": "other", "IMAGE_OTHER": "other", "MAX_TOKENS": "max_tokens",
                 "MISSING_THOUGHT_SIGNATURE": "thought_signature"}.get(reason, "unknown")
    u = g(r, "usageMetadata", "usage_metadata") or {}
    out = by_modality(g(u, "candidatesTokensDetails", "candidates_tokens_details"))
    total_out = g(u, "candidatesTokenCount", "candidates_token_count") or 0
    usage = {"input": g(u, "promptTokenCount", "prompt_token_count") or 0,
             "thought": g(u, "thoughtsTokenCount", "thoughts_token_count") or 0,
             "image_out": out.get("IMAGE", 0)}
    usage["text_out"] = total_out - usage["image_out"]
    detail = {"finish_reason": reason, "block_reason": block, "text_parts": texts}
    return shape, finals, thoughts, usage, detail, g(r, "modelVersion", "model_version")

def inspect_interaction(r):
    finals, thoughts, texts = [], 0, 0
    for s in g(r, "steps") or []:
        blocks = g(s, "content") if g(s, "type") == "model_output" else g(s, "summary")
        for b in blocks or []:
            if g(b, "type") == "image" and g(s, "type") == "model_output":
                finals.append(b)
            elif g(b, "type") == "image":
                thoughts += 1
            elif g(b, "type") == "text" and g(s, "type") == "model_output":
                texts += 1
    status = g(r, "status")
    if finals:
        shape = "ok"
    elif status in ("in_progress", "queued", "requires_action"):
        shape = "not_finished"
    elif status in ("failed", "cancelled"):
        shape = "failed"
    elif status == "incomplete":
        shape = "max_tokens"
    elif status == "completed":
        shape = "text_only" if texts else "empty"
    else:
        shape = "unknown"
    u = g(r, "usage") or {}
    out = by_modality(g(u, "output_tokens_by_modality"))
    usage = {"input": g(u, "total_input_tokens") or 0, "thought": g(u, "total_thought_tokens") or 0,
             "image_out": out.get("IMAGE", 0), "text_out": out.get("TEXT", 0)}
    return shape, finals, thoughts, usage, {"status": status, "text_blocks": texts}, g(r, "model")

def estimate(model, usage):
    key = next((k for k in PRICE if model and str(model).split("/")[-1].startswith(k)), None)
    if not key:
        return None
    p_in, p_text, p_img = PRICE[key]
    usd = (usage["input"] * p_in + (usage["thought"] + usage["text_out"]) * p_text
           + usage["image_out"] * p_img) / 1e6
    return round(usd, 6)

def main(argv):
    if len(argv) < 2:
        print(__doc__)
        return 2
    r = json.loads(pathlib.Path(argv[1]).read_text(encoding="utf-8"))
    if isinstance(g(r, "error"), dict):
        e = r["error"]
        print(json.dumps({"api": "error", "shape": "http_error", "code": e.get("code"),
                          "status": e.get("status"), "next": ACTION["http_error"]},
                         ensure_ascii=False, indent=2))
        return 1
    if any(g(r, k) is not None for k in ("candidates", "promptFeedback", "prompt_feedback",
                                         "usageMetadata", "usage_metadata", "modelVersion")):
        api, res = "generateContent", inspect_generate_content(r)
    elif g(r, "steps") is not None or g(r, "status"):
        api, res = "interactions", inspect_interaction(r)
    else:
        print("无法识别:既不是 generateContent 响应,也不是 interaction")
        return 2
    shape, finals, thoughts, usage, detail, model = res
    if "--save" in argv and finals:
        d = pathlib.Path(argv[argv.index("--save") + 1])
        d.mkdir(parents=True, exist_ok=True)
        for i, blob in enumerate(finals, 1):
            ext = str(g(blob, "mimeType", "mime_type") or "image/png").split("/")[-1]
            (d / f"image_{i}.{ext}").write_bytes(base64.b64decode(g(blob, "data")))
    print(json.dumps({"api": api, "model": model, "shape": shape, "final_images": len(finals),
                      "thought_images": thoughts, **detail, "usage_tokens": usage,
                      "estimated_usd": estimate(model, usage), "next": ACTION[shape]},
                     ensure_ascii=False, indent=2))
    return 0 if finals else 1

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

用法:

bash
python3 nb_inspect.py resp.json                    # 只判断
python3 nb_inspect.py interaction.json --save out  # 有最终图片时存到 out/ 目录

对一份模拟 IMAGE_SAFETY 的测试 JSON(手写的示意数据:输入 12 token、思考 180 token、没有图片输出),输出如下:

json
{
  "api": "generateContent",
  "model": "gemini-3.1-flash-image",
  "shape": "policy_block",
  "final_images": 0,
  "thought_images": 0,
  "finish_reason": "IMAGE_SAFETY",
  "block_reason": null,
  "text_parts": 0,
  "usage_tokens": {
    "input": 12,
    "thought": 180,
    "image_out": 0,
    "text_out": 0
  },
  "estimated_usd": 0.000546,
  "next": "输出被图片安全或政策规则拦截:换题材或描述方式;原样重试没用,四个可调安全类别也不包括它"
}

这个脚本基于离线 fixture 测试,没有对 Google 的实时调用测试。测试用的是 23 份手写 JSON(fixture):generateContent 覆盖正常出图、思考中间图加最终图、只有文字、只有中间图、NO_IMAGE、IMAGE_SAFETY、IMAGE_PROHIBITED_CONTENT、SAFETY、IMAGE_RECITATION、IMAGE_OTHER、MAX_TOKENS、MISSING_THOUGHT_SIGNATURE、提示词被拦、没有候选、未知枚举值和 SDK 导出的下划线字段;Interactions 覆盖出图、只有文字、failed、incomplete、in_progress;另有 HTTP 错误体和无法识别的 JSON。形态判断、退出码、token 汇总、费用估算和 --save 存图全部通过(Python 3.12,5 组 unittest)。真实响应里如果出现文档之外的字段或新的枚举值,脚本会报 unknown,这时以原始 JSON 为准。

失败的调用计了多少费

Google 的价格表按 token 和模态计费,没有单独说明被拦截或没出图的响应怎么收费,既没写按整张图收,也没写免费。能确定的只有这次调用的用量字段记了什么:

  • generateContent 的 usageMetadata:promptTokenCount(输入)、thoughtsTokenCount(思考)、candidatesTokenCount(输出合计),candidatesTokensDetails 按模态拆出其中的文字和图片。
  • Interactions 的 usage:total_input_tokens、total_thought_tokens,output_tokens_by_modality 按模态拆分输出。

按 Gemini API 价格表(付费层 Standard,截至 2026 年 9 月 24 日)的单价:

模型输入文字与思考输出图片输出一张 1K 图
Nano Banana 2 gemini-3.1-flash-image$0.50 / 100 万 token$3 / 100 万 token$60 / 100 万 token1,120 token,约 $0.067
Nano Banana Pro gemini-3-pro-image$2 / 100 万 token$12 / 100 万 token$120 / 100 万 token约 $0.134(2K 同价)

估算公式:输入 token × 输入单价 +(思考 token + 文字输出 token)× 文字单价 + 图片输出 token × 图片单价。思考 token 不论是否返回思考内容都计费;文档另外写明思考过程中的中间图不收费。

generateContent 与 Interactions API 的用量字段对应到输入、思考、文字输出、图片输出四类 token,按 Nano Banana 2 单价估算一次没出图调用的费用

拿上面那份示意数据算:12 × $0.50 / 100 万 + 180 × $3 / 100 万 ≈ $0.000546。图片输出 token 为 0,这一次就不含一张图的 $0.067。这只是演示算法,数字来自手写的测试数据;你自己的失败调用要用它真实的 usageMetadata 或 usage 代入,最终金额以账单为准。如果失败响应里图片输出 token 不为 0,就按实际数字算,不要套用“拦截免费”或“拦截按整张收费”的说法。

客户端或工具里不显示图片

在第三方客户端里选了 Nano Banana 却只看到文字,按这个顺序排查:

  1. 模型 ID。 如果客户端预设里还是 gemini-2.5-flash-image-preview、gemini-3-pro-image-preview 这类已下线的 ID,改成上面表格里的正式版。
  2. 是否请求了图片、能否显示图片。 客户端要在请求里带上图片输出设置,还要能把 inlineData 或 image 内容块渲染出来;两者缺一,界面上都只剩文字。
  3. 用 curl 复现。 用同一个提示词、同一个模型 ID 按前面的命令发一次,再用脚本判断:curl 能出图,问题就在客户端;curl 也没图,按上面的对照表处理。

换一个中转平台或第三方接口,并不能解决拦截问题。只要对方最终调用的是 Google 的同一个模型,拦截规则就跟着模型走,IMAGE_SAFETY 不会因为换了入口而消失。

常见问题

Nano Banana 返回 200 却没有图片,是不是代码写错了? 不一定。拦截、只回文字、NO_IMAGE 都不是 HTTP 错误,而是正常返回的响应,原因写在 finishReason、promptFeedback.blockReason 或 interaction 的 status 里。先用脚本判断形态:报告“有最终图片”才是解析代码的问题。

把安全设置调成 BLOCK_NONE 能解决 IMAGE_SAFETY 吗? 不能。可调的四个安全类别对 Gemini 2.5 和 3 系列默认已是 OFF,IMAGE_SAFETY 不在其中,文档也没有关闭它的参数。只能改写内容;Interactions API 则不支持自定义安全设置。

被拦截的那次调用要付钱吗? Google 没有公布专门规则。看这次调用的 usageMetadata(generateContent)或 usage(Interactions):输入 token 按输入单价,思考和文字输出 token 按文字单价,图片输出 token 按图片单价,估算结果仍以账单为准。

三月能跑的代码现在报错,怎么回事? 大概率是模型 ID:gemini-3.1-flash-image-preview 和 gemini-3-pro-image-preview 已在 2026 年 6 月 25 日停止服务,换成 gemini-3.1-flash-image 或 gemini-3-pro-image 即可;原版 gemini-2.5-flash-image 在 2026 年 10 月 2 日下线。

官方文档

接入参数、价格与扣费规则以老张API开发文档为准:图片生成计费与失败处理。

说明:laozhang.ai 是我们自己的服务,其价格是平台报价,不是独立比较的结论。

参考来源6

本文引用的外部页面,按正文出现顺序排列。最后更新于 2026年9月24日。

  1. 1.Gemini API 弃用时间表ai.google.dev/gemini-api/docs/deprecations
  2. 2.图片生成文档ai.google.dev/gemini-api/docs/image-generation
  3. 3.generateContent API 参考ai.google.dev/api/generate-content
  4. 4.Interactions API 参考ai.google.dev/api/interactions
  5. 5.安全设置文档ai.google.dev/gemini-api/docs/safety-settings
  6. 6.Gemini API 价格表ai.google.dev/gemini-api/docs/pricing
Nano Banana 2 无法出图时检查入口、模型和完整返回的概念示意
故障排查

Nano Banana 2 无法使用?先检查模型、用量与 API 返回

Nano Banana 2 当前模型 ID 为 gemini-3.1-flash-image。生成失败先检查使用入口、用量和完整返回;HTTP 200 无图片不能直接判定安全拦截,旧的固定额度和恢复时间也不能作为当前保证。

24 分钟