コンテンツにスキップ

LangGraph マルチエージェント 写経レクチャー — 学習メモ+用語集

LangGraph で複数エージェントを協調させる典型パターン(Supervisor / Swarm / Command(goto=) / 共有 Store / TODO 駆動)を、5 本の短いサンプルで素手から段階的に学ぶ。 サンプルの並びと起動手順は README.md、連載本体の解説は第18回 (../../software-design/18/STUDY_NOTES.md)・第23回 (../../software-design/23/STUDY_NOTES.md)・第24回 (../../software-design/24/STUDY_NOTES.md) を参照。

対応連載回:

  • 第18回: マルチエージェント協調(データ分析エージェント)
  • 第23回: Supervisor / Swarm の 2 大パターン紹介
  • 第24回: Supervisor + 共有メモリ + TODO で Claude Code 風 文章執筆エージェント

注: フォルダには ex0N.py(短い名前。現行の実体)と ex0N_*.py(説明的な旧名)の 2 系統が並んでいるが、中心ロジックは同じ。本メモは ex0N.py を基準に行番号を引く。


全体像

学ぶ順序

# ファイル 学ぶ概念 中心 API LLM
01 ex01.py 最小 Supervisor。ルーター LLM が worker に委譲する型を「組み立て済み」で体験 langgraph_supervisor.create_supervisor / create_react_agent(name=...)
02 ex02.py ex01 の中身を素手で再現。ノードが Command(goto=...) を return して動的ルーティング StateGraph / Command(goto=) / with_structured_output
03 ex03.py Swarm(ピアツーピア handoff)。窓口エージェントから専門担当へ LLM が tool で bounce langgraph_swarm.create_swarm / create_handoff_tool
04 ex04.py researcher → writer の橋渡しを InMemoryStore(共有メモリ)で。state とは別経路 InMemoryStoreBaseStore)/ store.putsearchget
05 ex05.py TODO リスト + クロージャで「自分宛の宿題だけ取る」3 エージェント協調(第24回相当を最小化) シングルトン TodoManager / クロージャ tool / create_supervisor

読む順番の意図: ex01 で「完成品の Supervisor」を体験 → ex02 で「その中身(ルーター LLM + handoff + Command(goto=))を素手で」開けて見せる → ex03 で「supervisor のいない別アーキ(Swarm)」へ → ex04 で「エージェント間でデータをどう渡すか(state でなく Store)」→ ex05 で「タスクを構造化して回す(TODO 駆動)」。ex01 と ex02 はペアで読むのが肝(同じ Supervisor を「黒箱」と「素手」の両方で見る)。

2 大アーキテクチャの形(Supervisor vs Swarm)

flowchart TD
    subgraph SV["Supervisor(ex01, ex02, ex04, ex05)"]
        U1([user]) --> S{{supervisor<br/>ルーターLLM}}
        S -->|委譲| W1[worker A]
        S -->|委譲| W2[worker B]
        W1 -->|戻す| S
        W2 -->|戻す| S
        S -->|FINISH| OUT1([最終回答])
    end

    subgraph SW["Swarm(ex03)"]
        U2([user]) --> A1[faq_support<br/>窓口]
        A1 -.->|transfer_to_tech_support| A2[tech_support<br/>専門]
        A2 -.->|transfer_to_faq_support| A1
        A2 --> OUT2([最終回答])
    end

要点: Supervisor は中央のルーターが毎回ハブとして経由する「ハブ&スポーク」Swarm はエージェント同士が直接バトンを渡す「ピアツーピア」で中央司令塔がいない。点線(-.->)は「LLM が handoff tool を選んだときだけ起こる」遷移を表す。

ex02 の素手ルーティング(Command(goto=) の動き)

