知識がなくても始められる、AIと共にある豊かな毎日。
AI 学習・資格

ADK のエージェントを手元の PC で書いて動かす — 出題の3割を占めるカスタムエージェントを Google Cloud なしで練習する

ゲンキ

Google Cloud の Professional Agentic Architect の試験ガイドでは、§3「Developing custom agents」が出題の約 33% を占める。その中心にあるのが、Google が Apache 2.0 で公開している Python のライブラリ Agent Development Kit(ADK)でカスタムエージェントを作ることだ。ADK のエージェントは、Google Cloud のアカウントがなくても手元の PC で書いて動かせる。モデルを手元の Ollama につなげば、API キーも要らない。

※本記事にはアフィリエイト広告(PR)が含まれます。

そこで 2026 年 10 月 12 日に、ADK Python v2.11.0 を Windows 11 の Python 3.13 の venv に入れ、LiteLLM 経由で Ollama の gemma4:12B を使って、3種類を動かした。1つ目は、在庫・プリンターの状態・予約の3つのツールを持つエージェントである。2つ目は、LLM→Python の関数→LLM の順に動く Workflow、3つ目は、3つの LLM を並列に動かして JoinNode で合流させる Workflow だ。題材は作業場のプリンターとフィラメントの在庫で、値はすべて実験用に作った架空のものである。

先に結果を書く。ツールの呼び出しは、2回の実行とも5問すべてで指示どおりの順番だった。順番の Workflow では、素材と個数の決まりの確認を LLM ではなく Python の関数に任せ、決まりに合わない依頼を関数が見つけて、最後の LLM がそれを説明した。並列の Workflow も動いたが、手元の Ollama 1台では所要がほとんど縮まなかった。並列で 41.2 秒と 36.9 秒、同じ3つを1つずつ動かした合計は 43.1 秒である。また、旧来のクラスの SequentialAgent は、v2.11.0 で非推奨の警告を出した。

なお、当サイトはこの試験を受けていない。ここに載せる数字は、各条件1回か2回の実行の記録で、「この実行ではこうだった」以上のことは言えない。また、手元の練習は試験の実技ラボの代わりにもならない。どこまでが手元で練習でき、どこからが Google Cloud を要するかは、後半で試験ガイドの項目ごとに表にする。

忍者AdMax

出題の3割を占める §3 は何を求めているか

出題の3割を占める §3 は何を求めているか

試験ガイドは英語の PDF で、4ページある。§3 の見出しは「Section 3: Developing custom agents (~33% of the exam)」で、次の3つの小項目に分かれる。意味を日本語で添えるが、項目名は原文のまま残す。

  • 3.1 Designing and building agentic workflows in code: コードでエージェントのワークフローを設計して作る。モデルの選び方(LLM と SLM、自前のホスティングと SaaS、OSS と専有のモデルを、費用・セキュリティ・構成を考えて選ぶ)、ADK のようなオープンソースのライブラリでカスタムエージェントを作ること、セッションとメモリの設定(例は Agent Platform Memory Bank と managed sessions)、Agents CLI でのスキルの設定(例は plugins と agent vs. human mode)が並ぶ
  • 3.2 Integrating enterprise domain knowledge: 企業の知識を組み込む。RAG とベクトル検索(例は Vector Search と Agent Retrieval)、エージェントの権限(例は Agent Identity)、Google Cloud のツール(例は Agent Registry と Google Cloud MCP Servers)で機能をつなぐことが並ぶ
  • 3.3 Orchestrating and coordinating agentic workflows: エージェントのワークフローを組み立てて調整する。MCP と Agent2Agent(A2A)のプロトコルでエージェントをつなぐこと、並列のエージェント・順番のエージェント・グラフのワークフローでの引き継ぎを、Agent Identity・Agent Registry・Agent Runtime(旧 Agent Engine)・agent policies などの Google Cloud のツールで扱うことが並ぶ

並べてみると、ADK のコードで書ける部分と、Google Cloud のマネージドサービスを前提にした部分が混ざっている。エージェントを作る、ワークフローを組む、MCP のツールをつなぐ、といった部分はライブラリの機能なので、手元で練習できる。一方で、Memory Bank、Agent Identity、Agent Registry、Agent Runtime は Google Cloud のサービスとして提供されていて、手元だけでは動かせない。この記事では前者を実際に動かし、後者は ADK の公式ドキュメントで「何が要るか」までを確かめた。

試験の状態と日程は 2026 年 10 月時点のもので、一般受験の申込は 11 月 2 日に始まり、受験は 11 月中旬に始まると案内されている。試験ガイドの PDF の本文には改訂日の記載が無く、一般受験に向けて変わる可能性もある。試験の全体像と準備の計画は Google Cloud の Professional Agentic Architect は何を証明する資格なのか — 11月の一般提供までに準備する4週間の全体設計(2026-10-19 公開) にまとめた。

また、出題範囲の製品を AI がどこまで正しく知っているかは、Agentic Architect の出題範囲の新しい製品を AI はどこまで知っているか — 試験ガイドの 28 項目を公式ドキュメントで照合した(2026-10-20 公開) で照合している。改称や新しい製品が多い範囲なので、AI の説明をそのまま覚える前に、公式資料と照らし合わせておきたい。

手元の環境 — ADK v2.11.0・LiteLLM・Ollama の gemma4:12B

手元の環境 — ADK v2.11.0・LiteLLM・Ollama の gemma4:12B

実行した環境を先にまとめる。

項目この実行の値
OS と GPUWindows 11、RTX 5070 Ti(VRAM 16,303 MiB)
Python3.13 の venv(PC の Python には入れない)
ADKgoogle-adk 2.11.0
LiteLLM1.105.0(ADK とは別に入れた。ADK が求めるのは 1.84 以上)
Ollama0.40.2(Windows 版)
モデルgemma4:12B(11.9B、Q4_K_M。ollama show の表示では tools と thinking に対応し、thinking は既定で有効)
コンテキスト長4096(この実行の ollama ps の表示)
モデルの指定LiteLlm(model="ollama_chat/gemma4:12B")
実行日2026-10-12

