Skip to content

Commit d7ee7d6

Browse files
authored
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
1 parent 983c044 commit d7ee7d6

23 files changed

Lines changed: 985 additions & 167 deletions

client/python/USAGE.md

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ Example available in:
4141

4242
### Async Client Support
4343

44-
For async applications, use `AsyncVortexDB`. It mirrors the synchronous client API and uses `grpc.aio` under the hood.
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`.
4545

46-
Example available in:
47-
```examples/async_usage.py```
46+
Examples available in:
47+
```examples/async_usage.py``` & ```examples/async_batch_usage.py```
4848

4949
```python
5050
async with AsyncVortexDB(
@@ -57,6 +57,12 @@ async with AsyncVortexDB(
5757
)
5858
```
5959

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+
6066
---
6167

6268
## Client API
@@ -71,8 +77,10 @@ Async client class for I/O-heavy applications. It has the same constructor and m
7177

7278
```
7379
await db.insert(...)
80+
await db.batch_insert(...)
7481
await db.get(...)
7582
await db.search(...)
83+
await db.batch_search(...)
7684
await db.delete(...)
7785
await db.close()
7886
```
@@ -115,6 +123,22 @@ Raises
115123

116124
---
117125

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+
118142
#### **Get**
119143

120144
Fetch a point by its ID
@@ -149,6 +173,32 @@ Raises
149173

150174
---
151175

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+
152202
#### **Delete**
153203

154204
Delete a point by its ID
@@ -214,6 +264,19 @@ All fields are directly accessible:
214264

215265
---
216266

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+
217280
### `Similarity`
218281

219282
Enum representing distance functions:
@@ -311,4 +374,4 @@ python -m grpc_tools.protoc \
311374

312375
After running this:
313376
- `vector_db_pb2_grpc.py` and `vector_db_pb2.py` will be updated
314-
- 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())

client/python/examples/async_usage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
async def main():
77
async with AsyncVortexDB(
88
grpc_url="localhost:50051",
9-
api_key="your-api-key",
9+
api_key="my-secret-password",
1010
) as db:
1111
point_id = await db.insert(
1212
vector=DenseVector([0.1, 0.2, 0.3]),

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()
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from vortexdb import VortexDB
2+
from vortexdb import Similarity, SearchQuery, 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+
q = SearchQuery(
19+
vector=vectors[0],
20+
similarity=Similarity.COSINE,
21+
limit=3,
22+
)
23+
res = db.search(query=q)
24+
print("Single SearchQuery:\n", res)
25+
26+
# List of SearchQuery
27+
queries = [
28+
SearchQuery(vectors[0], Similarity.HAMMING, 3),
29+
SearchQuery(vectors[1], Similarity.EUCLIDEAN, 2),
30+
q,
31+
]
32+
res = db.batch_search(queries=queries)
33+
print("\nBatch SearchQuery:\n", res)
34+
35+
# List of vectors with global Similarity and Limit
36+
res = db.batch_search(
37+
queries=vectors,
38+
similarity=Similarity.COSINE,
39+
limit=3,
40+
)
41+
print("\nList of DenseVectors:\n", res)
42+
43+
# List of tuple (DenseVector, Similarity) with global Limit
44+
queries = [
45+
(vectors[0], Similarity.COSINE),
46+
(vectors[1], Similarity.MANHATTAN),
47+
]
48+
res = db.batch_search(
49+
queries=queries,
50+
limit=3,
51+
)
52+
print("\nList of (DenseVector, Similarity):\n", res)
53+
54+
# List of tuple (DenseVector, Limit) with global Similarity
55+
queries = [
56+
(vectors[0], 2),
57+
(vectors[1], 4),
58+
]
59+
res = db.batch_search(
60+
queries=queries,
61+
similarity=Similarity.COSINE,
62+
)
63+
print("\nList of (DenseVector, Limit):\n", res)
64+
65+
db.close()
66+
67+
68+
if __name__ == "__main__":
69+
main()

0 commit comments

Comments
 (0)