sequenceDiagram
    participant START
    participant SV as supervisor_node
    participant LLM as ルーターLLM<br/>(structured output)
    participant M as math_node
    participant W as writer_node

    START->>SV: 起動(add_edge は START→supervisor のみ)
    SV->>LLM: 履歴を渡し RouteDecision を要求
    LLM-->>SV: next="math"
    SV-->>M: Command(goto="math")
    M->>M: 計算 → AIMessage(name="math")
    M-->>SV: Command(goto="supervisor")
    SV->>LLM: 再度判断
    LLM-->>SV: next="writer"
    SV-->>W: Command(goto="writer")
    W-->>SV: Command(goto="supervisor")
    SV->>LLM: 再度判断
    LLM-->>SV: next="FINISH"
    SV-->>START: Command(goto=END)

ポイント: 静的な add_edgeSTART → supervisor の 1 本だけ。「次にどのノードへ行くか」はノード関数が実行時に返す Command(goto=...) が決める。これが LangGraph の動的ルーティングの核。


サンプル別の要点

ex01: create_supervisor で最小 Supervisor

「タスクコーディネーター(Supervisor)が、依頼内容を見て適切な worker に委譲する」型を、組み立て済みのヘルパーで体験する。

中心 API の役割:

  • create_react_agent(model, tools, name, prompt) — 1 つの ReAct エージェント(LLM が tool 呼び出しを自律的に回すループ)を作る。name=マルチエージェントで必須: Supervisor がこの名前で worker を識別し、handoff 先として指定する。
  • langgraph_supervisor.create_supervisor(agents, model, prompt, output_mode) — 内部で「ルーター LLM + 各 worker への handoff tool + Command(goto=...) 配線」を自動生成する。つまり ex02 で素手で書くものを 1 関数で吐く。
# ex01.py:33-45
math_agent = create_react_agent(
    model=model, tools=[add, multiply],
    name="math_expert",                         # ① worker の識別名(必須)
    prompt="あなたは計算専門のエージェント。…",
)
greeter_agent = create_react_agent(
    model=model, tools=[get_user_name],
    name="greeter", prompt="あなたは挨拶担当。…",
)
# ex01.py:48-60
workflow = create_supervisor(
    agents=[math_agent, greeter_agent],         # ② 順序は問わない
    model=model,
    prompt="あなたはタスクコーディネーター。…委譲してください。",  # ③ ルーター LLM への指示
    output_mode="full_history",                 # ④ worker のやり取りを全部 messages に残す
)
graph = build_workflow().compile()              # ⑤ Studio/`langgraph dev` 用に compile 済みも公開
やってること なぜそうする
worker に name を付ける Supervisor が「math_expert に委譲」と名前で指定するため。無名だと handoff 先を識別できない
worker のリストを渡す Supervisor がこの集合から「誰に振るか」を選ぶ
Supervisor 用 prompt ルーティング判断は LLM がやる。worker の能力一覧を渡して選ばせる
output_mode="full_history" worker 内部の全メッセージを残す。"last_message" だと各 worker の最終発話だけになる

ex02: Command(goto=) で素手 handoff

ex01 の create_supervisor が裏でやっていることを、StateGraph で素手に展開する。ex01 を読んだ直後に対比で読むのが効果的。

中心メカニズム:

  • Command(goto=..., update=...) — ノード関数の戻り値。goto次に行くノード名を、updatestate への差分書き込みを同時に指定する。add_edge を書かずに動的に行き先を決められる(条件分岐を add_conditional_edges で書く旧来法より素直)。
  • llm.with_structured_output(RouteDecision) — LLM の出力を Pydantic モデルに強制する。自由テキストを regex で抜くのでなく、next: Literal["math","writer","FINISH"] という型に縛るので、ルーティング先が必ず有効値になる。
# ex02.py:15-18
class RouteDecision(BaseModel):
    next: Literal["math", "writer", "FINISH"] = Field(...)   # ① 行き先を型で縛る

# ex02.py:20-35
def supervisor_node(state: MessagesState) -> Command:
    decision = (
        llm.with_structured_output(RouteDecision)            # ② 構造化出力
        .invoke([("system", decision_prompt), *state["messages"]])
    )
    goto = END if decision.next == "FINISH" else decision.next  # ③ FINISH→END に変換
    return Command(goto=goto)                                # ④ 行き先だけ返す(update なし)

# ex02.py:37-47
def math_node(state: MessagesState) -> Command:
    response = llm.invoke([("system", "あなたは計算専門。…"), *state["messages"]])
    return Command(
        goto="supervisor",                                   # ⑤ 仕事後は必ず supervisor に戻す
        update={"messages": [AIMessage(content=str(response.content), name="math")]},  # ⑥ 結果を state に積む
    )

# ex02.py:65-72
builder.add_edge(START, "supervisor")                        # ⑦ 静的エッジはこの 1 本だけ
やってること なぜそうする
nextLiteral で限定 存在しないノード名へ goto する事故を型で防ぐ
with_structured_output ルーティング判断を JSON スキーマに沿わせる。LLM が勝手な語を返さない
"FINISH"END LLM には人間語の FINISH を返させ、コード側で LangGraph 終端 END に翻訳
supervisor は goto だけ supervisor 自身は state を書かない(判断するだけ)
⑤⑥ worker は goto="supervisor" + update ハブ&スポーク。worker は結果を積んで必ずハブに戻す
add_edge 1 本 残りの遷移は全部 Command(goto) が実行時に決める。これが ex01 の create_supervisor の中身

ex03: Swarm(ピアツーピア handoff)

中央 Supervisor を置かない。エージェント同士が「これは自分の担当じゃない」と思ったら、handoff tool を自分で選んで相手に bounce する。最初の窓口(faq_support)→ 専門担当(tech_support)へ転送する FAQ パターン。

中心 API:

  • create_handoff_tool(agent_name, description) — 「agent_name に処理を引き渡す tool」を生成する。LLM はこれを普通のツールと同じように呼ぶ。tool 名は transfer_to_<agent_name> になる(ex03 の prompt が transfer_to_tech_support と書いているのはこの命名規約に合わせたもの)。
  • create_swarm(agents, default_active_agent) — handoff tool 付きの agent 群をまとめる。default_active_agent最初にユーザーを受けるエージェント。
# ex03.py:23-30
handoff_to_tech = create_handoff_tool(
    agent_name="tech_support",                              # ① tool 名は transfer_to_tech_support になる
    description="技術的な詳細質問は tech_support に転送する",
)
handoff_to_faq = create_handoff_tool(agent_name="faq_support", description="…")

# ex03.py:33-52
faq_agent = create_react_agent(
    model=model, tools=[handoff_to_tech],                  # ② 窓口は「技術へ転送」だけ持つ
    name="faq_support", prompt="…技術的詳細は transfer_to_tech_support で転送する。",
)
tech_agent = create_react_agent(
    model=model, tools=[lookup_doc, handoff_to_faq],       # ③ 専門は資料引き + 「FAQ へ戻す」
    name="tech_support", prompt="…lookup_doc で資料を引いて回答する。",
)

# ex03.py:55-58
workflow = create_swarm(
    agents=[faq_agent, tech_agent],
    default_active_agent="faq_support",                    # ④ 最初の窓口
)
やってること なぜそうする
handoff tool を生成 「相手へバトンを渡す」操作を LLM の選択肢(tool)にする。これが Swarm の本質
窓口は転送 tool のみ faq は簡単な質問に答え、難しいものは tech へ渡す役
専門は実務 tool + 戻す tool 技術質問が終わって雑談に戻ったら faq へ bounce する
default_active_agent Swarm には司令塔がいないので「最初に誰が出るか」を明示する

ex01/ex02 との違い: Supervisor では「supervisor が毎回呼ばれて次を決める」。Swarm では supervisor は存在せず、現在アクティブなエージェントが直接次を指名する。会話の「アクティブなエージェント」が state に保持され、handoff tool が呼ばれるとそれが切り替わる。

