درس 7 — Interface web et mesure

Journaliser et mesurer la satisfaction

5 دقائق قراءة

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

Journaliser et mesurer la satisfaction

Pourquoi journaliser ?

Sans journal, vous ne savez pas ce que les gens demandent, ni où le chatbot se trompe. Le journal sert à trois choses : comprendre (quelles questions ?), corriger (quelles réponses ont reçu 👎 ?), surveiller (erreurs, lenteurs).

flowchart LR
  E["Échange terminé"] --> M["masquer() : e-mail, téléphone, IBAN"]
  M --> J["journal.jsonl (une ligne JSON par événement)"]
  A["Clic 👍 / 👎"] --> J
  J --> S["statistiques() → GET /stats"]
  S --> D["Décisions : compléter la fiche, ajuster le prompt"]

Le format JSONL (JSON Lines) met un objet JSON par ligne : on ajoute à la fin du fichier sans le relire, et n'importe quel outil (Python, jq, BigQuery) sait le lire.

Le code

files/chatbot.py (extrait)
EMAIL = re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+")
TELEPHONE = re.compile(r"(?:\+33|0)\s?[1-9](?:[\s.-]?\d{2}){4}")
IBAN = re.compile(r"\bFR\d{2}(?:\s?[0-9A-Z]{4}){5}\s?[0-9A-Z]{3}\b", re.I)


def masquer(texte: str) -> str:
    """Retire les données personnelles évidentes avant d'écrire le journal."""
    texte = IBAN.sub("[IBAN]", texte)
    texte = EMAIL.sub("[EMAIL]", texte)
    return TELEPHONE.sub("[TEL]", texte)


def journaliser(evenement: dict) -> None:
    evenement = {"ts": datetime.now(timezone.utc).isoformat(timespec="seconds"), **evenement}
    ligne = json.dumps(evenement, ensure_ascii=False)
    if os.environ.get("K_SERVICE"):  # variable définie par Cloud Run :
        print(ligne, flush=True)     # stdout y part dans Cloud Logging
    with JOURNAL.open("a", encoding="utf-8") as f:
        f.write(ligne + "\n")

Chaque échange produit un événement echange (question et réponse masquées, latence, modèle, erreur éventuelle), chaque clic un événement avis, relié par l'identifiant id reçu dans l'en-tête X-Echange-Id.

Les indicateurs

files/chatbot.py (extrait de statistiques)
return {
    "echanges": len(echanges),
    "erreurs": sum(1 for e in echanges if e.get("erreur")),
    "avis": len(avis),
    "satisfaction": round(positifs / len(avis), 2) if avis else None,
    "taux_avis": round(len(avis) / len(echanges), 2) if echanges else None,
    "latence_mediane_ms": latences[len(latences) // 2] if latences else None,
}
IndicateurCe qu'il ditPiège
satisfactionpart des avis positifscalculé sur ceux qui votent seulement
taux_avispart des réponses notéessous quelques pourcents, la satisfaction est peu fiable
erreurspannes du modèleà surveiller avec une alerte
latence_mediane_mstemps typique d'une réponse complètela médiane résiste aux valeurs extrêmes, contrairement à la moyenne

Sur 1 000 échanges, 40 avis dont 36 positifs. Que conclure ?

Pourquoi la médiane plutôt que la moyenne pour la latence ?

Exercice : la part de « je ne sais pas »

Ajoutez à statistiques() un indicateur sans_reponse : la part des réponses qui renvoient vers l'accueil faute d'information. Indice : le prompt fait donner le contact de l'accueil dans ce cas.

Voir la solution
chatbot.py (ajout dans statistiques)
renvois = sum(1 for e in echanges if "accueil@" in e["reponse"] or "[EMAIL]" in e["reponse"])
# ...
"sans_reponse": round(renvois / len(echanges), 2) if echanges else None,

Attention : le journal est masqué, donc l'e-mail de l'accueil y apparaît comme [EMAIL]. C'est une heuristique : certaines réponses donnent le contact même quand elles répondent. Pour un indicateur exact, faites ajouter au modèle un marqueur explicite, ou classez les réponses avec un second appel. Regardez cet indicateur par thème : il dit quoi ajouter à la fiche.

Résultat obtenu

Extrait réel de journal.jsonl après la session console du 2026-10-15 (l'IBAN, fictif, a été masqué avant écriture) :

journal.jsonl (extrait réel, 2026-10-15)
{"ts": "2026-10-15T14:32:16+00:00", "type": "echange", "id": "234a8ba195de", "session": "40cbb89b5c8c4383bff688cc9161c838", "question": "Mon IBAN est [IBAN], vous pouvez le noter ?", "reponse": "Je ne peux pas prendre en note vos coordonnées bancaires sur ce chat. [...]", "latence_ms": 3945, "modele": "gemini-3.5-flash", "erreur": null}

Et les statistiques renvoyées par GET /stats à la fin du test de bout en bout (test_http_reel.py), après 5 échanges et un 👍 :

sortie réelle, 2026-10-15
stats : {'echanges': 5, 'erreurs': 0, 'avis': 1, 'satisfaction': 1.0, 'taux_avis': 0.2, 'latence_mediane_ms': 3209}

Je sais faire

  • Écrire un journal JSONL, un événement par ligne
  • Masquer e-mails, téléphones et IBAN avant d'écrire
  • Relier un avis à sa réponse par un identifiant d'échange
  • Lire satisfaction, taux d'avis et latence médiane avec leurs pièges
التالي