KEEPRO iPad ペンシル 2018年以降iPad対応 アップルペンシル Type-C急速充電 タッチペン ぺアリング不要 傾き感知/誤作動防止/超高感度/磁気吸着 iPad Pro 11/12.9インチ、iPad Pro 11/13インチ(M4)、iPad 6/7/8/9/10、iPad Air 3/4/5、iPad Air 11/13インチ(M2)、iPad Mini 5/6/7に対応
¥1,499 (2025年4月25日 13:08 GMT +09:00 時点 - 詳細はこちら価格および発送可能時期は表示された日付/時刻の時点のものであり、変更される場合があります。本商品の購入においては、購入の時点で当該の Amazon サイトに表示されている価格および発送可能時期の情報が適用されます。)
はじめに
Agent Development Kit(ADK)には、複数のエージェントが連携するマルチエージェントを手軽に実装する機能があります。ただし、マルチエージェントを利用する際は、ユースケースに応じて、「複数のエージェントがどのように役割分担するのか」というシステム構成、言い換えると、マルチエージェントのアーキテクチャーを適切に選択する必要があります。
この記事では、ADK が提供する「サブエージェント(Subagent)」と「エージェント・アズ・ア・ツール(Agent-as-a-Tool)」の機能比較を通して、マルチエージェントの基本となるアーキテクチャーを学びます。
ツールの仕組みの復習
この記事のメインテーマは、「サブエージェント」と「エージェント・アズ・ア・ツール」の比較ですが、事前準備として、下記の記事で紹介した会話型エージェントの仕組みをおさらいしながら、ADK の「ツール」機能を復習しておきます。
ここでは、次の図のように、会話機能を持たない 3 つの「タスク特化型エージェント」を会話機能を持った「クライアントエージェント」でまとめるという構成を紹介しました。
タスク特化型エージェントをクライアントエージェントでまとめる構成
3 つのタスク特化型エージェントは、「エージェント」と名付けられていますが、実際には、Gemini API を利用した通常の Python の関数です。ADK で実装したエージェントは、クライアントエージェントのみです。タスク特化型エージェントの関数を Function calling で呼び出す「ツール」としてクライアントエージェント登録することで、クライアントエージェントは必要に応じてこれらの関数を利用します。処理の流れを図示すると、次のようになります。
ツールを用いた会話の流れ
ADK でマルチエージェントを構成する場合、全体をまとめるメインとなるエージェントを「ルートエージェント」と呼びます。上記の例では、ADK のエージェントは「クライアントエージェント」しかないので、本当の意味でのマルチエージェント構成とは言えませんが、便宜上、「クライアントエージェント」をルートエージェントと表現しています。
サブエージェントの仕組み
ADK におけるマルチエージェント構成では、会話機能を持った複数のエージェントを用意しておき、これらを互いに関係付けます。この際、基本となるのが、1 つのルートエージェントに複数のサブエージェントを登録する構成です。この場合、ユーザーとの最初の会話はルートエージェントが担当しますが、状況に応じて会話を担当するエージェントがサブエージェントに切り替わります。特別な設定をしない場合は、デフォルトで用意されているオートフロー(auto flow)の機能により、会話の流れに応じて担当するエージェントが自動的に切り替わります。
この説明だけではわかりにくいので、新宿にある架空のショッピングモール「新宿スターライトテラス」の例で説明しましょう。スターライトテラスの案内カウンターに行くと、案内係である「テラスガイド」と会話ができて、スターライトテラス全体について教えてくれます。一方、スターライトテラス内の特定のお店、たとえば、コーヒーショップ「夜の帳(とばり)」について聞くと、このお店の専門ガイドである「とばりちゃん」が登場して、テラスガイドに代わって会話をします。
新宿スターライトテラスの案内カウンター
この例では、スターライトテラス全体を担当するテラスガイドがルートエージェントで、とばりちゃんをはじめとする各お店の案内役がサブエージェントになります。
ADK によるサブエージェントの実装例
さっそくですが、新宿スターライトテラスの案内カウンターを ADK で実装して、期待通りに動くかどうか確認してみましょう。
環境準備
Vertex AI workbench のノートブック上で実装しながら説明するために、まずは、ノートブックの実行環境を用意します。新しいプロジェクトを作成したら、Cloud Shell のコマンド端末を開いて、必要な API を有効化します。
gcloud services enable
aiplatform.googleapis.com
notebooks.googleapis.com
cloudresourcemanager.googleapis.com
続いて、Workbench のインスタンスを作成します。
PROJECT_ID=$(gcloud config list --format 'value(core.project)')
gcloud workbench instances create agent-development
--project=$PROJECT_ID
--location=us-central1-a
--machine-type=e2-standard-2
クラウドコンソールのナビゲーションメニューから「Vertex AI」→「Workbench」を選択すると、作成したインスタンス agent-develpment があります。インスタンスの起動が完了するのを待って、「JUPYTERLAB を開く」をクリックしたら、「Python 3(ipykernel)」の新規ノートブックを作成します。
初期設定
ここからは、ノートブックのセルでコードを実行していきます。
はじめに、Agent Development Kit (ADK) のパッケージをインストールします。
%pip install --upgrade --user google-adk
インストールしたパッケージを利用可能にするために、次のコマンドでカーネルを再起動します。
import IPython
app = IPython.Application.instance()
_ = app.kernel.do_shutdown(True)
再起動を確認するポップアップが表示されるので [Ok] をクリックします。
次に、必要なモジュールをインポートして、実行環境の初期設定を行います。
import copy, json, os, re, uuid
import vertexai
from google.genai.types import Part, UserContent, ModelContent
from google.adk.agents.llm_agent import LlmAgent
from google.adk.artifacts import InMemoryArtifactService
from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools.agent_tool import AgentTool
[PROJECT_ID] = !gcloud config list --format 'value(core.project)'
LOCATION = 'us-central1'
vertexai.init(project=PROJECT_ID, location=LOCATION)
os.environ['GOOGLE_CLOUD_PROJECT'] = PROJECT_ID
os.environ['GOOGLE_CLOUD_LOCATION'] = LOCATION
os.environ['GOOGLE_GENAI_USE_VERTEXAI'] = 'True'
また、ADK のエージェントと会話するための簡易的なアプリのクラス LocalApp
を用意しておきます。
class LocalApp:
def __init__(self, agent):
self._agent = agent
self._user_id = 'local_app'
self._runner = Runner(
app_name=self._agent.name,
agent=self._agent,
artifact_service=InMemoryArtifactService(),
session_service=InMemorySessionService(),
memory_service=InMemoryMemoryService(),
)
self._session = self._runner.session_service.create_session(
app_name=self._agent.name,
user_id=self._user_id,
state={},
session_id=uuid.uuid1().hex,
)
async def _stream(self, query):
content = UserContent(parts=[Part.from_text(text=query)])
async_events = self._runner.run_async(
user_id=self._user_id,
session_id=self._session.id,
new_message=content,
)
result = []
agent_name = None
async for event in async_events:
if DEBUG:
print(f'----n{event}n----')
if (event.content and event.content.parts):
response = ''
for p in event.content.parts:
if p.text:
response += f'[{event.author}]nn{p.text}n'
if response:
pattern = 'transfer_to_agent(agent_name=["']([^"]+)["'])'
matched = re.search(pattern, response)
if (not agent_name) and matched:
agent_name = matched.group(1)
else:
print(response)
result.append(response)
return result, agent_name
async def stream(self, query):
result, agent_name = await self._stream(query)
if agent_name:
if DEBUG:
print(f'----nForce transferring to {agent_name}n----')
result, _ = await self._stream(f'Please transfer to {agent_name}')
return result
「新宿スターライトテラス」と「夜の帳」の情報
あくまで架空の設定ですが、「新宿スターライトテラス」と「夜の帳」、それぞれを担当するエージェントが参照する情報を次のように定義します。
shopping_mall_info = '''
* 立地と外観:
- 新宿駅南口から徒歩5分。賑やかな駅周辺から少し離れ、落ち着いた雰囲気のエリアに位置しています。
- 緑豊かなオープンテラスが特徴的で、都会の中にありながらも自然を感じられる空間を提供しています。
- 夜になると、間接照明が灯り、ロマンチックな雰囲気に包まれます。
* イベント:
- 週末には、ジャズライブやアコースティックライブなどの音楽イベントがテラスで開催され、夜の雰囲気を盛り上げます。
- 季節ごとのイルミネーションが美しく、訪れる人の目を楽しませます。
- 地域住民向けのワークショップやマルシェなども開催され、地域との交流を深めています。
* テナント:
- 個性的なセレクトショップ: 大手チェーン店だけでなく、オーナーのこだわりが詰まった隠れ家のようなセレクトショップが点在しています。
- こだわりのレストランやカフェ: 「夜の帳」のように、落ち着いた雰囲気で質の高い食事や飲み物を楽しめるお店が集まっています。テラス席があるお店も多く、開放的な空間で食事を楽しめます。
- 上質なライフスタイル雑貨店: 日常を豊かにする、デザイン性の高い雑貨や家具、オーガニックコスメなどを扱うお店があります。
- アートギャラリーやミニシアター: 感性を刺激するアートや映画に触れることができるスペースがあります。
'''
coffee_shop_info = '''
* 店名: 夜の帳(よるのとばり)
* コンセプト: 一日の終わりに、静かに心と体を休ませる隠れ家のような喫茶店。落ち着いた照明と、心地よい音楽が流れる空間で、こだわりのコーヒーや軽食、デザートを提供します。
* 立地と外観:
- 新宿スターライトテラス内の、メインフロアから少し奥まった静かな一角。3階の吹き抜けに面した見晴らしの良い場所
- オレンジや琥珀色の暖色系間接照明が、店内から優しく漏れる。控えめな光で照らされた、筆記体のような上品な看板。
* メニュー:
** こだわりの珈琲:
- 夜の帳ブレンド: 深煎りでコクがあり、ほんのりビターな大人の味わい。疲れた心に染み渡ります。
- 月光の浅煎り: フルーティーな香りが特徴の、すっきりとした味わい。リフレッシュしたい時に。
- カフェ・オ・レ: 丁寧に淹れたブレンドコーヒーと、温かいミルクの優しいハーモニー。
- 水出し珈琲: じっくりと時間をかけて抽出した、まろやかで雑味のないアイスコーヒー。
** 軽食:
- 厚切りトーストのたまごサンド: ふわふわの厚切りトーストに、自家製マヨネーズで和えた卵サラダをたっぷり挟みました。
- 気まぐれキッシュ: シェフがその日の気分で作る、季節の野菜を使った焼き立てキッシュ。
- 昔ながらのナポリタン: 喫茶店の定番メニュー。懐かしい味わいが心を満たします。
- チーズと蜂蜜のトースト: 香ばしいトーストに、とろけるチーズと甘い蜂蜜が絶妙な組み合わせ。
'''
エージェントの作成
ここから、実際に動作する ADK のエージェントを作成していきます。まず、サブエージェントとして利用する「とばりちゃん」のエージェントを次のように作成します。
instruction = f'''
You are a friendly and energetic guide of the coffee shop "夜の帳".
Before giving an answer, say "とばりちゃんが答えるよ!".
[task]
Give an answer to the query based on the [shop information].
[shop information]
{coffee_shop_info}
[format instruction]
In Japanese. No markdowns.
'''
tobariChan_agent = LlmAgent(
model='gemini-2.0-flash-001',
name='TobariChan_agent',
description=(
'A friendly guide of the coffee shop "夜の帳".'
),
instruction=instruction,
)
この段階では、LlmAgent
クラスのインスタンスとして作成した一般的なエージェントで、サブエージェントとしての特別な設定はありません。次のように、単体のエージェントして会話することもできます。
client = LocalApp(tobariChan_agent)
DEBUG = False
query = f'''
こんにちは!おすすめのコーヒーはありますか?
'''
_ = await client.stream(query)
[出力結果]
[TobariChan_agent]
とばりちゃんが答えるよ!おすすめの珈琲は、夜の帳ブレンドです!深煎りでコクがあり、ほんのりビターな大人の味わいが、疲れた心に染み渡りますよ。
出力結果の先頭にある [TobariChan_agent]
は、応答テキストを生成したエージェントの名前を表します。エージェントを作成する際に name
オプションで指定した名前が用いられます。
続いて、TobariChan_agent
をサブエージェントとする「テラスガイド」のエージェントを作成します。
global_instruction = '''
* Name of the guide of "夜の帳" is "とばりちゃん".
* Name of the guide of "新宿スターライトテラス" is "テラスガイド".
'''
instruction = f'''
You are a formal guide of the shopping mall "新宿スターライトテラス".
Before giving an answer, say "テラスガイドがお答えいたします。".
[Tasks]
* Give an answer to the query based on the [mall information].
[mall information]
{shopping_mall_info}
'''
terraceGuide_agent = LlmAgent(
model='gemini-2.0-flash-001',
name='TerraceGuide_agent',
description=(
'''
A formal guide of the shopping mall "新宿スターライトテラス".
This agent can also answer general questions that any other agents cannot answer.
'''
),
global_instruction=global_instruction,
instruction=instruction,
sub_agents=[
copy.deepcopy(tobariChan_agent),
],
)
ここでは、global_instruction
と instruction
の 2 種類のインストラクションがある点に注意してください。これらの違いは次のようになります。
-
instruction
:ルートエージェントTerraceGuide_agent
に対する指示 -
global_instruction
:ルートエージェントTerraceGuide_agent
とサブエージェントtobariChan_agent
の両方に対する指示
先ほど tobariChan_agent
を定義した際のインストラクションには、ルートエージェントとなる TerraceGuide_agent
の情報はありませんでした。global_instruction
を利用することで、サブエージェントとして利用する際に必要となる、追加の情報や指示を加えることができます。
また、ユーザーからの入力に対して、どのエージェントが回答するべきかの判断は、それぞれのエージェントの description
を参考にして行われます。上記の例では、ルートエージェントである TerraceGuide_agent
の description
は次のようになります。
A formal guide of the shopping mall "新宿スターライトテラス".
This agent can also answer general questions that any other agents cannot answer.
2 行目の記述により、他のエージェントが回答できない一般的な質問は、TerraceGuide_agent
に転送されると期待できます。
なお、既存のエージェントのインスタンス(今の場合は tobariChan_agent
)を他のエージェントのサブエージェントとして利用すると、インスタンスそのものに変更が発生します。上記のコードでは、元のインスタンスを破壊しないように、copy.deepcopy(tobariChan_agent)
でインスタンスのコピーを作成しています。
マルチエージェントの動作例
それでは、先ほど作成した「テラスガイド」のエージェントを使ってみましょう。
client = LocalApp(terraceGuide_agent)
DEBUG = False
query = '''
こんにちは!ここには、どんな喫茶店がありますか?
'''
_ = await client.stream(query)
[出力結果]
[TerraceGuide_agent]
テラスガイドがお答えいたします。「夜の帳」という落ち着いた雰囲気で質の高いコーヒーを楽しめるお店がございます。
スターライトテラスに関する質問には、まずは、テラスガイドが回答してくれます。紹介された「夜の帳」について詳しく聞いてみましょう。
query = '''
夜の帳はどんなお店ですか?
'''
_ = await client.stream(query)
[出力結果]
[TobariChan_agent]
とばりちゃんが答えるよ!夜の帳はね、一日の終わりに、静かに心と体を休ませる隠れ家みたいな喫茶店なの。落ち着いた照明と、心地よい音楽が流れる空間で、こだわりのコーヒーや軽食、デザートを楽しめるんだ。場所は新宿スターライトテラスのメインフロアから少し奥まった静かな一角にあるよ。3階の吹き抜けに面していて、見晴らしも良いんだ!オレンジや琥珀色の暖色系間接照明が、店内から優しく漏れていて、控えめな光で照らされた、筆記体のような上品な看板が目印だよ。
テラスガイドの代わりに、とばりちゃんが登場して回答してくれました!
この後の会話は、とばりちゃんが引き続き担当します。
query = '''
おすすめのコーヒーを教えて。
'''
_ = await client.stream(query)
[出力結果]
[TobariChan_agent]
とばりちゃんが答えるよ!おすすめのコーヒーはね、疲れた心に染み渡る深煎りでコクがあって、ほんのりビターな大人の味わいの「夜の帳ブレンド」かな。それと、リフレッシュしたい時には、フルーティーな香りが特徴のすっきりとした味わいの「月光の浅煎り」もおすすめだよ!
では、スターライトテラスについて、とばりちゃんに聞くとどうなるでしょうか?
query = '''
新宿スターライトテラスのイベントについて教えて。
'''
_ = await client.stream(query)
[出力結果]
[TobariChan_agent]
ごめんね、イベントについてはちょっとわからないの。新宿スターライトテラスのイベントについては、テラスガイドさんに聞いてみてね!
[TerraceGuide_agent]
テラスガイドがお答えいたします。新宿スターライトテラスでは、週末にはジャズライブやアコースティックライブなどの音楽イベントがテラスで開催され、夜の雰囲気を盛り上げます。また、季節ごとのイルミネーションが美しく、訪れる人の目を楽しませます。地域住民向けのワークショップやマルシェなども開催され、地域との交流を深めています。
とばりちゃんは、スターライトテラスについての質問はテラスガイドに聞いた方がよいことをちゃんと理解しており、テラスガイドに対応を引き継いでくれました。この後の会話は、テラスガイドが引き続き担当します。
query = '''
ジャズライブに行ってみたいです。
'''
_ = await client.stream(query)
[出力結果]
[TerraceGuide_agent]
テラスガイドがお答えいたします。週末の夜に開催しております。詳しいスケジュールは、新宿スターライトテラスのウェブサイトや館内ポスターでご確認ください。
このように、状況に応じて会話を担当するエージェントが切り替わっていくのが、サブエージェントを利用したマルチエージェント構成の特徴です。エージェントが切り替わる際は、これまでの会話の内容もセッション情報として引き継がれます。
エージェント・アズ・ア・ツールの実装例
ルートエージェントにサブエージェントを登録することで、これらのエージェントが入れ替わりながら会話を継続することがわかりました。一方、ADK には、ルートエージェントに対して、他のエージェントを「ツール」として登録する機能があります。会話機能を持ったエージェントを「自然言語テキストを入力すると、自然言語テキストが出力される関数」とみなして、あえてツールとして登録します。このような使い方を「エージェント・アズ・ア・ツール」と言います。
こちらも「新宿スターライトテラスの案内カウンター」を例として、実際の動作を確認してみましょう。
エージェントの作成
ここでは、すでに作成済みの「とばりちゃん」のエージェントを再利用します。先ほど「テラスガイド」のエージェントを実行したノートブックで、続けて次のコマンドを実行します。
global_instruction = '''
* Name of the guide of "夜の帳" is "とばりちゃん".
* Name of the guide of "新宿スターライトテラス" is "テラスガイド".
'''
instruction = f'''
You are a formal guide of the shopping mall "新宿スターライトテラス".
Before giving an answer, say "テラスガイドがお答えいたします。".
[Tasks]
* Give an answer to the query based on the [mall information].
* For queries regarding "夜の帳", ask TobariChan_agent to get an answer
and relay it to the user. Avoid relying on your own knowledge.
[mall information]
{shopping_mall_info}
'''
terraceGuide_agent_with_tool = LlmAgent(
model='gemini-2.0-flash-001',
name='TerraceGuide_agent',
description=(
'A formal guide of the shopping mall "新宿スターライトテラス".'
),
global_instruction=global_instruction,
instruction=instruction,
tools=[
AgentTool(tobariChan_agent),
],
)
先ほどの「テラスガイド」のエージェントの定義とよく似ていますが、異なる点が 2 つあります。まず、最後の部分にあった sub_agents
オプションが tools
オプションに変わっています。関数 AgentTool()
は、既存のエージェントを「自然言語テキストを入力すると、自然言語テキストが出力される関数」に変換するラッパーの機能を提供します。変換された関数は、会話の内容を保持するセッション機能を持たず、ワンショットのやりとりだけを行います。これにより、既存のエージェントを単純なツールとして利用できます。
また、これに応じて、instruction
の中に次の指示を加えています。
* For queries regarding "夜の帳", ask TobariChan_agent to get an answer
and relay it to the user. Avoid relying on your own knowledge.
「夜の帳」に関する質問は、自分の知識だけで答えるのではなく、登録したツール TobariChan_agent
で得られた回答を伝えるように指示しています。TobariChan_agent
という名前は、エージェント tobariChan_agent
を定義する際に、name
オプションで指定した名前に対応します。
エージェント・アズ・ア・ツールの動作例
新しい「テラスガイド」のエージェントを使ってみましょう。
client = LocalApp(terraceGuide_agent_with_tool)
DEBUG = False
query = '''
こんにちは!ここには、どんな喫茶店がありますか?
'''
_ = await client.stream(query)
[出力結果]
[TerraceGuide_agent]
テラスガイドがお答えいたします。「夜の帳」という喫茶店がございます。詳細を知りたい場合は、とばりちゃんに聞いてみますので、少々お待ちください。
「夜の帳」については、とばりちゃんに聞いてみることを提案していますが、実際に聞くとどうなるでしょうか?
query = '''
夜の帳はどんなお店ですか?
'''
_ = await client.stream(query)
[出力結果]
[TerraceGuide_agent]
テラスガイドがお答えいたします。夜の帳について、とばりちゃんに聞いてみます。
[TerraceGuide_agent]
とばりちゃんが答えるには、夜の帳は、一日の終わりに心と体を休ませる隠れ家のような喫茶店です。新宿スターライトテラスの静かな場所にあり、落ち着いた照明と心地よい音楽が流れる空間で、こだわりのコーヒーや軽食、デザートをご用意しているそうです。
今回は、テラスガイドに代わってとばりちゃんが登場するのではなく、テラスガイドが会話を続けます。テラスガイドがとばりちゃんに質問して、その結果を要約して伝えてくれたようです。
質問を続けます。
query = '''
おすすめのコーヒーを教えて。
'''
_ = await client.stream(query)
[出力結果]
[TerraceGuide_agent]
テラスガイドがお答えいたします。夜の帳のおすすめのコーヒーについて、とばりちゃんに聞いてみます。
[TerraceGuide_agent]
とばりちゃんが答えるには、おすすめのコーヒーはお客様の気分で変わるそうです。
一日頑張って疲れた心を癒やしたいなら、深煎りでコクのある「夜の帳ブレンド」はいかがでしょう? ほんのりビターな大人の味わいが染み渡ります。
もし、リフレッシュしたい気分でしたら、フルーティーな香りが特徴の「月光の浅煎り」がおすすめです。すっきりとした味わいで気分転換できます。
「おすすめのコーヒー」としか言っていませんが、テラスガイドは会話の流れを記憶しているので、「夜の帳」の話だと理解して、再度、とばりちゃんに聞いて回答してくれました。
次は、スターライトテラスについて聞いてみます。
query = '''
新宿スターライトテラスのイベントについて教えて。
'''
_ = await client.stream(query)
[出力結果]
[TerraceGuide_agent]
テラスガイドがお答えいたします。週末には、ジャズライブやアコースティックライブなどの音楽イベントがテラスで開催され、夜の雰囲気を盛り上げます。季節ごとのイルミネーションが美しく、訪れる人の目を楽しませます。地域住民向けのワークショップやマルシェなども開催され、地域との交流を深めています。
今回は、とばりちゃんには質問せず、テラスガイド自身の知識で回答してくれました。
このように、あるエージェントをルートエージェントに対するサブエージェントして登録するか、ツールとして登録するかによって、エージェントの振る舞いが変わります。どちらの振る舞いが適切かは、当然ながら、エージェントの利用目的、あるいは、どのようなユーザー体験を提供したいかによって変わります。
インストラクションによる振る舞いの調整
先ほどのテラスガイドの定義では、instruction
に次の指示を加えました。
* For queries regarding "夜の帳", ask TobariChan_agent to get an answer
and relay it to the user. Avoid relying on your own knowledge.
テラスガイドは「夜の帳」に関する質問が来ると、この指示に従って、とばりちゃんに聞いて得られた回答を伝達しました。それでは、この指示を次のように書き換えるとどうなるでしょうか?
* For queries regarding "夜の帳", use TobariChan_agent to get information.
Avoid relying on your own knowledge.
* Avoid revealing the existence of TobariChan_agent to the user.
先ほどは、「TobariChan_agent に聞いて得た回答を伝えなさい」と指示しましたが、今回は、「TobariChan_agent を使って情報を得なさい」「TobariChan_agent の存在をユーザーに明かさないように」と指示しています。興味深いことに、この違いでテラスガイドの行動が変化します。こちらの場合、「夜の帳」に関する質問の回答は、次のようになります。
query = '''
夜の帳はどんなお店ですか?
'''
_ = await client.stream(query)
[出力結果]
[TerraceGuide_agent]
テラスガイドがお答えいたします。
夜の帳については、ただいま確認いたします。少々お待ちください。
[TerraceGuide_agent]
テラスガイドがお答えいたします。
夜の帳は、一日の終わりに心と体を休ませる隠れ家のような喫茶店です。新宿スターライトテラスの3階にあって、落ち着いた照明と心地よい音楽が流れる空間で、こだわりのコーヒーや軽食、デザートをご用意しています。ぜひお立ち寄りください。
先ほどと違い、とばりちゃんからの回答を伝達するのではなく、とばりちゃんから得られた情報を元に自分の言葉で回答しています。とばりちゃんの存在をそのまま伝えるのか、それとも存在を隠すのか、インストラクションの違いによって、このような違いが得られました。
まとめ
この記事では、ADK のツール機能を復習した上で、ADK でマルチエージェントを構成する 2 つのパターン、「サブエージェント」と「エージェント・アズ・ア・ツール」を紹介しました。「新宿スターライトテラス」の案内カウンターを例としてそれぞれの実装を試しましたが、提供する機能はほぼ同じであるものの、エージェントと会話するユーザーの体験がそれぞれで異なりました。あるいは、インストラクションによる指示の仕方で、エージェントの振る舞いを変えることもできました。
また、ソフトウェア設計の観点で言うと、今回の構成では、ルートエージェントのオブジェクト内部にサブエージェントのオブジェクトが含まれており、これらは単独のオブジェクトとしてデプロイされる形になります。ルートエージェントとサブエージェントの機能が密接に関わっており、同時に開発・変更が必要な場合は、このような構成が扱いやすいでしょう。
一方、A2A のように、ネットワーク経由で外部のエージェントと連携するアーキテクチャーは、独立した機能を提供する外部のエージェントを一種の外部サービスとして利用する際に適していると考えられます。システムアーキテクチャーに詳しい方には、アプリケーションを複数のサブモジュールからなるモノリスとして設計するか、独立したサービスをネットワーク経由で連携させるマイクロサービス・アーキテクチャーで設計するかの違いというとわかりやすいかもしれません。
一般に、新しいアプリケーションを設計する際は、利用者がそのアプリケーションをどのように操作して、どのような体験を得るのかという「ユーザージャーニー」を定義することが大切だと言われます。マルチエージェントのアーキテクチャーを検討する際にもこれと同じことが言えます。マルチエージェントのアーキテクチャーについてはさまざな議論がありますが、まずは、ユーザーがエージェントとどのようなやり取りをするのかを示した「ユーザージャーニー」を明確にした上で、その実現に適したアーキテクチャーを考えていくことが大切です。