高频出现的原文是这一条:
{
"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"] 会得到一段文字描述,不写这一项有些客户端会直接报参数错误。
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: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))
image/jpg 不是标准写法,应该用 image/jpeg;PNG 是 image/png;WebP 是 image/webp。类型和实际内容不一致时,有的接口直接报参数错误,有的会返回一段「无法读取图片」的文字。
某些编码工具会按 76 字符折行,折行后的字符串提交上去会被判非法。上传前把 \n 和 \r 去掉,这一步用正则处理干净。
参考图太大时,整个请求体会撞上服务端的大小限制,报错关键字通常是 Request payload size exceeds the limit。做法是先压缩:长边压到 1568 像素以内、转成 WebP 或中等质量 JPEG,体积能降到原来的十分之一,肉眼几乎看不出差别。
图片不是放在一个固定字段里的,要遍历:
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 开头。文件只有几百字节,基本可以确定是解析错了或者返回的不是图片。
校验还能再细一步:把图片的长宽读出来,确认与请求里写明的比例一致。比例对不上说明参数没生效,多半是传参层级放错了位置。这类问题不会报错,但结果不对,比报错更难发现。
保存路径带上时间戳和随机后缀,批量出图时不会互相覆盖。文件名里再带上任务编号,出问题时顺着文件名就能把整个任务的结果一次性翻出来。
{
"candidates": [
{"finishReason": "IMAGE_SAFETY", "content": {"parts": []}}
]
}
finishReason 是 IMAGE_SAFETY,parts 是空的。看到这个值,改参数、重试、换通道都不会有用,要改的是描述内容。
图片模型对人物相关内容的策略比文本更严,凡是可能被当成真实人物形象的描述都容易触发拦截。画插画、场景、产品、风格化的人物形象通常没有问题,以实际业务需要为准,把描述写清楚就好。
对违反法律法规和上游策略的内容,平台不予处理,也不会提供规避手段。把人工复核环节做进流程,让个别被拦的图有兜底路径,业务就不会中断。
图片生成的计算量放在那里,非流式调用下客户端会一直静默等待。默认 60 秒的超时对生图偏紧,建议至少给 180 秒,稳定一点的做法是给 300 秒。
一批几十张图同时发出去,很容易撞上 429。把并发压到三到五,并在每张之间留一点间隔,比事后重试省事。
| 场景 | 建议并发 | 建议超时 | 单张价格参考 |
|---|---|---|---|
| 单张调试 | 1 | 180 秒 | NanoBanana-Pro 0.27 元/次 |
| 小批量出图 | 3 | 300 秒 | NanoBanana-Pro 0.27 元/次 |
| 大批量任务 | 3 到 5 | 300 秒 | 按张累计,先跑样本估总量 |
生图按张计费,重试一次就多花一次的钱。被策略拦的图重发也是白花,先把描述改好再发,是省钱的做法。
这三类都是展示层的问题,日志里能看到完整的返回内容,接口本身是成功的。
从返回内容里提取 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-pro | 0.031 元/次 | 图片描述的文案与标签生成 |
| gemini-3-pro-preview | 0.05 元/次 | 复杂提示词改写与风格设计 |
| gemini-3.1-pro-preview | 0.09 元/次 | 高难度创意与批量脚本设计 |
| claude-sonnet-4-5-thinking | 0.09 元/次 | 长文与多图任务编排 |
| NanoBanana-Pro(生图) | 0.27 元/次 | 图片生成与图片编辑 |
parts 提取图片,两种字段命名都判一遍,落盘后再返回地址。