vd#
vd — one Pythonic interface to every vector database.
vd is a facade over vector databases. Its purpose is to let you operate
on any vectorDB, and switch between them with a one-argument change, while
keeping each backend’s particular power one escape hatch away. It does three
things:
Choose —
recommend_backend(),print_backends_table()and the provider registry help you (or an AI agent) pick the right backend.Set up —
check_requirements()andsetup_guide()diagnose and walk you through installing and starting a backend.Operate —
connect()returns a uniform client; collections behave asMutableMappingofDocumentplus asearch()method.
Quick start#
>>> import vd
>>> client = vd.connect('memory') # switch DB = change this one word
>>> col = client.create_collection('docs')
>>> col['a'] = vd.Document(id='a', text='cats', vector=[1.0, 0.0])
>>> col['b'] = vd.Document(id='b', text='dogs', vector=[0.0, 1.0])
>>> [hit['id'] for hit in col.search([0.9, 0.1], limit=1)]
['a']
Embedding is external#
vd stores and searches vectors. Turning text into vectors is another
package’s job (e.g. ef). Pass an embedder to connect() only for
the convenience of writing/searching raw text; otherwise pass
Document objects carrying vectors, and pre-computed query vectors.
Functions
Return the path to the bundled AI-agent skills directory. |
|
|
Connect to a vector database backend and return its |
|
Class decorator: register an adapter |
|
Async sibling of |
|
Async sibling of |
|
Register a native async client factory for backend |
Return the names of backends with a registered native async client. |
|
|
Return |
|
Walk |
Return the names of all backends with a registered (importable) adapter. |
|
Return providers |
|
Return every provider with live |
|
Print every known vector database, grouped by deployment archetype. |
|
Return the full provider registry as |
|
|
Return one provider's metadata, or |
|
Return one provider's metadata with |
Return a compact |
|
|
Return a human-readable setup blurb for one provider. |
|
Return the |
|
Return a |
|
Print a side-by-side comparison table of the given providers. |
|
Recommend a vector database from a few yes/no facts about the situation. |
|
Run |
|
Diagnose whether |
|
Return a full, copy-pasteable setup playbook for |
|
Return (and optionally run) the |
|
Egress: keep only the text. |
|
Egress: keep only the document id. |
|
Egress: keep |
|
Egress: keep |
|
Cosine similarity of two vectors (1.0 identical, 0.0 orthogonal). |
|
Euclidean (L2) distance between two vectors. |
|
Normalize a flexible document input to a |
|
Export a collection to a file in the specified format. |
|
Import documents into a collection from a file. |
|
Export a collection to JSONL (JSON Lines) format. |
|
Import documents from JSONL format into a collection. |
|
Export a collection to JSON format. |
|
Import documents from JSON format into a collection. |
|
Export collection as a directory with one JSON file per document. |
|
Import documents from a directory of JSON files. |
|
Migrate a collection from one backend to another. |
|
Migrate all (or selected) collections from one client to another. |
|
Copy a collection with flexible source/target specification. |
|
Compute comprehensive statistics for a collection. |
|
Get the distribution of values for a metadata field. |
|
Find near-duplicate documents in a collection. |
|
Find outlier documents (those dissimilar to their neighbors). |
|
Sample document IDs from a collection. |
|
Validate collection integrity and identify issues. |
|
Chunk text into smaller pieces. |
|
Chunk multiple documents while preserving metadata. |
|
Clean and normalize text. |
|
Normalize whitespace in text. |
|
Truncate text to maximum length. |
|
Extract metadata from text. |
|
Check if a backend is healthy and accessible. |
|
Check collection health and compute basic stats. |
|
Benchmark search performance on a collection. |
|
Benchmark document insertion performance. |
|
Search with multiple queries and combine results. |
|
Find documents similar to a specific document. |
|
Combine multiple result lists using Reciprocal Rank Fusion. |
|
Remove duplicate results. |
|
Hybrid (dense + lexical) search that works on any vd Collection. |
|
Brute-force BM25 lexical search over a vd collection's stored |
|
Load configuration from a file. |
|
Save configuration to a file. |
|
Connect to a backend using configuration from a file. |
|
Generate an example configuration file content. |
|
|
|
Element-wise mean of document embeddings. |
|
Parse a window spec into a |
|
Coerce a timestamp-like value into a tz-aware UTC |
|
ISO-8601 (UTC) string suitable for cross-backend metadata storage. |
Classes
|
The unit stored in a |
|
A live connection to one backend: |
|
A collection of documents: |
|
Base class implementing the |
Base class implementing the |
|
|
A collection that supports efficient batch insertion. |
|
A collection that supports native hybrid (dense + lexical) search. |
|
Marker protocol set on async clients/collections that use a backend's native async SDK rather than the universal |
|
The async sibling of |
|
The async sibling of |
|
Adapt a sync |
|
Adapt a sync |
|
Base class for native async clients (the async sibling of |
Base class for native async collections (the async sibling of |
|
|
A reusable Okapi BM25 index over a vd collection's stored |
|
Time-indexed wrapper over any vd |
|
A time window: |
Exceptions
Base class for every error |
|
Raised on a write to a static (immutable) index. |
|
Raised when a metadata filter uses an operator a backend cannot honor. |
|
Raised when an operation needs a capability the backend lacks. |
|
Raised when text is given but no embedder is configured. |
|
Raised when a known backend's Python package is not installed. |
- class vd.AbstractClient(*, embedder=None, **config)[source]#
Bases:
MappingBase class implementing the
Clientcontract for adapters.A
Clientis aMapping[str, Collection]. A backend subclasses this and implementscreate_collection(),get_collection(),delete_collection(), andlist_collections(); the mapping behavior, theget_or_create_collection()convenience, theclientescape hatch, and context-manager support come for free.- Parameters:
- property client: Any#
The raw backend client — a supported, documented escape hatch.
Drop to it for backend-specific operations the facade does not expose. Returns
Nonefor backends with no external client object (e.g. the in-memory backend).
- close()[source]#
Release backend resources. Default no-op; adapters override as needed.
- Return type:
- abstractmethod create_collection(name, *, dimension=None, metric='cosine', **index_config)[source]#
Create a new collection.
- Parameters:
name (
str) – Collection name.dimension (
Optional[int]) – Vector dimension. May beNonefor backends that can infer it from the first written vector; required up front by backends that cannot.metric (
str) – Distance metric:"cosine","dot", or"l2".**index_config – Backend-specific index tuning (HNSW
M/ef, IVFnlist, …). Documented per adapter; never abstracted into a common enum.
- Raises:
ValueError – If a collection of that name already exists.
- Return type:
- abstractmethod delete_collection(name)[source]#
Drop a collection; raise
KeyErrorif absent.- Return type:
- abstractmethod get_collection(name)[source]#
Return an existing collection; raise
KeyErrorif absent.- Return type:
- class vd.AbstractCollection[source]#
Bases:
_CollectionPolicy,MutableMappingBase class implementing the
Collectioncontract for adapters.A backend subclasses this and implements the raw primitives below; everything users see is provided here, once, uniformly:
flexible
__setitem__inputs (text / tuple /Document),optional text embedding when a
Documentarrives without a vector,text-query embedding in
search(),central filter validation against
supported_filter_operators,egressresult transforms,batch helpers (
add_documents(),upsert()),eager dimension-mismatch detection.
Subclass responsibilities (raw primitives)#
_write(doc)Upsert one document. Its
vectoris guaranteed non-Noneand dimension-checked._read(key) -> DocumentFetch one document; raise
KeyErrorif absent._drop(key)Delete one document; raise
KeyErrorif absent._keys() -> Iterator[str]Iterate document ids.
_count() -> intNumber of documents.
_query(vector, *, limit, filter, **kwargs) -> Iterable[SearchResult]Raw nearest-neighbor search.
filteris the canonical AST — the adapter translates it. Each result is a dict with at leastid,text,score,metadata.
Optional overrides#
_write_many(docs)Efficient bulk upsert. Defaults to a loop over
_write.native(property)The raw backend collection handle (escape hatch).
- add_documents(documents, *, batch_size=100)[source]#
Add many documents, embedding and writing them in batches.
Each item may be a string, a
(text, ...)tuple, or aDocument(seeDocumentInput). Items without anidget a deterministic auto-generated one.- Return type:
- property native: Any#
The raw backend collection handle — a supported, documented escape hatch.
Use it to reach backend-specific features the facade does not expose, rather than circumventing
vd. ReturnsNoneif the adapter has no distinct native object.
- search(query, *, limit=10, filter=None, egress=None, **kwargs)[source]#
Return the
limitdocuments most similar toquery.- Parameters:
query (
Union[str,list[float]]) – Query text (embedded via the client’sembedder) or a pre-computed query vector.limit (
int) – Maximum number of results.filter (
Optional[dict[str,Any]]) – Metadata filter in the canonicalvddialect (seevd.filters). Validated against this backend’ssupported_filter_operatorsbefore the query runs, so an unsupported operator fails with a clearUnsupportedFilterError.egress (
Optional[Callable[[dict[str,Any]],Any]]) – Transform applied to each result dict before it is yielded.**kwargs – Backend-specific search options, passed through to
_query.
- Yields:
dict –
{"id", "text", "score", "metadata"}— or whateveregressreturns.scoreis a higher-is-better, per-metric canonical similarity (see the “Score semantics” table at the top ofvd.base): cosine in[-1, 1], dot in(-inf, +inf), l2 squashed to(0, 1]. Adapters whose backend returns a native combined-ranking score on a different scale (e.g. Elasticsearch, Atlas, Pinecone) document the deviation in their own docstring.- Return type:
- class vd.AsyncAbstractClient(*, embedder=None, **config)[source]#
Bases:
objectBase class for native async clients (the async sibling of
vd.AbstractClient).A backend implements
create_collection(),get_collection(),delete_collection()andlist_collections()as coroutines / async generators;get_or_create_collection(), theclientescape hatch,close()andasync withsupport come for free. Register the class withregister_async_backend()soconnect_async()returns it.- Parameters:
- backend_name: str = ''#
The registry name of this backend (set by
register_async_backend()).
- abstractmethod async create_collection(name, *, dimension=None, metric='cosine', **index_config)[source]#
Create a new collection; raise
ValueErrorif it exists.- Return type:
- abstractmethod async delete_collection(name)[source]#
Drop a collection; raise
KeyErrorif absent.- Return type:
- abstractmethod async get_collection(name)[source]#
Return an existing collection; raise
KeyErrorif absent.- Return type:
- async get_or_create_collection(name, *, dimension=None, metric='cosine', **index_config)[source]#
Return collection
name, creating it if missing.- Return type:
- class vd.AsyncAbstractCollection[source]#
Bases:
_CollectionPolicyBase class for native async collections (the async sibling of
vd.AbstractCollection).A backend subclasses this and implements
asyncraw primitives; the user-facingAsyncCollectionsurface is provided here, with the same input coercion, embedding, dimension checks, filter validation andegresshandling as the sync base (both share one policy mixin).Subclass responsibilities (async raw primitives)#
async _write_many(docs)Upsert documents; each
vectoris set and dimension-checked.async _read(key) -> DocumentFetch one document; raise
KeyErrorif absent.async _drop(key)Delete one document; raise
KeyErrorif absent._keys() -> AsyncIterator[str]An async generator of document ids.
async _count() -> intNumber of documents.
async _query(vector, *, limit, filter, **kwargs) -> list[SearchResult]Raw nearest-neighbor search;
filteris the canonical AST.
- async add_documents(documents, *, batch_size=100)[source]#
Batch upsert — mirrors
vd.AbstractCollection.add_documents().- Return type:
- async search(query, *, limit=10, filter=None, egress=None, **kwargs)[source]#
Yield the
limitdocuments most similar toquery.Same contract as
vd.AbstractCollection.search().- Return type:
AsyncIterator[dict[str,Any]]
- class vd.AsyncClient(*args, **kwargs)[source]#
Bases:
ProtocolThe async sibling of
Client.Same operations — collection create / fetch / drop / list — exposed as awaitables and async iterators. Construct via
vd.connect_async().
- class vd.AsyncClientWrapper(sync_client)[source]#
Bases:
objectAdapt a sync
Clientto theAsyncClientcontract by dispatching every method toasyncio.to_thread().Use
connect_async()rather than instantiating this directly.- Parameters:
sync_client (
Any) – A liveClient(typically obtained fromvd.connect()).
- async close()[source]#
Release backend resources. Calls
close()on the sync client if present.- Return type:
- async create_collection(name, *, dimension=None, metric='cosine', **index_config)[source]#
Create a new collection; raise
ValueErrorif it exists.- Return type:
- async get_collection(name)[source]#
Return an existing collection; raise
KeyErrorif absent.- Return type:
- async get_or_create_collection(name, *, dimension=None, metric='cosine', **index_config)[source]#
Return collection
name, creating it if missing.- Return type:
- class vd.AsyncCollection(*args, **kwargs)[source]#
Bases:
ProtocolThe async sibling of
Collection.Same conceptual surface — storage +
search— but every method is awaitable and iterators areAsyncIterator. The mapping interface is exposed as explicitget/set/delete/keys/countmethods (the stdlib’sMutableMappingABC has no async counterpart; explicit methods are the Motor / aiopg convention).Construct via
vd.connect_async(); the universalAsyncCollectionWrapperinvd.asynchronousadapts every backend to this protocol by dispatching to the sync API throughasyncio.to_thread(). Backends with native async SDKs override the wrapper and additionally satisfySupportsNativeAsync.
- class vd.AsyncCollectionWrapper(sync_collection)[source]#
Bases:
objectAdapt a sync
Collectionto theAsyncCollectioncontract by dispatching every method toasyncio.to_thread().Use
connect_async()rather than instantiating this directly — it will pick this wrapper or a native async adapter as appropriate.- Parameters:
:: (sync_collection) – A live
Collection(typically obtained from aClient).
- native_async#
Always
Falsefor this wrapper. The wrapper still satisfiesSupportsNativeAsyncstructurally (the attribute is present), but the boolean tells callers that I/O is happening in a thread pool rather than on the event loop. Prefer a native implementation for high-concurrency workloads.- Type:
- async add_documents(documents, *, batch_size=100)[source]#
Batch upsert — mirrors
add_documents().- Return type:
- async search(query, *, limit=10, filter=None, egress=None, **kwargs)[source]#
Yield the
limitdocuments most similar toquery.The underlying search runs once on a worker thread; results stream from memory. (Most backends’ sync
searchalready returns a list or a fully-realized iterator under the hood.)- Return type:
AsyncIterator[dict[str,Any]]
- property sync: Any#
The underlying sync
Collection— a documented escape hatch.
- class vd.BM25Index(collection, *, filter=None, tokenize=<function _tokenize>)[source]#
Bases:
objectA reusable Okapi BM25 index over a vd collection’s stored
text.Builds the query-independent term statistics — per-document token lists, document frequencies, document lengths, and the mean length — once in
__init__(), then answers many queries against them viasearch(). This is the build-once / query-many companion tobm25_lexical_search()(which builds a throwaway index for a single query): for batch evaluation or any repeated querying of the same collection it turns an O(N · Q) workload (re-tokenizing every document on every query) into O(N + scoring · Q).Construction is O(N) in the collection size; each
search()is O(matching documents). Fine for prototypes and collections up to ~100k documents; for larger workloads switch to a backend with a native text index (weaviate, elasticsearch, redis, lancedb, …).- Parameters:
collection (
Collection) – Any vd Collection (or mapping-likeid -> objexposing.textand.metadata). Documents whosetextis empty contribute zero score and are dropped at build time.filter (
Optional[dict]) – Canonicalvdmetadata filter, applied once at build time (viavd.filters.matches_filter()) so the index covers only the matching documents and its statistics reflect that subset.tokenize (
Callable[[str],list[str]]) – Tokenizer (default: lowercased\w+tokens). Pass a custom one for stemming, CJK, etc.
Examples
>>> import vd >>> c = vd.connect('memory').create_collection('t', dimension=2) >>> c['a'] = vd.Document(id='a', text='the quick brown fox', vector=[1.0, 0.0]) >>> c['b'] = vd.Document(id='b', text='lazy dog sleeps', vector=[0.0, 1.0]) >>> index = vd.BM25Index(c) >>> index.search('quick fox', limit=1)[0]['id'] 'a'
- search(query_text, *, limit=10, k1=1.5, b=0.75)[source]#
Okapi BM25 scores for
query_textover the indexed documents.Returns result dicts in the same shape as
Collection.search()—{"id", "text", "score", "metadata"}— sorted by descending score.k1/bare the standard Okapi hyperparameters (scoring-time, so one index can be queried with different settings).
- exception vd.BackendNotInstalledError[source]#
Bases:
VdError,ImportErrorRaised when a known backend’s Python package is not installed.
Distinct from an unknown backend name (a plain
ValueError): the backend exists invd’s provider registry, but its client library is missing. The message carries thepip installcommand to fix it.
- class vd.Client(*args, **kwargs)[source]#
Bases:
ProtocolA live connection to one backend:
Mapping[str, Collection].Collections are created explicitly (so create-time parameters such as
dimensionandmetriccan be supplied) and fetched either byget_collection()or by mapping accessclient[name].- create_collection(name, *, dimension=None, metric='cosine', **index_config)[source]#
Create a new collection; raise
ValueErrorif it exists.- Return type:
- class vd.Collection(*args, **kwargs)[source]#
Bases:
ProtocolA collection of documents:
MutableMapping[str, Document]+search.The mapping half is storage;
search()is the single retrieval extension. This minimal surface is everythingvd’s tooling depends on. Batch insertion is an optional capability — seeSupportsBatch.
- class vd.Document(id, text='', vector=None, metadata=<factory>)[source]#
Bases:
objectThe unit stored in a
Collection.- Parameters:
id (
str) – Unique identifier; the key under which the document lives in a collection.text (
str) – The text content. May be empty for vector-first use cases where no text is associated with a vector.vector (
Optional[list[float]]) – The embedding. IfNonewhen written, the collection embedstextwith its client’sembedder— or raisesEmbeddingRequiredErrorif none is configured.metadata (
dict[str,Any]) – Arbitrary metadata, used for filtering and carried through search results.
Examples
>>> doc = Document(id="doc1", text="Hello world") >>> doc.id, doc.text, doc.metadata ('doc1', 'Hello world', {}) >>> Document(id="v1", vector=[0.1, 0.2]).text ''
- exception vd.EmbeddingRequiredError[source]#
Bases:
VdError,RuntimeErrorRaised when text is given but no embedder is configured.
vdoperates on vectors. Passing raw text tocollection[key] = textorcollection.search(text)only works when theClientwas created with anembedder. Otherwise, pass aDocumentwith avector(or a pre-computed query vector) directly.
- exception vd.StaticIndexError[source]#
Bases:
VdErrorRaised on a write to a static (immutable) index.
Some backends — notably a plain FAISS flat index — build an index that cannot accept incremental
__setitem__/__delitem__after creation. Such collections setAbstractCollection.supports_incremental_writestoFalseand raise this on write. Callers branch on that flag before triggering the error, and use the adapter’s documentedrebuild()path.
- class vd.SupportsBatch(*args, **kwargs)[source]#
Bases:
ProtocolA collection that supports efficient batch insertion.
add_documentsandupsertare not part of the minimalCollectioncontract. Every adapter built onAbstractCollectionhappens to provide them, but generic code should still feature-discover:if isinstance(collection, SupportsBatch): collection.add_documents(many_docs, batch_size=256)
- class vd.SupportsHybrid(*args, **kwargs)[source]#
Bases:
ProtocolA collection that supports native hybrid (dense + lexical) search.
Hybrid search has no syntactic convergence across vector databases, so it is an opt-in capability, never baseline. Prefer the top-level
vd.hybrid_search()— it dispatches to this protocol when the collection implements it and falls back to a pure-Python BM25 + RRF fusion otherwise. Feature-discover directly only when you specifically need to refuse the fallback path:if isinstance(collection, SupportsHybrid): hits = collection.hybrid_search("query text", limit=20)
The portable contract is Reciprocal Rank Fusion (every native backend supports it). Weighted-blend (
alpha) and other backend-specific fusion variants are accepted via**kwargsand documented per adapter — they are not portable across backends.- Parameters:
query (str or list[float]) – Query text (embedded via the collection’s embedder if configured) or a pre-computed query vector for the dense side.
query_text (str, optional) – Explicit text for the lexical side. Defaults to
querywhenqueryis a string. Required whenqueryis a vector.limit (int) – Number of fused results to return.
filter (dict, optional) – Canonical
vdmetadata filter applied to both sub-searches.k_dense (int, optional) – How many results to fetch from each sub-search before fusion. Both default to
max(4 * limit, 50). Widen for higher recall.k_lexical (int, optional) – How many results to fetch from each sub-search before fusion. Both default to
max(4 * limit, 50). Widen for higher recall.rrf_k (int) – Reciprocal Rank Fusion constant (typically 60).
egress (callable, optional) – Transform applied to each fused result before it is yielded.
**kwargs – Backend-specific knobs (e.g.
alpha=0.7on weaviate,ranker="weighted"on milvus). Documented per adapter.
- class vd.SupportsNativeAsync(*args, **kwargs)[source]#
Bases:
ProtocolMarker protocol set on async clients/collections that use a backend’s native async SDK rather than the universal
asyncio.to_thread()wrapper.Why care: in high-concurrency event-loop apps (FastAPI, Starlette, etc.), a
to_thread-wrapped backend still blocks a worker thread per request. For real non-blocking I/O, prefer collections that satisfy this protocol. The wrapper sets this attribute toFalse; native adapters set it toTrue.isinstance(c, SupportsNativeAsync)matches both — checkc.native_asyncfor the boolean.
- class vd.TimeIndexedCollection(collection, *, ts_field='ts', ts_parser=<function to_datetime>)[source]#
Bases:
MutableMappingTime-indexed wrapper over any vd
Collection.Maintains a sorted
(ts_epoch, id)index alongside the underlying collection. Each stored document MUST carry a timestamp in its metadata underts_field(default"ts"). The stored value is normalized to an ISO-8601 string so backend-side filtering remains usable.- Parameters:
collection (
MutableMapping) – Any vd Collection (MutableMapping +search).ts_field (
str) – Metadata key holding the timestamp.ts_parser (
Callable[[Any],datetime]) – Optional custom parserAny -> datetime. Defaults toto_datetime().
Notes
The index is rebuilt on construction from whatever the underlying collection already contains (so the wrapper is safe to re-wrap a persisted collection across process restarts).
- property base: MutableMapping#
The wrapped underlying collection.
- query_window(start=None, end=None, *, filt=None)[source]#
Yield documents with
start <= ts < end, in chronological order.start/endmay beNonefor half-open infinity.filtis an optional MongoDB-style predicate applied to document metadata, evaluated client-side (so it works on any backend).
- reindex()[source]#
Force-rebuild the in-memory time index from the underlying collection.
- Return type:
- search_window(query, *, start=None, end=None, limit=10, filt=None, **kwargs)[source]#
Semantic search restricted to a time window.
Builds a metadata filter on
ts_fieldand delegates to the underlying collection’ssearch. Falls back to a client-side post-filter for backends that don’t honor the filter.
- time_range()[source]#
Return
(min_ts, max_ts)as aware datetimes, or None if empty.>>> from vd import connect, Document >>> import hashlib >>> emb = lambda t: [b/128.0-1.0 for b in hashlib.md5(t.encode()).digest()[:4]] >>> col = connect('memory', embedder=emb).create_collection('t') >>> t = TimeIndexedCollection(col) >>> t['a'] = Document(id='a', text='x', metadata={'ts': '2025-01-01'}) >>> t['b'] = Document(id='b', text='y', metadata={'ts': '2025-03-01'}) >>> [d.isoformat() for d in t.time_range()] ['2025-01-01T00:00:00+00:00', '2025-03-01T00:00:00+00:00']
- window_iter(window='1d', *, start=None, end=None, reducer=<function count_docs>, skip_empty=False, align=True)[source]#
Yield
(window_start, window_end, reducer_value)over fixed windows.- Parameters:
window (
Union[str,timedelta,int,float]) – Window size. Seeparse_window()for accepted forms.start (
Union[str,datetime,int,float,None]) – Override the data range. Default: actual min/max ts in the index.end (
Union[str,datetime,int,float,None]) – Override the data range. Default: actual min/max ts in the index.reducer (
Callable[[Iterable[Document]],Any]) – Callable taking the iterable of in-window ``Document``s. Default iscount_docs(). See alsomean_vector().skip_empty (
bool) – If True, omit windows that contained zero documents.align (
bool) – If True (default), alignstartto the previous midnight (for daily windows) or towindow-rounded boundary so downstream joins are clean. If False, use the literalstart.
- Return type:
- exception vd.UnsupportedCapabilityError[source]#
Bases:
VdError,NotImplementedErrorRaised when an operation needs a capability the backend lacks.
Prefer feature-discovery —
isinstance(collection, SupportsHybrid)— over catching this, but it is the clear, typed fallback when an optional operation is called on a backend that does not implement it.
- exception vd.UnsupportedFilterError[source]#
Bases:
VdError,ValueErrorRaised when a metadata filter uses an operator a backend cannot honor.
The canonical, backend-agnostic filter language lives in
vd.filters(a MongoDB-style JSON dialect). When a filter uses an operator outside a backend’s documented subset — or one that does not exist at all — this is raised, so the caller can simplify the filter or drop to the backend’s native filter via the escape hatch (collection.native).
- exception vd.VdError[source]#
Bases:
ExceptionBase class for every error
vdraises on its own behalf.
- vd.benchmark_insert(collection, n_documents=100, *, text_length=100, batch_size=10)[source]#
Benchmark document insertion performance.
- Parameters:
collection (
Collection) – Collection to benchmarkn_documents (
int) – Number of documents to inserttext_length (
int) – Length of test documentsbatch_size (
int) – Batch size for insertion
- Returns:
Benchmark results
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> results = vd.benchmark_insert(docs, n_documents=50)
- vd.benchmark_search(collection, query, *, n_queries=100, limit=10)[source]#
Benchmark search performance on a collection.
- Parameters:
collection (
Collection) – Collection to benchmarkquery (
str) – Query text to usen_queries (
int) – Number of queries to runlimit (
int) – Number of results per query
- Returns:
Benchmark results with:
total_time: Total time for all queries
avg_latency: Average query latency
min_latency: Minimum latency
max_latency: Maximum latency
p50, p95, p99: Latency percentiles
queries_per_second: Throughput
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> # Add some documents... >>> results = vd.benchmark_search(docs, "test query", n_queries=50)
- vd.bm25_lexical_search(collection, query_text, *, limit=10, filter=None, k1=1.5, b=0.75)[source]#
Brute-force BM25 lexical search over a vd collection’s stored
text.Builds a throwaway
BM25Indexovercollectionand runs a single query against it. Used as the default lexical side ofhybrid_search()when a collection does not implementSupportsHybrid.Cost is O(N) in the collection size — fine for prototypes and collections up to ~100k documents. **For repeated queries over the same collection, build a**
BM25Indexonce and callBM25Index.search()per query instead of calling this function in a loop — the term statistics are then computed once rather than on every call. For larger workloads, switch to a backend with native hybrid search (weaviate, elasticsearch, redis, lancedb, …) or pass a customlexical_searchcallable tohybrid_search()that consults a real text index.- Parameters:
collection (
Collection) – Any vd Collection. Documents whosetextis empty contribute zero score and are filtered out of the result.query_text (
str) – The lexical query.limit (
int) – Maximum number of results.filter (
Optional[dict]) – Canonicalvdmetadata filter. Applied client-side viavd.filters.matches_filter().k1 (
float) – BM25 hyperparameters. Defaults match the standard Okapi BM25.b (
float) – BM25 hyperparameters. Defaults match the standard Okapi BM25.
- Returns:
Result dicts in the same shape as
Collection.search()—{"id", "text", "score", "metadata"}— sorted by descending score.- Return type:
Examples
>>> import vd >>> c = vd.connect('memory').create_collection('t', dimension=2) >>> c['a'] = vd.Document(id='a', text='the quick brown fox', vector=[1.0, 0.0]) >>> c['b'] = vd.Document(id='b', text='lazy dog sleeps', vector=[0.0, 1.0]) >>> hits = bm25_lexical_search(c, 'quick fox', limit=1) >>> hits[0]['id'] 'a'
- vd.check_requirements(backend, *, verbose=True)[source]#
Diagnose whether
backendis ready to use, and say what to do if not.Runs an installed-check plus archetype-specific checks (embedded / server / managed), then computes the single most useful next step.
- Parameters:
backend (
str) – A provider name (seevd.list_all_backends()).verbose (
bool) – Print a human-readable report (in addition to returning the dict).
- Returns:
{"backend", "archetype", "ok", "checks", "next_step"}wherechecksis a list of{"name", "ok", "detail"}records.- Return type:
Examples
>>> report = check_requirements('memory', verbose=False) >>> report['ok'] True
- vd.chunk_documents(documents, chunk_size=500, *, overlap=50, strategy='chars', id_template='{doc_id}_chunk_{chunk_num}', preserve_metadata=True)[source]#
Chunk multiple documents while preserving metadata.
- Parameters:
documents (
Iterator[tuple[str,str|dict]]) – Iterator of (doc_id, text) or (doc_id, text, metadata) tupleschunk_size (
int) – Size of each chunkoverlap (
int) – Overlap between chunksstrategy (
str) – Chunking strategy (see chunk_text)id_template (
str) – Template for chunk IDs. Can use {doc_id} and {chunk_num}preserve_metadata (
bool) – Whether to copy metadata to all chunks
- Yields:
tuple – (chunk_id, chunk_text, metadata) tuples
- Return type:
Examples
>>> docs = [('doc1', 'Long text...', {'author': 'Alice'})] >>> chunks = list(chunk_documents(docs, chunk_size=20)) >>> len(chunks) >= 1 True
- vd.chunk_text(text, chunk_size=500, *, overlap=50, strategy='chars', preserve_sentences=True)[source]#
Chunk text into smaller pieces.
- Parameters:
text (
str) – Text to chunkchunk_size (
int) – Target size of each chunk (in characters or tokens depending on strategy)overlap (
int) – Number of characters/tokens to overlap between chunksstrategy (
str) –Chunking strategy:
’chars’: Character-based chunking
’words’: Word-based chunking
’sentences’: Sentence-based chunking
’paragraphs’: Paragraph-based chunking
preserve_sentences (
bool) – Try to avoid breaking sentences when using chars/words strategy
- Returns:
List of text chunks
- Return type:
Examples
>>> text = "This is sentence one. This is sentence two. This is sentence three." >>> chunks = chunk_text(text, chunk_size=30, strategy='chars') >>> len(chunks) >= 2 True
>>> chunks = chunk_text(text, strategy='sentences') >>> len(chunks) 3
- vd.clean_text(text, *, lowercase=False, remove_extra_whitespace=True, remove_urls=False, remove_emails=False, remove_numbers=False, remove_punctuation=False)[source]#
Clean and normalize text.
- Parameters:
- Returns:
Cleaned text
- Return type:
Examples
>>> text = "Hello World! Visit https://example.com" >>> clean_text(text, remove_urls=True) 'Hello World! Visit' >>> clean_text(text, lowercase=True, remove_punctuation=True) 'hello world visit httpsexamplecom'
- vd.collection_stats(collection)[source]#
Compute comprehensive statistics for a collection.
- Parameters:
collection (
Collection) – Collection to analyze- Returns:
Statistics including:
total_documents: Number of documents
avg_text_length: Average text length in characters
min_text_length: Minimum text length
max_text_length: Maximum text length
total_chars: Total characters across all documents
metadata_fields: Set of all metadata fields used
metadata_field_counts: Count of documents with each metadata field
embedding_dimension: Dimension of embeddings (if available)
has_vectors: Number of documents with vectors
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> docs['doc1'] = ("Hello", {'category': 'greeting'}) >>> stats = vd.collection_stats(docs) >>> print(stats['total_documents']) 1
- vd.compare_backends(names, *, characteristics=None)[source]#
Return a
{name: {characteristic: value}}table for the given providers.
- vd.connect(backend, *, embedder=None, **backend_kwargs)[source]#
Connect to a vector database backend and return its
Client.This is the single entry point of
vd. Switching vector databases is a one-argument change here.- Parameters:
backend (
str) – Backend name:"memory","chroma","qdrant","faiss","lancedb","sqlite_vec","duckdb","pgvector","pinecone", … Runvd.list_backends()for what is installed.embedder (
Optional[Callable[[str],list[float]]]) – Atext -> vectorfunction. Supply it only if you want the convenience of passing raw text tocollection[key] = "text"andcollection.search("query text").vdnever embeds on its own — with no embedder, passDocumentobjects with vectors and pre-computed query vectors.**backend_kwargs – Backend-specific connection options (
persist_directory,url,api_key,path, …). See each adapter’s docstring.
- Returns:
A connected client — a
Mappingof collection name to collection.- Return type:
Examples
>>> client = connect('memory') >>> client = connect('chroma', persist_directory='./db') >>> client = connect('qdrant', url='http://localhost:6333')
- async vd.connect_async(backend, *, native=True, **kwargs)[source]#
Async sibling of
vd.connect().Returns an
AsyncClient. When the backend has a native async client (seelist_async_backends(); todayqdrant), its registered factory decides: it may return a native client doing real non-blocking I/O (qdrant withurl=) or the wrapper (embedded qdrant, whose async client would block the loop). Every other backend goes through the universalAsyncClientWrapper, built onasyncio.to_thread(). Checkclient.native_asyncto tell them apart.- Parameters:
backend (
str) – Backend name — same vocabulary asvd.connect().native (
bool) – Use the backend’s native async client when one is registered (default).Falseforces theto_threadwrapper around the sync adapter.**kwargs – Forwarded to the native client’s constructor, or to
vd.connect()for the wrapper. Both take the same arguments (embedder,url,path, …).
- Returns:
A live async client.
awaitonce at session start:client = await vd.connect_async("memory")- Return type:
Examples
>>> import asyncio, vd >>> async def go(): ... client = await vd.connect_async("memory") ... col = await client.create_collection("docs", dimension=2) ... await col.set("a", vd.Document(id="a", text="x", vector=[1.0, 0.0])) ... return await col.count() >>> asyncio.run(go()) 1
- vd.connect_from_config(path=None, *, profile=None, apply_env=True, embedder=None, **overrides)[source]#
Connect to a backend using configuration from a file.
- Parameters:
path (
Union[str,Path,None]) – Path to configuration file. If not provided, searches for default config files.profile (
Optional[str]) – Profile name to use from configuration. Defaults to ‘default’ or the VD_PROFILE environment variable.apply_env (
bool) – Whether to apply environment variable overridesembedder (
Optional[Callable[[str],list[float]]]) – Optionaltext -> vectorconvenience embedder, passed tovd.connect(). A vd config file describes the *backend connection*, not embedding — embedding stays the caller’s concern.**overrides – Additional keyword arguments to override configuration values
- Returns:
Connected client instance
- Return type:
Examples
>>> # With a config file >>> client = connect_from_config('vd.yaml')
>>> # With a specific profile >>> client = connect_from_config('vd.yaml', profile='production')
>>> # With environment variable VD_PROFILE=dev >>> client = connect_from_config()
>>> # With overrides >>> client = connect_from_config('vd.yaml', persist_directory='./data')
- vd.copy_collection(source, target, *, batch_size=100, preserve_vectors=True)[source]#
Copy a collection with flexible source/target specification.
- Parameters:
source (
tuple[str,str] |Collection) – Either a Collection object or (backend_name, collection_name) tupletarget (
tuple[str,str,dict] |Collection) – Either a Collection object or (backend_name, collection_name, config) tuplebatch_size (
int) – Batch size for copyingpreserve_vectors (
bool) – Whether to preserve vectors
- Returns:
Migration statistics
- Return type:
Examples
>>> import vd >>> # Copy between backends >>> stats = vd.copy_collection( ... source=('memory', 'docs'), ... target=('chroma', 'docs', {'persist_directory': './data'}), ... )
- vd.cosine_similarity(vec1, vec2)[source]#
Cosine similarity of two vectors (1.0 identical, 0.0 orthogonal).
- Return type:
Examples
>>> cosine_similarity([1.0, 0.0], [1.0, 0.0]) 1.0 >>> cosine_similarity([1.0, 0.0], [0.0, 1.0]) 0.0
- vd.count_docs(docs)[source]#
lenreducer that also handles generator inputs.- Return type:
>>> count_docs(iter([1, 2, 3])) 3
- vd.create_example_config(format='yaml')[source]#
Generate an example configuration file content.
- Parameters:
format (
str) – Format of configuration: ‘yaml’ or ‘toml’- Returns:
Example configuration as a string
- Return type:
Examples
>>> yaml_config = create_example_config('yaml') >>> print(yaml_config) >>> toml_config = create_example_config('toml')
- vd.deduplicate_results(results, *, key='id', keep='first')[source]#
Remove duplicate results.
- Parameters:
- Yields:
dict – Deduplicated results
- Return type:
Examples
>>> results = [ ... {'id': 'doc1', 'score': 0.9}, ... {'id': 'doc1', 'score': 0.8}, ... {'id': 'doc2', 'score': 0.7} ... ] >>> unique = list(deduplicate_results(iter(results))) >>> len(unique) 2
- vd.euclidean_distance(vec1, vec2)[source]#
Euclidean (L2) distance between two vectors.
- Return type:
Examples
>>> euclidean_distance([1.0, 0.0], [1.0, 0.0]) 0.0
- vd.export_collection(collection, output_path, *, format='jsonl', **kwargs)[source]#
Export a collection to a file in the specified format.
- Parameters:
collection (
Collection) – Collection to exportformat (
str) – Export format: ‘jsonl’, ‘json’, ‘directory’**kwargs – Additional format-specific options
- Returns:
Number of documents exported
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> vd.export_collection(docs, 'backup.jsonl')
- vd.export_to_directory(collection, output_dir, *, include_vectors=True)[source]#
Export collection as a directory with one JSON file per document.
Useful for version control and easy browsing.
- Parameters:
collection (
Collection) – Collection to exportinclude_vectors (
bool) – Whether to include vectors
- Returns:
Number of documents exported
- Return type:
- vd.export_to_json(collection, output_path, *, include_vectors=True, indent=2)[source]#
Export a collection to JSON format.
Creates a JSON array of all documents.
- vd.export_to_jsonl(collection, output_path, *, include_vectors=True)[source]#
Export a collection to JSONL (JSON Lines) format.
Each line is a JSON object representing a document.
- Parameters:
collection (
Collection) – Collection to exportinclude_vectors (
bool) – Whether to include embedding vectors in the export
- Returns:
Number of documents exported
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> docs['doc1'] = "Hello" >>> vd.export_to_jsonl(docs, 'backup.jsonl') 1
- vd.extract_metadata(text, *, extract_title=True, extract_length=True, extract_word_count=True, extract_language=False)[source]#
Extract metadata from text.
- Parameters:
- Returns:
Extracted metadata
- Return type:
Examples
>>> text = "My Title\n\nThis is the content." >>> meta = extract_metadata(text) >>> meta['title'] 'My Title' >>> meta['char_count'] 30
- vd.find_duplicates(collection, *, threshold=0.95, method='cosine')[source]#
Find near-duplicate documents in a collection.
- Parameters:
collection (
Collection) – Collection to analyzethreshold (
float) – Similarity threshold above which documents are considered duplicatesmethod (
str) – Similarity method: ‘cosine’ or ‘exact’
- Returns:
List of (doc_id1, doc_id2, similarity) tuples for duplicates
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> docs['doc1'] = "Hello world" >>> docs['doc2'] = "Hello world" >>> duplicates = vd.find_duplicates(docs) >>> len(duplicates) > 0 True
- vd.find_outliers(collection, *, n_neighbors=5, threshold=0.3)[source]#
Find outlier documents (those dissimilar to their neighbors).
- Parameters:
collection (
Collection) – Collection to analyzen_neighbors (
int) – Number of neighbors to considerthreshold (
float) – Average similarity threshold below which a document is an outlier
- Returns:
List of (doc_id, avg_similarity) for outliers
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> # Add some documents... >>> outliers = vd.find_outliers(docs)
- vd.get_backend_characteristics()[source]#
Return a compact
{name: characteristics}map for comparison tooling.
- vd.get_install_instructions(name)[source]#
Return a human-readable setup blurb for one provider.
- Return type:
- vd.health_check_backend(backend_name, **config)[source]#
Check if a backend is healthy and accessible.
- Parameters:
backend_name (
str) – Backend name to check**config – Backend-specific configuration
- Returns:
Health report with keys:
status: ‘healthy’, ‘unhealthy’, or ‘unavailable’
available: Whether backend is installed
registered: Whether backend is registered
message: Status message
details: Additional details (if connected successfully)
- Return type:
Examples
>>> import vd >>> status = vd.health_check_backend('memory') >>> print(status['status']) 'healthy'
- vd.health_check_collection(collection)[source]#
Check collection health and compute basic stats.
- Parameters:
collection (
Collection) – Collection to check- Returns:
Health report
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> status = vd.health_check_collection(docs)
- vd.hybrid_search(collection, query, *, query_text=None, limit=10, filter=None, k_dense=None, k_lexical=None, rrf_k=60, lexical_search=None, egress=None, **kwargs)[source]#
Hybrid (dense + lexical) search that works on any vd Collection.
Dispatches to the collection’s native
hybrid_searchwhen it implementsSupportsHybrid(efficient, server-side). Otherwise fuses the collection’s own densesearch()with a client-side lexical scan (default:bm25_lexical_search()) via **Reciprocal Rank Fusion**.The portable contract is RRF. Backend-specific knobs (weighted blend
alpha, fusion-type variants, native ranker choices) are accepted via**kwargsand forwarded to the adapter when it has a native implementation; they are ignored by the client-side fallback.- Parameters:
collection (
Collection) – Any vd Collection — native-hybrid or not.query (
Union[str,list[float]]) – Query text (embedded by the collection if it has an embedder) or a pre-computed query vector. Whenqueryis a vector,query_textis required.query_text (
Optional[str]) – Explicit text for the lexical side. Defaults toquerywhenqueryis a string.limit (
int) – Number of fused results to return.filter (
Optional[dict]) – Canonicalvdmetadata filter, applied to both sub-searches.k_dense (
Optional[int]) – How many results to fetch from each sub-search before fusion. Default ismax(4 * limit, 50)for each side. Widen for higher recall.k_lexical (
Optional[int]) – How many results to fetch from each sub-search before fusion. Default ismax(4 * limit, 50)for each side. Widen for higher recall.rrf_k (
int) – Reciprocal Rank Fusion constant (typically 60).lexical_search (
Optional[Callable[...,list[dict[str,Any]]]]) – Customlexical_search(collection, query_text, *, limit, filter, \*\*kwargs) -> list[SearchResult]. Defaults tobm25_lexical_search(). Used only on the fallback path; on the native path it is ignored with aUserWarning.egress (
Optional[Callable[[dict[str,Any]],Any]]) – Per-result transform applied before yielding.**kwargs – Extra options. On the native path they are forwarded to the adapter (e.g.
alpha=0.7on weaviate). On the fallback path they are ignored.
- Yields:
dict – Fused result dicts.
scoreis the RRF score on the fallback path, or the adapter’s fused score on the native path.- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> col = client.create_collection('docs', dimension=2) >>> col['a'] = vd.Document(id='a', text='cats purr', ... vector=[1.0, 0.0]) >>> col['b'] = vd.Document(id='b', text='dogs bark', ... vector=[0.0, 1.0]) >>> hits = list(vd.hybrid_search(col, [0.9, 0.1], query_text='cats', ... limit=1)) >>> hits[0]['id'] 'a'
- async vd.hybrid_search_async(collection, query, *, query_text=None, limit=10, filter=None, k_dense=None, k_lexical=None, rrf_k=60, lexical_search=None, egress=None, **kwargs)[source]#
Async sibling of
vd.hybrid_search().For a wrapped sync collection, runs
vd.hybrid_search()(native hybrid if the backend has it, else the client-side BM25 + RRF fallback) on a worker thread. For a native async collection it awaits the collection’s ownhybrid_searchif it has one, and otherwise fuses the collection’s async dense search with a client-side BM25 scan (O(N): it reads every document) via RRF. On a native collection, anasync deflexical_searchreceives the async collection; a sync one receives a materialized{id: Document}dict (every document is read per call) and runs on a worker thread. Either way the awaitable + async iterator interface stays uniform.Parameters mirror
vd.hybrid_search()exactly; see that function for the full docs.- Yields:
dict – Fused result dicts.
- Return type:
AsyncIterator[dict[str, Any]]
Examples
>>> import asyncio, vd >>> async def go(): ... client = await vd.connect_async("memory") ... col = await client.create_collection("docs", dimension=2) ... await col.set("a", vd.Document(id="a", text="cats", ... vector=[1.0, 0.0])) ... await col.set("b", vd.Document(id="b", text="dogs", ... vector=[0.0, 1.0])) ... hits = [] ... async for h in vd.hybrid_search_async(col, [0.9, 0.1], ... query_text="cats", limit=1): ... hits.append(h["id"]) ... return hits >>> asyncio.run(go()) ['a']
- vd.import_collection(collection, input_path, *, format=None, **kwargs)[source]#
Import documents into a collection from a file.
- Parameters:
collection (
Collection) – Collection to import intoformat (
Optional[str]) – Import format: ‘jsonl’, ‘json’, ‘directory’ If None, inferred from file extension**kwargs – Additional format-specific options
- Returns:
Number of documents imported
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> vd.import_collection(docs, 'backup.jsonl')
- vd.import_from_directory(collection, input_dir, *, batch_size=100, skip_existing=False, pattern='*.json')[source]#
Import documents from a directory of JSON files.
- Parameters:
- Returns:
Number of documents imported
- Return type:
- vd.import_from_json(collection, input_path, *, batch_size=100, skip_existing=False)[source]#
Import documents from JSON format into a collection.
- vd.import_from_jsonl(collection, input_path, *, batch_size=100, skip_existing=False)[source]#
Import documents from JSONL format into a collection.
- Parameters:
collection (
Collection) – Collection to import intobatch_size (
int) – Batch size for adding documentsskip_existing (
bool) – If True, skip documents with IDs that already exist
- Returns:
Number of documents imported
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> vd.import_from_jsonl(docs, 'backup.jsonl') 1
- vd.install_backend(backend, *, run=False)[source]#
Return (and optionally run) the
pip installcommand forbackend.
- vd.install_command(name)[source]#
Return the
pip installcommand that makesnameusable.A backend
vdhas an adapter for installs throughvd’s own extra (vd[<backend>]), which pins exactly the client libraries that adapter imports. The extra is double-quoted so the command is safe to paste into zsh, bash, PowerShell and cmd. Providers without an adapter get their raw client package(s).- Return type:
Examples
>>> install_command('qdrant') 'pip install "vd[qdrant]"' >>> install_command('opensearch') 'pip install opensearch-py' >>> install_command('memory') 'memory needs no installation (built into vd)'
- vd.list_all_backends()[source]#
Return every provider with live
installed/has_adapterflags added.
- vd.list_async_backends()[source]#
Return the names of backends with a registered native async client.
Every other backend still works with
connect_async(), through the universalto_threadwrapper.
- vd.list_available_backends()[source]#
Return providers
vdcanconnect()right now.A backend is available iff its adapter module imported successfully — which happens only when its client library is installed. This is exactly the set of registered backends.
- vd.list_backends()[source]#
Return the names of all backends with a registered (importable) adapter.
- vd.load_config(path=None, *, format=None)[source]#
Load configuration from a file.
Automatically detects format from file extension if not specified.
- Parameters:
- Returns:
Configuration dictionary
- Return type:
Examples
>>> config = load_config('vd.yaml') >>> config = load_config('vd.toml') >>> config = load_config() # Looks for default config files
- vd.matches_filter(metadata, filter)[source]#
Return
Trueifmetadatasatisfies the MongoDB-stylefilter.An empty or
Nonefilter matches everything. Unknown operators raiseUnsupportedFilterError— they never silently match.- Parameters:
- Return type:
Examples
>>> matches_filter({'year': 2024}, None) True >>> matches_filter({'year': 2024, 'cat': 'tech'}, ... {'year': {'$gte': 2020}, 'cat': 'tech'}) True >>> matches_filter({'views': 50}, {'views': {'$gte': 10, '$lte': 100}}) True
- vd.mean_vector(docs)[source]#
Element-wise mean of document embeddings.
Noneif empty / no vectors.>>> from vd.base import Document >>> mean_vector([ ... Document(id='a', text='', vector=[1.0, 2.0]), ... Document(id='b', text='', vector=[3.0, 4.0]), ... ]) [2.0, 3.0] >>> mean_vector([]) is None True
- vd.metadata_distribution(collection, field, *, top_n=None)[source]#
Get the distribution of values for a metadata field.
- Parameters:
collection (
Collection) – Collection to analyzefield (
str) – Metadata field nametop_n (
Optional[int]) – If specified, return only the top N most common values
- Returns:
Mapping of field values to their counts
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> docs['doc1'] = ("Hello", {'category': 'A'}) >>> docs['doc2'] = ("World", {'category': 'A'}) >>> docs['doc3'] = ("Test", {'category': 'B'}) >>> dist = vd.metadata_distribution(docs, 'category') >>> print(dist) {'A': 2, 'B': 1}
- vd.migrate_client(source_client, target_client, *, collection_names=None, batch_size=100, preserve_vectors=True, progress_callback=None)[source]#
Migrate all (or selected) collections from one client to another.
- Parameters:
source_client (
Client) – Source database clienttarget_client (
Client) – Target database clientcollection_names (
Optional[list[str]]) – Specific collections to migrate. If None, migrates all.batch_size (
int) – Batch size for migrationpreserve_vectors (
bool) – Whether to preserve vectorsprogress_callback (
Optional[Callable[[str,int,int],None]]) – Function called with (collection_name, current, total)
- Returns:
Overall migration statistics
- Return type:
Examples
>>> import vd >>> source = vd.connect('memory') >>> target = vd.connect('chroma', persist_directory='./backup') >>> stats = vd.migrate_client(source, target)
- vd.migrate_collection(source_collection, target_collection, *, batch_size=100, preserve_vectors=True, progress_callback=None, skip_existing=False)[source]#
Migrate a collection from one backend to another.
- Parameters:
source_collection (
Collection) – Source collection to migrate fromtarget_collection (
Collection) – Target collection to migrate tobatch_size (
int) – Number of documents to migrate per batchpreserve_vectors (
bool) – Whether to preserve pre-computed vectorsprogress_callback (
Optional[Callable[[int,int],None]]) – Function called with (current, total) to report progressskip_existing (
bool) – If True, skip documents that already exist in target
- Returns:
Migration statistics with keys:
total: Total documents in source
migrated: Number of documents migrated
skipped: Number of documents skipped
failed: Number of failures
errors: List of error messages
- Return type:
Examples
>>> import vd >>> # Create source and target >>> source_client = vd.connect('memory') >>> target_client = vd.connect('chroma', persist_directory='./data') >>> source = source_client.get_collection('my_docs') >>> target = target_client.create_collection('my_docs') >>> >>> # Migrate >>> stats = vd.migrate_collection(source, target) >>> print(f"Migrated {stats['migrated']} documents")
- vd.multi_query_search(collection, queries, *, limit=10, combine='interleave', filter=None, **kwargs)[source]#
Search with multiple queries and combine results.
- Parameters:
collection (
Collection) – Collection to searchlimit (
int) – Total number of results to returncombine (
str) –How to combine results:
’interleave’: Interleave results from each query
’concatenate’: Concatenate all results
’union’: Remove duplicates across queries
’best’: Take best results across all queries
**kwargs – Additional search options
- Yields:
dict – Search results
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> results = vd.multi_query_search( ... docs, ... ["What is AI?", "How does ML work?"], ... limit=10 ... )
- vd.normalize_document_input(doc_input, *, auto_id=True)[source]#
Normalize a flexible document input to a
Document.Accepted shapes: a
Document; astr(just text); a tuple(text, id),(text, metadata), or(text, id, metadata).- Parameters:
- Return type:
Examples
>>> normalize_document_input(("Hello", "doc1")).id 'doc1' >>> normalize_document_input(("Hello", {"k": "v"})).metadata {'k': 'v'} >>> normalize_document_input("Hello world").id.startswith('doc_') True
- vd.normalize_whitespace(text)[source]#
Normalize whitespace in text.
Replaces tabs, multiple spaces, and multiple newlines with single versions.
Examples
>>> normalize_whitespace("Hello\t\tWorld \n\n\nTest") 'Hello World \nTest'
- vd.parse_window(window)[source]#
Parse a window spec into a
timedelta.Strings use a trailing unit char:
"1d","4h","30m","15s","1w". Numbers are treated as seconds. Atimedeltais returned as-is.- Return type:
>>> parse_window('1d') == timedelta(days=1) True >>> parse_window('4h') == timedelta(hours=4) True >>> parse_window(3600) == timedelta(hours=1) True
- vd.print_backends_table()[source]#
Print every known vector database, grouped by deployment archetype.
- Return type:
- vd.print_comparison(names)[source]#
Print a side-by-side comparison table of the given providers.
- Return type:
- vd.print_recommendation(**kwargs)[source]#
Run
recommend_backend()and print the recommendation readably.- Return type:
- vd.reciprocal_rank_fusion(result_lists, *, k=60)[source]#
Combine multiple result lists using Reciprocal Rank Fusion.
RRF is a simple yet effective way to combine rankings from multiple sources.
- Parameters:
- Returns:
Combined and re-ranked results
- Return type:
Examples
>>> results1 = [{'id': 'doc1', 'score': 0.9}, {'id': 'doc2', 'score': 0.8}] >>> results2 = [{'id': 'doc2', 'score': 0.95}, {'id': 'doc3', 'score': 0.7}] >>> combined = reciprocal_rank_fusion([results1, results2])
- vd.recommend_backend(*, corpus_size='medium', persistence=True, can_run_docker=True, cloud_ok=True, budget='free', existing_db=None, needs_hybrid=False, air_gapped=False)[source]#
Recommend a vector database from a few yes/no facts about the situation.
A direct encoding of the decision framework in the report’s §4. Returns a primary pick, a runner-up, and the reasoning trail.
- Parameters:
corpus_size (
str) – Rough vector count: tiny <100k, small <10M, medium ~10M, large <100M, huge >100M.persistence (
bool) – Must data survive a process restart?can_run_docker (
bool) – Can the user run Docker / operate a server process?cloud_ok (
bool) – Is a managed cloud service acceptable (vs. on-prem only)?budget (
str) – Free-tier-only, or is paid acceptable?existing_db (
Optional[str]) – A database the user already operates — strongly biases the pick.needs_hybrid (
bool) – Need keyword + vector ranking fused in one query?air_gapped (
bool) – Must run with zero network / zero telemetry?
- Returns:
{"primary", "runner_up", "reasoning", "alternatives"}.- Return type:
Examples
>>> rec = recommend_backend(corpus_size='tiny', persistence=False) >>> rec['primary'] 'memory' >>> rec = recommend_backend(existing_db='postgres') >>> rec['primary'] 'pgvector'
- vd.register_async_backend(name, factory=None)[source]#
Register a native async client factory for backend
name.Use as a class decorator on an
AsyncAbstractClientsubclass, or call it with afactory(a class or a function, sync orasync, taking theconnect_async()keyword arguments). Once registered,connect_async()returns the factory’s client instead of theto_threadwrapper.Examples
>>> @register_async_backend('example') ... class ExampleAsyncClient(AsyncAbstractClient): ... ...
- vd.register_backend(name)[source]#
Class decorator: register an adapter
Clientundername.Examples
>>> from vd.base import AbstractClient >>> @register_backend('example') ... class ExampleClient(AbstractClient): ... ...
- vd.sample_collection(collection, n, *, method='random', seed=None)[source]#
Sample document IDs from a collection.
- Parameters:
collection (
Collection) – Collection to sample fromn (
int) – Number of documents to samplemethod (
str) – Sampling method: ‘random’, ‘first’, ‘diverse’
- Returns:
Sampled document IDs
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> # Add 100 documents... >>> sample = vd.sample_collection(docs, 10, method='random') >>> len(sample) 10
- vd.save_config(config, path, *, format=None)[source]#
Save configuration to a file.
- Parameters:
- Return type:
Examples
>>> config = { ... 'profiles': { ... 'dev': {'backend': 'memory'}, ... 'prod': {'backend': 'chroma', 'persist_directory': './data'} ... } ... } >>> save_config(config, 'vd.yaml')
- vd.search_similar_to_document(collection, doc_id, *, limit=10, exclude_self=True, filter=None, **kwargs)[source]#
Find documents similar to a specific document.
- Parameters:
- Yields:
dict – Search results
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> similar = vd.search_similar_to_document(docs, 'doc1', limit=5)
- vd.setup_guide(backend)[source]#
Return a full, copy-pasteable setup playbook for
backend.Covers: the pip install, a Docker one-liner for server backends, the environment variables for managed backends, a verify command, and the relevant documentation links.
- Return type:
- vd.text_only(result)[source]#
Egress: keep only the text.
>>> text_only({'text': 'hi'})->'hi'.- Return type:
- vd.to_datetime(ts)[source]#
Coerce a timestamp-like value into a tz-aware UTC
datetime.Accepts ISO-8601 strings (with or without timezone), date-only strings (
"2025-03-13"), epoch seconds (int or float), anddatetimeobjects. Naive datetimes / strings are assumed UTC.- Return type:
>>> to_datetime('2025-03-13T09:00:00').isoformat() '2025-03-13T09:00:00+00:00' >>> to_datetime('2025-03-13').isoformat() '2025-03-13T00:00:00+00:00' >>> to_datetime(1741856400).isoformat() '2025-03-13T09:00:00+00:00'
- vd.to_iso(ts)[source]#
ISO-8601 (UTC) string suitable for cross-backend metadata storage.
- Return type:
>>> to_iso('2025-03-13T09:00:00') '2025-03-13T09:00:00+00:00'
- vd.truncate_text(text, max_length, *, suffix='...')[source]#
Truncate text to maximum length.
- Parameters:
- Returns:
Truncated text
- Return type:
Examples
>>> truncate_text("This is a long text", 10) 'This is...'
- vd.validate_collection(collection)[source]#
Validate collection integrity and identify issues.
- Parameters:
collection (
Collection) – Collection to validate- Returns:
Validation report with:
valid: Whether collection is valid
issues: List of issue descriptions
warnings: List of warning messages
stats: Basic stats
- Return type:
Examples
>>> import vd >>> client = vd.connect('memory') >>> docs = client.create_collection('test') >>> report = vd.validate_collection(docs) >>> print(report['valid']) True
- vd.validate_filter(filter, *, supported=frozenset({'$and', '$eq', '$exists', '$gt', '$gte', '$in', '$lt', '$lte', '$ne', '$nin', '$not', '$or'}))[source]#
Walk
filterand raiseUnsupportedFilterErroron any operator that is unknown or not insupported.Backends that translate the canonical filter to a native query call this with their own (possibly narrower)
supportedsubset, so callers get a clearvderror up front instead of an opaque backend error later.- Parameters:
- Return type:
Examples
>>> validate_filter({'year': {'$gte': 2020}}) # ok, returns None >>> validate_filter({'a': {'$regex': '.*'}}) # not in the language Traceback (most recent call last): ... vd.base.UnsupportedFilterError: Unknown filter operator '$regex'. ... >>> validate_filter({'a': {'$exists': True}}, supported={'$eq'}) Traceback (most recent call last): ... vd.base.UnsupportedFilterError: Filter operator '$exists' is not supported ...
Modules
Analytics and statistics for vd collections. |
|
Async support for |
|
Backend adapters for the vector databases |
|
Core contracts, data model, and abstract bases for the |
|
Command-line interface for vd. |
|
Configuration management for vd. |
|
The canonical metadata-filter language for |
|
Health check and validation utilities for vd. |
|
Import/export utilities for vd collections. |
|
Migration utilities for moving data between backends. |
|
Setup assistance: turning provider metadata into actionable diagnostics. |
|
Advanced search utilities for vd. |
|
Text preprocessing and chunking utilities for vd. |
|
Time-indexed vector collection wrapper. |
|
The backend registry, the |