FAISS flat IP index with cosine similarity
FAISS is a library from Meta that does fast similarity search over dense vectors. For a per-video index with a few hundred chunks, the simplest flavor (IndexFlatIP) is perfect. It does an exact inner product across every vector.
FAISS does not have a built-in cosine similarity index, but there is a standard trick: if you L2-normalize every vector before indexing, then inner product is exactly cosine similarity. One division, one flag, done.
import faiss
def build_index(chunks: list[dict],
client=None) -> tuple[faiss.Index, list[dict]]:
"""Build a FAISS inner-product index (cosine after normalization)."""
texts = [c["text"] for c in chunks]
embeddings = get_embeddings(texts)
# L2 normalize so inner product == cosine similarity
norms = np.linalg.norm(embeddings, axis=1, keepdims=True)
embeddings = embeddings / (norms + 1e-10)
dim = embeddings.shape[1]
index = faiss.IndexFlatIP(dim)
index.add(embeddings)
return index, chunksNormalize every vector to unit length, then add to an inner-product index. The tiny epsilon (1e-10) prevents division by zero on pathological vectors.
def search_index(query: str, index: faiss.Index,
chunks: list[dict], client=None,
top_k: int = 5) -> list[dict]:
"""Search the FAISS index for chunks most relevant to query."""
q_emb = get_embeddings([query])
q_emb = q_emb / (np.linalg.norm(q_emb) + 1e-10)
scores, indices = index.search(q_emb, top_k)
results = []
for score, idx in zip(scores[0], indices[0]):
if idx < len(chunks):
chunk = chunks[idx].copy()
chunk["score"] = float(score)
results.append(chunk)
return resultsThe query goes through the same embedding model and the same normalization. FAISS returns the top_k matches with their similarity scores, and we attach those scores to the returned chunks.
Ordering exercise: Order the indexing steps
Loading practice…
Quiz: Quiz
Loading practice…