Lección 1 — Cadrer

Le problème et l'architecture

7 min de lectura

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

Le problème et l'architecture

Lundi, 9 h 05. La boîte de Lin & Lumière contient 46 messages arrivés depuis vendredi soir : 19 demandes de suivi, 11 questions sur les frais de port ou les délais, 7 demandes de retour, 4 réclamations, le reste en vrac. Deux conseillères vont passer la matinée à copier des numéros de suivi. Les 4 réclamations, les seules qui demandent un humain, attendront midi.

L'objectif tient en une phrase : l'agent traite seul les demandes répétitives, transmet les autres à une conseillère avec le contexte, et chaque conversation laisse une trace mesurable (coût, latence, issue).

Le mécanisme : qui décide où va un message

La tentation est de construire « un chatbot » puis d'y ajouter un bouton « parler à un humain ». Cela casse dès le premier redémarrage : si l'état « en attente d'une conseillère » vit dans la mémoire de l'agent ou dans le navigateur, un déploiement le perd et le client se retrouve face au robot qui ne sait plus rien.

La décision de ce projet : une conversation a un statut stocké en base, et c'est ce statut, pas le modèle, qui route chaque message.

stateDiagram-v2
  [*] --> agent : premier message
  agent --> agent : réponse de l'agent
  agent --> en_attente : outil transferer_a_un_conseiller ou panne du modèle
  en_attente --> humain : une conseillère prend la conversation
  humain --> agent : la conseillère rend la main
  humain --> close : conversation close
  agent --> close
  close --> [*]
StatutQui répondCe que voit le client
agentl'agent ADKune réponse en 1 à 3 secondes
en_attentepersonne (le message est stocké)« transmis à un conseiller, position N »
humainla conseillère, depuis sa consoleses messages, sur le même canal
closepersonnele message suivant ouvre une nouvelle conversation

Trois autres décisions découlent du terrain :

  1. L'identité vient du canal. Sur WhatsApp, Meta a déjà authentifié le numéro de téléphone : il suffit à prouver qu'on parle au titulaire de la commande. Sur le web, il faut l'e-mail de la commande. L'agent ne voit jamais une commande sans cette preuve.
  2. Tout fonctionne sans clé. La recherche bascule sur BM25 et un faux modèle joue le rôle de Gemini : make test vérifie le routage, la sécurité et les métriques en 3 secondes, hors réseau.
  3. Une seule base de vérité. SQLite contient conversations, messages, coûts et latences ; le tableau de bord les relit. Pas de second système de métriques à maintenir.

Mise en œuvre : le dépôt et le premier lancement

files/
support/   config.py  store.py  kb.py  tools.py  agent.py  service.py  whatsapp.py  metrics.py  app.py
data/      faq.md  cgv.md  commandes.json        (boutique fictive, 8 commandes aux dates relatives)
web/       index.html  whatsapp.html  conseiller.html  tableau-de-bord.html  style.css  *.js
eval/      conversations.json  run_eval.py       (30 conversations de référence)
tests/     conftest.py (faux modèle)  test_regles.py  test_parcours.py  test_eval.py
scripts/   e2e.py
Makefile  Dockerfile  docker-compose.yml  requirements.txt  .env.example  ci/github-actions.yml

Les commandes du jeu de données portent des dates relatives (« livrée il y a 8 jours ») converties au chargement : sans cela, le cas « retour dans le délai de 30 jours » deviendrait faux un mois après la publication du dépôt.

files/support/store.py (extrait)
def _charger_commandes(chemin: Path, aujourd_hui: date) -> dict[str, dict[str, Any]]:
    """Les dates du jeu de données sont relatives (« il y a N jours ») pour rester valides dans le temps."""
    def jour(n: int | None) -> str | None:
        return None if n is None else (aujourd_hui - timedelta(days=n)).isoformat()

Installez et lancez les tests, sans clé :

terminal
cd files
make setup        # .venv, dépendances, .env copié depuis .env.example
make test

Renseignez ensuite GOOGLE_API_KEY dans .env et lancez l'application :

terminal
make run          # http://localhost:8080 ; console : /conseiller?token=<ADMIN_TOKEN de .env>

Ce qui casse en production

SymptômeCauseParade dans ce projet
Après un déploiement, un client en attente d'humain reparle au robotstatut d'escalade gardé en mémoirestatut en base, lu à chaque message
L'agent donne le contenu d'une commande à un inconnuoutil qui accepte un simple numéropreuve d'identité obligatoire dans l'outil, pas dans la consigne
Les tests passent en local, échouent en CItests qui appellent le vrai modèlefaux modèle BaseLlm et repli BM25
Le jeu de test « vieillit »dates absolues dans les donnéesdates relatives

La règle de décision

Construisez votre propre agent quand vos outils métier (commandes, retours, règles maison) sont spécifiques et que vous voulez mesurer chaque tour. Prenez un produit existant quand votre besoin est couvert par ses intégrations : un assistant WhatsApp Business du marché comme Watpilot se branche sur Shopify ou Google Sheets sans une ligne de code. Dans les deux cas, les questions de ce projet (identité, escalade, coût, évaluation) restent celles à poser au fournisseur.

Résultat obtenu

Exécution du 2026-10-16, sans clé ni réseau :

make test, avec -v (2026-10-16, extrait)
tests/test_eval.py::test_jeu_de_30_conversations PASSED
tests/test_parcours.py::test_escalade_puis_reprise PASSED
tests/test_parcours.py::test_reprise_apres_redemarrage PASSED
tests/test_parcours.py::test_panne_modele_escalade PASSED
tests/test_parcours.py::test_webhook_whatsapp_bout_en_bout PASSED
tests/test_regles.py::test_acces_refuse_sans_fuite PASSED
tests/test_regles.py::test_regles_de_retour PASSED
...
============================== 18 passed in 2.40s ==============================

Pratique

Un client WhatsApp est en attente d'une conseillère. Vous redéployez le service pendant ce temps. Il écrit « toujours là ? ». Avec l'architecture de ce projet, que se passe-t-il ?

La direction demande d'ajouter dans la consigne « ne révèle jamais une commande sans l'e-mail du client ». Un développeur propose de s'arrêter là. Que répondez-vous ?

Exercice. Le jeu de données contient une commande en retard (LL-10533). Sans lire le code, prédisez le statut de la conversation après ces deux messages WhatsApp de son titulaire : « Où est mon colis LL-10533 ? », puis « Je veux annuler et être remboursé ». Vérifiez ensuite dans le simulateur (/whatsapp, client Paul) et dans la console.

Voir la solution

Premier message : statut agent. L'outil suivre_colis reconnaît le numéro de téléphone (identité WhatsApp), renvoie le retard et la consigne de l'article 5 des CGV.

Second message : statut en_attente. Une annulation fait partie des cas où la consigne impose transferer_a_un_conseiller ; l'agent ne peut pas annuler lui-même. La conversation apparaît en tête de la file de la console, avec le motif résumé par l'agent.

  • Je sais expliquer pourquoi le statut d'une conversation doit vivre en base
  • Je sais où placer une règle de sécurité : dans l'outil, pas seulement dans la consigne
  • J'ai lancé make test (18 tests verts) puis make run
Siguiente