ann_router.base module

The common ANN index interface every backend implements.

The router’s whole value proposition is that eight very different vector-search engines — from a pure-numpy brute-force scan to Qdrant — can be driven through one small surface. This module defines that surface:

  • Capabilities — a static descriptor of what a backend can do (remove? filter? persist? needs a GPU?), so the router can reason about a backend without importing its (possibly absent) dependency.

  • ANNIndex — the abstract base class with build / add / add_with_ids / remove / search / save / load.

  • NotSupported and BackendUnavailable — the two honest failure modes: an operation a backend genuinely cannot do (Annoy removes), versus a backend whose optional dependency is not installed.

Consumes: ann_router.spec (metric names). Produces: the base classes every module in ann_router.backends subclasses.

Author: Warith Harchaoui <warith.harchaoui@deraison.ai>

class ann_router.base.ANNIndex(dim, metric='cosine', **kwargs)[source]

Bases: ABC

Abstract base class for every ANN backend adapter.

Subclasses wrap one engine behind a uniform surface so the router — and downstream code — can build, query, mutate, and persist an index without knowing which engine is underneath. Not every backend supports every operation; those raise NotSupported and advertise the limitation via capabilities().

Parameters:
  • dim (int) – Embedding dimensionality the index is built for.

  • metric ({"cosine", "l2", "ip"}, optional) – Distance metric. Defaults to "cosine".

  • **kwargs – Backend-specific build parameters (e.g. HNSW M).

Notes

Concrete constructors must not import their heavy dependency at module import time — only inside methods (or a lazily-called checker) — so that import ann_router stays cheap and dependency-free.

abstractmethod add(vectors)[source]

Append vectors, assigning them the next contiguous ids.

Parameters:

vectors (numpy.ndarray) – Shape (m, dim).

Raises:

NotSupported – If the backend is frozen after build (e.g. Annoy).

Return type:

None

abstractmethod add_with_ids(vectors, ids)[source]

Append vectors with explicit ids.

Parameters:
  • vectors (numpy.ndarray) – Shape (m, dim).

  • ids (numpy.ndarray) – Shape (m,) integer ids.

Raises:

NotSupported – If the backend cannot map external ids or is frozen.

Return type:

None

abstractmethod build(vectors, ids=None)[source]

Build the index from an initial batch of vectors.

Parameters:
  • vectors (numpy.ndarray) – Shape (n, dim), dtype float32 (coerced if needed).

  • ids (numpy.ndarray, optional) – Shape (n,) integer ids. Defaults to range(n).

Returns:

self, so calls can be chained.

Return type:

ANNIndex

abstractmethod classmethod capabilities()[source]

Return the static capability descriptor for this backend.

Returns:

What the backend can do — readable without importing its dependency.

Return type:

Capabilities

abstractmethod classmethod is_available()[source]

Return True if the backend’s dependency is importable.

Returns:

True when the engine can actually be used on this machine.

Return type:

bool

abstractmethod load(path)[source]

Load a previously save()d index from path.

Parameters:

path (str) – Source path produced by save().

Returns:

self, populated from disk.

Return type:

ANNIndex

abstractmethod remove(ids)[source]

Delete vectors by id.

Parameters:

ids (numpy.ndarray) – Shape (m,) integer ids to drop.

Raises:

NotSupported – If the backend cannot delete (e.g. Annoy) or only tombstones.

Return type:

None

abstractmethod save(path)[source]

Persist the index to path.

Parameters:

path (str) – Destination file (or directory) path.

Raises:

NotSupported – If the backend cannot serialise itself.

Return type:

None

abstractmethod search(queries, k)[source]

Return the k nearest neighbours of each query.

Parameters:
  • queries (numpy.ndarray) – Shape (q, dim).

  • k (int) – Number of neighbours per query.

Returns:

  • ids (numpy.ndarray) – Shape (q, k) integer neighbour ids (-1 pads missing slots).

  • distances (numpy.ndarray) – Shape (q, k) distances under the index metric.

Return type:

tuple[ndarray, ndarray]

exception ann_router.base.BackendUnavailable[source]

Bases: ImportError

Raised when a backend’s optional dependency is not installed.

Importing ann_router must never fail because faiss or turbovec are absent, so backends defer their heavy imports. When a caller actually tries to use an uninstalled backend, this actionable error names the pip install extra that fixes it.

Examples

>>> raise BackendUnavailable("faiss not installed. Run: pip install 'ann-router[faiss]'")
Traceback (most recent call last):
...
ann_router.base.BackendUnavailable: faiss not installed. Run: pip install 'ann-router[faiss]'
class ann_router.base.Capabilities(name, supports_add, supports_remove, supports_filter, persistent, needs_gpu, approximate, metrics, pip_extra)[source]

Bases: object

Static description of what a backend can and cannot do.

The router reads this without importing the backend’s dependency, so it can rank engines even when their libraries are absent. Each flag maps to a routing consequence documented inline.

Parameters:
  • name (str) – The backend identifier (matches ann_router.spec.BackendName).

  • supports_add (bool) – Can accept vectors after the initial build (streaming inserts).

  • supports_remove (bool) – Can delete vectors by id without a full rebuild.

  • supports_filter (bool) – Can restrict a search by structured metadata / payload.

  • persistent (bool) – Naturally survives process restarts (a database) as opposed to needing an explicit save/load round-trip.

  • needs_gpu (bool) – Requires a GPU to be worthwhile (or at all).

  • approximate (bool) – Returns approximate neighbours (vs. exact ground truth).

  • metrics (tuple of str) – The distance metrics the backend supports.

  • pip_extra (str) – The optional-dependency extra that installs it (empty for exact).

Examples

>>> cap = Capabilities(name="exact", supports_add=True, supports_remove=True,
...                     supports_filter=False, persistent=False, needs_gpu=False,
...                     approximate=False, metrics=("cosine", "l2", "ip"), pip_extra="")
>>> cap.approximate
False
approximate: bool
metrics: tuple[str, ...]
name: str
needs_gpu: bool
persistent: bool
pip_extra: str
supports_add: bool
supports_filter: bool
supports_remove: bool
to_dict()[source]

Return a JSON-serialisable view of the descriptor.

Returns:

One key per flag.

Return type:

dict

Examples

>>> from ann_router.backends.exact import ExactIndex
>>> ExactIndex.capabilities().to_dict()["name"]
'exact'
exception ann_router.base.NotSupported[source]

Bases: RuntimeError

Raised when a backend genuinely cannot perform a requested operation.

This is distinct from “not yet implemented”: it flags a fundamental limitation of the engine (e.g. Annoy is frozen after build and cannot add or remove). The router uses the capability descriptor to avoid routing a dynamic workload to such a backend, but the guard here is the last line of defence for callers who instantiate a backend directly.

Examples

>>> raise NotSupported("annoy: remove() unsupported (frozen index)")
Traceback (most recent call last):
...
ann_router.base.NotSupported: annoy: remove() unsupported (frozen index)