AsyncClient + AsyncCollection — Phase 1 (universal wrapper)
The fourth deferred item from #11. Adds async/await support to vd so it composes with FastAPI, Starlette, modern LLM SDKs, and other event-loop apps — without breaking the synchronous API.
Design (confirmed with maintainer)
Wrapper-default + native opt-in, mirroring the SupportsHybrid + RRF-fallback pattern from PR #16:
- A universal
AsyncClient/AsyncCollection wrapper exposes every sync vd Collection/Client as async/await methods via asyncio.to_thread. Every backend gets async support day one through this wrapper.
- Adapters with native async SDKs (chroma, qdrant, weaviate, elasticsearch, redis, mongodb, lancedb, milvus, pinecone, turbopuffer — 10 of 15) override the wrapper with adapter-specific implementations in follow-up PRs.
- A
SupportsNativeAsync runtime-checkable marker lets consumers distinguish to_thread-wrapped from truly non-blocking adapters when it matters (high-concurrency HTTP servers).
API
# Entry point — async sibling of vd.connect()
client = await vd.connect_async("chroma", embedder=my_embedder)
# AsyncClient surface
async for name in client.list_collections():
...
col = await client.create_collection("docs", dimension=384)
# AsyncCollection — explicit get/set/delete/keys/count (MutableMapping
# doesn't have an async counterpart in the stdlib; explicit methods
# are the Motor / aiopg pattern)
await col.set("doc1", vd.Document(id="doc1", text="..."))
doc = await col.get("doc1")
async for doc_id in col.keys():
...
n = await col.count()
async for hit in col.search(query_vec, limit=10):
...
# Async hybrid mirrors the sync entry
async for hit in vd.hybrid_search_async(col, query_vec, query_text="..."):
...
# Context manager
async with await vd.connect_async("redis") as client:
...
Phase 1 scope (this issue)
| File |
Change |
vd/base.py |
New AsyncClient/AsyncCollection/SupportsNativeAsync runtime-checkable protocols. |
vd/asynchronous.py (new) |
AsyncClientWrapper/AsyncCollectionWrapper (asyncio.to_thread-based), connect_async, hybrid_search_async. (Module named asynchronous to avoid the async keyword.) |
vd/__init__.py |
Export new names. |
tests/test_async.py |
Contract tests on the memory backend (sufficient to exercise the universal wrapper; native paths will get their own test parametrization in follow-ups). |
Phase 2 (follow-ups, one per backend)
Native async implementations for the 10 backends with async SDKs. Same shape as the SupportsHybrid follow-up (#17) — each backend is its own clean piece of work and its own PR.
Refs
Refs #11. Follows #16.
AsyncClient + AsyncCollection — Phase 1 (universal wrapper)
The fourth deferred item from #11. Adds
async/awaitsupport to vd so it composes with FastAPI, Starlette, modern LLM SDKs, and other event-loop apps — without breaking the synchronous API.Design (confirmed with maintainer)
Wrapper-default + native opt-in, mirroring the
SupportsHybrid+ RRF-fallback pattern from PR #16:AsyncClient/AsyncCollectionwrapper exposes every sync vd Collection/Client asasync/awaitmethods viaasyncio.to_thread. Every backend gets async support day one through this wrapper.SupportsNativeAsyncruntime-checkable marker lets consumers distinguish to_thread-wrapped from truly non-blocking adapters when it matters (high-concurrency HTTP servers).API
Phase 1 scope (this issue)
vd/base.pyAsyncClient/AsyncCollection/SupportsNativeAsyncruntime-checkable protocols.vd/asynchronous.py(new)AsyncClientWrapper/AsyncCollectionWrapper(asyncio.to_thread-based),connect_async,hybrid_search_async. (Module namedasynchronousto avoid theasynckeyword.)vd/__init__.pytests/test_async.pyPhase 2 (follow-ups, one per backend)
Native async implementations for the 10 backends with async SDKs. Same shape as the SupportsHybrid follow-up (#17) — each backend is its own clean piece of work and its own PR.
Refs
Refs #11. Follows #16.