Pythonで作るGemini APIキー診断ツールの話
はじめに
プログラミング未経験者の挑戦記録です
私はコードを一行も書けませんが、AIを「デバッグの指揮官」として活用することで、APIの接続障害を自力で特定・解決できました。
コード自体はAI(Gemini/ChatGPT等)が作成したものですが、「どう調査し、どう判断したか」というプロセスの記録として共有します。
このサイトで利用しているAI機能には Google Gemini API を使用しています。
今回の記事は、開発中に遭遇した、APIがうまく動作しない、というトラブルにおいて
- 何が起きているのか
- どこまでが自分のミスで
- どこからが API 側の仕様(または不具合)なのか
これらをハッキリさせるために、Pythonで APIキー診断ツールを自作するに至った経緯をまとめたものです。
① アプリの仕様と機能(簡単に)
今回開発していたのは、今の状態や気分を短い言葉で記録するための個人向けPWAアプリです。
基本はあらかじめ用意された言葉をタップして選ぶほか、必要に応じて自分の言葉を追加できるシンプルな作りです。ここに Gemini API を利用した機能 を組み込んでおり、有効にすると 「選択したカテゴリに応じた言葉の候補をAIが提案してくれる」 という仕様になっています。
本記事で扱うのは、この AI提案機能まわりの API 動作検証と障害切り分け についてです。
内部的な技術構成は以下の通りです。
- サーバー側で Gemini API を直接呼び出す
- API を呼ぶ部分は Python (
google-generativeai) - モデル名を指定して
generateContentを実行 - 生成できなかった場合、画面には「AI休憩中」と表示する
※ ちなみに、エラーコードをそのまま出すとアプリ内の雰囲気になじまないので、画面内ではあえてかわいく「AI休憩中」と表示する仕様にしています。
② 遭遇した事象:「同じコードなのに動かない?」
Googleさんの Antigravity をWindowsにインストールして、開発とテストを進めていたときのことです。
Antigravityが書き出したコードは Gemini 1.5 を指定していました。また、Google AI Studio から発行されたAPIキーも、間違いなく 無料枠 と書いてあります。
それなのに、困ったことが起きました。
- APIキーは有効なはずなのに、画面はずっと「AI休憩中」
- ログを見ると
limit: 0みたいなエラーが出ている - 指定したモデルが動かない
この段階では、原因がさっぱり分かりませんでした。
- APIキーが壊れた?
- 無料枠を使い切った?
- モデルの指定が間違ってる?
- それとも Gemini API 側がダウンしてる?
③ 推測をやめて、Python で診断ツールを作ろう
「たぶんこうだろう」で修正してまたエラーになるのは疲れたし、なにより試行錯誤するだけで Antigravity のトークンが無駄になってしまう ので、APIの状態を直接チェックすることにしました。
そこで ChatGPT に作ってもらったのが、Python製の Gemini APIキー診断ツールです。
やりたいことはひとつだけ。
「この API キーは、今この瞬間に動くのか?」
診断ツールでは、Python から次の処理を行います。
- Gemini API から 今使えるモデルの一覧をもらう
- その中で、文章生成(generateContent)ができるやつだけ選ぶ
- Flash 系 / Pro(Thinking)系に自動で分ける
- それぞれの 一番新しいやつを勝手に選ぶ
- 「ping」みたいな超短い言葉で 実際にテストする
④ 診断ツールのコード
以下が作成したコードです。
旧バージョンのコードを表示する(旧SDK版)
実行には google-generativeai ライブラリが必要です。
pip install google-generativeai
"""
Gemini API 生成対応モデル自動識別・生成診断ツール
- ListModels を唯一の情報源とする
- generateContent 対応モデルのみ抽出
- Flash / Pro 系を自動分類
- 各カテゴリで「最新らしきモデル」を自動選択
- 超短文生成で最終確認
2026 diagnostic edition
"""
import re
import sys
try:
import google.generativeai as genai
from google.api_core import exceptions
except ImportError:
print("必要なパッケージがありません。以下を実行してください:")
print("pip install google-generativeai google-api-core")
sys.exit(1)
def normalize_name(name: str) -> str:
return name.lower()
def extract_date_score(name: str) -> int:
"""
モデル名に含まれる YYYYMM / YYYY-MM / YYYY をスコア化
数値が大きいほど新しいとみなす
"""
matches = re.findall(r"(20\d{2})(\d{2})?", name)
if not matches:
return 0
year, month = matches[-1]
score = int(year) * 100
if month:
score += int(month)
return score
def classify_models(models):
flash = []
pro = []
for m in models:
name = normalize_name(m.name)
if "generatecontent" not in [x.lower() for x in m.supported_generation_methods]:
continue
if "flash" in name:
flash.append(m)
elif any(k in name for k in ["pro", "thinking", "research"]):
pro.append(m)
return flash, pro
def select_latest(models):
if not models:
return None
scored = []
for m in models:
score = extract_date_score(m.name)
scored.append((score, m))
scored.sort(key=lambda x: x[0], reverse=True)
return scored[0][1]
def test_generation(model_name):
print(f"\n--- {model_name} 生成テスト ---")
model = genai.GenerativeModel(model_name)
try:
res = model.generate_content("ping")
print(" ✅ 生成成功")
print(f" 応答: {res.text}")
return "OK"
except exceptions.ResourceExhausted as e:
print(" ⚠ クォータ制限")
print(f" 詳細: {e}")
return "QUOTA"
except exceptions.InvalidArgument as e:
print(" ❌ 無効なリクエスト")
print(f" 内容: {e}")
return "INVALID"
except Exception as e:
print(" ❌ その他エラー")
print(f" 内容: {e}")
return "ERROR"
def main():
print("--- Gemini API 自動モデル識別・生成診断 ---\n")
api_key = input("APIキーを入力してください(このツール内でのみ使用され、保存されません): ").strip()
if not api_key:
print("APIキーが入力されていません。終了します。")
return
genai.configure(api_key=api_key)
# Step 1: ListModels
print("\n[Step 1] モデル一覧取得")
try:
models = list(genai.list_models())
print(f" ✅ {len(models)} モデル取得")
except Exception as e:
print(f" ❌ ListModels 失敗: {e}")
return
# Step 2: generateContent 対応モデル抽出
print("\n[Step 2] generateContent 対応モデル抽出")
gc_models = [
m for m in models
if "generateContent" in m.supported_generation_methods
]
print(f" ✅ generateContent 対応: {len(gc_models)} モデル")
# Step 3: 分類
flash_models, pro_models = classify_models(gc_models)
print(f"\n[Step 3] 分類結果")
print(f" Flash 系: {len(flash_models)}")
print(f" Pro / Thinking 系: {len(pro_models)}")
# Step 4: 最新モデル選定
flash_latest = select_latest(flash_models)
pro_latest = select_latest(pro_models)
print("\n[Step 4] 最新モデル候補")
if flash_latest:
print(f" Flash: {flash_latest.name}")
else:
print(" Flash: 該当なし")
if pro_latest:
print(f" Pro: {pro_latest.name}")
else:
print(" Pro: 該当なし")
# Step 5: 生成テスト
print("\n[Step 5] 生成テスト")
results = {}
if flash_latest:
results[flash_latest.name] = test_generation(flash_latest.name)
if pro_latest:
results[pro_latest.name] = test_generation(pro_latest.name)
# サマリ
print("\n--- 診断サマリ ---")
for name, status in results.items():
print(f"{name}: {status}")
print("\n観測完了。")
if __name__ == "__main__":
main()
【2026年2月追記】Gemini 3.x 世代への対応と新SDKへの移行
Gemini 3.x 世代の登場に合わせて、診断ツールを大幅にアップデートしました。これもAIにコーディングしてもらいました。
主な変更点
-
新SDK
google-genaiへの対応: Google の新しい公式 SDK に合わせてコードを刷新しました - 最新モデル判定ロジックの強化: 世代(3.x等)を優先しつつ、日付スコアを組み合わせることで、より正確に最新モデルを選定できるようにしました
- SDKの不具合対策: モデル一覧取得時にサポートメソッドが空で返ってくるバグを考慮し、モデル名から機能を推測する救済措置を実装しました
新しいツールを使用するには、以下のライブラリが必要です。
pip install google-genai
"""
Gemini API 生成対応モデル自動識別・生成診断ツール (Gemini 3.x 対応版)
- 新しい公式SDK `google-genai` を使用
- ListModels を情報源として取得
- generateContent 対応モデルのみ抽出 (SDKのバグによる欠落回避も実装)
- Flash / Pro / Thinking 系を自動分類
- メジャーバージョン(3.x等)と日付から「最新モデル」を自動選択
- 超短文生成で最終確認
2026 diagnostic edition
"""
import re
import sys
try:
# 新しいSDKのインポート
from google import genai
from google.genai import errors
except ImportError:
print("必要なパッケージがありません。以下を実行してください:")
print("pip install google-genai")
sys.exit(1)
def normalize_name(name: str) -> str:
"""
モデル名を小文字化し、不要なプレフィックスを取り除く
APIから返される名前には 'models/' が付与されることがあるための対策
"""
return name.replace("models/", "").lower()
def extract_date_score(name: str) -> int:
"""
モデル名からバージョン番号(メジャー・マイナー)と YYYYMM / YYYY-MM を抽出してスコア化。
Gemini 3.x などの世代が上がったモデルを確実に「最新」として判定するためのロジック。
数値が大きいほど新しいとみなす。
"""
score = 0
# 1. バージョン番号のスコア化 (例: gemini-3.0 -> major:3, minor:0)
# バージョンが最優先されるよう大きな桁に割り当てる
version_match = re.search(r"gemini-(\d+)\.?(\d+)?", name)
if version_match:
major = int(version_match.group(1))
minor = int(version_match.group(2)) if version_match.group(2) else 0
score += (major * 10000000) + (minor * 1000000)
# 2. 日付のスコア化 (例: 202602 -> 202600 + 2)
matches = re.findall(r"(20\d{2})[-_]?(\d{2})?", name)
if matches:
year, month = matches[-1]
date_score = int(year) * 100
if month:
date_score += int(month)
score += date_score
# 3. 特殊な接尾辞へのスコア加算 (latest や exp はベースモデルより新しいと判定)
if "latest" in name:
score += 90
if "exp" in name or "preview" in name:
score += 50
return score
def classify_models(models):
"""
取得したモデルリストを Flash 系 と Pro/Thinking 系 に分類する
"""
flash = []
pro = []
for m in models:
name = normalize_name(m.name)
if "flash" in name:
flash.append(m)
# Thinking系やResearchモデル、Proモデルはすべて上位系としてまとめる
elif any(k in name for k in ["pro", "thinking", "research", "ultra"]):
pro.append(m)
return flash, pro
def select_latest(models):
"""
スコア計算を用いてリストの中から最も新しいと推定されるモデルを1つ選択する
"""
if not models:
return None
scored = []
for m in models:
score = extract_date_score(normalize_name(m.name))
scored.append((score, m))
# スコアの降順でソートし、トップを返す
scored.sort(key=lambda x: x[0], reverse=True)
return scored[0][1]
def test_generation(client, model_name):
"""
選定されたモデルに対して実際に短いプロンプトで生成テストを行い、状態を診断する
"""
# プレフィックスを削除した純粋なモデル名を使用する
clean_name = normalize_name(model_name)
print(f"\n--- {clean_name} 生成テスト ---")
try:
# 新SDKでのコンテンツ生成メソッド
res = client.models.generate_content(model=clean_name, contents="ping")
print(" ✅ 生成成功")
# 応答の前後の空白を削除して出力
print(f" 応答: {res.text.strip() if res.text else '空の応答'}")
return "OK"
# 新SDK固有のエラーハンドリング
except errors.APIError as e:
if e.code == 429:
print(" ⚠ クォータ制限")
print(f" 詳細: {e.message}")
return "QUOTA"
elif e.code == 400:
print(" ❌ 無効なリクエスト")
print(f" 詳細: {e.message}")
return "INVALID"
else:
print(f" ❌ APIエラー ({e.code})")
print(f" 詳細: {e.message}")
return f"API_ERROR_{e.code}"
except Exception as e:
print(" ❌ その他エラー")
print(f" 内容: {e}")
return "ERROR"
def main():
print("--- Gemini API 自動モデル識別・生成診断 (Gemini 3.x 世代対応) ---\n")
api_key = input(
"APIキーを入力してください(このツール内でのみ使用され、保存されません): "
).strip()
if not api_key:
print("APIキーが入力されていません。終了します。")
return
# クライアントの初期化 (新しいSDKの方式)
try:
client = genai.Client(api_key=api_key)
except Exception as e:
print(f" ❌ クライアント初期化失敗: {e}")
return
# Step 1: ListModels
print("\n[Step 1] モデル一覧取得")
try:
# 新SDKの client.models.list() はイテレータを返すためリスト化する
models = list(client.models.list())
print(f" ✅ {len(models)} モデル取得")
except Exception as e:
print(f" ❌ ListModels 失敗: {e}")
return
# Step 2: generateContent 対応モデル抽出
print("\n[Step 2] generateContent 対応モデル抽出")
gc_models = []
for m in models:
# 新SDKの一部バージョンではメソッドリストがNoneや空で返るバグがあるため安全に取得
methods = getattr(m, "supported_generation_methods", []) or []
methods_lower = [x.lower() for x in methods]
# 明示的にサポートされているか、またはバグで空配列だがモデル名に "gemini" が含まれている場合は通す
if "generatecontent" in methods_lower:
gc_models.append(m)
elif not methods and "gemini" in normalize_name(m.name):
gc_models.append(m)
print(f" ✅ generateContent 対応: {len(gc_models)} モデル")
# Step 3: 分類
flash_models, pro_models = classify_models(gc_models)
print(f"\n[Step 3] 分類結果")
print(f" Flash 系: {len(flash_models)}")
print(f" Pro / Thinking 系: {len(pro_models)}")
# Step 4: 最新モデル選定
flash_latest = select_latest(flash_models)
pro_latest = select_latest(pro_models)
print("\n[Step 4] 最新モデル候補")
if flash_latest:
print(f" Flash: {normalize_name(flash_latest.name)}")
else:
print(" Flash: 該当なし")
if pro_latest:
print(f" Pro: {normalize_name(pro_latest.name)}")
else:
print(" Pro: 該当なし")
# Step 5: 生成テスト
print("\n[Step 5] 生成テスト")
results = {}
if flash_latest:
results[normalize_name(flash_latest.name)] = test_generation(
client, flash_latest.name
)
if pro_latest:
results[normalize_name(pro_latest.name)] = test_generation(
client, pro_latest.name
)
# サマリ
print("\n--- 診断サマリ ---")
for name, status in results.items():
print(f"{name}: {status}")
print("\n観測完了。")
if __name__ == "__main__":
main()
(余談)APIキー漏洩事件
本来なら、このツールで「判定」して済めばよかったのですが……。
自分の不手際で、APIキーを書いてあったメモ帳ごと GitHub にコミットしてしまう というやらかしをしてしまいました。(このあとAntigravityに正しいやり方を教わりました)
慌てて Google Cloud の管理画面から、APIキーを発行したプロジェクトをいったん削除し、新しいプロジェクトを作って APIキーを再発行しました。
(APIキーの扱いは本当に気をつけましょう……)
⑤ わかったこと:UIの優しさと、APIの厳格さ
気を取り直して再発行したキーでツールを実行してみると、アプリ上では一言で「休憩中」としていた裏側で、何が起きていたかが見えてきました。
以下が実際の診断ツールの出力ログです。※アップデート前の生ログです。
[Step 1] モデル一覧取得
✅ 54 モデル取得
[Step 2] generateContent 対応モデル抽出
✅ generateContent 対応: 34 モデル
[Step 3] 分類結果
Flash 系: 18
Pro / Thinking 系: 7
[Step 4] 最新モデル候補
Flash: models/gemini-2.5-flash-preview-09-2025
Pro: models/deep-research-pro-preview-12-2025
[Step 5] 生成テスト
--- models/deep-research-pro-preview-12-2025 生成テスト ---
⚠ クォータ制限
詳細: 429 You exceeded your current quota, please check your plan and billiequests"er_input_token_count, limit: 0, model: deep-research-pro-previews(以下省略)
実際に返ってきたエラーを見ると、Flash系は通るのに、Pro系はクォータ超過(You exceeded your current quota)ではじかれている ことがはっきりしました。
つまり、APIが止まる理由は「単純な使いすぎ」だけではありませんでした。
「今のプランでは、この特定のモデル(Pro版など)を使う枠が足りません(あるいは枠自体の設定がない)」
というように、モデルごとに細かく制限がかかっていたのです。
ユーザー向けに「AI休憩中」と表現するのはUI体験として正解でしたが、開発者としてはその言葉に甘えず、**「どのモデルが、どういう理由で制限されているのか」**までログで見ないと、本当の状況は掴めないんだなと勉強になりました。
解決編:Flashモデルへの移行
診断ツールの結果を見て、自分のなかである結論に至りました。
「このアプリ、短い言葉を提案するだけだし、高機能な Pro モデルじゃなくても Flash モデルで十分だ」
ログが「Pro モデルは全滅でも Flash モデルは元気に pong を返しています。」と言っているので、それに乗っかろうと決めました。
そこで、元のアプリ側のプログラムにも、この診断ツールで得た知見を活かして**「動的にモデルの状態をチェックし、使えるモデル(主にFlash)を優先的に使う処理」**を組み込みました。
すると……
あれだけ消えなかった「AI休憩中」の表示がついに消え、サクサクとAIが言葉を提案してくれるようになりました! 完全解決です!(*^○^*)
(※ アプリ側の具体的な修正コードについては、使用する言語やフレームワークによって実装方法が大きく異なるため、ここでは割愛します。要は「決め打ち」をやめて「生きているモデルを選ぶ」ロジックに変えただけです)
⑥ 補足:このツールのスタンス
このツールはあくまで個人用のデバッグ用です。
- APIキーは実行時に手入力(ファイル保存しない)
- セキュリティとかはあまり考えてない
また、コード自体は AI に書いてもらいましたが、
- どこをチェックするか(モデル一覧と生成テスト)
- 何を「異常」と判断するか
といった 「調査の方針」と「結果の読み解き」は自分でやりました。「動いた/動かない」だけで終わらせず、「なんで?」をログから追えるようにするのが大事ですね…!
おわりに
今回の件で強く感じたのは、
APIのエラーには、いろんな事情がある
ということです。
「AI休憩中」という表現はユーザーを刺激しないための工夫ですが、開発者側はその裏にある API の仕様やクォータ情報 を冷静に見つめる必要があることに気がつきました。
「AIが止まった、壊れたかな?」と焦る前に、
「お、今はFlashなら通るのか」とか「Pro版だけ制限がかかってるな」といった風に、事実を切り分けて観測する。
そんな泥臭い確認作業こそが、AIアプリを安定させる近道なんだなと学びました。
もし同じように「コードは合っているはずのになぜか動かない」と悩んでいる方がいれば、この記事が解決への小さなヒントになることを願っています。
免責事項
- 本記事の内容は、特定の時点・特定のアカウント環境での検証結果に基づいています
- Gemini API の仕様や無料枠の制限は、これから変わるかもしれません
- あくまで「ある個人の環境ではこうだったよ」という記録として参考にしてください