ADK の版と要件

ADK の Python 版は、GitHub の google/adk-python で公開されている。ライセンスは Apache 2.0 で、LICENSE の本体でも確かめた。PyPI の作者の表記は Google LLC で、このリポジトリはフォークではなく Google の本家である。

この記事で使ったのは v2.11.0 で、PyPI には 2026 年 10 月 2 日(UTC)に公開された。2026 年 10 月 12 日時点では、これより新しいリリースは無い。v2.11.0 の Python の要件は 3.10 以上で、実験に使った 3.13 も対象に入っている。ただし、リポジトリの main(未リリース)では要件が 3.11 以上に上がっている。次の版を入れるときは、手元の Python の版を確かめ直したい。

README の題は「Agent Development Kit (ADK) 2.0」で、Python 版の v2.0.0 は 2026 年 5 月 19 日(UTC)に公開された。README は、アプリを Agent と Workflow の2つのクラスで組むと説明している。グラフのワークフロー(Workflow)は、Python では v2.0.0 から使える。なお、ADK には Java・Kotlin・Go・TypeScript の版もあるが、この記事は Python 版だけを扱う。

README はまた、ADK を「Gemini 向けに最適化されているが、特定のモデルに縛られない」と書いている(原文は「While optimized for Gemini, ADK is model-agnostic」)。この前置きは落とさないでおきたい。Gemini で動かす前提の手順が多いなかで、手元のモデルでも動かせる、という位置づけである。

Ollama のモデルは LiteLLM を通して呼ぶ

ADK の公式ドキュメントによると、ADK は Ollama で動かすモデルを、LiteLLM というライブラリ経由で使う。Ollama のモデルは、ADK のクラス LiteLlm に包んで Agent の model に渡す。Gemini 以外のモデルの渡し方はこれだけではなく、たとえば Agent Platform 上の Claude はモデル名の文字列で指定でき、Apigee を通すモデルには ApigeeLlm を使う。ここで大文字と小文字の違いに注意したい。LiteLLM はライブラリの名前で、LiteLlm は ADK のクラスの名前だ。

LiteLLM は pip install google-adk だけでは入らない。ADK のドキュメントは別に入れるよう書いており、求める版は 1.84 以上である。さらに ADK の公式は、PyPI に置かれた LiteLLM の 1.82.7 と 1.82.8 に不正なコードが混入していたとして注意を出しており、これも下限の版を守る理由になる。

モデル名の前には ollama_chat/ を付ける。これは ADK の設定ではなく、LiteLLM のモデル名の書き方だ。ADK の公式ドキュメントは ollama/ ではなく ollama_chat/ を使うよう求めており、ollama のままではツールの呼び出しが止まらなくなったり、前の文脈を無視したりすることがあると注意している。LiteLLM の文書も ollama_chat を勧めている。Ollama のアドレスは、環境変数 OLLAMA_API_BASE で渡す。

もう一つ、Ollama の OpenAI 互換 API を openai の前置きで呼ぶ方法も、公式ドキュメントに載っている。この記事では ollama_chat だけを使った。OpenAI 互換 API でできることと、手元のモデルにツールを呼ばせる考え方は、ローカルLLMのツール呼び出しを実務に繋ぐ — OpenAI互換APIとMCPの使いどころ(2026-08-26 公開) で書いた。

ツールの呼び出しに対応したモデルを選ぶ

ツールを使うエージェントには、ツールの呼び出しに対応したモデルが要る。ADK の公式ドキュメントは、確実に動かすために tools に対応したモデルを使うよう書き、ollama show の出力の Capabilities に tools が出るかで確かめる方法を示している。Ollama の側では、モデルのページの tools の表示か、ollama.com の tools で絞り込んだ一覧で探せる。

ただし LiteLLM は、ネイティブのツール呼び出しに対応しないモデルでは、JSON で呼び出しを作らせる方式に切り替える。対応しないモデルがまったく動かないわけではない。公式ドキュメントの書き方も、あくまで「確実さのために対応したモデルを選ぶ」である。

ADK の公式サンプル(hello_world_ollama)は ollama_chat/mistral-small3.1 を使っているが、当サイトはこのモデルを使わず、gemma4:12B だけで動かした(Ollama のモデルページの表記は小文字の gemma4:12b)。Gemma 4 は Ollama のモデルページに tools の表示があり、モデルカードもネイティブの関数呼び出しへの対応を挙げている。大きさは E2B・E4B・12B・26B A4B・31B の5つで、ライセンスは Apache 2.0、重みが公開されたモデルである。

一方で、ADK の公式ドキュメントの Ollama と Gemma のページには、Gemma 4 を Ollama で使う例が載っていない(2026 年 10 月 12 日に確認)。したがって、以下の結果は公式に示された組み合わせを再現したものではなく、当サイトの実行の記録である。なお、Ollama のページの例には gemma3 が使われているが、Gemma のページは Gemma 3 にネイティブの関数呼び出しが無いと書いている。この記事では Gemma 3 を例にしない。

VRAM はダウンロードの大きさとは別に測る

gemma4:12B のダウンロードの大きさは、Ollama のタグ一覧で 7.7〜8.0GB と表示される。これは VRAM の使用量ではない。この実行では、GPU 全体の VRAM の使用量が、モデルを読み込む前に 3,637 MiB、読み込んだ後に 11,844 MiB で、増えた分は 8,207 MiB(11,844 − 3,637)だった。この2つは実験のメモに書き留めた値で、測ったときの出力そのものは保存していない。nvidia-smi で1秒おきに測った実行中のピークは結果のファイルに残しており、ツールのエージェントの2回目の実行で 13,440 MiB、1回目の実行で 11,923 MiB だった(差の原因は確かめていない)。どちらも 16,303 MiB の GPU に収まっている。

