Fast hybrid vector search, built in Rust.
Dense and sparse retrieval in one index. Native multivector / ColBERT reranking. Embeddable as a Rust library or served over HTTP. Beats hnswlib on the recall–latency Pareto frontier up to 96% recall.
# Rust
cargo add annex
# Python
pip install ANNexDB v0.2.0 · Apache-2.0 / MIT · one maintainer, actively developed
k-NN graph · query vector and nearest neighbors
ANNex is a Rust-native embedded library with hybrid dense-sparse retrieval, multivector / late-interaction reranking, and WAL-backed persistence. Compare with the most commonly evaluated alternatives.
| Feature | ANNex | Qdrant | FAISS | hnswlib |
|---|---|---|---|---|
| Dense vector search | ✓ | ✓ | ✓ | ✓ |
| Sparse / BM25 | ✓ | ✓ | ✗ | ✗ |
| Hybrid RRF fusion | ✓ | ✓ | ✗ | ✗ |
| Multivector / ColBERT | ✓ | ✓ | ✗ | ✗ |
| Payload filters | ✓ | ✓ | ✗ | ✗ |
| Embedded (no server) | ✓ | ✗ | ✓ | ✓ |
| HTTP server | ✓ | ✓ | ✗ | ✗ |
| Rust-native library | ✓ | ✗ | ✗ | ✗ |
| Python bindings | ✓ | ✓ | ✓ | ✓ |
| Snapshots + WAL | ✓ | ✓ | ✗ | ✗ |
ANNex leads hnswlib across the full recall frontier from 83% to 96% recall on NYT256. The gap widens at higher recall targets — where the engineering cost of approaching exactness usually hurts most.
The Rust library is the core — embed it directly or build snapshots that the Python bindings can load. The HTTP server (annex-multivector) handles hybrid queries and named vector fields.
[dependencies]
annex = "0.2.0" use annex::{DistanceMetric, Segment};
// build a 128-dim cosine index
let mut seg = Segment::with_config(
DistanceMetric::Cosine,
16, // M (HNSW graph connections)
64, // ef_construction
16, // level cap
128, // vector dimension
);
// insert vectors
for id in 0..1000u64 {
let vector = vec![id as f32; 128];
seg.insert_with_id(id, vector, None).unwrap();
}
// search — returns up to k scored hits
let query = vec![0.1_f32; 128];
let hits = seg.search(&query, 10, None).unwrap();
for hit in hits {
println!("id={} score={:.4}", hit.id, hit.raw_score);
} import annexdb
import numpy as np
# load a snapshot built by the Rust library
index = annexdb.Index("segment.bin", quantize=False)
# single query
query = np.random.rand(index.dim()).astype(np.float32)
ids, scores = index.search(query, k=10, ef=128)
# multithreaded batch search
queries = np.random.rand(64, index.dim()).astype(np.float32)
ids, scores = index.search_batch(
queries, k=10, ef=128, threads=4
) These are real constraints, not caveats. If any of them are blocking for your use case, ANNex is probably not the right choice yet.