Aller au contenu principal

Vector Store avec FAISS

FAISS : recherche vectorielle haute performance

FAISS (Facebook AI Similarity Search) est une bibliothèque open source de Meta, conçue pour la recherche de similarité dans des ensembles de vecteurs de grande taille. Elle est rapide, efficace en mémoire, et fonctionne aussi bien avec quelques centaines qu’avec des millions de vecteurs.

Installation et premiers pas

# pip install faiss-cpu
# ou faiss-gpu pour l'accélération GPU

import faiss
import numpy as np

Créer un index FAISS

L’index le plus simple est IndexFlatL2, qui effectue une recherche exhaustive par distance L2 (euclidienne) :

# Supposons que nos embeddings sont une matrice NumPy
# embeddings.shape = (n_chunks, 1024)

# Créer l'index
dimension = embeddings.shape[1]  # 1024 pour mistral-embed
index = faiss.IndexFlatL2(dimension)

# Ajouter les vecteurs
index.add(embeddings.astype(np.float32))  # FAISS requiert float32

print(f"Index créé: {index.ntotal} vecteurs de dimension {dimension}")

Types d’index FAISS

FAISS propose plusieurs types d’index selon vos besoins :

dimension = 1024

# 1. IndexFlatL2 : recherche exacte, distance L2
# Le plus précis, mais le plus lent pour de gros volumes
index_flat_l2 = faiss.IndexFlatL2(dimension)

# 2. IndexFlatIP : recherche exacte, produit scalaire (inner product)
# Utilisez avec des vecteurs normalisés pour la similarité cosinus
index_flat_ip = faiss.IndexFlatIP(dimension)

# 3. IndexIVFFlat : recherche approximative avec partitionnement
# Plus rapide pour de gros volumes, légère perte de précision
nlist = 100  # Nombre de partitions (clusters)
quantizer = faiss.IndexFlatL2(dimension)
index_ivf = faiss.IndexIVFFlat(quantizer, dimension, nlist)
# Nécessite un entraînement avant utilisation :
# index_ivf.train(embeddings)
# index_ivf.add(embeddings)

# 4. IndexHNSWFlat : graphe de voisins (très rapide en recherche)
index_hnsw = faiss.IndexHNSWFlat(dimension, 32)  # 32 = nombre de voisins

Recherche dans l’index

from mistralai import Mistral

client = Mistral(api_key=api_key)

def search_index(query, index, chunks, k=5):
    """Rechercher les chunks les plus proches d'une requête."""
    # 1. Encoder la requête
    response = client.embeddings.create(
        model="mistral-embed",
        inputs=[query],
    )
    query_vector = np.array([response.data[0].embedding], dtype=np.float32)

    # 2. Rechercher dans FAISS
    distances, indices = index.search(query_vector, k)

    # 3. Formater les résultats
    results = []
    for i, (dist, idx) in enumerate(zip(distances[0], indices[0])):
        if idx >= 0:  # FAISS retourne -1 si pas assez de résultats
            results.append({
                "rank": i + 1,
                "distance": float(dist),
                "text": chunks[idx]["text"],
                "source": chunks[idx].get("source", ""),
            })

    return results

# Exemple de recherche
results = search_index("Comment fonctionne le RAG ?", index, chunks)
for r in results:
    print(f"  #{r['rank']} (dist: {r['distance']:.4f}) [{r['source']}]")
    print(f"    {r['text'][:100]}...")

Similarité cosinus avec FAISS

Pour utiliser la similarité cosinus au lieu de la distance L2, normalisez vos vecteurs :

def create_cosine_index(embeddings):
    """Créer un index FAISS avec similarité cosinus."""
    # Normaliser les vecteurs (norme L2 = 1)
    norms = np.linalg.norm(embeddings, axis=1, keepdims=True)
    normalized = (embeddings / norms).astype(np.float32)

    # Utiliser IndexFlatIP (produit scalaire = cosinus pour vecteurs normalisés)
    dimension = normalized.shape[1]
    index = faiss.IndexFlatIP(dimension)
    index.add(normalized)

    return index

cosine_index = create_cosine_index(embeddings)

Sauvegarder et charger un index

# Sauvegarder
faiss.write_index(index, "rag_faiss.index")
print("Index FAISS sauvegardé")

# Charger
index_loaded = faiss.read_index("rag_faiss.index")
print(f"Index chargé: {index_loaded.ntotal} vecteurs")

Index pour gros volumes

Pour des millions de vecteurs, utilisez un index IVF (Inverted File) :

def create_ivf_index(embeddings, nlist=100, nprobe=10):
    """Créer un index IVF pour de gros volumes."""
    dimension = embeddings.shape[1]
    data = embeddings.astype(np.float32)

    # Créer le quantizer et l'index IVF
    quantizer = faiss.IndexFlatL2(dimension)
    index = faiss.IndexIVFFlat(quantizer, dimension, nlist)

    # Entraîner l'index (nécessaire pour IVF)
    print("Entraînement de l'index...")
    index.train(data)

    # Ajouter les vecteurs
    index.add(data)

    # Nombre de partitions à explorer lors de la recherche
    index.nprobe = nprobe  # Plus grand = plus précis, plus lent

    print(f"Index IVF créé: {index.ntotal} vecteurs, {nlist} partitions")
    return index

# Pour 100K+ vecteurs
# large_index = create_ivf_index(embeddings, nlist=256, nprobe=20)

Points clés à retenir

  • FAISS est la référence pour la recherche vectorielle rapide
  • IndexFlatL2 pour les petits corpus (moins de 100K vecteurs), recherche exacte
  • IndexIVFFlat pour les gros corpus, recherche approximative mais rapide
  • Normalisez les vecteurs et utilisez IndexFlatIP pour la similarité cosinus
  • Sauvegardez l’index sur disque avec faiss.write_index pour éviter de reconstruire