pgvector avancé : embeddings et recherche sémantique — tutoriel 2026
Cet article fait partie du cluster Bases de données spécialisées. Pour la vue d’ensemble — pourquoi choisir une base vectorielle, quand préférer DuckDB ou ClickHouse — lisez d’abord le pilier.
Introduction
Imaginez une PME à Dakar qui gère des centaines de documents OHADA, des contrats fournisseurs et des fiches produits rédigées en français et en wolof. Un employé tape dans le moteur de recherche interne : « comment calculer la taxe sur la valeur ajoutée pour un importateur de Chine ? ». La recherche par mots-clés classique renvoie zéro résultat — le document pertinent parle de « TVA à l’importation » et de « dédouanement ». La recherche sémantique, elle, comprend l’intention et retrouve le bon document. C’est exactement ce que pgvector rend possible, directement dans PostgreSQL, sans abonnement à Pinecone, sans infrastructure Qdrant séparée à maintenir.
pgvector est une extension open-source pour PostgreSQL qui ajoute un type de données natif vector, des opérateurs de distance (cosinus, L2, produit scalaire) et des index spécialisés (HNSW, IVFFlat). Publiée sur github.com/pgvector/pgvector, elle permet de stocker des embeddings — ces représentations numériques denses du sens d’un texte — directement à côté de vos données relationnelles. Pour une PME ouest-africaine qui héberge déjà PostgreSQL sur un VPS Ubuntu, pgvector transforme cette base existante en moteur de recherche sémantique sans coût supplémentaire de licence ni dépendance à un service cloud américain facturable en dollars.
Dans ce tutoriel, vous apprendrez à installer pgvector, à générer des embeddings avec des modèles locaux via Ollama, à construire un index HNSW performant, et à monter un pipeline RAG (Retrieval-Augmented Generation) complet en Python. Comptez environ trente minutes pour les étapes 1 à 6, et une heure supplémentaire pour l’intégration RAG de l’étape 7.
Prérequis
- PostgreSQL 14 ou supérieur — pgvector requiert au minimum PG 13, mais la version 14+ est recommandée pour les performances des index HNSW introduits dans pgvector 0.5.0. Sur Ubuntu 22.04 :
sudo apt install postgresql-16. - Un modèle d’embeddings — soit en local via Ollama (recommandé pour l’Afrique de l’Ouest : zéro dépendance réseau après le téléchargement initial), soit via l’API Mistral Embed si vous avez une connexion stable.
- Python 3.10+ avec
pip install psycopg2-binary pgvector langchain langchain-community ollama. - Niveau attendu : intermédiaire — vous savez écrire du SQL et un script Python basique.
- Temps estimé : ~30 minutes pour les étapes 1 à 6, ~60 minutes supplémentaires pour l’étape 7 RAG complète.
Étape 1 — Comprendre les embeddings vectoriels et la similarité cosinus
Avant d’écrire la moindre ligne de SQL, il est essentiel de comprendre ce que vous allez stocker dans la colonne vector. Un embedding est la transformation d’un texte — une phrase, un paragraphe, un document entier — en un vecteur de nombres flottants. Par exemple, le modèle bge-m3 de BAAI produit des vecteurs de 1024 dimensions : un tableau de 1024 décimaux entre -1 et 1. Ce n’est pas une compression aléatoire : le modèle a appris, durant son entraînement sur des milliards de phrases, à placer les textes de sens proche dans des zones proches de cet espace à 1024 dimensions.
La mesure de proximité la plus utilisée pour les embeddings de texte est la similarité cosinus. Elle mesure l’angle entre deux vecteurs plutôt que leur distance euclidienne absolue. Deux phrases exprimant la même idée dans des formulations différentes auront un cosinus proche de 1 (angle proche de 0°). Deux phrases sans rapport auront un cosinus proche de 0. Dans pgvector, l’opérateur <=> calcule la distance cosinus (1 − similarité), donc une valeur proche de 0 signifie des textes très proches. L’opérateur <-> calcule la distance L2 (euclidienne), et <#> le produit scalaire négatif.
Pour un cas d’usage RAG sur des documents OHADA en français, le cosinus est le bon choix : il est insensible à la longueur du texte, ce qui évite de favoriser les longs documents simplement parce qu’ils contiennent plus de mots. Retenez cette règle de base : utilisez <=> (cosinus) pour la recherche documentaire et le RAG, <-> (L2) uniquement quand vous travaillez sur des vecteurs d’images ou de données numériques normalisées.
Étape 2 — Installer l’extension pgvector
L’installation de pgvector se fait en deux temps : d’abord compiler et installer la bibliothèque système, puis activer l’extension dans la base PostgreSQL cible. Sur un VPS Ubuntu 22.04 ou 24.04 avec PostgreSQL 16 installé via les dépôts officiels PGDG, la méthode la plus propre est d’utiliser le paquet apt précompilé fourni par l’équipe PGDG.
# Ajouter la clé et le dépôt PGDG si ce n'est pas déjà fait
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \
| sudo gpg --dearmor -o /usr/share/keyrings/postgresql.gpg
echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] \
https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
| sudo tee /etc/apt/sources.list.d/pgdg.list
sudo apt update
# Installer pgvector pour PostgreSQL 16
sudo apt install -y postgresql-16-pgvector
Cette commande installe les fichiers .so et .control nécessaires dans le répertoire d’extensions de PostgreSQL. L’extension n’est pas encore active dans votre base de données : elle est simplement disponible sur le système. Pour l’activer, connectez-vous à votre base avec psql en tant que superutilisateur et exécutez la commande SQL d’activation. Si vous utilisez Docker, l’image officielle pgvector/pgvector:pg16 sur Docker Hub embarque déjà l’extension — aucune installation manuelle nécessaire.
-- Connexion en tant que superutilisateur
-- sudo -u postgres psql -d ma_base
CREATE EXTENSION IF NOT EXISTS vector;
-- Vérifier que l'installation est correcte
SELECT extname, extversion FROM pg_extension WHERE extname = 'vector';
La requête de vérification doit renvoyer une ligne avec vector et le numéro de version (0.7.x en avril 2026). Si vous obtenez ERROR: could not open extension control file, c’est que le paquet système n’est pas installé pour la bonne version de PostgreSQL — vérifiez avec pg_lsclusters quelle version tourne réellement sur votre serveur.
Étape 3 — Créer une table avec une colonne vector(1024)
Maintenant que l’extension est activée, vous pouvez utiliser le type vector(N) dans vos définitions de table. La valeur N correspond à la dimension des embeddings produits par votre modèle : 1024 pour bge-m3, 1536 pour text-embedding-ada-002 d’OpenAI, 768 pour de nombreux modèles BERT. Il est impératif que toutes les lignes d’une même table utilisent la même dimension — pgvector refusera l’insertion si les dimensions ne correspondent pas.
-- Table pour stocker des documents et leurs embeddings
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
titre TEXT NOT NULL,
corps TEXT NOT NULL,
source TEXT, -- URL ou chemin du fichier d'origine
categorie TEXT, -- ex: 'ohada', 'syscohada', 'contrat'
embedding vector(1024), -- dimension bge-m3
cree_le TIMESTAMPTZ DEFAULT NOW()
);
-- Index partiel utile si tous les documents n'ont pas encore leur embedding
CREATE INDEX idx_documents_embedding_not_null
ON documents (id)
WHERE embedding IS NOT NULL;
Ce schéma est intentionnellement simple pour rester lisible. Dans un projet réel, vous ajouterez des colonnes métadonnées supplémentaires : numéro de page pour les PDF, date de publication du texte légal, langue détectée, identifiant de l’utilisateur propriétaire du document. Ces métadonnées serviront à filtrer les résultats de recherche — par exemple, ne chercher que dans les documents de la catégorie syscohada — avant d’appliquer la distance vectorielle. C’est ce qu’on appelle le pre-filtering, et pgvector le supporte nativement via des conditions WHERE classiques combinées à la requête de similarité.
Étape 4 — Générer des embeddings avec Ollama bge-m3 ou Mistral Embed
La qualité de votre recherche sémantique dépend directement du modèle d’embeddings choisi. Pour un contexte ouest-africain avec des documents en français, en anglais et partiellement en wolof ou en dioula, le modèle bge-m3 de BAAI (Beijing Academy of Artificial Intelligence) est la référence open-source en 2026 : il supporte 100+ langues, produit des vecteurs de 1024 dimensions, et fonctionne entièrement en local via Ollama. C’est un avantage décisif sur un VPS avec une connexion Internet variable.
# Installer Ollama sur Ubuntu
curl -fsSL https://ollama.com/install.sh | sh
# Télécharger le modèle bge-m3 (environ 1.2 Go)
ollama pull bge-m3
# Vérifier qu'Ollama répond sur le port 11434
curl -s http://localhost:11434/api/tags | python3 -m json.tool | grep -A2 'bge-m3'
Une fois Ollama lancé avec bge-m3 disponible, voici le script Python qui lit des documents depuis la base, génère leurs embeddings et les persiste. La fonction embed_texts fait appel à l’API REST locale d’Ollama sur le port 11434 — aucune requête ne sort de votre serveur.
import psycopg2
import requests
import json
# Connexion PostgreSQL
conn = psycopg2.connect(
host="localhost", dbname="ma_base",
user="postgres", password="mon_mdp"
)
cur = conn.cursor()
def embed_text(texte: str) -> list[float]:
"""Appel Ollama local pour obtenir l'embedding d'un texte."""
payload = {"model": "bge-m3", "prompt": texte}
resp = requests.post(
"http://localhost:11434/api/embeddings",
json=payload, timeout=30
)
resp.raise_for_status()
return resp.json()["embedding"]
# Récupérer les documents sans embedding
cur.execute("""
SELECT id, corps FROM documents
WHERE embedding IS NULL
LIMIT 100
""")
documents = cur.fetchall()
for doc_id, corps in documents:
vecteur = embed_text(corps)
# Psycopg2 accepte une liste Python pour le type vector
cur.execute(
"UPDATE documents SET embedding = %s WHERE id = %s",
(vecteur, doc_id)
)
print(f"Embedding généré pour document #{doc_id}")
conn.commit()
cur.close()
conn.close()
Si vous préférez l’API cloud Mistral Embed — utile pour un démarrage rapide sans GPU ni RAM importante — remplacez la fonction embed_text par un appel à https://api.mistral.ai/v1/embeddings avec le modèle mistral-embed, qui produit des vecteurs de 1024 dimensions compatibles avec le schéma créé à l’étape 3. Attention : Mistral Embed est payant et facturé par token ; pour des corpus de milliers de documents, le coût s’accumule. La stratégie recommandée est de générer les embeddings une seule fois, de les persister en base, et de ne rappeler l’API qu’à l’insertion de nouveaux documents.
Étape 5 — Index HNSW pour des requêtes rapides
Sans index, une recherche de similarité dans pgvector effectue un parcours séquentiel de toute la table — ce qu’on appelle un exact nearest neighbor search. Sur 1 000 documents, c’est imperceptible. Sur 100 000 documents, chaque requête prend plusieurs secondes. L’index HNSW (Hierarchical Navigable Small World), introduit dans pgvector 0.5.0 et largement amélioré dans les versions 0.6.x et 0.7.x, résout ce problème : il construit un graphe hiérarchique qui permet de trouver les N plus proches voisins approximatifs en quelques millisecondes, même sur des millions de vecteurs.
-- Créer l'index HNSW pour la distance cosinus
-- m = nombre de connexions par nœud (défaut 16, augmenter pour plus de rappel)
-- ef_construction = taille de la liste candidate à la construction (défaut 64)
CREATE INDEX idx_documents_hnsw
ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- Vérifier la taille de l'index
SELECT pg_size_pretty(pg_relation_size('idx_documents_hnsw'));
La construction de l’index peut prendre de quelques secondes à plusieurs minutes selon la taille de la table et les ressources du serveur. Sur un VPS 4 vCPU / 8 Go RAM, comptez environ 2 minutes pour indexer 100 000 vecteurs de dimension 1024. Notez que la construction bloque les écritures sur la table en mode AccessShareLock — sur une base en production, préférez CREATE INDEX CONCURRENTLY pour éviter tout downtime. Le paramètre ef_search contrôle le compromis rappel/vitesse à la requête : SET hnsw.ef_search = 100 améliore la précision au détriment de la latence, tandis que la valeur par défaut de 40 convient pour la plupart des cas.
Étape 6 — Requête de similarité avec l’opérateur <-> et ORDER BY
Avec l’index HNSW en place, vous pouvez exécuter des recherches sémantiques en SQL pur. La syntaxe est simple : on compare l’embedding de la requête (généré à la volée) à tous les embeddings stockés, on trie par distance croissante (les plus proches en premier), et on limite aux N premiers résultats. L’opérateur <=> calcule la distance cosinus.
-- Exemple : recherche des 5 documents les plus proches d'un vecteur de requête
-- On suppose que l'embedding de la requête est passé en paramètre depuis Python
SELECT
id,
titre,
LEFT(corps, 200) AS extrait,
1 - (embedding <=> $1::vector) AS similarite_cosinus
FROM documents
WHERE embedding IS NOT NULL
AND categorie = 'ohada' -- filtre métadonnée avant similarité
ORDER BY embedding <=> $1::vector -- tri par distance cosinus croissante
LIMIT 5;
Le paramètre $1 est l’embedding de la question posée par l’utilisateur, converti en vecteur PostgreSQL. En Python avec psycopg2, vous passez simplement la liste de flottants retournée par Ollama et pgvector s’occupe de la conversion. Le champ similarite_cosinus calculé (1 - distance) varie entre 0 et 1 : une valeur supérieure à 0.75 indique généralement un bon résultat pour des documents en français. En dessous de 0.5, le document n’est probablement pas pertinent et ne devrait pas être transmis au LLM dans un pipeline RAG.
Une subtilité importante : PostgreSQL ne peut utiliser l’index HNSW que si la requête respecte exactement l’opérateur sur lequel l’index a été créé. Si vous avez indexé avec vector_cosine_ops (opérateur <=>) mais que vous requêtez avec <-> (L2), l’index ne sera pas utilisé et vous aurez un seq scan. Vérifiez toujours avec EXPLAIN ANALYZE que le plan de requête mentionne bien Index Scan using idx_documents_hnsw.
Étape 7 — Pipeline RAG complet : retrieval + LLM via LangChain
Le RAG (Retrieval-Augmented Generation) est le cas d’usage le plus puissant de pgvector pour une PME. Le principe : au lieu d’envoyer une question directement à un LLM qui hallucine des réponses approximatives, on commence par récupérer les documents réellement pertinents depuis la base de données, puis on les injecte dans le contexte du LLM pour qu’il formule une réponse ancrée dans vos données réelles. pgvector joue le rôle du moteur de retrieval ; un LLM local via Ollama (par exemple mistral:7b ou qwen2.5:7b) joue le rôle du générateur.
from langchain_community.vectorstores.pgvector import PGVector
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.llms import Ollama
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# --- Configuration ---
CONNECTION_STRING = "postgresql+psycopg2://postgres:mon_mdp@localhost/ma_base"
COLLECTION_NAME = "documents" # correspond à la table créée manuellement
# --- Embeddings via Ollama bge-m3 ---
embeddings = OllamaEmbeddings(
base_url="http://localhost:11434",
model="bge-m3"
)
# --- Vector store pgvector ---
# use_jsonb=True stocke les métadonnées dans une colonne JSONB native
vectorstore = PGVector(
connection_string=CONNECTION_STRING,
embedding_function=embeddings,
collection_name=COLLECTION_NAME,
use_jsonb=True
)
# --- Retriever : 4 documents les plus proches, score threshold 0.70 ---
retriever = vectorstore.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={"k": 4, "score_threshold": 0.70}
)
# --- LLM local ---
llm = Ollama(model="mistral:7b", base_url="http://localhost:11434")
# --- Prompt système adapté au contexte africain ---
PROMPT_TEMPLATE = """Tu es un assistant juridique et comptable expert en droit OHADA.
Réponds en français uniquement à partir des extraits de documents fournis.
Si la réponse n'est pas dans les documents, dis-le explicitement.
Documents pertinents :
{context}
Question : {question}
Réponse :"""
prompt = PromptTemplate(
template=PROMPT_TEMPLATE,
input_variables=["context", "question"]
)
# --- Chaîne RAG ---
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # "stuff" = tous les docs dans un seul contexte
retriever=retriever,
return_source_documents=True,
chain_type_kwargs={"prompt": prompt}
)
# --- Exemple d'invocation ---
resultat = qa_chain.invoke({
"query": "Comment enregistrer une immobilisation en SYSCOHADA révisé ?"
})
print("Réponse :", resultat["result"])
print("\nSources utilisées :")
for doc in resultat["source_documents"]:
print(" -", doc.metadata.get("titre", "sans titre"))
Ce pipeline complet fonctionne entièrement en local : bge-m3 génère l’embedding de la question sur votre serveur, pgvector retrouve les 4 documents OHADA les plus pertinents, Mistral 7b rédige la réponse en français en s’appuyant sur ces extraits. La réponse cite implicitement les documents retrouvés, et la liste des sources est accessible dans resultat["source_documents"] pour afficher les références à l’utilisateur. Pour intégrer ce pipeline dans ERPNext ou dans une interface web, il suffit d’exposer la fonction qa_chain.invoke comme endpoint FastAPI ou comme action Python dans ERPNext.
Erreurs fréquentes
| Erreur rencontrée | Cause probable | Solution |
|---|---|---|
ERROR: type "vector" does not exist |
Extension pas activée dans la base cible ou paquet système manquant | Exécuter CREATE EXTENSION vector; en superutilisateur dans la bonne base |
ERROR: expected 1024 dimensions, not 768 |
Modèle d’embeddings changé entre les insertions | Utiliser toujours le même modèle ; si changement de modèle, régénérer tous les embeddings |
| Seq scan au lieu de Index Scan | Mauvais opérateur dans la requête vs l’index, ou enable_seqscan = on |
Vérifier EXPLAIN ANALYZE ; s’assurer que l’opérateur de la requête correspond à celui de l’index (vector_cosine_ops ↔ <=>) |
OllamaEmbeddings timeout après quelques secondes |
bge-m3 trop lent sur un VPS sans GPU (temps de warm-up) | Augmenter le timeout dans la configuration LangChain (timeout=120) ; garder le modèle chargé avec ollama run bge-m3 en arrière-plan |
Index HNSW non utilisé après CREATE INDEX CONCURRENTLY |
La transaction de création est encore en cours ou a échoué silencieusement | Vérifier SELECT * FROM pg_indexes WHERE tablename = 'documents'; et relancer si absent |
| Résultats peu pertinents malgré un bon index | Embeddings de mauvaise qualité : textes trop courts, langue non supportée par le modèle | Enrichir les textes avant l’embedding (ajouter titre + résumé) ; tester bge-m3 vs multilingual-e5-large sur un échantillon annoté manuellement |
ERROR: cannot use an expression index on this column avec LangChain PGVector |
LangChain crée sa propre table avec le type ctype non compatible |
Utiliser l’option use_jsonb=True et s’assurer que la version de langchain-community est >= 0.2.0 |
Adaptation au contexte ouest-africain
RAG sur documents OHADA et SYSCOHADA
Le droit des affaires en Afrique de l’Ouest est largement régi par les Actes Uniformes de l’OHADA (Organisation pour l’Harmonisation en Afrique du Droit des Affaires) et par le plan comptable SYSCOHADA révisé en vigueur depuis 2018. Ces documents sont publics, disponibles en PDF sur le site officiel de l’OHADA à Yaoundé. Un pipeline RAG basé sur pgvector permet à un cabinet comptable à Abidjan ou à Bamako de construire un assistant interne capable de répondre à des questions précises : « Quelle est la durée d’amortissement des logiciels en SYSCOHADA ? », « Quels actes nécessitent un notaire selon l’Acte Uniforme sur les sociétés commerciales ? »
La procédure concrète est la suivante : télécharger les PDF des Actes Uniformes, les découper en chunks de 500 à 800 tokens avec LangChain RecursiveCharacterTextSplitter, générer les embeddings avec bge-m3, et stocker les chunks dans la table documents avec les métadonnées source = 'OHADA-AUS-2014' et categorie = 'droit-societes'. L’ensemble du corpus OHADA tient dans moins de 50 Mo de texte — parfaitement gérable sur un VPS 2 Go de RAM.
Recherche sémantique multilingue français/wolof avec bge-m3
Le wolof est parlé par plus de 80 % de la population sénégalaise, et de nombreux documents internes des PME — notes internes, messages clients, descriptions de produits — contiennent du wolof romanisé mélangé au français. bge-m3 supporte le wolof comme l’une des 100+ langues de son entraînement (via son corpus multilingue MIRACL et mC4), ce qui en fait un choix supérieur à des modèles monolingues ou franco-anglais. En pratique, cela signifie qu’une requête en français (« contrat de location ») peut retrouver un document partiellement rédigé en wolof (« bëgg naa xam li def ci contrat bi ») si les sens sont proches selon le modèle. Les résultats ne seront pas parfaits — le wolof romanisé reste sous-représenté dans les corpus d’entraînement — mais ils seront significativement meilleurs qu’avec un modèle francophone pur.
Intégration ERPNext pour l’assistance clients
ERPNext, très répandu dans les PME ouest-africaines comme ERP open-source hébergeable sur un VPS, peut être enrichi d’une fonctionnalité d’assistance sémantique via pgvector. Le principe : créer un Doctype Base de connaissance dans ERPNext qui stocke des articles de support (procédures internes, FAQ produits, conditions de garantie), exporter ces documents vers la table documents PostgreSQL via un script Python planifié (cron quotidien), puis exposer un endpoint FastAPI qui reçoit la question d’un technicien support et renvoie les 3 documents les plus pertinents avec un résumé généré par le LLM local. Le technicien voit la réponse directement dans l’interface ERPNext via un widget HTML personnalisé dans le formulaire de ticket client. Ce type d’intégration, entièrement self-hostée, représente une économie significative par rapport à des solutions SaaS comme Intercom Fin ou Zendesk AI — avec une maîtrise totale des données clients.
Tutoriels frères
- DuckDB : analyser des CSV et Parquet sans serveur — tutoriel pas-à-pas — le compagnon idéal de pgvector pour l’analyse exploratoire de vos données avant vectorisation
- TimescaleDB : séries temporelles dans PostgreSQL — tutoriel 2026 — combiner TimescaleDB (métriques IoT) et pgvector (alertes sémantiques) dans la même instance Postgres
Pour aller plus loin
- Retour au pilier : Bases de données modernes 2026 : DuckDB, ClickHouse, TimescaleDB, pgvector
- Source primaire : github.com/pgvector/pgvector — documentation officielle, changelog, benchmarks, exemples SQL
- Comparatif Weaviate vs pgvector : weaviate.io/blog/pgvector-vs-weaviate — quand basculer vers une base vectorielle dédiée
- LangChain PGVector : python.langchain.com — PGVector integration
- Prochain tutoriel recommandé dans le cluster : ClickHouse pour l’analyse de logs applicatifs — stocker les requêtes de recherche sémantique dans ClickHouse pour analyser les patterns d’usage
FAQ
Q : pgvector peut-il remplacer complètement Pinecone ou Qdrant ?
Pour la plupart des PME, oui. pgvector couvre les cas d’usage les plus courants : stockage d’embeddings, recherche par similarité cosinus, index HNSW, filtrage par métadonnées. Là où Qdrant ou Weaviate restent supérieurs, c’est sur les corpus de plusieurs dizaines de millions de vecteurs (pgvector commence à montrer ses limites au-delà de 5-10 millions de lignes sur un seul nœud) et sur les fonctionnalités avancées comme les requêtes hybrides (sparse + dense) natives ou la réplication vectorielle distribuée. Pour une PME avec moins de 500 000 documents, pgvector dans PostgreSQL est le choix le plus pragmatique.
Q : Quelle est la différence entre HNSW et IVFFlat dans pgvector ?
IVFFlat (Inverted File with Flat quantization) est l’ancien index de pgvector, introduit avant HNSW. Il divise l’espace vectoriel en clusters (listes) et ne cherche que dans les clusters les plus proches. Il est plus rapide à construire et consomme moins de mémoire, mais son rappel est inférieur à HNSW et il nécessite un paramètre lists à calibrer selon la taille du corpus. HNSW (Hierarchical Navigable Small World) construit un graphe multi-couche et offre un meilleur compromis rappel/vitesse dans presque tous les cas. La recommandation officielle de pgvector depuis la version 0.5.0 est d’utiliser HNSW par défaut, sauf contrainte mémoire sévère.
Q : Comment choisir la taille des chunks pour le RAG ?
La taille de chunk optimale dépend de votre modèle d’embeddings et de la nature des documents. Pour bge-m3 avec une fenêtre contextuelle de 8192 tokens, des chunks de 512 à 800 tokens avec un chevauchement (overlap) de 100 tokens donnent généralement de bons résultats sur des textes légaux ou techniques en français. Des chunks trop courts (< 200 tokens) perdent le contexte et produisent des embeddings peu informatifs. Des chunks trop longs (> 1500 tokens) diluent le sens et réduisent la précision de la similarité. Testez empiriquement en annotant une centaine de questions-réponses et en mesurant le taux de rappel à k=3.
Q : pgvector fonctionne-t-il avec Supabase ou des instances cloud PostgreSQL ?
Oui. Supabase Active pgvector par défaut sur toutes les instances depuis 2023 — il suffit d’exécuter CREATE EXTENSION vector;. Neon, Aiven, et ElephantSQL supportent également pgvector. Amazon RDS for PostgreSQL et Aurora PostgreSQL supportent pgvector depuis 2023 (version 15.2+). Si vous êtes sur un hébergeur mutualisé qui ne propose pas pgvector, la solution la plus simple est de migrer vers un VPS Ubuntu (DigitalOcean, Hetzner, OVH) où vous contrôlez l’installation des extensions.
Q : Comment mettre à jour les embeddings quand un document est modifié ?
La bonne pratique est de créer un trigger PostgreSQL sur la table documents qui positionne embedding = NULL chaque fois que la colonne corps est mise à jour. Un job cron Python (toutes les heures ou toutes les nuits selon la criticité) récupère les lignes avec embedding IS NULL, régénère les embeddings via Ollama, et les persiste. Cette approche asynchrone évite de ralentir les écritures applicatives avec le temps de génération d’embedding (typiquement 200-500 ms par document avec bge-m3 sur CPU).
Q : Est-ce qu’on peut faire de la recherche hybride (mots-clés + sémantique) avec pgvector ?
Oui, en combinant pgvector avec l’indexation full-text native de PostgreSQL (tsvector / tsquery). La technique s’appelle Reciprocal Rank Fusion (RRF) : on exécute deux requêtes en parallèle (une BM25 via to_tsquery, une vectorielle via <=>), on récupère les classements respectifs, et on fusionne avec la formule RRF pour obtenir un classement hybride. C’est plus complexe à implémenter que dans Weaviate qui le fait nativement, mais c’est parfaitement faisable en SQL pur avec des CTE. Un exemple complet est disponible dans les issues de pgvector sur GitHub.