Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

microvec-0.2.0.tar.gz (279.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

microvec-0.2.0-py3-none-any.whl (11.8 kB view details)

Uploaded Python 3

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

Hashes for microvec-0.2.0.tar.gz
Algorithm Hash digest
SHA256 a8655bcb3f947ae0423978c2842ef239ce734a21120287a27f150dbc1de1e32c
MD5 97c2f1c250a020b6f8f4adb85e0daac4
BLAKE2b-256 608ac9f946b52cad974562fa091ec91c822bde72d801250aff1d86052aca8bbd

See more details on using hashes here.

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

Hashes for microvec-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 854f85bceab93ac0c26155a496c471f58abd3a7d0b3dfa9621a61c0c2eadf9f8
MD5 02654073d3c6685a0fbf263a07124e1b
BLAKE2b-256 6acbb470f405e4ec00370f22de4d55849999404f9f0dae7750029a0f75616c0b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page