A lightweight, production-grade in-memory vector database
Project description
MicroVector
A lightweight, production-grade in-memory vector database for Python.
pip install microvector
No external services. No complex setup. Just numpy and your embeddings.
Features
- Fast similarity search — cosine, euclidean, and dot product metrics
- Rich results — every search result includes the document text and metadata, not just an index
- Batch operations — insert thousands of vectors in one call
- Metadata filtering — filter candidates before scoring with any Python predicate
- Safe persistence — JSON + numpy format (no pickle, no security risk)
- Full type hints — works great with mypy and IDEs
- Zero required dependencies beyond numpy
Quick Start
microvec stores and searches vectors — you bring the embeddings from any model you like.
With OpenAI
import numpy as np
from openai import OpenAI
from microvector import MicroVectorDB
client = OpenAI() # uses OPENAI_API_KEY env var
def embed(text: str) -> np.ndarray:
result = client.embeddings.create(input=text, model="text-embedding-3-small")
return np.array(result.data[0].embedding, dtype=np.float32)
db = MicroVectorDB() # dimension inferred from first insert
db.add_node(embed("Paris is the capital of France"), "Paris is the capital of France")
db.add_node(embed("The Eiffel Tower is in Paris"), "The Eiffel Tower is in Paris")
db.add_node(embed("Python is a programming language"), "Python is a programming language")
results = db.search_top_k(embed("What city is the Eiffel Tower in?"), k=2)
for r in results:
print(f"{r.score:.3f} {r.document}")
# 0.921 The Eiffel Tower is in Paris
# 0.887 Paris is the capital of France
With a local model (no API key)
import numpy as np
from sentence_transformers import SentenceTransformer
from microvector import MicroVectorDB
model = SentenceTransformer("all-MiniLM-L6-v2") # pip install sentence-transformers
def embed(text: str) -> np.ndarray:
return model.encode(text, normalize_embeddings=True)
db = MicroVectorDB() # dimension inferred from first insert
db.add_node(embed("Paris is the capital of France"), "Paris is the capital of France")
results = db.search_top_k(embed("What is the capital of France?"), k=1)
print(results[0].document) # Paris is the capital of France
Save and reload
db.save("my_knowledge_base")
db = MicroVectorDB.load("my_knowledge_base")
Installation
Requires Python 3.9+ and numpy >= 1.21.
pip install microvector
Or from source:
git clone https://github.com/huolter/microVector
cd microVector
pip install -e ".[dev]"
API Reference
MicroVectorDB(dimension=None)
Create a new database. If dimension is omitted, it is inferred automatically from the first vector inserted and locked for all subsequent inserts.
db = MicroVectorDB() # recommended — dimension inferred from first insert
db = MicroVectorDB(dimension=512) # or set explicitly
add_node(vector, document, metadata=None, num_bits=None) → int
Add a single vector. Returns the assigned index.
idx = db.add_node(
vector=np.array([...]),
document="The text this vector represents",
metadata={"source": "wikipedia", "date": "2024-01"},
num_bits=8, # optional: apply 8-bit scalar quantization
)
| Parameter | Type | Description |
|---|---|---|
vector |
np.ndarray |
1-D array matching the database dimension |
document |
str |
Text or identifier associated with this vector |
metadata |
dict |
Optional key-value pairs stored alongside the vector |
num_bits |
int |
Optional quantization (1–16 bits). Reduces memory at the cost of precision. |
add_nodes(vectors, documents, metadata=None) → list[int]
Batch insert. All inputs are validated before any insertion occurs.
import numpy as np
vectors = np.random.rand(1000, 512)
docs = [f"Document {i}" for i in range(1000)]
indices = db.add_nodes(vectors, docs)
search_top_k(query_vector, k, metric='cosine', filter_fn=None) → list[SearchResult]
Find the top-k most similar vectors. Returns results sorted by score descending.
results = db.search_top_k(query, k=5)
results = db.search_top_k(query, k=5, metric="euclidean")
# Filter before scoring — only consider documents tagged "news"
results = db.search_top_k(
query, k=5,
filter_fn=lambda node: node.metadata.get("type") == "news"
)
Metrics:
| Metric | Range | Notes |
|---|---|---|
cosine |
[-1, 1] | Best for text embeddings. Direction-based, scale-invariant. |
euclidean |
(0, 1] | 1 / (1 + dist). Identical vectors = 1.0. |
dot |
(-∞, +∞) | Fastest. Best for pre-normalized unit vectors. |
SearchResult fields:
result.index # int — node's assigned index
result.document # str — the document text
result.score # float — similarity score (higher = more similar)
result.metadata # dict — metadata stored with this node
search_by_threshold(query_vector, min_score, metric='cosine') → list[SearchResult]
Return all nodes with score >= min_score, sorted descending.
results = db.search_by_threshold(query, min_score=0.8)
get_node(index) → Node
Retrieve a node by index.
node = db.get_node(42)
print(node.document, node.metadata)
update_node(index, vector=None, document=None, metadata=None)
Update fields of an existing node. Omit fields to leave them unchanged.
db.update_node(42, document="Updated text")
db.update_node(42, metadata={"status": "reviewed"})
remove_node(index)
Remove a node. Its index is permanently retired (never reused).
db.remove_node(42)
save(path) / MicroVectorDB.load(path)
Persist to and restore from a .mvdb/ directory. Safe format: JSON + numpy binary.
db.save("my_db") # creates my_db.mvdb/
db = MicroVectorDB.load("my_db")
The .mvdb/ directory contains:
index.json— node metadata and documents (human-readable)vectors.npy— all vectors as a 2-D numpy array
Utility methods
len(db) # number of nodes
42 in db # check if index exists
repr(db) # MicroVectorDB(dimension=512, nodes=1000, next_index=1000)
db.stats() # {'count': 1000, 'dimension': 512, 'next_index': 1000, ...}
Exceptions
All exceptions inherit from MicroVectorError.
from microvector import (
MicroVectorError,
DimensionMismatchError, # wrong vector dimension
EmptyDatabaseError, # search on empty database
NodeNotFoundError, # get/update/remove non-existent index
)
Design Notes
Why in-memory? MicroVector is designed for small-to-medium datasets (up to ~100k vectors) where the simplicity of pure-Python outweighs the need for a dedicated service. It starts instantly, needs no configuration, and is trivially embeddable in any Python application.
Why not pickle? Pickle can execute arbitrary code when loading untrusted files. MicroVector uses JSON + numpy binary format which is safe, portable, and inspectable with any text editor.
Index monotonicity. Deleted indexes are never reused. This prevents stale external references from silently pointing to new data.
Roadmap
- Approximate nearest neighbor (HNSW)
- Voronoi cell indexing
- FAISS optional backend for large datasets
- Async search support
- Benchmarks
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file microvec-0.2.0.tar.gz.
File metadata
- Download URL: microvec-0.2.0.tar.gz
- Upload date:
- Size: 279.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a8655bcb3f947ae0423978c2842ef239ce734a21120287a27f150dbc1de1e32c
|
|
| MD5 |
97c2f1c250a020b6f8f4adb85e0daac4
|
|
| BLAKE2b-256 |
608ac9f946b52cad974562fa091ec91c822bde72d801250aff1d86052aca8bbd
|
File details
Details for the file microvec-0.2.0-py3-none-any.whl.
File metadata
- Download URL: microvec-0.2.0-py3-none-any.whl
- Upload date:
- Size: 11.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
854f85bceab93ac0c26155a496c471f58abd3a7d0b3dfa9621a61c0c2eadf9f8
|
|
| MD5 |
02654073d3c6685a0fbf263a07124e1b
|
|
| BLAKE2b-256 |
6acbb470f405e4ec00370f22de4d55849999404f9f0dae7750029a0f75616c0b
|