壁紙アプリ向けの下絵を作っているスクリプトが、あと3日で止まります。
imagen-4.0-generate-001 を含む Imagen 4 系のモデルが 2026年8月17日に停止し、画像生成は gemini-3.1-flash-image へ寄せる、というのが公式に案内されている道筋です。移行の説明としてよく見かけるのは「client.models.generate_images を client.models.generate_content に変えてください」という一文で、これ自体は正しいのですが、実際に手を動かすと引っかかるのはメソッド名ではありませんでした。
引っかかったのは、config に詰めていた引数のほうです。
呼び出し箇所の洗い出しについては8月17日の Gemini 画像生成モデル停止に備えるで書きましたので、この記事はその続き、実際に書き換える段の話にします。
引数は17個あって、そのまま移せたのは5個でした
推測で書きたくなかったので、インストール済みの SDK を直接読みました。以下は google-genai 2.18.1 で、GenerateImagesConfig のフィールド名と、移行先の ImageConfig / GenerateContentConfig のフィールド名を突き合わせた結果です。
from google.genai import types
gi = set(types.GenerateImagesConfig.model_fields) - {"http_options"}
ic = set(types.ImageConfig.model_fields)
gc = set(types.GenerateContentConfig.model_fields)
print(len(gi), sorted(gi & ic)) # そのまま残る
print(sorted((gi - ic) & gc)) # 上位の config へ移る
print(sorted(gi - ic - gc)) # 同名の行き先がない出力はこうなりました。対象17個のうち、ImageConfig に同じ名前で残ったのは5個です。
旧 GenerateImagesConfig の引数 | 行き先 | 補足 |
|---|---|---|
| aspect_ratio / image_size / output_mime_type / output_compression_quality / person_generation | ImageConfig にそのまま | 名前も意味も変わらず移せました |
| seed / labels | GenerateContentConfig の直下 | 画像固有の設定ではなくなり、一段上に移りました |
| number_of_images | 呼び出し回数か candidate_count | 後述しますが、扱いが最も変わる引数です |
| safety_filter_level | safety_settings | 単一の文字列から SafetySetting のリストへ。手で書き直す必要があります |
| negative_prompt | プロンプト本文 | 独立した引数ではなくなったため、文章として書くことになります |
| add_watermark / enhance_prompt / guidance_scale / language / include_rai_reason / include_safety_attributes / output_gcs_uri | なし | 落とす判断が要ります |
person_generation が ImageConfig 側に残っていたのは、私にとっては良い意味で予想外でした。人物の扱いを明示していた箇所は書き換えなしで通ります。逆に guidance_scale や negative_prompt のように「絵の寄せ方を数値と除外語で調整していた」引数は、まとめて消えます。ここに依存した調整をしていたなら、移行はパラメータの付け替えではなく、プロンプトの書き直しになります。
なお ImageConfig には prominent_people と image_output_options という、Imagen 側になかったフィールドが増えています。移行のついでに眺めておくと、後から驚かずに済みます。
数え方が変わると、失敗の意味も変わります
いちばん静かに影響が大きいのは number_of_images でした。
Imagen では1回の呼び出しで複数枚を受け取れました。number_of_images=4 と書けば、成功なら4枚、失敗ならゼロ枚です。判定は二値で済みます。
generate_content で同じことをしようとすると、多くの場合は「1回1枚をN回」に置き換わります。candidate_count というフィールド自体は GenerateContentConfig に存在しますが、画像モデルで実際に複数枚が返るかどうかは自分の API キーで確かめるべき事柄なので、私は確実な側、つまりループを選びました。
置き換えた瞬間に、次の3つが変わります。
ひとつ、失敗が部分的になります。4回のうち3回成功して1回だけ 503 が返る、という状態が普通に起きるようになりました。呼び出し側が「4枚来る前提」で書かれていると、ここで静かに壊れます。
ふたつ、レート制限の見え方が変わります。1回だったものが4回になるので、分あたりのリクエスト数の上限に当たるタイミングが早まります。バッチで回している処理ほど影響が出ます。
みっつ、seed の意味が変わります。Imagen 時代の「1回で4枚」に seed を添えるのと、seed を固定したまま4回呼ぶのとでは、期待する結果が違います。後者は同じ絵を4枚受け取ることになりかねません。枚数分の変化が欲しいなら、seed は呼び出しごとにずらすか、そもそも外す判断が必要です。
私はこの3つ目に気づくのが遅く、対応表を作っている途中で「これは引数の移し替えではなく、生成の設計が変わる話だ」と考えを改めました。
互換レイヤーを1枚挟んで、呼び出し側を止めない
呼び出し箇所が1つなら直接書き換えれば済みます。個人開発では、こういう小さな呼び出しが手元のあちこちに残っているのが常でして、私の場合も例外ではありませんでした。ただ、検証用スクリプトや月末のバッチまで含めると、期日までに全部を書き換えて全部を確かめるのは、3日ではやや慌ただしいところです。
そこで、旧来の引数を受け取って新しい形に組み替える関数を1枚挟みました。呼び出し側は当面そのままにして、内側だけを新 API に向けています。
from google.genai import types
IMAGE_CONFIG_KEYS = {"aspect_ratio", "image_size", "output_compression_quality",
"output_mime_type", "person_generation"}
TOP_LEVEL_KEYS = {"seed"}
NO_DESTINATION = {"add_watermark", "enhance_prompt", "guidance_scale",
"include_rai_reason", "include_safety_attributes",
"language", "output_gcs_uri", "labels"}
class UnportedArgument(RuntimeError):
pass
def build_request(*, model, prompt, config):
"""旧 GenerateImagesConfig 相当の dict から generate_content の引数を組む。"""
cfg = dict(config)
notes = []
for key in sorted(cfg):
if key in NO_DESTINATION:
raise UnportedArgument(
f"{key} には gemini-3.1-flash-image 側の対応先がありません。"
f"落とすか、別の手段に置き換えるかを決めてください。")
if "negative_prompt" in cfg:
prompt = f"{prompt}\n\n避けてほしい要素: {cfg.pop('negative_prompt')}"
notes.append("negative_prompt をプロンプト本文に畳みました")
if "safety_filter_level" in cfg:
cfg.pop("safety_filter_level")
notes.append("safety_filter_level は safety_settings への手当てが必要です")
n = cfg.pop("number_of_images", 1)
image_config = {k: cfg.pop(k) for k in list(cfg) if k in IMAGE_CONFIG_KEYS}
top = {k: cfg.pop(k) for k in list(cfg) if k in TOP_LEVEL_KEYS}
if cfg:
notes.append(f"未知のキーを無視しました: {sorted(cfg)}")
gen_config = types.GenerateContentConfig(
response_modalities=["IMAGE"],
image_config=types.ImageConfig(**image_config) if image_config else None,
**top,
)
return {"model": model, "contents": prompt, "config": gen_config}, n, notes
def generate_images_compat(client, *, model, prompt, config):
kwargs, n, notes = build_request(model=model, prompt=prompt, config=config)
images, failures = [], []
for i in range(n):
try:
res = client.models.generate_content(**kwargs)
except Exception as e: # 1回落ちても残りは続ける
failures.append((i, repr(e)))
continue
for part in res.parts:
if getattr(part, "inline_data", None):
images.append(part.inline_data.data)
return images, failures, notes意識して入れたのは、行き先のない引数で例外を出す部分です。黙って捨てる実装にすると、guidance_scale を渡していたつもりの箇所が、何の警告もないまま別の絵を返すようになります。移行の最中に最も避けたいのは、動いているように見えて結果だけが変わっている状態でした。ですので、判断が要る引数は人間に返す設計にしています。
戻り値を「画像のリストと、失敗した回のリスト」に分けたのも同じ理由です。呼び出し側で len(images) を確認せざるを得ない形にしておけば、部分失敗を見落としにくくなります。
API キーを使わずに、形だけ先に確かめる
書き換えた直後に本番のキーで叩くと、失敗したときに「組み立てが間違っているのか、モデル側の問題なのか」が切り分けにくくなります。私は先に、呼び出しを記録するだけのスタブで形を確かめました。
class _Part:
def __init__(self, data): self.inline_data = type("D", (), {"data": data})()
class _Res:
def __init__(self, data): self.parts = [_Part(data)]
class _Models:
def __init__(self): self.calls = []
def generate_content(self, **kw):
self.calls.append(kw)
if len(self.calls) == 2:
raise RuntimeError("503 UNAVAILABLE") # 2回目だけ落としてみる
return _Res(b"\x89PNG-stub")
class StubClient:
def __init__(self): self.models = _Models()旧来の引数一式を渡して走らせた、実際の出力がこちらです。
呼び出し回数 : 3
取得できた画像 : 2 枚
失敗した回 : [(1, "RuntimeError('503 UNAVAILABLE')")]
注記 : negative_prompt をプロンプト本文に畳みました
注記 : safety_filter_level は safety_settings への手当てが必要です
送った contents : '夜明けの海を上から見た抽象画\n\n避けてほしい要素: 文字, 透かし'
image_config : aspect_ratio='9:16' person_generation='ALLOW_ADULT' output_mime_type='image/png'
seed : 42
response_modalities: ['IMAGE']
strict 判定 : guidance_scale には gemini-3.1-flash-image 側の対応先がありません。3回呼んで2枚、という結果が目で見えるので、部分失敗の扱いを決めないまま先へ進めなくなります。types.ImageConfig と types.GenerateContentConfig は実物を組み立てていますから、フィールド名の誤りや型の不一致はこの段階で例外になります。ネットワークには一度も出ていません。
念のため申し添えますと、この方法で確かめられるのは組み立ての正しさだけです。生成される絵の傾向、レイテンシ、費用は自分のキーで実際に走らせないと分かりません。そこは分けて考えていただければと思います。
17日までに決める順番
残り時間が短いので、私は次の順で片づけました。判断の重い順ではなく、後戻りが発生しにくい順です。
- 行き先のない7つの引数を渡している箇所を先に洗う。ここだけは仕様の判断が要るため、機械的には終わりません
number_of_imagesが2以上の箇所を抜き出し、seedを併用しているかを確認する。併用していれば、ずらすか外すかを決める- 互換レイヤーを入れて、スタブで形を通す
- 実キーで1本だけ流し、出力の傾向を目で見る。ここで初めて絵の話になります
- 停止前に確保しておきたい生成物があれば、17日より前に手元へ落としておく
5番目は移行そのものとは別の話ですが、旧モデルでしか出せなかった絵を使っている場合、停止後に同じものを作り直す手段はありません。この観点を先に整理しておくと、移行の優先順位が変わることがあります。廃止に振り回されないパイプラインの組み方についてはプレビュー画像モデル停止の朝に学んだことで、モデル ID の一元管理まで含めて詳しく扱っております。
今回の移行で私が学んだのは、廃止対応の難しさは新 API の学習コストではなく、旧 API に預けていた前提を自分が把握していない点にある、ということでした。17個の引数のうち10個に同名の行き先がないと分かった時点で、これは付け替えではなく設計の見直しだと腹をくくれたのは、結果として良かったと思っています。
同じ期日に向き合っている方の、書き換えの手がかりになれば幸いです。お読みいただきありがとうございました。