API reference.
The full Python surface for Feather v0.16.0 — DB lifecycle, records, context graph, and search. Includes add_batch(), int8 quantization, persisted HNSW graph, compact(), and BM25 hybrid search.
Database
DB.open(filename, dim)
Opens an existing .feather file, or creates a new one at the given path. dim is fixed for the lifetime of the file.
from feather_db import DB
db = DB.open("vectors.feather", dim=384)filenamestrPath to the .feather filedimintVector dimension (immutable after creation)
Records
db.add(id, vec, meta=None)
Insert a vector under a given entity ID. Reuse the same ID to update. Metadata carries importance, namespace, and arbitrary attributes.
from feather_db import Metadata
meta = Metadata()
meta.importance = 0.85
meta.set_attribute("type", "brief")
db.add(id=1001, vec=vector, meta=meta)idintEntity ID — reuse to update the vector in placevecnp.ndarrayFloat32 array of length dimmetaMetadata?Optional metadata: importance, namespace, attributes
db.link(from_id, to_id, rel_type, weight=1.0)
Creates a typed, weighted edge between two records. The graph is bidirectional in the index but the edge itself is directional.
db.link(from_id=1001, to_id=1002, rel_type="informed_by", weight=0.9)rel_typestrEdge label — "informed_by", "contradicts", etc.weightfloatEdge weight used during BFS traversal
Retrieval
db.hybrid_search(vec, query, k=10, rrf_k=60)
Fuses dense vector similarity with Okapi BM25 keyword matching via Reciprocal Rank Fusion — the same RRF formula used by Qdrant, Weaviate, and Elasticsearch. Inverted index is auto-built at load() from metadata content, fully backward-compatible with v3–v6 files.
# Hybrid — dense + BM25 fused via RRF
results = db.hybrid_search(
vec=query_vec,
query="adaptive memory decay",
k=10,
rrf_k=60,
)
# Or pure BM25 keyword search
results = db.keyword_search("adaptive memory decay", k=10)vecnp.ndarrayDense query vector (embedding)querystrKeyword query — tokenized, stop-word filtered, lowercasedkintTop-k results after fusionrrf_kintRRF damping constant. Default 60 — same as industry standardfilterSearchFilterOptional — namespace/entity/attribute filters apply to both dense and sparse legs
db.context_chain(query, k=5, hops=2)
Seeded semantic vector search, followed by N-hop BFS expansion across the typed graph. Returns the full context neighbourhood — not just nearest neighbours.
results = db.context_chain(query_vec, k=5, hops=2)
for r in results:
print(r.id, r.score, r.meta.get_attribute("type"))querynp.ndarraySeed query vectorkintTop-k for the initial semantic searchhopsintBFS expansion depth across typed edges
db.search(query, k=10, filter=None)
Pure approximate nearest neighbour search. Use this when you just want top-k vectors without graph traversal. Combine with a FilterBuilder for multi-tenant workloads.
from feather_db import FilterBuilder
f = (FilterBuilder()
.namespace("nike")
.attribute("channel", "instagram")
.importance_gte(0.5)
.build())
results = db.search(query, k=5, filter=f)Bulk Operations
db.add_batch(ids, vecs, metas=None)
Parallel bulk ingestion with the GIL released. 3.4× faster than sequential add() at 50K+ vectors. Use for corpus ingestion, cold-start memory seeding, and batch imports.
import numpy as np
from feather_db import DB, Metadata
db = DB.open("corpus.feather", dim=768)
ids = list(range(10_000))
vecs = np.random.randn(10_000, 768).astype(np.float32)
metas = [Metadata(importance=0.8) for _ in range(10_000)]
# Parallel — GIL released during HNSW construction
db.add_batch(ids, vecs, metas=metas)
db.save()idslist[int]Entity IDs — same length as vecsvecsnp.ndarray2-D float32 array, shape (N, dim)metaslist[Metadata]?Optional per-vector metadata, length N
Persistence
db.save()
Flushes the in-memory HNSW graph and all metadata to the .feather file. In v0.16.0+, save() embeds the prebuilt HNSW graph directly into the file (format v9), enabling 5–25× faster cold load (48ms vs 2.7s).
Graph persists only when: no pending forget()/purge(), no on-disk quantization. Call compact() then save()to restore fast-load after forget operations.
db.compact()
Removes forgotten/purged records from the index, reclaims memory, and restores eligibility for persisted-graph fast load on the next save(). Run after bulk forget() operations or as a scheduled maintenance step.