Mémoire des clients pour les agents IA avec un budget strict de tokens.
Comment une couche de mémoire d’agent extrait des faits, évalue leur importance, supprime les doublons selon un coefficient de cosinus de 0,92, stocke les vecteurs dans pgvector, et effectue des recherches dans une limite de 2 048 tokens avec mécanisme d’éviction.
Sources: Engineering a Multi-Agent AI Platform — Part 6: Memory Under Token Constraints
Un agent de support parle avec un client, et tout ce dont il a besoin doit tenir dans une seule fenêtre de contexte. GPT-4o offre 128 K de tokens, tandis que les modèles plus petits peuvent en offrir 32 K ou moins. Cette fenêtre doit contenir l’instruction système, les définitions des outils, les connaissances récupérées, la conversation en cours ainsi que tout ce que l’agent se souvient au sujet de cette personne en particulier. Ce texte décrit une architecture de mémoire adaptée à ce système : la manière dont les faits sont extraits des conversations terminées, évalués, dédoublés, stockés sous forme de vecteurs et récupérés à nouveau dans le respect d’une limite stricte de tokens.
La fenêtre est un budget, pas un réservoir
Tout ce qui est connu sur un client doit être intégré dans l’instruction système, ce qui oblige à compresser les autres entrées. La récupération des connaissances est tronquée, l’historique des conversations est raccourci, et l’instruction système peut être déplacée si loin en arrière que le modèle perd de vue ses propres instructions.
En ne rien incluant, l’agent traite chaque conversation comme une première rencontre. Un client qui demande l’heure de son rendez-vous reçoit la réponse « Je n’ai pas cette information », alors même qu’il l’a indiquée mardi dernier.
La solution consiste à allouer une capacité de mémoire fixe au client. Dans le système décrit ici, cette capacité est de 2 048 tokens. Dans ce cadre, le système retient uniquement les informations les plus importantes pour ce client au cours de cette conversation. Pas toutes les informations, ni au hasard, mais uniquement celles qui sont pertinentes.
Extraction des faits à partir de conversations terminées
Lorsqu’une conversation se termine, un processus de consolidation la parcourt afin de identifier les faits dignes d’être conservés. L’extraction s’effectue à l’aide d’un LLM (GPT-4o-mini) avec une instruction structurée demandant quelles informations factuelles sur le client peuvent être extraites, ainsi que d’évaluer chaque fait de 0 à 1 en fonction de sa spécificité, de sa pertinence pratique et de sa fréquence de réapparition.
Le mot-clé est « spécifique ». Les faits doivent décrire ce client et ne pas présenter des observations générales. « Préfère les rendez-vous le matin » est un fait. « A posé une question » ne l’est pas.
Le modèle répond par un tableau JSON, chaque élément contenant une chaîne de texte et un score d’importance. Le processus ne conserve que les faits ayant un score d’au moins 0,5. Ce seuil élimine les observations sans intérêt, dont le score se situe généralement entre 0,2 et 0,3, et retient tout ce qui peut être utile par la suite.
Un échange typique de dix messages donne deux à quatre faits qualifiés, tandis qu’une longue session de dépannage peut en produire six à huit. La plupart des conversations génèrent au moins un fait de ce type.
Ce que représente le score d’importance
Ce chiffre unique allant de 0,0 à 1,0 combine trois critères.
- Spécificité. Dans quelle mesure ce fait concerne-t-il précisément ce client ? « Préfère l’e-mail au téléphone » en fait partie, tandis que « Est un client » ne l’est pas.
- Utilisabilité. L’agent peut-il s’en servir pour répondre mieux la prochaine fois ? « A un problème récurrent avec la facture n°4821 » remplit cette condition, contrairement à « A dit bonjour ».
- Récurrence. Cela aura-t-il de l’importance lors de plusieurs conversations ? Le fuseau horaire UTC+3 est mentionné à chaque fois qu’il s’agit de planifier, mais une question sur le temps qu’il fait aujourd’hui n’apparaît qu’une fois.
L’équipe chargée de la conception a testé des scores distincts pour chaque dimension et a constaté qu’un score combiné était mieux calibré, tout en rendant la demande plus simple. Selon leurs mesures, le seuil de 0,5 élimine environ 60 % des faits extraits, ne conservant que ceux ayant un fort signal.
Embeddings et contrôle des doublons
Chaque fait qui survit est intégré sous forme de vecteur à 3072 dimensions, ce qui correspond simplement à la taille de sortie du modèle d’incorporation utilisé, OpenAI’s text-embedding-3-large. Plus il y a de dimensions, plus il est possible de distinguer finement les similitudes, ce qui est utile lorsqu’il faut différencier une répétition reformulée d’une information véritablement nouvelle.
Avant qu’un nouveau fait ne soit enregistré, il est comparé à tous les faits existants pour ce client. Une similarité cosinus supérieure à 0,92 signifie « doublon, ignorer ».
Pourquoi ce chiffre ? Les gens expriment une même idée de multiples manières. « Préfère la communication par e-mail », « Souhaite être contacté par e-mail » et « A dit ne pas consulter souvent son téléphone, utilise plutôt l’e-mail » décrivent tous un même fait sous-jacent. Avec une valeur de 0,92, le système détecte de manière fiable de telles répétitions sémantiques. Des seuils plus bas, tels que 0,85 ou 0,88, commençaient à fusionner des faits qui étaient en réalité très différents, tandis que des seuils plus élevés, comme 0,95 ou 0,97, permettaient à trop de reformulations de s’accumuler.
Cette vérification a lieu à chaque cycle de consolidation. Sans elle, un client qui mentionnerait sa zone horaire dans chaque conversation accumulerait des dizaines de lignes presque identiques, consommant ainsi inutilement de l’espace de stockage et des ressources de recherche en raison de ces répétitions.
Pourquoi Postgres avec pgvector
Les faits sont stockés dans PostgreSQL, l’extension pgvector étant chargée de la recherche de similarité. Une ligne customer_memories contient :
content: le fait sous forme de texte brutembedding: une colonne vectorielle de 3072 éléments utilisée pour la recherche de similaritéimportance: le score attribué lors de l’extractionsource_channel: le canal d’où provient le fait (webchat, whatsapp, voix, etc.)created_at: l’heure de la première insertionlast_accessed_at: la dernière fois où il a été utilisé dans un chat
Un stockage dédié comme Qdrant ou Pinecone constituerait l’alternative habituelle, mais trois facteurs s’y opposent.
- Les collections sont très petites. Au maximum 500 faits par client. Un service distinct ajoute des étapes réseau, des problèmes de gestion des connexions et des risques de non-conformité qui ne sont pas justifiés à cette échelle.
- Les transactions sont essentielles. Les données appartiennent au dossier d’un client. Lorsqu’un client est supprimé conformément au RGPD, ses données doivent disparaître en même temps que tout le reste. Avec pgvector, cela se fait au sein d’une seule transaction, tandis qu’un stockage vectoriel distinct nécessiterait une cohérence distribuée.
- La requête est simple. Il suffit de filtrer par customer_id avec une correspondance exacte, puis de trier selon la similarité vectorielle, tâche que pgvector gère efficacement grâce à un index composite.
La base de connaissances relève d’un autre cas. Les documents de produits, les FAQ et les manuels de politique se trouvent dans Qdrant, car cette collection est volumineuse, avec des milliers de documents par locataire, gère un fort trafic et n’a pas besoin d’un lien transactionnel avec des données relationnelles. Problème différent, outil différent.
Récupération avec une limite de tokens
Lorsqu’un nouveau message arrive et que l’agent doit y répondre, la récupération depuis la mémoire entre en jeu :
- Intégrer le message reçu à l’aide du même modèle de 3072 dimensions utilisé pour le stockage.
- Sélectionner les dix lignes les plus proches de
customer_memoriescorrespondant à ce customer_id, classées selon la distance<=>entre chaque embedding stocké et le vecteur de requête. - Parcourir les résultats dans l’ordre de similarité, en tenant compte du compteur de tokens cumulé.
- Estimer le nombre de tokens de chaque fait à
len(content) / 4, ce qui constitue une valeur approximative mais prudente. - S’arrêter dès que l’ajout du fait suivant ferait dépasser 2 048 tokens.
- Mettre à jour
last_accessed_atpour chaque fait qui a été récupéré.
L’opérateur <=> donne la distance cosinus ; une valeur plus faible indique une correspondance plus proche avec le message actuel.
Le plafond est fixe. L’agent reçoit les faits les plus pertinents qui tiennent dans 2 048 tokens, classés par ordre de pertinence. Si le fait le plus important occupe 800 tokens, seuls deux ou trois faits pourraient tenir ; si chaque fait est court, de 50 à 80 tokens, quinze à vingt pourraient être pris en compte. Le budget s’adapte au contenu réel sans nécessiter de réglage manuel. La valeur par défaut de dix lignes a rarement d’importance dans la pratique, car la limite de tokens est généralement la première à être atteinte, mais elle empêche la requête de base de données de devenir illimitée.
La mise à jour de last_accessed_at remplit deux fonctions : elle indique à la politique d’éviction quels faits conservent leur place, et elle montre quels souvenirs l’agent utilise réellement et lesquels restent inutilisés.
La limite de 500 faits et ceux qui sont éliminés
Un client ne peut conserver qu’un maximum de 500 faits en mémoire. Ce plafond permet de prévoir le coût de stockage par client et de maintenir des performances de récupération optimales, car la similarité cosinus sur 500 vecteurs est rapide et fiable, tandis qu’elle devient bruitée au-delà de 50 000 vecteurs.
Lorsque la consolidation cherche à enregistrer un nouveau fait et que le client atteint déjà le nombre de 500 faits, le processus d’éviction commence. L’ordre de suppression est le suivant :
- Les faits non modifiés depuis 90 jours ou plus, qui sont obsolètes et probablement sans importance aujourd’hui.
- Parmi ceux qui restent, ceux ayant le score d’importance le plus bas.
- En cas d’égalité, le plus ancien en date, déterminé par la valeur de
created_at.
Quatre-vingt jours conviennent au contexte commercial. Un fait appris il y a trois mois et jamais utilisé depuis est peu probable d’avoir de l’importance, mais un fait appris il y a six mois et utilisé chaque semaine, comme une préférence de fuseau horaire, reste en mémoire indéfiniment car son last_accessed_at est constamment mis à jour. L’élimination des données a lieu après toute consolidation dépassant la limite fixée, et ne supprime que ce qui est nécessaire, généralement un à trois faits, et non une purge massive.
La mémoire et les connaissances sont des choses différentes
Ces deux systèmes résolvent des problèmes distincts et se disputent la même ressource temporelle.
La mémoire est liée à chaque client, de taille limitée, personnelle et acquise à travers les interactions : le nom de la fille est Amara, les rapports doivent être au format PDF, une réclamation antérieure concernant une livraison a été résolue par un crédit de 15 %. Les connaissances, quant à elles, sont liées à chaque locataire ou sont globales, de grande taille, partagées et rédigées par des personnes : documentation du produit, tableaux de prix, politiques de retour et guides de dépannage.
Chacun dispose de son propre budget de tokens : 2 048 pour la mémoire et un budget plus important, généralement de 4 096 à 8 192, pour les connaissances. Ils partagent la fenêtre de contexte du modèle sans s’entraider mutuellement. Même si la mémoire utilise toute son allocation, les connaissances conservent leur budget complet.
Cette séparation permet d’éviter un problème grave. Sans budgets distincts, un client disposant de 500 faits détaillés pourrait éclipser toute récupération de connaissances, et l’agent se souviendrait de tout au sujet de cette personne tout en oubliant la documentation du produit dont il a besoin pour l’aider.
Comment la mémoire influence la profondeur du raisonnement
La mémoire alimente également le classificateur de complexité qui détermine le niveau de raisonnement nécessaire pour une demande, et ce, de deux manières.
Le premier est le signal de « dépendance au contexte ». Lorsque la récupération fournit des faits très pertinents, ce qui correspond à une faible distance cosinus par rapport au message actuel, la note context_dependency augmente. Le classificateur interprète cela comme « la bonne réponse dépend de données spécifiques au client » et peut envoyer la demande à un niveau de raisonnement supérieur. La question « Pouvez-vous vérifier ma commande ? » obtient une note plus élevée lorsque les faits stockés mentionnent une commande en attente, par rapport à la même question posée par un client sans historique.
Le deuxième facteur est la concurrence avec le budget dédié à la réflexion. En mode chaîne de pensée complète, le modèle alloue 4 096 ou 16 384 tokens à la réflexion, en fonction de la complexité. Les informations mémorielles insérées dans l’instruction utilisent des tokens provenant de la même fenêtre finie ; ainsi, un utilisateur disposant de 2 048 tokens de mémoire laisse moins de place qu’un utilisateur en possédant 200. Ces budgets sont gérés de manière indépendante et constituent chacun un plafond fixe, ce qui empêche le système d’échanger l’un contre l’autre.
Les aspects qui restent difficiles
Deux difficultés ne figurent pas dans le diagramme d’architecture.
Dérive des faits. Les préférences changent. Ceux qui « préfèrent les rendez-vous le matin » peuvent, trois mois plus tard, préférer ceux de l’après-midi. Le système ne détecte pas automatiquement les contradictions entre les faits anciens et les nouveaux. Le seuil de 0,92 permet parfois de les repérer, lorsque la nouvelle déclaration ressemble suffisamment à l’ancienne pour correspondre, mais ce n’est pas fiable. Pour l’instant, les faits anciens échappent au système en raison de la politique de durée de validité et d’accès, et une meilleure gestion des contradictions fait partie des projets futurs.
Hallucination d’extraction. Le LLM chargé de l’extraction infère parfois des éléments que personne n’a mentionnés. Une question neutre formulée d’une certaine manière peut être enregistrée sous la forme "le client semble frustré par le produit". Le seuil de 0,5 élimine les inférences les plus faibles, mais pas toutes. La déduplication aide indirectement, car une inférence inventée réapparaît rarement, restant ainsi dans une ligne peu utilisée jusqu’à ce qu’elle soit supprimée pour libérer de l’espace.
Rien ici n’est une solution définitive. Ces deux problèmes sont gérés grâce au plafond de stockage, à la politique de suppression et au signal de fréquence d’accès, qui favorisent ensemble les faits récents, fréquemment utilisés et importants. Les suppositions obsolètes et les préférences dépassées se retrouvent en bas de la liste et sont supprimées lorsque de l’espace est nécessaire.
Deux flux en résumé
Après une conversation, le pipeline de consolidation extrait des faits à l’aide du LLM, évalue leur importance, filtre ceux dont le score est inférieur à 0,5, génère des embeddings, supprime les doublons lorsque le seuil de cosinus atteint 0,92, stocke ce qui est nouveau et élimine les données dès que la limite de 500 faits est atteinte. Avant de générer une réponse, le message reçu est transformé en embedding, les faits les plus pertinents sont sélectionnés en fonction de la similarité cosinus, collectés dans le cadre d’un budget de 2 048 tokens, puis injectés dans le contexte de l’agent.