Gemma 4 の 12B は最大 256K トークンのコンテキストを扱えるが、この実行のコンテキスト長は 4096 だった。上の VRAM の値は、この長さで動かしたときのものだ。量子化と VRAM の見積もりの考え方は、手元のVRAMで動くモデルを見積もる — 量子化とメモリ計算の基礎(2026-08-25 公開) で説明した。

入れ方と動かし方

作業用の venv を作り、その中に ADK と LiteLLM を入れる。LiteLLM は、ADK が求める 1.84 以上にする。

pip install google-adk
pip install "litellm>=1.84"

この実行で入っていた LiteLLM は 1.105.0 で、この記事の結果はこの版で動かしたときのものである。同じ版で試すなら、"litellm==1.105.0" と版を指定して入れる。なお当機では、後から ADK の評価の機能を入れたときに、LiteLLM が 1.85.7 に下がった。追加の機能を入れた後は、LiteLLM の版を確かめ直したい。

Windows では、LiteLLM がキャッシュのファイルを UTF-8 以外の文字コードで読み、UnicodeDecodeError で止まることがある。ADK の公式ドキュメントは、環境変数 PYTHONUTF8 を 1 にして防ぐよう書いている。この実行でも PYTHONUTF8=1 を付け、bash の書き方で次のように動かした。<venv> は venv のフォルダ、<実験用のスクリプト> は下の Runner のコードを含むファイルである。

PYTHONUTF8=1 <venv>/Scripts/python.exe <実験用のスクリプト>.py

当機では、もう一つ環境に固有の問題が出た。この実行の LiteLLM は、最初の呼び出しでトークナイザーのファイル(tiktoken の cl100k_base)をダウンロードしようとして、証明書の検証に失敗した(CERTIFICATE_VERIFY_FAILED)。スクリプトの先頭で truststore の truststore.inject_into_ssl() を呼んで解消したが、これは当機の環境では必要だった、という記録である。社内の通信を検査する仕組みを通る環境などで起きうる。

エージェントを動かすコードは、公式サンプル hello_world_ollama の main.py と同じく、Runner と InMemorySessionService を使う。記録と時間の計測の処理は省いた。

from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types

async def run_one(agent, app: str, prompt: str) -> list:
    svc = InMemorySessionService()
    runner = Runner(agent=agent, app_name=app, session_service=svc)
    s = await svc.create_session(app_name=app, user_id="u1")
    msg = types.Content(role="user", parts=[types.Part(text=prompt)])
    events = []
    async for ev in runner.run_async(user_id="u1", session_id=s.id, new_message=msg):
        events.append(ev)
    return events

これを呼ぶには、エージェントと問いを渡して asyncio で動かす。root_agent は次の節で作るツールのエージェントで、スクリプトの末尾に次のように書く。gemma4:12B は答えとは別に思考の部分(thought が True の部分)を返すことがあるので、答えを表示するときはそれを除く。

import asyncio

def answer_text(events: list) -> str:
    # 思考の部分(thought が True の部分)を除き、答えの文だけをつなぐ
    texts = []
    for ev in events:
        parts = (ev.content.parts if ev.content else None) or []
        texts += [p.text for p in parts if p.text and not p.thought]
    return "\n".join(texts)

events = asyncio.run(run_one(root_agent, "workshop", "PETG のスプールは何本ありますか?"))
print(answer_text(events))

adk run と adk web

ADK には、スクリプトを書かずに動かす入口もある。adk run は端末で対話し、adk web はブラウザで操作する開発用の画面を開く。公式ドキュメントは、adk web は本番の配置に使うものではないと書いている。利用状況の送信は、既定でオフだ。さらに adk api_server を使えば、エージェントを REST API として出すこともできる。

adk create は、雛形を作るときにモデルを対話で選ばせる。v2.11.0 のコードでは、Gemini のモデルを選ぶと API キーか Google Cloud の設定を求め、「Other models (fill later)」を選ぶと認証を求めずに、モデル名を後で書き込む雛形を作る。手元のモデルで動かすには、雛形の model に LiteLlm(model="ollama_chat/…") を書く。なお、この記事の所要時間は、上の Runner のコードから動かした記録である。adk run や adk web での所要は測っていない。

Ollama で手元のモデルを動かし、プログラムから呼び出す基本を先に押さえておきたい人には、ローカル LLM のプログラミングを扱う次の一冊がある。

ツールを3つ持つ ADK のエージェントを書く

ツールを3つ持つ ADK のエージェントを書く

1つ目は、作業場の在庫とプリンターを管理するエージェントだ。フィラメントのスプールの在庫を返す関数、プリンターの状態を返す関数、空いたプリンターを予約する関数の3つを、ツールとして渡した。在庫は PLA が4本、PETG が1本、TPU が0本、ASA が2本で、プリンターは p1 が空き、p2 が印刷中という設定である。どれも実験用の架空の値だ。

from google.adk import Agent
from google.adk.models.lite_llm import LiteLlm

MODEL = "ollama_chat/gemma4:12B"

# 実験用に作った架空の値
STOCK = {"PLA": 4, "PETG": 1, "TPU": 0, "ASA": 2}
PRINTERS = {"p1": {"state": "idle"}, "p2": {"state": "printing", "progress": 0.42}}
RESERVATIONS: list[dict] = []

ツールにする3つの関数は、次のとおりである。

def get_spool_stock(material: str) -> dict:
    """Return how many unopened spools of the given filament material are in stock."""
    m = material.strip().upper()
    return {"material": m, "spools": STOCK.get(m, 0)}

def get_printer_status(printer_id: str) -> dict:
    """Return the current state of a printer ("idle" or "printing")."""
    p = PRINTERS.get(printer_id.strip().lower())
    if p is None:
        return {"printer_id": printer_id, "error": "unknown printer"}
    return {"printer_id": printer_id.strip().lower(), **p}

