はじめに

この蚘事は、Google ADK によるマルチ゚ヌゞェント瀟内ITヘルプデスク構築を題材にした党4回シリヌズの第2回です。

回 テヌマ
第1回 Gemini CLI で゚ヌゞェントを動かしおみる
第2回本蚘事 ADK でマルチ゚ヌゞェントを構築し Google Cloud にデプロむする
第3回 ADK マルチ゚ヌゞェントを BigQuery ず連携させる
第4回 Vertex AI Evaluation で ADK マルチ゚ヌゞェントの応答品質を枬る

前回では、Gemini CLI を䜿っお GEMINI.md 1぀で ITヘルプデスク゚ヌゞェントを動かし、゚ヌゞェントの基本的な動きを䜓感したした。
同時に、シングル゚ヌゞェントには物足りなさを感じる堎面も出おきたしたFAQ デヌタを参照できない・䌚話の文脈が続かない・耇合問題を単䞀゚ヌゞェントが党郚匕き受けおしたう。

本蚘事ではその物足りなさを解消しおいきたす。Google ADKAgent Development Kitを䜿い、「ルヌタヌ・FAQ怜玢・゚スカレヌション」の3぀の゚ヌゞェントが連携するマルチ゚ヌゞェントシステムを構築し、Google Cloud にデプロむしたす。

Google ADK ずは

Google ADKAgent Development Kit は、Google が提䟛する゚ヌゞェント開発甚の Python フレヌムワヌクです。

特城 内容
マルチ゚ヌゞェント察応 sub_agents で゚ヌゞェントの芪子構造を定矩できる
ツヌル連携 Python 関数をツヌルずしお゚ヌゞェントに枡せる
Google Cloud ず盎結 Vertex AI Agent Engine に数行でデプロむできる
ロヌカル実行察応 Runner クラスでロヌカル環境でも動䜜確認できる

第1回はファむル1枚で゚ヌゞェントを動かしたしたが、ADK ではコヌドを曞く分だけ自由床が䞊がりたす。

構築するシステム

ナヌザヌ問い合わせ
    ↓
① ルヌタヌ゚ヌゞェント受付・分類
    └─ ② FAQ怜玢゚ヌゞェントBigQuery照䌚 → 回答生成
           └─ ③ ゚スカレヌション゚ヌゞェントチケット生成

ADK の゚ヌゞェントは芪子構造で繋がりたす。芪゚ヌゞェントがナヌザヌの入力を受け取り、sub_agents ずしお登録された子゚ヌゞェントに凊理を任せる、ずいう流れです。
子゚ヌゞェントはさらに孫゚ヌゞェントを持おるので、3段階以䞊のチェヌンも䜜れたす。
*本蚘事ではモックデヌタを䜿っお構造だけを䜜り、動䜜を確認したす。

article2-adk-multiagent/
├── agents/
│   ├── __init__.py
│   ├── escalation.py   ← ③ ゚スカレヌション゚ヌゞェント
│   ├── faq_searcher.py ← ② FAQ怜玢゚ヌゞェントモックデヌタ
│   └── router.py       ← ① ルヌタヌ゚ヌゞェント゚ントリヌポむント
├── .env                ← 環境倉数Google Cloud プロゞェクト蚭定
├── demo.py             ← ロヌカル動䜜確認スクリプト
├── deploy.py           ← Vertex AI Agent Engine デプロむスクリプト
└── requirements.txt

実装

③ ゚スカレヌション゚ヌゞェントescalation.py

他の゚ヌゞェントを呌び出さない䞀番シンプルな構造なので、ここから䜜りたす。

from google.adk.agents import Agent

escalation_agent = Agent(
    name="escalation_agent",
    model="gemini-2.5-flash",
    description=(
        "FAQ で解決できなかった問い合わせや、管理者暩限・物理障害・"
        "セキュリティむンシデントが必芁な耇雑なケヌスを担圓する。"
        "有人察応チケットを生成しお匕き継ぎメッセヌゞを返す。"
    ),
    instruction="""
あなたは瀟内 IT ヘルプデスクの゚スカレヌション担圓゚ヌゞェントです。
FAQ 怜玢゚ヌゞェントが解決できなかった問い合わせを受け取り、有人察応チケットを䜜成したす。

【有人察応チケット】
カテゎリ問い合わせのカテゎリ
芁玄問い合わせ内容を 1〜2 文で芁玄
緊急床高 / äž­ / 䜎
    """,
)

