使っているモデル ID を一行ずつ書いた models_in_use.txt と、廃止表から写した TSV を突き合わせて、危ない行だけを出します。
#!/usr/bin/env python3"""使用中のモデル ID を、Gemini 廃止表と突き合わせて警告する。deprecations.tsv の形式(タブ区切り・ヘッダなし): model<TAB>shutdown(YYYY-MM-DD または空)<TAB>recommended_replacement(空可)exit code は 0 = 問題なし / 1 = 要対応。CI に置くとそのまま落ちます。"""import sysfrom datetime import date, timedeltaWARN_WINDOW = 90 # 何日先までを「まもなく」とみなすかdef load_table(path): table = {} with open(path, encoding="utf-8") as f: for line in f: row = line.rstrip("\n").split("\t") if len(row) < 3 or not row[0].strip(): continue model, shutdown, replacement = (c.strip() for c in row[:3]) table[model] = ( date.fromisoformat(shutdown) if shutdown else None, replacement or None, ) return tabledef audit(in_use, table, today): """戻り値は (深刻度, モデル, 一行メッセージ) のリスト。""" findings = [] for model in in_use: if model not in table: # 表にない = 廃止対象として告知されていない、か、綴り間違い。 findings.append(("WARN", model, "廃止表に行がありません。ID の綴りを確認してください")) continue shutdown, replacement = table[model] if shutdown and shutdown <= today: findings.append(("FAIL", model, f"停止日 {shutdown} を経過しています")) elif shutdown and shutdown - today <= timedelta(days=WARN_WINDOW): findings.append(("FAIL", model, f"停止日 {shutdown}(残り {(shutdown - today).days} 日)")) # ここが今回の本題。移行先そのものが生きているかを見る。 if replacement: if replacement not in table: findings.append(("WARN", model, f"推奨代替 {replacement} が廃止表にありません")) else: rep_shutdown = table[replacement][0] if rep_shutdown and rep_shutdown <= today: findings.append(("FAIL", model, f"推奨代替 {replacement} は {rep_shutdown} に停止済みです")) elif rep_shutdown: findings.append(("WARN", model, f"推奨代替 {replacement} にも停止日 {rep_shutdown} があります")) return findingsdef main(): table = load_table(sys.argv[1]) with open(sys.argv[2], encoding="utf-8") as f: in_use = [l.strip() for l in f if l.strip() and not l.startswith("#")] findings = audit(in_use, table, date.today()) for level, model, message in findings: print(f"{level}\t{model}\t{message}") if not findings: print("OK\t-\t使用中のモデルに未対応の廃止はありません") sys.exit(1 if any(l == "FAIL" for l, _, _ in findings) else 0)if __name__ == "__main__": main()
やってはいけないのは、無言で空を返すことです。冒頭で私が踏んだのがまさにこれで、分類が空で返ってきても画面上は何事もなく、原因に気づくまでのあいだ、空のカテゴリが配られておりました。似た形の静かな失敗は OS の非推奨 API でも起きます(iOS 27 の新機能を Rork に頼むと、エラーにならないまま古い書き方へ戻ります にその例をまとめております)。
出荷済みのバージョンには、もう手が届きません
モデル ID をサーバへ出す設計に変えても、すでに配ってしまったバージョンは古いままです。ここだけは、あとから直せません。