◉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 し…
記事一覧/API / SDK
◈ API / SDK/2026-04-29上級

Gemini API × Inngest で耐障害性の高いAIワークフローを構築する本番運用ガイド

Gemini API のレート制限・タイムアウト・部分失敗に強いワークフローを、Inngest の durable execution で組む本番ガイドです。最小構成から fan-out / fan-in の並列処理、人間の承認待ち、予算ガードレール、dead-letter と監視まで進めます。

gemini-api288inngestdurable-executionworkflow8production105

✦ プレミアム記事

「Gemini API を呼ぶ箇所まではうまくいくのに、その後の DB 書き込みや別サービスへの通知が落ちると、最初からやり直すしかない」——そんな相談を、ここ数ヶ月で何度か耳にしました。Gemini を本番に乗せる段階で多くの方がぶつかるのは、API 単体の使い方ではなく、その前後の処理を含めた長時間ジョブを「途中で落ちても安全に再開できる」ように作る部分です。

私は個人のアプリ開発で数年前から似た問題に悩まされてきました。AdMob 連携、画像生成、サーバーレス CRON、外部 API のリトライ。試行錯誤の末、現在は Inngest を使った durable execution(永続実行)に落ち着いています。Cloud Tasks や BullMQ と比べても、Gemini のような「途中で何が起きるか分からない外部 API」を組み込むワークフローには明確に向いている、というのが3週間ほど運用した上での率直な印象です。

このガイドでは、Gemini API と Inngest を組み合わせて「落ちても再開できる」AI ワークフローを設計するための具体的なコードと、本番で必ず必要になる落とし穴の回避策を、実装例とともに整理します。コピペで動くコードを並べるので、手元のエディタを開きながら読み進めていただけたら嬉しいです。

なぜ Gemini API には durable execution が必要なのか

「durable execution(永続実行)」は耳慣れない言葉ですが、要点は 「関数の途中の状態をエンジンが永続化し、失敗したステップだけを再実行できる」 という設計思想です。Inngest 以外にも Temporal や Restate が同じ思想で作られています。

Gemini API を本番で叩いていると、以下のような現実によく遭遇します。

  • レート制限(HTTP 429)が瞬間的に発生し、リトライしないと処理が止まる
  • ストリーミングが途中で切れ、レスポンスの最後の chunk だけが届かない
  • マルチモーダル入力(動画・PDF)の解析中にタイムアウトする
  • Function Calling の戻り値を使って外部 API を叩く部分で別の障害が発生する

ナイーブに try/catch と setTimeout でリトライを書くと、リトライのたびにトークン消費が発生する点が痛いところです。1万件の生成バッチで途中5,000件目で失敗したら、最初からやり直すと API コストが倍になります。これを「失敗したステップだけを再実行する」のが durable execution の本質です。

Inngest を選んだ理由は次の3つです。

第一に、ステップ単位での冪等性が言語レベルで強制されること。step.run('name', fn) で囲んだ処理は、成功すれば結果がキャッシュされ、再実行時はスキップされます。Gemini API 呼び出し1回ごとに step.run で囲むだけで、無駄な再呼び出しを避けられます。

第二に、ローカル開発体験が良いこと。npx inngest-cli@latest dev でローカルダッシュボードが起動し、実行中の関数のステップが時系列で可視化されます。Cloud Tasks のように「ローカルでは動かして本番でしか挙動が再現しない」という辛さがありません。

第三に、TypeScript ファーストで型がしっかり効くこと。イベントペイロードの型を定義すれば、関数側で補完が効き、本番でペイロードのキー名が変わったときにビルドエラーで気づけます。

Step 1: 最小構成の Gemini × Inngest ワークフロー

まずは「ユーザーから記事生成リクエストを受け付け、Gemini で本文を生成し、結果を DB に保存する」という最小フローを書きます。Next.js App Router での実装例です。

依存関係は次の通りです。@google/genai は2026年4月時点での Gemini 公式 SDK 名で、旧 @google/generative-ai は非推奨になっています。

npm install @google/genai inngest
npm install -D @types/node typescript

Inngest クライアントとイベント型の定義から始めます。

// src/inngest/client.ts
import { Inngest, EventSchemas } from "inngest";
 
type Events = {
  "article/generate.requested": {
    data: {
      userId: string;
      topic: string;
      lengthHint: "short" | "medium" | "long";
      requestId: string; // 冪等性キー
    };
  };
};
 
export const inngest = new Inngest({
  id: "gemilab-content-pipeline",
  schemas: new EventSchemas().fromRecord<Events>(),
});

requestId を必ず受け取る点が重要です。後段の処理で「同じリクエストに対する再試行か、別のリクエストか」を判別する基準になります。クライアント側で UUID v7 を発行する運用を私は好みます。

