Systèmes multi-agents

Créez des applications sophistiquées en composant des agents spécialisés au sein d’équipes.

Vue d’ensemble de la hiérarchie des agents

Rendering architecture…

Ce que vous allez créer

Dans ce guide, vous allez créer un Système de service client dans lequel un coordinateur achemine les demandes vers des spécialistes :

                        ┌─────────────────────┐
       User Query       │                     │
      ────────────────▶ │    COORDINATOR      │
                        │  "Route to expert"  │
                        └──────────┬──────────┘
                                   │
                   ┌───────────────┴───────────────┐
                   │                               │
                   ▼                               ▼
        ┌──────────────────┐            ┌──────────────────┐
        │  BILLING AGENT   │            │  SUPPORT AGENT   │
        │                  │            │                  │
        │  💰 Payments     │            │  🔧 Tech Issues  │
        │  📄 Invoices     │            │  🐛 Bug Reports  │
        │  💳 Subscriptions│            │  ❓ How-To       │
        └──────────────────┘            └──────────────────┘

Concepts clés :

  • Coordinateur - Reçoit toutes les demandes et décide qui les traite
  • Spécialistes - Agents spécialisés qui excellent dans des domaines précis
  • Transfert - Passage fluide du coordinateur au spécialiste

TeamSpec portable

Utilisez TeamSpec lorsque la même équipe doit s’exécuter de manière identique dans les projets générés, CLI, l’aperçu Studio et les déploiements Enterprise. La spécification est constituée de données sérialisables ; compile associe chaque nom déclaré à toute implémentation Agent existante et renvoie une racine CompiledTeam exécutable. Cela n’ajoute pas de nouveau type d’agent atomique.

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),
])?;

Pour utiliser directement la crate, activez la fonctionnalité team-tools de adk-agent lorsque la spécification contient Delegate. Avec la crate parapluie, activez tools (ou le niveau standard). Les spécifications utilisant uniquement des transferts se compilent avec l’ensemble de fonctionnalités de base adk-agent.

RelationComportement de contrôleMécanisme abaissé
HandoffL’appelant transfère le contrôle et ne reprend pas l’exécutionRunner transfer_to_agent
DelegateL’appelant invoque un membre, reçoit son résultat et reprend l’exécutionAgentTool

Les cibles sont exactes pour chaque membre. Le compilateur rejette les membres et arêtes en double, les points de terminaison manquants, les cycles, les membres inaccessibles, les limites invalides, les liaisons d’agents manquantes et les liaisons supplémentaires. Les arêtes de délégation peuvent transporter des schémas d’entrée/sortie JSON, une projection du contexte et de l’historique, des clés d’état exactes, des exigences de délai d’expiration et d’approbation, un comportement de nouvelle tentative/de repli, ainsi qu’un disjoncteur. Les arêtes de transfert prennent en charge une nouvelle tentative/un repli de démarrage borné ; les champs réservés à la délégation sont rejetés plutôt que d’être ignorés silencieusement. Les replis doivent être déclarés comme des arêtes distinctes ; la validation n’élargit jamais leur autorité.

La politique applicable à toute l’équipe borne la profondeur des transferts et délégations, la concurrence, les événements, les appels au modèle et aux outils, les jetons, le coût, le nombre de relations et le temps écoulé. Les règles de terminaison peuvent arrêter l’exécution en cas d’escalade, lorsqu’un auteur final exact est atteint ou lorsqu’un marqueur textuel est rencontré. Chaque événement contient un reçu d’exécution sérialisable dans __adk_team_execution_v1, notamment la liste des membres figée, les arêtes causales de délégation/transfert, l’utilisation agrégée et l’état terminal. Utilisez CompiledTeam::execution_snapshot pour l’inspecter, puis restore_execution_snapshot avant de reprendre avec le même identifiant d’invocation. Un transfert actif reprend au niveau de sa cible figée. Une délégation non résolue n’est pas automatiquement rejouée, car le contrat générique Agent ne peut pas prouver qu’un appel d’outil est idempotent ; l’exécution renvoie à la place une erreur exploitable. Reprenez cette délégation via son propre mécanisme de point de contrôle ou échouez/redémarrez explicitement l’opération.

Pour les architectures réutilisables, TeamArchitectureTemplate réduit les préréglages de superviseur, de routeur et hiérarchiques à des données TeamSpec explicites. WorkflowArchitectureTemplate compile les préréglages séquentiels, parallèles, de répartition/agrégation et de boucle de révision bornée à partir d’agents de workflow existants. La résolution fondée sur les capacités est disponible via TeamAgentRegistry ; la sélection est déterministe, peut exiger des étiquettes d’état, de version, de condensat et de confiance, et la liste retenue est figée dans le reçu d’exécution.

