◉GEMINI LABEN
●OMNI 1.2 — Gemini Omni 1.2 Flash が GA になりました(11/5)。最長15秒の動画生成と会話での編集に対応し、1.1 Flash は 12/3 に自動で 1.2 へ振り替わります●COMPUTER — Computer Use が Interactions / GenerateContent API で GA になりました(10/30)●LIVE — Live と TTS のプレビュー版は最短 11/17 に停止します(残り10日)。移行先は gemini-3.8-live と 3.8 系 TTS です●CLI — Gemini API CLI(gemini-api)がベータで登場(10/27)。エージェントやCI向けのゼロ依存クライアントです●2.5 PRO — gemini-2.5-pro と 2.5-flash は 12/1 から自動で振り替えられます(残り24日)。移行先は 3.8 Flash と 3.5 Flash-Lite です●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…●OMNI 1.2 — Gemini Omni 1.2 Flash が GA になりました(11/5)。最長15秒の動画生成と会話での編集に対応し、1.1 Flash は 12/3 に自動で 1.2 へ振り替わります●COMPUTER — Computer Use が Interactions / GenerateContent API で GA になりました(10/30)●LIVE — Live と TTS のプレビュー版は最短 11/17 に停止します(残り10日)。移行先は gemini-3.8-live と 3.8 系 TTS です●CLI — Gemini API CLI(gemini-api)がベータで登場(10/27)。エージェントやCI向けのゼロ依存クライアントです●2.5 PRO — gemini-2.5-pro と 2.5-flash は 12/1 から自動で振り替えられます(残り24日)。移行先は 3.8 Flash と 3.5 Flash-Lite です●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…
記事一覧/API / SDK
◈ API / SDK/2026-08-08上級

打ち切りは失敗の合図ではありませんでした — ブロッキング挙動を伴う関数呼び出しの設計

呼び出しが返るまで待つブロッキング型のツールをエージェントに持ち込むと、並列化と打ち切りの常識が反転します。サンドボックスの最小ハーネスで実測した数値をもとに、再試行を観測へ振り替える設計と unknown を握りつぶさない扱い方をまとめました。

Gemini API254Function Calling17エージェント設計4タイムアウト5冪等性6

✦ プレミアム記事

ロボティクス向けの公式ドキュメントを読んでいて、ある箇所で手が止まりました。モデルが返した関数呼び出しをすべて実行し終えてから、その結果をモデルへ返す — という実行ループの書き方です。ロボットの動作は、完了を待ってから次の指示に進みます。

実機を持たない私には縁のない話に見えました。読み流しかけて、しかし引っかかりが残りました。

自分の自動化にも、返るまで待つしかない処理がいくつもあるからです。

App Store Connect へのビルド送信。壁紙アセットの一括変換。課金レシートの検証。どれも「呼んだ瞬間に終わる」ものではなく、呼び出し側が待っている間に外の世界が変わっていきます。ロボットアームが動くのと構造は同じでした。

そこで、手元のサンドボックスで測ってみることにしました。結論から書きますと、私がそれまで安全だと思っていた二つの習慣 — 並列化と、余裕を持たせたタイムアウト — は、この種のツールに対しては両方とも逆に働いていました。

「返るまで待つ」ツールは、どこにでもある

ブロッキング挙動という言葉はロボティクス文脈で出てきますが、条件を抽象化すると身近になります。

条件意味個人開発での例
効果が外にある戻り値ではなく、外部の状態が変わることが目的ビルド送信、メール送信、決済確定
取り消せない開始後に呼び出し側から止める手段がないアセット変換ジョブ、通知の配信
重ねられない同じ資源に対して同時に走らせてはいけない同一ファイルの書き換え、在庫の引き当て

三つのうち二つ以上が当てはまるツールを、私は「ブロッキング側」と呼んで別扱いすることにしました。通常のツール(検索、要約、参照)とは設計の前提がまるで違うからです。

通常のツールは、失敗したらもう一度呼べば済みます。ブロッキング側は、もう一度呼ぶことそのものが事故になります。

手元で再現する — 動作を止められないツールの最小ハーネス

実機もクラウド課金も使わずに、この構造だけを取り出したハーネスを書きました。動作の代わりにファイルへ追記し、「同時に走ってはいけない区間」の重なりを数えます。

import asyncio, time
 
class Actuator:
    """物理動作の代役。開始から完了までの間、外から観測すると『動作中』。"""
    def __init__(self):
        self.busy = False
        self.overlaps = 0   # 重ねてはいけない区間が重なった回数
        self.effects = 0    # 外の世界に実際に起きた効果の数
 
    async def move(self, action_id: str, dur: float) -> None:
        if self.busy:
            self.overlaps += 1
        self.busy = True
        try:
            await asyncio.sleep(dur)          # ここが実際の物理動作・外部ジョブ
            self.effects += 1
            with open("effects.log", "a") as f:
                f.write(action_id + "\n")
        finally:
            self.busy = False
 
 
async def blocking_call(act: Actuator, action_id: str, dur: float, timeout: float | None = None) -> str:
    """ツール呼び出し。timeout はクライアント側の待ちを切り上げるだけで、動作自体は止まらない。"""
    task = asyncio.create_task(act.move(action_id, dur))
    if timeout is None:
        await task
        return "ok"
    try:
        # shield が要点。wait_for は通常タスクを cancel するが、
        # 現実の物理動作・外部ジョブは cancel できないため、その非対称性を再現する
        await asyncio.wait_for(asyncio.shield(task), timeout)
        return "ok"
    except asyncio.TimeoutError:
        return "timeout"    # 呼び出しは返る。しかし動作は続いている

asyncio.shield を挟んでいるのが肝でした。asyncio.wait_for は素朴に書くと待っているタスクを取り消します。それでは「打ち切ったら動作も止まった」という、現実には成立しない世界を再現してしまいます。

実機のアームは、こちらが待つのをやめても動き続けます。決済も、ビルドの送信も同じです。shield はその非対称性をコードの上で正直に表現するための一行でした。

ここは実装上の落とし穴でもありました。shield を外したハーネスで最初に測ったとき、二重実行はきれいに0件で並びます。安全な設計ができた証拠に見えました。

実際には、テスト側だけが取り消しに応じていただけです。本番運用に置き換えれば、HTTP リクエストを中断してもサーバー側の処理は進みますし、subprocess を待つのをやめてもプロセスは走り続けます。取り消しが効くのはローカルの await だけでした。

自分の書いた抽象が、現実より都合よく振る舞っていないか。ブロッキング側を扱うときは、まずそこを疑うことをお勧めします。数字が最初から良すぎるときは、たいてい模型の方が甘くできています。

✦

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

この記事の続きを読む

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

この記事で得られること
✦打ち切りが発火したケースのうち97〜100%は、実際には動作が成功していた(実測3水準)
✦共有資源を持つツールを4本並列にすると壁時計は1203ms→301msになる一方、重なりが3回発生する
✦再試行を観測に振り替えると二重実行は30件→0件、追加の待ちは中央値67msで済む
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

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

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

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

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

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

関連記事

◈ API / SDK2026-08-03
Memory profiles の更新ポリシーを測って選ぶ — 確定値を守るガードが精度を16ポイント下げた記録
Memory Bank の Memory profiles が GA になり、構造化された記憶を後段へ渡せるようになりました。更新ポリシーを3種類つくって同条件で比較したところ、直感的に正しく見えた防御が精度を下げていました。計測コード一式と、フィールド別TTLへ切り替えるまでの記録です。
◈ API / SDK2026-08-02
environment hooks に置いた門番の実測 — 判定は46マイクロ秒、起動は23ミリ秒
Managed Agents の environment hooks に破壊的コマンドのガードを置くまでの記録です。正規表現の denylist が20件中9件を素通りさせた実測から、argv 分解による検出率100パーセントまでの3段階と、判定より500倍重い起動コストの内訳をまとめました。
◈ API / SDK2026-05-07
Gemini API で DEADLINE_EXCEEDED が頻発する時に最初に見直すべき5つのポイント
Gemini API で DEADLINE_EXCEEDED が突然頻発したときの切り分けを5つのポイントに整理しました。サーバー側処理時間の計測、入力サイズのしきい値、SDK 既定タイムアウトの上書き、Pro と Flash の選び直し、指数バックオフの実装まで扱います。