マルチエージェントシステム
専門エージェントをチームとして構成することで、洗練されたアプリケーションを構築します。
エージェント階層の概要
構築するもの
このガイドでは、コーディネーターがクエリを専門家にルーティングするカスタマーサービスシステムを作成します。
┌─────────────────────┐
User Query │ │
────────────────▶ │ COORDINATOR │
│ "Route to expert" │
└──────────┬──────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ BILLING AGENT │ │ SUPPORT AGENT │
│ │ │ │
│ 💰 Payments │ │ 🔧 Tech Issues │
│ 📄 Invoices │ │ 🐛 Bug Reports │
│ 💳 Subscriptions│ │ ❓ How-To │
└──────────────────┘ └──────────────────┘
主要な概念:
- Coordinator - すべてのリクエストを受け取り、誰が処理するかを決定します
- Specialists - 特定のドメインに特化したエージェント
- Transfer - コーディネーターから専門家へのシームレスな引き渡し
クイックスタート
1. プロジェクトの作成
cargo new multi_agent_demo
cd multi_agent_demo
Cargo.tomlに依存関係を追加します:
[dependencies]
adk-rust = "2.0.0"
tokio = { version = "1", features = ["full"] }
dotenvy = "0.15"
.envをAPIキーで作成します:
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...
マルチエージェント転送の仕組み
全体像
親エージェントにサブエージェントを追加すると、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! │
│ │
└──────────────────────────────────────────────────────────────────────┘
仕組み
| コンポーネント | 役割 |
|---|---|
.sub_agent() | 親の下に専門家を登録する |
transfer_to_agent tool | サブエージェントが存在する場合に自動的に注入される |
| エージェントの説明 | どのエージェントが何を担当するかをLLMが判断するための情報 |
| Runner | 転送イベントを検出し、対象エージェントを呼び出す |
| 共有セッション | 転送後も状態と履歴を保持する |
サブAgent追加前と追加後
サブAgentなし - 1つのAgentがすべてを実行します:
User ──▶ coordinator ──▶ Response (handles billing AND support)
サブAgentあり - スペシャリストがそれぞれのドメインを処理します:
User ──▶ coordinator ──▶ billing_agent ──▶ Response (billing expert)
──▶ support_agent ──▶ Response (tech expert)
階層型マルチAgentシステム
複雑なシナリオでは、多層的な階層を作成できます。各Agentは独自のサブAgentを持つことができ、ツリーを形成します:
視覚化: 3層のコンテンツチーム
┌─────────────────────┐
│ PROJECT MANAGER │ ← Level 1: Top-level coordinator
│ "Manage projects" │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ CONTENT CREATOR │ ← Level 2: Mid-level coordinator
│ "Coordinate R&W" │
└──────────┬──────────┘
│
┌────────────────┴────────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ RESEARCHER │ │ WRITER │ ← Level 3: Specialists
│ │ │ │
│ 📚 Gather facts │ │ ✍️ Write content │
│ 🔍 Analyze data │ │ 📝 Polish text │
│ 📊 Find sources │ │ 🎨 Style & tone │
└──────────────────┘ └──────────────────┘
リクエストがどのように流れるか
User: "Create a blog post about electric vehicles"
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PROJECT MANAGER: "This is a content task" │
│ → transfers to content_creator │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ CONTENT CREATOR: "Need research first, then writing" │
│ → transfers to researcher │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ RESEARCHER: "Here's what I found about EVs..." │
│ → provides research summary │
└─────────────────────────────────────────────────────────────┘
完全なコード例
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(())
}
Agentの階層:
project_manager
└── content_creator
├── researcher
└── writer
プロンプトの例:
- 「医療におけるAIに関するブログ記事を作成してください」 → PM → Content Creator → Writer
- 「電気自動車について調査してください」 → PM → Content Creator → Researcher
サブAgentの設定
任意の LlmAgent に sub_agent() ビルダーメソッドを使用してサブAgentを追加します:
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を持つことができます
- サブAgentは独自のサブ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システムのテスト
実行例
cargo run --manifest-path examples/tier_examples/enterprise/Cargo.toml --bin 14-enterprise-multi-agent
テストプロンプトの例
カスタマーサービス:
- 「前回の請求書について質問があります」→
billing_agentにルーティングするべき - 「アプリがクラッシュし続けます」→
support_agentにルーティングするべき - 「プランをアップグレードするにはどうすればよいですか?」→
billing_agentにルーティングするべき - 「こんにちは、助けが必要です」→ 明確化のため
coordinatorに留まるべき
階層的:
- 「ヘルスケアにおけるAIに関するブログ記事を作成する」→ PM → Content Creator → Writer
- 「電気自動車の歴史を調査する」→ PM → Content Creator → Researcher
- 「現在のプロジェクトの状況はどうなっていますか?」→
project_managerに留まるべき
転送の問題をデバッグする
転送が期待どおりに機能しない場合:
- エージェント名の確認 - 転送呼び出しで正確に一致する必要があります
- 説明のレビュー - より具体的でキーワードが豊富なものにする
- 指示の明確化 - いつ転送するかを明示する
- エッジケースのテスト - 曖昧なリクエストを試してルーティング動作を確認する
- 転送インジケーターを探す -
[Agent: name]はどのエージェントが応答しているかを示します
グローバル指示
基本的な使用法
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()?;
グローバル指示とエージェント指示
- Global Instruction: 階層内のすべてのエージェントに適用され、全体的なパーソナリティ/コンテキストを設定します
- Agent Instruction: 各エージェントに固有で、その特定の役割と動作を定義します
両方の指示は会話履歴に含まれ、グローバル指示が最初に表示されます。
動的なグローバル指示
より高度なシナリオでは、指示を動的に計算するグローバル指示プロバイダーを使用できます。
use adk_core::GlobalInstructionProvider;
let provider: GlobalInstructionProvider = Arc::new(|ctx| {
Box::pin(async move {
// Access context information
let user_id = ctx.user_id();
// Compute dynamic instruction
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()?;
状態変数インジェクション
グローバルおよびエージェントの指示はどちらも、{variable}構文を使用して状態変数インジェクションをサポートします。
// Set state in a previous agent or 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()?;
フレームワークは、セッションの状態から指示テンプレートに値を自動的にインジェクションします。
一般的なマルチエージェントパターン
コーディネーター/ディスパッチャーパターン
中央のエージェントが、専門のサブエージェントにリクエストをルーティングします。
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...
主なポイント:
- コーディネーターがリクエストを分析し、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()?;
Workflow Agentとの組み合わせ
マルチエージェントシステムは、ワークフローエージェント(Sequential、Parallel、Loop)とうまく連携します。
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 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の最終応答をセッション状態に自動的に保存し、後続のAgentが利用できるようにします。
AgentTool状態とアーティファクトの転送
AgentToolを使用してAgentをToolとしてラップする場合、サブAgentからの状態変更とアーティファクトは、親コンテキストに自動的に転送されます。
use adk_tool::AgentTool;
// Create a sub-agent that modifies state
let data_processor = LlmAgentBuilder::new("data_processor")
.instruction("Process the data and save results.")
.output_key("processed_data")
.model(model.clone())
.build()?;
// Wrap as a tool - state_delta and artifact_delta are forwarded
let processor_tool = AgentTool::new(Arc::new(data_processor));
// Parent agent can use the tool and see state changes
let coordinator = LlmAgentBuilder::new("coordinator")
.instruction("Use the data_processor tool, then access {processed_data}.")
.model(model.clone())
.tool(Arc::new(processor_tool))
.build()?;
AgentToolは、内部的にサブAgentを非ストリーミングモード(StreamingMode::None)で実行するため、サブAgentは完全な応答を蓄積してから親に返します。これにより、部分的なストリーミングチャンクが空の結果を生成する可能性のある問題を防止します。
これにより、AgentToolパターンを使用する際に、親Agentと子Agent間のシームレスなデータフローが可能になります。
マルチエージェントシステムの実行
ランチャーの使用
Launcher は、マルチエージェントシステムを実行およびテストする簡単な方法を提供します。
use adk_rust::Launcher;
let coordinator = /* your multi-agent setup */;
Launcher::new(Arc::new(coordinator))
.run()
.await?;
実行モード:
# Interactive console mode
cargo run --manifest-path examples/tier_examples/enterprise/Cargo.toml --bin 14-enterprise-multi-agent
# Use a generated API project when you need HTTP serving
cargo adk new multi_agent_api --template api
機能:
- エージェントインジケーター: どのAgentが応答しているかを表示します
[Agent: coordinator] - 転送の視覚化: 転送イベントを表示します
🔄 [Transfer requested to: billing_agent] - シームレスな引き継ぎ: 転送後、ターゲットAgentが即座に応答します
- 会話履歴: Agent間の転送全体でコンテキストを維持します
転送のテスト
マルチエージェントシステムが正しく機能することを確認するには:
- Agent名を確認する 応答時に括弧内に表示される
- 転送インジケーターを探す (
🔄) Agentが引き継ぐとき - 即座の応答を確認する 再プロンプトなしでターゲットAgentから
- さまざまなリクエストタイプをテストする 適切なルーティングを確実にするため
- エッジケースを確認する 存在しないAgentへの転送など
転送の問題をデバッグする
転送が機能しない場合:
- サブエージェントが追加されていることを確認する
.sub_agent()経由で - エージェントの説明を確認する - LLM は転送を決定するためにこれらを使用します
- 指示を確認する - 親はいつ転送するかを言及する必要があります
- エージェント名を確認する - 転送呼び出しで正確に一致する必要があります
- ロギングを有効にする - イベントストリームで転送アクションを確認するため
ベストプラクティス
- 明確な説明: LLMが良い転送判断を下せるように、説明的なAgent名と説明を記述します
- 具体的な指示: 各Agentにその役割に対する明確で焦点を絞った指示を与えます
- グローバル指示の使用: すべてのAgentで一貫したパーソナリティとコンテキストを設定します
- 状態管理: Agent間の通信には
output_keyと状態変数を使用します - 階層の深さの制限: メンテナンス性を向上させるため、階層は浅く(2-3 levels)保ちます
- 転送ロジックのテスト: さまざまなリクエストに対してAgentが正しいサブAgentに転送されることを確認します
関連
- LLM Agent - コアAgent設定
- ワークフロー Agents - Sequential, Parallel, and Loop agents
- Sessions - Session状態管理
前へ: ← Workflow Agents | 次へ: Graph Agents →