Capacités du plan d’exécution

La compilation vérifie le AgentCapabilities déclaré par l’agent source. Une source déléguée doit consommer les outils d’exécution associés à l’invocation, une source de transfert doit émettre des transferts, et un délégué approuvé doit prendre en charge la confirmation d’appel exact. LlmAgent et CodeActAgent déclarent leurs capacités d’exécution prises en charge ; les agents personnalisés conservent des valeurs par défaut prudentes et peuvent remplacer capabilities().

Les équipes conservent le comportement ADK standard : les plugins Runner, les rappels d’agent/modèle/outil, les garde-fous, l’annulation, l’identité de la requête, la mémoire, les artefacts, les sessions et la persistance des événements continuent d’envelopper les agents membres existants. compile_with_hooks ajoute des rappels asynchrones TeamLifecycleHook ordonnés pour les limites de l’équipe, du membre et de la relation exacte. Les hooks peuvent observer la réussite ou l’échec, ou refuser une opération en attente sans élargir sa cible. Runner appelle également le hook asynchrone govern_transfer de la racine immédiatement avant un transfert validé.

Les spans de traçage natifs team.run, team.member.run et team.relationship.execute transportent l’identité de l’équipe, du membre et de l’arête, l’état et la durée de la relation via l’exportateur adk-telemetry normal. Les échecs d’exécution utilisent des codes structurés stables tels que agent.team.policy_denied, agent.team.budget_exceeded et agent.team.resume_unsafe.

Les contrats de délégation distinguent les lectures d’état (stateKeys) des écritures (stateWriteKeys). stateMerge: rejectConflicts rejette une écriture enfant lorsque la valeur parente a changé pendant la délégation, et artifactPrefixes limite les noms d’artefacts. Le AgentTool autonome expose les mêmes contrôles. Sa valeur par défaut reste la priorité au dernier écrivain pour assurer la compatibilité ; les relations d’équipe utilisent par défaut le rejet des conflits.

CompiledTeam::resume_plan décrit l’unique action suivante sûre après la restauration d’un reçu : coordinateur, cible de transfert, délégué prenant en charge les points de contrôle, ou délégation non sûre avec arrêt en cas d’échec. validate_team_replay vérifie l’intégrité causale du reçu, tandis que analyze_execution rend compte de la couverture de la topologie sans fournisseur, des échecs et de la profondeur causale maximale pour les tests et les pipelines d’évaluation.

BlackboardSpec couvre la discussion de groupe régie en rotation et pilotée par sélecteur. Elle limite le nombre de tours et la projection de la transcription, et les transitions du sélecteur utilisent des listes d’autorisation exactes. La composition sub_agent existante reste prise en charge pour assurer la compatibilité ascendante, mais il s’agit d’un arbre en mémoire plutôt que d’une définition d’équipe portable.


Démarrage rapide

1. Créer votre projet

cargo new multi_agent_demo
cd multi_agent_demo

Ajoutez les dépendances à Cargo.toml :

[dependencies]
adk-rust = "2.1.0"
tokio = { version = "1", features = ["full"] }
dotenvy = "0.15"

Créez .env avec votre clé API :

echo 'GOOGLE_API_KEY=your-api-key' > .env

2. Exemple de service client

Voici un exemple complet et fonctionnel :

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(())
}

Interaction d’exemple :

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...

Fonctionnement du transfert entre plusieurs agents

Vue d’ensemble

Lorsque vous ajoutez des sous-agents à un agent parent, le LLM acquiert la capacité de transférer le contrôle :

                    ┌─────────────────────┐
    User Message    │                     │
   ─────────────────▶    COORDINATOR      │
                    │                     │
                    └──────────┬──────────┘
                               │
           "This is a billing question..."
                               │
              ┌────────────────┴────────────────┐
              │                                 │
              ▼                                 ▼
   ┌──────────────────┐              ┌──────────────────┐
   │  billing_agent   │              │  support_agent   │
   │  💰 Payments     │              │  🔧 Tech Issues  │
   │  📄 Invoices     │              │  🐛 Bug Reports  │
   └──────────────────┘              └──────────────────┘

Déroulement du transfert, étape par étape

Voici exactement ce qui se produit lorsqu’un utilisateur pose une question de facturation :

┌──────────────────────────────────────────────────────────────────────┐
│ 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!                  │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘

Ce qui permet son fonctionnement

