Gemini 生图接口报错:图片生成失败的排查与修复

生图接口的报错分四类,先归位再修

一类报错:参数没配对,请求直接被拒

高频出现的原文是这一条:

{
  "error": {
    "code": 400,
    "message": "Image generation is not supported for this model. Missing response modalities configuration.",
    "status": "INVALID_ARGUMENT"
  }
}

指向很明确:请求里没有声明要图片输出,或者用的模型根本不产出图片。这类问题一改参数就好,跟网络、key、余额都没关系。

二类报错:模型名写错,接口找不到

{"error": {"code": 404, "message": "models/gemini-2.5-flash is not found for API version v1beta"}}

把纯文本模型的 id 拿来做生图,会走到这个结果。生图要用生图模型,模型名以平台控制台的模型列表为准,别凭印象拼。

三类报错:图片输入格式不对

带参考图做图生图时,报错通常长这样:

{"error": {"code": 400, "message": "Invalid value at 'contents[0].parts[0].inline_data.data'", "status": "INVALID_ARGUMENT"}}

多半是 base64 字符串里混进了 data:image/png;base64, 这段前缀,或者字符被截断。还有一类是返回体正常但图片没解析出来,下面单独讲。

参数怎么配才出图

要显式声明输出模态

原生接口里必须写清要文本还是图片,两个都要就都写上:

{
  "contents": [{"parts": [{"text": "一只坐在窗台上的橘猫,水彩风格"}]}],
  "generationConfig": {"responseModalities": ["TEXT", "IMAGE"]}
}

只写 ["TEXT"] 会得到一段文字描述,不写这一项有些客户端会直接报参数错误。

走 OpenAI 兼容入口的写法

import os
from openai import OpenAI

client = OpenAI(api_key=os.environ["XYU_API_KEY"], base_url="https://xyuapi.top/v1")

resp = client.chat.completions.create(
    model="nanobanana-pro",              # 生图模型,具体 id 以控制台模型列表为准
    messages=[{"role": "user", "content": "一只坐在窗台上的橘猫,水彩风格"}],
)

msg = resp.choices[0].message
print(msg.content[:200] if msg.content else "无文本内容")

兼容入口在不同客户端下返回图片的形式不完全一样:有的把 base64 塞进 content 的 markdown 图片语法里,有的放在扩展字段里。解析前先把整段结构打出来看。

模型选对,价格差别很大

文本模型和生图模型是两套计费。生图按张算,NanoBanana-Pro 是 0.27 元/次;同样是调用一次,用错模型不是花钱多的问题,而是根本拿不到图。

价格上也能看出分量:NanoBanana-Pro 出图是 0.27 元/次,一张图的价钱接近 gemini-2.5-pro 文本调用的九倍,所以出图任务在参数验证上多花几分钟,比批量跑错再重来划算得多。

同一个生图模型在不同客户端里的表现也会有差异。有的客户端会自动帮你加上「生成一张图片」之类的引导语,有的把原始描述原样发出去。如果两个客户端结果差得明显,先把实际发出的请求体拿出来对比,问题多半就在这里。

图片输入的四个格式坑

坑一:把 data URL 前缀一起传进去

data:image/png;base64,iVBOR... 这一整串是给浏览器看的。提交给接口时要只留纯 base64 部分:

import base64, pathlib

raw = pathlib.Path("input.png").read_bytes()
b64 = base64.b64encode(raw).decode("ascii")   # 纯 base64,无前缀、无换行
print("字节数:", len(raw), "base64 长度:", len(b64))

坑二:MIME 类型写错

image/jpg 不是标准写法,应该用 image/jpeg;PNG 是 image/png;WebP 是 image/webp。类型和实际内容不一致时,有的接口直接报参数错误,有的会返回一段「无法读取图片」的文字。

坑三:base64 里带了换行

某些编码工具会按 76 字符折行,折行后的字符串提交上去会被判非法。上传前把 \n\r 去掉,这一步用正则处理干净。

