Lightweight local vector database with persistence to disk, supporting multiple similarity metrics and easy-to-use API.
Project description
microvector
Lightweight local vector database with persistence to disk, supporting multiple similarity metrics and an easy-to-use API.
A refactor and repackaging of HyperDB optimized for CPU-only environments with improved type safety and developer experience.
Features
- 🚀 Simple API: Clean, intuitive interface with
PartitionStorepattern - 💾 Persistent Storage: Automatically caches vector stores to
.pickle.gzfiles - 🔍 Multiple Similarity Metrics: Choose from cosine, dot product, Euclidean, or Derrida distance
- 🎯 Type Safe: Full type annotations with strict pyright compliance
- ⚡ CPU Optimized: Designed for CPU-only environments (no CUDA required)
- 🔄 Flexible Caching: Use persistent stores or create temporary in-memory collections
- 📦 Easy Installation: One-command setup with automatic PyTorch CPU configuration
- ✨ Partition-level Operations: Add, remove, and search documents through dedicated store objects
Installation
pip install microvector
Or for development:
git clone https://github.com/loganpowell/microvector.git
cd microvector
uv sync
Quick Start
from microvector import Client
# Initialize the client
client = Client()
# Save a collection (by default, in-memory only)
store = client.save(
partition="my_documents",
collection=[
{"text": "Python is a popular programming language", "category": "tech"},
{"text": "Machine learning models learn from data", "category": "ai"},
{"text": "The quick brown fox jumps over the lazy dog", "category": "example"},
],
key="text"
)
# Search using the store
results = store.search(
term="artificial intelligence and ML",
)
for result in results:
print(f"Score: {result['similarity_score']:.4f} - {result['text']}")
# Add more documents (also in-memory by default)
store.add([
{"text": "Deep learning uses neural networks", "category": "ai"}
])
# Search again with the updated store
results = store.search("neural networks", top_k=3)
# Persist to disk when ready
client.save(
partition="my_documents",
collection=store.to_dict(),
cache=True # Now save to disk
)
API Reference
Client
The main interface for creating and managing vector stores.
Client(
model_cache: str = "./.cached_models",
vector_cache: str = "./.vector_cache",
embedding_model: str = "avsolatorio/GIST-small-Embedding-v0",
search_algo: str = "cosine"
)
Parameters:
| Parameter | Description | Default |
|---|---|---|
model_cache |
Directory for caching downloaded embedding models | "./.cached_models" |
vector_cache |
Directory for persisting vector stores | "./.vector_cache" |
embedding_model |
HuggingFace model name for generating embeddings | "avsolatorio/GIST-small-Embedding-v0" |
search_algo |
"cosine", "dot", "euclidean", or "derrida" |
"cosine" |
Note: The search_algo is set at the client level and applies to all partitions created by that client. This ensures consistency and prevents issues with switching algorithms on already-normalized vectors.
save()
Create or update a vector store and return a PartitionStore for operations.
store = client.save(
partition: str,
collection: list[dict[str, Any]],
key: str = "text",
cache: bool = False,
append: bool = False
) -> PartitionStore
Parameters:
| Parameter | Description | Default |
|---|---|---|
partition |
Unique identifier for this vector store | - |
collection |
List of documents (dictionaries) to vectorize | - |
key |
Field name to use for embedding | "text" |
cache |
If True, persist to disk; if False, keep in-memory only | False |
append |
If True, add to existing store; if False, replace | False |
Returns: PartitionStore object with methods for searching and managing documents
Example:
store = client.save(
partition="products",
collection=[
{"description": "Wireless headphones", "price": 99.99},
{"description": "Smart watch", "price": 299.99},
],
key="description",
cache=True # Persist to disk
)
print(f"Partition: {store.partition}")
print(f"Size: {store.size}")
print(f"Algorithm: {store.algo}")
PartitionStore
A partition-specific interface for vector operations returned by client.save().
Attributes:
| Attribute | Type | Description |
|---|---|---|
partition |
str | Name of the partition |
key |
str | Field being vectorized |
algo |
str | Similarity algorithm in use ("cosine", "dot", "euclidean", or "derrida") |
size |
int | Number of documents in the store (read-only property) |
Methods:
search()
Search this partition for similar documents.
results = store.search(
term: str,
top_k: int = 5
) -> list[dict[str, Any]]
Parameters:
| Parameter | Description | Default |
|---|---|---|
term |
Search query string | - |
top_k |
Maximum number of results to return | 5 |
Returns: List of documents with similarity scores
[
{
"text": "Machine learning is awesome",
"category": "ai",
"similarity_score": 0.923
},
...
]
Example:
results = store.search("laptop computers", top_k=3)
for result in results:
print(f"{result['similarity_score']:.3f} - {result['description']}")
add()
Add new documents to this partition.
success = store.add(
collection: list[dict[str, Any]] | dict[str, Any],
cache: bool = False
) -> bool
Parameters:
| Parameter | Description | Default |
|---|---|---|
collection |
Single document or list of documents to add | - |
cache |
If True, persist changes; if False, keep in-memory | False |
Returns: True if successful, False otherwise
Example:
# Add a single document
store.add({"description": "USB-C cable", "price": 12.99})
# Add multiple documents
store.add([
{"description": "Keyboard", "price": 79.99},
{"description": "Mouse pad", "price": 19.99}
], cache=True)
print(f"Updated size: {store.size}")
remove()
Remove a document from this partition by index or content.
success = store.remove(
item: int | dict[str, Any],
cache: bool = False
) -> bool
Parameters:
| Parameter | Description | Default |
|---|---|---|
item |
Document index (int) or document content (dict) to remove | - |
cache |
If True, persist changes; if False, keep in-memory | False |
Returns: True if successful, False otherwise
Example:
# Remove by index
store.remove(0)
# Remove by document content
store.remove({"description": "Wireless headphones", "price": 99.99})
delete()
Delete this partition's cache file from disk.
success = store.delete() -> bool
Returns: True if successful, False otherwise
Example:
if store.delete():
print(f"Partition '{store.partition}' deleted from disk")
Similarity Algorithms
| Algorithm | Best For | Range |
|---|---|---|
cosine |
General text similarity (default) | 0-1 (higher is more similar) |
dot |
When magnitude matters | Unbounded |
euclidean |
Spatial distance | 0-∞ (lower is more similar) |
derrida |
Experimental alternative distance | 0-∞ (lower is more similar) |
Advanced Usage
Using Different Algorithms
The similarity algorithm is set at the client level and applies to all partitions:
# Create clients with different algorithms
cosine_client = Client(search_algo="cosine")
dot_client = Client(search_algo="dot")
euclidean_client = Client(search_algo="euclidean")
# Each client's partitions use its algorithm
cosine_store = cosine_client.save("docs_cosine", documents)
dot_store = dot_client.save("docs_dot", documents)
# Different algorithms, different results
cosine_results = cosine_store.search("query")
dot_results = dot_store.search("query")
Why client-level? Vectors are normalized based on the algorithm. Switching algorithms on existing vectors would produce incorrect results, so we lock the algorithm at creation time for consistency.
Custom Embedding Models
Use any HuggingFace sentence-transformer model:
client = Client(
embedding_model="intfloat/e5-small-v2",
search_algo="cosine"
)
Nested Key Paths
Access nested fields using dot notation:
collection = [
{
"product": {
"name": "Laptop",
"specs": {"cpu": "Intel i7"}
}
},
{
"product": {
"name": "Mouse",
"specs": {"cpu": None}
}
}
]
store = client.save(
partition="products",
collection=collection,
key="product.name" # Extract "Laptop", "Mouse" from nested structure
)
# Search works on the nested field
results = store.search("computer", top_k=1)
print(results[0]["product"]["name"]) # "Laptop"
Working with Multiple Partitions
Organize different datasets in separate partitions:
# Create stores for different content types
news_store = client.save("news_articles", news_data, key="content")
review_store = client.save("product_reviews", review_data, key="review_text")
ticket_store = client.save("support_tickets", tickets, key="description")
# Search each independently
news_results = news_store.search("economy")
review_results = review_store.search("quality")
ticket_results = ticket_store.search("login issue")
Incremental Updates
Add new documents to existing stores without replacing them:
# Create initial store (in-memory by default)
store = client.save(
partition="knowledge_base",
collection=[
{"text": "Python is a programming language"},
{"text": "JavaScript runs in browsers"},
]
)
print(f"Initial size: {store.size}") # 2
# Add more documents using the store's add() method
store.add([
{"text": "TypeScript adds types to JavaScript"},
{"text": "Rust is memory-safe"},
])
print(f"Updated size: {store.size}") # 4
# Or use save() with append=True
client.save(
partition="knowledge_base",
collection=[{"text": "Go is designed for concurrency"}],
append=True
)
# Persist all changes to disk when ready
store.add([], cache=True) # Flush to disk
Persistent Storage
Enable caching to persist vector stores to disk:
# Save directly to disk
store = client.save(
partition="permanent_docs",
collection=documents,
cache=True # Persist immediately
)
# Add documents and persist
store.add(new_documents, cache=True)
# Remove documents and persist
store.remove(0, cache=True)
# Later, load from cache
loaded_store = client.save(
partition="permanent_docs",
collection=[], # Empty collection loads from cache
cache=True
)
In-Memory Operations
Work with documents without persisting to disk (default behavior):
# Create a temporary store (cache=False is the default)
temp_store = client.save(
partition="temp_analysis",
collection=documents
)
# Add documents without caching (default behavior)
temp_store.add(more_documents)
# Search as normal
results = temp_store.search("query")
# Since cache=False (default), changes aren't persisted
Document Management
# Create store (in-memory by default)
store = client.save("products", initial_products)
# Add new products (in-memory)
store.add([
{"name": "New Product", "price": 49.99}
])
# Remove by index (in-memory)
store.remove(0)
# Remove by content (in-memory)
store.remove({"name": "Old Product", "price": 19.99})
# Check current size
print(f"Current inventory: {store.size} items")
# Persist changes to disk
store.add([], cache=True)
# Or delete entire partition from disk
if store.delete():
print("Partition deleted")
Development Setup
This project uses uv for dependency management and automatically configures CPU-only PyTorch.
Quick Start
-
Install dependencies:
uv sync -
Verify setup:
uv run python setup_dev.py
-
Run tests:
uv run pytest
-
Type checking:
uv run pyright
What Gets Installed
- PyTorch (CPU-only): Automatically from PyTorch CPU index
- Transformers: HuggingFace transformers library
- Sentence Transformers: For embedding generation
- NumPy: Numerical computing
No special flags or manual PyTorch installation needed - just uv sync and go!
Performance Tips
- Reuse Client instances - Model loading is expensive
- Use persistent caching - Vector computation is cached automatically
- Batch your saves - Save collections together when possible
- Choose the right algorithm - Cosine is fastest for most use cases
- Adjust top_k - Lower values are faster
Architecture
microvector/
├── main.py # Client API
├── partition_store.py # PartitionStore class for partition operations
├── store.py # Vector storage and similarity search
├── cache.py # Persistence layer
├── embed.py # Embedding generation
├── search.py # Search utilities
├── algos.py # Similarity algorithms
└── utils.py # Helper functions
License
MIT License - see LICENSE file for details.
Credits
Based on HyperDB by John Dagdelen. Refactored and maintained by Logan Powell.
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 microvector-0.3.11.tar.gz.
File metadata
- Download URL: microvector-0.3.11.tar.gz
- Upload date:
- Size: 176.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c61bb38ec0763b9d58b4cbea485c36e60e3197d5dc003ee8aa24cdba7a4abdf
|
|
| MD5 |
fdc72fb5057a4623383e470568199610
|
|
| BLAKE2b-256 |
d8ac675bdfbaa9b0f349f38e09882037b94394aed43226fa7f22f6b82f945891
|
Provenance
The following attestation bundles were made for microvector-0.3.11.tar.gz:
Publisher:
publish.yml on loganpowell/microvector
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
microvector-0.3.11.tar.gz -
Subject digest:
5c61bb38ec0763b9d58b4cbea485c36e60e3197d5dc003ee8aa24cdba7a4abdf - Sigstore transparency entry: 714114742
- Sigstore integration time:
-
Permalink:
loganpowell/microvector@bb18d84de050676bbc3a1c01141dce9ebbee7af2 -
Branch / Tag:
refs/tags/v0.3.11 - Owner: https://github.com/loganpowell
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bb18d84de050676bbc3a1c01141dce9ebbee7af2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file microvector-0.3.11-py3-none-any.whl.
File metadata
- Download URL: microvector-0.3.11-py3-none-any.whl
- Upload date:
- Size: 36.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0df811506b651b85fbb4521af4c81367caf05a78f12fc345ad200e99bff10f4a
|
|
| MD5 |
4b3a96f0b37efcf2ba2725d41269ee06
|
|
| BLAKE2b-256 |
f70f71af8de2718cd7f0c1f87015849b493cad890e9d5d0d5b24b44aebaa0d31
|
Provenance
The following attestation bundles were made for microvector-0.3.11-py3-none-any.whl:
Publisher:
publish.yml on loganpowell/microvector
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
microvector-0.3.11-py3-none-any.whl -
Subject digest:
0df811506b651b85fbb4521af4c81367caf05a78f12fc345ad200e99bff10f4a - Sigstore transparency entry: 714114743
- Sigstore integration time:
-
Permalink:
loganpowell/microvector@bb18d84de050676bbc3a1c01141dce9ebbee7af2 -
Branch / Tag:
refs/tags/v0.3.11 - Owner: https://github.com/loganpowell
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bb18d84de050676bbc3a1c01141dce9ebbee7af2 -
Trigger Event:
release
-
Statement type: