移行の順番を決めようとして、Gemini の廃止一覧を開きました。gemini-2.5-flash の行を探して、停止日の欄で手が止まりました。
日付が入っていません。No shutdown date announced とだけ書かれています。gemini-2.5-pro も gemini-2.5-flash-lite も同じです。ページの更新日は2026年8月13日でした。
一方で、開発者フォーラムには7月9日付で「gemini-2.5-flash を呼んだら404が返り、This model models/gemini-2.5-flash is no longer available と言われた」という報告が立っています。gemini-2.5-pro については「新規ユーザーには提供されない」という別の文面の報告もあります。
停止日は空欄、しかし呼び出しは通らない。この二つは、実は矛盾していません。矛盾して見えるのは、停止日の欄を「その日までは使える保証」として読んでしまったときです。
停止日の欄が空いていることと、まだ使えることは別です
廃止一覧のページには、表のすぐ上に注記があります。表に載っている停止日は、そのモデルが引退させられる最も早い日を示すものであって、それより後に別途連絡がある、という趣旨のものです。
つまりこの表は、下限を示しています。上限ではありません。「10月16日まではこのままでいい」という読み方をすると、根拠のない安心を買ってしまいます。実際、gemini-2.5-flash や gemini-2.5-pro の停止日は、以前は日付が入っていたものが現在は未告知の表記に変わっています。日付が消えたことは、猶予が延びたことを意味しません。
8月13日更新時点の記載を、私の側で読み取れた範囲で整理します。
| モデル | 公式表の停止日 | 推奨される置き換え先 |
|---|---|---|
gemini-2.5-pro | 未告知 | 記載なし |
gemini-2.5-flash | 未告知 | 記載なし |
gemini-2.5-flash-lite | 未告知 | 記載なし |
gemini-2.5-flash-image | 2026年10月2日 | gemini-3.1-flash-image-preview |
gemini-2.0-flash/gemini-2.0-flash-001 | 2026年6月1日(経過済み) | gemini-3.6-flash |
この表で一番落ち着かないのは、gemini-2.5-flash-image の置き換え先が gemini-3.1-flash-image-preview になっている点です。GA のモデルから preview のモデルへ移せ、と読めます。preview を本番の既定にしたくない立場からすると、そのまま従うわけにはいきません。一番強いモデルが preview で、安いモデルが GA ですで触れた順序の逆転が、廃止一覧の側にも顔を出しています。
「no longer available」と「no longer available to new users」は違う失敗です
報告されている文面には2種類あります。この違いは、切り分けのときに効いてきます。
前者(no longer available)は、そのモデルの入口そのものが閉じている状態を指します。どのキーで呼んでも同じ結果になるはずです。
後者(no longer available to new users)は、呼び出し元によって結果が変わります。すでにそのモデルを使った実績のあるプロジェクトは通り、新しく作ったプロジェクトやキーでは弾かれる、という非対称が起きます。
個人開発で複数のアプリとサイトを一人で見ていると、用途ごとに切ったキーが自然に増えていきます。私自身、本番・検証・定期実行で別々のキーを使っており、作られた時期もばらばらです。
この非対称は、開発の現場では気持ちの悪い形で現れます。長く使っている本番プロジェクトのキーでは通るのに、新しく切ったステージング用のプロジェクトや、CI のために後から作ったキーでは404になる。同じコード、同じモデルID、違う結果です。
こうなると、原因の候補がコード側に見えてしまいます。SDK のバージョンを疑い、リージョンを疑い、リクエストの組み立てを疑って時間を溶かします。私は「同じコードで環境によって結果が違うときは、まずキーの側を疑う」を先に持ってくるようにしました。モデルの提供範囲がプロジェクト単位で切られている以上、コードをいくら読んでも答えは出てきません。
自分のキーから何が見えているかを、5分で確かめる
推測をやめて、キーごとに models.list を叩くのが早いです。依存を足したくなければ curl だけで済みます。
curl -s "https://generativelanguage.googleapis.com/v1beta/models?pageSize=200" \
-H "x-goog-api-key: YOUR_API_KEY" \
| grep -o '"name": "models/[^"]*"' \
| sed 's|.*models/||; s|"$||' \
| sort出てきた一覧に gemini-2.5-flash が含まれていなければ、そのキーからはもう見えていません。移行を「そのうち」に置いておける段階は過ぎています。
ここからが本題です。確かめるキーは1本では足りません。 本番用と、CI・ステージング用の両方で走らせて差分を取ります。片方にしか見えないモデルがあるなら、それはいずれ本番でも消える候補です。
#!/usr/bin/env python3
"""本番キーと CI キーで models.list の結果を比べ、片方にしか見えないモデルを出す。"""
import json
import os
import sys
import urllib.request
ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/models?pageSize=200"
def list_models(api_key):
names = set()
url = ENDPOINT
while url:
req = urllib.request.Request(url, headers={"x-goog-api-key": api_key})
with urllib.request.urlopen(req, timeout=30) as res:
body = json.load(res)
for model in body.get("models", []):
if "generateContent" in model.get("supportedGenerationMethods", []):
names.add(model["name"].replace("models/", "", 1))
token = body.get("nextPageToken")
url = ENDPOINT + "&pageToken=" + token if token else None
return names
def main():
prod_key = os.environ.get("PROD_API_KEY")
ci_key = os.environ.get("CI_API_KEY")
if not prod_key or not ci_key:
print("PROD_API_KEY と CI_API_KEY を環境変数に設定してください", file=sys.stderr)
return 2
prod = list_models(prod_key)
ci = list_models(ci_key)
only_prod = sorted(prod - ci)
only_ci = sorted(ci - prod)
print("本番キーから見えるモデル: %d 件" % len(prod))
print("CI キーから見えるモデル: %d 件" % len(ci))
print("本番にだけ見える:", ", ".join(only_prod) if only_prod else "なし")
print("CI にだけ見える:", ", ".join(only_ci) if only_ci else "なし")
return 1 if (only_prod or only_ci) else 0
if __name__ == "__main__":
raise SystemExit(main())generateContent に対応したモデルだけを拾っているのは、埋め込みや音声のモデルを混ぜると差分が読みにくくなるためです。差分があったときに終了コード1を返すようにしてあるので、そのまま定期実行に載せて、差分が出た日だけ気づく形にできます。
ページングを素直に回している点も、地味ですが必要でした。pageSize を大きめに取っても、提供モデルが増え続けている以上、1ページで収まる保証はありません。
モデルIDは、コードよりも周辺に残ります
呼び出し箇所を直せば終わり、とはいきませんでした。私の手元で残っていたのは、むしろコードの外側です。
grep -rInE 'gemini-(2\.0|2\.5)[a-z0-9.-]*' . \
--include='*.py' --include='*.ts' --include='*.js' --include='*.kt' --include='*.swift' \
--include='*.json' --include='*.yaml' --include='*.yml' --include='*.toml' --include='*.md'このコマンドを自分のリポジトリで回して、次のような場所から出てきました。
- 設定ファイルの既定値(コード側は環境変数で上書きできるのに、既定値が古いまま)
- テストの記録済みレスポンス。中に古いモデルIDが焼き付いていて、テストは緑のまま通り続ける
- 読者向けに書いたコード例と、社内向けの手順書
- App Store と Google Play の説明文、機能紹介のページに書いた「◯◯を使っています」という一文
このうち、静かで厄介なのは2番目です。記録済みのレスポンスを再生しているテストは、実際の入口が閉じても何も言いません。この構造についてはテストは緑のまま、本番だけが止まるで、フィクスチャの鮮度を見張る側から書きました。
4番目は技術的な破損ではありませんが、読んだ人に古い前提を渡し続けます。移行のチェックリストに、コード検索だけでなく文章の検索を1行足しておくと安心です。
置き換え先を二度選ばないための、もう一つの日付
ここで急いで置き換え先を決めると、近いうちにもう一度同じ作業をすることになります。もう一つ日付が控えているためです。
gemini-3.7-flash は8月13日に一般提供へ入りました。ただし公表されている導入価格(100万トークンあたり入力 $0.75・出力 $3.75)は2026年12月31日までで、2027年1月1日からは $1.50 / $7.50 になると案内されています。今日の単価で採用を決めると、年明けに同じ計算をやり直すことになります。
ですから、2.5 系からの移行先を選ぶ判断は、次の二つを同時に置いて決めるのが無駄がありません。
- いま呼べなくなったら困る処理は何か(対話・バッチ・画像・埋め込みで、止まったときの痛みが違います)
- その処理を2027年1月以降も回し続けるのか、年内で役目が終わるのか
年内で終わる処理なら導入価格の恩恵をそのまま受けられます。回し続ける処理なら、倍額を前提にした比較にしておかないと、年明けに二度目の移行が発生します。この観点での使い分けはGemini 3.7 Flash と 3.1 Pro の使い分けを、速さではなくやり直しのコストで決めるで、やり直しのコスト側から整理しています。
なお、404 が返り始めたあとの扱いも決めておいてください。停止済みモデルの404は、待てば直る種類の失敗ではありません。指数バックオフで再試行を重ねると、直らないものを待ち続ける時間だけが積み上がります。この待ち時間が成功側の統計にどう現れなかったかは、停止済みモデルの再試行は、成功レイテンシには出ませんでしたで詳しく扱いました。
今日できることを一つだけ挙げるなら、本番のキーと CI のキーで models.list を叩いて、差分があるかどうかを見ることです。差分がゼロなら、しばらくは表の更新を待つ側に回れます。差分が出たなら、それが公式の告知より早い合図です。
移行の判断材料になれば嬉しく思います。お読みいただきありがとうございました。