坑四:请求体太大

参考图太大时,整个请求体会撞上服务端的大小限制,报错关键字通常是 Request payload size exceeds the limit。做法是先压缩:长边压到 1568 像素以内、转成 WebP 或中等质量 JPEG,体积能降到原来的十分之一,肉眼几乎看不出差别。

返回体里的图片藏在哪

逐层遍历 parts 才找得到

图片不是放在一个固定字段里的,要遍历:

import base64, pathlib

def save_images(resp_json, prefix: str = "out") -> list:
    saved = []
    for cand in resp_json.get("candidates", []):
        for part in cand.get("content", {}).get("parts", []):
            inline = part.get("inlineData") or part.get("inline_data")
            if not inline:
                continue
            data = base64.b64decode(inline["data"])
            ext = (inline.get("mimeType") or "image/png").split("/")[-1]
            p = pathlib.Path(f"{prefix}_{len(saved)}.{ext}")
            p.write_bytes(data)
            saved.append(str(p))
    return saved

注意兼容两种字段命名:驼峰和蛇形在不同 SDK 里都可能出现,两种都判一下,省得换 SDK 时踩坑。

如果遍历完一张都没找到,就把完整响应写进日志文件再看。有的兼容层会把图片放在自定义字段里,或者直接给一个图片地址,字段名各家不同,看一眼原始结构比反复猜要快。

只拿到文字不代表失败

模型有时选择用文字回答,比如描述它想画什么、或者说明为什么不能画。先看文本内容写了什么,再判断是不是被策略拦。

拿到图片要顺手校验

写盘后看一眼文件大小和开头字节:PNG 的前八个字节是固定魔数,JPEG 以 FF D8 开头。文件只有几百字节,基本可以确定是解析错了或者返回的不是图片。

校验还能再细一步:把图片的长宽读出来,确认与请求里写明的比例一致。比例对不上说明参数没生效,多半是传参层级放错了位置。这类问题不会报错,但结果不对,比报错更难发现。

保存路径带上时间戳和随机后缀,批量出图时不会互相覆盖。文件名里再带上任务编号,出问题时顺着文件名就能把整个任务的结果一次性翻出来。

图片被策略拦截怎么办

IMAGE_SAFETY 的返回长这样

{
  "candidates": [
    {"finishReason": "IMAGE_SAFETY", "content": {"parts": []}}
  ]
}

finishReasonIMAGE_SAFETYparts 是空的。看到这个值,改参数、重试、换通道都不会有用,要改的是描述内容。

涉及真实人物形象的描述基本会被拦

图片模型对人物相关内容的策略比文本更严,凡是可能被当成真实人物形象的描述都容易触发拦截。画插画、场景、产品、风格化的人物形象通常没有问题,以实际业务需要为准,把描述写清楚就好。

合规边界要当成前提

对违反法律法规和上游策略的内容,平台不予处理,也不会提供规避手段。把人工复核环节做进流程,让个别被拦的图有兜底路径,业务就不会中断。

超时与限流:生图比文本更耗时

生图一次要十几秒到几十秒

图片生成的计算量放在那里,非流式调用下客户端会一直静默等待。默认 60 秒的超时对生图偏紧,建议至少给 180 秒,稳定一点的做法是给 300 秒。

批量生图要压并发

一批几十张图同时发出去,很容易撞上 429。把并发压到三到五,并在每张之间留一点间隔,比事后重试省事。

场景建议并发建议超时单张价格参考
单张调试1180 秒NanoBanana-Pro 0.27 元/次
小批量出图3300 秒NanoBanana-Pro 0.27 元/次
大批量任务3 到 5300 秒按张累计,先跑样本估总量

重试就是重花钱

生图按张计费,重试一次就多花一次的钱。被策略拦的图重发也是白花,先把描述改好再发,是省钱的做法。

客户端显示不出图片

