Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mini-mem0

A small, readable memory layer for LLM apps: a simplified mem0 clone.

It extracts durable facts from conversations, stores them as embeddings, and (crucially) reconciles new facts against what it already knows instead of blindly appending. So your app can "remember" a user across sessions: that they're vegetarian, that they moved from Pune to Bangalore, that they no longer eat meat.

conversation ──▶ extract facts ──▶ embed ──▶ search similar ──▶ decide ──▶ ADD / UPDATE / DELETE / NOOP

Why this is more than a vector DB

A naive memory is "embed every message and append." That accumulates duplicates and keeps stale, contradicted facts forever. The interesting part of mem0, and of this clone, is the update decision: before storing a new fact, look at similar existing memories and choose one of four operations.

Event When Example
ADD Genuinely new information new: "Allergic to peanuts"
UPDATE Refines / corrects an existing memory (same id) "Lives in Pune""Lives in Bangalore"
DELETE Contradicts an existing memory, no replacement "Eats meat" removed after "I stopped eating meat"
NOOP Already known "Lives in Pune" stated again

This is the most important logic in the codebase. See memory/memory.py (add_gather_candidates_decide_apply) and the prompts in memory/prompts.py.


The memory loop in detail

add(user_id, messages)

  1. Extract atomic facts from the conversation with one LLM call ({"facts": ["Lives in Pune", "Is vegetarian"]}).
  2. Embed each fact and search the user's existing memories for similar ones.
  3. Reconcile in a single LLM call. The retrieved memories are presented to the LLM as a numbered list (integer id to real UUID map), so the LLM only ever sees small integers and cannot hallucinate a database id. It returns one operation per fact:
    {"memory": [
      {"id": 0, "text": "Lives in Bangalore", "event": "UPDATE", "old_memory": "Lives in Pune"},
      {"id": 7, "text": "Is vegetarian",       "event": "ADD"}
    ]}
  4. Apply each operation: write to the vector store + SQLite, and append a row to the history log. If no similar memories exist, the LLM call is skipped and every fact is an ADD. Malformed ids (e.g. an UPDATE to a non-existent memory) fail safe to ADD.

search(user_id, query, top_k=5)

Embeds the query and returns the most similar memories for that user, with a similarity score. SQLite is the source of truth for the memory text; the vector store only holds embeddings + ids.

get_all(user_id) / delete(memory_id) / history(memory_id)

Basic management. History is recorded before a delete, so the change log of a memory (ADD → UPDATE → DELETE) survives even after the memory itself is gone.


Architecture

memory/                 core library (no FastAPI dependency)
├── memory.py           the Memory class: add / search / get_all / delete / history
├── prompts.py          fact-extraction + update-decision prompts
├── llm.py              BaseLLM + OpenAI / Ollama backends + get_llm() factory
├── embeddings.py       SentenceTransformerEmbedder (prod) + HashEmbedder (offline/tests)
└── store.py            SQLiteMetadataStore + ChromaVectorStore + InMemoryVectorStore
api/
└── main.py             FastAPI app exposing the routes
tests/
└── test_memory.py      pytest tests for ADD / UPDATE / DELETE / NOOP, search, history
demo.py                 multi-turn conversation demo (offline by default)

Everything is built from four pluggable parts (an LLM, an embedder, a vector store, and a metadata store) injected into Memory. Production uses Chroma + sentence-transformers + SQLite + your chosen LLM; the tests swap in fakes so the decision logic is exercised with no network and no model download.

Swapping the LLM (API ↔ local)

All LLM access goes through memory/llm.py. Select a backend with env vars:

# Local model via Ollama (default)
export MEM0_LLM_PROVIDER=ollama
export MEM0_LLM_MODEL=llama3.1

# Hosted / OpenAI-compatible API
export MEM0_LLM_PROVIDER=openai
export MEM0_LLM_MODEL=gpt-4o-mini
export OPENAI_API_KEY=sk-...
# export OPENAI_BASE_URL=...   # any OpenAI-compatible endpoint

Install

python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

Run the demo

The demo runs fully offline by default (a rule-based stand-in LLM + a lexical hash embedder, no downloads) so you can see the loop immediately:

python demo.py

It feeds a 4-turn conversation, prints the ADD/UPDATE/DELETE/NOOP decision per turn, shows that "Lives in Pune" is replaced by "Lives in Bangalore", and recalls the vegetarian fact stated in turn 1. To use the real stack (sentence-transformers + Chroma + your LLM):

python demo.py --real

Run the service

uvicorn api.main:app --reload

Routes

Method Path Body / Query
POST /memories {"user_id": "...", "messages": [{"role": "user", "content": "..."}]}
GET /memories/search ?user_id=&query=&top_k=5
GET /memories ?user_id=
DELETE /memories/{id} (none)
GET /memories/{id}/history (none)
# add
curl -X POST localhost:8000/memories -H 'content-type: application/json' \
  -d '{"user_id":"ravi","messages":[{"role":"user","content":"I live in Pune and I am vegetarian"}]}'

# search
curl 'localhost:8000/memories/search?user_id=ravi&query=where%20do%20they%20live'

# history of one memory
curl localhost:8000/memories/<id>/history

Run the tests

pytest

The tests drive the memory loop with a scripted fake LLM and the offline hash-embedder / in-memory vector store, so the ADD / UPDATE / DELETE / NOOP decision, per-user search scoping, and the history log are all verified deterministically, with no API keys and no model downloads.


Design notes & simplifications vs. real mem0

  • Single batched decision call over all extracted facts + retrieved candidates (like mem0), rather than one call per fact. This lets the LLM consolidate related facts and resolve contradictions across the whole batch.
  • Integer-id indirection for retrieved memories prevents the LLM from inventing UUIDs.
  • Kept simple on purpose: no entity/graph store, no async batching, no lemmatized hash dedup, no agent/run scoping. Just the user_id-scoped fact memory that makes the add/update loop clear.

About

A lightweight memory layer for LLM apps (simplified mem0): extracts durable facts from conversations, embeds them, and reconciles ADD/UPDATE/DELETE/NOOP instead of blindly appending.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages