8月17日に一部の画像生成モデルが止まる、と気づいたのは公開のわずか3日前でした。個人開発で iOS と Android のアプリを回しながら、複数の技術ブログも運用していると、自分が触っている API の変更履歴を毎日読みに行く余裕は正直ありません。その日はたまたまチェンジログを開いて、締め切りが目前だと知って血の気が引きました。
問題は「情報が出ていなかったこと」ではありません。情報はきちんと公開されていました。私が毎日それを読みに行けなかっただけです。だとすれば、読みに行く作業のほうを自動化すればいい。しかも、公式ページを直接根拠にして、私が実際に使っているモデルの廃止だけを拾ってくれれば、通知は一気に静かになります。
Gemini の url-context を使うと、これが素直に組めます。以下では、公式チェンジログを url-context に読ませ、自分のモデル一覧と突き合わせて、影響のある変更だけを差分で通知する「廃止レーダー」を組み立てます。落とし穴も含めて、そのまま動く形で残します。
グラウンディングと構造化を1回の呼び出しに詰め込まない
最初に設計の勘所を1つだけ共有します。ここを外すと後で必ず作り直しになります。
url-context のようなツールを有効にした呼び出しと、response_schema による厳密な JSON 出力は、同じ1回の generate_content に同居させると挙動が安定しません。ツール側がフェッチした内容を返す経路と、スキーマに沿って構造化する経路が競合し、スキーマが無視されたり、逆にツールが呼ばれなかったりします。私はここで半日溶かしました。
なので、処理を二段に分けます。
段 役割 ツール 出力形式
1段目 公式ページを読んで廃止予告を素のテキストで抜き出す url-context を有効化 プレーンテキスト
2段目 抜き出したテキストと自分のマニフェストを突き合わせ、影響だけを構造化する ツールなし・response_schema のみ 厳密な JSON
一見すると呼び出しが2回になって無駄に見えますが、実運用では逆に安くつきます。1段目は軽量な gemini-flash-latest で十分ですし、2段目に渡すのは抜き出し済みの短いテキストなので、入力トークンが小さく済みます。私の環境では1回あたりの合計で数円に収まり、月に3回動かしても負担になりません。
手順1:自分が実際に使っているモデルとエンドポイントを1枚にする
レーダーの精度は、突き合わせる「自分の側の台帳」で決まります。ここを曖昧にすると、世の中の全廃止予告に反応する騒がしい通知器になってしまいます。
私は、アプリと各サイトで実際に呼んでいるモデル・機能を1枚の YAML にまとめています。ポイントは、どこで使っているか を必ず添えることです。廃止予告を受け取ったとき、真っ先に知りたいのは「どのアプリを直せばいいか」だからです。
# manifest.yaml — 自分が実際に使っている Gemini の面
used :
- id : "gemini-flash-latest"
kind : model
where : [ "wallpaper-app-android" , "labs-auto-post" ]
note : "軽量生成の主力。別名なので静かな昇格に注意"
- id : "gemini-embedding-2"
kind : model
where : [ "labs-related-articles" ]
- id : "url_context"
kind : feature
where : [ "labs-migration-radar" ]
- id : "batch-api"
kind : feature
where : [ "wallpaper-app-review-classify" ]
watch_pages :
- "https://ai.google.dev/gemini-api/docs/changelog"
- "https://ai.google.dev/gemini-api/docs/deprecations"
where に App Store / Google Play 側のアプリ名や、どのブログの自動投稿かを書いておくと、通知を見た瞬間に修正対象が分かります。私はここに AdMob 連携のある画面まで書き込んでいて、モデルを差し替えるときに広告表示の初期化順序を壊さないよう気をつける、というメモ代わりにもしています。
手順2:url-context に変更履歴ページだけを読ませる
1段目です。watch_pages に並べた公式 URL を url-context に渡し、廃止・提供終了に関わる記述だけを素のテキストで抜き出してもらいます。
from google import genai
from google.genai import types
client = genai.Client( api_key = "YOUR_API_KEY" )
def fetch_deprecation_notes (pages: list[ str ]) -> tuple[ str , list[ dict ]]:
url_list = " \n " .join( f "- { u } " for u in pages)
prompt = (
"次の公式ページを読み、モデルや機能の『提供終了・非推奨・廃止・"
"移行が必要』に関する記述だけを、日付と対象名を必ず含めて箇条書きで"
"抜き出してください。該当がなければ『該当なし』とだけ書いてください。 \n "
f " { url_list } "
)
resp = client.models.generate_content(
model = "gemini-flash-latest" ,
contents = prompt,
config = types.GenerateContentConfig(
tools = [types.Tool( url_context = types.UrlContext())],
temperature = 0 ,
),
)
# 取得ステータスの確認(握りつぶし防止)
meta = []
cand = resp.candidates[ 0 ]
if cand.url_context_metadata:
for u in cand.url_context_metadata.url_metadata:
meta.append({
"url" : u.retrieved_url,
"status" : str (u.url_retrieval_status),
})
return resp.text, meta
ここで見落としがちなのが url_context_metadata の確認です。url-context は、ページの取得に失敗しても、それらしい答えを返してくることがあります。取得ステータスを見ずに本文だけを信じると、「今日は該当なし」という誤った安心を掴まされます。私は各 URL の url_retrieval_status を必ずログに残し、SUCCESS 以外が混じった回はレーダーの判定を保留にしています。この握りつぶしの怖さと検証の型は、取得ステータスを検証してから通すゲート設計 に詳しくまとめています。
temperature=0 にしているのは、抜き出しに創造性は要らないからです。同じページなら同じ結果に寄せたい。ここが揺れると、次の差分検知が毎回誤爆します。
手順3:マニフェストと突き合わせ、影響だけを JSON で受け取る
2段目です。1段目のテキストと、手順1のマニフェストを両方渡し、自分が使っている面に関わる変更だけ を構造化してもらいます。ツールは使わず、response_schema だけを効かせます。
from pydantic import BaseModel
class Affected ( BaseModel ):
used_id: str # マニフェストの id と一致させる
change_type: str # deprecation / removal / migration など
deadline: str # 期日(不明なら "unknown")
summary: str # 何が起きるかの一文
action: str # 私が取るべき対応
class Radar ( BaseModel ):
affected: list[Affected]
def diff_against_manifest (notes: str , manifest_yaml: str ) -> Radar:
prompt = (
"以下は公式ページから抜き出した廃止・移行に関する記述です。 \n "
f "--- 記述 --- \n{ notes }\n\n "
"以下は私が実際に使っているモデル・機能の一覧です。 \n "
f "--- 使用一覧(YAML) --- \n{ manifest_yaml }\n\n "
"使用一覧に含まれる id に関係する変更だけを抽出してください。"
"関係しない廃止予告は無視します。used_id は必ず使用一覧の id と"
"一致させ、期日が読み取れない場合は 'unknown' としてください。"
)
resp = client.models.generate_content(
model = "gemini-flash-latest" ,
contents = prompt,
config = types.GenerateContentConfig(
response_mime_type = "application/json" ,
response_schema = Radar,
temperature = 0 ,
),
)
return Radar.model_validate_json(resp.text)
used_id をマニフェストの id と一致させる、という制約が地味に効きます。これがあると、次の差分検知で「同じ項目か」を機械的に判定できます。自由記述の要約だけで差分を取ろうとすると、言い回しが少し変わっただけで別物と見なされ、通知が二重に飛びます。
構造化を別呼び出しに切り出したことで、gemini-flash-latest が指す実体が静かに昇格しても、この段はスキーマで守られます。別名モデルの扱いに不安がある場合は、CI 側で先回りして落とす作りと併用すると安心です。私はモデル非推奨を CI で先回りして止める仕組み と役割分担させ、こちらのレーダーは「気づく」係、CI は「止める」係と分けています。
手順4:前回との差分だけを通知する
ここまでで「今この瞬間に効いている影響」は分かります。ですが、毎回同じ廃止予告が通知されると、人は必ず読み飛ばすようになります。レーダーの価値は、前回から変わった分だけ を届けることにあります。
import json, hashlib
from pathlib import Path
STATE = Path( "radar_state.json" )
def _key (a) -> str :
raw = f " { a.used_id } | { a.change_type } | { a.deadline } "
return hashlib.sha1(raw.encode()).hexdigest()[: 12 ]
def new_items (radar: Radar) -> list[Affected]:
prev = json.loads( STATE .read_text()) if STATE .exists() else {}
now, fresh = {}, []
for a in radar.affected:
k = _key(a)
now[k] = a.model_dump()
if k not in prev:
fresh.append(a)
STATE .write_text(json.dumps(now, ensure_ascii = False , indent = 2 ))
return fresh
差分キーを used_id と change_type と deadline の組み合わせにしているのは、期日が前倒しになったら再通知したい からです。対象は同じでも締め切りが動くのは、対応の優先度が変わる重要な変化です。要約文をキーに含めないのは、言い回しの揺れで誤爆させないためです。
あとは new_items() の戻り値が空でなければ通知するだけです。私は自分宛てのチャットに投げていますが、ここは何でも構いません。大事なのは、静かな日には本当に何も来ないことです。この「何も来ない」を信じられる状態こそ、レーダーの完成形だと考えています。
運用して分かった、過検知と取りこぼしの調整
一度組んで終わり、にはなりませんでした。実際に回すと、通知が騒がしい日と、逆に拾ってほしいのに黙っている日があります。効いた調整を残します。
バージョン別名の揺れを吸収する
gemini-flash-latest のような別名と、gemini-3.1-flash-image-preview のような具体版が、同じ廃止予告の中で混在します。マニフェストの id を別名で書いていると、具体版の廃止予告を取りこぼします。私は、別名を使っている項目には「この別名が現在指している具体版」を note に書き添え、1段目の抜き出しプロンプトで「別名・具体版の両方の記述を残す」よう明示しました。これで取りこぼしが目に見えて減りました。
「予告」と「実施済み」を区別する
チェンジログには、これから止まる予告と、すでに止まった報告の両方が載ります。2段目のスキーマに change_type を持たせ、removal(実施済み)と deprecation(予告)を分けたことで、対応の緊急度を通知の側で色分けできるようになりました。実施済みが自分の使用面に出てきたら、それはもう障害です。
取得失敗を「該当なし」と誤読させない
最初の版では、url-context がページ取得に失敗した回も「該当なし」として静かに通り過ぎていました。手順2の url_retrieval_status 検証を入れる前は、これが一番怖い穴でした。取得に失敗した回は判定を保留し、次回に持ち越す。この一手で、誤った安心を掴む頻度をほぼゼロにできました。
症状 原因 効いた対処
具体版の廃止を取りこぼす マニフェストが別名だけ note に具体版を併記し抜き出しで両方残す
同じ予告が毎回来る 差分キーに要約文を含めていた キーを id・種別・期日の3点に固定
止まった直後に気づけない 予告と実施済みを混同 change_type で分けて緊急度を色分け
「該当なし」を鵜呑み 取得失敗の握りつぶし 取得ステータス検証で保留・持ち越し
この調整を入れる前後で、私の環境では余計な通知がおおよそ9割減りました。数字そのものより、「通知が来たら本当に自分ごとだ」と信じられるようになったことのほうが効きました。
まとめ
大がかりな監視基盤を作る必要はありません。まずやることは1つだけです。今この瞬間、自分のアプリとサイトが呼んでいるモデル名・機能名を、手順1の YAML に書き出してみてください。それだけで、次に廃止予告が出たとき、影響が自分に及ぶかどうかを機械的に確かめる土台ができます。
url-context の面白さは、公式ページという「一番正しい情報源」を、遠回りせず直接根拠にできるところにあると感じています。私自身、これを回し始めてから、変更履歴を毎日開く緊張から解放されました。同じように複数のプロダクトを個人で抱えている方の、静かな安心につながれば嬉しいです。お読みいただきありがとうございました。