ex04: InMemoryStore で共有メモリ

researcher が調べたメモを writer に渡す経路を、state(messages)とは別の Store で作る。「会話履歴」と「作業用の蓄積データ」を分離する設計。

中心 API:

  • InMemoryStoreBaseStore の実装)— key-value ストア。store.put(namespace, key, value) で書き、store.search(namespace) で一覧、store.get(namespace, key) で 1 件取得。namespace はタプル(例 ("session", "research_notes"))で、セッションごとに区切れるのが利点。
  • なぜ state でなく Store か: messages はやり取りのたびに膨らむ会話ログ。一方で「調査メモ」は構造化された蓄積データ。Store に分けると、writer は list_notes/read_note必要なメモだけ取り出せる(履歴を全部読み返さなくてよい)。
# ex04.py:11-14
store = InMemoryStore()                                    # ① プロセス内共有 store
RESEARCH_NS = ("session", "research_notes")                # ② namespace(セッション分離の鍵)

# ex04.py:17-21  researcher の書き込みツール
@tool
def save_note(topic: str, content: str) -> str:
    store.put(RESEARCH_NS, topic, {"content": content})    # ③ topic を key にして保存
    return f"'{topic}' を保存しました"

# ex04.py:24-38  writer の読み出しツール
@tool
def list_notes() -> str:
    items = store.search(RESEARCH_NS)                      # ④ namespace 内を全件
    return "\n".join(f"- {it.key}: {it.value['content']}" for it in items)

@tool
def read_note(topic: str) -> str:
    item = store.get(RESEARCH_NS, topic)                   # ⑤ key 指定で 1 件
    return item.value["content"] if item else f"'{topic}' は未保存"
やってること なぜそうする
InMemoryStore() シングルトン dict でも動くが、BaseStore は LangGraph 公式の置き場で namespace・永続化差し替えに対応
namespace をタプルで ("session", "<id>") のように切ればマルチセッションで混線しない(dict だと全セッション共用で衝突する)
put(ns, key, value) value は dict。topic を key にして「上書き保存」になる
④⑤ search / get writer は会話履歴ではなく「メモ集」を直接参照 → 文脈の節約になる

注(推測ではなく README 記載の設計意図): README は「シングルトン dict だとマルチセッションで混線する。BaseStore 経由なら namespace でセッション分離がきれいに保てる」と明記している。本サンプル自体は単一セッションなので混線は起きないが、書き方の作法として Store を使っている。

ex05: TODO 駆動マルチエージェント(第24回相当の最小化)

「task_decomposer が依頼を TODO に分解 → researcher が research タスクを潰す → writer が writer タスクを潰す」を、シングルトン TodoManagerクロージャで自分の宿題だけ取る tool で実現する。Claude Code の TODO リスト方式の最小版。

中心メカニズム:

  • TodoManager(シングルトン dataclass)— add / for_agent(agent) / complete(id, result) を持つ。for_agent が「指定エージェント宛の未完了 TODO だけ」を返すのが肝。
  • クロージャ tool make_get_my_todos(agent_name)agent_name閉じ込めた get_my_todos tool を返す。同じ実装から researcher 用・writer 用の別 tool を量産でき、各エージェントは「自分宛 TODO だけ」を見る。第24回 sd_24create_get_my_todos_for_agent と同じ発想。
# ex05.py:34-43  for_agent が「自分宛の未完了だけ」を絞る
def for_agent(self, agent: str) -> list[Todo]:
    return [t for t in self.items if t.agent == agent and not t.done]  # ① 担当 × 未完了

# ex05.py:61-70  クロージャで agent_name を埋め込む
def make_get_my_todos(agent_name: str):
    @tool
    def get_my_todos() -> str:
        """自分のエージェント宛の未完了 TODO 一覧を返す"""
        todos = todo_manager.for_agent(agent_name)         # ② 閉じ込めた agent_name を使う
        return "\n".join(f"#{t.id}: {t.description}" for t in todos) or "(…ありません)"
    return get_my_todos

