Skip to content

Commit 2760e84

Browse files
4adexArshdeep54SK1PPRpeopleig
authored
Update from refactor
* feat: add batch insert and search (#74) Signed-off-by: Arshdeep54 <balarsh535@gmail.com> * Inmemory storage implementation (#82) * Implement in-memory storage engine * Wire in-memory storage through API snapshots * Add HTTP smoke coverage for in-memory storage * Handle in-memory storage errors in gRPC * Add in-memory checkpoint header * Add async Python client (#83) * Add async Python client * Deduplicate Python gRPC connection helpers * Benchmarking optimizations (#84) * Reduce in-memory checkpoint lock scope * Optimize HNSW distance calculations * Expose HNSW tuning parameters * Add Python batch vector client methods * Expose KD-tree rebuild tuning parameters * Update tui with new name and details (#79) * Support query-time HNSW ef * Async batch support (#85) * Update Python client to support batch insert and search * Add batch support to the async client * Add master test script for all usage example files Run linter on client/python * Add test for api parity between sync and async classes --------- Signed-off-by: Arshdeep54 <balarsh535@gmail.com> Co-authored-by: Arshdeep Singh <110114582+Arshdeep54@users.noreply.github.com> Co-authored-by: Khushal Agrawal <117133859+SK1PPR@users.noreply.github.com> Co-authored-by: Khushal Agrawal <dev@khushal.net> Co-authored-by: Prachet Patel <patelprachet@gmail.com>
1 parent 61af5ed commit 2760e84

59 files changed

Lines changed: 3151 additions & 289 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Cargo.lock‎

Lines changed: 319 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎client/python/USAGE.md‎

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,30 @@ The client supports usage as a context manager, which automatically closes the u
3939
Example available in:
4040
```examples/context_manager_usage.py```
4141

42+
### Async Client Support
43+
44+
For async applications, use `AsyncVortexDB`. It mirrors the synchronous client API and uses `grpc.aio` under the hood, including full support for `batch_insert` and `batch_search`.
45+
46+
Examples available in:
47+
```examples/async_usage.py``` & ```examples/async_batch_usage.py```
48+
49+
```python
50+
async with AsyncVortexDB(
51+
grpc_url="localhost:50051",
52+
api_key="your-api-key",
53+
) as db:
54+
point_id = await db.insert(
55+
vector=DenseVector([0.1, 0.2, 0.3]),
56+
payload=Payload.text("hello async vortex"),
57+
)
58+
```
59+
60+
### Batch Insertion and Search Support
61+
62+
Both `VortexDB` and `AsyncVortexDB` support batch insertion and batch search queries.
63+
Methods of usage and examples available in:
64+
```examples/batch_insert_usage.py``` & ```examples/search_query_usage.py``` & ```examples/async_batch_usage.py```
65+
4266
---
4367

4468
## Client API
@@ -47,6 +71,27 @@ Example available in:
4771

4872
Main client class for interacting with the VortexDB gRPC server.
4973

74+
### `AsyncVortexDB`
75+
76+
Async client class for I/O-heavy applications. It has the same constructor and method names as `VortexDB`, but methods are awaitable:
77+
78+
```
79+
await db.insert(...)
80+
await db.batch_insert(...)
81+
await db.get(...)
82+
await db.search(...)
83+
await db.batch_search(...)
84+
await db.delete(...)
85+
await db.close()
86+
```
87+
88+
It also supports async context manager usage:
89+
90+
```
91+
async with AsyncVortexDB(...) as db:
92+
...
93+
```
94+
5095
#### **Constructor**
5196

5297
```
@@ -78,6 +123,22 @@ Raises
78123

79124
---
80125

126+
#### **Batch Insert**
127+
128+
Insert multiple vectors with payloads in a single request
129+
```
130+
batch_insert(*, items: list[tuple[DenseVector, Payload]]) -> list[str]
131+
```
132+
133+
Returns
134+
- List of `point_id` (UUID string)
135+
136+
Raises
137+
- `TypeError` if input structure is invalid
138+
- gRPC-mapped errors (see Error Handling)
139+
140+
---
141+
81142
#### **Get**
82143

83144
Fetch a point by its ID
@@ -112,6 +173,32 @@ Raises
112173

113174
---
114175

176+
#### **Batch Search**
177+
178+
Search for nearest neighbours for multiple queries in a single request
179+
```
180+
batch_search(
181+
*,
182+
queries,
183+
similarity: Similarity | None = None,
184+
limit: int | None = None,
185+
) -> list[list[str]]
186+
```
187+
188+
Returns
189+
- `TypeError` for invalid query formats
190+
- `ValueError` if required parameters are missing
191+
192+
Supported Input Formats:
193+
The `queries` parameter is flexible and supports multiple formats:
194+
- List of `SearchQuery` objects
195+
- List of `(DenseVector, Similarity, Limit)` tuples
196+
- List of `(DenseVector, Similarity)` tuples with a global `Limit`
197+
- List of `(DenseVector, Limit)` tuples with a global `Similarity`
198+
- List of `DenseVector` with global `Similarity` and `Limit`
199+
200+
---
201+
115202
#### **Delete**
116203

117204
Delete a point by its ID
@@ -177,6 +264,19 @@ All fields are directly accessible:
177264

178265
---
179266

267+
### `SearchQuery`
268+
269+
```
270+
SearchQuery(
271+
vector: DenseVector,
272+
similarity: Similarity,
273+
limit: int,
274+
)
275+
```
276+
Structured representation of a search request
277+
278+
---
279+
180280
### `Similarity`
181281

182282
Enum representing distance functions:
@@ -274,4 +374,4 @@ python -m grpc_tools.protoc \
274374

275375
After running this:
276376
- `vector_db_pb2_grpc.py` and `vector_db_pb2.py` will be updated
277-
- No other client code should need changes
377+
- No other client code should need changes

‎client/python/examples/all.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# This file is like a master test. Runs all the examples
2+
# Not exactly the purpose of the examples dir,
3+
# but helps in checking if any code updates haven't broken the API
4+
5+
from pathlib import Path
6+
import subprocess
7+
import pytest
8+
9+
# Didn't know I could do this with pytest, so cool
10+
# Just run: pytest ./all.py -v
11+
12+
EXAMPLES_DIR = Path(__file__).parent
13+
example_files = sorted(EXAMPLES_DIR.glob("*_usage.py"))
14+
15+
16+
@pytest.mark.parametrize(
17+
"script_path",
18+
example_files,
19+
ids=lambda p: p.stem,
20+
)
21+
def test(script_path):
22+
"""Run all example scripts to check if they crash or not"""
23+
result = subprocess.run(
24+
["python3", str(script_path)], capture_output=True, text=True
25+
)
26+
assert result.returncode == 0, (
27+
f"Script {script_path} failed with stderr:\n{result.stderr}"
28+
)
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import asyncio
2+
3+
from vortexdb import AsyncVortexDB
4+
from vortexdb import Payload, Similarity, SearchQuery, to_dense_vectors
5+
6+
7+
async def main():
8+
async with AsyncVortexDB(
9+
grpc_url="localhost:50051",
10+
api_key="my-secret-password",
11+
) as db:
12+
raw_vectors = [
13+
[0.1, 0.2, 0.3],
14+
[0.4, 0.5, 0.6],
15+
[0.7, 0.8, 0.9],
16+
]
17+
vectors = to_dense_vectors(raw_vectors)
18+
19+
p1 = Payload.text("hello world")
20+
p2 = Payload.image("/img/a.png")
21+
p3 = Payload.text("foo bar")
22+
23+
items = [
24+
(vectors[0], p1),
25+
(vectors[1], p2),
26+
(vectors[2], p3),
27+
]
28+
29+
# Batch Insert
30+
point_ids = await db.batch_insert(items=items)
31+
print("Inserted ids:\n", point_ids)
32+
33+
q = SearchQuery(
34+
vector=vectors[0],
35+
similarity=Similarity.COSINE,
36+
limit=3,
37+
)
38+
res = await db.search(query=q)
39+
print("\nSingle SearchQuery:\n", res)
40+
41+
# List of SearchQuery
42+
queries = [
43+
SearchQuery(vectors[0], Similarity.HAMMING, 3),
44+
SearchQuery(vectors[1], Similarity.EUCLIDEAN, 2),
45+
q,
46+
]
47+
res = await db.batch_search(queries=queries)
48+
print("\nBatch SearchQuery:\n", res)
49+
50+
# List of vectors with global Similarity and Limit
51+
res = await db.batch_search(
52+
queries=vectors,
53+
similarity=Similarity.COSINE,
54+
limit=3,
55+
)
56+
print("\nList of DenseVectors:\n", res)
57+
58+
# List of tuple (DenseVector, Similarity) with global Limit
59+
queries = [
60+
(vectors[0], Similarity.COSINE),
61+
(vectors[1], Similarity.MANHATTAN),
62+
]
63+
res = await db.batch_search(
64+
queries=queries,
65+
limit=3,
66+
)
67+
print("\nList of (DenseVector, Similarity):\n", res)
68+
69+
# List of tuple (DenseVector, Limit) with global Similarity
70+
queries = [
71+
(vectors[0], 2),
72+
(vectors[1], 4),
73+
]
74+
res = await db.batch_search(
75+
queries=queries,
76+
similarity=Similarity.COSINE,
77+
)
78+
print("\nList of (DenseVector, Limit):\n", res)
79+
80+
for pid in point_ids:
81+
await db.delete(point_id=pid)
82+
83+
84+
if __name__ == "__main__":
85+
asyncio.run(main())
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import asyncio
2+
3+
from vortexdb import AsyncVortexDB, DenseVector, Payload, Similarity
4+
5+
6+
async def main():
7+
async with AsyncVortexDB(
8+
grpc_url="localhost:50051",
9+
api_key="my-secret-password",
10+
) as db:
11+
point_id = await db.insert(
12+
vector=DenseVector([0.1, 0.2, 0.3]),
13+
payload=Payload.text("hello async vortex"),
14+
)
15+
16+
point = await db.get(point_id=point_id)
17+
if point is not None:
18+
print(point.pretty())
19+
20+
results = await db.search(
21+
vector=DenseVector([0.1, 0.2, 0.3]),
22+
similarity=Similarity.COSINE,
23+
limit=5,
24+
)
25+
print(results)
26+
27+
await db.delete(point_id=point_id)
28+
29+
30+
if __name__ == "__main__":
31+
asyncio.run(main())

‎client/python/examples/basic_usage.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from vortexdb import VortexDB
2-
from vortexdb import DenseVector, Payload, Similarity # from vortexdb.models
2+
from vortexdb import DenseVector, Payload, Similarity # from vortexdb.models
3+
34

45
def main():
56
# Initialize client
@@ -32,5 +33,6 @@ def main():
3233
# Close connection
3334
db.close()
3435

36+
3537
if __name__ == "__main__":
3638
main()
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from vortexdb import VortexDB
2+
from vortexdb import Payload, to_dense_vectors
3+
4+
5+
def main():
6+
db = VortexDB(
7+
grpc_url="localhost:50051",
8+
api_key="my-secret-password",
9+
)
10+
11+
raw_vectors = [
12+
[0.1, 0.2, 0.3],
13+
[0.4, 0.5, 0.6],
14+
[0.7, 0.8, 0.9],
15+
]
16+
vectors = to_dense_vectors(raw_vectors)
17+
18+
p1 = Payload.text("hello world")
19+
p2 = Payload.image("/img/a.png")
20+
p3 = Payload.text("foo bar")
21+
22+
items = [
23+
(vectors[0], p1),
24+
(vectors[1], p2),
25+
(vectors[2], p3),
26+
]
27+
28+
# Batch Insert
29+
point_ids = db.batch_insert(items=items)
30+
print("Inserted ids:\n", point_ids)
31+
32+
for pid in point_ids:
33+
db.delete(point_id=pid)
34+
35+
db.close()
36+
37+
38+
if __name__ == "__main__":
39+
main()

‎client/python/examples/context_manager_usage.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from vortexdb import VortexDB, DenseVector, Payload, Similarity
22

3+
34
def main():
45
with VortexDB(
56
grpc_url="localhost:50051",
67
api_key="my-secret-password",
78
) as db:
8-
99
# Insert a vector
1010
point_id = db.insert(
1111
vector=DenseVector([0.1, 0.2, 0.3]),
@@ -30,5 +30,6 @@ def main():
3030
# At this point, the gRPC channel is closed automatically
3131
print("Connection closed")
3232

33+
3334
if __name__ == "__main__":
3435
main()

0 commit comments

Comments
 (0)