次に Gemini を呼び出す関数を書きます。

// src/inngest/functions/generate-article.ts
import { GoogleGenAI } from "@google/genai";
import { inngest } from "../client";
 
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY! });
 
export const generateArticle = inngest.createFunction(
  {
    id: "generate-article",
    // 同じ userId からの並列実行を1件に制限(暴走防止)
    concurrency: { key: "event.data.userId", limit: 3 },
    // バックオフを伴うリトライ。Geminiの429は短期復旧することが多いため指数バックオフが効きやすい
    retries: 4,
  },
  { event: "article/generate.requested" },
  async ({ event, step, logger }) => {
    const { topic, lengthHint, requestId } = event.data;
 
    // ステップ1: アウトライン生成(失敗してもここだけ再実行される)
    const outline = await step.run("generate-outline", async () => {
      const res = await ai.models.generateContent({
        model: "gemini-2.5-pro",
        contents: `次のトピックの記事アウトラインをH2見出し5つで提案してください。\nトピック: ${topic}\n長さ目安: ${lengthHint}`,
        config: { temperature: 0.4, responseMimeType: "text/plain" },
      });
      return res.text ?? "";
    });
 
    // ステップ2: 本文生成(outlineが既に得られていれば、再実行時はステップ1をスキップ)
    const body = await step.run("generate-body", async () => {
      const res = await ai.models.generateContent({
        model: "gemini-2.5-pro",
        contents: `以下のアウトラインに沿って記事本文を書いてください。\n\n${outline}`,
        config: { temperature: 0.6, maxOutputTokens: 8000 },
      });
      return res.text ?? "";
    });
 
    // ステップ3: DB保存(外部APIなのでもちろん step.run で囲む)
    await step.run("save-to-db", async () => {
      await fetch(`${process.env.API_BASE_URL}/articles`, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ requestId, topic, body }),
      });
    });
 
    logger.info("article generated", { requestId, topicLength: topic.length });
    return { requestId, bodyLength: body.length };
  },
);

ここで一番伝えたいのは、step.run で囲むかどうかが、本番で泣くか笑うかの分かれ道だということです。step.run を外して書くと、retry のたびに最初の outline 生成からやり直されます。Gemini 2.5 Pro のトークン単価は決して安くないので、これは数日でコストの問題として顕在化します。

最後に Next.js から Inngest 関数を公開します。

// src/app/api/inngest/route.ts
import { serve } from "inngest/next";
import { inngest } from "@/inngest/client";
import { generateArticle } from "@/inngest/functions/generate-article";
 
export const { GET, POST, PUT } = serve({
  client: inngest,
  functions: [generateArticle],
});

ローカルでは npx inngest-cli@latest dev を立ち上げ、http://localhost:3000/api/inngest を register URL として指定すれば、ダッシュボードからイベントを発火させてフローを目視で確認できます。

✦

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

この記事の続きを読む

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

この記事で得られること
✦Gemini API の途中失敗・タイムアウト・レート制限で詰まっていた長時間ジョブを、Inngestの自動リトライとステップ単位の冪等性で安定稼働させられるようになる
✦fan-out / fan-in・人間による承認待ち・スロットリング・dead-letter といった本番ワークフローのパターンを、コピペ可能なTypeScriptコードで習得できる
✦コスト超過と暴走を防ぐ予算ガードレール、観測可能性、リプレイ可能なイベント駆動設計を、自社サービスに今日から組み込める
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

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

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

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

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

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

関連記事

◈ API / SDK2026-07-04
Gemini API の英語出力に日本語が『たまに』混ざるとき — 混入率を計測して段階的に締める運用メモ
英語出力を指示したGemini APIが、100回に数回だけ日本語を混ぜてくる——この『たまに』を止められない本当の理由と、混入率をSLOとして計測し、段階的リカバリで本番品質まで締める運用パターンを実装コード付きで整理します。
◈ API / SDK2026-05-20
Gemini API 構造化出力スキーマを本番アプリで安全に進化させる設計記録
本番アプリで Gemini API の JSON 出力スキーマを変えるとき、何を契約として守り、何を段階的に切り替えるか。旧版と新版を同時に返す Dual-Emit パターン、旧スキーマ利用箇所を見積もる compatibility checker、Sunset プロトコルの実装記録です。
◈ API / SDK2026-05-02
Gemini API を個人アプリに組み込む時の 7 つの設計判断 — エラー設計から品質モニタリングまで
Gemini API を個人アプリに本番導入した時に直面する設計判断を、私自身の運用記録から 7 つに絞ってまとめました。コストとレイテンシのトレードオフ、エラー時のフォールバック設計、品質モニタリングまで、教科書には載っていない実戦的な判断軸を共有します。