GEMINI LABEN
NANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定ですNANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定です
記事一覧/API / SDK
API / SDK/2026-04-23上級

Gemini API の Parallel Function Calling を本番で使いこなす — 並列ツール実行の設計パターンと落とし穴

Gemini API の Parallel Function Calling をレスポンス速度と信頼性の両面から本番運用する設計パターンを解説。並列実行の仕様、DAG設計、部分失敗対策、監視までを実装コード付きで詳しく紹介します。

Gemini API191Function Calling16Parallel Function Calling本番運用47エージェント設計Python38並列処理2

プレミアム記事

Function Calling を使ってエージェントを組んでみたら、ツールを複数呼ぶたびに体感速度が遅くなっていく——そんな経験はありませんか。私も最初に Gemini API で調査系エージェントを組んだとき、検索・計算・RAG・外部 API 呼び出しをすべて逐次で回していて、ユーザーが投稿ボタンを押してから最終回答が出るまでに十数秒かかっていました。

この「逐次で遅い」を一段飛ばしで解決するのが、Gemini API の Parallel Function Calling(並列ツール実行)です。モデルが一度のターンで複数の関数呼び出しを発行し、アプリケーション側でそれらを並列に実行してから結果をまとめて返す、というパターンです。

ここではParallel Function Calling の仕様と、単に「並列に呼ぶ」だけでは本番で通用しない理由、そして順序依存・部分失敗・レート制限・監視まで含めた設計パターンを、動作するコードとともに解説します。「とりあえず並列化したら逆に不安定になった」という段階でつまずいている方にも、そのまま現場に持ち帰れる内容を目指しました。

Parallel Function Calling とは何か — 仕様の整理

Gemini API の Parallel Function Calling は、モデルが 1 ターンの応答で複数の functionCall パートを返す機能です。従来の逐次的な Function Calling では「ツール A を呼ぶ → 結果をモデルに返す → ツール B を呼ぶ → ...」という往復が必要でしたが、並列実行では次のような応答が一度に返ってきます。

{
  "candidates": [{
    "content": {
      "parts": [
        { "functionCall": { "name": "search_orders",  "args": { "user_id": "u_123" }}},
        { "functionCall": { "name": "get_inventory",  "args": { "sku": "ABC-001" }}},
        { "functionCall": { "name": "fetch_shipping", "args": { "zip": "150-0001" }}}
      ]
    }
  }]
}

この 3 つの呼び出しは互いに独立しているため、アプリ側で asyncio.gatherPromise.all を使って並列に実行できます。モデル呼び出しの往復は本来この「1 ターン」だけで済み、その間のネットワーク往復(通常 200〜800ms)が 3 回から 1 回に減ります。

ただし、モデルが並列で返してくれるかどうかは、ツールのシグネチャや会話履歴、tool_config の設定に依存します。特に以下のケースでは逐次実行にフォールバックしがちです。

  • ツール B が「ツール A の結果」を引数として必要とする場合(依存関係あり)
  • toolConfig.function_calling_config.modeANYallowed_function_names を 1 つに絞った場合
  • プロンプトに「まず A を呼んで、その後に B を判断しなさい」と逐次的な指示が書かれている場合

逆に、モデルから並列呼び出しを引き出すには「独立して実行できるツールであることを示す説明文を書く」「同じ情報を別経路で集める必要があると伝える」など、意図的なプロンプト設計が必要です。このあたりは本記事の後半で具体例を示します。

基本実装:Python SDK での並列ツール呼び出し

まずは最小構成で Parallel Function Calling を動かしてみます。Google の新しい google-genai SDK(旧 google-generativeai からの移行後のもの)を使い、FastAPI で呼び出せるエージェントを想定しています。

このコードは「ユーザーが注文状況を問い合わせてきたとき、注文履歴・在庫・配送情報を並列で取得して回答する」という典型的なサポートエージェントの例です。

# pip install google-genai httpx tenacity
import asyncio
import os
from typing import Any
from google import genai
from google.genai import types
 
client = genai.Client(api_key=os.environ["YOUR_GEMINI_API_KEY"])
 
# --- ツール定義 ---
def declare_tools() -> list[types.Tool]:
    return [
        types.Tool(function_declarations=[
            types.FunctionDeclaration(
                name="search_orders",
                description="指定ユーザーの過去3ヶ月の注文履歴を返す。ユーザーIDから注文を検索し、在庫・配送と並列で安全に呼び出せる。",
                parameters=types.Schema(
                    type="OBJECT",
                    properties={"user_id": types.Schema(type="STRING")},
                    required=["user_id"],
                ),
            ),
            types.FunctionDeclaration(
                name="get_inventory",
                description="指定SKUの現在在庫数を返す。注文履歴・配送と並列で安全に呼び出せる。",
                parameters=types.Schema(
                    type="OBJECT",
                    properties={"sku": types.Schema(type="STRING")},
                    required=["sku"],
                ),
            ),
            types.FunctionDeclaration(
                name="fetch_shipping",
                description="郵便番号から配送予定日と利用可能な配送業者を返す。注文履歴・在庫と並列で安全に呼び出せる。",
                parameters=types.Schema(
                    type="OBJECT",
                    properties={"zip": types.Schema(type="STRING")},
                    required=["zip"],
                ),
            ),
        ])
    ]
 
