numpy-vector-store 0.1.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- numpy_vector_store-0.3.0/PKG-INFO +258 -0
- numpy_vector_store-0.3.0/README.md +234 -0
- {numpy_vector_store-0.1.0 → numpy_vector_store-0.3.0}/pyproject.toml +5 -4
- {numpy_vector_store-0.1.0 → numpy_vector_store-0.3.0}/src/numpy_vector_store/__init__.py +3 -3
- numpy_vector_store-0.3.0/src/numpy_vector_store/vector_store.py +419 -0
- numpy_vector_store-0.3.0/tests/test_vector_store.py +693 -0
- {numpy_vector_store-0.1.0 → numpy_vector_store-0.3.0}/uv.lock +4 -79
- numpy_vector_store-0.1.0/PKG-INFO +0 -205
- numpy_vector_store-0.1.0/README.md +0 -182
- numpy_vector_store-0.1.0/src/numpy_vector_store/vector_store.py +0 -253
- numpy_vector_store-0.1.0/tests/test_vector_store.py +0 -549
- {numpy_vector_store-0.1.0 → numpy_vector_store-0.3.0}/.github/FUNDING.yml +0 -0
- {numpy_vector_store-0.1.0 → numpy_vector_store-0.3.0}/.github/workflows/publish-pypi.yml +0 -0
- {numpy_vector_store-0.1.0 → numpy_vector_store-0.3.0}/.github/workflows/publish-testpypi.yml +0 -0
- {numpy_vector_store-0.1.0 → numpy_vector_store-0.3.0}/.gitignore +0 -0
- {numpy_vector_store-0.1.0 → numpy_vector_store-0.3.0}/LICENSE +0 -0
- {numpy_vector_store-0.1.0 → numpy_vector_store-0.3.0}/justfile +0 -0
- {numpy_vector_store-0.1.0 → numpy_vector_store-0.3.0}/tests/__init__.py +0 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: numpy-vector-store
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A fast, lightweight, and zero-setup in-memory vector store powered by NumPy
|
|
5
|
+
Project-URL: Homepage, https://github.com/tvanreenen/numpy-vector-store
|
|
6
|
+
Project-URL: Repository, https://github.com/tvanreenen/numpy-vector-store
|
|
7
|
+
Project-URL: Issues, https://github.com/tvanreenen/numpy-vector-store/issues
|
|
8
|
+
Author: Tim VanReenen
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: embeddings,numpy,search,store,vector
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: numpy>=1.20.0
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# NumPy Vector Store
|
|
26
|
+
|
|
27
|
+
A fast, lightweight, zero-setup in-memory vector store powered by NumPy.
|
|
28
|
+
|
|
29
|
+
- **Tiny local vector search** for projects that do not need a vector database
|
|
30
|
+
- **Fast exact vector search** using vectorized NumPy operations
|
|
31
|
+
- **Simple typed API** returning `VectorHit(index, value, metadata)`
|
|
32
|
+
- **Composable filtering** by passing prefiltered row indexes with `within_rows`
|
|
33
|
+
- **Portable persistence** as trusted local `.npz` files with `vectors` + `metadata`
|
|
34
|
+
- **No framework opinions**: bring your own embeddings, chunking, async, and metadata model
|
|
35
|
+
|
|
36
|
+
## Why?
|
|
37
|
+
|
|
38
|
+
This library is purpose-built for small to medium-scale vector search tasks and
|
|
39
|
+
offers a simple alternative to heavyweight vector databases when you do not need
|
|
40
|
+
network services, indexing infrastructure, ingestion pipelines, or domain-specific
|
|
41
|
+
metadata filtering.
|
|
42
|
+
|
|
43
|
+
## When/Where?
|
|
44
|
+
|
|
45
|
+
Below are benchmark results for cosine similarity search to help you assess its
|
|
46
|
+
suitability for your use case.
|
|
47
|
+
|
|
48
|
+
| Embedding Type | Dimensions | ~5ms | ~25ms | ~100ms | ~500ms |
|
|
49
|
+
|----------------|------------|------|--------|---------|---------|
|
|
50
|
+
| **Sentence Transformers** | 384 | 1K vectors<br/>1.5MB | 10K vectors<br/>15MB | 100K vectors<br/>147MB | 500K vectors<br/>732MB |
|
|
51
|
+
| **OpenAI Small** | 1536 | 500 vectors<br/>3MB | 5K vectors<br/>29MB | 25K vectors<br/>147MB | 100K vectors<br/>586MB |
|
|
52
|
+
| **OpenAI Large** | 3072 | 200 vectors<br/>2MB | 2.5K vectors<br/>29MB | 5K vectors<br/>59MB | 25K vectors<br/>293MB |
|
|
53
|
+
|
|
54
|
+
*Benchmarks performed on Apple M2 hardware.*
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
uv add numpy-vector-store
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import numpy as np
|
|
66
|
+
from numpy_vector_store import VectorStore
|
|
67
|
+
|
|
68
|
+
store = VectorStore[dict[str, str]](dimensions=3)
|
|
69
|
+
|
|
70
|
+
store.add(
|
|
71
|
+
vectors=np.array([
|
|
72
|
+
[1.0, 0.0, 0.0],
|
|
73
|
+
[0.0, 1.0, 0.0],
|
|
74
|
+
[0.0, 0.0, 1.0],
|
|
75
|
+
]),
|
|
76
|
+
metadata=[
|
|
77
|
+
{"title": "x-axis"},
|
|
78
|
+
{"title": "y-axis"},
|
|
79
|
+
{"title": "z-axis"},
|
|
80
|
+
],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
hits = store.cosine_search(
|
|
84
|
+
query=np.array([0.9, 0.1, 0.0]),
|
|
85
|
+
top_k=2,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
for hit in hits:
|
|
89
|
+
print(f"{hit.metadata['title']}: {hit.value:.3f}")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`metadata` is an opaque row payload returned with hits. It can be a dict,
|
|
93
|
+
dataclass, string, integer row ID, or any other Python object that fits your
|
|
94
|
+
application.
|
|
95
|
+
|
|
96
|
+
## Normalization
|
|
97
|
+
|
|
98
|
+
`VectorStore` defaults to `normalize=True`, which scales each stored vector to
|
|
99
|
+
length `1`. Normalization preserves vector direction while discarding magnitude:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
[3.0, 4.0] -> [0.6, 0.8]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This is the default because it makes cosine similarity fast and direction-only,
|
|
106
|
+
which is the common case for semantic embeddings. Use `normalize=False` when
|
|
107
|
+
vector length matters, such as when magnitude encodes strength, confidence,
|
|
108
|
+
counts, scale, or raw geometry.
|
|
109
|
+
|
|
110
|
+
Zero vectors are rejected in both modes because cosine similarity is undefined
|
|
111
|
+
for zero-norm vectors.
|
|
112
|
+
|
|
113
|
+
| Method | `normalize=True` default | `normalize=False` |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `cosine_search` | True cosine similarity over stored unit vectors; fastest/default path for embeddings | True cosine similarity over raw vectors; computes vector norms during search |
|
|
116
|
+
| `dot_search` | Dot product of unit vectors, effectively equivalent to cosine similarity | True dot product over original vectors; use when magnitude should affect ranking |
|
|
117
|
+
| `euclidean_search` | Distance between normalized directions; useful only when direction-normalized distance is intended | True Euclidean distance over original vectors; use for geometric/feature-space nearest neighbors |
|
|
118
|
+
| `get` | Returns normalized vectors | Returns original vectors |
|
|
119
|
+
| `save` | Saves normalized vectors | Saves raw vectors |
|
|
120
|
+
| `load` | Loads and normalizes vectors | Loads vectors exactly as stored |
|
|
121
|
+
|
|
122
|
+
## Search Methods
|
|
123
|
+
|
|
124
|
+
Use `cosine_search` for semantic embeddings and direction-only similarity:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
hits = store.cosine_search(query, top_k=10, min_value=0.75)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Use `dot_search` with `normalize=False` when larger-magnitude vectors should
|
|
131
|
+
rank higher:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
store = VectorStore[dict[str, str]](dimensions=3, normalize=False)
|
|
135
|
+
store.add(vectors, metadata)
|
|
136
|
+
hits = store.dot_search(query, top_k=10, min_value=0.0)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Use `euclidean_search` with `normalize=False` for raw coordinate or feature-space
|
|
140
|
+
nearest-neighbor search:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
store = VectorStore[dict[str, str]](dimensions=3, normalize=False)
|
|
144
|
+
store.add(vectors, metadata)
|
|
145
|
+
hits = store.euclidean_search(query, top_k=10, max_value=1.5)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Prefiltering
|
|
149
|
+
|
|
150
|
+
The store does not implement a metadata query language. To filter by metadata,
|
|
151
|
+
produce row indexes first, then pass them with `within_rows`.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
rows = [
|
|
155
|
+
i
|
|
156
|
+
for i, metadata in enumerate(store.metadata)
|
|
157
|
+
if metadata["title"].startswith("x")
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
hits = store.cosine_search(query, top_k=10, within_rows=rows)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
For structured NumPy metadata, use NumPy to produce the row indexes:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
metadata_table = np.array(
|
|
167
|
+
[
|
|
168
|
+
("intro", "A", 2024),
|
|
169
|
+
("setup", "A", 2023),
|
|
170
|
+
("guide", "B", 2024),
|
|
171
|
+
],
|
|
172
|
+
dtype=[("title", "U20"), ("product", "U10"), ("year", "i4")],
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
store = VectorStore[int](dimensions=3)
|
|
176
|
+
store.add(vectors, metadata=np.arange(len(metadata_table)))
|
|
177
|
+
|
|
178
|
+
mask = (metadata_table["product"] == "A") & (metadata_table["year"] >= 2024)
|
|
179
|
+
rows = np.flatnonzero(mask)
|
|
180
|
+
|
|
181
|
+
hits = store.cosine_search(query, within_rows=rows)
|
|
182
|
+
|
|
183
|
+
for hit in hits:
|
|
184
|
+
row = metadata_table[hit.metadata]
|
|
185
|
+
print(row["title"], hit.value)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Persistence
|
|
189
|
+
|
|
190
|
+
Pass a `file_path` and call `save()` / `load()` explicitly:
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
store = VectorStore[dict[str, str]](dimensions=1536, file_path="vectors.npz")
|
|
194
|
+
store.add(embeddings, metadata)
|
|
195
|
+
store.save()
|
|
196
|
+
|
|
197
|
+
loaded = VectorStore[dict[str, str]](dimensions=1536, file_path="vectors.npz")
|
|
198
|
+
loaded.load()
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
If you save with `normalize=False`, load with `normalize=False` too:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
store = VectorStore[dict[str, str]](
|
|
205
|
+
dimensions=1536,
|
|
206
|
+
file_path="raw-vectors.npz",
|
|
207
|
+
normalize=False,
|
|
208
|
+
)
|
|
209
|
+
store.add(raw_vectors, metadata)
|
|
210
|
+
store.save()
|
|
211
|
+
|
|
212
|
+
loaded = VectorStore[dict[str, str]](
|
|
213
|
+
dimensions=1536,
|
|
214
|
+
file_path="raw-vectors.npz",
|
|
215
|
+
normalize=False,
|
|
216
|
+
)
|
|
217
|
+
loaded.load()
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Context manager usage auto-saves on exit:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
with VectorStore[dict[str, str]](dimensions=1536, file_path="vectors.npz") as store:
|
|
224
|
+
store.add(embeddings, metadata)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Persistence uses a minimal NumPy `.npz` contract with `vectors` and `metadata`
|
|
228
|
+
arrays. The `.npz` file does not encode the `normalize` setting; choose the same
|
|
229
|
+
setting when loading that you used when saving. Loading validates shape,
|
|
230
|
+
dimensions, row counts, and zero-norm vectors. It also uses `allow_pickle=True`
|
|
231
|
+
for flexible Python metadata payloads, so only load files generated by your own
|
|
232
|
+
application or another trusted local process. Loading untrusted `.npz` files is
|
|
233
|
+
not a supported security model.
|
|
234
|
+
|
|
235
|
+
## Compatibility
|
|
236
|
+
|
|
237
|
+
This project is still pre-1.0, so occasional breaking changes are expected while
|
|
238
|
+
the API stabilizes. Breaking changes are documented in GitHub release notes.
|
|
239
|
+
Deprecated APIs will keep warning for at least one point release before removal.
|
|
240
|
+
|
|
241
|
+
## Contributing
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
git clone https://github.com/tvanreenen/numpy-vector-store.git
|
|
245
|
+
cd numpy-vector-store
|
|
246
|
+
uv sync --frozen --group dev
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Before submitting a pull request:
|
|
250
|
+
|
|
251
|
+
1. Run `uv run ruff check`
|
|
252
|
+
2. Run `uv run ruff format --check`
|
|
253
|
+
3. Run `uv run mypy src/`
|
|
254
|
+
4. Run `uv run pytest`
|
|
255
|
+
|
|
256
|
+
## License
|
|
257
|
+
|
|
258
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# NumPy Vector Store
|
|
2
|
+
|
|
3
|
+
A fast, lightweight, zero-setup in-memory vector store powered by NumPy.
|
|
4
|
+
|
|
5
|
+
- **Tiny local vector search** for projects that do not need a vector database
|
|
6
|
+
- **Fast exact vector search** using vectorized NumPy operations
|
|
7
|
+
- **Simple typed API** returning `VectorHit(index, value, metadata)`
|
|
8
|
+
- **Composable filtering** by passing prefiltered row indexes with `within_rows`
|
|
9
|
+
- **Portable persistence** as trusted local `.npz` files with `vectors` + `metadata`
|
|
10
|
+
- **No framework opinions**: bring your own embeddings, chunking, async, and metadata model
|
|
11
|
+
|
|
12
|
+
## Why?
|
|
13
|
+
|
|
14
|
+
This library is purpose-built for small to medium-scale vector search tasks and
|
|
15
|
+
offers a simple alternative to heavyweight vector databases when you do not need
|
|
16
|
+
network services, indexing infrastructure, ingestion pipelines, or domain-specific
|
|
17
|
+
metadata filtering.
|
|
18
|
+
|
|
19
|
+
## When/Where?
|
|
20
|
+
|
|
21
|
+
Below are benchmark results for cosine similarity search to help you assess its
|
|
22
|
+
suitability for your use case.
|
|
23
|
+
|
|
24
|
+
| Embedding Type | Dimensions | ~5ms | ~25ms | ~100ms | ~500ms |
|
|
25
|
+
|----------------|------------|------|--------|---------|---------|
|
|
26
|
+
| **Sentence Transformers** | 384 | 1K vectors<br/>1.5MB | 10K vectors<br/>15MB | 100K vectors<br/>147MB | 500K vectors<br/>732MB |
|
|
27
|
+
| **OpenAI Small** | 1536 | 500 vectors<br/>3MB | 5K vectors<br/>29MB | 25K vectors<br/>147MB | 100K vectors<br/>586MB |
|
|
28
|
+
| **OpenAI Large** | 3072 | 200 vectors<br/>2MB | 2.5K vectors<br/>29MB | 5K vectors<br/>59MB | 25K vectors<br/>293MB |
|
|
29
|
+
|
|
30
|
+
*Benchmarks performed on Apple M2 hardware.*
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv add numpy-vector-store
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import numpy as np
|
|
42
|
+
from numpy_vector_store import VectorStore
|
|
43
|
+
|
|
44
|
+
store = VectorStore[dict[str, str]](dimensions=3)
|
|
45
|
+
|
|
46
|
+
store.add(
|
|
47
|
+
vectors=np.array([
|
|
48
|
+
[1.0, 0.0, 0.0],
|
|
49
|
+
[0.0, 1.0, 0.0],
|
|
50
|
+
[0.0, 0.0, 1.0],
|
|
51
|
+
]),
|
|
52
|
+
metadata=[
|
|
53
|
+
{"title": "x-axis"},
|
|
54
|
+
{"title": "y-axis"},
|
|
55
|
+
{"title": "z-axis"},
|
|
56
|
+
],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
hits = store.cosine_search(
|
|
60
|
+
query=np.array([0.9, 0.1, 0.0]),
|
|
61
|
+
top_k=2,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
for hit in hits:
|
|
65
|
+
print(f"{hit.metadata['title']}: {hit.value:.3f}")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`metadata` is an opaque row payload returned with hits. It can be a dict,
|
|
69
|
+
dataclass, string, integer row ID, or any other Python object that fits your
|
|
70
|
+
application.
|
|
71
|
+
|
|
72
|
+
## Normalization
|
|
73
|
+
|
|
74
|
+
`VectorStore` defaults to `normalize=True`, which scales each stored vector to
|
|
75
|
+
length `1`. Normalization preserves vector direction while discarding magnitude:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
[3.0, 4.0] -> [0.6, 0.8]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This is the default because it makes cosine similarity fast and direction-only,
|
|
82
|
+
which is the common case for semantic embeddings. Use `normalize=False` when
|
|
83
|
+
vector length matters, such as when magnitude encodes strength, confidence,
|
|
84
|
+
counts, scale, or raw geometry.
|
|
85
|
+
|
|
86
|
+
Zero vectors are rejected in both modes because cosine similarity is undefined
|
|
87
|
+
for zero-norm vectors.
|
|
88
|
+
|
|
89
|
+
| Method | `normalize=True` default | `normalize=False` |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `cosine_search` | True cosine similarity over stored unit vectors; fastest/default path for embeddings | True cosine similarity over raw vectors; computes vector norms during search |
|
|
92
|
+
| `dot_search` | Dot product of unit vectors, effectively equivalent to cosine similarity | True dot product over original vectors; use when magnitude should affect ranking |
|
|
93
|
+
| `euclidean_search` | Distance between normalized directions; useful only when direction-normalized distance is intended | True Euclidean distance over original vectors; use for geometric/feature-space nearest neighbors |
|
|
94
|
+
| `get` | Returns normalized vectors | Returns original vectors |
|
|
95
|
+
| `save` | Saves normalized vectors | Saves raw vectors |
|
|
96
|
+
| `load` | Loads and normalizes vectors | Loads vectors exactly as stored |
|
|
97
|
+
|
|
98
|
+
## Search Methods
|
|
99
|
+
|
|
100
|
+
Use `cosine_search` for semantic embeddings and direction-only similarity:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
hits = store.cosine_search(query, top_k=10, min_value=0.75)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Use `dot_search` with `normalize=False` when larger-magnitude vectors should
|
|
107
|
+
rank higher:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
store = VectorStore[dict[str, str]](dimensions=3, normalize=False)
|
|
111
|
+
store.add(vectors, metadata)
|
|
112
|
+
hits = store.dot_search(query, top_k=10, min_value=0.0)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Use `euclidean_search` with `normalize=False` for raw coordinate or feature-space
|
|
116
|
+
nearest-neighbor search:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
store = VectorStore[dict[str, str]](dimensions=3, normalize=False)
|
|
120
|
+
store.add(vectors, metadata)
|
|
121
|
+
hits = store.euclidean_search(query, top_k=10, max_value=1.5)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Prefiltering
|
|
125
|
+
|
|
126
|
+
The store does not implement a metadata query language. To filter by metadata,
|
|
127
|
+
produce row indexes first, then pass them with `within_rows`.
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
rows = [
|
|
131
|
+
i
|
|
132
|
+
for i, metadata in enumerate(store.metadata)
|
|
133
|
+
if metadata["title"].startswith("x")
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
hits = store.cosine_search(query, top_k=10, within_rows=rows)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
For structured NumPy metadata, use NumPy to produce the row indexes:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
metadata_table = np.array(
|
|
143
|
+
[
|
|
144
|
+
("intro", "A", 2024),
|
|
145
|
+
("setup", "A", 2023),
|
|
146
|
+
("guide", "B", 2024),
|
|
147
|
+
],
|
|
148
|
+
dtype=[("title", "U20"), ("product", "U10"), ("year", "i4")],
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
store = VectorStore[int](dimensions=3)
|
|
152
|
+
store.add(vectors, metadata=np.arange(len(metadata_table)))
|
|
153
|
+
|
|
154
|
+
mask = (metadata_table["product"] == "A") & (metadata_table["year"] >= 2024)
|
|
155
|
+
rows = np.flatnonzero(mask)
|
|
156
|
+
|
|
157
|
+
hits = store.cosine_search(query, within_rows=rows)
|
|
158
|
+
|
|
159
|
+
for hit in hits:
|
|
160
|
+
row = metadata_table[hit.metadata]
|
|
161
|
+
print(row["title"], hit.value)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Persistence
|
|
165
|
+
|
|
166
|
+
Pass a `file_path` and call `save()` / `load()` explicitly:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
store = VectorStore[dict[str, str]](dimensions=1536, file_path="vectors.npz")
|
|
170
|
+
store.add(embeddings, metadata)
|
|
171
|
+
store.save()
|
|
172
|
+
|
|
173
|
+
loaded = VectorStore[dict[str, str]](dimensions=1536, file_path="vectors.npz")
|
|
174
|
+
loaded.load()
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
If you save with `normalize=False`, load with `normalize=False` too:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
store = VectorStore[dict[str, str]](
|
|
181
|
+
dimensions=1536,
|
|
182
|
+
file_path="raw-vectors.npz",
|
|
183
|
+
normalize=False,
|
|
184
|
+
)
|
|
185
|
+
store.add(raw_vectors, metadata)
|
|
186
|
+
store.save()
|
|
187
|
+
|
|
188
|
+
loaded = VectorStore[dict[str, str]](
|
|
189
|
+
dimensions=1536,
|
|
190
|
+
file_path="raw-vectors.npz",
|
|
191
|
+
normalize=False,
|
|
192
|
+
)
|
|
193
|
+
loaded.load()
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Context manager usage auto-saves on exit:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
with VectorStore[dict[str, str]](dimensions=1536, file_path="vectors.npz") as store:
|
|
200
|
+
store.add(embeddings, metadata)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Persistence uses a minimal NumPy `.npz` contract with `vectors` and `metadata`
|
|
204
|
+
arrays. The `.npz` file does not encode the `normalize` setting; choose the same
|
|
205
|
+
setting when loading that you used when saving. Loading validates shape,
|
|
206
|
+
dimensions, row counts, and zero-norm vectors. It also uses `allow_pickle=True`
|
|
207
|
+
for flexible Python metadata payloads, so only load files generated by your own
|
|
208
|
+
application or another trusted local process. Loading untrusted `.npz` files is
|
|
209
|
+
not a supported security model.
|
|
210
|
+
|
|
211
|
+
## Compatibility
|
|
212
|
+
|
|
213
|
+
This project is still pre-1.0, so occasional breaking changes are expected while
|
|
214
|
+
the API stabilizes. Breaking changes are documented in GitHub release notes.
|
|
215
|
+
Deprecated APIs will keep warning for at least one point release before removal.
|
|
216
|
+
|
|
217
|
+
## Contributing
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
git clone https://github.com/tvanreenen/numpy-vector-store.git
|
|
221
|
+
cd numpy-vector-store
|
|
222
|
+
uv sync --frozen --group dev
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Before submitting a pull request:
|
|
226
|
+
|
|
227
|
+
1. Run `uv run ruff check`
|
|
228
|
+
2. Run `uv run ruff format --check`
|
|
229
|
+
3. Run `uv run mypy src/`
|
|
230
|
+
4. Run `uv run pytest`
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
@@ -7,7 +7,7 @@ name = "numpy-vector-store"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "A fast, lightweight, and zero-setup in-memory vector store powered by NumPy"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
11
|
license = {text = "MIT"}
|
|
12
12
|
authors = [
|
|
13
13
|
{name = "Tim VanReenen"},
|
|
@@ -18,10 +18,11 @@ classifiers = [
|
|
|
18
18
|
"Intended Audience :: Developers",
|
|
19
19
|
"License :: OSI Approved :: MIT License",
|
|
20
20
|
"Programming Language :: Python :: 3",
|
|
21
|
-
"Programming Language :: Python :: 3.9",
|
|
22
21
|
"Programming Language :: Python :: 3.10",
|
|
23
22
|
"Programming Language :: Python :: 3.11",
|
|
24
23
|
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: 3.14",
|
|
25
26
|
]
|
|
26
27
|
dependencies = [
|
|
27
28
|
"numpy>=1.20.0",
|
|
@@ -41,7 +42,7 @@ path = "src/numpy_vector_store/__init__.py"
|
|
|
41
42
|
|
|
42
43
|
[tool.ruff]
|
|
43
44
|
line-length = 88
|
|
44
|
-
target-version = "
|
|
45
|
+
target-version = "py310"
|
|
45
46
|
|
|
46
47
|
[tool.ruff.lint]
|
|
47
48
|
select = [
|
|
@@ -62,7 +63,7 @@ ignore = [
|
|
|
62
63
|
known-first-party = ["numpy_vector_store"]
|
|
63
64
|
|
|
64
65
|
[tool.mypy]
|
|
65
|
-
python_version = "3.
|
|
66
|
+
python_version = "3.10"
|
|
66
67
|
warn_return_any = true
|
|
67
68
|
warn_unused_configs = true
|
|
68
69
|
disallow_untyped_defs = true
|
|
@@ -5,9 +5,9 @@ This package provides a lightweight vector store implementation for storing
|
|
|
5
5
|
and searching vector embeddings using NumPy arrays.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "0.
|
|
8
|
+
__version__ = "0.3.0"
|
|
9
9
|
__author__ = "Tim VanReenen"
|
|
10
10
|
|
|
11
|
-
from .vector_store import VectorStore
|
|
11
|
+
from .vector_store import VectorHit, VectorStore
|
|
12
12
|
|
|
13
|
-
__all__ = ["VectorStore"]
|
|
13
|
+
__all__ = ["VectorHit", "VectorStore"]
|