Local semantic search depends on two compatible pieces: dense vectors that capture text meaning and an index that can rank those vectors for each query. Sentence Transformers produces the embeddings, while FAISS provides an in-process search index that can be saved and loaded without a separate vector database service.
The corpus uses sentence-transformers/all-MiniLM-L6-v2 with an exact IndexFlatIP index. Normalizing document and query embeddings makes inner-product ranking equivalent to cosine-similarity ranking; IndexFlatIP still scans every stored vector, so larger collections may need an approximate FAISS index instead.
Flat indexes return sequential row numbers rather than application records. A JSON sidecar kept in the same order as the indexed vectors maps each result back to its document ID and text, and a row-count check stops the search when the saved index and metadata no longer agree.
Steps to build a FAISS index with Sentence Transformers:
- Install Sentence Transformers and the CPU build of FAISS in the active Python environment.
$ python -m pip install --upgrade sentence-transformers faiss-cpu
- Create build_faiss_index.py with the imports and ordered corpus records.
- build_faiss_index.py
import json from pathlib import Path import faiss import numpy as np from sentence_transformers import SentenceTransformer corpus = [ { "id": "doc-001", "text": "Sentence Transformers converts text into dense embeddings.", }, { "id": "doc-002", "text": "FAISS stores vectors and searches nearest neighbors locally.", }, { "id": "doc-003", "text": "Cross-encoders rerank a small set of retrieved passages.", }, { "id": "doc-004", "text": "Qdrant stores vectors behind a database service API.", }, ]
- Append the normalized document-embedding and exact-index section to build_faiss_index.py.
model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2") texts = [item["text"] for item in corpus] document_embeddings = model.encode_document( texts, normalize_embeddings=True, convert_to_numpy=True, ) document_embeddings = np.asarray(document_embeddings, dtype="float32") dimension = document_embeddings.shape[1] index = faiss.IndexFlatIP(dimension) index.add(document_embeddings)
encode_document() preserves the document route or prompt defined by retrieval models that distinguish corpus text from queries.
- Append the index and metadata persistence section to build_faiss_index.py.
faiss.write_index(index, "support-faq.faiss") Path("support-faq.json").write_text( json.dumps(corpus, indent=2), encoding="utf-8", )
- Append the persisted-index search section to build_faiss_index.py.
loaded_index = faiss.read_index("support-faq.faiss") metadata = json.loads(Path("support-faq.json").read_text(encoding="utf-8")) if loaded_index.ntotal != len(metadata): raise RuntimeError("FAISS rows and metadata rows do not match") query_embedding = model.encode_query( ["Which library searches vectors nearest neighbors locally?"], normalize_embeddings=True, convert_to_numpy=True, ) query_embedding = np.asarray(query_embedding, dtype="float32") scores, row_ids = loaded_index.search(query_embedding, k=2) print(f"embedding dimension: {dimension}") print(f"indexed vectors: {loaded_index.ntotal}") print("top matches:") for rank, (score, row_id) in enumerate(zip(scores[0], row_ids[0]), start=1): record = metadata[int(row_id)] print(f"{rank}. {record['id']} score={score:.4f} text={record['text']}")
encode_query() applies the matching query route or prompt before FAISS searches the reloaded index.
- Run build_faiss_index.py to build the searchable index.
$ python build_faiss_index.py embedding dimension: 384 indexed vectors: 4 top matches: 1. doc-002 score=0.6675 text=FAISS stores vectors and searches nearest neighbors locally. 2. doc-004 score=0.3530 text=Qdrant stores vectors behind a database service API.
The first run may download the embedding model before the search output appears.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.