◉GEMINI LABEN
●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…
記事一覧/開発ツール
⟐ 開発ツール/2026-07-09上級

例外にならない失敗を型で閉じる — Gemini API のレスポンスを判別可能ユニオンに正規化する

HTTP 200 なのに本文が空、という失敗は try/catch では捕まりません。finishReason と blockReason を判別可能ユニオンに正規化し、never による網羅性チェックで処理漏れをコンパイル時に潰す設計をまとめました。

Gemini API248TypeScript8エラーハンドリング型安全本番運用49

✦ プレミアム記事

深夜、壁紙アプリの説明文生成バッチが「成功 1,842 件」と表示したまま終わりました。翌朝、Firestore を覗くと空文字のドキュメントが 20 件ほど混ざっていました。

例外は一つも投げられていません。HTTP ステータスは全て 200。SDK も何も文句を言いませんでした。ただ response.text が空文字だっただけです。

このとき私が書いていたコードは、こういう形でした。

try {
  const res = await ai.models.generateContent({ model, contents });
  await save(res.text ?? "");   // ← ここが空文字を静かに通す
} catch (e) {
  logger.error(e);
}

個人開発では、こういう失敗の見つかり方が一番こたえます。誰も監視していない時間帯に、静かにデータだけが痩せていきます。

try/catch は「投げられた失敗」しか捕まえません。Gemini API の失敗の多くは、投げられずに、正常なレスポンスの形をして返ってきます。この記事は、その取りこぼしを型システムで塞ぐまでの実装記録です。

Gemini API の失敗は三層に分かれています

整理してみると、失敗は発生場所によって三層に分かれていました。

層例捕まえ方
トランスポート層タイムアウト、DNS 解決失敗、接続断例外が投げられる
HTTP 層400 INVALID_ARGUMENT、429、500、503例外が投げられる
意味論層candidates 空、finishReason: SAFETY / MAX_TOKENS / RECITATION投げられない。自分で見る

上二層は既に多くの記事で語られていますし、私も429 の見分け方についての記事で扱いました。問題は三層目です。

意味論層の失敗は、SDK にとっては成功です。レスポンスは組み立てられ、promptFeedback や candidates[0].finishReason に事情が書き込まれ、そのまま返されます。呼び出し側が読まなければ、それは無かったことになります。

私が空文字を保存してしまったのは、まさにこの層でした。

起こりうる結果を、まず全部書き出す

型を書く前に、実際のレスポンスがどんな形を取りうるかを列挙しました。SDK のレスポンスから読める分岐は、私の用途では次の5つに収まりました。

  1. promptFeedback.blockReason が立っている(入力が止められた。candidates は存在しない)
  2. candidates が空配列(モデルが何も返さなかった)
  3. finishReason: "STOP" で本文あり(唯一の成功)
  4. finishReason が "MAX_TOKENS" / "SAFETY" / "RECITATION" / "OTHER"(出力側で止まった)
  5. finishReason: "STOP" なのに本文が空文字(構造化出力でスキーマに合わず、空を返してくる場合に遭遇しました)

5番目は特に厄介でした。finishReason だけを見て成功と判定すると、静かに空文字が通ります。前掲のバッチが 20 件の空ドキュメントを作ったのは、この経路です。

つまり判定は「finishReason が STOP か」ではなく、「STOP であり、かつ本文が非空か」でなければなりません。当たり前のようでいて、コードにはこの かつ が書かれていませんでした。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦HTTP 200 で返る5種類の失敗(空 candidates / SAFETY / MAX_TOKENS / RECITATION / OTHER)を漏れなく列挙する方法
✦判別可能ユニオンと never チェックで、新しい finishReason の追加をコンパイルエラーとして検出する実装
✦失敗種別ごとのリトライ可否表と、30日間の実運用ログから得た失敗内訳の実測値
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

⟐ 開発ツール2026-04-22
Gemini API × Cloud Tasks で作る非同期AIジョブキュー — タイムアウトとリトライを設計から解く
Cloud Run のタイムアウトと Gemini API のレート制限に挟まれて落ちる同期実装から、Cloud Tasks を軸にした非同期ジョブキュー設計へ移行する実践ガイドです。リトライ・DLQ・進捗通知まで本番で通る形で解説します。
⟐ 開発ツール2026-09-10
浮世絵カタログの表記ゆれ照合を、正規化と候補列挙の二層で組む手順
同じ絵師名が三通りに散らばったカタログを揃えるとき、Gemini に同一判定をさせると世代違いが混ざります。正規化を先に置き、モデルには候補の列挙だけを頼む二層構成を、動くコードと費用の見積もりつきでお伝えします。
⟐ 開発ツール2026-09-06
アイコンだけのボタンは、翻訳が揃っていても読み上げでは無言になります
翻訳ファイルが揃っていても、読み上げラベルの穴は残ります。iOS と Android からアイコン要素とラベルを集め、機械で決まる不備をローカルで確定させてから Gemini に文言だけを渡す二段構えを、実際の出力とともに書き残します。