
「このエージェント、なんか挙動がおかしい」——そう気づいたときには、もうコンテキストウィンドウが散らかり放題で、どのツール呼び出しが原因か追えなくなっている。
単一 LLM エージェントに複雑なタスクを丸投げすると、こういう事態が起きがちです。
金融 LLM フレームワーク TradingAgents は、この問題を「専門エージェントのチームへの分割」で解いています。
取り上げたいのは株式取引のロジックではなく、アナリスト・リサーチャー・トレーダー・リスク管理者という役割分離の 設計意図 です。
受発注でも、コンテンツ生成パイプラインでも転用できる設計の骨格として読み解きます。
なぜ1つのエージェントに全部やらせてはいけないのか
単一エージェントが破綻するのは、たいてい「複雑さが閾値を超えた瞬間」です。
AWS が公開している DevOps Companion の事例がわかりやすいです。
もともとは単一エージェントで、リポジトリ検査・サービス分類・IaC 生成・パイプライン設定・デプロイ・オブザーバビリティ設定まで、すべてひとつのコンテキストウィンドウで処理していました。
シンプルなリポジトリ相手には問題なく動いていましたが、複雑なコードベースになると途端に崩れました。
コンテキストが膨らむにつれてツール呼び出しの品質が落ち、序盤の分析結果を途中で忘れ、どこでエラーが起きたのか切り分けられなくなっていきました。
解決策は「エージェントを賢くすること」ではありませんでした。
4つの専門エージェントに分割し、それぞれに絞り込んだ責任範囲・システムプロンプト・ツールセットを持たせることでした。
この事例から見えてくる失敗パターンは3つに集約されます。
まず コンテキスト汚染 です。
情報収集フェーズのノイズが判断フェーズのプロンプトに混入し、推論の精度が下がります。
ニュース記事をたくさん読み込んだ後のコンテキストで意思決定をさせると、LLM は最初の方に読んだ情報の重みを誤って扱いがちです。
次が ツール過多 です。
20個のツールを一度に渡すと、LLM はどれをいつ使うべきか迷い始めます。
「このエージェントが持つべきツールはこれだけ」という絞り込みは、役割を先に定義しないと難しい。
そして エラーの追跡困難 です。
7ステップのワークフローがひとつのエージェントの中で動いていると、ステップ4の失敗がステップ7で表面化し、デバッグが著しく難しくなります。
どのツール呼び出しが、どの判断に影響したのかを遡れないと、プロンプトを直してもなぜ改善したのかが分からない。
「賢くするため」ではなく「責任境界を引くため」——これがマルチエージェント設計の核心です。
「このエージェントは何を知っていて、何を決めてよくて、何を知らなくていいか」という問いが、設計の正しい出発点になります。
タスクではなく 責任の境界 を先に定義する、というのが実務設計での重要な順番です。
TradingAgentsのアーキテクチャを読み解く——役割・情報フロー・意思決定の流れ
TradingAgents は、単一モデルを「専門エージェントのチーム」に置き換えた OSS フレームワークです。
LangGraph で連結された複数エージェントが、構造化された議論と多層リスクレビューを経て意思決定を行います。
全体の情報フローは「並列収集 → 統合議論 → 判断 → 拒否権チェック → 承認実行」という階層構造です。
各役割を整理します。
- ファンダメンタルズ・センチメント・ニュース・テクニカルの4アナリスト: 専用ツール(財務データ API・Reddit 検索・ニュースフィード・チャート分析)を持ち、並列で情報収集を行う。アウトプットは分野別の分析レポートで、他の領域には関与しない。
- リサーチャー(強気・弱気の2名): 4つのアナリストレポートを受け取り、対立する観点で議論する。アウトプットは統合レポートで、最終判断は下さない。
- トレーダー: リサーチャーの統合レポートをもとに取引判断を下す。「判断権限」を持つ唯一の主体。
- リスク管理者: トレーダーの判断を受け取り、市場条件・リスク指標に照らして承認または拒否を行う。判断権限ではなく 拒否権 を持つ。
- ファンドマネージャー: リスク管理者が承認した判断を最終実行する。
この構造で注目すべきは、「誰が判断し、誰がそれをチェックするか」が明確に分離されている点です。
トレーダーは判断するが拒否権を持たず、リスク管理者は拒否できるが判断を下さない。
この 非対称な権限設計 が、システム全体の健全性を保つ仕掛けになっています。
4アナリストが並列で動くのも重要な設計判断です。
ファンダメンタルズ分析とセンチメント分析は本質的に独立しているため、直列で処理する理由がありません。
「この情報は他の情報を待つ必要があるか?」という問いで並列化の判断ができます。
逆にリサーチャーはアナリスト全員のレポートが揃わないと議論できないので、直列の依存関係が生まれます。
情報フローを整理すると、このシステムは「広く集める → 絞り込む → 決める → 検証する」という認知的な作業の分業としても読めます。
各エージェントは「入ってくる情報の形」と「出す情報の形」が明確に決まっていて、その境界が責任範囲の輪郭を作っています。
「拒否権エージェント」という設計——承認フローをコードで実装する
リスク管理エージェントを「Human-in-the-loop の機械的代替」として捉えると設計意図がクリアになります。
人間のレビュアーが承認/差し戻しを返すのと同じように、エージェントが構造化された判断結果を返す設計です。
ポイントは出力を「テキストの説明文」ではなく 承認か拒否かを機械が読める形式 にすることです。
LangGraph でグラフ状態を条件分岐させるには、次ノードへの遷移を制御できるフラグが必要になります。
エージェントが「承認します。なぜなら……」という文字列を返すだけでは、そこから approved かどうかを正規表現で抜き出す脆弱なコードを書く羽目になります。
最初から構造体を返させる設計にしておくと、その問題がまるごと消えます。
from typing import TypedDict, Literal
from langgraph.graph import StateGraph, END
class TradingState(TypedDict):
trader_decision: str
approval_result: dict | None
class ApprovalResult(TypedDict):
approved: bool
reason: str
def risk_management_agent(state: TradingState) -> TradingState:
decision = state["trader_decision"]
# 実際には LLM を呼び出してリスク評価を行う
approved = decision == "buy"
result: ApprovalResult = {
"approved": approved,
"reason": "リスク範囲内と判断" if approved else "現状の条件では許容できない",
}
return {**state, "approval_result": result}
def route_by_approval(state: TradingState) -> Literal["execute", "reject"]:
result = state["approval_result"]
return "execute" if result and result["approved"] else "reject"
def fund_manager_execute(state: TradingState) -> TradingState:
print(f"実行: {state['trader_decision']}")
return state
def handle_rejection(state: TradingState) -> TradingState:
print(f"差し戻し: {state['approval_result']['reason']}")
return state
graph = StateGraph(TradingState)
graph.add_node("risk_management", risk_management_agent)
graph.add_node("execute", fund_manager_execute)
graph.add_node("reject", handle_rejection)
graph.set_entry_point("risk_management")
graph.add_conditional_edges(
"risk_management",
route_by_approval,
{"execute": "execute", "reject": "reject"},
)
graph.add_edge("execute", END)
graph.add_edge("reject", END)
app = graph.compile()
コードのポイントは2つあります。
ひとつ目は ApprovalResult を TypedDict で定義している こと。
テキストをそのまま次ノードに渡すのではなく、approved フラグを持つ構造体にすることで、条件分岐のロジックをエージェントの出力から切り離せます。
LLM の出力を Pydantic や TypedDict でパースして構造体に変換する処理を挟むと、グラフ全体がテキストの揺れに左右されなくなります。
ふたつ目は add_conditional_edges で遷移先を制御している こと。
route_by_approval 関数が approved の値を見て execute か reject を返し、それに応じてグラフの次ノードが決まります。
条件分岐のロジックをエージェント本体から切り出しているのがポイントで、リスク管理エージェントは「評価して結果を返す」ことだけに集中できます。
遷移先の判断はグラフの構造側が持つ、という役割分離がここにも現れています。
承認フローをエージェントで実装するとき、もうひとつ考えておきたいのが 差し戻し後の扱い です。
reject ノードは現状で終端になっていますが、実際のシステムでは「差し戻し理由を添えてトレーダーに再判断を促す」ループを組むことが多いです。
そのときも reason フィールドを持つ構造体で返す設計にしていれば、トレーダーエージェントへのフィードバックとしてそのまま再利用できます。
構造体で返す設計の恩恵が、こういう拡張場面でじわじわ出てきます。
金融以外への転用——「情報収集・判断・承認」の3層分離を自分のドメインに当てはめる
TradingAgents の構造は、金融ドメインを抽象化すると「情報収集層 → 判断層 → 承認層」という3層パターンとして読めます。
この骨格は、ドメインを選ばず転用できます。
たとえば コンテンツ生成パイプライン を考えてみます。
SEO 記事を量産するシステムで、「キーワードと競合情報を収集するエージェント」「原稿を生成するエージェント」「品質・ポリシー基準でレビューするエージェント」に分けるとします。
レビューエージェントが「このコンテンツはガイドライン違反の表現を含む」と判断したとき、テキストで警告するだけではパイプライン全体が止まりません。
approved フラグと violation_reason を持つ構造体を返させ、グラフ側で publish か revise に分岐させる設計にすれば、TradingAgents のリスク管理エージェントと同じ仕掛けが使えます。
受発注業務の承認フロー も同じ構造に当てはまります。
「発注内容を整理して与信情報を付加するエージェント(情報収集層)」「与信スコアと在庫をもとに受注可否を判断するエージェント(判断層)」「金額や顧客区分に応じて上長承認が必要か判定するエージェント(承認層)」という分け方が自然に出てきます。
それぞれのエージェントが「何を受け取り・何を返し・何を決めてよいか」を先に定義しておくことで、後からルールが変わっても影響範囲が明確になります。
各エージェントの責任境界を TypedDict で表現すると、次のようになります。
from typing import TypedDict, Literal
# 情報収集層のアウトプット
class CollectorOutput(TypedDict):
keyword: str
competitor_count: int
search_volume_tier: Literal["high", "medium", "low"]
reference_urls: list[str]
# 判断層のアウトプット
class GeneratorOutput(TypedDict):
title: str
body: str
word_count: int
target_keyword: str
# 承認層のアウトプット
class ReviewResult(TypedDict):
approved: bool
issues: list[str] # 問題点の一覧(承認時は空リスト)
action: Literal["publish", "revise", "reject"]
このコードは3つのエージェントの「インターフェース」だけを定義しています。
LLM 呼び出しの中身には触れていません。
それが意図的な設計です。
エージェントの責任境界は、実装ではなく インターフェース で決まります。
何を受け取って何を返すかが決まれば、モデル選択・プロンプトの工夫・ツールの組み合わせは後から変えられます。
逆に言えば、インターフェースを曖昧にしたまま実装に入ると、エージェント間の依存が「なんとなく渡されるテキスト」で繋がることになり、後からの変更が怖くなります。
CollectorOutput の reference_urls が ReviewResult の approved 判断に影響するかどうか、も設計上の問いです。
「承認層は原稿の内容だけを評価する」という設計にするなら、ReviewResult へのインプットに CollectorOutput を含めないことが境界の定義になります。
何を渡さないかを決めることが、責任の分離を実装に落とし込む作業のひとつです。
マルチエージェント設計のトレードオフ——複雑さを引き受ける前に知っておくこと
マルチエージェント構成は、課題を解決すると同時に新しい課題を生み出します。
「エージェントを増やせば増やすほどシステムが賢くなる」というのは幻想で、設計を誤ると単一エージェントより遅くて壊れやすいシステムが出来上がります。
まずレイテンシの問題があります。
エージェントが増えるたびに LLM 呼び出しが増え、それが直列に並ぶ箇所では待ち時間が積み上がります。
TradingAgents の4アナリストが並列で動けるのは、互いに依存関係がないからです。
並列化できる箇所を見つけて活かす設計ができなければ、エージェントを増やすほど遅くなります。
次にデバッグの難しさがあります。
単一エージェントのエラーはコンテキストウィンドウの中に原因があることが多いですが、マルチエージェントになると「エージェントAは正しい出力を返したが、エージェントBへの渡し方が悪かった」という境界上の問題が増えます。
エージェント間の状態遷移をトレースできる仕組みを設計段階から組み込んでおかないと、本番でのデバッグが非常に辛くなります。
そしてコーディネーションコストです。
エージェント間でやり取りされる情報が増えるほど「誰がどの情報を持っているか」の管理が複雑になります。
LangGraph のような状態機械を使えばある程度抑制できますが、グラフ自体の保守が新たな負債になり得ます。
単一エージェントとマルチエージェントをどう選ぶかの判断軸を整理します。
単一エージェントが向くケース:
- タスクが一連の推論で完結し、情報収集と判断が分離できない
- 並列化できる独立したサブタスクがない
- レイテンシが重要で、LLM 呼び出しの回数を最小化したい
- チームの規模が小さく、システムの複雑さをコントロールできるリソースが限られている
マルチエージェントが向くケース:
- 複数の独立した情報収集が並列で実行できる
- 「判断する役割」と「検証・承認する役割」が明確に分離できる
- 一部のサブタスクが失敗したとき、他のサブタスクへの影響を最小化したい
- 各エージェントの入出力をインターフェースとして定義できるくらい責任境界が明確
TradingAgents は後者の条件をすべて満たしています。
アナリストの情報収集は互いに独立しており、トレーダーの判断とリスク管理者の検証は役割として分離できます。
設計の出発点は「どうアーキテクチャをかっこよく組むか」ではなく、「このタスクは本当に分割できるか、そして分割したときに境界が明確に引けるか」という問いです。
その問いに YES と答えられるとき、マルチエージェント設計の複雑さを引き受ける価値が生まれます。
株式会社ホコサキは、山口県宇部を拠点に業務システム開発・AI 活用支援・DX 推進に取り組んでいます。
LLM エージェントの設計から実装まで、実務に寄り添った形でご支援しています。
ご相談はお気軽にどうぞ。

