|
| 1 | +# LiveKit Memory |
| 2 | + |
| 3 | +In-process, in-memory **semantic memory** for LiveKit agents — sub-10ms end-to-end |
| 4 | +retrieval (embed the query *and* search) of a user's fixed context, fully self-hosted. |
| 5 | + |
| 6 | +```shell |
| 7 | +pip install livekit-memory[recommended] # static embedder + ANN index |
| 8 | +``` |
| 9 | + |
| 10 | +## Why it's fast |
| 11 | + |
| 12 | +The hard constraint for a voice agent loop is the *end-to-end* budget: you hand it text, |
| 13 | +it must embed and search in under 10ms. A transformer embedding on CPU alone is ~10ms |
| 14 | +(p99 ~50ms) and blows that. The route here: |
| 15 | + |
| 16 | +- **Static embeddings (Model2Vec)** — token-lookup + mean-pool, no transformer forward |
| 17 | + pass. ~0.03ms per short query on CPU. |
| 18 | +- **In-memory index** — exact brute-force cosine (sub-ms to ~100k vectors), automatically |
| 19 | + upgrading to a [usearch](https://github.com/unum-cloud/usearch) HNSW graph for large |
| 20 | + per-user corpora (~0.27ms search at 1M vectors). |
| 21 | + |
| 22 | +Measured on an Apple M4 Pro: **0.17ms median / 0.31ms p99 end-to-end at 1M vectors** — |
| 23 | +~30× under budget. |
| 24 | + |
| 25 | +## Usage |
| 26 | + |
| 27 | +```python |
| 28 | +from livekit.memory import MemoryStore, Model2VecEmbedder |
| 29 | + |
| 30 | +# one store per user / session |
| 31 | +store = MemoryStore(embedder=Model2VecEmbedder(), backend="auto", expected_size=1_000_000) |
| 32 | + |
| 33 | +# "fixed" facts about the user — pinned, always available |
| 34 | +store.upsert("name", "The user's name is Ada Lovelace.") |
| 35 | +store.upsert("units", "Prefers metric units.") |
| 36 | + |
| 37 | +# free-form semantic memories — ranked by relevance |
| 38 | +store.add("Discussed the analytical engine and Bernoulli numbers.", metadata={"session": 42}) |
| 39 | + |
| 40 | +# the latency-critical call your agent makes each turn: |
| 41 | +context = store.context("what should I call them, and what did we talk about?") |
| 42 | +# -> a prompt-ready string with the pinned facts + top relevant memories |
| 43 | + |
| 44 | +# or rank directly: |
| 45 | +for hit in store.search("mathematics history", limit=5): |
| 46 | + print(hit.score, hit.text) |
| 47 | +``` |
| 48 | + |
| 49 | +### Bring your own embedder |
| 50 | + |
| 51 | +`embedder` accepts an `Embedder`, any batched `list[str] -> vectors` callable (pass |
| 52 | +`dims=`), or `None` for the dependency-free `HashingEmbedder` (deterministic, no model |
| 53 | +download — good for tests/offline). For higher recall at the cost of latency, an ONNX |
| 54 | +transformer embedder can be wrapped via `CallableEmbedder`. |
| 55 | + |
| 56 | +### Persistence |
| 57 | + |
| 58 | +`store.save(dir)` / `MemoryStore.load(dir, embedder=...)` snapshot items and both indices |
| 59 | +to disk (the same embedder must be supplied on load). |
| 60 | + |
| 61 | +## Backends |
| 62 | + |
| 63 | +| Backend | When | Latency (384d) | |
| 64 | +|---|---|---| |
| 65 | +| `bruteforce` (default) | ≲100k vectors / user | exact, ~1.5ms @ 100k | |
| 66 | +| `usearch` | ≳100k vectors / user | HNSW, ~0.27ms @ 1M | |
| 67 | +| `auto` | picks per `expected_size` | — | |
| 68 | + |
| 69 | +`usearch` is an optional dependency (`pip install livekit-memory[ann]`); without it, |
| 70 | +`auto` stays on exact brute force. |
| 71 | + |
| 72 | +See https://docs.livekit.io for more information. |
0 commit comments