マルチエージェントシステム

専門化されたエージェントをチームに組み合わせて、高度なアプリケーションを構築します。

エージェント階層の概要

Rendering architecture…

構築するもの

このガイドでは、コーディネーターが問い合わせを専門担当者に振り分ける カスタマーサービスシステム を作成します。

                        ┌─────────────────────┐
       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 に留まるはずです

転送に関する問題のデバッグ

転送が期待どおりに機能しない場合:

  1. エージェント名を確認する - 転送呼び出しで完全に一致している必要があります
  2. 説明を見直す - より具体的でキーワードを豊富に含むものにします
  3. 指示を明確にする - 転送するタイミングを明示します
  4. エッジケースをテストする - あいまいなリクエストを試して、ルーティングの動作を確認します
  5. 転送インジケーターを確認する - [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]
  • シームレスな引き継ぎ: 転送後、対象エージェントが直ちに応答
  • 会話履歴: エージェント間の転送全体でコンテキストを維持

転送のテスト

マルチエージェントシステムが正しく動作することを確認するには:

  1. エージェント名を確認: エージェントが応答するとき、角括弧内に表示されることを確認
  2. 転送インジケーターを確認: エージェントが引き継ぐときに転送インジケーター (🔄) が表示されることを確認
  3. 即時応答を確認: 再度プロンプトを送らなくても、対象エージェントから応答されることを確認
  4. さまざまなリクエストタイプをテスト: 適切にルーティングされることを確認
  5. エッジケースを確認: 存在しないエージェントへの転送などを確認

転送に関する問題のデバッグ

転送が機能しない場合:

  • サブエージェントが追加されていることを確認: .sub_agent() を使用していることを確認
  • エージェントの説明を確認: LLM は転送の判断にこれらを使用します
  • 指示を確認: 親エージェントが転送のタイミングについて言及していることを確認
  • エージェント名を確認: 転送呼び出しで完全に一致している必要があります
  • ロギングを有効化: イベントストリームで転送アクションを確認

ベストプラクティス

  1. 明確な説明: LLM が適切な転送判断を行えるよう、説明的なエージェント名と説明を記述する
  2. 具体的な指示: 各エージェントに、その役割に沿った明確で焦点を絞った指示を与える
  3. グローバル指示を使用: すべてのエージェントで一貫したパーソナリティとコンテキストを設定する
  4. 状態管理: output_key と状態変数を使用してエージェント間で通信する
  5. 階層の深さを制限: 保守性を高めるため、階層を浅く(2~3レベル)保つ
  6. 転送ロジックをテスト: さまざまなリクエストに対して、エージェントが正しいサブエージェントへ転送することを確認する

前へ: ← ワークフローエージェント | 次へ: グラフエージェント →

マルチエージェントシステム - ADK-Rust ドキュメント | ADK-Rust