マルチエージェントシステム
専門のAgentをチームとして構成することで、高度なアプリケーションを構築します。
構築するもの
このガイドでは、コーディネーターがクエリを専門家へルーティングするカスタマーサービスシステムを作成します。
┌─────────────────────┐
User Query │ │
────────────────▶ │ COORDINATOR │
│ "Route to expert" │
└──────────┬──────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ BILLING AGENT │ │ SUPPORT AGENT │
│ │ │ │
│ 💰 Payments │ │ 🔧 Tech Issues │
│ 📄 Invoices │ │ 🐛 Bug Reports │
│ 💳 Subscriptions│ │ ❓ How-To │
└──────────────────┘ └──────────────────┘
主要な概念:
- コーディネーター - すべてのリクエストを受け取り、誰が処理するかを決定します
- スペシャリスト - 特定のドメインに秀でた専門のAgent
- 転送 - コーディネーターからスペシャリストへのシームレスな引き渡し
クイックスタート
1. プロジェクトの作成
cargo new multi_agent_demo
cd multi_agent_demo
Cargo.tomlに依存関係を追加します。
[dependencies]
adk-rust = { version = "0.2", features = ["agents", "models", "cli"] }
tokio = { version = "1", features = ["full"] }
dotenvy = "0.15"
APIキーを含む.envを作成します。
echo 'GOOGLE_API_KEY=your-api-key' > .env
2. カスタマーサービス例
以下は完全に動作する例です。
use adk_rust::prelude::*;
use adk_rust::Launcher;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.5-flash")?);
// Specialist: Billing Agent
let billing_agent = LlmAgentBuilder::new("billing_agent")
.description("Handles billing questions: payments, invoices, subscriptions, refunds")
.instruction("You are a billing specialist. Help customers with:\n\
- Invoice questions and payment history\n\
- Subscription plans and upgrades\n\
- Refund requests\n\
- Payment method updates\n\
Be professional and provide clear information about billing matters.")
.model(model.clone())
.build()?;
// Specialist: Technical Support Agent
let support_agent = LlmAgentBuilder::new("support_agent")
.description("Handles technical support: bugs, errors, troubleshooting, how-to questions")
.instruction("You are a technical support specialist. Help customers with:\n\
- Troubleshooting errors and bugs\n\
- How-to questions about using the product\n\
- Configuration and setup issues\n\
- Performance problems\n\
Be patient and provide step-by-step guidance.")
.model(model.clone())
.build()?;
// Coordinator: Routes to appropriate specialist
let coordinator = LlmAgentBuilder::new("coordinator")
.description("Main customer service coordinator")
.instruction("You are a customer service coordinator. Analyze each customer request:\n\n\
- For BILLING questions (payments, invoices, subscriptions, refunds):\n\
Transfer to billing_agent\n\n\
- For TECHNICAL questions (errors, bugs, how-to, troubleshooting):\n\
Transfer to support_agent\n\n\
- For GENERAL greetings or unclear requests:\n\
Respond yourself and ask clarifying questions\n\n\
When transferring, briefly acknowledge the customer and explain the handoff.")
.model(model.clone())
.sub_agent(Arc::new(billing_agent))
.sub_agent(Arc::new(support_agent))
.build()?;
println!("🏢 Customer Service Center");
println!(" Coordinator → Billing Agent | Support Agent");
println!();
Launcher::new(Arc::new(coordinator)).run().await?;
Ok(())
}
対話例:
You: I have a question about my last invoice
[Agent: coordinator]
Assistant: I'll connect you with our billing specialist to help with your invoice question.
[Agent: billing_agent]
Assistant: Hello! I can help you with your invoice. What specific question do you have about your last invoice?
You: Why was I charged twice?
[Agent: billing_agent]
Assistant: I understand your concern about the duplicate charge. Let me help you investigate this...
エージェント間転送の仕組み
全体像
親エージェントにsub-agentを追加すると、LLMはタスクを委任する能力を得ます。
┌─────────────────────┐
User Message │ │
─────────────────▶ COORDINATOR │
│ │
└──────────┬──────────┘
│
"This is a billing question..."
│
┌────────────────┴────────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ billing_agent │ │ support_agent │
│ 💰 Payments │ │ 🔧 Tech Issues │
│ 📄 Invoices │ │ 🐛 Bug Reports │
└──────────────────┘ └──────────────────┘
転送フローのステップバイステップ
ユーザーが請求に関する質問をした際に、正確に何が起こるかを以下に示します。
┌──────────────────────────────────────────────────────────────────────┐
│ STEP 1: User sends message │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ User: "Why was I charged twice on my invoice?" │
│ │
│ ↓ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ COORDINATOR AGENT │ │
│ │ Receives message first │ │
│ └──────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────────────┐
│ STEP 2: LLM analyzes and decides to transfer │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ 🧠 LLM thinks: "This is about an invoice charge..." │
│ "Invoice = billing topic..." │
│ "I should transfer to billing_agent" │
│ │
│ 📞 LLM calls: transfer_to_agent(agent_name="billing_agent") │
│ │
└──────────────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────────────┐
│ STEP 3: Runner detects transfer and invokes target │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ transfer event ┌─────────────────┐ │
│ │ Runner │ ─────────────────────▶ │ billing_agent │ │
│ └─────────┘ (same user message) └─────────────────┘ │
│ │
│ • Runner finds "billing_agent" in agent tree │
│ • Creates new context with SAME user message │
│ • Invokes billing_agent immediately │
│ │
└──────────────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────────────┐
│ STEP 4: Target agent responds │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ billing_agent responds │ │
│ │ │ │
│ │ "I can help with your duplicate │ │
│ │ charge. Let me investigate..." │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ✅ User sees seamless response - no interruption! │
│ │
└──────────────────────────────────────────────────────────────────────┘
機能の仕組み
| Component | Role |
|---|---|
.sub_agent() | 親の下にスペシャリストを登録 |
transfer_to_agent tool | sub-agentが存在する場合に自動的に挿入される |
| Agent descriptions | LLMがどのアгентが何を処理するかを決定するのに役立つ |
| Runner | 転送イベントを検出し、ターゲットAgentを呼び出す |
| Shared session | 転送間で状態と履歴が保持される |
sub-agent追加前と追加後
sub-agentなし - 1つのAgentがすべてを実行します。
User ──▶ coordinator ──▶ Response (handles billing AND support)
sub-agentあり - スペシャリストがそれぞれの専門分野を処理します。
User ──▶ coordinator ──▶ billing_agent ──▶ Response (billing expert)
──▶ support_agent ──▶ Response (tech expert)
階層型マルチエージェントシステム
複雑なシナリオでは、多段階の階層を作成できます。各エージェントは独自のサブエージェントを持ち、ツリーを形成します。
視覚化: 3レベルのコンテンツチーム
┌─────────────────────┐
│ PROJECT MANAGER │ ← レベル1: トップレベルの調整役
│ "プロジェクトを管理" │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ CONTENT CREATOR │ ← レベル2: 中間レベルの調整役
│ "R&Wを調整する" │
└──────────┬──────────┘
│
┌────────────────┴────────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ RESEARCHER │ │ WRITER │ ← レベル3: 専門家
│ │ │ │
│ 📚 事実を収集 │ │ ✍️ コンテンツを執筆 │
│ 🔍 データを分析 │ │ 📝 テキストを推敲 │
│ 📊 ソースを探す │ │ 🎨 スタイルとトーン│
└──────────────────┘ └──────────────────┘
リクエストがどのように流れるか
User: "電気自動車に関するブログ投稿を作成してください"
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PROJECT MANAGER: "これはコンテンツタスクです" │
│ → content_creatorに転送する │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ CONTENT CREATOR: "まず調査が必要です。その後、執筆です" │
│ → researcherに転送する │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ RESEARCHER: "EVについて見つけたことは次のとおりです..." │
│ → 調査概要を提供する │
└─────────────────────────────────────────────────────────────┘
完全なコード例
use adk_rust::prelude::*;
use adk_rust::Launcher;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.5-flash")?);
// Level 3: Leaf specialists
let researcher = LlmAgentBuilder::new("researcher")
.description("Researches topics and gathers comprehensive information")
.instruction("You are a research specialist. When asked to research a topic:\n\
- Gather key facts and data\n\
- Identify main themes and subtopics\n\
- Note important sources or references\n\
Provide thorough, well-organized research summaries.")
.model(model.clone())
.build()?;
let writer = LlmAgentBuilder::new("writer")
.description("Writes polished content based on research")
.instruction("You are a content writer. When asked to write:\n\
- Create engaging, clear content\n\
- Use appropriate tone for the audience\n\
- Structure content logically\n\
- Polish for grammar and style\n\
Produce professional, publication-ready content.")
.model(model.clone())
.build()?;
// Level 2: Content coordinator
let content_creator = LlmAgentBuilder::new("content_creator")
.description("Coordinates content creation by delegating research and writing")
.instruction("You are a content creation lead. For content requests:\n\n\
- If RESEARCH is needed: Transfer to researcher\n\
- If WRITING is needed: Transfer to writer\n\
- For PLANNING or overview: Handle yourself\n\n\
Coordinate between research and writing phases.")
.model(model.clone())
.sub_agent(Arc::new(researcher))
.sub_agent(Arc::new(writer))
.build()?;
// Level 1: Top-level manager
let project_manager = LlmAgentBuilder::new("project_manager")
.description("Manages projects and coordinates with content team")
.instruction("You are a project manager. For incoming requests:\n\n\
- For CONTENT creation tasks: Transfer to content_creator\n\
- For PROJECT STATUS or general questions: Handle yourself\n\n\
Keep track of overall project goals and deadlines.")
.model(model.clone())
.sub_agent(Arc::new(content_creator))
.build()?;
println!("📊 Hierarchical Multi-Agent System");
println!();
println!(" project_manager");
println!(" └── content_creator");
println!(" ├── researcher");
println!(" └── writer");
println!();
Launcher::new(Arc::new(project_manager)).run().await?;
Ok(())
}
エージェント階層:
project_manager
└── content_creator
├── researcher
└── writer
プロンプト例:
- 「AIのヘルスケアにおけるブログ投稿を作成してください」 → PM → Content Creator → Writer
- 「電気自動車を調査してください」 → PM → Content Creator → Researcher
サブエージェントの設定
sub_agent()ビルダーメソッドを使用して、任意のLlmAgentにサブエージェントを追加します。
let parent = LlmAgentBuilder::new("parent")
.description("Coordinates specialized tasks")
.instruction("Route requests to appropriate specialists.")
.model(model.clone())
.sub_agent(Arc::new(specialist_a))
.sub_agent(Arc::new(specialist_b))
.build()?;
要点:
- 各Agentは複数のサブエージェントを持つことができます
- サブエージェントは独自のサブエージェントを持つことができます(多階層構造)
- Agent名は階層内で一意である必要があります
- 説明はLLMがどのAgentに転送するかを決定するのに役立ちます
効果的な転送指示の記述
Agentの転送を成功させるには、明確な指示と説明を提供します。
親Agentの指示
let coordinator = LlmAgentBuilder::new("coordinator")
.description("Main customer service coordinator")
.instruction("You are a customer service coordinator. Analyze each request:\n\n\
- For BILLING questions (payments, invoices, subscriptions):\n\
Transfer to billing_agent\n\n\
- For TECHNICAL questions (errors, bugs, troubleshooting):\n\
Transfer to support_agent\n\n\
- For GENERAL greetings or unclear requests:\n\
Respond yourself and ask clarifying questions")
.model(model.clone())
.sub_agent(Arc::new(billing_agent))
.sub_agent(Arc::new(support_agent))
.build()?;
サブAgentの説明
let billing_agent = LlmAgentBuilder::new("billing_agent")
.description("Handles billing questions: payments, invoices, subscriptions, refunds")
.instruction("You are a billing specialist. Help with payment and subscription issues.")
.model(model.clone())
.build()?;
let support_agent = LlmAgentBuilder::new("support_agent")
.description("Handles technical support: bugs, errors, troubleshooting, how-to questions")
.instruction("You are a technical support specialist. Provide step-by-step guidance.")
.model(model.clone())
.build()?;
ベストプラクティス:
- 目的を明確に示す記述的なAgent名を使用します
- 詳細な説明を記述します — LLMはこれらを使用して転送を決定します
- ユーザーの要求と一致する可能性のある特定のキーワードを説明に含めます
- 親Agentの指示に明確な委任ルールを記述します
- Agentの説明全体で一貫した用語を使用します
マルチAgentシステムのテスト
実行例
# Run the customer service example
cargo run --bin customer_service
# Run the hierarchical example
cargo run --bin hierarchical
テストプロンプトの例
カスタマーサービス:
- 「前回の請求書について質問があります」→
billing_agentにルーティングされるべきです - 「アプリがクラッシュし続けます」→
support_agentにルーティングされるべきです - 「プランをアップグレードするにはどうすればよいですか?」→
billing_agentにルーティングされるべきです - 「こんにちは、助けが必要です」→ 明確化のために
coordinatorに留まるべきです
階層型:
- 「ヘルスケアにおけるAIに関するブログ記事を作成してください」→ PM → Content Creator → Writer
- 「電気自動車の歴史を調査してください」→ PM → Content Creator → Researcher
- 「現在のプロジェクトの状況はどうなっていますか?」→
project_managerに留まるべきです
転送の問題のデバッグ
転送が期待通りに機能しない場合は、以下を確認してください。
- Agent名を確認します — 転送呼び出しで正確に一致する必要があります
- 説明を見直します — より具体的でキーワードを豊富にします
- 指示を明確にします — いつ転送するかを明示的にします
- エッジケースをテストします — あいまいなリクエストを試してルーティング動作を確認します
- 転送インジケータを探します —
[Agent: name]はどのAgentが応答しているかを示します
グローバル指示
基本的な使用法
let agent = LlmAgentBuilder::new("assistant")
.description("A helpful assistant")
.global_instruction(
"You are a professional assistant for Acme Corp. \
Always maintain a friendly but professional tone. \
Our company values are: customer-first, innovation, and integrity."
)
.instruction("Help users with their questions and tasks.")
.model(model.clone())
.build()?;
グローバル指示と Agent 指示
- グローバル指示: 階層内のすべての Agent に適用され、全体的なパーソナリティ/コンテキストを設定します。
- Agent 指示: 各 Agent に固有であり、その特定の役割と動作を定義します。
両方の指示は会話履歴に含まれ、グローバル指示が最初に表示されます。
動的なグローバル指示
より高度なシナリオでは、指示を動的に計算するグローバル指示プロバイダーを使用できます。
use adk_core::GlobalInstructionProvider;
let provider: GlobalInstructionProvider = Arc::new(|ctx| {
Box::pin(async move {
// コンテキスト情報にアクセスします
let user_id = ctx.user_id();
// 動的な指示を計算します
let instruction = format!(
"You are assisting user {}. Tailor your responses to their preferences.",
user_id
);
Ok(instruction)
})
});
let agent = LlmAgentBuilder::new("assistant")
.description("A personalized assistant")
.global_instruction_provider(provider)
.model(model.clone())
.build()?;
状態変数の挿入
グローバル指示と Agent 指示の両方で、{variable} 構文を使用した状態変数の挿入がサポートされています。
// 以前の Agent または Tool で状態を設定します
// state["company_name"] = "Acme Corp"
// state["user_role"] = "manager"
let agent = LlmAgentBuilder::new("assistant")
.global_instruction(
"You are an assistant for {company_name}. \
The user is a {user_role}."
)
.instruction("Help with {user_role}-level tasks.")
.model(model.clone())
.build()?;
フレームワークは、セッションの状態から指示テンプレートに値を自動的に挿入します。
一般的なマルチエージェントパターン
コーディネーター/ディスパッチャーパターン
中心となるAgentが、専門のサブAgentにリクエストをルーティングします。
let billing = LlmAgentBuilder::new("billing")
.description("Handles billing and payment questions")
.model(model.clone())
.build()?;
let support = LlmAgentBuilder::new("support")
.description("Provides technical support")
.model(model.clone())
.build()?;
let coordinator = LlmAgentBuilder::new("coordinator")
.instruction("Route requests to billing or support agents as appropriate.")
.sub_agent(Arc::new(billing))
.sub_agent(Arc::new(support))
.model(model.clone())
.build()?;
会話例:
User: I have a question about my last invoice
[Agent: coordinator]
Assistant: I'll connect you with our billing specialist.
🔄 [Transfer requested to: billing]
[Agent: billing]
Assistant: Hello! I can help you with your invoice.
What specific question do you have?
User: Why was I charged twice?
[Agent: billing]
Assistant: Let me investigate that duplicate charge for you...
主なポイント:
- coordinatorがリクエストを分析し、billing Agentに転送します。
- billing Agentは同じターンで即座に応答します。
- その後のメッセージはbilling Agentとの間で続きます。
- 転送インジケーター(
🔄)は、ハンドオフが発生したことを示します。
階層的なタスク分解
複雑なタスクを分解するための多段階の階層です。
// Low-level specialists
let researcher = LlmAgentBuilder::new("researcher")
.description("Researches topics and gathers information")
.model(model.clone())
.build()?;
let writer = LlmAgentBuilder::new("writer")
.description("Writes content based on research")
.model(model.clone())
.build()?;
// Mid-level coordinator
let content_creator = LlmAgentBuilder::new("content_creator")
.description("Creates content by coordinating research and writing")
.sub_agent(Arc::new(researcher))
.sub_agent(Arc::new(writer))
.model(model.clone())
.build()?;
// Top-level manager
let project_manager = LlmAgentBuilder::new("project_manager")
.description("Manages content creation projects")
.sub_agent(Arc::new(content_creator))
.model(model.clone())
.build()?;
ワークフローAgentとの組み合わせ
マルチAgentシステムは、ワークフローAgent(SequentialAgent, ParallelAgent, LoopAgent)とうまく機能します。
use adk_agent::workflow::{SequentialAgent, ParallelAgent};
// Create specialized agents
let validator = LlmAgentBuilder::new("validator")
.instruction("Validate the input data.")
.output_key("validation_result")
.model(model.clone())
.build()?;
let processor = LlmAgentBuilder::new("processor")
.instruction("Process data if {validation_result} is valid.")
.output_key("processed_data")
.model(model.clone())
.build()?;
// Combine in a sequential workflow
let pipeline = SequentialAgent::new(
"validation_pipeline",
vec![Arc::new(validator), Arc::new(processor)]
);
// Use the pipeline as a sub-agent
let coordinator = LlmAgentBuilder::new("coordinator")
.description("Coordinates data processing")
.sub_agent(Arc::new(pipeline))
.model(model.clone())
.build()?;
Agent間のコミュニケーション
階層内のAgentは、共有されたSession stateを介して通信します。
// Agent A saves data to state
let agent_a = LlmAgentBuilder::new("agent_a")
.instruction("Analyze the topic and save key points.")
.output_key("key_points") // Automatically saves output to state
.model(model.clone())
.build()?;
// Agent B reads data from state
let agent_b = LlmAgentBuilder::new("agent_b")
.instruction("Expand on the key points: {key_points}")
.model(model.clone())
.build()?;
output_keyの設定により、Agentの最終応答が自動的にSession stateに保存され、後続のAgentがそれを利用できるようになります。
マルチエージェントシステムの実行
Launcherの使用
Launcherは、マルチエージェントシステムを実行およびテストする簡単な方法を提供します。
use adk_rust::Launcher;
let coordinator = /* your multi-agent setup */;
Launcher::new(Arc::new(coordinator))
.run()
.await?;
実行モード:
# Interactive console mode
cargo run --example multi_agent -- chat
# Web server mode with UI
cargo run --example multi_agent -- serve
cargo run --example multi_agent -- serve --port 3000
機能:
- Agent indicators: どのagentが応答しているかを表示します
[Agent: coordinator] - 転送の可視化: 転送イベントを表示します
🔄 [Transfer requested to: billing_agent] - シームレスな引き渡し: 転送後、ターゲットのagentが即座に応答します
- 会話履歴: agent間の転送全体でコンテキストを維持します
転送のテスト
マルチエージェントシステムが正しく機能することを確認するには:
- agent名が応答時に角括弧で囲まれて表示されることを確認します
- agentが引き渡す際に転送インジケーター (
🔄) を探します - 再プロンプトなしでターゲットのagentからの即時応答を確認します
- 適切なルーティングを確実にするために、さまざまなリクエストタイプをテストします
- 存在しないagentへの転送など、エッジケースを確認します
転送の問題のデバッグ
転送が機能しない場合:
.sub_agent()を介してsub-agentが追加されていることを確認します- agentの説明を確認します - LLMはこれらを使用して転送を決定します
- 指示を確認します - 親はいつ転送するかを記述する必要があります
- agent名を確認します - 転送呼び出しで正確に一致する必要があります
- イベントストリームで転送アクションを表示するためにログを有効にします
ベストプラクティス
- 明確な説明: LLMが良い転送決定を下すのに役立つよう、記述的なagent名と説明を記述します
- 具体的な指示: 各agentにその役割のための明確で集中的な指示を与えます
- グローバルな指示の使用: すべてのagentに一貫したパーソナリティとコンテキストを設定します
- 状態管理: agent間の通信には
output_keyと状態変数を使用します - 階層の深さの制限: メンテナンス性を向上させるため、階層を浅く保ちます (2~3レベル)
- 転送ロジックのテスト: さまざまなリクエストに対して、agentが正しいsub-agentに転送することを確認します
関連
- LLM Agent - コアとなるAgentの設定
- Workflow Agents - 逐次、並列、およびループのagent
- Sessions - Sessionの状態管理
前へ: ← Workflow Agents | 次へ: Graph Agents →