def reserve_printer(printer_id: str, material: str) -> dict:
    """Reserve an idle printer for a job with the given material."""
    pid, m = printer_id.strip().lower(), material.strip().upper()
    if PRINTERS.get(pid, {}).get("state") != "idle":
        return {"reserved": False, "reason": "printer is not idle"}
    if STOCK.get(m, 0) <= 0:
        return {"reserved": False, "reason": "no spool in stock"}
    RESERVATIONS.append({"printer_id": pid, "material": m})
    return {"reserved": True, "printer_id": pid, "material": m}

Python の関数を tools に並べると、ADK はそれを FunctionTool に包む。公式ドキュメントによると、ADK は関数名・docstring・引数・型ヒントを調べて、モデルに渡す説明を作る。つまり、docstring の1行がそのままツールの説明書になる。引数の型ヒントを省かないのも、このためである。

指示は英語で書き、答えは日本語にさせた。決まりは4つある。在庫の質問には在庫の関数を、プリンターの質問には状態の関数を呼ぶ。予約の前には、必ずプリンターの状態を確かめ、次に素材の在庫を確かめて、空いていて在庫が1本以上あるときだけ予約する。そして、あいさつや関係のない質問には、ツールを呼ばずに答える。

INSTRUCTION = """You manage a small 3D-printing workshop. Answer in Japanese.
Rules:
- To answer a question about filament stock, call get_spool_stock.
- To answer a question about a printer, call get_printer_status.
- Before calling reserve_printer, always call get_printer_status for that printer first,
  then get_spool_stock for that material. Call reserve_printer only if the printer is idle
  and at least one spool is in stock. Otherwise explain why you did not reserve it.
- For greetings or questions unrelated to the workshop, answer without calling any tool."""

root_agent = Agent(
    name="workshop_agent",
    model=LiteLlm(model=MODEL),
    instruction=INSTRUCTION,
    tools=[get_spool_stock, get_printer_status, reserve_printer],
)

予約の関数自身も、プリンターが空いているか、在庫があるかを確かめ、合わなければ予約しない。指示に書いた決まりをモデルが守らなかったときの備えとして、同じ確認を関数の側にも置いた形である。

結果 — ツールの呼び出しは2回とも指示どおりだった

5つの問いを、同じエージェントで2回動かした。表のツールの列は2回目の記録で、呼んだツールとその順番は1回目も同じだった。

問い呼んだツール(順)所要(2回目)所要(1回目)
PETG のスプールは何本ありますか?get_spool_stock(material=PETG)7.2 秒7.6 秒
p2 は今空いていますか?get_printer_status(printer_id=p2)2.2 秒2.9 秒
p1 で PLA を使って刷りたいです。空いていれば予約してください。get_printer_status(printer_id=p1) → get_spool_stock(material=PLA) → reserve_printer(material=PLA, printer_id=p1)5.1 秒5.0 秒
p1 を TPU で予約してください。get_printer_status(printer_id=p1) → get_spool_stock(material=TPU)4.7 秒5.0 秒
こんにちは。今日もよろしくお願いします。なし1.7 秒1.8 秒

2回とも、5問すべてで指示どおりの順番にツールを呼んだ。3問目の PLA の予約は、状態→在庫→予約の順で、決まりのとおりに予約まで進んでいる。4問目の TPU の予約は、状態→在庫と確かめたところで止まり、予約の関数を呼ばなかった。TPU の在庫は0本なので、これが決まりどおりの動きである。5問目のあいさつでは、ツールを1つも呼ばなかった。なお、3問目は2回とも、4問目は1回目だけ、状態と在庫の2つを1回の応答でまとめて呼び出していた(並びは状態→在庫)。予約の関数を呼んだのは、どちらの回も2つの結果を受け取った後である。

4問目で予約の関数が呼ばれなかったので、関数の側に置いた確認は、この実行では働く場面が無かった。モデルが決まりを守ったからだ。とはいえ、2回の実行で守れたことは、次の実行でも守る保証にはならない。予約や発注のように取り消しにくい操作をするツールには、関数の側の確認を残しておくほうがよい。

所要は1問あたり 1.7〜7.2 秒(2回目)と 1.8〜7.6 秒(1回目)で、ツールを3回呼んだ3問目より、1回だけ呼んだ1問目のほうが長かった。ただし1問目はどちらの回も最初に動かした問いで、所要は答えの長さによっても変わる。この記録から、ツールの数と所要の関係は読み取れない。

英語の推論の文の扱い

1回目の実行では、5問中4問で、最後の答えの文の頭に英語の推論の文が付いていた(残る1問も、ツールを呼ぶ途中で英語の文を出していた)。ただし1回目の記録は、モデルが返した思考(thinking)の部分と答えの部分を、区別せずにつないでいた。そのため、この英語の文が思考の部分だったのか、答えの本文に混ざったものだったのかは、記録からは分からない。そこで2回目は、2つを分けて記録した。

2回目の記録では、答えの文は5問とも日本語だけで、英語の推論の文は答えとは別の思考の部分(thought: true)として返ってきていた(あいさつを除く4問で計6件)。同じエージェントを別の実行で動かしたときにも、思考の部分が返ってきた。gemma4:12B は、ollama show の表示で thinking に対応し、既定で有効になっているモデルである。画面や記録に答えを出すときは、思考の部分を除いて答えの部分だけを取り出す処理を、最初から書いておきたい。

順番の Workflow — 決まりの確認は LLM ではなく Python の関数に任せる

順番の Workflow — 決まりの確認は LLM ではなく Python の関数に任せる

2つ目は、印刷の依頼を受け付ける Workflow だ。最初の LLM(intake_agent)が依頼の文を JSON に直し、次の Python の関数(check_rules)が素材と個数の決まりを確かめ、最後の LLM(reply_agent)が結果を日本語で返す。素材は PLA・PETG・ASA・TPU の4種類、個数は1〜20 の整数という決まりにした。

Workflow は、edges に START から順にノードを並べると、その順に動く。ノードには LLM のエージェントも、普通の Python の関数も置ける。公式サンプル(workflows/sequence)は Agent に model を指定していないので、手元のモデルで動かすために、各 Agent に LiteLlm を渡した。

import json
import re
from google.adk import Agent, Workflow
from google.adk.models.lite_llm import LiteLlm

MODEL = "ollama_chat/gemma4:12B"
ALLOWED = {"PLA", "PETG", "ASA", "TPU"}

intake_agent = Agent(
    name="intake_agent",
    model=LiteLlm(model=MODEL),
    instruction="""Read the user's print request and return only one JSON object:
{"part": <short English name>, "material": <PLA|PETG|ASA|TPU|OTHER>, "quantity": <integer>}.
No other text.""",
)

決まりを確かめる関数は、LLM が返した文から JSON を取り出し、素材と個数を調べる。JSON が壊れていて読めないときも、空の内容として扱うので、素材と個数の両方が問題として返る。

def check_rules(node_input: str) -> str:
    """Deterministic checks the LLM is not trusted with."""
    m = re.search(r"\{.*\}", str(node_input), re.S)
    try:
        spec = json.loads(m.group(0)) if m else {}
    except Exception:
        spec = {}
    problems = []
    if str(spec.get("material", "")).upper() not in ALLOWED:
        problems.append("material is not one we stock")
    q = spec.get("quantity")
    if type(q) is not int or q < 1 or q > 20:
        problems.append("quantity must be an integer from 1 to 20")
    return json.dumps({"spec": spec, "problems": problems}, ensure_ascii=False)

個数を isinstance(q, int) ではなく type(q) is not int で確かめるのは、Python では真偽値(bool)が int のサブクラスで、isinstance では JSON の true が個数1として通ってしまうからだ。この実行で動かしたコードは isinstance(q, int) で確かめていたが、2件の依頼で最初の LLM が返した個数は 3 と 50 の整数だったので、どちらの書き方でも結果は変わらない。

最後の LLM と、3つを順に並べた Workflow は次のとおりである。

reply_agent = Agent(
    name="reply_agent",
    model=LiteLlm(model=MODEL),
    instruction="""You receive a JSON object with "spec" and "problems".
If "problems" is empty, confirm the order in two Japanese sentences.
Otherwise, explain each problem in Japanese and ask the user to fix it.""",
)

root_agent = Workflow(
    name="seq_workflow",
    edges=[("START", intake_agent, check_rules, reply_agent)],
)

依頼を2件、1回ずつ動かした結果が次の表だ。最後の出力は、最後の LLM が返した文の改行を詰めたものである。

依頼所要最後の出力
PETG でスマホスタンドを3個刷ってください。12.8 秒注文内容を確認いたしました。PETG製のフォンスタンドを3個、承りました。
ナイロンでギアを50個お願いします。8.0 秒以下の問題があるため、修正をお願いします。・素材が在庫品に含まれていません。・数量は1から20までの整数である必要があります。

1件目は決まりに合っていたので、関数は問題なしを返し、最後の LLM が受け付けの文を書いた。2件目は、ナイロンが決めた4種類に入らず、50個が上限の 20 を超える。関数がこの2点を問題として返し、最後の LLM がそれを日本語で説明した。

なぜ確認を関数に任せるのか

素材が決めた4種類に入るか、個数が1〜20 の範囲かは、規則で書ける確認だ。LLM に確かめさせても、この実行では正しく判断できたかもしれない。しかし、次の実行で同じ答えが返る保証はなく、間違えたときにどこで間違えたかも追いにくい。これに対して関数は、同じ入力に毎回同じ答えを返す。

そこで LLM には、関数では書きにくい部分だけを任せた。自由に書かれた依頼の文を決まった形の JSON に直す部分と、確認の結果を人に伝わる文にする部分である。この分担は、試験ガイドの 3.3 にある「順番のエージェント」と「グラフのワークフロー」を、手元で具体的に考える練習になる。どこまでを LLM に任せ、どこからを決まった処理にするかは、エージェントの設計で選ぶことそのものだからだ。

並列の Workflow — 3つの視点を同時に走らせ、JoinNode で合流する

並列の Workflow — 3つの視点を同時に走らせ、JoinNode で合流する

3つ目は、1つの依頼を3つの視点で同時に見る Workflow だ。素材の選び方、強度と造形の向き、造形の時間とその短縮の3つの視点の LLM を、START から並列に動かした。JoinNode が3つの完了を待って合流し、最後の Python の関数(merge)が3つの答えを1つにまとめる。

公式ドキュメントによると、Workflow では START を複数の行に書くと並列の道になり、JoinNode が全部の完了を待つ合流点になる。この実行では、公式サンプル(workflows/fan_out_fan_in)と同じく、並列にするノードをタプルで束ねる書き方を使った。

from typing import Any
from google.adk import Agent, Event, Workflow
from google.adk.models.lite_llm import LiteLlm
from google.adk.workflow import JoinNode

MODEL = "ollama_chat/gemma4:12B"

def reviewer(name: str, focus: str) -> Agent:
    return Agent(
        name=name,
        model=LiteLlm(model=MODEL),
        instruction=f"""Review the user's 3D-print request only from the viewpoint of {focus}.
Answer in at most two Japanese sentences.""",
    )

material_reviewer = reviewer("material_reviewer", "filament material choice")
strength_reviewer = reviewer("strength_reviewer", "mechanical strength and print orientation")
time_reviewer = reviewer("time_reviewer", "print time and how to shorten it")
join_node = JoinNode(name="join_reviews")

async def merge(node_input: dict[str, Any]):
    yield Event(message="\n".join(f"[{k}] {v}" for k, v in node_input.items()))

root_agent = Workflow(
    name="par_workflow",
    edges=[("START", (material_reviewer, strength_reviewer, time_reviewer), join_node, merge)],
)

依頼は「棚を支える L 字ブラケットを PLA で刷りたい。耐荷重は 5kg くらい欲しい。」の1件で、同じ依頼を2回動かした。比べるために、同じ3つの LLM を1つずつ順に動かした所要も測った。

動かし方所要
並列の Workflow(1回目)41.2 秒
並列の Workflow(2回目)36.9 秒
3つを1つずつ(素材 21.2 秒+強度 10.3 秒+時間 11.6 秒)43.1 秒

3つの視点の答えは JoinNode で合流し、[time_reviewer] のように視点の名前を付けて1つにまとまった。2回目の答えから例を挙げると、時間の視点は「レイヤーの高さを上げる、または印刷速度を上げることで時間を短縮できます。」と返している。ただし、これはモデルの出力で、当サイトが確かめた助言ではない。

手元の Ollama 1台では、並列に書いても所要はほとんど縮まなかった

並列の所要は 41.2 秒と 36.9 秒で、1つずつ動かした合計の 43.1 秒とほとんど変わらなかった。短いほうの 36.9 秒でも、差は 6.2 秒(43.1 − 36.9)である。とはいえ答えの長さが毎回違うので、秒数の比較は目安にとどまる。

この実行では、Ollama の OLLAMA_NUM_PARALLEL を設定していなかった。設定を変えた比較はしていないので、縮まなかった原因は切り分けていない。言えるのは、Workflow で並列に書くことと、手元の GPU 1枚の上で計算が速く終わることは別だ、という点までである。

そのため、並列に書く意味を所要の短縮だけで考えないほうがよい。3つの視点を互いに独立したノードとして書けること、合流点で答えをそろえて次の処理に渡せることは、所要とは関係なく得られる構造の側の利点だ。試験ガイドの 3.3 も、並列のエージェントを「選ぶ」(Selecting)対象として挙げている。

順番と並列のワークフローを手元で書けたら、次は複数のエージェントを本番でどう組み合わせ、どう運用するかが問題になる。Google の A2A と ADK でマルチエージェントを設計する一冊を挙げておく。

旧クラスの SequentialAgent と ParallelAgent は v2.11.0 で非推奨

旧クラスの SequentialAgent と ParallelAgent は v2.11.0 で非推奨

ADK には以前から、SequentialAgent・ParallelAgent・LoopAgent という3つのクラスがある。公式ドキュメントはこれらをテンプレートのワークフローと呼び、モデルに頼らず決まった順で子のエージェントを動かすものと説明している。SequentialAgent はリストの順に動かし、ParallelAgent は同時に動かし、LoopAgent は決めた回数か終了の条件まで繰り返す。また、ParallelAgent の枝どうしでは、会話の履歴は分かれる。ただし v2.11.0 のコードの説明では、セッションの状態(session state)は全部の枝で共有され、同じキーに書くと最後に書いた値だけが残る。ドキュメントのページは状態も自動では共有しないと書いていて、表現が揃っていない。

ところが公式ドキュメントは、Python と Go の ADK 2.0 からは、テンプレートのワークフローは、グラフのワークフローや動的なワークフロー(dynamic workflows)などのより柔軟な仕組みに置き換わった(superseded)と書いている。この記事ではグラフのワークフローだけを扱う。さらに v2.11.0 のコードでは、3つのクラスとも非推奨(deprecated)とされ、将来の版で削除される予定と書かれている。それでも v2.11.0 ではまだ読み込める。SequentialAgent は作ることもでき、作ると次の警告が出た。

DeprecationWarning: SequentialAgent is deprecated in favor of Workflow and will be removed in a future version. Workflow cannot yet be used as an LlmAgent sub-agent.

前半は、「Workflow を使うよう SequentialAgent は非推奨になり、将来の版で削除される」という意味である。後半は Workflow の側の制限で、「Workflow は、まだ LlmAgent の子エージェントにはできない」と書いている。旧クラスから Workflow へ移るときは、この制限に当たらないかを確かめる必要がある。

公式ドキュメントの各ページは、旧クラスを今も通常の機能として説明し、ページの上部に置き換えの注記を付けている。コードの非推奨のほうが強い表現なので、この記事はコードに合わせた。

試験ガイドの言葉と ADK の書き方を対応させる

試験ガイドの 3.3 は「parallel agents, sequential agents, and graph workflow」と3つを並べている。したがって、用語としては3つとも押さえておく必要がある。v2.11.0 での書き方との対応は、次のとおりである。

試験ガイドの言葉ADK v2.11.0 での書き方この記事で動かしたか
順番のエージェント(sequential agents)Workflow の edges にノードを順に並べる。旧来の SequentialAgent は非推奨Workflow で動かした
並列のエージェント(parallel agents)Workflow で START から複数のノードに分け、JoinNode で合流させる。旧来の ParallelAgent は非推奨Workflow で動かした
グラフのワークフロー(graph workflow)Workflow(Python では v2.0.0 から)上の2つで使った

旧クラスの意味を知るには、公式ドキュメントのテンプレートのワークフローの説明が役に立つ。ただし、新しく書くコードは Workflow で書き、旧クラスは自分で警告を出して確かめる程度にとどめた。LoopAgent も同じく非推奨だが、この記事では動かしていない。

セッションとメモリ — 手元で動くものと Google Cloud が要るもの

セッションとメモリ — 手元で動くものと Google Cloud が要るもの

試験ガイドの 3.1 は、セッションとメモリの設定(Configuring sessions and memory)を挙げ、例に Agent Platform Memory Bank と managed sessions を出している。ADK のセッションとメモリには、手元で動く実装と、Google Cloud が要る実装がある。公式ドキュメントで確かめた分け方を、表にする。

