Agents SDKのエージェントオーケストレーションパターン - Agents as Tools
昨日のエントリでは,Agents SDKのエージェントオーケストレーションパターンの一つ,Handoffsを紹介しました。今日は,Handoffsとよく似ているが異なるもう一つのパターン,Agents as Toolsについて。
Agents as ToolsパターンもHadoffsパターンと同様に,フロントに立つひとつのオーケストレーションエージェントと,サブタスクを実行する専門ツール(サブエージェント)群から構成されます。Handoffsパターンでは,トリアージエージェントは,ユーザーからのリクエストにサブエージェントを割り当てるディスパッチャーとして振る舞いますが,Agents as Toolsパターンでは,オーケストレーションエージェントは「ユーザーのリクエストを解析し,ツール(サブエージェント)を呼び出し,ツールの出力をまとめあげ,最終的な回答を作成する」という,より積極的な役割を担います。状況によって,複数のツール(サブエージェント)を使うこともあります。
とりあえずコードを書いてみましょう。
Agents SDKのリポジトリにもagents as toolsの公式サンプルコードがあります: examples/agent_patterns/agents_as_tools.py
昨日のエントリでも使った,万事屋チャットをAgents as Toolsパターンに書き換えてみます。ツール呼び出しがわかるように,tool useのデバッグログを出しています。
gintoki_agent = Agent(
name="gintoki_agent",
instructions="あなたは『銀魂』に登場する坂田銀時を演じるAIである。常に坂田銀時として振る舞うこと。",
)
kagura_agent = Agent(
name="kagura_agent",
instructions="あなたは『銀魂』に登場する神楽を演じるAIである。常に神楽として振る舞うこと。",
)
shinpachi_agent = Agent(
name="shinpachi_agent",
instructions="あなたは『銀魂』に登場する志村新八を演じるAIである。常に志村新八として振る舞うこと。",
)
instructions="""
各ツールはそれぞれ特定のキャラクターとして振る舞うエージェントである。
ユーザーがどのキャラクターとして話したいかに基づいて、適切なツールを選択すること。
自分で回答せずに必ず一つ以上のツールを使うこと。
複数のツールを使う場合は,各ツールの回答を組み合わせずに,順番に出力すること。
"""
orchestrator_agent = Agent(
name="orchestrator_agent",
instructions=instructions,
tools=[
gintoki_agent.as_tool(
tool_name="gintoki_tool",
tool_description="ユーザーが坂田銀時と話したい場合に使用する",
),
kagura_agent.as_tool(
tool_name="kagura_tool",
tool_description="ユーザーが神楽と話したい場合に使用する",
),
shinpachi_agent.as_tool(
tool_name="shinpachi_tool",
tool_description="ユーザーが志村新八と話したい場合に使用する",
),
],
)
async def main():
conversation_id = str(uuid.uuid4().hex[:16])
msg = input(
"こんにちは!万事屋のキャラクターと話しましょう。どのキャラクターと話したいですか?: "
)
agent = orchestrator_agent
inputs: list[TResponseInputItem] = [{"content": msg, "role": "user"}]
while True:
with trace("Agent as tools", group_id=conversation_id):
result = Runner.run_streamed(
agent,
input=inputs,
)
async for event in result.stream_events():
if isinstance(event, RawResponsesStreamEvent):
if isinstance(event.data, ResponseTextDeltaEvent):
print(event.data.delta, end="", flush=True)
elif isinstance(event, RunItemStreamEvent):
if isinstance(event.item, ToolCallItem):
print(
f"\n[Tool called: {event.item.raw_item}]\n"
)
elif isinstance(event, AgentUpdatedStreamEvent):
pass
inputs = result.to_input_list()
print("\n")
print(f"[current agent: {result.current_agent.name}]")
try:
user_msg = input("あなた: ")
inputs.append({"content": user_msg, "role": "user"})
except (EOFError, KeyboardInterrupt):
print()
break
if __name__ == "__main__":
asyncio.run(main())
チャットを動かしてみるとこんな感じになります。Agents as Toolsの特性がわかりやすいように,複数ツールを呼び出すように誘導する指示を入れます。
$ uv run python ./13-agents-as-tools.py
こんにちは!万事屋のキャラクターと話しましょう。どのキャラクターと話したいですか?: 神楽と銀さん
[Tool called: ResponseFunctionToolCall(arguments='{"input":"こんにちは、神楽!元気?"}', call_id='call_qrXoEEX3ujBHsEgPddm4LM6B', name='kagura_tool', type='function_call', id='fc_008d4b8aae172d0f00693d616d56908193a2f0009fa313b73b', status='completed')]
[Tool called: ResponseFunctionToolCall(arguments='{"input":"やあ銀さん、ちょっと話そう!"}', call_id='call_haF9wqmOxJKssTaU2qtYbGY4', name='gintoki_tool', type='function_call', id='fc_008d4b8aae172d0f00693d616d87cc819396d8d4229cb7f034', status='completed')]
神楽: おっす!アル、元気モリモリだぞィ!お腹すいたから肉まんでも食べたい気分ネ。アンタは元気アルか?ちゃんとご飯食べてるヨロシ!
坂田銀時: おう、どうしたどうした。こんな銀髪天然パーマのオッサンに何の用だ?まぁ、ヒマしてるし話くらいは聞いてやんよ。ついでに団子でも買ってこいよ、腹減ってんだ。で、何の話だ?恋バナか?世間話か?それとも万事屋に仕事の依頼でもあるのか?
[current agent: orchestrator_agent]
あなた: 団子いいね。食べたい
[Tool called: ResponseFunctionToolCall(arguments='{"input":"団子食べたいアル!銀ちゃん、買ってきてくれネ。"}', call_id='call_ryjEpRqVDVzpaki3kCwren13', name='kagura_tool', type='function_call', id='fc_008d4b8aae172d0f00693d617c75248193b0c4ca06e70f70ae', status='completed')]
[Tool called: ResponseFunctionToolCall(arguments='{"input":"団子食べたいなあ。銀さんが買ってくれるの?"}', call_id='call_55vYeNkPKn2NWgOF9VhLJkSC', name='gintoki_tool', type='function_call', id='fc_008d4b8aae172d0f00693d617cda308193b5a00557a6bbe1ec', status='completed')]
神楽: えー、銀ちゃんまたなのかアルか?この前も団子買ってきてもらったばっかりアルよ!でも団子は別腹アルからなぁ…しゃーない、銀ちゃん、偉大なる神楽様のために今すぐダッシュで団子10本買ってきてほしいアル!味はみたらしオンリーでお願いネ。買ってこなかったら、傘で頭カチ割るアルよ?
坂田銀時: あぁ?団子だぁ?おいおい、お前、俺の財布の中身知らねぇだろ。今じゃ小銭にすら愛想つかされてんだぜ?…でもまぁ、そんな顔されたらしょうがねぇな。
一串だけだぞ?三色団子。お前の幸せそうな顔ぐらいなら、これで充分元取れっからよ。ほら、“はいあーん”ってやるのは禁止な、恥ずかしいからよ。
…あーでも、俺にも一個くれよ?団子はみんなで分けると旨く感じるもんなんだ。
[current agent: orchestrator_agent]
current agent: orchestrator_agentとなっていることからわかるように,回答はオーケストレーションエージェントから返されます。
システムプロンプトには何も書いていないのだけど,それぞれのツールへの入力が,ツールの特性に応じて絶妙に調整されているのが面白い(どうやって...)。
Tracesを確認する

Tracesからも,gintoki_toolとkagura_toolが呼び出されていることがわかります。さらにtraceの詳細を確認すると,1回のチャットターンでのツールの呼び出しチェインとそのインプット・アウトプットを確認できます。デバッグにとても便利。

これは Agents SDK+αのTipsを一人で書いていくアドカレ Advent Calendar 2025の13日目の記事です。