description は、芪゚ヌゞェントが「どのケヌスでこの子゚ヌゞェントを呌ぶか」を刀断するための説明文です。LLM がこのテキストを読んで「この゚ヌゞェントに任せるべきか」を考えたす。

コヌドは抜粋です。実装では instruction に出力フォヌマットず、緊急床高 / äž­ / 䜎の刀定基準䟋高セキュリティむンシデント疑い・業務停止を続けお曞いおいたす。

② FAQ怜玢゚ヌゞェントfaq_searcher.py

from google.adk.agents import Agent
from agents.escalation import escalation_agent

# モック FAQ デヌタ蚘事3で BigQuery に差し替える
# 実際にはネットワヌク・アカりント・ハヌドりェア・゜フトりェアの4カテゎリ分を甚意
MOCK_FAQ_DATA = [
    {
        "faq_id": "NW-001",
        "category": "ネットワヌク系",
        "question": "VPN に接続できない / 認蚌゚ラヌが出る",
        "answer": "...",
        "keywords": "VPN,接続できない,認蚌゚ラヌ,ログむンできない",
    },
    # ... 他3カテゎリアカりント・ハヌドりェア・゜フトりェアも同じ圢匏で甚意
]

def search_faq(query: str) -> str:
    """FAQ デヌタをキヌワヌド怜玢する"""
    query_lower = query.lower()
    for faq in MOCK_FAQ_DATA:
        keywords = faq["keywords"].split(",")
        if any(kw.strip() in query_lower for kw in keywords):
            return f"カテゎリ{faq['category']}\n\n【FAQ ID: {faq['faq_id']}】\n..."
    return "該圓する FAQ が芋぀かりたせんでした。"

faq_searcher_agent = Agent(
    name="faq_searcher_agent",
    model="gemini-2.5-flash",
    description="ネットワヌク・アカりント・ハヌドりェア・゜フトりェアに関する IT 問い合わせを FAQ から怜玢しお回答する。",
    instruction="search_faq ツヌルで怜玢し、FAQ が芋぀かった堎合は回答する。芋぀からない堎合は escalation_agent に匕き継ぐ。",
    tools=[search_faq],           # Python 関数をツヌルずしお枡す
    sub_agents=[escalation_agent], # 解決できない堎合ぱスカレヌションぞ
)

tools=[search_faq] ず曞くだけで、search_faq 関数が゚ヌゞェントから「呌び出せるツヌル」ずしお扱われたす。
LLM が䌚話の流れから「いたツヌルを呌ぶべきか」を刀断しお、必芁なら関数を実行しおくれたす。

コヌドは抜粋です。実装では instruction に圹割・回答圢匏・泚意事項FAQ にない情報を勝手に補わない、芋぀からない堎合は必ず escalation_agent に匕き継ぐ等を现かく曞いおいたす。

① ルヌタヌ゚ヌゞェントrouter.py

from google.adk.agents import Agent
from agents.faq_searcher import faq_searcher_agent

router_agent = Agent(
    name="router_agent",
    model="gemini-2.5-flash",
    description="瀟内 IT ヘルプデスクのルヌタヌ゚ヌゞェント。",
    instruction="""
すべおの IT 問い合わせを受け取り、faq_searcher_agent に振り分けたす。
faq_searcher_agent が FAQ 照䌚・回答生成・゚スカレヌション刀定を担圓したす。
自分で回答を生成せず、必ず faq_searcher_agent に振り分けるこず。
    """,
    sub_agents=[faq_searcher_agent],
)

# ゚ントリヌポむントADK が認識するルヌト゚ヌゞェント
root_agent = router_agent

sub_agents に登録された子゚ヌゞェントは、芪゚ヌゞェントから「呌び出せる盞手」ずしお扱われたす。
ルヌタヌが「どの子゚ヌゞェントに任せるか」を、ナヌザヌの問い合わせ内容ず各゚ヌゞェントの description を読んで刀断したす。

コヌドは抜粋です。実装では instruction に察応カテゎリネットワヌク・アカりント・ハヌドりェア・゜フトりェア・セキュリティの䞀芧ず、ルヌタヌ自身は回答を生成しないルヌルを明蚘しおいたす。

ロヌカルで動かす

ADK では Runner にルヌト゚ヌゞェントを枡し、セッションを䜜っお問い合わせを流す構成になりたす。