ComposantRôle
.sub_agent()Enregistre les spécialistes sous le parent
transfer_to_agent outilInjecté automatiquement lorsque des sous-agents existent
Descriptions des agentsAide LLM à décider quel agent gère quoi
ExécuteurDétecte les événements de transfert et invoque l’agent cible
Session partagéeÉtat et historique conservés lors des transferts

Avant et après l’ajout de sous-agents

Sans sous-agents – Un seul agent s’occupe de tout :

User ──▶ coordinator ──▶ Response (handles billing AND support)

Avec des sous-agents – Des spécialistes gèrent leur domaine :

User ──▶ coordinator ──▶ billing_agent ──▶ Response (billing expert)
                    ──▶ support_agent ──▶ Response (tech expert)

Systèmes multi-agents hiérarchiques

Pour les scénarios complexes, vous pouvez créer des hiérarchies à plusieurs niveaux. Chaque agent peut avoir ses propres sous-agents, formant ainsi un arbre :

Visuel : équipe de contenu à 3 niveaux

                    ┌─────────────────────┐
                    │  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 │
   └──────────────────┘              └──────────────────┘

Comment les requêtes descendent dans la hiérarchie

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                                │
└─────────────────────────────────────────────────────────────┘

Exemple de code complet

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(())
}

Hiérarchie des agents :

project_manager
└── content_creator
    ├── researcher
    └── writer

Exemples de requêtes :

  • « Créer un article de blog sur l’IA dans le domaine de la santé » → PM → Créateur de contenu → Rédacteur
  • « Faire des recherches sur les véhicules électriques » → PM → Créateur de contenu → Chercheur

Configuration des sous-agents

Ajoutez des sous-agents à n’importe quel LlmAgent à l’aide de la méthode de construction sub_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()?;

Points clés :

  • Chaque agent peut avoir plusieurs sous-agents
  • Les sous-agents peuvent avoir leurs propres sous-agents (hiérarchies à plusieurs niveaux)
  • Les noms des agents doivent être uniques au sein de la hiérarchie
  • Les descriptions aident LLM à décider vers quel agent transférer la requête

Rédiger des instructions de transfert efficaces

Pour réussir les transferts entre agents, fournissez des instructions et des descriptions claires :

Instructions de l’agent parent

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()?;

Descriptions des sous-agents

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()?;

Bonnes pratiques :

  • Utilisez des noms d’agents descriptifs qui indiquent clairement leur objectif
  • Rédigez des descriptions détaillées – LLM les utilise pour décider des transferts
  • Incluez des mots-clés spécifiques dans les descriptions, correspondant aux requêtes probables des utilisateurs
  • Donnez des règles de délégation claires dans les instructions de l’agent parent
  • Utilisez une terminologie cohérente dans les descriptions des agents

Tester votre système multi-agents

Exécuter les exemples

cargo run --manifest-path examples/tier_examples/enterprise/Cargo.toml --bin 14-enterprise-multi-agent

Exemples de requêtes de test

Service client :

  • « J’ai une question concernant ma dernière facture » → Doit être dirigé vers billing_agent
  • « L’application ne cesse de planter » → Doit être dirigé vers support_agent
  • « Comment mettre à niveau mon forfait ? » → Doit être dirigé vers billing_agent
  • « Bonjour, j’ai besoin d’aide » → Doit rester avec coordinator pour clarification

Hiérarchique :

  • « Créer un article de blog sur l’IA dans le domaine de la santé » → PM → Content Creator → Writer
  • « Faire des recherches sur l’histoire des véhicules électriques » → PM → Content Creator → Researcher
  • « Quel est l’état d’avancement de nos projets actuels ? » → Doit rester avec project_manager

Débogage des problèmes de transfert

Si les transferts ne fonctionnent pas comme prévu :

  1. Vérifiez les noms des agents - Ils doivent correspondre exactement dans les appels de transfert
  2. Examinez les descriptions - Rendez-les plus spécifiques et riches en mots-clés
  3. Clarifiez les instructions - Indiquez explicitement quand effectuer un transfert
  4. Testez les cas limites - Essayez des demandes ambiguës pour observer le comportement du routage
  5. Recherchez les indicateurs de transfert - [Agent: name] indique quel agent répond

Instruction globale

Utilisation de base

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()?;

Instruction globale et instruction de l’agent

  • Instruction globale : appliquée à tous les agents de la hiérarchie, elle définit la personnalité et le contexte généraux
  • Instruction de l’agent : spécifique à chaque agent, elle définit son rôle et son comportement particuliers

Les deux instructions sont incluses dans l’historique de la conversation, l’instruction globale apparaissant en premier.

Instructions globales dynamiques