# ex05.py:107-126  各 worker に「自分用 get_my_todos」を配る
researcher = create_react_agent(
    tools=[make_get_my_todos("research"), mock_research, complete_todo], )  # ③
writer = create_react_agent(
    tools=[make_get_my_todos("writer"), mock_write, complete_todo], )       # ④

# ex05.py:128-135  実行順を prompt で固定
workflow = create_supervisor(
    agents=[decomposer, researcher, writer],
    prompt="…task_decomposer → researcher → writer の順で必ず呼ぶ。…")        # ⑤
やってること なぜそうする
担当 × 未完了でフィルタ 各エージェントが「他人の宿題」「終わった宿題」を見ないようにする
クロージャで agent_name を固定 同じ関数定義から researcher 用 / writer 用の別 tool を作る。tool 自身は引数なしで呼べる
③④ worker ごとに専用 tool researcher には "research"、writer には "writer" を埋めて渡す
prompt で順序を固定 分解→調査→執筆の依存順を Supervisor に守らせる(自由に振らせない)

注: mock_research / mock_write は Web 検索や実生成をせず固定文字列を返すモック(実行を速く・安くするため)。学ぶ対象は「TODO の受け渡し構造」であって調査内容ではない。


用語集(最重要)

写経しながら「言葉が混乱する」ポイントを 1 枚に整理する。

1. 一番混乱する対比: Supervisor と Swarm

両方とも「複数エージェントの協調」だが、司令塔がいるか / 誰が次を決めるかが真逆。

観点 Supervisor Swarm
ハブ&スポーク(中央に supervisor) ピアツーピア(中央なし)
次の担当を決めるのは 中央の supervisor(ルーター LLM) 今アクティブなエージェント自身
経由 worker は毎回 supervisor に戻る エージェント間を直接 bounce
切り替えの仕組み supervisor が Command(goto=worker) アクティブエージェントが handoff tool を呼ぶ
使う API create_supervisor / Command(goto=) create_swarm / create_handoff_tool
具体例 ex01・ex02・ex04・ex05 ex03
向いている場面 タスクの順序・配分を中央で統制したい 担当が会話の流れで自然に切り替わる(FAQ→専門→FAQ)

判定基準(1 文): 「誰が次を決めるか」を中央の 1 体に集約したいなら Supervisor、各エージェントに『自分で次へ渡す』判断を持たせたいなら Swarm

よくある誤解: 「Swarm は supervisor が省略された版」ではない。Swarm にはそもそも supervisor が存在しない。ex03 を回しても supervisor ノードは一度も呼ばれない(README の観察ポイントにも明記)。

2. Command(goto=) によるハンドオフ vs 通常の関数呼び出し

ノードが「次どこへ行くか」を決める仕組みは、普通の Python の関数呼び出しとは別物。

観点 Command(goto="X") 通常の関数呼び出し x()
何をする グラフの次ノードを指定して戻る(制御を LangGraph に返す) その場で関数本体を実行して値を受け取る
戻った後 LangGraph ランタイムが X ノードを次に実行 呼び出し元が続行
state update= で差分をリデューサ経由で反映 関数戻り値を自分で扱う
具体例 ex02 return Command(goto="supervisor", update={...}) ex01 の add/multiply(tool 内の純計算)

判定基準: グラフのノード遷移を制御したいなら Command(goto=)、ノード内のローカルな計算なら普通の関数 / toolCommand を「return」する点が重要で、関数を呼ぶのではなくランタイムへの指示書を返す

3. 共有 Store と state(MessagesState)の違い

エージェント間でデータを渡す経路が 2 系統ある。混同しやすい。

観点 state(MessagesState の messages) Store(InMemoryStore / BaseStore
中身 会話履歴(人間⇄AI のメッセージ列) 任意の key-value(構造化メモ・蓄積データ)
増え方 やり取りごとに追記され膨らむ put した key だけ。上書き可
取り出し方 全履歴がプロンプトに載る get/search欲しい分だけ
区切り グラフ実行(thread)単位 namespace タプルで自由に分離
具体例 ex02 の update={"messages":[...]} ex04 の save_note/read_note
使う API MessagesState / Command(update=) store.put / search / get

判定基準: 「会話の流れそのもの」は state、「会話とは別に貯めて後で参照したい作業データ」は Store。ex04 が調査メモを Store に置くのは、writer が全会話履歴を読み返さず read_note でピンポイント取得できるようにするため。

よくある誤解: 「Store はグローバル dict と同じ」→ 機能的には近いが、Store は namespace でセッション分離でき、InMemoryStorePostgresStore 等に差し替えれば永続化できる。ex04 はシングルトン dict でも動くが、あえて公式の BaseStore 作法で書いている。

4. worker の作り方: create_react_agentname と handoff

用語 役割 内部メカニズム 出てくる回
create_react_agent 1 つの ReAct エージェント(tool 呼び出しを自律的に回す LLM ループ)を生成 LLM→tool→LLM…を tool 呼び出しが尽きるまで回す ex01・ex03・ex04・ex05
name=(引数) そのエージェントの識別名 Supervisor / Swarm が「誰に渡すか」をこの名で指定。マルチエージェントでは必須 全回
create_handoff_tool(agent_name=) 「相手にバトンを渡す」tool を生成 tool 名 transfer_to_<agent_name>。LLM が選ぶと active agent が切り替わる ex03
create_supervisor(agents=) ルーター LLM + handoff + 配線を自動生成 ex02 を 1 関数に畳んだもの ex01・ex04・ex05
output_mode Supervisor の messages の残し方 "full_history"=worker の全やり取り / "last_message"=各 worker の最終発話のみ ex01

判定基準: worker を素手で配線するなら ex02 の StateGraph+Command、組み立て済みでよいなら create_supervisor。学習では一度 ex02 で素手を経験してから ex01 に戻ると create_supervisor のありがたみが分かる。

5. TODO 駆動まわりの語

用語 役割 具体例
TodoManager(シングルトン) 全エージェント共通の TODO 置き場 ex05 todo_manager
for_agent(agent) 「担当 × 未完了」で TODO を絞る ex05:34-35
クロージャ tool(make_get_my_todos agent_name を閉じ込めた tool を量産 ex05:61-70
complete_todo(id, result) TODO を完了に倒す 各 worker が消化後に呼ぶ

判定基準: 「各エージェントに自分の宿題だけ見せたい」→ クロージャで agent_name を埋めた tool を配る(引数で渡すと LLM が他人の名前を入れる事故が起きうる)。

6. クイック早見表(迷ったらここ)

困りごと 見る/使うもの
中央で順序・配分を統制したい Supervisor(create_supervisor / ex01・ex02)
会話の流れで担当を切り替えたい Swarm(create_swarm + create_handoff_tool / ex03)
create_supervisor の中身を知りたい ex02(StateGraph + Command(goto=) の素手版)
ノードから次のノードを指定したい Command(goto="X", update={...}) を return(ex02)
LLM のルーティング先を有効値に縛りたい with_structured_output(RouteDecision) + Literal(ex02)
エージェント間でメモ/データを渡したい InMemoryStoreBaseStore)+ namespace(ex04)
履歴を膨らませず必要分だけ参照したい Store の get/search(ex04)
タスクを分解して順に消化させたい TODO リスト + クロージャ tool(ex05)
各エージェントに自分の宿題だけ見せたい make_get_my_todos(agent_name) クロージャ(ex05)
worker を Supervisor/Swarm に識別させたい create_react_agent(name=...) を必ず付ける

学んだこと(要点)

  • Supervisor と Swarm の本質的な違いは「次を決める主体」。Supervisor は中央ルーター LLM、Swarm は今アクティブなエージェント自身。Swarm に supervisor は存在しない。
  • Command(goto=..., update=...) が LangGraph 動的ルーティングの核。静的 add_edge は最小限(ex02 は START→supervisor の 1 本だけ)で、残りはノードが実行時に返す Command で決まる。
  • create_supervisor は ex02 の素手コードを 1 関数に畳んだもの。ルーター LLM・handoff tool・Command 配線を自動生成している。一度素手で書くと中身が腑に落ちる。
  • Swarm の handoff は「LLM が tool として転送を選ぶ」create_handoff_tooltransfer_to_<name> という tool を作り、それが呼ばれると active agent が切り替わる。
  • エージェント間のデータ受け渡しは state と Store の 2 系統。会話の流れは state、貯めて後で引く作業データは Store(namespace でセッション分離・永続化差し替えが効く)。
  • クロージャで agent_name を埋めた tool を配ると、各エージェントが「自分宛の宿題だけ」を安全に取れる。引数で渡すより事故が少ない。
  • create_react_agent(name=...) はマルチエージェントで必須。Supervisor/Swarm がこの名前で worker を識別・指名する。
  • ルーティング判断は with_structured_output + Literal で型に縛ると、LLM が存在しないノード名を返す事故を防げる。

拡張アイデア

  1. ex02 にチェックポイント(メモリ)を追加builder.compile(checkpointer=InMemorySaver()) を入れ、thread_id を変えてマルチターン会話を試す。supervisor が前回の続きから判断する様子を観察する。
  2. ex03 に 3 体目(billing_support)を追加 — 課金質問を担当する agent と handoff tool を足し、faq → billing → tech と複数ホップする会話を作る。アクティブエージェントが state でどう保持されるかをログで追う。
  3. ex04 の Store を namespace でセッション分離RESEARCH_NS("session", session_id) に変え、2 セッションを交互に回して「混線しない」ことを実測する(README の主張をコードで確認)。さらに InMemoryStorePostgresStore に差し替えて永続化を試す。
  4. ex05 に「再分解」ループを足す — researcher が「調査が足りない」と判断したら task_decomposer に TODO を追加させ、supervisor が再び researcher に振る自己修正ループを作る。lectures/loop_basics のトリガー設計と接続する。
  5. ex01 と ex02 の実行コストを Langfuse で比較lectures/langfuse_basics@observe でラップし、create_supervisor(ex01)と素手 supervisor(ex02)で LLM 呼び出し回数・トークン・レイテンシがどう違うかをトレースで可視化する。
  6. Supervisor と Swarm の同一タスクでの挙動比較 — 同じ「FAQ→技術質問」タスクを ex01 系(Supervisor)と ex03(Swarm)の両方で解き、ホップ数・最終 messages 長・無駄な往復の差を測る。

現代版に移植するなら

  • README の .env 手順は使用禁止(プロジェクト CLAUDE.md ルール)。cp .env.sample .env + vi .env ではなく、.env.op + op run --env-file=.env.op -- uv run python ex01.py を使う(詳細は ~/.claude/docs/secret-handling.md)。
  • langgraph-supervisor / langgraph-swarm は比較的新しいヘルパー(pyproject.toml>=0.0.27 / >=0.0.11)。API が動くうちは便利だが、本質を理解するには ex02 の素手版を読むのが確実。バージョン更新で create_handoff_tool の tool 命名規約(transfer_to_<name>)が変わる可能性があるので、prompt 内の tool 名指定は実際の生成名と突き合わせて確認する。
  • with_structured_output は Pydantic v2 前提(decision.next でインスタンスのフィールドにアクセス。ex02:32-33 のコメントが明記)。旧来の dict 返しを期待するコードと混ぜない。

記事参照


作成: 2026-06-12 / 最終更新: 2026-06-12