# --- ツール実装(本番では DB / 外部 API を呼ぶ)---
async def search_orders(user_id: str) -> dict[str, Any]:
    await asyncio.sleep(0.3)  # DBアクセスの模擬
    return {"orders": [{"id": "o_9001", "sku": "ABC-001", "status": "shipped"}]}
 
async def get_inventory(sku: str) -> dict[str, Any]:
    await asyncio.sleep(0.2)
    return {"sku": sku, "stock": 42}
 
async def fetch_shipping(zip: str) -> dict[str, Any]:
    await asyncio.sleep(0.4)
    return {"zip": zip, "carriers": ["JP Post", "Yamato"], "eta_days": 2}
 
TOOL_REGISTRY = {
    "search_orders": search_orders,
    "get_inventory": get_inventory,
    "fetch_shipping": fetch_shipping,
}
 
# --- 並列呼び出しの本体 ---
async def run_parallel_tools(function_calls: list[types.FunctionCall]) -> list[types.Part]:
    async def dispatch(fc: types.FunctionCall) -> types.Part:
        impl = TOOL_REGISTRY.get(fc.name)
        if impl is None:
            result = {"error": f"unknown tool: {fc.name}"}
        else:
            try:
                result = await impl(**(fc.args or {}))
            except Exception as exc:
                # 1 つのツールが失敗しても他を止めないために必ず握りつぶす
                result = {"error": str(exc), "tool": fc.name}
        return types.Part.from_function_response(name=fc.name, response=result)
 
    # asyncio.gather で並列実行(return_exceptions=False でも上の except で守られる)
    return await asyncio.gather(*(dispatch(fc) for fc in function_calls))
 
async def agent_turn(user_message: str) -> str:
    tools = declare_tools()
    history: list[types.Content] = [
        types.Content(role="user", parts=[types.Part.from_text(user_message)])
    ]
 
    for _ in range(4):  # 最大4ターン
        resp = client.models.generate_content(
            model="gemini-2.5-pro",
            contents=history,
            config=types.GenerateContentConfig(tools=tools, temperature=0.2),
        )
        parts = resp.candidates[0].content.parts
        fcalls = [p.function_call for p in parts if p.function_call]
        if not fcalls:
            return "".join(p.text or "" for p in parts)
 
        # モデルの functionCall 群を履歴に追加
        history.append(types.Content(role="model", parts=parts))
 
        # 並列実行
        tool_parts = await run_parallel_tools(fcalls)
        history.append(types.Content(role="user", parts=tool_parts))
 
    return "会話がループに達しました。人間にエスカレーションします。"
 
if __name__ == "__main__":
    msg = "user_id u_123 で ABC-001 の注文ステータスを知りたい。郵便番号は 150-0001。"
    print(asyncio.run(agent_turn(msg)))

期待される出力は、Gemini がまず 3 つの functionCall を並列発行し、アプリ側で約 400ms(3 つの最大値に近い時間)でまとめて実行、その結果をモデルに戻して日本語の回答を生成する、という流れです。逐次実行していた場合は約 900ms(合計)かかっていた処理が、並列化で 400ms 程度に縮みます。

なぜこの書き方なのか

  • run_parallel_toolsasyncio.gather でまとめて呼び出すのは、Python で IO バウンドな処理を並列実行する最もシンプルな方法です。ThreadPoolExecutor でもよいですが、httpx.AsyncClient など非同期 HTTP クライアントを併用するなら asyncio で統一する方がコードが素直になります。
  • 個別ツールの中で try / except を必ず書いているのは、1 つの失敗で全体が止まらないようにするためです。asyncio.gather はデフォルトで例外が 1 つ投げられた瞬間に他のタスクを中断しないものの、結果を取りに行く側で例外が伝播します。return_exceptions=True を使うやり方もありますが、私は「ツール実装側で必ず {error: ...} を返す」統一規約の方が下流の処理が書きやすいと感じています。

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

この記事の続きを読む

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

この記事で得られること
Function Calling を逐次実行していてレスポンスが重くなっていた実装を、並列実行に切り替えて体感速度を2〜4倍にできる
部分失敗・タイムアウト・順序依存といった並列化特有の落とし穴を、実装可能なコードパターンで回避できる
顧客サポートやデータ分析エージェントの応答時間をSLA内に収めるための、本番で使えるアーキテクチャを習得できる
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

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

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

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

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

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

関連記事

API / SDK2026-05-24
Gemini File API でアップロードしたファイルが48時間後に消える問題への対処法
Gemini File API のファイルは48時間で削除される仕様です。突然 PERMISSION_DENIED や NOT_FOUND が返るようになった原因と、再アップロードを前提とした実装パターンを整理します。
API / SDK2026-05-04
Gemini の Function Calling で構造化出力を実装する — 複数ツール連携の設計パターン
Gemini API の Function Calling を使って信頼性の高い構造化出力を実装する方法を解説します。ツール定義のコツから複数ツール連携、エラー処理まで実践的に解説します。
API / SDK2026-07-16
Gemini にスクリーンショットを採点させたら、全部78〜85点だった — 絶対スコアを捨ててペア比較に切り替えるまで
Gemini Vision に App Store スクリーンショットを100点満点で採点させると、良い案も崩した案も78〜85点に潰れます。弁別能を実測して絶対スコアを捨て、位置バイアスを除いたペア比較でランキングを作るまでの実装記録です。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →