◉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-06-13上級

Gemini で自律エージェントを動かす最小構成 — ツールループ設計で学んだこと

google-genai SDK の自動 Function Calling で自律エージェントを最小構成から作る手順。ステップ上限・ツール許可リスト・再試行の設計判断を、ブログ運用の自動化で得た実体験とともにまとめます。

Gemini API248エージェント16Function Calling17自律型AI

個人開発のかたわら複数のブログを並行運用するようになってから、毎朝の定型作業が静かに積み上がっていきました。アクセス状況の確認、更新キューの整理、画像アセットの振り分け。一つひとつは数分でも、サイトが増えるたびに朝の時間が削られていきます。

「判断はモデルに任せて、手は道具に任せる」。そう割り切って Gemini にツールを持たせる構成へ移行したのが、私にとっての自律エージェント開発の始まりでした。

最初に断っておくと、エージェントは魔法ではありません。実体は「モデルがツール呼び出しを提案し、コードが実行し、結果をモデルに返す」というループの反復です。この構造を自分の手で一度書いておくと、フレームワークやマネージドサービスを選ぶときの判断軸が一気に明確になります。ここでは、その最小構成と、運用して初めて見えた設計判断をまとめます。

自動 Function Calling だけで動く最小エージェント

現行の Python SDK は google-genai です(pip install google-genai)。旧 google-generativeai のサンプルが検索上位に残っていますが、クラス構成が異なるため混ぜると動きません。私自身、移行直後に旧 SDK の GenerativeModel を探して時間を溶かしました。

新 SDK の大きな利点は、Python 関数をそのまま渡すと自動 Function Calling が働くことです。docstring と型ヒントから関数宣言が生成され、モデルが必要と判断すれば SDK が関数を実行し、結果を踏まえた最終応答まで返してくれます。

from google import genai
from google.genai import types
 
# GEMINI_API_KEY 環境変数を自動で読み込みます
client = genai.Client()
 
def get_article_stats(slug: str) -> dict:
    """指定したスラッグの記事の直近7日間の閲覧数とクリック数を返します。"""
    # 実際には GA4 / Search Console のデータストアを引きます
    return {"slug": slug, "views": 1280, "clicks": 96}
 
def list_pending_drafts() -> list[str]:
    """公開待ちの下書きスラッグ一覧を返します。"""
    return ["gemini-batch-api-notes", "image-asset-pipeline"]
 
response = client.models.generate_content(
    model="gemini-3.5-flash",
    contents="公開待ちの下書きを確認して、直近の人気記事の傾向と合わせて公開順の提案をしてください。",
    config=types.GenerateContentConfig(
        tools=[get_article_stats, list_pending_drafts],
    ),
)
 
print(response.text)

このコードだけで、モデルは list_pending_drafts を呼び、必要に応じて get_article_stats を複数回呼び、結果を統合した提案文を返します。ループを自分で書いていないのに複数ステップの実行が成立するのは、SDK が内部でツールループを回しているためです。

つまずきやすいのは docstring の書き方です。自動 Function Calling では docstring がそのままモデルへの説明になるため、「何を返すか」を曖昧に書くと呼び出し判断がぶれます。引数の意味と戻り値の中身を一文ずつ明記する。それだけで呼び出しの安定感が目に見えて変わりました。

手動ループに切り替える判断基準

自動実行は便利ですが、私は本番のパイプラインでは手動ループに切り替えています。理由は二つあります。実行前にツール呼び出しの内容を検査したいこと、そしてステップ数の上限を自分で握りたいことです。

from google import genai
from google.genai import types
 
client = genai.Client()
 
publish_tool = types.Tool(function_declarations=[
    types.FunctionDeclaration(
        name="publish_article",
        description="指定スラッグの記事を公開します。破壊的操作のため確認が必要です。",
        parameters=types.Schema(
            type=types.Type.OBJECT,
            properties={"slug": types.Schema(type=types.Type.STRING)},
            required=["slug"],
        ),
    ),
])
 
config = types.GenerateContentConfig(
    tools=[publish_tool],
    # 自動実行を止めて、呼び出し提案だけ受け取ります
    automatic_function_calling=types.AutomaticFunctionCallingConfig(disable=True),
)
 
contents = [
    types.Content(role="user", parts=[
        types.Part(text="レビュー済みの記事を公開してください。対象: gemini-batch-api-notes"),
    ]),
]
 
MAX_STEPS = 6  # 暴走防止。経験上、定型運用タスクは6ステップで収まります
 
for step in range(MAX_STEPS):
    response = client.models.generate_content(
        model="gemini-3.5-flash", contents=contents, config=config,
    )
    part = response.candidates[0].content.parts[0]
 
    if not part.function_call:
        print(response.text)  # ツール提案がなくなったら完了
        break
 
    call = part.function_call
    # ここで呼び出し内容を検査してから実行します
    result = execute_with_allowlist(call.name, dict(call.args))
 
    contents.append(response.candidates[0].content)
    contents.append(types.Content(role="user", parts=[
        types.Part.from_function_response(name=call.name, response={"result": result}),
    ]))

ポイントは MAX_STEPS です。初期の実装でこれを入れておらず、ツールがエラー文字列を返し続ける状況でモデルが同じ呼び出しを繰り返し、無駄な API 課金だけが積み上がったことがあります。ループの上限は「保険」ではなく必須の部品だと考えるようになりました。

ツールは読み取り系と破壊系で分ける

運用して最も効いた設計判断は、ツールを「読み取り系」と「破壊系」に分けたことです。

統計の取得や下書き一覧の参照は、モデルが何度呼んでも実害がありません。一方で「公開する」「削除する」「外部に投稿する」は、一度実行すると取り消せません。私の実装では、破壊系ツールは許可リスト方式にして、ループ内で実行する前に引数を検証しています。

READ_TOOLS = {"get_article_stats", "list_pending_drafts"}
WRITE_TOOLS = {"publish_article"}
 
def execute_with_allowlist(name: str, args: dict) -> str:
    if name in READ_TOOLS:
        return run_tool(name, args)
    if name in WRITE_TOOLS:
        # 破壊系は対象の実在確認を挟んでから実行します
        if not draft_exists(args.get("slug", "")):
            return f"対象 {args.get('slug')} が見つからないため中止しました"
        return run_tool(name, args)
    return f"ツール {name} は許可されていません"

「存在しない対象への破壊的操作はツール側で握りつぶす」。この一段を入れてから、モデルの判断ミスがそのまま事故になる経路が塞がれました。モデルの賢さに頼るのではなく、ツールの設計で安全側に倒す。エージェント開発の品質は、実はツール側のコードで決まる部分が大きいと感じています。

マルチエージェントの前に、1エージェント+良いツール

エージェント関連の記事では複数エージェントの協調がよく語られます。私も一度、「分析役」「執筆役」「レビュー役」と分けた構成を試しました。結論として、個人開発の規模では維持コストに見合いませんでした。

役割間の受け渡しプロンプトが増えるほどデバッグが難しくなり、どこで判断が崩れたのか追跡するだけで時間が消えます。同じ成果は「1つのエージェントに、責務の明確なツールを揃える」ほうが安定して出せました。マルチエージェントが活きるのは、役割ごとに必要なコンテキストが本当に分離している場合に限られる、というのが現時点での私の判断です。

なお、ループの実行環境ごと Google 側に任せたい場合は、Managed Agents(公開プレビュー)という選択肢もあります。隔離されたサンドボックスで計画からコード実行までを担ってくれますが、上で書いたような「破壊系ツールの検証を自分のコードで握る」構成とはトレードオフになります。私はまず自前ループで運用の勘所を掴んでから、移せる部分だけを移す段階移行を取っています。

運用で毎月見ている数字

エージェントを回し続けるかどうかは、感覚ではなく数字で判断するようにしています。私が月初に確認しているのは三つです。

ツール呼び出しの成功率(直近は 96% 前後で、低下したら docstring か引数スキーマを疑います)。1タスクあたりの平均ステップ数(2〜3 で安定していれば健全、上振れしたらプロンプトの目標設定が曖昧になっています)。そして usage_metadata から合計したトークン消費です。自動化で浮いた作業時間とこの費用を並べたとき、見合わなくなった機能は畳む。この基準を持っておくと、エージェントを「動いているから残す」ではなく「効いているから残す」で運用できます。

次に試すこと

まずは読み取り系ツールを2つだけ持つ最小エージェントを、自動 Function Calling で動かしてみてください。docstring を一文直すだけで挙動が変わる感覚を掴めたら、手動ループ化とステップ上限の導入に進むのが近道です。

ツール定義をさらに細かく制御したくなったら、Gemini API の Function Calling 活用法が次の一歩になります。基本の呼び出しに不安があれば Gemini APIクイックスタートから固めるのも良い順序です。

同じように日々の運用を少しずつ自動化している方の参考になれば幸いです。

シェア

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

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

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

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⬡ 高度な活用2026-09-05
長い動画を Gemini に辿らせるとき、見つける問いと「無い」を確かめる問いは別々に投げています
9月1日に来たエージェント的動画理解では、どこを観るかをモデル自身が決めます。見つける問いには効きますが、映り込みが無いことの確認には被覆の保証がありません。問いの形で処理モードを分ける判断と、被覆を自分で担保する実装を書き残します。
⬡ 高度な活用2026-09-24
Gemini に「合っていますか」と聞くのをやめ、答えを伏せたまま分類し直してもらう設計に変えました
自分で付けたラベルの確認を Gemini に頼むと、頷く答えが返りがちです。確認型・反論指示型・伏せ型の三つを比べて伏せ型を選んだ理由と、答えが漏れる経路の塞ぎ方、食い違いだけを人が見る振り分けのコードまで書き残します。
⬡ 高度な活用2026-09-14
BLOCK_NONE を入れても結果が変わらないとき — Gemini のブロックを閾値で動かせるかどうかの見分け方
安全フィルタで止まったとき、閾値を緩めて直る場合と、何をしても直らない場合があります。返ってきたレスポンスのどこを見て、その境目を先に判定するかをまとめました。