クラスや機能動く場所公式ドキュメントの説明
InMemorySessionService手元手元のメモリに置く。再起動で会話のデータは消える。この記事の Runner はこれを使った
DatabaseSessionService手元SQLite・PostgreSQL・MySQL などに表として残す
adk web・adk run の既定手元エージェントごとの SQLite(.adk/session.db)。手元に保存する指定が既定で有効
InMemoryMemoryService手元キーワードの一致で探す。試作と手元の開発向け
SQLite のメモリ手元v2.11.0 で追加(sqlite:// で指定)
VertexAiSessionServiceGoogle Cloudサービス名は Agent Platform Sessions(試験ガイドの managed sessions に当たると当サイトは読む)。Google Cloud のプロジェクト、Cloud Storage のバケット、Agent Runtime のインスタンスが要る(コードのデプロイは要らない)
VertexAiMemoryBankServiceGoogle CloudMemory Bank。Agent Platform 上の Agent Runtime のインスタンスが要る
VertexAiRagMemoryServiceGoogle CloudRAG のメモリ。Knowledge Engine が要る

クラス名には「VertexAi」が残っているが、製品の名前は Agent Platform で、試験ガイドも「Agent Platform Memory Bank」と書いている。クラス名と製品名を取り違えないようにしたい。

手元で練習できるのは、セッションを残すか消すか、メモリをどう探すか、という仕組みの部分である。たとえば InMemorySessionService で動かしたエージェントは、公式ドキュメントのとおりなら再起動で前の会話を失う。DatabaseSessionService や SQLite のメモリに替えれば、手元でも残せる。この記事では、残る・消えるの違いを動かして確かめてはいない。一方、マネージドの Memory Bank がどう覚えてどう取り出すかは、Google Cloud で動かさないと確かめられない。

express mode は「Google Cloud なし」に入れない

ADK の公式ドキュメントには、Agent Platform の express mode も載っている。正式な Google Cloud のプロジェクトを作らずに、Agent Platform のセッションや Memory Bank を試せる無料の枠で、試作と開発のためのものだ。ただしプレビューで、express mode のプロジェクトは 90 日しか有効でない。Agent Runtime へのデプロイには、有料のアクセスが要る。

express mode は、Google のアカウントと API キーを使う Google Cloud のサービスである。そのため、この記事の「Google Cloud なし」の範囲には入れない。Memory Bank などのクラウド側を無料で試す道として、手元の練習とは別に考えるとよい。

§3 の項目ごとに、手元でできることと Google Cloud が要ることを分ける

§3 の項目ごとに、手元でできることと Google Cloud が要ることを分ける

ここまでの実行と公式ドキュメントをもとに、§3 の項目を「手元でできる」「一部」「Google Cloud が要る」に分けた。「手元でできる」は、Windows の Python と Ollama だけで、Google のアカウントも API キーも使わずに動かせるものだ。「一部」は、仕組みは手元で練習できるが、試験ガイドが例に挙げた Google Cloud の製品はクラウドが要るものである。

試験ガイドの項目手元でできることGoogle Cloud が要ること分け方
3.1 モデルの選び方自前で動かす、重みが公開されたモデルを ADK から使う(この記事は gemma4:12B)SaaS や専有のモデル(Gemini など)と比べるには API キーが要る。Gemini API のキーは Google AI Studio で作れる(キーは Google Cloud のプロジェクトに結び付き、新しい利用者には AI Studio が既定のプロジェクトを自動で作る)一部
3.1 ADK でカスタムエージェントを作るAgent・関数のツール・Workflow を書いて動かす(この記事で動かした)なし手元でできる
3.1 セッションとメモリInMemory と SQLite のセッション・メモリMemory Bank と managed sessions(どちらも Agent Runtime のインスタンスが要る)一部
3.1 Agents CLI でスキルを設定ADK 本体のスキル(実験的)とプラグインをコードで使うAgents CLI には Gemini API キーか Google Cloud の資格情報が要る一部
3.2 RAG とベクトル検索手元だけで完結する RAG の公式の手順は確認していないVector Search・Agent Retrieval、RAG のメモリ(Knowledge Engine)Google Cloud が要る
3.2 エージェントの権限なしAgent Identity(製品は GA。ADK の Auth Manager 連携はドキュメントでプレビュー表示)Google Cloud が要る
3.2 Google Cloud のツールで機能をつなぐMCP のサーバーを McpToolset でつなぐ仕組みAgent Registry(プロジェクトで API を有効にする)、Google Cloud MCP Servers一部
3.3 プロトコルでつなぐMCP(McpToolset)、A2A(localhost で公開して呼ぶ。Python では実験的)なし(本番の配置は別)手元でできる
3.3 マルチエージェントの引き継ぎとワークフローWorkflow の順番と並列(この記事で動かした)。旧クラスは非推奨Agent Identity・Agent Registry・Agent Runtime・agent policies一部

このうち、この記事で実際に動かしたのは、「ADK でカスタムエージェントを作る」と「マルチエージェントの引き継ぎとワークフロー」の手元の部分だけである。MCP や A2A、スキル、プラグインは、公式ドキュメントで手元で動く仕組みがあると確かめた段階にとどまる。

表を読むときの注意

モデルの選び方の行にある「SLM」は、試験ガイドに定義が無い。そのため、この記事では gemma4:12B を SLM とは呼ばない。同じく「OSS」も、Gemma 4 については「Apache 2.0 で重みが公開されたモデル」とだけ書く。学習データやコードの公開までは確かめていないので、「オープンソースのモデル」とは断定しない。

Agents CLI の行の「agent vs. human mode」は、公式資料で意味を特定できなかったので、ここでは説明しない。なお Agents CLI は Python 3.11 以上で動き、手元のモデルだけで使う手順は公式資料に見当たらなかった。

RAG の行は、手元で仕組みだけを真似ることはできる。たとえば関数のツールで手元の文書を探させれば、検索して答える流れは書ける。ただし、それは当サイトが考えた練習の方法で、公式の手順ではない。試験ガイドが例に挙げる Vector Search や Agent Retrieval は、Google Cloud で動かす製品である。

Agent Runtime は Google Cloud のマネージドな実行環境で、公式ドキュメントによると有料のサービスであり、無料の枠を超えると費用がかかる。Agent Identity は 2026 年 4 月 22 日に GA になった Google Cloud のサービスで、ADK から使うには Google Cloud のプロジェクトと認証の設定が要る。ADK の連携機能 Agent Identity Auth Manager は、ADK のドキュメントではプレビューと表示されている(IAM のリリースノートでは、auth manager は 2026 年 8 月 22 日に GA になった)。Agent Registry も、Google Cloud のプロジェクトで API を有効にして使う。どれも、手元で代わりのものを動かして練習した気にならないほうがよい。

手元の練習は実技ラボの代わりにならない

試験は選択式の試験と実技ラボの2部構成で、公式の FAQ によると、実技ラボは Google Skills で行い、稼働中のクラウドの環境で実行とコーディングの力を確かめる。手元で ADK を書けることは、その準備の一部にすぎない。表の「Google Cloud が要る」の行は、手元の ADK ではなく、Google Cloud の環境で練習する部分になる。

Memory Bank や Agent Runtime のような Google Cloud の側は、手元では動かせない。クラウドの構成の中でエージェントをどこに置くかを考えるには、AI を組み込んだ Google Cloud の設計を扱う次の一冊が役に立つ。

この実行で言えないこと

この実行で言えないこと

結果を読むときの限界を、まとめて書いておく。

  • 各条件1回か2回の実行: ツールのエージェントと並列の Workflow は2回、順番の Workflow は依頼ごとに1回である。「この実行ではこうだった」以上に一般化できない
  • モデルは gemma4:12B だけ: 公式サンプルの mistral-small3.1 や、Gemini などのクラウドのモデルとは比べていない。ADK の公式ドキュメントの Ollama と Gemma のページに、gemma4 を Ollama で使う例は無い
  • 問いは少なく、データは架空: ツールのエージェントは5問、順番の Workflow は2件、並列の Workflow は1件の依頼で、在庫とプリンターの値は実験用に作ったもの
  • 所要時間は目安: 答えの長さが毎回違う。並列の比較では Ollama の設定を変えていない。adk run・adk web での所要は測っていない
  • 英語の推論の文の記録: 1回目の記録は思考の部分と答えを区別していないので、英語の文がどちらだったかは分からない
  • 並列の答えの中身: 強度や時間についての助言はモデルの出力で、当サイトが確かめたものではない
  • Google Cloud の側は動かしていない: Memory Bank・Agent Identity・Agent Registry・Agent Runtime などは、公式ドキュメントで何が要るかを確かめただけ
  • 試験の内容との関係: 当サイトは受験していない。手元で動かせることと、試験で問われる設計の判断は別で、実技ラボの代わりにもならない

それでも、ADK v2.11.0 が LiteLLM 経由で手元のモデルにつながり、ツールを持つエージェントと順番・並列の Workflow がこの環境で動いたことは、この記録から言える。

まとめ — ADK のエージェントを手元で書き、Google Cloud の部分は公式資料で補う

まとめ — ADK のエージェントを手元で書き、Google Cloud の部分は公式資料で補う
  • ADK Python v2.11.0(Apache 2.0、Python 3.10 以上)は、LiteLLM 経由で Ollama のモデルにつなげば、Google Cloud のアカウントも API キーも使わずに手元で動く。モデル名には、LiteLLM の書き方で ollama_chat/ を付ける
  • ツールを3つ持つエージェントは、この実行では2回とも、5問すべてで指示どおりの順番にツールを呼んだ。取り消しにくい操作は、関数の側でも確かめる
  • 順番の Workflow では、規則で書ける確認を Python の関数のノードに任せ、LLM には文を JSON に直す部分と結果を説明する部分を任せた
  • 並列の Workflow は動いたが、手元の Ollama 1台では所要がほとんど縮まなかった(41.2 秒・36.9 秒 対 1つずつの合計 43.1 秒)
  • SequentialAgent などの旧クラスは v2.11.0 で非推奨で、警告が出る。新しく書くなら Workflow で書き、試験ガイドの3つの言葉との対応を押さえる
  • §3 のうち、エージェントの作り方・ワークフロー・MCP と A2A は手元で練習できる。Memory Bank・Agent Identity・Agent Registry・Agent Runtime などは Google Cloud が要る

練習の順番としては、まずツールを1つ持つ ADK のエージェントから始めるとよい。ollama show で tools への対応を確かめてから動かし、呼び出しの順番を記録で確かめる。次に Workflow で順番と並列を組み、どの確認を関数のノードに置くかを自分で決める。旧クラスは、警告を出して意味を読む程度で足りる。

Google Cloud が要る項目は、公式ドキュメントで「何が要るか」を押さえたうえで、クラウドの環境で練習する。手元とクラウドの担当を用途で振り分ける考え方は、ローカルとクラウドの担当を決める — 用途で振り分けるAI環境構築の逆引きガイド(2026-08-30 公開) にまとめてある。試験の状態や日程は 2026 年 10 月時点の情報なので、申込の前に公式のページで確かめ直してほしい。

参照

ブラウザだけでできる本格的なAI画像生成【ConoHa AI Canvas】
ABOUT ME
swiftwand
swiftwand
AIを使って、毎日の生活をもっと快適にするアイデアや将来像を発信しています。 初心者にもわかりやすく、すぐに取り入れられる実践的な情報をお届けします。 Sharing ideas and visions for a better daily life with AI. Practical tips that anyone can start using right away.
記事URLをコピーしました