multi-agenttutorielIArustv0.1.8

Construire des systèmes multi-agents avec ADK-Rust

Apprenez quand et comment utiliser différents modèles multi-agents—de la simple coordination à l'orchestration complexe basée sur des graphes—avec la sécurité de type et les performances de Rust.

·15 min de lecture·ADK-Rust v0.1.8

1. Introduction

Le Problème : L'IA Qui Atteint Ses Limites

Vous avez construit votre premier agent IA. C'est impressionnant — il peut répondre à des questions, résumer des documents, et peut-être même écrire du code. Mais la réalité vous rattrape :

"Pouvez-vous vérifier ma dernière facture et aussi m'aider à configurer le API?"

Votre agent peine. Il n'a pas été entraîné sur les systèmes de facturation ET la documentation développeur ET les flux de dépannage. Son prompt d'instruction fait déjà 2000 tokens pour tenter de tout couvrir. La qualité des réponses se dégrade.

C'est le plafond de l'agent unique. À mesure que votre application grandit, vous faites face à des compromis douloureux :

  • Prompts surchargés: Chaque nouvelle capacité signifie des instructions plus longues, une latence plus élevée et plus de confusion pour le modèle
  • Bon à tout faire: Un agent gérant la facturation, le support ET les ventes devient médiocre dans les trois domaines
  • Maintenance impossible: Modifier la logique de facturation ne devrait pas risquer de casser vos flux de support
  • Pas de spécialisation: Votre agent de calcul ne peut pas avoir un outil de calculatrice tandis que votre agent de recherche a la recherche web — ils partagent tout

La Solution : Des Agents Spécialisés Travaillant Ensemble

Les systèmes multi-agents résolvent ce problème en décomposant les tâches complexes en rôles spécialisés. Au lieu d'un généraliste débordé, vous créez des spécialistes ciblés :

  • Service client: Un coordinator dirige les utilisateurs vers des spécialistes de la facturation, du support technique ou des ventes — chacun avec une formation et des outils ciblés
  • Création de contenu: Un agent de recherche rassemble les faits, un writer élabore le récit, un éditeur peaufine — chaque agent maîtrise une compétence
  • Génération de code: Un planificateur conçoit l'architecture, un coder implémente, un réviseur détecte les bugs — différentes perspectives améliorent la qualité

Le résultat ? Chaque agent reste concentré, les invites restent gérables, et vous pouvez mettre à jour la logique de facturation sans toucher au support. Ce sont des microservices pour l'IA.

Ce que vous apprendrez

ADK-Rust fournit trois modèles progressivement puissants pour l'orchestration multi-agents. Ce tutoriel vous enseignera :

  • Quand utiliser chaque modèle en fonction de vos exigences
  • Comment les implémenter avec du code Rust prêt pour la production
  • Pourquoi les compromis architecturaux sont importants pour votre cas d'utilisation spécifique

2. Choisir le bon modèle

Avant de plonger dans le code, comprenons ce que chaque modèle offre :

ModèleIdéal pourNiveau de contrôleComplexité
CoordinateurTransferts de conversationLLM décideFaible
AgentToolTraitement des réponsesLe coordinateur traiteMoyenne
Graphe de superviseurFlux de travail complexesGestion complète de l'étatÉlevée

3. Modèle 1 : Le Coordinateur (Sous-agents)

🎯 Cas d'utilisation : Routage du service client

Vous construisez un bot de service client. Les utilisateurs peuvent poser des questions sur la facturation, demander de l'aide technique ou s'informer sur de nouvelles fonctionnalités. Chaque domaine nécessite des connaissances spécialisées, mais les utilisateurs ne devraient pas avoir besoin de savoir quel service contacter.

Le modèle Coordinateur utilise le transfert automatique d'agent. Lorsque vous ajoutez des sous-agents via .sub_agent(), ADK-Rust injecte un outil transfer_to_agent. Le LLM décide quand transférer en fonction de la conversation.

