Lección 4 — Tester, évaluer, exploiter

Tester sans clé, exécuter pour de vrai, exploiter

6 min de lectura

Este contenido aún no está traducido. Se muestra la versión original.

Tester sans clé, exécuter pour de vrai, exploiter

1. Trois niveaux de tests

flowchart LR
  F["Fonctions pures : outils, calculer_budget"] --> P["Pipeline complet avec faux modèle"] --> R["Exécution réelle avec Gemini"]
  F -.- C1["gratuit, instantané"]
  P -.- C2["gratuit, vérifie le câblage ADK"]
  R -.- C3["quelques appels payants"]
NiveauCe qu'il prouveClé d'API ?
Fonctions puresles filtres et le calcul du budget sont justesnon
Faux modèlel'état circule bien : output_key, injection {cle}, ordre des agentsnon
Exécution réelleles instructions suffisent au vrai modèleoui

2. Un faux modèle pour ADK

Un modèle, pour ADK, est un objet qui hérite de BaseLlm et implémente generate_content_async. Notre faux modèle rejoue un script : un tuple = un appel d'outil, une chaîne = une réponse texte. Il mémorise aussi l'instruction système reçue, pour vérifier l'injection d'état.

files/test_agent.py (faux modèle)
class FauxModele(BaseLlm):
    model: str = "faux"
    script: list = []
    vus: list = []  # instructions système reçues

    async def generate_content_async(self, llm_request, stream=False):
        self.vus.append(llm_request.config.system_instruction)
        item = self.script.pop(0)
        part = (types.Part(function_call=types.FunctionCall(name=item[0], args=item[1]))
                if isinstance(item, tuple) else types.Part(text=item))
        yield LlmResponse(content=types.Content(role="model", parts=[part]))

Comme construire(model) accepte une fonction nom d'agent → modèle, on donne un faux modèle différent à chaque agent :

files/test_agent.py (pipeline)
def test_pipeline_complet_avec_faux_modele():
    modeles, fabrique = faux(itineraire())
    etat = asyncio.run(a.planifier(a.DEMANDE_EXEMPLE, agent=a.construire(fabrique)))
    assert etat["transport"] == "T2 à 96 EUR" and etat["hebergement"] == "H2 à 98 EUR"
    # l'assembleur a reçu les sorties des 3 agents dans son instruction
    assert "T2 à 96 EUR" in modeles["assembleur"].vus[-1] and "A1, A2" in modeles["assembleur"].vus[-1]
    assert etat["controle_budget"]["total"] == 626 and etat["controle_budget"]["respecte"]

Le test du pipeline avec faux modèle passe. Qu'est-ce qu'il ne prouve PAS ?

3. Lancer les tests et l'exécution réelle

terminal
cd files
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
pytest -q                          # sans clé
cp .env.example .env               # puis mettez votre vraie clé dans .env
python agent.py                    # exécution réelle (5 à 8 appels au modèle)

4. Servir le pipeline en HTTP

Les commandes adk web (interface de développement) et adk api_server (API REST) attendent un dossier par agent, qui est un package Python exposant root_agent :

terminal
mkdir -p apps/voyage && cp files/agent.py files/donnees.py apps/voyage/
echo "from . import agent" > apps/voyage/__init__.py
cd apps && adk api_server --port 8000 .

Le pipeline a besoin d'un état initial (demande, voyageurs, nuits, budget_max). On le passe à la création de la session, puis on envoie le message :

terminal (deuxième fenêtre)
curl -s -X POST localhost:8000/apps/voyage/users/u1/sessions -H 'Content-Type: application/json' \
  -d '{"state": {"demande": "2 voyageurs, Lyon vers Lisbonne, 3 nuits, culture et gastronomie, 900 euros",
                 "voyageurs": 2, "nuits": 3, "budget_max": 900}}'
# -> {"id": "<SESSION_ID>", ...}
curl -s -X POST localhost:8000/run -H 'Content-Type: application/json' \
  -d '{"appName": "voyage", "userId": "u1", "sessionId": "<SESSION_ID>",
       "newMessage": {"role": "user", "parts": [{"text": "Planifie ce voyage."}]}}'

Vous ouvrez adk web et envoyez directement un message, sans créer la session avec un état. Que risque-t-il de se passer ?

5. Vers la production

SujetDans le labEn production
Donnéescatalogue Python fictifAPI de réservation réelles, derrière les mêmes outils
SessionsInMemoryRunner (perdues à l'arrêt)service de session persistant (base SQL ou Vertex AI)
Budgetvérifié, simplement signaléboucle de replanification (exercice de la leçon 3)
Hébergementposte localCloud Run ou Vertex AI Agent Engine, modèle via Vertex AI
Suiviprint des événementstraces OpenTelemetry, coût par demande

Exercice : tester le dépassement de budget

Écrivez un test qui fait choisir à l'assembleur (faux modèle) T1, H1 et les activités A3 et A2, et vérifiez que le verdict final est négatif. Combien coûte cet itinéraire ?

Voir la solution
files/test_agent.py (ajout)
def test_pipeline_signale_le_depassement():
    _, fabrique = faux(itineraire("T1", "H1", ("A3", "A2")))
    etat = asyncio.run(a.planifier(a.DEMANDE_EXEMPLE, agent=a.construire(fabrique)))
    v = etat["controle_budget"]
    assert v["total"] == 2 * 189 + 3 * 145 + 2 * (79 + 45) == 1061
    assert not v["respecte"]

1061 EUR : 378 de transport, 435 d'hôtel, 248 d'activités, soit 161 EUR au-dessus du budget.

Résultat obtenu

Tests sans clé (2026-10-15) :

pytest -v
test_agent.py::test_outils_filtrent_le_catalogue PASSED
test_agent.py::test_budget_recalcule_depuis_le_catalogue PASSED
test_agent.py::test_identifiant_invente_refuse PASSED
test_agent.py::test_pipeline_complet_avec_faux_modele PASSED
============================== 4 passed in 1.27s ===============================

Exécution réelle via adk api_server le même jour (gemini-3.5-flash) : 11 événements, dont les auteurs montrent les branches parallèles entrelacées puis la séquence finale.

POST /run (auteurs des événements, dernier texte)
11 événements
['transport', 'transport', 'activites', 'activites', 'hebergement', 'hebergement', 'hebergement',
 'transport', 'activites', 'assembleur', 'controle_budget']
Budget OK : 800 EUR pour 900 EUR max. Détail : {'transport': 192, 'hebergement': 294, 'activites': 314}

Je sais faire

  • J'écris un faux modèle BaseLlm qui rejoue des appels d'outils et des réponses
  • Je teste le câblage d'un pipeline ADK sans clé d'API
  • Je lance un vrai test de bout en bout et je lis les événements
  • Je sers un agent avec adk api_server en créant la session avec son état initial