Outils et état de session
Installer et configurer
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt # google-adk==2.10.0, python-dotenv, pytest
cp .env.example .env # puis collez votre clé d'API Gemini dans .envLe fichier .env contient trois variables : GOOGLE_API_KEY (la clé créée dans Google AI Studio), GOOGLE_GENAI_USE_VERTEXAI=FALSE (mode « clé d'API », sans projet Google Cloud) et MODEL (par défaut gemini-3.5-flash). agent.py les charge avec load_dotenv().
Un outil = une fonction bien décrite
ADK transforme chaque fonction en déclaration d'outil envoyée au modèle : le nom de la fonction, la docstring (la description) et les annotations de type (les paramètres). Le modèle ne voit que cela : une docstring vague donne un outil mal utilisé.
def verifier_stock(sku: str) -> dict:
"""Donne le stock disponible d'un produit.
Args:
sku: identifiant produit, par exemple "CASQ-01".
"""
p = CATALOGUE.get(sku.upper())
if not p:
return {"erreur": f"Produit {sku} inconnu."}
return {"sku": sku.upper(), "stock": p["stock"], "disponible": p["stock"] > 0}Trois règles appliquées partout dans agent.py :
| Règle | Pourquoi |
|---|---|
Renvoyer un dict | le modèle reçoit une structure claire (stock, disponible) plutôt qu'une phrase |
Renvoyer {"erreur": ...} au lieu de lever une exception | le modèle lit l'erreur et peut corriger ou expliquer au client |
Donner un exemple de valeur dans Args | le modèle comprend le format attendu ("CASQ-01", pas "casque nova") |
Le modèle appelle verifier_stock(sku="XYZ") et le produit n'existe pas. Quelle est la meilleure réaction de l'outil ?
Une erreur explicite permet au modèle de proposer une recherche au client. Un stock de 0 serait un mensonge (le produit n'existe pas) et une exception interromprait la conversation.
Le panier dans l'état de session
Une session ADK regroupe les échanges d'une conversation. Elle porte un dictionnaire state que vos outils lisent et modifient via le paramètre spécial tool_context : ADK le remplit automatiquement et ne le montre pas au modèle.
def ajouter_au_panier(sku: str, quantite: int, tool_context: ToolContext) -> dict:
"""Ajoute un produit au panier du client (stocké dans l'état de session).
Args:
sku: identifiant produit.
quantite: nombre d'unités (1 à 5).
"""
sku = sku.upper()
p = CATALOGUE.get(sku)
if not p:
return {"erreur": f"Produit {sku} inconnu."}
panier = dict(tool_context.state.get("panier", {}))
total = panier.get(sku, 0) + quantite
if quantite < 1 or total > MAX_QUANTITE:
return {"erreur": f"Quantité refusée : 1 à {MAX_QUANTITE} unités par produit."}
if total > p["stock"]:
return {"erreur": f"Stock insuffisant : {p['stock']} disponible(s)."}
panier[sku] = total
tool_context.state["panier"] = panier # on réaffecte : ADK enregistre le changement
return {"panier": panier}flowchart LR
T["Outil ajouter_au_panier"] -->|"tool_context.state['panier'] = ..."| D["state_delta de l'événement"]
D --> SS[("Service de sessions")]
SS -->|"tour suivant"| I["Instruction : Panier actuel : {panier?}"]
I --> M["Gemini voit le panier"]
L'instruction de l'agent contient {panier?} : ADK y injecte la valeur de state["panier"] à chaque appel du modèle. Le ? rend la variable optionnelle (pas d'erreur tant que le panier n'existe pas).
Assembler l'agent
root_agent = Agent(
name="service_client",
model=MODEL,
description="Assistant de commandes et de suivi de livraison.",
instruction=INSTRUCTION,
tools=[chercher_produits, verifier_stock, ajouter_au_panier, voir_panier,
preparer_commande, confirmer_commande, statut_livraison, escalader_vers_humain],
before_model_callback=garde_fou_entree,
before_tool_callback=garde_fou_outils,
)Le nom root_agent n'est pas anodin : c'est celui que cherchent les commandes adk web et adk run. Les deux callbacks sont expliqués dans la leçon suivante.
Où vit le panier entre deux messages du client ?
Le panier est écrit dans tool_context.state, enregistré par le service de sessions et réinjecté dans l'instruction via {panier?}. Une variable globale serait partagée entre tous les clients.
Exercice : un outil retirer_du_panier
Écrivez retirer_du_panier(sku, tool_context) qui retire complètement un produit du panier, et renvoie une erreur s'il n'y est pas. Ajoutez-le à la liste tools.
Voir la solution
def retirer_du_panier(sku: str, tool_context: ToolContext) -> dict:
"""Retire complètement un produit du panier.
Args:
sku: identifiant produit, par exemple "CASQ-01".
"""
panier = dict(tool_context.state.get("panier", {}))
if sku.upper() not in panier:
return {"erreur": f"{sku} n'est pas dans le panier."}
del panier[sku.upper()]
tool_context.state["panier"] = panier
return {"panier": panier}Pensez aussi à invalider une éventuelle commande en attente (state["commande_en_attente"] = None) : sinon le client pourrait confirmer un récapitulatif devenu faux.
Résultat obtenu
Extrait du scénario réel (gemini-3.5-flash, 2026-10-15) : le modèle enchaîne deux outils dans le même tour, et le panier est bien dans l'état.
CLIENT > Le sans fil est en stock ? Ajoutez-en 2 à mon panier.
[outil] verifier_stock({'sku': 'CASQ-01'})
[outil] ajouter_au_panier({'quantite': 2, 'sku': 'CASQ-01'})
AGENT > Le Casque audio sans fil Nova est bien en stock avec 12 unités disponibles.
J'ai ajouté 2 exemplaires à votre panier. Souhaitez-vous passer à la préparation de la
commande ou voir votre panier ?Et le test sans clé test_panier_dans_etat_de_session vérifie etat["panier"] == {"CASQ-01": 2} : PASSED.
Je sais faire
- Configurer ADK en mode clé d'API avec un
.env - Écrire un outil avec une docstring et des types que le modèle comprend
- Renvoyer des erreurs exploitables plutôt que lever des exceptions
- Lire et écrire l'état de session avec
tool_context.state - Injecter une valeur de l'état dans l'instruction avec
{cle?}