from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from agents.router import root_agent

runner = Runner(agent=root_agent, app_name="helpdesk_demo",
                session_service=InMemorySessionService())

# session 䜜成 → runner.run_async() に問い合わせを枡し、
# event.is_final_response() で最終回答を拟う

.env ファむルで Vertex AI ぞの接続先を蚭定したす。

GOOGLE_GENAI_USE_VERTEXAI=1
GOOGLE_CLOUD_PROJECT=your-project-id
GOOGLE_CLOUD_LOCATION=us-central1

実行結果

python demo.py で、甚意した4ケヌスを順に投げお挙動を確認したした。代衚ずしお「FAQ で解決できるケヌス」ず「゚スカレヌションが必芁なケヌス」を1件ず぀茉せたす。
FAQ で解決できるケヌスVPN 認蚌゚ラヌ

ナヌザヌ: VPN に接続しようずするず認蚌゚ラヌが出お接続できたせん

゚ヌゞェント:
カテゎリネットワヌク系

以䞋の手順をお詊しください
1. Caps Lock がオフになっおいるか確認する
2. パスワヌドを再入力する
3. MFA アプリの認蚌コヌドが最新か確認する
4. 別のネットワヌクテザリング等に切り替えお詊す
䞊蚘で解決しない堎合は IT 担圓者ぞお問い合わせください。

゚スカレヌションが必芁なケヌスセキュリティむンシデント

ナヌザヌ: 自分のアカりントで身に芚えのないログむン履歎がありたす

゚ヌゞェント:
申し蚳ございたせん。お問い合わせの内容に぀いお、自動察応での解決が難しい状況です。
IT 担圓者ぞ匕き継ぎたすので、以䞋のチケット情報をご確認ください。

【有人察応チケット】
カテゎリセキュリティ
芁玄アカりントに身に芚えのないログむン履歎がある
緊急床高

他2ケヌスパスワヌドリセット・党瀟障害も期埅どおり、それぞれ FAQ 回答ず゚スカレヌションチケットを返したした。ルヌタヌ → FAQ怜玢 → ゚スカレヌションずいう3段階の匕き枡しが、if 文によるルヌルを1行も曞かずに LLM の刀断だけで動いおいたす。

Google Cloud ぞのデプロむVertex AI Agent Engine

ロヌカルで動䜜確認できた゚ヌゞェントを、deploy.py で Google Cloud にデプロむしたす。

事前準備ステヌゞング甚 GCS バケットの䜜成

Vertex AI Agent Engine はデプロむ時にコヌドや䟝存関係を Cloud Storage 経由で受け枡すため、ステヌゞング甚バケットが必須です。バケット名は䞀意なので、helpdesk-agent-staging-<任意の文字列> のように自分の環境で重耇しない名前に眮き換えおください。

gcloud storage buckets create gs://helpdesk-agent-staging-<任意の固有文字列> \
    --project=your-project-id \
    --location=us-central1 \
    --uniform-bucket-level-access

.env の远蚘

ロヌカル実行時の .env に、ステヌゞングバケットを1行远蚘したす。

GOOGLE_CLOUD_STAGING_BUCKET=gs://helpdesk-agent-staging-<任意の固有文字列>

deploy.py

import os

import vertexai
from dotenv import load_dotenv
from vertexai import agent_engines

from agents.router import root_agent

# .env の倀をシェル環境倉数より優先シェルに叀い GOOGLE_CLOUD_LOCATION
# が残っおいお意図しないリヌゞョンに飛ぶ事故を防ぐ
load_dotenv(override=True)

vertexai.init(
    project=os.environ["GOOGLE_CLOUD_PROJECT"],
    location=os.environ["GOOGLE_CLOUD_LOCATION"],
    staging_bucket=os.environ["GOOGLE_CLOUD_STAGING_BUCKET"],
)

# ゚ヌゞェントを AdkApp でラップAgent Engine が認識できる圢匏に倉換
adk_app = agent_engines.AdkApp(agent=root_agent, enable_tracing=True)

# extra_packages: agents/ パッケヌゞをコンテナに同梱
# これがないず Reasoning Engine 環境で from agents.router import ... に倱敗する
remote_agent = agent_engines.create(
    adk_app,
    requirements=[
        "google-adk>=1.0.0",
        "google-cloud-aiplatform[agent_engines,adk]>=1.93.0",
    ],
    extra_packages=["agents"],
    display_name="IT ヘルプデスク マルチ゚ヌゞェント",
    description="ルヌタヌ・FAQ怜玢・゚スカレヌションの3゚ヌゞェント構成",
)

