Leçon 9 — Projet final : un assistant qui se souvient

Évaluer la mémoire : 10 scénarios, 3 configurations

8 min de lecture

Évaluer la mémoire : 10 scénarios, 3 configurations

Le protocole

  1. Session 1 : Camille envoie 10 messages (prénom, allergie, langage, ville, équipe, prix HT, mercredi, Jira, une question sans intérêt, puis le changement d'équipe) ; Bob se présente. L'assistant répond et apprend.
  2. Nouvelle session pour chaque question, dans trois configurations :
ConfigurationCe que contient le contexteCe qu'elle représente
sans_memoireles consignes et la questionun chatbot sans mémoire
historique_complettoute la session 1, mot pour motla solution naïve « on recolle tout »
memoireles 4 faits les plus proches, rappelés depuis SQLitenotre assistant
  1. Notation par expressions régulières (scenarios.json) : expect (doit apparaître), forbid (ne doit pas apparaître). Pour le mode memoire, on mesure aussi si le bon fait était parmi les 4 rappelés.
flowchart LR
  S1["Session 1 : 11 messages, extraction"] --> DB[("eval.db")]
  DB --> M["memoire : top-4 faits"]
  S1 --> H["historique_complet : transcription"]
  N["sans_memoire"]
  M --> Q["10 questions, nouvelle session à chaque fois"]
  H --> Q
  N --> Q
  Q --> SC["Score, tokens, bon fait rappelé"]

Les 10 questions couvrent des difficultés différentes :

#Ce qu'on teste
1, 4, 7, 8rappel direct (prénom, ville, jour à éviter, outil de tickets)
2rappel d'une contrainte de santé formulée autrement (« traiteur »)
3appliquer une préférence, pas seulement la citer : le code doit être en Go (func )
5mise à jour : Logistique, pas Paiements
6préférence de format (prix HT)
9isolation : Bob ne doit pas hériter du langage de Camille
10ne pas inventer : aucune couleur de voiture n'a été donnée

Résultat obtenu

Exécution réelle du 2026-10-15 avec la clé d'API (gemini-3.5-flash, extraction gemini-3.5-flash-lite, gemini-embedding-2) :

== sans_memoire : 4/10 réussis, tokens d'entrée moyens 95
  KO  #1  'Je ne sais pas comment vous vous appelez.'
  KO  #5  'Je ne connais pas votre équipe actuelle.'
  OK  #6  "Je ne connais pas vos habitudes d'affichage des prix. \n\nSouhaitez-vous que je vous l'indique en Hors Taxes (HT"
  OK  #8  'Vous pouvez créer le ticket sur notre outil de suivi habituel (Jira / GitHub) dans le projet correspondant au '
  OK  #9  'Je ne connais pas le langage dans lequel vous codez au travail.'
  OK  #10 'Je ne connais pas la couleur de votre voiture.'

== historique_complet : 10/10 réussis, tokens d'entrée moyens 591

== memoire : 10/10 réussis, tokens d'entrée moyens 159, bon fait rappelé 8/8
  OK  #3  '```go\nfunc Add(a, b int) int {\n    return a + b\n}\n```'
  OK  #5  "Tu fais partie de l'équipe Logistique."
  OK  #8  'Tu peux créer ton ticket de bug sur **Jira**, dans le projet **PAY**.'
  OK  #9  "Je ne connais pas le langage dans lequel tu codes au travail, cette information n'est pas dans ma mémoire."
  OK  #10 'Je ne la connais pas. Je sais seulement que tu viens au bureau à vélo !'
ConfigurationScoreTokens d'entrée moyensLecture
sans mémoire4/10 (en réalité 2/10, voir plus bas)95ne sait rien, mais n'invente pas
historique complet10/10591parfait, mais le coût grandit avec chaque message
mémoire long terme10/10159même qualité, coût borné, bon fait rappelé 8 fois sur 8

(tableau : exécution avec la clé d'API ; les chiffres Vertex AI sont juste en dessous)

Même évaluation sur Vertex AI (europe-west1, gemini-2.5-flash, extraction gemini-2.5-flash-lite, gemini-embedding-001), le 2026-10-15 :

== sans_memoire : 3/10 réussis, tokens d'entrée moyens 93
== historique_complet : 10/10 réussis, tokens d'entrée moyens 494
== memoire : 10/10 réussis, tokens d'entrée moyens 150, bon fait rappelé 8/8
  OK  #3  '```go\npackage main\n\nfunc add(a, b int) int {\n\treturn a + b\n}\n```'
  OK  #9  "Bob, je ne sais pas si tu codes au travail, ni dans quel langage si c'est le cas. ..."

Mêmes conclusions avec une autre famille de modèles et un autre modèle d'embedding : le code n'a changé que par trois variables d'environnement. Mais ce 10/10 cache un défaut : pendant la session 1, l'extracteur a écrasé le fait « vient au bureau en vélo » (voir la leçon « Extraire, mettre à jour, oublier »). Aucune des 10 questions ne portait sur le vélo : l'évaluation ne pouvait pas le voir. Un score parfait sur 10 scénarios ne prouve pas l'absence d'erreur, il prouve que ces 10 cas passent. Vérifiez aussi le contenu de la base après la session 1 (mem.all(user) et mem.history(user)), pas seulement les réponses.

Les faux positifs de la notation

Le mode sans_memoire obtient 4/10, mais deux succès sont chanceux : #6 propose le HT sans le savoir (« Souhaitez-vous … HT ? »), #8 devine Jira parmi deux outils. Seuls #9 et #10 (ne pas inventer) sont de vrais succès. Une notation par mots-clés est rapide, gratuite et reproductible, mais elle se trompe dans les deux sens.

Le mode historique_complet obtient le même score que la mémoire. Pourquoi ne pas s'en contenter ?

Mesurer l'impact : qualité, tokens, coût

Pour passer des tokens à des euros, multipliez par le tarif d'entrée de votre modèle (page de tarifs). Sur ce test, la mémoire économise environ 430 tokens d'entrée par question par rapport à l'historique complet, mais elle coûte un appel d'extraction (modèle léger) et des appels d'embedding à chaque message appris. Le bilan dépend du rapport entre messages et questions : une mémoire est d'autant plus rentable que les sessions sont longues et nombreuses.

Que mesure « bon fait rappelé 8/8 » ?

Exploiter et nettoyer

  • En local : la mémoire est un fichier (eval.db, memoire.db). Supprimer le fichier = tout oublier. eval_rappel.py repart d'une base vide à chaque exécution.
  • Sur Google Cloud : en production, la table facts irait dans Cloud SQL ou Firestore, avec un droit d'accès et d'effacement pour chaque utilisateur (RGPD : la mémoire contient des données personnelles, comme l'allergie de Camille). Le seul objet créé sur Google Cloud pendant nos tests est le cache explicite du module 3, supprimé à la fin de chaque exécution (caches restants du lab : []).

Exercice : un scénario qui casse

Ajoutez à scenarios.json un scénario que la mémoire actuelle devrait rater, puis proposez la correction.

Voir la solution

Exemple : une préférence conditionnelle. Session 1 : « Le vendredi, réponds-moi en anglais, je prépare la réunion avec l'équipe de Londres. » Question (un vendredi) : « Résume-moi le ticket PAY-12. »

{"id": 11, "user": "camille", "q": "Nous sommes vendredi. Résume en une phrase : le paiement échoue quand le panier est vide.",
 "expect": ["\\b(the|is|when)\\b"], "fait": "vendredi|anglais"}

Le rappel par similarité risque de ne pas remonter le fait « anglais le vendredi » : la question parle de paiement, pas de langue. Corrections possibles : rappeler aussi les faits marqués « préférence permanente » à chaque appel (une petite liste toujours présente, en tête du contexte), et ne réserver la similarité qu'aux faits de contenu.

Je sais faire

  • Concevoir des scénarios de rappel qui couvrent rappel, application, mise à jour, isolation et non-invention
  • Comparer plusieurs stratégies de contexte sur les mêmes questions
  • Séparer la qualité de la recherche (bon fait rappelé) de celle de la réponse
  • Repérer les faux positifs d'une notation par mots-clés