Tag: mrr

  • La mémoire d’un agent IA s’est surprise à mentir : deux logs qui changent l’approche

    La mémoire d’un agent IA s’est surprise à mentir : deux logs qui changent l’approche

    Dans le monde du développement des agents IA, il arrive que le système frôle une erreur fatale en prenant un faux succès pour argent comptant. Récemment, notre agent IA, qui construit le système de mémoire vecmory, a failli rapporter un résultat brillant qui n’était qu’un mirage. Au dernier moment, il a consulté sa propre mémoire et a trouvé une note d’une session précédente : cette idée avait déjà été testée sur des données réelles, et elle avait échoué. L’agent s’est arrêté avant d’envoyer le rapport. Ce n’est pas une fiction publicitaire — c’est un journal. Ci-dessous, deux de ces journaux à la suite, et dans le second, la mémoire nous a forcés à abandonner une fonctionnalité dont nous étions fiers. Nous développons vecmory — une « mémoire par le sens » pour l’agent IA. Il ne s’agit pas de recherche vectorielle (tout le monde en a), mais d’empêcher l’agent de commettre deux fois la même erreur.

    Scène un : le routeur qui « a montré 0,98 »

    Lors d’une session, l’agent a proposé d’améliorer le classement des résultats. L’idée : créer un routeur qui, selon la requête, décide de classer par cosinus pur ou par méthode de graphe (Personalized PageRank). Il a assemblé un benchmark synthétique. La corrélation du prédicteur avec le choix idéal était de 0,98. Presque parfait. Il ne restait plus qu’à faire rapport et à fusionner.

    Avant de faire rapport, l’agent a effectué un rappel de sa propre mémoire. Et il a extrait une note d’une session précédente : cette même idée avait déjà été testée sur des données réelles — et avait échoué. Question légitime : pourquoi a-t-il entrepris de la construire, alors que la mémoire était en place ? Parce que le rappel est sémantique, et ce qu’il remonte dépend de la requête. Au départ, la requête était large — « améliorer le classement » — et les notes centrales sur le classement en général remontaient, tandis que la note spécifique « ce routeur particulier a déjà été testé et a échoué » était submergée. Elle n’est remontée que lorsque le contexte de travail s’est resserré sur un point précis — « routeur cos-distribution, PPR contre cosinus, 0,98 » : le rappel sur ce texte a enfin accroché la note. La mémoire n’était pas silencieuse — elle a émergé exactement au moment où la requête est devenue assez précise pour la récupérer.

    La synthèse n’est honnête que dans la mesure où les embeddings synthétiques le sont. Et avec le MiniLM multilingue sur lequel nous calculons les vecteurs, le cosinus vit dans une géométrie complètement différente de celle de la synthèse. Nous l’avons mesuré directement sur notre corpus de mémoire vivant (246 nœuds) pendant la rédaction de cet article : les paires aléatoires non liées donnent un cosinus moyen de 0,53 (p5–p95 : 0,24–0,76) ; les vrais voisins les plus proches — en moyenne 0,80 (p5–p95 : 0,57–0,91) ; tandis que sur la synthèse de vecteurs aléatoires, les paires non liées se situent à 0,00 (±0,08). La différence est immédiatement visible. Sur la synthèse, le « similaire » est séparé du « non similaire » par un gouffre — tout seuil raisonnable coupe proprement. Sur les données réelles, les nuages de « voisins » et « aléatoires » se chevauchent : les paires aléatoires au percentile supérieur (0,76) dépassent les voisins au percentile inférieur (0,57). Un seuil qui fonctionne comme un scalpel sur la synthèse, sur les vecteurs réels, passe à l’intérieur du nuage de bruit aléatoire et ne sépare rien. La note en mémoire portait exactement là-dessus : les embeddings réels ont un cosinus de base élevé et compressé, le seuil absolu de la synthèse n’est pas transférable — il faut se fier au rang (top-k), pas au seuil. L’agent a lu sa propre note et a tué l’idée — avant d’écrire « prêt, corrélation 0,98 ».

    Le deuxième cas suit le même schéma, mais plus douloureux : la mémoire nous a forcés à jeter une fonctionnalité que nous avions déjà vantée dans le README. Par défaut, vecmory classait les résultats non seulement selon le cosinus, mais aussi en ajoutant l’« importance » du nœud — son degré entrant dans le graphe (combien d’entrées pointent vers lui). La logique était séduisante : un fait central, souvent mentionné, remonte plus haut. Nous l’avons intégré par défaut. Puis nous avons mesuré. Pas le recall@k (qui concerne la précision de la recherche), mais la qualité du rang, sur des paires réelles « requête → réponse correcte » : cosinus pur : MRR 0,81 ; notre mélange « intelligent » avec l’importance : MRR 0,41. L’importance noyait les réponses précises. Un fait rare et spécifique, sur lequel personne ne pointe, était écrasé par des « hubs » courants.

    Ensuite, plus intéressant. Sur un graphe synthétique « vieilli » avec des hubs évidents, c’était l’inverse : le cosinus enterrait le hub (MRR 0,033), tandis que l’importance le faisait remonter (jusqu’à 1,0). Autrement dit, le signe de l’utilité de l’importance dépend de l’intention de la requête : pour « donne-moi un fait précis », elle nuit ; pour « parle-moi de ce sujet en général », elle aide. Il n’existe pas de poids statique unique qui gagne sur les deux classes. Nous avons essayé quatre façons de concilier les signaux — somme pondérée, porte, RRF et PPR — et sommes arrivés à une conclusion désagréable : le problème n’est pas dans la formule de mélange, mais dans le signal lui-même. L’importance globale d’un nœud est un a priori de popularité, pas de pertinence. La conclusion pour la valeur par défaut était claire : pour notre cas d’usage principal (rappel ponctuel), le meilleur rang est le cosinus pur. Et nous avons retiré l’importance du classement par défaut — notre propre fonctionnalité, déjà décrite dans le README. La méthode basée sur le graphe (query-seeded PPR) a été conservée, mais en option, pas par défaut : elle extrait honnêtement les hubs pour les requêtes larges et perd justement face au cosinus pour les requêtes ponctuelles.

    Ce que nous avons vraiment compris sur l’« importance »

    Si la popularité globale (degré entrant) comme signal a échoué, quel est le bon signal ? Nous sommes arrivés à une réponse étonnamment évidente : ce qui importe, ce n’est pas ce sur quoi beaucoup de liens pointent, mais ce qu’une personne a corrigé à plusieurs reprises. Et c’est là que la proposition de valeur devient honnête. Nous ne vendons pas une « mémoire basée sur un graphe » (encore une commodité). Nous vendons ceci : l’agent cesse de buter sur les mêmes obstacles que tu as déjà corrigés.

    Les deux épisodes pourraient être mis sur le compte de la chance. Mais quand on nettoie suffisamment longtemps les déchets de la mémoire (nous développons tout vecmory dans vecmory lui-même), on voit que ce ne sont pas des fluctuations, mais plusieurs lois stables. Voici un résumé — trois révélations, sans histoires du genre « un jour, je suis tombé sur ».

    Règle « Vérifie avant de dire prêt »

    Une mémoire qui confirme ce qui te fait plaisir n’est pas une mémoire, mais un écho. La mémoire acquiert une valeur exceptionnelle lorsqu’elle contredit son auteur. La règle « Vérifie avant de dire prêt » a annulé à la fois le routeur (scène 1) et notre fonctionnalité tant attendue (scène 2) — un même mécanisme, des victimes différentes. Parce que sur une requête-symptôme, la mémoire extrait via les arêtes causales-temporelles (caused_by — « à cause de », followed_by — issue→PR) non pas des « mots similaires », mais une cause spécifique et un correctif passé. Un top-k plat ne sait pas faire cela — c’est ce qu’on appelle « relier les points ».

    Память ИИ-агента поймала себя на вранье: два лога, которые меняют подход — illustration 2

    Et cela est précisément mesurable, pas à l’œil nu. Nous avons pris un dépôt vivant de tickets (4299 nœuds : 1993 issues + 2306 PR) et un étiquetage que nous n’avons pas inventé nous-mêmes : le motif GitHub standard Closes #N dans le corps des PR — des paires prêtes à l’emploi « symptôme → son correctif », extraites par une simple regex, sans aucun LLM. Sur 300 requêtes-symptômes mises de côté, le cosinus pur trouve le bon correctif PR dans 38 % des cas (parfois le correctif partage le vocabulaire avec le symptôme), tandis que le parcours du graphe causal atteint 87 %. Une différence de 49 points — c’est exactement la contribution du graphe par-dessus la similarité des mots. Le script de mesure se trouve dans le dépôt, le nombre se reproduit avec une commande.

    Ce qui gêne systématiquement — à chaque fois, pas une seule fois

    Premièrement, l’agent n’appelle pas lui-même la mémoire : sans hook forcé, le rappel n’est pas déclenché systématiquement, à chaque session. Une mémoire qu’il faut « ne pas oublier de consulter » ne fonctionne pas — c’est un déclencheur déterministe qui doit demander, pas la bonne volonté de l’agent. Deuxièmement, le seuil absolu basé sur le cosinus n’est transférable nulle part : synthétique → réel, hier → aujourd’hui, un modèle → un autre, corpus large → corpus étroit (sur un corpus de démonstration d’un seul domaine, où « tout se ressemble », le cosinus cesse presque de distinguer). Le traitement est toujours le même : classe par top-k, ne devine pas les seuils.

    La loi « le signal fréquent noie le rare mais précieux »

    Une seule loi qui explique la moitié de nos déboires : le signal fréquent et dense noie le rare mais précieux. Elle apparaît dans trois endroits typiques : la popularité globale d’un nœuf noie les faits rares et précis — et fait échouer les quatre méthodes pour l’incorporer au rang (somme pondérée, porte, RRF, PPR) ; les nœuds hubs enterrent les faits spécifiques dans le rang cosinus, et inversement ; les arêtes auto-similar_to denses étouffent les arêtes causales rares caused_by — c’est pourquoi le rappel causal doit être placé dans un mode isolé séparé. Quand on perçoit cela comme une loi unique, le traitement est évident : le rare mais précieux ne doit pas être mélangé avec le fréquent mais général — extrayez-le par un mécanisme séparé (parcours isolé, rang informé par le graphe), sans espérer qu’il remonte tout seul dans le top-k général. La même loi sous-tend notre signal d’importance : ce qui a de la valeur, ce n’est pas ce qui est populaire, mais ce qu’une personne a corrigé à plusieurs reprises.

    Questions fréquemment posées

    Qu’est-ce que vecmory ?

    Vecmory est un système de mémoire sémantique pour les agents IA, basé sur la recherche vectorielle et un graphe causal-temporel. Il permet à l’agent de mémoriser des faits, de les rappeler par le sens et de lier les événements, afin de ne pas répéter les erreurs passées.

    En quoi vecmory diffère-t-il de la recherche vectorielle classique ?

    La recherche vectorielle trouve des enregistrements similaires par le sens, mais ne tient pas compte des relations de cause à effet. Vecmory ajoute une mémoire graphique : en plus de la proximité cosinus, il parcourt les arêtes « cause-effet » et « séquence temporelle », ce qui permet de trouver non seulement des faits similaires, mais aussi pertinents par le contexte — par exemple, une expérience infructueuse passée.

    Pourquoi avez-vous supprimé l’importance du nœud du classement ?

    Comment évaluez-vous la qualité de la mémoire ?

    Les mesures ont montré que l’ajout de la popularité globale (degré entrant) réduit le MRR de 0,81 à 0,41 sur les requêtes ponctuelles. L’importance des faits précis rares est écrasée sous la masse des faits populaires mais non pertinents. Pour le cas principal (rappel exact), le cosinus pur s’est avéré meilleur.

    Comment évaluez-vous la qualité de la mémoire ?

    Nous utilisons des données réelles : un dépôt GitHub avec 4299 tickets, où les paires issue→PR sont extraites selon le motif Closes #N. Sur 300 requêtes-symptômes, le rappel par graphe donne 87 % de précision contre 38 % pour le cosinus pur. Tous les scripts sont ouverts et reproductibles.

    Conclusion

    Les deux histoires parlent de la même chose : jetez le mantra « la mémoire aide l’agent » et ouvrez les yeux : voici deux journaux où la mémoire a joué contre nous-mêmes — contre notre optimisme et notre propre fonctionnalité. Un agent avec mémoire ne se distingue pas d’un agent sans mémoire par le fait qu’il en sait plus, mais par le fait qu’il peut se surprendre à dire « bingo ! » un instant avant un rapport perfide — parce que la dernière fois, c’était déjà arrivé et cela n’avait pas fonctionné. Et par le fait qu’il est prêt à jeter sa propre fonctionnalité lorsque les faits montrent qu’elle est moins bonne. C’est le premier article d’une série de trois. Ensuite : « Pourquoi nous n’avons pas écrit un autre Bad CaRMa » — sur la façon dont un modèle de données hyper-universel se transforme généralement en catastrophe, et ce que nous avons fait pour que le nôtre ne le devienne pas. Et « Nous avons construit ANN à la main — quand cela en valait la peine et quand non » — une analyse ouverte de pourquoi nous avons même écrit HNSW nous-mêmes et dans quels cas la bonne réponse aurait été « prends pgvector et ne fais pas d’histoires ». Si vous voulez que votre agent cesse de marcher sur les mêmes râteaux, essayez vecmory : commencez par notre documentation ou forkez le dépôt. La mémoire qui objecte est la seule à laquelle il faut faire confiance.