マルチエージェントシステム
専門化されたエージェントをチームに組み合わせて、高度なアプリケーションを構築します。
エージェント階層の概要
構築するもの
このガイドでは、コーディネーターが問い合わせを専門担当者に振り分ける カスタマーサービスシステム を作成します。
┌─────────────────────┐
User Query │ │
────────────────▶ │ COORDINATOR │
│ "Route to expert" │
└──────────┬──────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ BILLING AGENT │ │ SUPPORT AGENT │
│ │ │ │
│ 💰 Payments │ │ 🔧 Tech Issues │
│ 📄 Invoices │ │ 🐛 Bug Reports │
│ 💳 Subscriptions│ │ ❓ How-To │
└──────────────────┘ └──────────────────┘
主な概念:
- コーディネーター - すべてのリクエストを受け取り、誰が対応するかを決定します
- 専門担当者 - 特定の領域に特化し、その分野で優れたエージェントです
- 引き継ぎ - コーディネーターから専門担当者へのシームレスな引き渡しです
ポータブル TeamSpec
同じチームを生成されたプロジェクト、CLI、Studio プレビュー、Enterprise デプロイメントで同一に実行する必要がある場合は、TeamSpec を使用します。仕様はシリアライズ可能なデータです。compile は、宣言された各名前を既存の任意の Agent 実装にバインドし、実行可能な CompiledTeam ルートを返します。これは新しいアトミックエージェント種別を追加するものではありません。
use adk_agent::{
Agent, RelationshipKind, TeamMemberSpec, TeamRelationship, TeamSpec,
};
use std::sync::Arc;
let spec = TeamSpec {
name: "support_team".into(),
description: "Customer support routing".into(),
coordinator: "coordinator".into(),
members: vec![
TeamMemberSpec::new("coordinator"),
TeamMemberSpec::new("billing_agent"),
TeamMemberSpec::new("research_agent"),
],
relationships: vec![
TeamRelationship::new(
"coordinator", "billing_agent", RelationshipKind::Handoff,
),
TeamRelationship::new(
"coordinator", "research_agent", RelationshipKind::Delegate,
),
],
policy: Default::default(),
};
let root = spec.compile(vec![
Arc::new(coordinator) as Arc<dyn Agent>,
Arc::new(billing_agent),
Arc::new(research_agent),
])?;
クレートを直接使用する場合、仕様に Delegate が含まれているときは、adk-agent の team-tools 機能を有効にします。アンブレラクレートを使用する場合は、tools(または standard ティア)を有効にします。引き継ぎのみの仕様は、ベースの adk-agent 機能セットでコンパイルできます。
| 関係 | 制御動作 | 低レベル化されたメカニズム |
|---|---|---|
Handoff | 呼び出し元が制御を移譲し、再開しない | ランナー transfer_to_agent |
Delegate | 呼び出し元がメンバーを呼び出し、その結果を受け取って再開する | AgentTool |
ターゲットはメンバーごとに厳密に定義されます。コンパイラは、重複するメンバーとエッジ、欠落したエンドポイント、サイクル、到達不能なメンバー、無効な境界値、欠落したエージェントバインディング、余分なバインディングを拒否します。委譲エッジには、入力/出力 JSON スキーマ、コンテキストと履歴の投影、厳密な状態キー、タイムアウトと承認の要件、再試行/フォールバックの動作、サーキットブレーカーを持たせることができます。ハンドオフエッジは、上限付きの起動時再試行/フォールバックをサポートします。委譲専用フィールドは、黙って無視されるのではなく拒否されます。フォールバックは個別に宣言されたエッジでなければならず、検証によってその権限が拡大されることはありません。
チーム全体のポリシーによって、ハンドオフ/委譲の深さ、同時実行数、イベント数、モデルおよびツールの呼び出し回数、トークン数、コスト、関係数、実時間が制限されます。終了ルールでは、エスカレーション、厳密な最終作成者、またはテキストマーカーを条件として停止できます。すべてのイベントには、__adk_team_execution_v1 にシリアライズ可能な実行レシートが含まれます。このレシートには、固定されたロスター、因果関係のある委譲/ハンドオフエッジ、集計された使用量、終了ステータスが含まれます。CompiledTeam::execution_snapshot を使用してこれを検査し、同じ呼び出し ID で再開する前に restore_execution_snapshot を使用してください。
アクティブなハンドオフは、固定されたターゲットで再開されます。未解決の委譲は自動的に再実行されません。これは、汎用の Agent コントラクトでは、ツール呼び出しがべき等であることを証明できないためです。代わりに、ランタイムは対処可能なエラーを返します。その委譲は独自のチェックポイント機構を通じて再開するか、操作を明示的に失敗または再起動してください。
再利用可能なアーキテクチャでは、TeamArchitectureTemplate がスーパーバイザー、ルーター、階層型プリセットを明示的な TeamSpec データに変換します。
WorkflowArchitectureTemplate は、既存のワークフローエージェントから、シーケンシャル、並列、ファンアウト/ファンイン、および上限付きレビューループのプリセットをコンパイルします。Capability ベースの解決は TeamAgentRegistry を通じて利用できます。選択は決定論的に行われ、ヘルス、バージョン、ダイジェスト、信頼ラベルを必須にできます。また、選択された構成は実行レシート内で凍結されます。
実行プレーンの機能
コンパイルでは、ソースエージェントが宣言した AgentCapabilities を検査します。委譲ソースは呼び出しスコープのランタイムツールを使用し、ハンドオフソースは転送を出力し、承認済みの委譲先は厳密な呼び出し確認をサポートする必要があります。
LlmAgent と CodeActAgent は、サポートするランタイム機能を宣言します。カスタムエージェントは保守的なデフォルトを維持し、capabilities() をオーバーライドできます。
チームは標準の ADK 動作を維持します。Runner プラグイン、エージェント/モデル/ツールのコールバック、ガードレール、キャンセル、リクエスト識別情報、メモリ、アーティファクト、セッション、イベント永続化は、既存のメンバーエージェントを引き続きラップします。compile_with_hooks は、チーム、メンバー、厳密な関係境界に対する順序付き非同期 TeamLifecycleHook コールバックを追加します。フックは成功/失敗を監視したり、対象範囲を広げることなく保留中の操作を拒否したりできます。Runner は、検証済みのハンドオフの直前に、ルートの非同期 govern_transfer フックも呼び出します。
ネイティブの team.run、team.member.run、team.relationship.execute トレーシングスパンは、通常の adk-telemetry エクスポーターを通じて、チーム/メンバー/エッジの識別情報、ステータス、関係の継続時間を伝達します。ランタイム障害では、agent.team.policy_denied、agent.team.budget_exceeded、agent.team.resume_unsafe のような安定した構造化コードが使用されます。
委譲コントラクトでは、状態の読み取り (stateKeys) と書き込み
(stateWriteKeys) を区別します。stateMerge: rejectConflicts は、委譲中に親の値が変更された場合に子の書き込みを拒否し、artifactPrefixes はアーティファクト名を制限します。スタンドアロンの AgentTool でも同じ制御を利用できます。互換性のため、デフォルトは引き続き最終書き込み優先ですが、Team 関係ではデフォルトで競合を拒否します。
CompiledTeam::resume_plan は、レシートを復元した後に実行できる唯一の安全な次のアクション、すなわちコーディネーター、ハンドオフ先、チェックポイント対応デリゲート、または安全でないフェイルクローズ型委譲について説明します。validate_team_replay は因果レシートの完全性を検証し、analyze_execution はテストおよび評価パイプライン向けに、プロバイダーに依存しないトポロジーのカバレッジ、失敗、および最大因果深度を報告します。
BlackboardSpec は、管理対象のラウンドロビン方式およびセレクター駆動のグループチャットを扱います。ラウンド数とトランスクリプト投影に上限を設け、セレクターの遷移では厳密な許可リストを使用します。既存の
sub_agent のコンポジションも後方互換性のため引き続きサポートされますが、これはポータブルなチーム定義ではなく、メモリ内のツリーです。
クイックスタート
1. プロジェクトを作成する
cargo new multi_agent_demo
cd multi_agent_demo
Cargo.toml に依存関係を追加します。
[dependencies]
adk-rust = "2.1.0"
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-3.7-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 ツール | サブエージェントが存在する場合に自動注入 |
| エージェントの説明 | LLM がどのエージェントが何を処理するかを判断するのに役立つ |
| ランナー | 転送イベントを検出し、対象のエージェントを呼び出す |
| 共有セッション | 転送後も状態と履歴が保持される |
サブエージェント追加前と追加後
サブエージェントなし - 1つのエージェントがすべてを実行:
User ──▶ coordinator ──▶ Response (handles billing AND support)
サブエージェントあり - 各専門家が担当領域を処理:
User ──▶ coordinator ──▶ billing_agent ──▶ Response (billing expert)
──▶ support_agent ──▶ Response (tech expert)
階層型マルチエージェントシステム
複雑なシナリオでは、複数レベルの階層を作成できます。各エージェントは独自のサブエージェントを持つことができ、ツリーを形成します。
視覚化: 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-3.7-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 → コンテンツ作成者 → ライター
- 「電気自動車について調査」 → PM → コンテンツ作成者 → リサーチャー
サブエージェントの設定
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()?;
主なポイント:
- 各エージェントは複数のサブエージェントを持つことができます
- サブエージェントは独自のサブエージェントを持つことができます(複数レベルの階層)
- エージェント名は階層内で一意でなければなりません
- 説明は、LLMが転送先のエージェントを決定するのに役立ちます
効果的な転送指示の作成
エージェントの転送を成功させるには、明確な指示と説明を提供します。
親エージェントの指示
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()?;
サブエージェントの説明
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()?;
ベストプラクティス:
- 目的を明確に示す説明的なエージェント名を使用する
- 詳細な説明を記述する - LLMはこれらを使用して転送を決定します
- 想定されるユーザーリクエストに一致する具体的なキーワードを説明に含める
- 親エージェントの指示に明確な委任ルールを記載する
- エージェントの説明全体で一貫した用語を使用する
マルチエージェントシステムのテスト
例の実行
cargo run --manifest-path examples/tier_examples/enterprise/Cargo.toml --bin 14-enterprise-multi-agent
テストプロンプトの例
カスタマーサービス:
- 「前回の請求書について質問があります」 →
billing_agentにルーティングされるはずです - 「アプリが何度もクラッシュします」 →
support_agentにルーティングされるはずです - 「プランをアップグレードするにはどうすればよいですか?」 →
billing_agentにルーティングされるはずです - 「こんにちは、助けが必要です」 → 明確化のため、
coordinatorに留まるはずです
階層型:
- 「医療におけるAIについてブログ記事を作成して」 → PM → コンテンツ作成者 → ライター
- 「電気自動車の歴史を調査して」 → PM → コンテンツ作成者 → リサーチャー
- 「現在進行中のプロジェクトの状況はどうなっていますか?」 →
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()?;
グローバル指示とエージェント指示
- グローバル指示: 階層内のすべてのエージェントに適用され、全体的なパーソナリティやコンテキストを設定します
- エージェント指示: 各エージェントに固有のもので、個別の役割と動作を定義します
両方の指示が会話履歴に含まれ、グローバル指示が最初に表示されます。
動的なグローバル指示
より高度なシナリオでは、指示を動的に計算するグローバル指示プロバイダーを使用できます。
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...
主なポイント:
- コーディネーターがリクエストを分析し、請求エージェントに転送する
- 請求エージェントは同じターン内で直ちに応答する
- 以降のメッセージは請求エージェントとのやり取りを継続する
- 転送インジケーター(
🔄)により、ハンドオフが発生したタイミングを確認できる
階層型タスク分解
複雑なタスクを分解するための多層階層構造:
// 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()?;
ワークフローエージェントとの組み合わせ
マルチエージェントシステムは、ワークフローエージェント(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 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設定により、エージェントの最終レスポンスがセッション状態に自動的に保存され、後続のエージェントから利用できるようになります。
AgentTool状態とアーティファクトの転送
AgentToolを使用してエージェントをツールとしてラップすると、サブエージェントによる状態の変更とアーティファクトが親コンテキストに自動的に転送されます。
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は、内部でサブエージェントを非ストリーミングモード(StreamingMode::None)で実行するため、サブエージェントは親に返す前に完全なレスポンスを蓄積します。これにより、部分的なストリーミングチャンクによって空の結果が生成される問題を防ぎます。
これにより、AgentToolパターンを使用する際に、親エージェントと子エージェント間でシームレスなデータフローが実現します。
マルチエージェントシステムの実行
Launcher の使用
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: coordinator] - 転送の可視化: 転送イベントを表示
🔄 [Transfer requested to: billing_agent] - シームレスな引き継ぎ: 転送後、対象エージェントが直ちに応答
- 会話履歴: エージェント間の転送全体でコンテキストを維持
転送のテスト
マルチエージェントシステムが正しく動作することを確認するには:
- エージェント名を確認: エージェントが応答するとき、角括弧内に表示されることを確認
- 転送インジケーターを確認: エージェントが引き継ぐときに転送インジケーター (
🔄) が表示されることを確認 - 即時応答を確認: 再度プロンプトを送らなくても、対象エージェントから応答されることを確認
- さまざまなリクエストタイプをテスト: 適切にルーティングされることを確認
- エッジケースを確認: 存在しないエージェントへの転送などを確認
転送に関する問題のデバッグ
転送が機能しない場合:
- サブエージェントが追加されていることを確認:
.sub_agent()を使用していることを確認 - エージェントの説明を確認: LLM は転送の判断にこれらを使用します
- 指示を確認: 親エージェントが転送のタイミングについて言及していることを確認
- エージェント名を確認: 転送呼び出しで完全に一致している必要があります
- ロギングを有効化: イベントストリームで転送アクションを確認
ベストプラクティス
- 明確な説明: LLM が適切な転送判断を行えるよう、説明的なエージェント名と説明を記述する
- 具体的な指示: 各エージェントに、その役割に沿った明確で焦点を絞った指示を与える
- グローバル指示を使用: すべてのエージェントで一貫したパーソナリティとコンテキストを設定する
- 状態管理:
output_keyと状態変数を使用してエージェント間で通信する - 階層の深さを制限: 保守性を高めるため、階層を浅く(2~3レベル)保つ
- 転送ロジックをテスト: さまざまなリクエストに対して、エージェントが正しいサブエージェントへ転送することを確認する
関連事項
- LLM エージェント - コアエージェント設定
- ワークフローエージェント - Sequential、Parallel、Loop エージェント
- セッション - セッション状態管理
前へ: ← ワークフローエージェント | 次へ: グラフエージェント →