UtilisateurCoordinateurAchemine les requêtesvers les spécialistesFacturationSous-agentSupportSous-agentVentesSous-agenttransfer_to_agenttransfer_to_agenttransfer_to_agent

Caractéristiques Clés

  • Transfert fluide : L'utilisateur continue naturellement avec le spécialiste
  • Routage piloté par LLM : Le coordinator décide en fonction du contexte de la conversation
  • Continuité de la conversation : L'historique de la session est maintenu lors des transferts
  • Pas de traitement de réponse : Le spécialiste parle directement à l'utilisateur après le transfert

Implémentation

use adk_rust::prelude::*;
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.0-flash")?);

    // Specialist: Billing Agent
    // Clear description helps the coordinator know when to transfer
    let billing_agent = LlmAgentBuilder::new("billing_agent")
        .description(
            "Handles all billing questions: invoices, payments, refunds,              subscription plans, and account charges. Transfer here for              any money-related questions."
        )
        .instruction(
            "You are a billing specialist. Answer questions about invoices,              payments, and subscription plans. Be concise and accurate.              If asked about technical issues, suggest transferring to support."
        )
        .model(model.clone())
        .build()?;

    // Specialist: Technical Support Agent
    let support_agent = LlmAgentBuilder::new("support_agent")
        .description(
            "Provides technical support: troubleshooting, bug reports,              feature questions, and integration help. Transfer here for              any technical problems."
        )
        .instruction(
            "You are a technical support specialist. Help users troubleshoot              issues step by step. Ask clarifying questions when needed.              Be patient and thorough."
        )
        .model(model.clone())
        .build()?;

    // Coordinator: Routes to specialists
    let coordinator = LlmAgentBuilder::new("coordinator")
        .description("Customer service coordinator")
        .instruction(
            "You are a friendly customer service coordinator. Your job is to:\n             1. Greet users warmly\n             2. Understand their needs\n             3. Route to the right specialist:\n                - Billing questions → transfer to billing_agent\n                - Technical issues → transfer to support_agent\n             4. Handle general questions yourself\n\n             Always explain who you're connecting them with."
        )
        .model(model.clone())
        .sub_agent(Arc::new(billing_agent))
        .sub_agent(Arc::new(support_agent))
        .build()?;

    // Run with the built-in launcher
    Launcher::new(Arc::new(coordinator))
        .run()
        .await?;

    Ok(())
}

Exemple de conversation

Utilisateur: Bonjour, j'ai une question concernant ma facture

[coordinator]: Bonjour ! Je serais ravi de vous aider avec votre question de facturation. Permettez-moi de vous mettre en contact avec notre spécialiste de la facturation qui pourra vous assister.

Système: 🔄 Transfert vers : billing_agent

[billing_agent]: Bonjour ! Je suis le spécialiste de la facturation. Je peux vous aider avec les factures, les paiements et les questions d'abonnement. Que souhaitez-vous savoir concernant votre facture ?

Utilisateur: Pourquoi ai-je été facturé deux fois ce mois-ci ?

[billing_agent]: Je vais examiner cette double facturation pour vous...

✅ Quand utiliser le Coordinateur

  • L'utilisateur doit interagir directement avec les spécialistes
  • Les décisions de routage sont simples
  • Vous n'avez pas besoin de traiter les réponses des spécialistes
  • Le flux de conversation est linéaire (un spécialiste à la fois)

4. Modèle 2 : Agents comme outils (AgentTool)

🎯 Cas d'utilisation : Agrégation de connaissances

Vous construisez un assistant intelligent qui répond à des questions couvrant plusieurs domaines. Un utilisateur demande « Qu'est-ce que 15 % de 250, et pourquoi ce nombre est-il significatif dans l'histoire ? » Vous devez appeler un expert en mathématiques, puis un expert en anecdotes, et combiner leurs réponses.

Le modèle AgentTool encapsule les agents comme des outils appelables. Contrairement aux sous-agents, le coordinator invoque les spécialistes de manière programmatique et reçoit leurs réponses pour les traiter ou les combiner avant de répondre à l'utilisateur.

