9月の初め、手元の検証用スクリプトに残っていた gemini-2.5-flash-image を書き換えようとして、公式の Deprecations ページを開きました。該当の行には移行先がきちんと書かれておりましたので、その文字列をそのままコピーし、モデル ID の定数を差し替えました。
ところが差し替えた先の gemini-3.1-flash-image-preview は、同じページの別の表で、6月25日に停止済みと記されていたのです。
表の一行だけを見ていたら、止まるモデルから、すでに止まったモデルへ移るところでした。移行先は、書かれている一行ではなく、辿り着いた先で決まります。 その確かめ方を、実際に走らせた出力とあわせて書き残します。
10月2日の行が指していた移行先は、6月25日に止まっています
まず事実を並べます。Gemini deprecations の表から、関係する3行を抜き出しました。
| モデル ID | リリース | 停止日 | 表に書かれた推奨移行先 |
|---|---|---|---|
gemini-2.5-flash-image | 2025年10月2日 | 2026年10月2日 | gemini-3.1-flash-image-preview |
gemini-3.1-flash-image-preview | 2026年2月26日 | 2026年6月25日(停止済み) | gemini-3.1-flash-image |
gemini-3.1-flash-image | 2026年5月28日 | 停止日の announce なし | — |
実際に移るべき先は、-preview の付かない gemini-3.1-flash-image のほうです。2026年5月28日に GA となり、停止日はまだ announce されていません。
gemini-2.5-flash-image は「Nano Banana」の名で広まった初代で、個人開発者のサンプルコードや記事にいちばん多く残っている画像生成のエンドポイントだと思います。私自身、作品や配信する素材の制作には AI を使っておりませんが、挙動の確認用に叩くコードは残しておりました。そういう「動いているから触っていない」コードほど、期限に気づかないのです。
もう一点、記事にするなら必ず添えるべき注記があります。公式は、表の停止日を「最も早い可能性のある日付」と明記しています。10月2日に必ず落ちると決まったわけではありませんし、正確な日付は事前に通知されるとも書かれています。慌てる必要はありませんが、期日として扱わない理由にもなりません。
移行先が生きているかどうかは、二つの欄で決まります
行を読むときに私が見ているのは、次の二つです。
- 停止日の欄が「No shutdown date announced」になっているか。日付が入っていれば、その移行先もいずれ書き換えが要ります。
- モデル ID に
-previewが付いていないか。preview は GA より短い周期で入れ替わります。
gemini-3.1-flash-image-preview は、この両方に引っかかっていました。日付が入っていて、しかも preview です。それでも移行先の欄に書かれていたのは、行が announce された時点の情報のまま残っているからではないかと思います。表は行ごとに書かれ、あとから移行先の側が停止しても、その行が自動で追随するわけではないのでしょう。
つまり、表そのものが間違っているというより、一行だけでは完結しないつくりになっています。読み手の側で連鎖を辿る必要があります。
いま思えば、一行を読めば移行先が分かると思い込んでいたこと自体が、preview と GA の入れ替わりの速さを見くびっていたのかもしれません。
移行先を最後まで辿るスクリプト
手で辿ると見落としますので、短いスクリプトにしました。公式ページを保存したファイルを渡すと、停止日が announce されていないモデルに行き着くまで移行先を辿ります。標準ライブラリだけで動きます。
#!/usr/bin/env python3
"""Deprecations ページの「推奨移行先」を、生きているモデルまで辿る。
python3 resolve_replacement.py deprecations.md
python3 resolve_replacement.py deprecations.md gemini-2.5-flash-image
"""
import re, sys
from datetime import date, datetime
TODAY = date.today()
# | `model-id` | リリース日 | 停止日 | `推奨移行先` |
ROW = re.compile(
r"\|\s*`?([a-z0-9.\-]+)`?\s*\|" # モデル ID
r"[^|]*\|" # リリース日(使いません)
r"\s*([^|]*?)\s*\|" # 停止日
r"\s*`?([a-z0-9.\-]*)`?\s*\|" # 推奨移行先
)
def parse_date(text):
text = text.strip()
if not text or "No shutdown" in text:
return None # 停止日なし = 現時点では生きています
for fmt in ("%B %d, %Y", "%b %d, %Y"):
try:
return datetime.strptime(text, fmt).date()
except ValueError:
continue
return None
def load(path):
table = {}
with open(path, encoding="utf-8") as fh:
for line in fh:
m = ROW.search(line)
if not m:
continue
model, shutdown_text, replacement = m.groups()
if model in ("Model", "Preview models", "Deprecated models"):
continue # 表のヘッダと区切り行を落とします
table[model] = {
"shutdown": parse_date(shutdown_text),
"replacement": replacement.strip() or None,
}
return table
def resolve(table, model, seen=None):
seen = (seen or []) + [model]
row = table.get(model)
if row is None:
return model, seen, "unknown" # 表に載っていない ID
if row["shutdown"] is None:
return model, seen, "alive" # ここが終点です
nxt = row["replacement"]
if not nxt or nxt in seen: # 移行先が空、または循環
return model, seen, "dead-end"
return resolve(table, nxt, seen)
def status_of(table, model):
row = table.get(model)
if row is None:
return "表に載っていません"
if row["shutdown"] is None:
return "停止日なし"
left = (row["shutdown"] - TODAY).days
tail = "(停止済み)" if left <= 0 else "(あと %d 日)" % left
return "停止日 %s%s" % (row["shutdown"].isoformat(), tail)
def main():
table = load(sys.argv[1])
targets = sys.argv[2:] or sorted(
m for m, r in table.items() if r["shutdown"] and r["shutdown"] > TODAY
)
for model in targets:
listed = (table.get(model) or {}).get("replacement")
final, path, kind = resolve(table, model)
print("%-42s %s" % (model, status_of(table, model)))
print(" → 実際に移るべき先: %s (%s)" % (final, kind))
if len(path) > 2:
print(" 経路: " + " → ".join(path))
if listed and listed != final:
print(" ⚠ 表の移行先 %s は %s" % (listed, status_of(table, listed)))
print()
if __name__ == "__main__":
sys.exit(main())保存したページを渡して、問題の行だけを指定した結果です。
$ python3 resolve_replacement.py deprecations.md gemini-2.5-flash-image
gemini-2.5-flash-image 停止日 2026-10-02(あと 20 日)
→ 実際に移るべき先: gemini-3.1-flash-image (alive)
経路: gemini-2.5-flash-image → gemini-3.1-flash-image-preview → gemini-3.1-flash-image
⚠ 表の移行先 gemini-3.1-flash-image-preview は 停止日 2026-06-25(停止済み)resolve() を再帰にしてあるのは、後で述べるとおり、連鎖が2段で終わらない行があったからです。
連鎖していたのは、画像モデルだけではありませんでした
同じスクリプトを表の全行に通しました。停止日が入っている行のうち、書かれた移行先と、辿り着いた終点が食い違うものが11件あり、そのうち 5件は移行先自体がすでに停止済みでした。
| 元のモデル | 表に書かれた移行先 | 辿り着いた終点 | 段数 |
|---|---|---|---|
gemini-2.5-flash-image | gemini-3.1-flash-image-preview(停止済み) | gemini-3.1-flash-image | 2 |
gemini-2.5-flash-image-preview | gemini-2.5-flash-image(10月2日) | gemini-3.1-flash-image | 3 |
gemini-2.0-flash-preview-image-generation | gemini-2.5-flash-image(10月2日) | gemini-3.1-flash-image | 3 |
imagen-3.0-generate-002 | imagen-4.0-generate-001(停止済み) | gemini-3.1-flash-image | 2 |
gemini-2.0-flash-lite | gemini-3.1-flash-lite(2027年5月7日) | gemini-3.5-flash-lite | 2 |
gemini-robotics-er-1.5-preview | gemini-robotics-er-1.6-preview(停止済み) | gemini-robotics-er-2-preview | 2 |
画像生成まわりは3段の連鎖が二つありました。古いサンプルコードの gemini-2.0-flash-preview-image-generation から素直に辿ると、gemini-2.5-flash-image を経由して、ようやく gemini-3.1-flash-image に着きます。途中で止めると、20日後に止まるモデルに落ち着いてしまうわけです。
正直に書きますと、最初の版は11件ではなく16件を出しておりました。差の5件は誤検出です。停止日の欄が空の行——つまり、まだ止まる予定のないモデル——まで比較の対象にしていたためでした。gemini-3-flash-preview のように、停止日はないのに移行先だけが書かれている行があるのです。
判定の条件に「停止日が入っている行だけを見る」を足したところ、11件に落ち着きました。動くようになったコードより、条件を一つ足して減った件数のほうが、あとから読み返して役に立ちます。 減った理由をコメントに残しておくと、次に表の形が変わったときに自分を助けてくれます。
表を信じる前に、エンドポイントに聞く
ドキュメントは announce の記録で、実際に何が返ってくるかはエンドポイントが持っています。切り替える前に、使うつもりの ID が本当に配信されているかを一度だけ確かめる関数を、移行スクリプトの先頭に置いています。
from google import genai
client = genai.Client(api_key="YOUR_API_KEY")
def assert_served(model_id: str) -> None:
"""models.list に現れない ID は、その場で落とします。"""
served = {m.name.removeprefix("models/") for m in client.models.list()}
if model_id not in served:
near = sorted(s for s in served if s.startswith(model_id.split("-preview")[0]))
raise SystemExit(
"%s は配信されていません。近い ID: %s" % (model_id, ", ".join(near) or "なし")
)
TARGET = "gemini-3.1-flash-image" # 表を辿って確定させた ID
assert_served(TARGET) # ここを通れば、あとは差し替えるだけです近い ID を並べて出しているのは、-preview を取り違えたときに気づけるようにするためです。私は取り違えましたので、この2行を足しました。
書き換え自体は、モデル ID を一箇所にまとめてあれば定数の差し替えで終わります。まとめていなければ、そちらが先の仕事になります。なお、SDK のメソッドが非推奨になる話と、モデルそのものが止まる話は別の期限です。ここは混同しやすいところですので、generate_images の非推奨とモデル停止では期限が違うという話にも書き残しております。
10月2日までの、今日の一手
やることは一つで足ります。手元のコードとドキュメントを gemini-2.5-flash-image で検索して、1件でも当たったら、移行先を表の一行ではなく終点まで辿ってください。 終点は gemini-3.1-flash-image です。
期限が近い順に並べておきます。9月30日に gemini-omni-flash-preview、10月2日に gemini-2.5-flash-image。この二つを過ぎると、次は2027年5月7日の gemini-3.1-flash-lite まで間が空きます。9月30日のほうは期限を一枚の表にまとめた記事に整理しております。
私はこの一件から、移行先の欄を読んだら必ずその行にも飛ぶ、という手順を自分に課しました。二度手間に見えますが、止まったモデルへ移して本番で気づくよりは、はるかに安い手間です。
最後までお読みくださり、ありがとうございました。皆さまの手元のコードが、10月2日を静かに通り過ぎますように。