6月25日に、Gemini API の画像生成 preview モデル 2 種類 — gemini-3.1-flash-image-preview と gemini-3-pro-image-preview — が停止します。公式の非推奨スケジュール に予告されている確定済みの期限で、残りはおよそ 2 週間です。
私自身、個人開発の壁紙アプリ向けに画像バリエーションを生成するバッチ処理を運用しており、メインの経路は移行済みのつもりでいました。ところが棚卸しをしてみると、試作用に書いた横道のスクリプトに preview 版の参照がしっかり残っていました。着手前は「モデルIDを書き換えるだけ」と見積もっていた作業ですが、実際に手を動かすと、ID の思い込みで 404 を受け取ったり、停止後には二度と実行できない検証の存在に気づいたりと、想定より確認事項の多い移行でした。
以下、棚卸しから切り替え完了までに行った手順と、preview 版・GA 版で確認できた差分を、動くコードと一緒に残しておきます。
何が止まり、何に乗り換えるのか
停止するのは画像生成専用の preview モデル 2 つで、移行先はそれぞれの GA 版です。GA 版は 5月28日にすでに一般提供が始まっているため、「移行先がまだ存在しない」という種類の心配はありません。
gemini-3.1-flash-image-preview → 移行先は gemini-3.1-flash-image
gemini-3-pro-image-preview → 移行先は gemini-3-pro-image
ここでひとつ、私が最初に踏んだ落とし穴を共有します。pro 系の GA 版を gemini-3.1-pro-image と書いて、404 NOT_FOUND を受け取りました。flash 系には「3.1」が付くのに、pro 系の正式IDは「3」のままで、preview 版の名前から -preview を外しただけの形が正解でした。プロダクト紹介の文章では「Gemini 3.1 Pro Image」とまとめて表記されることもあるため、記憶を頼りにIDを推測すると、私と同じ回り道をすることになります。
なお、これは Gemini 3.2 Flash 本体が持つ Image Output とは別系統の、画像生成専用モデルの話です。本体側の画像出力だけを使っている場合、今回の停止の影響は受けません。
ちなみに 6 月の Gemini API は移行期限が立て続けにやってきます。6月8日に Interactions API の旧スキーマが削除され(このときの対応はGemini Interactions API の outputs が使えなくなったら — 6月6日廃止の旧スキーマから steps へ移行する手順 に書きました)、6月18日には Gemini CLI と Code Assist IDE 拡張の個人向け提供が終わり、6月25日が今回の画像 preview モデルです。期限をまとめてカレンダーに入れておくと、こうした作業の取りこぼしが減ります。
既存コードの棚卸し — モデルIDはコードの外にも潜んでいます
最初にやるべきことは、preview モデルへの参照を漏れなく見つける作業です。私はモデルIDの完全一致ではなく、image-preview という部分文字列で広めに検索しました。エイリアス定数や設定ファイルの中で省略形になっていても引っかかるからです。
# モデルID全体ではなく "image-preview" で広めに探します
grep -rn "image-preview" \
--include= "*.py" --include= "*.ts" --include= "*.js" \
--include= "*.env*" --include= "*.yaml" --include= "*.yml" \
--include= "*.json" --include= "*.toml" \
.
私の環境でこの検索に引っかかったのは、Python スクリプト 2 箇所と .env ファイル 1 箇所でした。そして危なかったのが、GitHub Actions のワークフロー定義に直書きしていた環境変数です。リポジトリ直下で検索していれば見つかりますが、「コードは直した、設定も直した」と安心した頭では見落としやすい場所でした。
コードの外にも置き場所はあります。Firebase Remote Config やデータベースの設定テーブルにモデルIDを持たせている構成なら、grep では見つかりません。配信側の管理画面を開いて、値を直接確認してください。
モデル廃止対応の一般的な流れはGemini API モデル非推奨・移行エラーの対処法 に整理していますが、今回は対象が画像系 2 モデルに絞られているぶん、棚卸しさえ正確なら作業量は小さく済みます。
移行先モデルIDの確定 — 思い込みで書き換えない
先ほどの 404 の経験から、私は移行先のIDを「ドキュメントで読んだ記憶」ではなく、自分の API キーで実際に見えるモデル一覧から確定するようにしました。
import os
from google import genai
client = genai.Client( api_key = os.environ[ "GEMINI_API_KEY" ])
# 画像系モデルだけを抽出して表示します
for m in client.models.list():
if "image" in m.name:
print (m.name)
期待する出力はおおよそ次のとおりです(6月25日までは preview 版も並んで見えます)。
models/gemini-3.1-flash-image
models/gemini-3-pro-image
models/gemini-3.1-flash-image-preview
models/gemini-3-pro-image-preview
この「一覧を正とする」進め方には、もうひとつ利点があります。AI Studio のキーと Vertex AI とでは、同じモデルでも見え方や提供タイミングが微妙に違うことがあります。自分の環境の一覧を見てから書き換えれば、「ドキュメントには載っているのに自分のキーでは 404」という種類の混乱を最初から避けられます。
コード差分 — Python SDK の Before/After
生成呼び出しの置き換え
呼び出し側の差分は、モデルIDの 1 行だけです。ただし、この機会にIDをコードから環境変数へ逃がしておくことをお勧めします。次の停止期限が来たとき、デプロイなしで切り替えられるようになります。
import os
# Before: preview 版(6月25日以降は 404 になります)
# resp = client.models.generate_content(
# model="gemini-3.1-flash-image-preview",
# contents=prompt,
# config=config,
# )
# After: GA 版。モデルIDは環境変数で差し替え可能にしておきます
IMAGE_MODEL = os.environ.get( "IMAGE_MODEL" , "gemini-3.1-flash-image" )
resp = client.models.generate_content(
model = IMAGE_MODEL ,
contents = prompt,
config = config,
)
レスポンス処理は変えずに済みました
移行前に身構えていたのがレスポンス構造の変化ですが、ここは preview 版と同じでした。画像は引き続き inline_data を持つ part として返ってきます。
import os
from google import genai
from google.genai import types
client = genai.Client( api_key = os.environ[ "GEMINI_API_KEY" ])
resp = client.models.generate_content(
model = os.environ.get( "IMAGE_MODEL" , "gemini-3.1-flash-image" ),
contents = "淡い水彩で描いた朝の海岸線。縦長のスマートフォン壁紙向けの構図" ,
config = types.GenerateContentConfig(
response_modalities = [ "TEXT" , "IMAGE" ],
),
)
for part in resp.candidates[ 0 ].content.parts:
if part.inline_data:
with open ( "output.png" , "wb" ) as f:
f.write(part.inline_data.data)
print ( "saved: output.png" )
# 期待する出力:
# saved: output.png
ひとつだけ注意点があります。response_modalities の指定は GA 版でも引き続き必要でした。「GA になったのだから既定で画像が返るだろう」と考えて外してみたところ、テキストだけが返ってくる挙動になったので、ここは preview 時代の書き方をそのまま残してください。
TypeScript 側 — 404 と 429 を同じ扱いにしない
Node.js 側の差分も中身は同じですが、エラーハンドリングだけは移行に合わせて見直す価値があります。停止後の preview ID が返す 404 は、何度リトライしても回復しません。一方で 429 や 503 は時間をおけば直る失敗です。この 2 つを同じリトライループに入れると、モデル停止のときにジョブキューが無駄な再試行で埋まります。
import { GoogleGenAI } from "@google/genai" ;
const ai = new GoogleGenAI ({ apiKey: process.env. GEMINI_API_KEY });
const IMAGE_MODEL = process.env. IMAGE_MODEL ?? "gemini-3.1-flash-image" ;
async function generateImage ( prompt : string ) : Promise < Buffer > {
try {
const resp = await ai.models. generateContent ({
model: IMAGE_MODEL ,
contents: prompt,
config: { responseModalities: [ "TEXT" , "IMAGE" ] },
});
const part = resp.candidates?.[ 0 ]?.content?.parts?. find (
( p ) => p.inlineData,
);
if ( ! part?.inlineData?.data) {
throw new Error ( "image payload missing" );
}
return Buffer. from (part.inlineData.data, "base64" );
} catch (err) {
const status = (err as { status ?: number }).status;
if (status === 404 ) {
// モデル停止 or ID間違い。リトライしても回復しないため即時アラート
throw err;
}
if (status === 429 || status === 503 ) {
// 一時的な失敗。指数バックオフ付きのリトライに回します
}
throw err;
}
}
ちょうど今週、Gemini 側で error 1076 / 1099 が広く発生する大きめの障害がありました。私のバッチは「失敗したジョブはキューに退避して翌朝に再試行」という設計にしていたため、障害中の生成ジョブは翌朝のリトライで自然に回復しました。404 系と一時障害系を分けておく設計は、モデル停止対応と障害対応の両方に同じ形で効きます。
停止前の今しかできない回帰チェック
私はこの工程を、今回の移行の本体だと考えています。6月25日を過ぎると preview 版は呼び出せなくなるため、「新旧モデルで同じプロンプトを流して出力を比べる」という検証は、停止前の今しか実行できません。
私は運用中のプロンプトから代表的な 60 本を固定セットとして選び、preview 版と GA 版の両方で生成して保存しました。
import os
import time
from google import genai
from google.genai import types
PROMPTS = [
"淡い水彩で描いた朝の海岸線。縦長のスマートフォン壁紙向けの構図" ,
"夜の都市を俯瞰したミニマルなローポリイラスト" ,
# 実運用のプロンプトから代表的なものを選びます(私は60本にしました)
]
MODELS = {
"preview" : "gemini-3.1-flash-image-preview" ,
"ga" : "gemini-3.1-flash-image" ,
}
client = genai.Client( api_key = os.environ[ "GEMINI_API_KEY" ])
def generate (model_id: str , prompt: str , out_path: str ) -> float :
"""1枚生成して保存し、所要秒数を返します。"""
start = time.time()
resp = client.models.generate_content(
model = model_id,
contents = prompt,
config = types.GenerateContentConfig(
response_modalities = [ "TEXT" , "IMAGE" ],
),
)
for part in resp.candidates[ 0 ].content.parts:
if part.inline_data:
with open (out_path, "wb" ) as f:
f.write(part.inline_data.data)
return time.time() - start
for i, prompt in enumerate ( PROMPTS ):
for label, model_id in MODELS .items():
os.makedirs( f "regression/ { label } " , exist_ok = True )
sec = generate(model_id, prompt, f "regression/ { label } / { i :03d } .png" )
print ( f "[ { label } ] # { i :03d } { sec :.1f } s" )
結果は次のとおりでした。
60 本中 58 本は、並べて見ても私には区別がつかない仕上がりでした
2 本は preview 版では生成されていたのに、GA 版では安全フィルタに止められました。人物の構図を細かく指定していたプロンプトで、表現を少し具体的に書き直したら通るようになりました
生成時間の中央値は preview 6.8 秒 → GA 6.5 秒で、体感できる差はありません
費用も私の請求ベースでは 1 枚あたりおよそ $0.04 前後のままでした。ただし料金は構成や解像度で変わるので、公式の料金ページ で自分の利用形態を確認してください
数字だけ見ると「ほぼ同じ」ですが、安全フィルタの 2 本は、回帰チェックなしでは本番で初めて発覚していたはずです。プロンプトが資産になっている運用ほど、この比較は停止前にやっておく価値があります。
切り替えの順序 — フォールバック先を preview にしない
切り替えそのものは環境変数の書き換えだけですが、順序と「フォールバックの向き」は設計し直しました。
ありがちなのが「新モデルで失敗したら旧モデルに戻す」という安全策です。今回のケースでこれをやると、6月25日以降は GA 版の一時的な失敗が、停止済み preview 版への 404 リクエストに変換されて二重に壊れます。フォールバックを置くなら、向き先は同系統の GA モデル(flash で失敗したら pro、またはその逆)か、生成をスキップしてジョブをキューに退避する形が筋です。
私の切り替え手順は次のようにしました。
環境変数 IMAGE_MODEL を GA 版に変更し、次のバッチから全量を GA 版で生成する
3 日間は比較用に preview 版の生成も少量だけ並走させる(回帰セットの再確認用)
並走で問題が出なければ、preview 版への参照をコードと設定から削除する
6月24日までに、フォールバック経路を含めて image-preview の grep が 0 件になっていることを確認する
バッチ処理ではなくユーザー向けのリアルタイム生成を運用している場合は、1 を「トラフィックの一部だけ GA に向ける」段階的な切り替えに置き換えると安全です。
なお、私の壁紙バリエーション生成の本流は、Gemini 3.2 Flash の Image Output で壁紙アプリの色違いバリエーション生成パイプラインを設計した実装メモ — Imagen 4 との使い分けと月額APIコストの実測 に書いた構成から大きくは変えていません。今回直したのは、その横で動いていた試作用スクリプトのほうです。本流が無事でも横道が壊れるのが廃止対応の常なので、棚卸しの範囲は「いま動いているもの全部」にしてください。
6月25日までの作業メモ
私が実際に使った作業の区切りを、所要時間の目安と一緒に置いておきます。
棚卸し(30 分): image-preview で grep する。CI 定義・環境変数・Remote Config も対象に入れる
移行先IDの確認(10 分): models.list で自分のキーから見えるIDを確定する
置き換え(30 分): モデルIDを環境変数化して GA 版に差し替える
回帰チェック(放置で半日): 固定プロンプトで新旧両方を生成し、目視と数値で比較する
切り替えと並走(3 日): 全量を GA 化し、preview は比較用の少量のみ残す
完全削除(6月24日まで): フォールバック経路も含めて preview 参照を 0 にする
この種の停止対応は、期限の当日ではなく、何も起きていない平常時に済ませておくのが安全です。今週の障害のさなかにモデル移行まで重なっていたらと想像すると、早めに着手して正解だったと感じています。
まずは手元のリポジトリで grep -rn "image-preview" . を実行するところから始めてみてください。参照が 1 件もなければ、この停止はあなたには関係のない話で終わります。残っていたら、上の手順がそのまま使えるはずです。同じ移行作業を控えている方の参考になれば幸いです。