STUDY NOTES
第23回: マルチエージェントの2大パターン — Supervisor と Swarm を langgraph-supervisor / langgraph-swarm で比較¶
第9回(Research Agent)や第18回(データ分析エージェント)でも「複数エージェントの協調」は扱ってきたが、それらは自前で StateGraph を組んで分岐を書くスタイルだった。第23回は LangChain 公式が 2025 年に提供開始した 2 つのプリビルトパッケージを紹介する回:
| パッケージ | パターン | 構造 | 比喩 |
|---|---|---|---|
langgraph-supervisor |
Supervisor(階層型) | 1 名の supervisor LLM が specialists 群を指示・統括 | 工事現場の現場監督 + 職人。意思決定は中央集権 |
langgraph-swarm |
Swarm(フラット型) | エージェント同士が handoff_tool で直接バトンを渡し合う |
コールセンターのオペレータ間で「これは技術担当に回しますね」と相互転送 |
同じ「マルチエージェント協調」でも、意思決定が中央にあるか分散しているかで構造がまるで違う。本回はそれぞれの最小サンプル(2エージェント構成)で書き味の違いを体感する。
2 つのパターンの判断軸: - タスクの分解が事前に決められる(情報収集 → 計算など、ステップが固定)→ Supervisor - タスクの遷移が動的(FAQ→技術サポート→FAQ と行ったり来たり)→ Swarm - 単純に少数(2–3)の専門家なら Swarm のほうが軽い。多数(5+)の specialists + ルーティングなら Supervisor のほうが拡張しやすい
全体像¶
23/
├── main.py ← Supervisor / Swarm 両方を CLI で走らせる検証スクリプト
├── langgraph.json ← Studio が "supervisor" と "swarm" 2 グラフを発見
└── src/sd_23/
├── supervisor_graph.py ← create_supervisor() でラップ
├── swarm_graph.py ← create_swarm() でラップ
└── agents/
├── math_agent.py ← create_react_agent (add/multiply/divide)
├── research_agent.py ← create_react_agent (web_search/wikipedia, mock)
├── faq_agent.py ← create_react_agent + handoff_tool→tech_support
└── tech_agent.py ← create_react_agent + handoff_tool→faq_support
Supervisor パターンの実行フロー¶
flowchart TD
User([ユーザ質問]) --> Sup[supervisor LLM]
Sup -->|tool_call: transfer_to_research_expert| Research[research_expert<br/>create_react_agent<br/>tools: web_search, wikipedia]
Research -->|結果| Sup
Sup -->|tool_call: transfer_to_math_expert| Math[math_expert<br/>create_react_agent<br/>tools: add, multiply, divide]
Math -->|結果| Sup
Sup -->|最終回答| User
style Sup fill:#FFF2E8,stroke:#ED7100
style Research fill:#E1F5FE,stroke:#0288D1
style Math fill:#E1F5FE,stroke:#0288D1
ポイント: 常に supervisor が中央。research_expert と math_expert は直接通信せず、必ず supervisor を経由する。
Swarm パターンの実行フロー¶
flowchart TD
User([ユーザ質問]) --> FAQ[faq_support<br/>tools: check_faq_database<br/>+ handoff→tech_support]
FAQ -->|tool_call: transfer_to_tech_support| Tech[tech_support<br/>tools: check_system_status, read_manual<br/>+ handoff→faq_support]
Tech -->|tool_call: transfer_to_faq_support| FAQ
FAQ -->|最終回答| User
Tech -->|最終回答| User
style FAQ fill:#E8F5E9,stroke:#3F8624
style Tech fill:#FCE4EC,stroke:#E7157B
ポイント: supervisor が存在しない。default_active_agent="faq_support" で起点を指定するだけで、後は agents が自分で「次は誰に渡すか」を tool_call で決める。
main.py の検証フロー(両パターン共通)¶
sequenceDiagram
participant CLI as main.py
participant WF as workflow (compiled)
participant CP as InMemorySaver
CLI->>WF: app.astream({messages: [HumanMessage(...)]}, stream_mode="updates", subgraphs=True)
loop chunk ごと
WF-->>CLI: (parent_ns, {node_name: output})
Note over CLI: subgraphs=True なので<br/>サブグラフの内部実行も流れてくる<br/>例: ("research_expert:abc", {agent: {messages: ...}})
CLI->>CLI: _print_node で整形 → 標準出力
end
subgraphs=True を付けると、Supervisor / Swarm の内側で動いている react_agent の中間 step も全部見える。デバッグ時の重要オプション。
使用ライブラリ・原理¶
langgraph_supervisor.create_supervisor(agents, model, prompt, ...)¶
from langgraph_supervisor import create_supervisor
workflow = create_supervisor(
agents=[math_agent, research_agent],
model=supervisor_model,
prompt=supervisor_prompt,
add_handoff_messages=True,
output_mode="full_history",
)
内部でやっていること:
- supervisor ノードを中心に置く
StateGraphを組み立てる - 各 sub-agent を
SendAPI で呼び出すtransfer_to_<agent_name>ツールを自動生成して supervisor に渡す - sub-agent が応答したら supervisor に戻る固定エッジを張る
output_modeで「sub-agent の全メッセージを supervisor の state に追加するか、最終応答だけか」を切り替え
create_react_agentとの対比:create_react_agentは「LLM + tools + while ループ」の単一エージェント。create_supervisorは「supervisor LLM がtransfer_to_Xという tool を介して sub-agent を呼び出す」というメタ構造で、sub-agent 自体も内部でcreate_react_agentだったりする。ReAct パターンの再帰的入れ子だと思うと分かりやすい。
主要パラメータ:
| パラメータ | 役割 |
|---|---|
agents |
sub-agent のリスト。各 agent は name= 属性を持っている必要あり(supervisor が transfer_to_<name> で呼ぶ) |
model |
supervisor が使う LLM |
prompt |
supervisor 用のシステムプロンプト。「どのエージェントが何の専門家か」を明記する責任は呼び出し側にある |
add_handoff_messages |
True にすると state に handoff の旨を示すメッセージが残る |
output_mode |
"full_history" で全 sub-agent メッセージを保持、"last_message" で最終だけ |
langgraph_swarm.create_swarm(agents, default_active_agent)¶
from langgraph_swarm import create_swarm
workflow = create_swarm(
agents=[faq_agent, tech_agent],
default_active_agent="faq_support",
)
内部:
- active_agent という追加 state フィールドを持つ
StateGraphを構築 - 起点として
default_active_agentを設定 - 各 agent が
handoff_toolを呼ぶと active_agent が切り替わり、グラフが別の agent ノードに遷移する - agent が tool_call なしで応答したら終了
langgraph_swarm.create_handoff_tool(agent_name, description)¶
from langgraph_swarm import create_handoff_tool
tech_handoff = create_handoff_tool(
agent_name="tech_support",
description="技術的な問題が発生した場合に技術サポートに転送",
)
tools = [check_faq_database, tech_handoff]
このツールは LLM から見ると「transfer_to_tech_support を呼ぶと制御が tech_support に移る」と振る舞う。中身は「state の active_agent を "tech_support" に変える Command(goto="tech_support") を返す」だけのシンプル実装(第18回でも見た Command パターンの再利用)。
app.astream(..., stream_mode="updates", subgraphs=True)¶
subgraphs=True の効果:
- 通常の
streamは トップレベルグラフのノード遷移のみを流す subgraphs=Trueを付けると 入れ子の sub-graph(=create_react_agent が内部で持つグラフ)の中間遷移も流れる- chunk が
(parent_namespace, outputs)のタプル形式になる(普段は dict だけ) parent_ns[0]で「どのエージェントの中の出来事か」が分かる(例:"research_expert:abc123")
主要 stream_mode 一覧(第22回からの再掲):
| mode | 流れるもの |
|---|---|
"values" |
state スナップショット全体 |
"updates" |
直近 step の差分のみ ← 本サンプルで使用 |
"messages" |
LLM トークン単位 |
"custom" |
StreamWriter ペイロード |
ファイル別の役割¶
| ファイル | 役割 |
|---|---|
main.py |
Supervisor と Swarm の 2 サンプル質問を投げて、astream(subgraphs=True) で全エージェントの中間 step を整形して標準出力 |
src/sd_23/supervisor_graph.py |
create_supervisor の 1 行で組み立て。supervisor 用プロンプトを直書き |
src/sd_23/swarm_graph.py |
create_swarm の 1 行で組み立て。プロンプトすら supervisor 側に持たない(各 agent のプロンプトに分散) |
src/sd_23/agents/math_agent.py |
create_react_agent(tools=[add, multiply, divide], name="math_expert") |
src/sd_23/agents/research_agent.py |
create_react_agent(tools=[web_search (mock), wikipedia (mock)], name="research_expert") |
src/sd_23/agents/faq_agent.py |
FAQ DB tool + create_handoff_tool("tech_support") |
src/sd_23/agents/tech_agent.py |
check_system_status + read_manual + create_handoff_tool("faq_support") |
langgraph.json |
Studio に "supervisor" と "swarm" 2 グラフを公開(同時に並べて比較できる) |
行レベルの工夫(中核ロジックの抜粋)¶
① Supervisor の組み立て — プロンプトに「誰が何の専門家か」を書くだけ (supervisor_graph.py:13-44)¶
def create_supervisor_workflow():
math_agent = create_math_agent() # ①
research_agent = create_research_agent()
supervisor_model = ChatAnthropic(temperature=0, model_name="claude-sonnet-4-20250514")
supervisor_prompt = """あなたはタスクコーディネーターです。
利用可能なエージェント:
- research_expert: 情報収集専門
- math_expert: 計算専門
タスク実行方法:
1. 情報収集が必要な場合、research_expertに委譲
2. 計算が必要な場合、具体的な数値と計算内容をmath_expertに伝える""" # ②
workflow = create_supervisor( # ③
agents=[math_agent, research_agent],
model=supervisor_model,
prompt=supervisor_prompt,
add_handoff_messages=True,
output_mode="full_history",
)
return workflow
| 行 | やってること | なぜそうする |
|---|---|---|
| ① | sub-agent をそれぞれ create_react_agent で組む |
sub-agent 側は普通の ReAct エージェント。supervisor から見ると単なる「name 付きのツール」になる |
| ② | プロンプトに全エージェントの専門分野を箇条書きで書く | supervisor は「自分が呼べるエージェント一覧」をプロンプトとツール定義の両方から知るが、自然言語で説明があったほうがルーティング精度が上がる(古典テク) |
| ③ | create_supervisor の 1 行で完成 |
中で Send API を使った fan-out / fan-in グラフを自動構築。自分で StateGraph + conditional_edges を書くと 50 行は必要 |
② Swarm の組み立て — プロンプトすら supervisor 側にない (swarm_graph.py:12-23)¶
def create_swarm_workflow():
faq_agent = create_faq_agent()
tech_agent = create_tech_agent()
workflow = create_swarm( # ①
agents=[faq_agent, tech_agent],
default_active_agent="faq_support", # ②
)
return workflow
| 行 | やってること | なぜそうする |
|---|---|---|
| ① | create_swarm(agents, default_active_agent) だけ |
Swarm にはコーディネーター LLM が存在しない。prompt 引数すら要らない |
| ② | default_active_agent で起点を指定 |
ユーザ最初の質問はこのエージェントが受ける。以降は agent 内の handoff_tool で動的に遷移 |
③ Handoff tool の宣言 — ツール定義 1 行で agent 間の橋を架ける (faq_agent.py:31-36)¶
def create_faq_agent() -> CompiledGraph:
model = ChatAnthropic(temperature=0, model_name="claude-sonnet-4-20250514")
tech_handoff = create_handoff_tool( # ①
agent_name="tech_support",
description="技術的な問題や詳細なサポートが必要な場合に技術サポートに転送",
)
tools = [check_faq_database, tech_handoff] # ②
prompt = """あなたの名前は「faq_support」です。FAQサポートエージェントとして動作しています。
...
- エラーコードの詳細診断など技術的な質問は、tech_supportへ転送してください""" # ③
agent = create_react_agent(model=model, tools=tools, name="faq_support", prompt=prompt)
return agent
| 行 | やってること | なぜそうする |
|---|---|---|
| ① | create_handoff_tool("tech_support", description=...) で「tech_support へバトンを渡すツール」を生成 |
LLM はこれを transfer_to_tech_support という普通の tool として扱う。実態は Command(goto="tech_support") を返す薄いラッパ |
| ② | 通常 tool と handoff tool を同じ tools リストに並べる | LLM 側はどれが handoff か意識しない(プロンプトで書くだけ)。LangGraph runtime が tool 結果を見て graph 遷移を判断 |
| ③ | プロンプトで「自分の name は X」「Y に送ること」と明記 | LLM が自分の役割と「いつ handoff すべきか」を理解する必要がある。プロンプトが Swarm の振る舞いの 80%を決める |
④ main.py の subgraphs ストリーミング (main.py:46-75)¶
async for chunk in app.astream(*stream_args, stream_mode="updates", subgraphs=True): # ①
if isinstance(chunk, tuple): # ②
parent_ns, outputs = chunk
if parent_ns and len(parent_ns) > 0:
parent_info = parent_ns[0] if isinstance(parent_ns[0], str) else str(parent_ns[0])
parent_name = parent_info.split(":")[0] if ":" in parent_info else parent_info # ③
else:
parent_name = None
for node_name, output in outputs.items():
_print_node(node_name, output, is_swarm, seen_messages, parent_name)
else:
for node_name, output in chunk.items(): # ④
_print_node(node_name, output, is_swarm, seen_messages)
| 行 | やってること | なぜそうする |
|---|---|---|
| ① | subgraphs=True で sub-agent の中間 step も購読 |
これがないと「supervisor → 結果」しか見えず、各 sub-agent のツール呼び出しがブラックボックスになる |
| ② | chunk がタプルなら subgraph 由来 | subgraphs=True の時だけ来るフォーマット |
| ③ | parent_info.split(":")[0] で UUID を剥がしてエージェント名だけ取り出し |
LangGraph は内部で "research_expert:abc-123" のような形で sub-agent をラベル付けする。表示用には UUID 部分が邪魔なので削る |
| ④ | タプルでない場合は通常 chunk(トップレベル node) | subgraphs=True でも親グラフ自身の遷移は dict 形式で来る |
学んだこと(要点)¶
- Supervisor vs Swarm の選び方:
- Supervisor: タスクが事前に分解できる、ルーティングロジックを 1 箇所に集めたい、新規エージェント追加で supervisor プロンプトだけ変えれば済む → 規模拡張に強い
- Swarm: エージェント間の遷移が動的、専門家が対等な関係、プロンプトがエージェントごとに自然に書ける → 小規模で書きやすい
langgraph-supervisor/langgraph-swarmはどちらも公式プリビルト。「マルチエージェント書くなら毎回StateGraphを組む」時代は終わった。自前 graph は「これらでは表現できない複雑なフロー」のときだけ書くcreate_react_agent(name=...)でnameを付けることが両パターンの前提。supervisor はtransfer_to_<name>経由で呼び出し、swarm はCommand(goto=<name>)で遷移するsubgraphs=Trueは マルチエージェント観察の必須オプション。これなしでは「supervisor が何を考えてどこに振ったか」が見えない。LangGraph Studio でもsubgraphs=True相当の可視化が効いている- handoff の宣言は ツール定義として 書ける。LLM 側のコードに特別な API は要らず、
create_handoff_toolの戻り値を tools リストに混ぜるだけ - Swarm では プロンプトに「自分の name」と「いつ handoff すべきか」を必ず書く。書かないと LLM は handoff を呼ばずに自分で答えようとする
- 第18回(自前で
Command(goto=...)をハンドラブルしていた)は Swarm の手作り実装だったと理解できる。langgraph-swarmは同じことを 1 行で書けるラッパ stream_mode="updates"+subgraphs=Trueの chunk 形式が dict と tuple で混在する点に注意。isinstance(chunk, tuple)で分岐する古典的な処理が必要
拡張アイデア¶
- 3 つ目以降の specialist を Supervisor に足す — 例えば
translation_agent(DeepL ラッパ)を作ってcreate_supervisor(agents=[math, research, translation], ...)に追加。プロンプトを更新するだけで supervisor が新エージェントに振り分けるかを観察 - Swarm に 3 エージェント目を入れて handoff グラフを複雑化 —
billing_supportを追加して FAQ↔Tech↔Billing の三角形を作る。各 agent に 2 つずつ handoff_tool を持たせて「無限ループに陥らないか」を実験 - Supervisor + Swarm の混在 — Supervisor 配下の sub-agent として「Swarm として組まれた sub-workflow」を入れる。ホテルのコンシェルジュ(supervisor)の下に「フロント / コールセンター / 客室サービス」の Swarm を置く構造
- LangGraph Studio で並列観察 —
langgraph.jsonに既に 2 グラフ登録済みなので、uv run langgraph devで Studio を立ち上げて同じ質問を両方に投げ、ノード遷移を視覚的に比較 - Supervisor プロンプトの A/B 比較 — 同じタスクに対して「specialist の説明が箇条書き」vs「自由文」vs「task type → agent の対応表」でルーティング精度がどう変わるか実験
output_mode="last_message"に変えてみる —full_historyだと state に sub-agent の全 ToolMessage が残る。last_messageにすると最終応答だけ。token 経済性と デバッグ性のトレードオフを体験- ハンドオフ無限ループ対策 — Swarm で「両方の agent が相手にバトンを渡し続ける」状況を意図的に作って、
recursion_limitでどう止まるかを観察。本来は agent 側に「同じ handoff を 2 回続けない」プロンプトが必要
現代版に移植するなら¶
1. 既にモデルは Claude 4 Sonnet 系(2026 年現在も新しい)¶
claude-sonnet-4-20250514 は本サンプル時点で十分新しい。2026 年現在さらに新しい claude-sonnet-4-6 も存在するので、必要なら差し替え可能。temperature=0 で deterministic にしているのは Supervisor / Swarm のルーティング学習のため適切。
2. web_search / wikipedia のモックを Tavily 実装に置き換える¶
第20回で使った TavilyClient を research_agent.py の web_search に注入する:
from tavily import TavilyClient
_tavily = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
@tool
def web_search(query: str) -> str:
"""ウェブ検索を実行します"""
response = _tavily.search(query, max_results=5)
return "\n".join(f"{r['title']}: {r['content'][:200]}" for r in response.get("results", []))
これだけで「実在の AI 市場規模」を取りに行く本物のリサーチエージェントになる。
3. Swarm で循環防止の tools_condition を入れる¶
現状の Swarm は handoff の往復に上限がない(recursion_limit 頼り)。各 agent のプロンプトに「同じ相手に 2 回連続で handoff したら自分で答える」を追記、または state に handoff_history: List[str] を持たせて agent 側で参照する設計が堅い。
4. print() → logging¶
main.py の整形ロジックは print まみれ。CI 等で動かすときに log level で抑えられない。logging + RichHandler に置き換えると色付き表示も得られる。
5. Supervisor プロンプトのハードコードをファイル分離¶
supervisor_graph.py の supervisor_prompt = """...""" は agent が増えるたびに編集が必要。prompts/supervisor.txt に分離して、agents リストから自動的に「利用可能エージェント」セクションを生成する(第20回の tool_descriptions 同様)。
既知の不具合・注意点¶
- Swarm の Supervisor 非存在ゆえの応答終端の曖昧さ: Swarm は「どの agent がいつ最終回答を返すか」が agent 任せ。今回のサンプルでは tech_support が「解決方法を返したら終了」とプロンプトで示唆しているが、handoff を呼ばずに普通に応答した瞬間に終了する仕様。意図せず途中で終わるリスク
- Handoff Loop の検出機構なし: 上記の通り、A↔B が handoff を続けると
recursion_limit(デフォルト 25)まで回る。本サンプルでは事故らない簡単な質問だが、本番では循環検出が必要 tech_agent.py:43で# type: ignore[call-arg]が抜けている: 他の agent ファイルは付けているのに tech_agent.py だけ抜けている。mypy strict 設定だと警告が出るlanggraph_supervisor/langgraph_swarmのバージョン未固定:pyproject.tomlでバージョン指定がないので、uv syncのタイミングで API 破壊変更を踏む可能性。両ライブラリは 2025 年中も急速に進化中research_agent.web_searchがハードコードのモック: 「2025年AI市場規模 5200億ドル」を返すだけで、実際にはモデルが「知っていそうな数字」を返すリスクと変わらない。実装意図を理解せず本番に持ち込むと事故る- InMemorySaver の state がプロセス再起動で消える: 検証スクリプトだから問題ないが、production では SqliteSaver / PostgresSaver に差し替え必須
main.py:43の handoff 判定:"transfer_to" in tool_name or "handoff" in tool_nameで判別しているが、ツール名がcreate_handoff_toolのデフォルトに依存。カスタム名前にすると検出が外れる
記事参照¶
- Software Design 2025 年 8 月号(推定)連載第23回「マルチエージェントの 2 大パターン」
- 関連: 第18回 STUDY_NOTES — 自前で
Command(goto=...)を書いた multi-agent。Swarm の手作り版 - 関連: 第9回 STUDY_NOTES — Research Agent。タスク分解型 (= Supervisor 的) の手作り版
- 公式
langgraph-supervisor: https://github.com/langchain-ai/langgraph-supervisor-py - 公式
langgraph-swarm: https://github.com/langchain-ai/langgraph-swarm-py - LangGraph Multi-Agent ガイド: https://langchain-ai.github.io/langgraph/concepts/multi_agent/
作成: 2026-05-25 / 最終更新: 2026-06-10