FAISS#

FAISS is a library for vector search in your process, with no server. FAISS stores vectors only, so FaissBackend keeps each chunk’s text and metadata beside the index and hands the permission filter to FAISS as a set of allowed ids: the search considers only chunks the subject may read, so pre-filtering is exact.

pip install "wardowl[faiss]"
from wardowl import MetadataACLAuthorizer, PermissionedRetriever
from wardowl.backends.faiss import FaissBackend

backend = FaissBackend(embed, dim=768)
backend.add(chunks)  # or backend.add(chunks, embeddings) with vectors you computed
retriever = PermissionedRetriever(backend, MetadataACLAuthorizer())

backend.save("data/faiss")  # the index and the chunks, in one directory
backend = FaissBackend.load("data/faiss", embed)

How filtering works#

A filter is matched against every chunk’s metadata with matches, the reference semantics, and the matching chunks become an IDSelectorBatch in the search parameters. Matching scans the metadata in Python, which is fast enough for the collections FAISS serves in one process (hundreds of thousands of chunks); for many millions, use a vector database with its own filtering.

Index types#

The default is exact search (IndexFlatIP over normalized vectors, so scores are cosine similarities). For approximate search, pass a trained index that supports add_with_ids, remove_ids and search parameters with an ID selector, such as an HNSW index inside an IndexIDMap2:

import faiss

index = faiss.IndexIDMap2(faiss.IndexHNSWFlat(768, 32, faiss.METRIC_INNER_PRODUCT))
backend = FaissBackend(embed, dim=768, index=index)

Changes#

add replaces chunks with the same id, and keeps only the last of an id repeated in one call, as if they were added in turn. delete removes chunks by id, and set_metadata changes a chunk’s permissions without embedding it again. A lock serializes changes and searches, so one backend can serve several threads.

The backend keeps its own copy of each chunk’s metadata and returns copies in search results, so changing a hit’s metadata, or a dict after passing it to add or set_metadata, changes no stored permission.