Building a Chatbot with Long-Term Memory¶
This guide shows how to give your chatbot persistent, compositional memory. The bot remembers past conversations and can blend related memories to build richer context.
The Problem¶
Standard chatbots have two memory options:
- Context window — limited to the last N messages, then forgotten
- Vector store — retrieves individual past messages, no synthesis
Neither handles: "Based on everything you know about me, what should I try next?"
Engramma adds a third option: compositional memory that attends to multiple stored patterns simultaneously.
Basic Setup¶
import numpy as np
from engramma_memory import EngrammaMemory
def embed(text: str, dim: int = 128) -> np.ndarray:
"""Replace with your embedding model (OpenAI, sentence-transformers, etc.)"""
rng = np.random.default_rng(hash(text) % (2**32))
vec = rng.standard_normal(dim).astype(np.float32)
return vec / (np.linalg.norm(vec) + 1e-8)
class ChatbotWithMemory:
def __init__(self, dim: int = 128):
self.mem = EngrammaMemory(dim=dim, backend="local")
self.dim = dim
self.history: list = []
def remember(self, text: str):
"""Store a message in long-term memory."""
embedding = embed(text, self.dim)
self.mem.store(key=embedding, value=embedding)
self.history.append(text)
def recall(self, query: str, top_k: int = 3) -> list:
"""Find relevant past messages."""
embedding = embed(query, self.dim)
results = self.mem.query(embedding, top_k=top_k)
return [self._match(r["value"]) for r in results if r["score"] > 0.2]
def compose_context(self, topics: list) -> str:
"""Blend multiple topics into unified context (Engramma-exclusive)."""
embeddings = [embed(t, self.dim) for t in topics]
composed = self.mem.compose(embeddings)
return self._match(composed)
def _match(self, value: np.ndarray) -> str:
"""Find the stored message closest to a value vector."""
value = value.flatten()[:self.dim]
best_dist, best_text = float('inf'), ""
for text in self.history:
dist = float(np.linalg.norm(embed(text, self.dim) - value))
if dist < best_dist:
best_dist, best_text = dist, text
return best_text
Usage¶
bot = ChatbotWithMemory()
# User tells the bot things over time
bot.remember("I love programming in Python")
bot.remember("Machine learning is fascinating")
bot.remember("I'm building a recommendation system")
bot.remember("My project deadline is next Friday")
bot.remember("I need help with PyTorch")
Simple Recall¶
# "What does this user know about AI?"
recalled = bot.recall("AI projects", top_k=3)
# -> ["Machine learning is fascinating", "I'm building a recommendation system", ...]
Compositional Context (Engramma Advantage)¶
# "What connects Python AND machine learning for this user?"
context = bot.compose_context(["Python", "machine learning"])
# -> Returns the message that best bridges both topics
This is fundamentally different from retrieving "Python" results and "ML" results separately. Engramma's multi-head attention finds patterns that relate to both topics simultaneously.
With a Real LLM¶
import openai
class SmartChatbot(ChatbotWithMemory):
def respond(self, user_message: str) -> str:
# Remember what the user said
self.remember(user_message)
# Recall relevant context
context = self.recall(user_message, top_k=5)
# Build prompt with memory
system = (
"You are a helpful assistant with memory of past conversations.\n"
f"Relevant memories:\n" + "\n".join(f"- {c}" for c in context)
)
response = openai.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": system},
{"role": "user", "content": user_message},
]
)
answer = response.choices[0].message.content
# Remember the response too
self.remember(answer)
return answer
With LangChain¶
from engramma_memory.integrations.langchain import EngrammaLangChainMemory
from langchain.chains import ConversationChain
from langchain_openai import ChatOpenAI
memory = EngrammaLangChainMemory(dim=128, embed_fn=embed)
chain = ConversationChain(llm=ChatOpenAI(), memory=memory)
response = chain.invoke({"input": "Tell me about Python"})
Forgetting Old Information¶
# User updates their preferences
bot.remember("Actually, I switched from PyTorch to JAX")
# Decay the old information
old_embedding = embed("I need help with PyTorch")
bot.mem.forget(old_embedding, strategy="decay")
Production Considerations¶
The local backend is great for prototyping but has limits:
| Limit | Local | Cloud |
|---|---|---|
| Max memories | 1000 | Unlimited |
| Persistence | RAM only | Persistent |
| Weighted composition | Equal only | Custom weights |
For a production chatbot, switch to cloud:
class ProductionChatbot(ChatbotWithMemory):
def __init__(self):
self.mem = EngrammaMemory(dim=128, backend="cloud", api_key="nx_live_...")
# Everything else stays the same
See Migrating to Engramma Cloud for the full guide.