Accueil / Articles

Un guichet d’assistance à cinq agents sur Gemini 3.1 et ADK : tri, recherche, action, QA et orchestrateur.

Créer un système de support client multi-agents avec Google ADK et Gemini 3.1 : transferts structurés, boucles de correction QA, Agent Engine ou Cloud Run, sessions et coûts.

Éditeur : REACTAPP.TOP19 min de lecture4349 mots

Sources: Building a Multi-Agent AI System with Gemini 3 and Google Cloud: From Single Agent to Orchestrated Intelligence

Imaginez une commande qui a mal tourné. Un client écrit que le mauvais article est arrivé, demande un remboursement et souhaite également que l’article correct soit envoyé le même jour. Derrière cette seule phrase se cachent trois tâches : vérifier la commande, traiter le retour et expédier rapidement un article de remplacement. Chacune nécessite ses propres données, sa propre politique et son propre processus d’action. Un bot de support isolé, avec une seule instruction complexe, peut gérer une question simple, mais il a tendance à échouer dès que plusieurs tâches sont mêlées. Il devient lent, fragile et sujet aux erreurs.

La solution réside dans une conception multi-agents : plusieurs spécialistes, chacun responsable d’une partie du flux de travail, avec un orchestrateur qui gère la circulation des tâches. Ce qui suit montre comment mettre en place ce système sur Google Cloud à l’aide des modèles Gemini 3.1 et du Google Agent Development Kit (ADK). Il explique comment définir des rôles d’agents aux frontières claires, comment un orchestrateur dirige les tâches, délègue des responsabilités et assemble les résultats, comment les agents échangent des informations entre eux, ainsi que, tout aussi important, quand un seul agent reste la meilleure option.

Quels sont les problèmes liés à un seul prompt trop long ?

Imaginez un agent de support qui a commencé avec de simples fonctions et a progressivement acquis de nouvelles capacités : la recherche dans une base de connaissances, un ton plus doux pour les clients mécontents, des vérifications automatiques. Son prompt système dépasse alors 3 000 tokens, et chaque ajout perturbe quelque chose qui existait déjà. Quatre symptômes apparaissent :

  • Fuite des contraintes. Les règles de remboursement s’appliquent désormais à des questions qui n’ont rien à voir avec les remboursements, de sorte qu’une simple demande concernant un produit reçoit une réponse basée sur la logique de retour.
  • Attention diluée. L’attention d’un modèle est limitée. Répartie sur des dizaines d’instructions, dont la plupart sont sans rapport avec la question posée, elle accorde moins d’importance à chacune.
  • Débogage impossible.Lorsqu’une réponse est incorrecte, il n’est pas clair laquelle des nombreuses instructions en est responsable.
  • Un seul parcours pour tout.Toute question est traitée selon le même processus, quel que soit son niveau de complexité.

En divisant le travail, chaque modèle dispose de son budget d’attention entier pour la tâche à accomplir. Il ne pense qu’à la politique de remboursement lorsqu’il traite des demandes de remboursement et uniquement au ton lorsqu’il examine un brouillon.

ADK a été conçu autour de cette idée. Son mécanisme AutoFlow gère les transferts entre spécialistes : lorsque l’orchestrateur dispose de sous-agents, ADK ajoute une fonction transfer_to_agent() et inscrit des descriptions de chaque spécialiste disponible dans son contexte. L’orchestrateur sait ainsi qui fait partie de son équipe et en quoi chaque membre excelle.

Structure du système