UtilisateurCoordinateurAppelle les agents comme outilsTraite les réponsesAgrège les résultatsRetourne à l'utilisateurExpert en mathématiquesAgentTool+ calculatriceExpert en anecdotesAgentToolConnaissances LLMChercheurAgentTool+ web_searchappel →← réponse

Différences clés par rapport au Coordinateur

Coordinateur (Sous-agents)

  • • Le spécialiste parle directement à l'utilisateur
  • • Un spécialiste à la fois
  • • Pas de traitement des réponses

AgentTool

  • • Le coordinateur reçoit les réponses
  • • Peut appeler plusieurs spécialistes
  • • Agrège et résume

Implémentation

use adk_agent::LlmAgentBuilder;
use adk_tool::{AgentTool, FunctionTool};
use adk_core::ToolContext;
use serde_json::{json, Value};
use std::sync::Arc;

// Calculator tool for the math agent
async fn calculator(
    _ctx: Arc<dyn ToolContext>,
    args: Value
) -> Result<Value, adk_core::AdkError> {
    let operation = args["operation"].as_str().unwrap_or("add");
    let a = args["a"].as_f64().unwrap_or(0.0);
    let b = args["b"].as_f64().unwrap_or(0.0);

    let result = match operation {
        "add" => a + b,
        "multiply" => a * b,
        "percent" => a * (b / 100.0),
        _ => return Err(adk_core::AdkError::Tool(
            format!("Unknown operation: {}", operation)
        )),
    };

    Ok(json!({ "result": result }))
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.5-flash")?);

    // Create calculator tool
    let calc_tool = FunctionTool::new(
        "calculator",
        "Performs arithmetic: add, multiply, percent.          Args: operation (string), a (number), b (number)",
        calculator,
    );

    // Math Expert agent - has its own tools
    let math_agent = LlmAgentBuilder::new("math_expert")
        .description(
            "A math expert that performs calculations. Use for any              math-related questions, percentages, or numerical analysis."
        )
        .instruction(
            "You are a math expert. Use the calculator tool for              calculations. Show your work step by step. Be precise."
        )
        .model(model.clone())
        .tool(Arc::new(calc_tool))
        .build()?;

    // Trivia Expert agent - uses LLM knowledge
    let trivia_agent = LlmAgentBuilder::new("trivia_expert")
        .description(
            "A trivia and history expert. Use for questions about              historical facts, pop culture, science facts, and trivia."
        )
        .instruction(
            "You are a trivia expert with vast knowledge across domains.              Answer questions accurately and include interesting related facts."
        )
        .model(model.clone())
        .build()?;

    // Wrap agents as tools with configuration
    let math_tool = AgentTool::new(Arc::new(math_agent))
        .skip_summarization(false)   // Summarize lengthy responses
        .forward_artifacts(true);     // Pass through any generated files

    let trivia_tool = AgentTool::new(Arc::new(trivia_agent))
        .skip_summarization(false);

    // Coordinator uses agents as tools
    let coordinator = LlmAgentBuilder::new("coordinator")
        .description("Smart assistant that combines expert knowledge")
        .instruction(
            "You are a helpful assistant with access to expert agents:\n             - math_expert: For calculations and math problems\n             - trivia_expert: For facts, history, and trivia\n\n             When questions span multiple domains, call multiple experts              and synthesize their responses into a cohesive answer."
        )
        .model(model)
        .tool(Arc::new(math_tool))
        .tool(Arc::new(trivia_tool))
        .build()?;

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

Exemple : Question multi-domaine

Utilisateur: Qu'est-ce que 15 % de 250, et ce nombre est-il significatif dans l'histoire ?

Système: // Le coordinateur appelle l'outil math_expert

[math_expert répond]: 15 % de 250 est 37,5

Système: // Le coordinateur appelle l'outil trivia_expert

[trivia_expert répond]: 37 et 38 sont moins remarquables historiquement, mais 37,5°C est la température du corps humain...

Système: // Le coordinateur synthétise

[coordinator]: 15% de 250 égale 37,5. Fait intéressant, 37,5°C (99,5°F) est proche de la température corporelle humaine moyenne de 37°C, ce qui en fait un nombre médicalement significatif !

✅ Quand utiliser AgentTool

  • Vous devez combiner les réponses de plusieurs experts
  • Le coordinateur doit résumer ou filtrer la sortie du spécialiste
  • Les spécialistes ont leurs propres outils (capacités imbriquées)
  • Vous voulez un contrôle programmatique sur l'invocation de l'agent

5. Modèle 3 : Le graphe superviseur

🎯 Cas d'utilisation : Pipeline de création de contenu

Vous construisez un système de création de contenu. Étant donné un sujet, vous devez : (1) le rechercher, (2) écrire un article, (3) ajouter des exemples de code. Le supervisor décide dynamiquement de l'ordre en fonction de la tâche, et les travailleurs peuvent revenir en arrière pour des révisions.

Le modèle de graphe superviseur utilise le système de workflow basé sur les graphes de ADK-Rust. Un agent supervisor achemine dynamiquement vers les travailleurs, avec une gestion complète de l'état et un support d'exécution cyclique.

STARTSuperviseurDécide du prochainagent travailleurChercheurTravailleurRédacteurTravailleurCodeurTravailleurENDretour de cycle"terminé" → finaliser → ENDÉtat Partagé• research_output• written_content• code_output

Pourquoi utiliser un graphe ?

  • Routage dynamique : Le superviseur décide du prochain travailleur en fonction de l'état actuel
  • Exécution cyclique : Les travailleurs peuvent revenir en arrière pour des itérations
  • État partagé : Tous les nœuds lisent/écrivent dans un objet d'état commun
  • Arêtes conditionnelles : Différents chemins basés sur les décisions de LLM
  • Limites de récursion : Empêcher les boucles infinies

Implémentation

use adk_agent::LlmAgentBuilder;
use adk_graph::{
    StateGraph,
    edge::{START, END},
    node::{AgentNode, ExecutionConfig, NodeOutput},
    state::State,
};
use adk_model::GeminiModel;
use serde_json::json;
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.0-flash")?);

    // Supervisor: Decides which worker should act next
    let supervisor = LlmAgentBuilder::new("supervisor")
        .description("Routes tasks to specialized workers")
        .instruction(
            "You are a task supervisor. Based on the task and work done so far,              decide who should work next.\n\n             Workers available:\n             - researcher: Gathers information and facts\n             - writer: Writes content based on research\n             - coder: Creates code examples\n\n             Respond with ONLY one word: 'researcher', 'writer', 'coder', or 'done'."
        )
        .model(model.clone())
        .build()?;

    // Workers with specialized roles
    let researcher = LlmAgentBuilder::new("researcher")
        .instruction("Research the topic. Provide key facts as bullet points.")
        .model(model.clone())
        .build()?;

    let writer = LlmAgentBuilder::new("writer")
        .instruction("Write engaging content based on the research provided.")
        .model(model.clone())
        .build()?;

    let coder = LlmAgentBuilder::new("coder")
        .instruction("Write clean, documented code examples for the topic.")
        .model(model.clone())
        .build()?;

    // Create AgentNodes with input/output mappers
    let supervisor_node = AgentNode::new(Arc::new(supervisor))
        .with_input_mapper(|state| {
            let task = state.get("task").and_then(|v| v.as_str()).unwrap_or("");
            let history = state.get("history")
                .and_then(|v| v.as_array())
                .map(|arr| arr.iter()
                    .filter_map(|h| h.get("agent").and_then(|a| a.as_str()))
                    .map(|s| format!("- {} completed", s))
                    .collect::<Vec<_>>()
                    .join("\n"))
                .unwrap_or_default();

            adk_core::Content::new("user").with_text(format!(
                "Task: {}\n\nWork completed:\n{}\n\nWho next?",
                task,
                if history.is_empty() { "None yet" } else { &history }
            ))
        })
        .with_output_mapper(|events| {
            let mut updates = std::collections::HashMap::new();
            for event in events {
                if let Some(content) = event.content() {
                    let text: String = content.parts.iter()
                        .filter_map(|p| p.text())
                        .collect();
                    let next = if text.to_lowercase().contains("researcher") {
                        "researcher"
                    } else if text.to_lowercase().contains("writer") {
                        "writer"
                    } else if text.to_lowercase().contains("coder") {
                        "coder"
                    } else {
                        "done"
                    };
                    updates.insert("next_agent".to_string(), json!(next));
                }
            }
            updates
        });

    // Build the graph
    let graph = StateGraph::with_channels(&[
        "task", "next_agent", "history",
        "research_output", "written_content", "code_output"
    ])
    .add_node(supervisor_node)
    .add_node(AgentNode::new(Arc::new(researcher)))
    .add_node(AgentNode::new(Arc::new(writer)))
    .add_node(AgentNode::new(Arc::new(coder)))
    // Finalize node compiles all outputs
    .add_node_fn("finalize", |ctx| async move {
        let research = ctx.get("research_output").and_then(|v| v.as_str());
        let content = ctx.get("written_content").and_then(|v| v.as_str());
        let code = ctx.get("code_output").and_then(|v| v.as_str());

        let result = format!(
            "=== FINAL OUTPUT ===\n\n{}\n\n{}\n\n{}",
            research.unwrap_or("No research"),
            content.unwrap_or("No content"),
            code.unwrap_or("No code")
        );
        Ok(NodeOutput::new().with_update("final_result", json!(result)))
    })
    // Graph structure
    .add_edge(START, "supervisor")
    .add_conditional_edges(
        "supervisor",
        |state| state.get("next_agent")
            .and_then(|v| v.as_str())
            .unwrap_or("done")
            .to_string(),
        [
            ("researcher", "researcher"),
            ("writer", "writer"),
            ("coder", "coder"),
            ("done", "finalize"),
        ],
    )
    // Workers cycle back to supervisor
    .add_edge("researcher", "supervisor")
    .add_edge("writer", "supervisor")
    .add_edge("coder", "supervisor")
    .add_edge("finalize", END)
    .compile()?
    .with_recursion_limit(15);  // Prevent infinite loops

    // Execute
    let mut input = State::new();
    input.insert("task".to_string(), json!("Create a guide about Rust error handling"));
    input.insert("history".to_string(), json!([]));

    let result = graph.invoke(input, ExecutionConfig::new("content-thread")).await?;
    println!("{}", result.get("final_result").and_then(|v| v.as_str()).unwrap_or(""));

    Ok(())
}

