NGT Memory : Mémoire persistante pour LLM avec graphe associatif et profil utilisateur
NGT Memory implémente une mémoire persistante pour les grands modèles de langage sans stockage externe. Le module conserve les informations utilisateur entre les sessions, récupère le contexte pertinent et l'injecte dans l'invite. Trois mécanismes de récupération garantissent la précision : la similarité cosinus des embeddings, un graphe associatif hebbien et une consolidation hiérarchique.
La similarité cosinus compare l'embedding de la requête avec les faits stockés. Le graphe relie les concepts qui s'activent ensemble : mentionner un régime alimentaire avec une requête sur les restaurants fait remonter le fait pertinent. La consolidation promeut les faits fréquemment utilisés en mémoire à long terme, déplaçant ceux rarement sollicités.
Le temps de récupération est de 2 à 3 ms sur CPU. Les principaux délais proviennent de l'API OpenAI : embeddings ~700 ms, génération ~800–1500 ms.
Installation et API
Le module s'exécute via Docker :
git clone https://github.com/ngt-memory/ngt-memory
cd ngt-memory
cp .env.example .env # OPENAI_API_KEY
docker-compose up -d
Code client :
import httpx
client = httpx.Client(base_url="http://localhost:9190")
# Stockage des faits
client.post("/chat", json={
"message": "Je suis végétarien et j'habite à Moscou.",
"session_id": "user_42"
})
# Récupération par session
r = client.post("/chat", json={
"message": "Que devrais-je manger ?",
"session_id": "user_42"
})
print(r.json()["response"]) # Restaurants végétariens à Moscou
print(r.json()["memories_count"]) # 2
Points de terminaison : /chat, /store, /retrieve, /session/reset, /health.
Profil utilisateur structuré
Le système extrait des champs des messages : âge, ville, régime alimentaire, allergies. Les données sont injectées dans l'invite système avec priorité :
[PROFIL UTILISATEUR — faits structurés, priorité maximale]
- âge : 30
- ville : "Moscou"
- régime : "végétarien"
[FIN PROFIL UTILISATEUR]
[CONTEXTE MÉMOIRE — faits vérifiés sur cet utilisateur]
1. [0.91] Je suis végétarien et j'habite à Moscou.
[FIN CONTEXTE MÉMOIRE]
Assemblage de fragments : "Je suis" + "30" + "ans" → "J'ai 30 ans" → âge=30 (confiance 0.6).
Résolution des conflits : Empêche la diminution de l'âge sans la phrase "Je me suis trompé." Mode correction — 60 secondes.
Filtre de qualité et architecture
Le filtre élimine les données inutiles :
- Nombres purs, caractères spéciaux, mots uniques
- Moins de 6 lettres
- Réponses de l'assistant aux données inutiles de l'utilisateur
Architecture :
- POST /chat
- OpenAI text-embedding-3-small (~700 ms)
- Extraction du profil (regex)
- Récupération NGT Memory (cosinus + graphe, 2–3 ms)
- OpenAI gpt-4.1-nano (~800–1500 ms)
- Filtre → stockage
Stack : FastAPI, AsyncOpenAI, Pydantic. Stockage en RAM d'un processus unique.
Résultats des expériences
Exp 44 : Précision factuelle avec mémoire 2.44/3 (+100% vs sans mémoire 1.22/3).
Exp 48 : Test A/B, 94% de victoires mémoire (17/18), score moyen 0.889 vs 0.056.
Exp 49 : 51/54 (94%) sur les cas limites : interlangue, assemblage, conflits.
Performances (5000 faits) :
| Opération | Débit | p50 |
|-----------|------------|-----|
| store() | 3450/s | 0.29 ms |
| retrieve()| 150/s | 6.3 ms |
End-to-end : mémoire 2.5 ms, embeddings 764 ms.
Défis de mise à l'échelle
- Multi-worker : chaque worker a son propre SessionStore. Solution :
--workers 1ou Redis. - Invite douce : le modèle ignorait les faits. Renforcée : "Considérez chaque fait comme une vérité absolue."
- Bugs regex :
Je suis allergique→nom= allergique. Liste noire 25+ mots.
Points clés à retenir
- Combiner cosinus + graphe + consolidation atteint 94% sur les tests limites.
- Le profil structuré avec priorité dans l'invite double la précision.
- Le filtre de qualité empêche la dégradation de la recherche par les données inutiles.
- Latence mémoire 2–3 ms, pas un goulot d'étranglement.
- Open source, BSL 1.1, prêt pour Docker.
— Editorial Team
Aucun commentaire pour le moment.