print(f"デプロむ完了: {remote_agent.resource_name}")

ハマりどころ

  • extra_packages=["agents"] を忘れるず、コンテナ起動時に ModuleNotFoundError: No module named 'agents' で倱敗したす。AdkApp は root_agent を pickle 化しお送りたすが、agents/ ディレクトリ自䜓は別途同梱する必芁がありたす。
  • staging_bucket を省略するず ValueError: Please provide a staging_bucket で停止したす最近の SDK で必須化されたした。
  • シェル環境倉数 GOOGLE_CLOUD_LOCATION などが既に蚭定されおいるず .env の倀より優先されるため、load_dotenv(override=True) で .env を適甚するのが安党です。

実行結果

$ python deploy.py
agent_engine.pkl・requirements.txt・dependencies.tar.gz をステヌゞングぞアップロヌド
Creating AgentEngine
Create AgentEngine backing LRO: projects/<YOUR_PROJECT_NUMBER>/locations/us-central1/reasoningEngines/<YOUR_REASONING_ENGINE_ID>/operations/...
デプロむ完了: projects/<YOUR_PROJECT_NUMBER>/locations/us-central1/reasoningEngines/<YOUR_REASONING_ENGINE_ID>

ステヌゞングぞのアップロヌドからコンテナビルド、Reasoning Engine の起動たで、デプロむには5分ほどかかりたした。

デプロむした゚ヌゞェントを呌び出す

デプロむした゚ヌゞェントは、リ゜ヌス名から agent_engines.get() で取埗しお呌び出したす。セッションを䜜っおから stream_query で問い合わせを送る流れですADK ゚ヌゞェントは streaming で結果を受け取る圢のみ甚意されおいたす。

from vertexai import agent_engines

# `deploy.py` の実行結果に出力された自分のリ゜ヌス名に眮き換える
remote_agent = agent_engines.get(
    "projects/<YOUR_PROJECT_NUMBER>/locations/us-central1/reasoningEngines/<YOUR_REASONING_ENGINE_ID>"
)

session = remote_agent.create_session(user_id="blog-demo-user")
for event in remote_agent.stream_query(
    user_id="blog-demo-user",
    session_id=session["id"],
    message="VPN に接続できたせん",
):
    for part in (event.get("content") or {}).get("parts", []):
        if text := part.get("text"):
            print(text, end="")

※冒頭に load_dotenv(override=True) ず vertexai.init(project=..., location=...) が必芁です。
クラりド偎での実行結果VPN 認蚌゚ラヌのケヌスを抜粋。他3ケヌスもロヌカルず同じ結果になりたした

ナヌザヌ: VPN に接続しようずするず認蚌゚ラヌが出お接続できたせん

゚ヌゞェント:
カテゎリネットワヌク系
以䞋の手順をお詊しください
1. Caps Lock がオフになっおいるか確認する
2. パスワヌドを再入力する
3. MFA アプリの認蚌コヌドが最新か確認する
4. 別のネットワヌクテザリング等に切り替えお詊す

䞊蚘で解決しない堎合は IT 担圓者ぞお問い合わせください。

コスト管理

Reasoning Engine はデプロむした瞬間から課金が始たりたす。動䜜確認だけなら、終わったらすぐ削陀しおおくず安心です。

gcloud ai reasoning-engines delete <RESOURCE_ID> \
    --region=us-central1 \
    --project=your-project-id

たずめず次回に぀いお

ADK を䜿っお3぀の゚ヌゞェントが連携するマルチ゚ヌゞェントシステムを構築し、Google Cloud にデプロむできたした。

できたこず 詳现
マルチ゚ヌゞェントの芪子構造 sub_agents で3゚ヌゞェントを連携
ツヌル連携 search_faq 関数を゚ヌゞェントのツヌルずしお統合
゚ヌゞェント間の振り分け LLM がどの゚ヌゞェントに任せるかを文脈から刀断
Google Cloud ぞのデプロむ Vertex AI Agent Engine に数行でデプロむ

珟時点では FAQ デヌタはモックです。
次回は BigQuery に実際の FAQ デヌタを栌玍し、FAQ怜玢゚ヌゞェントが BigQuery にク゚リを実行する構成に差し替えたす。

参考リンク