✅ Quand utiliser le graphe superviseur

  • L'ordre du workflow est dynamique et déterminé par LLM
  • Les workers peuvent avoir besoin d'itérer ou de revenir en arrière
  • Un état complexe doit être partagé entre les agents
  • Vous avez besoin de points de contrôle ou de workflows reprenables
  • La décomposition des tâches nécessite plusieurs étapes séquentielles

6. Comparaison des Modèles

CaractéristiqueCoordinateurAgentToolGraphe du Superviseur
L'utilisateur parle àSpécialiste directementCoordinateur uniquementSortie finale
Appels multi-agents❌ Un à la fois✅ Parallèle possible✅ Orchestré
Traitement des réponses
Workflows cycliques
État partagéSession uniquementSession uniquementÉtat complet du graphe
Complexité de la configuration🟢 Faible🟡 Moyenne🔴 Élevée

7. Conclusion

Les systèmes multi-agents vous permettent de créer des applications d'IA sophistiquées en combinant des agents spécialisés. Choisissez votre modèle en fonction de vos besoins :

  • Coordinateur: Rapide à configurer, idéal pour le routage du service client
  • AgentTool: Lorsque vous avez besoin de traiter ou de combiner des réponses
  • Graphe de superviseur: Flux de travail complexes, dynamiques et multi-étapes

🦀 Démarrer

Prêt à construire votre propre système multi-agents ?