10月7日の昼、Gemini API の deprecations 表を開いて、見覚えのある ID に目が止まりました。
gemini-3.1-flash-image。以前の記事で置き換え先に書いた gemini-3.1-flash-image-preview と、接尾辞だけが違う ID です。その行に、終了日 10月29日と、次の置き換え先 gemini-nano-banana-2.1 が並んでいました。
移った先の系統にも期限がある——。当たり前のことなのに、「移ったら終わり」と思い込んでいた自分に気づいた瞬間でした。
最初にお伝えしたいのは、今回の作業が「新しいモデルを試す」話ではないということです。どこで画像モデルを呼んでいるかを知り、切り替える日と戻る先を先に決めておく——それだけで、期限前日の慌ただしさはほとんど消えます。
移った先の系統にも、日付がありました
以前、モデルの廃止日を棚卸しする方法を書きました(二次情報の「10月16日」が一次表に見当たらない — Gemini のモデル廃止日を自分で棚卸しする)。そのとき台帳に写した行は、次のとおりです。
| モデル ID | 終了日 | 置き換え先 |
|---|---|---|
gemini-2.5-flash-image | 2026-10-02 | gemini-3.1-flash-image-preview |
gemini-3.1-flash-image | 2026-10-29 | gemini-nano-banana-2.1 |
下の行は、10月7日に表を開き直して追記したものです。ID の綴りは私が表から写したものなので、お手元でも公式の表と見比べてから台帳に入れていただければ幸いです。
ここで一つ、はっきりさせておきたいことがあります。-preview の付いた ID と付かない ID は、表の上では別の行です。台帳に gemini-3.1-flash-image と書いてあっても、コードに gemini-3.1-flash-image-preview が残っていれば、突き合わせは素通りしてしまうのです。-preview 側に終了日が付いているかどうかは、表で確かめてから台帳に足してください。
洗い出しは、台帳との突き合わせまで一度にやります
画像モデルの呼び出しは、コードだけにあるとは限りません。エージェントの設定ファイル、環境変数の既定値、ドキュメントの例——個人開発では、半年前の自分が置いたものが思わぬところに残っています。
次のスクリプトは、リポジトリ全体から画像モデルの ID を拾い、台帳(CSV)の終了日と突き合わせます。接尾辞(-preview など)まで含めて一つの ID として扱うのが要点です。
#!/usr/bin/env python3
"""リポジトリ内の画像モデル ID を洗い出し、台帳の期限と突き合わせる。"""
import csv, re, sys, datetime as dt
from pathlib import Path
LEDGER = Path(sys.argv[2] if len(sys.argv) > 2 else "image_models.csv")
ROOT = Path(sys.argv[1] if len(sys.argv) > 1 else ".")
SKIP = {".git", "node_modules", ".venv", "dist", ".next"}
EXT = {".py", ".ts", ".tsx", ".js", ".mjs", ".json", ".yaml", ".yml", ".toml", ".env", ".md"}
# -preview のような接尾辞まで丸ごと拾う(接尾辞違いは別の ID として扱うため)
PAT = re.compile(r"gemini-[0-9][0-9.]*-[a-z-]*image[a-z0-9-]*")
def load_ledger():
rows = {}
with LEDGER.open(encoding="utf-8") as f:
for r in csv.DictReader(f):
rows[r["model_id"]] = r
return rows
def main():
ledger, today = load_ledger(), dt.date.today()
hits = {}
for p in ROOT.rglob("*"):
if not p.is_file() or SKIP & set(p.parts) or p.suffix not in EXT:
continue
for n, line in enumerate(p.read_text(encoding="utf-8", errors="ignore").splitlines(), 1):
for m in PAT.findall(line):
hits.setdefault(m, []).append(f"{p.relative_to(ROOT)}:{n}")
for mid, places in sorted(hits.items()):
r = ledger.get(mid)
if r is None:
print(f"? {mid} 台帳に無い ID(公式の表で確認) {', '.join(places)}")
continue
left = (dt.date.fromisoformat(r["shutdown"]) - today).days if r["shutdown"] else None
tail = "期限なし" if left is None else f"あと {left} 日"
print(f"{'!' if left is not None and left < 30 else ' '} {mid} {tail} → {r['replacement'] or '-'} {', '.join(places)}")
main()台帳は、上の表をそのまま CSV にしたものです。
model_id,shutdown,replacement
gemini-3.1-flash-image,2026-10-29,gemini-nano-banana-2.1
gemini-2.5-flash-image,2026-10-02,gemini-3.1-flash-image-preview設定ファイルに 1 か所、Python に 1 か所、TypeScript に -preview 付きで 1 か所——という検証用の小さなリポジトリに流したところ、10月7日の実行結果は次のとおりでした。
! gemini-3.1-flash-image あと 22 日 → gemini-nano-banana-2.1 conf/agent.yaml:1, src/thumb.py:1
? gemini-3.1-flash-image-preview 台帳に無い ID(公式の表で確認) src/other.ts:1
! は 30 日を切ったもの、? は台帳が答えられないものです。後者こそ、人の目が要る行だと思っています。
切り替えは「一本の変数」と「戻り先」に分けます
呼び出し箇所が 3 つあるなら、3 つとも書き換えるのは遅いやり方です。モデル ID を返す関数を一本だけ作り、そこに切り替え日を持たせます。
import os, datetime as dt
# 台帳: 主モデルと戻り先、切り替え予定日
PLAN = {
"primary": "gemini-3.1-flash-image",
"next": "gemini-nano-banana-2.1",
"cutover": dt.date(2026, 10, 20), # 期限(10/29)の9日前
"shutdown": dt.date(2026, 10, 29),
}
def pick_model(today=None):
today = today or dt.date.today()
forced = os.environ.get("IMAGE_MODEL") # 手動の上書きが最優先
if forced:
return forced, "env"
if today >= PLAN["shutdown"]:
return PLAN["next"], "after-shutdown"
if today >= PLAN["cutover"]:
return PLAN["next"], "cutover"
return PLAN["primary"], "primary"
def generate(prompt, call, today=None):
"""call(model, prompt) は各自の画像生成呼び出し。失敗したら旧モデルへ戻す。"""
model, why = pick_model(today)
try:
return call(model, prompt), model
except Exception as e:
if model == PLAN["next"] and today_before_shutdown(today):
print(f"[warn] {model} 失敗 ({e}) → {PLAN['primary']} に戻します")
return call(PLAN["primary"], prompt), PLAN["primary"]
raise
def today_before_shutdown(today=None):
return (today or dt.date.today()) < PLAN["shutdown"]日付を差し込んで動かすと、次のように振る舞います。実際の API は呼ばず、call にダミーを渡した結果です。
2026-10-07 ('gemini-3.1-flash-image', 'primary')
2026-10-20 ('gemini-nano-banana-2.1', 'cutover')
2026-10-29 ('gemini-nano-banana-2.1', 'after-shutdown')
[warn] gemini-nano-banana-2.1 失敗 (quota) → gemini-3.1-flash-image に戻します
なぜこう書くのか、理由を三つ挙げます。
- 切り替え日を期限の 9 日前に置くのは、旧モデルがまだ動いているあいだに、新モデルの不具合を見つけるためです。
- 環境変数での上書きを最優先にしているのは、切り替え後に「やはり戻したい」となったとき、コードを触らずに済むからです。
- 戻り先は期限の前日までしか使えません。期限を過ぎれば、戻る先自体が無くなります。
これは検証用のスクリプトであって、本番の API 呼び出しまで確かめたものではありません。call の中身——クライアントの作り方や引数——は、お使いの構成に合わせてください。
画像モデルは、見た目で壊れます
文章モデルの差し替えなら、出力の JSON が同じ形かどうかで半分は判定できます。画像はそうはいきません。同じプロンプトでも、構図や色味、文字の入り方が変わる可能性があるからです。
私自身は、期限が近いときほど次の線引きにしています。
差し替え先の出力は、自分の目で数枚見るまで、本番に流さない。
切り替え日の前に、普段使っているプロンプトを 5 本ほど選び、新旧のモデルで 1 枚ずつ並べて眺めます。サイズ、余白、文字の崩れ。数字で測るより、目で見たほうが早く気づけます。
10月29日までに決めておくこと
日程は、次のように置いています。
| 日付 | やること |
|---|---|
| 〜10/10 | 洗い出しを実行し、? の行を公式の表で確認する |
| 〜10/15 | 新旧モデルで同じプロンプトを並べ、見た目を確認する |
| 10/20 | 切り替え日。旧モデルへの戻り先を残したまま新モデルへ |
| 10/29 | 終了日。環境変数の上書きを外し、戻り先の記述を整理する |
最初の一歩は、今日のうちに洗い出しを一度だけ流すことです。呼び出し箇所が 1 つだと分かるだけでも、気持ちはずいぶん軽くなります。
移った先にも期限があるという事実は、これからも何度か目にするのだと思います。その都度、台帳に 1 行足していく——この小さな習慣だけは、手放さないようにしています。