درس 5 — Tester, évaluer, exploiter

Exploiter en confiance

5 دقائق قراءة

هذا المحتوى غير مترجم بعد. تُعرض النسخة الأصلية.

Exploiter en confiance

Du lab à l'équipe commerciale

flowchart LR
  U["Utilisateur"] --> UI["Interface : chat interne, adk web, API"]
  UI --> AG["Agent analyste_sql"]
  AG --> GF["Garde-fous"]
  GF --> DB[("Réplique en lecture seule")]
  AG --> J["Journal d'audit : question, SQL, refus, durée"]
  J --> REV["Revue hebdomadaire"]
  REV --> EV["Nouvelles questions de référence"]
  EV --> AG

Trois principes pour passer en production :

  1. Ne jamais brancher l'agent sur la base de production principale : utilisez une réplique en lecture ou un entrepôt dédié. Un garde-fou qui échoue ne doit pas pouvoir ralentir les ventes.
  2. Tout tracer : question, requêtes proposées, refus, résultat, durée. L'état de session contient déjà requetes, refus et dernier_resultat.
  3. Faire grandir le jeu de référence : chaque question mal comprise en production devient une question de référence.

Dire les limites du résultat

Un résultat tronqué présenté comme complet est une erreur silencieuse. Le champ tronque et la consigne « Explication : ... et ses limites » obligent l'agent à le dire. Dans la trace ci-dessous (Résultat obtenu), l'agent annonce de lui-même : « L'affichage des résultats ci-dessus est tronqué aux 50 premières commandes. »

Un utilisateur demande « toutes les commandes livrées » (315 lignes). Quel comportement est le bon ?

Transposer les garde-fous à PostgreSQL

SQLite a servi de terrain d'entraînement. Sur une base serveur, chaque couche a son équivalent, souvent plus solide car appliqué par le serveur et par les droits :

Couche du lab (SQLite)Équivalent PostgreSQLCommentaire
mode=rorôle avec seulement GRANT SELECT, default_transaction_read_only = onle serveur refuse l'écriture, même avec un pilote mal configuré
autorisateur + liste blancheGRANT SELECT sur un schéma de vues dédié (reporting.*)l'agent ne voit que des vues, sans colonnes sensibles
SQLITE_IGNORE sur une colonnevue qui omet ou masque la colonnemême idée, côté base
progress handler (2 s)SET statement_timeout = '5s' sur le rôlecoupe les requêtes trop longues
LIMIT forcétoujours dans le code (enveloppe SELECT * FROM (...) LIMIT n)identique
EXPLAIN avant exécutionEXPLAIN (sans ANALYZE) pour valider et estimer le coûtrefuser au-delà d'un coût estimé

Sur PostgreSQL, quelle mesure remplace le mieux la liste blanche de tables de l'autorisateur SQLite ?

Quand relancer l'évaluation ?

ChangementRelancer pytestRelancer evaluer.py
modification de garde_fous.pyouiconseillé
nouvelle formulation de l'instructionouioui
changement de modèle (MODEL)nonoui, 2 ou 3 passages
nouvelle table autoriséeoui (ajoutez des cas)oui, avec de nouvelles questions

Pour réduire le coût, essayez MODEL=gemini-3.5-flash-lite et comparez son score à celui de gemini-3.5-flash : si l'exactitude tient sur vos questions, le modèle le plus léger suffit.

Exercice : un journal d'audit en JSON Lines

Ajoutez à agent.py une fonction qui, après chaque question, écrit une ligne JSON dans audit.jsonl : date, question, requêtes proposées, nombre de refus, nombre de lignes renvoyées. Le fichier ne doit contenir aucune donnée de résultat.

Voir la solution
files/agent.py (ajout)
import json
from datetime import datetime, timezone

def auditer(question: str, etat: dict, chemin: str = "audit.jsonl") -> None:
    ligne = {
        "date": datetime.now(timezone.utc).isoformat(timespec="seconds"),
        "question": question,
        "requetes": etat.get("requetes", []),
        "refus": etat.get("refus", 0),
        "nb_lignes": etat.get("dernier_resultat", {}).get("nb_lignes", 0),
    }
    with open(chemin, "a", encoding="utf-8") as f:
        f.write(json.dumps(ligne, ensure_ascii=False) + "\n")

# dans __main__ :
reponse, etat = asyncio.run(demander(q))
auditer(q, etat)

On journalise le SQL et des compteurs, pas les lignes : le journal d'audit ne doit pas devenir une copie non protégée des données. La question, elle, peut contenir des informations sensibles : définissez une durée de conservation.

Résultat obtenu

Exécution réelle le 2026-10-15 (gemini-3.5-flash) : « Donne-moi la liste de toutes les commandes livrées avec leur montant. » (fin de la réponse).

**Requête** :
SELECT c.id AS commande_id, c.date_commande, c.client_id,
       ROUND(SUM(lc.quantite * lc.prix_unitaire), 2) AS montant
FROM commandes c JOIN lignes_commande lc ON c.id = lc.commande_id
WHERE c.statut = 'livree'
GROUP BY c.id, c.date_commande, c.client_id ORDER BY c.id;

**Explication** :
Le montant de chaque commande est calculé en effectuant la somme du produit de la quantité
et du prix unitaire (`quantite * prix_unitaire`) des articles associés dans la table
`lignes_commande`. Un filtre est appliqué sur `commandes.statut = 'livree'`.
*Limites* : L'affichage des résultats ci-dessus est tronqué aux 50 premières commandes.

Je sais faire

  • Faire annoncer par l'agent les limites d'un résultat (troncature, filtres)
  • Tracer chaque question dans un journal d'audit sans y copier les données
  • Transposer lecture seule, liste blanche, délai et LIMIT à PostgreSQL
  • Décider quand relancer les tests et l'évaluation