Pour des scénarios plus avancés, vous pouvez utiliser un fournisseur d’instructions globales qui calcule l’instruction dynamiquement :

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()?;

Injection de variables d’état

Les instructions globales et celles des agents prennent en charge l’injection de variables d’état à l’aide de la syntaxe {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()?;

Le framework injecte automatiquement les valeurs de l’état de la session dans les modèles d’instructions.

Modèles multi-agents courants

Modèle coordinateur/dispatcher

Un agent central achemine les requêtes vers des sous-agents spécialisés :

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()?;

Exemple de conversation :

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...

Points clés :

  • Le coordinateur analyse la requête et la transfère à l’agent de facturation
  • L’agent de facturation répond immédiatement au cours du même tour
  • Les messages suivants continuent avec l’agent de facturation
  • Les indicateurs de transfert (🔄) indiquent quand les transferts ont lieu

Décomposition hiérarchique des tâches

Hiérarchies à plusieurs niveaux pour décomposer les tâches complexes :

// 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()?;

Combinaison avec des agents de workflow

Les systèmes multi-agents fonctionnent bien avec les agents de workflow (séquentiels, parallèles, en boucle) :

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()?;

Communication entre les agents

Les agents d’une hiérarchie communiquent par l’intermédiaire de l’état de session partagé :

// 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()?;

La configuration output_key enregistre automatiquement la réponse finale d’un agent dans l’état de la session, la rendant disponible pour les agents suivants.

Transfert de l’état et des artefacts AgentTool

Lors de l’utilisation de AgentTool pour encapsuler des agents en tant qu’outils, les modifications d’état et les artefacts des sous-agents sont automatiquement transférés au contexte parent :

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 exécute les sous-agents en mode non diffusé (StreamingMode::None) en interne, afin que le sous-agent accumule l’intégralité de sa réponse avant de la renvoyer au parent. Cela évite les problèmes où des fragments de diffusion partiels pourraient produire des résultats vides.

Cela permet un flux de données fluide entre les agents parent et enfant lors de l’utilisation du modèle AgentTool.

Exécution de systèmes multi-agents

Utilisation du lanceur

Le Launcher offre un moyen simple d’exécuter et de tester des systèmes multi-agents :

use adk_rust::Launcher;

let coordinator = /* your multi-agent setup */;

Launcher::new(Arc::new(coordinator))
    .run()
    .await?;

Modes d’exécution :

# 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

Fonctionnalités :

  • Indicateurs d’agent : indique quel agent répond [Agent: coordinator]
  • Visualisation des transferts : affiche les événements de transfert 🔄 [Transfer requested to: billing_agent]
  • Passages transparents : l’agent cible répond immédiatement après le transfert
  • Historique de la conversation : conserve le contexte lors des transferts entre agents

Tester les transferts

Pour vérifier que votre système multi-agent fonctionne correctement :

  1. Vérifiez que les noms des agents apparaissent entre crochets lorsqu’ils répondent
  2. Recherchez les indicateurs de transfert (🔄) lorsque les agents passent la main
  3. Vérifiez les réponses immédiates des agents cibles, sans nouvelle invite
  4. Testez différents types de requêtes pour garantir un routage approprié
  5. Vérifiez les cas limites, comme le transfert vers des agents inexistants

Déboguer les problèmes de transfert

Si les transferts ne fonctionnent pas :

  • Vérifiez que les sous-agents sont ajoutés via .sub_agent()
  • Vérifiez les descriptions des agents : le LLM les utilise pour décider des transferts
  • Examinez les instructions : le parent doit indiquer quand effectuer un transfert
  • Vérifiez les noms des agents : ils doivent correspondre exactement dans les appels de transfert
  • Activez la journalisation pour voir les actions de transfert dans le flux d’événements

Bonnes pratiques

  1. Descriptions claires : rédigez des noms et des descriptions d’agents explicites pour aider le LLM à prendre de bonnes décisions de transfert
  2. Instructions précises : donnez à chaque agent des instructions claires et ciblées pour son rôle
  3. Utilisez l’instruction globale : définissez une personnalité et un contexte cohérents pour tous les agents
  4. Gestion de l’état : utilisez output_key et des variables d’état pour la communication entre agents
  5. Limitez la profondeur de la hiérarchie : gardez des hiérarchies peu profondes (2 à 3 niveaux) pour faciliter la maintenance
  6. Testez la logique de transfert : vérifiez que les agents transfèrent les différentes requêtes vers les sous-agents appropriés

Précédent : ← Agents de workflow | Suivant : Agents de graphe →

Systèmes multi-agents - Documentation ADK-Rust | ADK-Rust