Au lieu d’une seule instruction, un orchestrateur central supervise un ensemble de sous-agents spécialisés. Les modèles n’ont pas besoin d’être identiques. Le meilleur raisonneur est affecté aux tâches où le jugement est essentiel (orchestration et révision de qualité), tandis que celui qui est le plus rapide et le moins coûteux est utilisé pour les tâches où la vitesse prime (triage) :

  • Orchestrateur : planifie et délègue les tâches. Gemini 3.1 Pro. Ses outils sont les agents spécialistes.
  • Agent de triage : analyse l’émotion et l’intention pour diriger les tâches. Gemini 3.1 Flash-Lite, soutenu par des classificateurs NLP.
  • Agent de recherche : collecte des faits et du contexte. Gemini 3.1 Flash, avec une recherche Vertex et une API CRM.
  • Agent d’action : effectue des transactions telles que les remboursements et les mises à jour. Gemini 3.1 Flash, avec des API de commande et de paiement.
  • Agent QA : vérifie les faits et le respect des politiques. Gemini 3.1 Pro, avec une base de connaissances sur les politiques.
  • Gemini 2.5 Flash reste un outil fiable pour les flux à un seul agent. Cependant, lorsque la qualité du raisonnement guide les décisions de routage, la gamme Gemini 3.1 (disponible généralement depuis le début de 2026) convient mieux, en particulier pour les rôles d’orchestrateur et de QA.

    Les composants Google Cloud impliqués :

    • Modèles Gemini 3.1 en tant que cœur de raisonnement, adaptés à l’utilisation d’outils.
    • Agent Development Kit en tant que framework gérant les transferts via AutoFlow.
    • Vertex AI Agent Engine en tant qu’environnement d’exécution géré pour déployer et échelonner les agents.
    • Firestore en tant qu’état de session partagé où les agents lisent et écrivent la mémoire des conversations.
    • Vertex AI Vector Search pour effectuer des recherches dans la base de connaissances.

    Mise en place d’ADK

    ADK fournit le cadre nécessaire, y compris le modèle de transfert permettant à un agent de transmettre efficacement l’appel à un autre. L’installation se fait en une seule ligne :

    pip install google-adk google-generativeai google-cloud-firestore pydantic
    

    Pydantic est mentionné explicitement car ADK s’appuie sur lui pour les schémas d’entrée et de sortie des agents, et en le spécifiant on assure la compatibilité des versions.

    La structure du projet doit être soigneusement planifiée dès le début. Les systèmes multi-agents rencontrent le plus souvent des problèmes aux points de connexion : un agent envoie un refund_id sous forme d’entier tandis que l’autre s’attend à une chaîne de caractères. Une structure simplifiée avec des schémas partagés permet d’éviter ce type de bug :

    support_agents/
    ├── main.py              # Entry point for Cloud Run
    ├── common/
    │   ├── __init__.py
    │   └── schemas.py       # Shared Pydantic models (the "handshake")
    ├── agents/
    │   ├── __init__.py
    │   ├── orchestrator.py
    │   ├── triage.py
    │   ├── research.py
    │   ├── action.py
    │   └── qa.py
    └── config.py            # Environment & project IDs
    

    Un hôpital constitue une analogie pertinente. L’orchestrateur, c’est l’infirmier de triage ; l’agent de recherche, c’est le technicien de laboratoire ; et l’agent d’intervention, c’est le chirurgien. Aucun d’eux ne parle au patient en même temps. Ils consultent et mettent à jour le dossier du patient, qui correspond ici à Firestore, et schemas.py représente le format de ce dossier sur lequel ils sont tous d’accord.

    L’agent de triage

    Le triage agit comme le contrôle aérien : il ne résout rien par lui-même et se contente de s’assurer que chaque demande atteint le bon spécialiste avec la bonne priorité.

    Le transfert de chaînes de caractères brutes entre les agents peut entraîner des erreurs. Un schéma Pydantic strict garantit que l’orchestrateur reçoive un objet Python validé, sans avoir à interpréter de texte brut. Le schéma passe en premier :

    # common/schemas.py
    from pydantic import BaseModel, Field
    
    class TriageResult(BaseModel):
        intents: list[str] = Field(description="List of detected customer goals")
        urgency: str = Field(pattern="^(low|medium|high|critical)quot;)
        sentiment: str = Field(description="Current emotional state of the user")
        required_agents: list[str] = Field(
            description="List of sub-agents needed (research, action, etc.)"
        )
    

    Puis l’agent qui le produit :

    # agents/triage.py
    from google.adk.agents import LlmAgent
    from common.schemas import TriageResult
    
    triage_agent = LlmAgent(
        name="triage_agent",
        model="gemini-3.1-flash-lite",  # Fast and cheap for classification
        instruction="""You are a triage specialist. Analyze the customer's
        message and categorize it accurately.
    
        Urgency rules:
        - critical: Active outages or safety threats
        - high: Frustrated users or financial issues > $100
        - medium: Standard requests needing action
        - low: General info/feedback
    
        You must NOT attempt to solve the problem. Only classify it.""",
        output_schema=TriageResult,  # Force structured JSON output
        output_key="triage_data"
    )
    

    Quatre choix de conception sont importants dans cette définition :

    • Une seule tâche. L’agent ne répond jamais à des questions ni n’exécute d’actions, il se contente de classer. Cela permet de garder les instructions courtes et d’améliorer la précision.
    • Sortie structurée. output_schema impose un JSON valide correspondant à TriageResult, et ADK le vérifie automatiquement via Pydantic.
    • Plusieurs intentions en même temps. Comme intents est une liste de chaînes de caractères, un message demandant à mettre à jour une adresse et également à vérifier la dernière facture fonctionne, ce qui échouerait avec un classificateur à une seule étiquette.
    • Transfert d’état. output_key="triage_data" enregistre le résultat dans l’état de la session, de sorte que l’orchestrateur peut le lire via session.state['triage_data'].

    L’agent de recherche

    L’agent de recherche est l’enquêteur. Il récupère des données brutes depuis Firestore et le pipeline de récupération, puis les synthétise en un résumé structuré destiné à l’agent d’exécution. Le schéma de ce résumé constitue le contrat entre ces deux rôles :

    # common/schemas.py (add to existing file)
    
    class ResearchBrief(BaseModel):
        order_status: str = Field(description="Current order state")
        customer_tier: str = Field(description="e.g. Gold, Standard")
        applicable_policy: str = Field(description="Relevant policy text")
        can_refund: bool = Field(description="Whether a refund is allowed")
        reasoning: str = Field(description="Why this conclusion was reached")
    

    Viennent ensuite ses outils et la définition de l’agent :

    # agents/research.py
    from google.adk.agents import LlmAgent
    from google.cloud import firestore
    from common.schemas import ResearchBrief
    
    db = firestore.Client()
    
    def lookup_order(order_id: str) -> dict:
        """Fetch real-time shipping status and line items for an order ID."""
        doc = db.collection('orders').document(order_id).get()
        if doc.exists:
            return doc.to_dict()
        return {"error": f"Order {order_id} not found"}
    
    def lookup_customer(customer_id: str) -> dict:
        """Retrieve customer profile, tier, and interaction history."""
        doc = db.collection('customers').document(customer_id).get()
        if doc.exists:
            return doc.to_dict()
        return {"error": f"Customer {customer_id} not found"}
    
    def search_knowledge_base(query: str) -> list:
        """Search product docs and company policies via RAG pipeline."""
        # Uses the RAG pipeline from our previous article
        from rag_pipeline import pipeline
        result = pipeline.query(query, top_k=5)
        return result['sources']
    
    research_agent = LlmAgent(
        name="research_agent",
        model="gemini-3.1-flash",
        instruction="""You are a research specialist.
        1. Extract identifiers (Order ID, Customer ID) from the triage data.
        2. Use tools to gather order history and relevant company policies.
        3. If policy info is missing, use search_knowledge_base.
    
        CRITICAL: Do not answer the customer. Output a formal ResearchBrief.""",
        tools=[lookup_order, lookup_customer, search_knowledge_base],
        output_schema=ResearchBrief,
        output_key="research_brief"
    )
    

    L’État se déplace sans code supplémentaire. Lorsque l’agent de recherche est lancé, ADK injecte triage_data provenant de l’étape précédente dans son contexte, de sorte qu’il sait déjà devoir rechercher la commande 12345 car le triage a identifié cette intention. Chaque agent s’appuie sur le travail du précédent et rien n’est fait deux fois. Un petit conseil de production : ADK enveloppe automatiquement les fonctions simples en objets FunctionTool, mais les envelopper explicitement avec from google.adk.tools import FunctionTool permet un suivi plus précis de la latence et une meilleure journalisation de l’utilisation des tokens dans Vertex AI Agent Engine.

    L’agent d’action

    Seul ce spécialiste dispose d’un accès en écriture aux API métier. Comme il effectue des opérations à haut risque, il bénéficie de schémas stricts et de règles de contrôle explicites. Le schéma de sortie est d’abord :

    # common/schemas.py (add to existing file)
    
    class ActionSummary(BaseModel):
        actions_taken: list[str] = Field(description="List of actions executed")
        refund_id: str | None = Field(default=None, description="Refund ID if issued")
        escalated: bool = Field(default=False)
        escalation_reason: str | None = Field(default=None)
    

    Puis l’agent lui-même, qui demande une confirmation humaine pour les opérations à haute valeur :

    # agents/action.py
    from google.adk.agents import LlmAgent
    from google.adk.tools import FunctionTool
    from common.schemas import ResearchBrief, ActionSummary
    
    def process_refund(order_id: str, amount: float, reason: str) -> dict:
        """Process a financial refund. Required: valid order_id and amount."""
        # In production: call your payment API
        return {
            "status": "processed",
            "refund_id": f"REF-{order_id}",
            "amount": amount
        }
    
    def create_replacement_order(
        order_id: str,
        item_id: str,
        shipping_speed: str
    ) -> dict:
        """Create a replacement order with specified shipping speed."""
        # In production: call your order management API
        return {
            "status": "created",
            "new_order_id": f"REPL-{order_id}",
            "estimated_delivery": "2 business days"
        }
    
    def escalate_to_human(reason: str, priority: str) -> dict:
        """Escalate to a human agent with full context."""
        return {
            "status": "escalated",
            "queue": "priority" if priority == "high" else "standard"
        }
    
    # Wrap refund tool with human confirmation for high-value transactions
    refund_tool = FunctionTool(
        func=process_refund,
        require_confirmation=True  # Pauses for human approval before executing
    )
    
    action_agent = LlmAgent(
        name="action_agent",
        model="gemini-3.1-flash",
        instruction="""You are an action specialist.
        Review the 'research_brief' in the session state.
    
        CRITICAL RULES:
        1. Only refund if 'can_refund' is True in the research brief
        2. If amount > $500, you MUST use escalate_to_human
        3. You must call tools for every action — do not just tell the user
           it happened
        4. If information is missing from the brief, escalate""",
        tools=[refund_tool, create_replacement_order, escalate_to_human],
        input_schema=ResearchBrief,   # Only sees research data, not raw chat
        output_schema=ActionSummary,
        output_key="action_result"
    )
    

    Deux paramètres assurent l’essentiel de la sécurité. require_confirmation existe car un système en production ne permet rarement à un agent d’émettre un remboursement uniquement sur la base d’instructions succinctes. Le flux de confirmation des outils d’ADK suspend l’exécution jusqu’à ce qu’une personne donne son accord, et l’orchestrateur voit un statut « en attente » qu’il peut rediriger vers le tableau de bord du personnel. input_schema est important car, sans lui, l’agent voit toute la conversation, y compris les échanges obsolètes. En restreignant les entrées au format ResearchBrief, on empêche l’intrusion de contextes périmés et on assure que les décisions se basent sur les données les plus récentes.

    Notez également que escalate_to_human est une règle métier délibérée et non un état de panne. L’orchestrateur peut voir précisément pourquoi l’agent s’est arrêté et transmettre le ticket à une personne, accompagné du rapport de recherche complet.

    L’agent QA

    Le contrôle qualité constitue la dernière étape. En plus de vérifier le libellé, il compare ce que l’agent d’exécution a fait avec ce qui était indiqué dans le brief de recherche, afin que l’action corresponde à la connaissance.

    Cette fonctionnalité s’appuie sur Gemini 3.1 Pro, car elle doit garder en mémoire l’ensemble de l’état de la conversation et détecter des contradictions subtiles, tâches où Pro surpasse systématiquement Flash. ADK lui permet également d’utiliser le mode de réflexion pour une auto-évaluation plus approfondie avant d’approuver une réponse. Le schéma de revue :

    # common/schemas.py (add to existing file)
    
    class QAReview(BaseModel):
        is_approved: bool = Field(description="True if the response meets all criteria")
        feedback: str = Field(description="Specific corrections if not approved")
        final_text: str = Field(description="The customer-facing message")
    

    Et la configuration de l’agent :

    # agents/qa.py
    from google.adk.agents import LlmAgent
    from google.adk.planners import BuiltInPlanner
    from google.genai.types import ThinkingConfig
    from common.schemas import QAReview
    
    qa_agent = LlmAgent(
        name="qa_agent",
        model="gemini-3.1-pro",
        instruction="""You are a senior QA auditor. Compare the 'action_result'
        against the 'research_brief' in the session state.
    
        CRITICAL CHECKS:
        1. Did the Action Agent refund the EXACT amount listed in the brief?
        2. Does the response cite the policy found during research?
        3. Is the tone empathetic but professional?
        4. Are ALL intents from the triage classification addressed?
        5. No unauthorized promises, discounts, or policy exceptions
    
        If any check fails, set is_approved to False with specific feedback.""",
        output_schema=QAReview,
        output_key="qa_review",
        # MEDIUM for routine QA; switch to HIGH for edge cases or audits
        planner=BuiltInPlanner(
            thinking_config=ThinkingConfig(thinking_level="MEDIUM")
        )
    )
    

    Le rejet ne met pas fin au processus. Lorsque qa_review.is_approved est faux, l’orchestrateur lit le champ feedback et renvoie à l’agent chargé de l’action ou de la recherche des corrections concrètes. Cet ensemble de corrections est ce qui distingue un agent en production d’un script de démonstration. Le processus est limité à deux cycles de révision, après lesquels le cas est transmis à une personne.

    L’orchestrateur

    L’orchestrateur ne mène ni de recherches ni d’actions. Il gère l’état du système et s’assure que les spécialistes interviennent dans un ordre logique. À partir de sub_agents, AutoFlow d’ADK ajoute l’outil transfer_to_agent(), et le LLM de l’orchestrateur sélectionne le prochain spécialiste en se basant sur ses instructions et sur l’état actuel de la session :

    # agents/orchestrator.py
    from google.adk.agents import LlmAgent
    from agents.triage import triage_agent
    from agents.research import research_agent
    from agents.action import action_agent
    from agents.qa import qa_agent
    
    orchestrator = LlmAgent(
        name="support_orchestrator",
        model="gemini-3.1-pro",  # Best reasoning for delegation decisions
        instruction="""You are the lead coordinator of a customer support team.
        Use the following specialists in order:
        1. triage_agent: to classify the intent and urgency
        2. research_agent: to gather order data and relevant policies
        3. action_agent: to execute the required operations
        4. qa_agent: to verify the final response
    
        Special cases:
        - If triage classifies as 'critical': bypass the chain and escalate
        - If QA returns is_approved=False: re-route to the responsible agent
          with the feedback (maximum 2 revision cycles, then escalate)
        - If any agent fails: escalate to human with full context
    
        You must never respond to the customer directly.
        Always route through the specialist pipeline.""",
        sub_agents=[triage_agent, research_agent, action_agent, qa_agent]
    )
    

    Le lancement

    ADK exécute les agents via un Runner, lequel suspend l’exécution chaque fois qu’un agent invoque un outil, puis redémarre l’agent une fois que le résultat est disponible :

    # main.py
    from google.adk.runners import Runner
    from google.adk.sessions import InMemorySessionService
    from agents.orchestrator import orchestrator
    
    APP_NAME = "customer_support"
    USER_ID = "system"
    
    # For production, replace with a Firestore-backed session service
    session_service = InMemorySessionService()
    
    runner = Runner(
        agent=orchestrator,
        app_name=APP_NAME,
        session_service=session_service
    )
    
    async def handle_customer_message(customer_id: str, message: str):
        """
        Entry point for customer messages.
        """
        session_id = f"support-{customer_id}"
    
        # Ensure session exists
        session = await session_service.get_session(
            app_name=APP_NAME, user_id=USER_ID, session_id=session_id
        )
        if not session:
            session = await session_service.create_session(
                app_name=APP_NAME, user_id=USER_ID, session_id=session_id
            )
    
        # run_async yields events as agents execute
        final_response = None
        async for event in runner.run_async(
            user_id=USER_ID,
            session_id=session_id,
            new_message=message
        ):
            if event.is_final_response():
                final_response = event
    
        return {
            "response": final_response.content.parts[0].text if final_response else None,
            "session_id": session_id
        }
    

    Pourquoi ne pas appeler directement les agents ? Le Runner gère l’ensemble du processus : il conserve les sessions, suit l’agent actif, traite les appels aux outils et enregistre chaque étape. Comme chaque agent écrit ses résultats sous une output_key, le Runner garantit que le résumé de la recherche est disponible lorsque l’agent d’exécution en a besoin, sans avoir à écrire manuellement de code supplémentaire. La traçabilité est également assurée automatiquement : chaque appel d’agent, utilisation d’outil et transfert est enregistré comme un événement, ce qui permet de résoudre les problèmes de débogage mentionnés précédemment. Lorsqu’un problème survient, le historique montre quel agent a pris quelle décision et pourquoi.

    Voies de déploiement

    Il existe deux façons de le déployer. Vertex AI Agent Engine propose un environnement géré qui nécessite presque aucune maintenance, tandis que Cloud Run offre un contrôle total sur le conteneur.

    Agent Engine

    C’est la solution à faible entretien, recommandée pour la plupart des configurations natives ADK. Il suffit de définir deux variables :

    PROJECT_ID=your-project-id
    LOCATION_ID=us-central1
    

    Puis déploiez avec une seule commande :

    adk deploy agent_engine \
      --project=$PROJECT_ID \
      --region=$LOCATION_ID \
      --display_name="Customer Support Team" \
      support_agents
    

    Agent Engine s’occupe de l’échelle, de la persistance des sessions et du journalage des traces tenant compte des agents. Il n’y a pas de Dockerfile, pas de code de session et aucune infrastructure à gérer.

    Cloud Run

    Choisissez cette option lorsque des bibliothèques système personnalisées, des fonctionnalités de réseau spécifiques comme VPC Service Controls, ou des composants non ADK sont nécessaires. L’exemple utilise Quart à la place de Flask. La syntaxe est presque identique, mais Quart gère nativement async/await, ce qui évite le mauvais usage de asyncio.run() consistant à démarrer un nouveau cycle d’événements pour chaque requête :

    # server.py
    from quart import Quart, request, jsonify
    from main import handle_customer_message
    
    app = Quart(__name__)
    
    @app.route("/support", methods=["POST"])
    async def support():
        data = await request.get_json()
        # No asyncio.run needed — Quart handles the event loop
        result = await handle_customer_message(
            data["customer_id"],
            data["message"]
        )
        return jsonify(result)
    
    @app.route("/health", methods=["GET"])
    async def health():
        return jsonify({"status": "healthy"})
    
    if __name__ == "__main__":
        app.run(host="0.0.0.0", port=8080)
    

    La définition du conteneur :

    # Dockerfile
    FROM python:3.11-slim
    
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt
    
    COPY . .
    EXPOSE 8080
    
    CMD ["python", "server.py"]
    

    Et la commande de déploiement :

    gcloud run deploy support-agents \
      --source . \
      --region us-central1 \
      --memory 4Gi \
      --cpu 2 \
      --allow-unauthenticated
    

    En bref, Cloud Run exige un effort de configuration modéré (un Dockerfile et une API), nécessite une gestion manuelle de l’état avec Firestore ou Redis, enregistre les journaux dans Cloud Logging standard et convient aux temps d’exécution et frameworks personnalisés. Agent Engine ne requiert qu’une seule commande, intègre des sessions par défaut, génère des traces spécifiques à l’agent et convient aux agents natifs ADK qui doivent s’échaler rapidement.

    Permettre aux conversations de persister

    L’exemple d’orchestrateur utilisait InMemorySessionService, ce qui est adapté aux tests mais fait perdre toutes les données lors du redémarrage du processus. En environnement de production, un service durable est nécessaire. ADK en propose deux.

    Avec Agent Engine, les sessions sont gérées automatiquement via le service de session Vertex AI :

    # config.py
    from google.adk.sessions import VertexAiSessionService
    
    session_service = VertexAiSessionService(
        project="your-project-id",
        location="us-central1",
        agent_engine_id="your-agent-engine-id"  # From adk deploy output
    )
    

    Agent Engine résume également les historiques longs et nettoie automatiquement derrière lui, ce qui élimine le besoin de code Firestore.

    Pour Cloud Run, DatabaseSessionService fonctionne avec un backend PostgreSQL ou SQLite :

    # config.py
    from google.adk.sessions import DatabaseSessionService
    
    # PostgreSQL for production
    session_service = DatabaseSessionService(
        db_url="postgresql+asyncpg://user:pass@host:5432/agents"
    )
    
    # Or SQLite for development
    session_service = DatabaseSessionService(
        db_url="sqlite+aiosqlite:///sessions.db"
    )
    

    Il crée ses tables lors du premier démarrage. En le plaçant dans le Runner, l’état de session survit aux redémarrages.

    Comment l’état est transféré entre les agents

    Aucune appel manuel à get() et set() n’est nécessaire. Le output_key de chaque agent écrit automatiquement dans l’état de session, et les agents suivants le lisent à partir de là :

    triage_agent (output_key="triage_data")
        ↓ writes to session.state["triage_data"]
    research_agent (reads triage_data, output_key="research_brief")
        ↓ writes to session.state["research_brief"]
    action_agent (input_schema=ResearchBrief, output_key="action_result")
        ↓ writes to session.state["action_result"]
    qa_agent (reads action_result + research_brief, output_key="qa_review")
    

    The Runner conserve cet état complet vers n’importe quel backend configuré, que ce soit Firestore, PostgreSQL ou Agent Engine.

    Trois habitudes permettent de maintenir une mémoire saine :

    • Résumer. Stocker des résultats condensés plutôt que des réponses API brutes, afin d’éviter que la fenêtre de contexte ne se remplisse inutilement.
    • Réexpirer les sessions. Les sessions de support devraient avoir une durée de vie de 24 à 48 heures, ce qui permet de garder la base de données plus petite et plus sécurisée.
    • Protéger les données personnelles. Les informations sensibles brutes telles que les numéros d’identité nationale ou les mots de passe ne doivent pas figurer dans l’état partagé ; elles doivent donc être masquées avant toute écriture dans la session.

    Lorsque un seul agent reste la meilleure solution

    Le système multi-agent n’est pas automatiquement supérieur. Une façon pratique de décider :

    • FAQ simple ou vérification d’état : un seul agent. Latence et coût les plus faibles, aucune logique complexe requise.
    • Enrichissement des données avec trois à cinq recherches : un seul agent. Une seule appel à un LLM suffit pour utiliser correctement les outils de base.
    • Flux de travail réglementés : plusieurs agents. L’isolation sépare la logique des actions de l’entrée utilisateur, ce qui limite les injections de prompts.
    • Affectation ambiguë : plusieurs agents. Un agent de tri dédié, spécialisé dans l’interprétation des intentions, réduit les conflits de règles.
    • Actions à haut risque dépassant environ 500 $ : plusieurs agents. Cela permet une double vérification par un agent de contrôle qualité avant toute utilisation d’une API.
    • Exigences en matière d’audit : plusieurs agents. Les entrées et sorties de chaque agent sont enregistrées séparément.

    Règle empirique : lorsque la longueur d’un prompt destiné à un seul agent approche de 3 000 tokens, c’est-à-dire au moment où apparaissent les problèmes de contraintes excessives et de dilution de l’attention, ou lorsque différentes étapes du flux de travail nécessitent des outils distincts, il est temps de diviser l’agent en plusieurs.

    Pièges courants

    • L’agent tout-en-un. Les performances chutent dès qu’un agent dispose de dix outils ou plus, car les modèles passent leur temps à choisir l’outil approprié plutôt qu’à comprendre la demande. Il convient de diviser l’agent en fonction de ses capacités : un agent chargé des remboursements, du transport et des questions juridiques devient alors trois spécialistes.
    • Contexte trop volumineux. Si chaque agent consulte l’intégralité de la conversation de dix échanges, les coûts en tokens explosent. Il faut compresser l’état du système afin que le tri ne prenne en compte que l’intention détectée et non toute la conversation.
  • Transferts incessants. L’agent A envoie le travail à B, qui le renvoie à A, ce qui crée un cycle infini d’utilisation des tokens. Il convient de définir un compteur maximal de transferts ; deux cycles avant une escalade humaine s’avèrent suffisants.
  • Bordures floues. Deux agents s’occupent de la même tâche, ou une tâche se retrouve entre eux. Il est nécessaire de définir des schémas input_schema et output_schema clairs, sans chevauchement des responsabilités.
  • Ignooration de la latence. Un pipeline séquentiel ajoute de 4 à 6 secondes, contre 1 à 2 secondes pour un seul agent. Le ParallelAgent d’ADK permet de distribuer les tâches de manière indépendante, et l’exécution en parallèle des vérifications de recherche et de conformité peut réduire la latence totale de 30 à 40 %.
  • À quoi s’attendre

    Les systèmes multi-agents sacrifient une partie de la latence pour assurer la fiabilité sur des tâches complexes. Les benchmarks publiés ainsi que l’expérience du secteur mettent en évidence ces avantages :

    • Précision sur des tâches complexes. Diviser les problèmes à plusieurs étapes en sous-tâches ciblées donne généralement de meilleurs résultats que les instructions monolithiques, et la documentation d’ADK indique des taux de réussite plus élevés lorsque les agents ont des missions précises.
    • Faillites contenues. Lorsqu’un composant tombe en panne, les dommages restent limités à cet agent et n’entravent pas toute la réponse.
    • Auditable. Des journaux distincts pour chaque agent facilitent le débogage et le respect des normes.

    Les coûts sont tout aussi réels :

    • Latence. Un pipeline composé de cinq agents nécessite environ 3 à 8 secondes, contre 1 à 2 pour un seul agent. Les appels séquentiels constituent le principal goulot d’étranglement.
  • Dépenses liées à l’API. Cinq agents (un orchestrateur et quatre spécialistes) coûtent environ quatre à cinq fois plus qu’un agent par requête, bien que le mélange de modèles permette de limiter cette augmentation. Le tri via Flash-Lite coûte 0,25 $ par million de tokens d’entrée, tandis que la version Pro à 2 $ par million est réservée à l’orchestration et aux tests de qualité, ce qui maintient le coût total par interaction en dessous d’un centime.
  • Tokens de réflexion. Le mode de réflexion étendue de Pro (thinking_level="HIGH") peut ajouter environ 20 000 tokens, facturés au taux de 12 $ par million de tokens de sortie. Une revue approfondie de qualité coûte donc environ 0,24 $ rien que pour les tokens de réflexion. Utilisez le niveau « HIGH » de manière sélective, car la plupart des vérifications de qualité suffisent avec le niveau « MEDIUM ».
  • Complexité. Il y a davantage d’éléments à surveiller. Ce système est avantageux pour les workflows complexes, mais superflu pour les plus simples.
  • Pour une estimation approximative du coût mensuel avec 100 000 requêtes complexes :

    • Orchestration sur Gemini 3.1 Pro : environ 80 $
    • Triage avec Flash-Lite : environ 20 $
    • Recherche avec Flash : environ 40 $
    • Actions avec Flash : environ 40 $
    • Vérification de qualité avec un niveau de réflexion MEDIUM sur Pro : environ 100 $
    • Cloud Run avec 2 cœurs CPU et 2 Go de mémoire : environ 120 $
    • Sessions PostgreSQL : environ 30 $
    • Recherche vectorielle, partagée avec une configuration à un seul agent : environ 100 $
    • Total : environ 530 $ par mois

    Le mélange de modèles constitue le véritable levier de réduction des coûts. Seulement environ 30 % des appels (orchestration et vérification de qualité) sont facturés au tarif Pro, tandis que le reste s’exécute sur Flash ou Flash-Lite, à un huitième à un cinquantième du prix par token. Le coût unitaire par interaction complexe est d’environ un demi-centime. Ces chiffres proviennent des tarifs publiés par Google Cloud, et les factures réelles varient en fonction de la complexité des requêtes, du niveau de réflexion et de la profondeur des appels des agents.

    Ordre d’adoption

    La séquence logique consiste à commencer avec un seul agent, à ajuster sa logique, à l’appuyer par des recherches, et à le diviser en spécialistes uniquement lorsque la prompt du système devient trop longue ou que les tâches sont trop variées pour qu’un seul modèle puisse les gérer de manière fiable. Chaque couche s’appuie sur celle qui se trouve en dessous.

    Le moment des microservices pour les agents

    La conception multi-agents ne rend pas l’IA plus complexe. Elle rend les flux de travail complexes prévisibles, tout comme les microservices ont remplacé les systèmes monolithiques : une seule prompt devient un ensemble distribué de composants, chacun chargé d’une tâche spécifique. Demander à un seul agent de trier, de rechercher, d’agir et d’auditer en même temps, c’est comme demander à une seule personne d’être à la fois réceptionniste, analyste principal, technicien et auditeur. Cela fonctionne dans une entreprise de une personne, mais échoue dans une grande entreprise.

    En confiant à chaque agent une tâche bien définie, des outils spécialisés ainsi qu’une couche de mémoire partagée, on obtient des résultats précis, car des instructions ciblées réduisent les écarts entre tâches non liées ; des systèmes faciles à déboguer, car le spécialiste en difficulté est visible ; et des solutions économiques, puisque l’exécution de cinq agents sur Gemini 3.1 coûte environ un demi-cent par interaction complexe, soit bien moins qu’une minute de travail d’un agent humain.

    Cette architecture laisse également place à l’évolution. Grâce au protocole Agent-to-Agent (A2A), un standard ouvert géré par la Linux Foundation, un système de ce type pourrait éventuellement négocier avec des agents gérés par des fournisseurs ou des partenaires, par exemple un agent de support communiquant directement avec l’agent d’un prestataire de livraison pour résoudre un colis perdu, sans intervention humaine.

    Pour l’instant, le conseil est simple : construisez d’abord un seul agent, puis décomposez-le lorsque les instructions deviennent trop longues ou que les tâches sont trop diverses.