只在前端渲染失败的三种原因

  1. 客户端只渲染文本,拿到了 base64 却没有把它当图片处理。
  2. 返回的是 markdown 图片语法,客户端没有开启 markdown 渲染。
  3. 图片以扩展字段返回,客户端根本没读这个字段。

这三类都是展示层的问题,日志里能看到完整的返回内容,接口本身是成功的。

自己写前端时怎么处理

从返回内容里提取 base64,拼成 data URL 再交给图片组件:

import re

def to_data_url(text: str) -> str | None:
    m = re.search(r"data:image/(png|jpeg|webp);base64,([A-Za-z0-9+/=]+)", text or "")
    if m:
        return m.group(0)
    return None

拿到 None 说明返回里没有内嵌图片,要去检查扩展字段。存图比内嵌更稳:先落盘成文件,前端只读文件地址,页面刷新不会丢。

图片要存到后端而不是只放浏览器

生成好的图如果只存在浏览器内存里,换个设备打开就没了。把图片写到服务端并返回一个地址,前端只引用地址,才算真正留住了结果。

尺寸、比例与文字渲染

比例参数怎么传

原生接口通过 imageConfig 指定比例和尺寸,比如 "aspectRatio": "16:9""imageSize": "2K"。常见的比例有 1:1、3:4、4:3、16:9、9:16,选哪个取决于用途:封面用 16:9,头像用 1:1,竖版海报用 9:16。

尺寸越大,耗时越长

同样的描述,出 4K 图的时间明显长于 1K,批量任务先按 1K 跑通流程再提尺寸,能省下不少耐心。

图上文字容易写错

生图模型写中文长句容易出错字。需要带文字的图,做法是让模型只出画面,文字用后期合成的方式加上去,成品质量更可控。

还有一种情况经常被误判成接口问题:图确实生成了,但和描述明显不符,比如主体不对、数量不对。这通常不是接口出错,而是描述里元素太多。一张图里塞五个主体,模型会自行取舍。把描述收窄成一两个主体加固定风格,命中率会明显提升。

描述里给风格词和镜头感,比堆形容词有用。水彩、扁平插画、产品摄影、微距这类具体的词,比「好看」「高级」这类模糊的词有效得多,也更容易复现同样的效果。

生图任务建议先跑一张确认参数组合正确,再放批量。参数跑错会按张数累计费用,改完再重跑就是双倍支出。

稳定接入与价格参考

接口信息

主入口 https://xyuapi.top/v1,备用 https://xyuai.cc/v1,另支持 Gemini 原生 /v1beta 路径。OpenAI 兼容协议,Chatbox、Cherry Studio、NextChat、Cline 这类客户端填上地址和 key 就能调;需要精细控制模态和尺寸时,直接走原生路径更顺手。

按次计费怎么算

平台按一次请求固定价收费,同样的价格你可以把描述写得详细一点,也可以带参考图,价格不随输入变长而变。对比按 token 计费的算法,做批量预算时直接按张数乘单价更清楚。

模型价格典型用途
gemini-2.5-pro0.031 元/次图片描述的文案与标签生成
gemini-3-pro-preview0.05 元/次复杂提示词改写与风格设计
gemini-3.1-pro-preview0.09 元/次高难度创意与批量脚本设计
claude-sonnet-4-5-thinking0.09 元/次长文与多图任务编排
NanoBanana-Pro(生图)0.27 元/次图片生成与图片编辑

现在的动作清单

  1. 确认模型是生图模型,并显式声明输出模态。
  2. 参考图先压到长边 1568 像素以内,base64 去掉前缀和换行。
  3. 遍历 parts 提取图片,两种字段命名都判一遍,落盘后再返回地址。
  4. 超时给到 180 到 300 秒,并发压到三到五,重试前先改描述。

相关阅读

🚀 想要立即使用?来小鱼API体验全系列AI模型

查看全部产品

支持Gemini / Claude / GPT / Grok / DeepSeek · 国内直连 · 支付宝/微信