
Faire un RAG n'oblige pas toujours à déployer une base vectorielle dédiée. Si une base PostgreSQL est déjà en place, et c'est très souvent le cas, l'extension pgvector y ajoute un type vector et la recherche par similarité. Le RAG vit alors dans la base existante : pas de nouveau service à installer, sauvegarder, superviser. Ce guide montre comment activer pgvector, créer une table vectorielle avec un index HNSW, y indexer un corpus et le rechercher par distance cosinus, le tout en SQL. Et il combine recherche vectorielle et filtrage SQL classique dans une seule requête. Public visé : développeur dont l'équipe exploite déjà PostgreSQL et veut y ajouter un RAG.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Quand pgvector suffit, et quand une base dédiée s'impose.
- Activer l'extension pgvector dans PostgreSQL.
- Créer une table avec une colonne
vectoret un index HNSW. - Rechercher par distance cosinus avec l'opérateur
<=>. - Combiner recherche vectorielle et filtrage SQL.
Prérequis
Section intitulée « Prérequis »- Python 3.10+ et les bases du SQL.
- PostgreSQL avec pgvector, l'image Docker
pgvector/pgvector. - Une instance Ollama avec
nomic-embed-text.
Quand pgvector suffit
Section intitulée « Quand pgvector suffit »Le parcours RAG a présenté FAISS, Chroma et Qdrant. pgvector se place à côté, avec une logique différente : il ne remplace pas la base de données, il l'augmente.
pgvector est le bon choix dans un cas précis et fréquent : l'équipe exploite déjà PostgreSQL, en maîtrise l'administration, les sauvegardes, le monitoring. Ajouter un RAG, c'est alors ajouter une extension et une table, pas un service de plus à exploiter. Les données vectorielles bénéficient de tout l'outillage Postgres existant.
Ses limites apparaissent à très grande échelle : une base vectorielle spécialisée comme Qdrant est optimisée pour des dizaines de millions de vecteurs interrogés en continu. Mais pour un corpus de taille raisonnable, une documentation d'entreprise, une base de connaissances, pgvector tient parfaitement, et économise un composant.
| Contexte | Choix conseillé |
|---|---|
| PostgreSQL déjà en place, corpus raisonnable | pgvector |
| Pas de base relationnelle, prototype | FAISS, Chroma |
| Très gros volume, service vectoriel dédié | Qdrant |
Activer pgvector
Section intitulée « Activer pgvector »pgvector est une extension PostgreSQL. L'image Docker pgvector/pgvector la fournit déjà compilée, il reste à l'activer dans la base.
docker run -d -e POSTGRES_PASSWORD=lab -e POSTGRES_DB=ragdb \ -p 5433:5432 pgvector/pgvector:pg17Côté Python, on se connecte avec psycopg et on active l'extension. La bibliothèque pgvector ajoute l'adaptation du type vector entre Python et PostgreSQL.
import psycopgfrom pgvector.psycopg import register_vector
def connexion() -> psycopg.Connection: """Ouvre une connexion PostgreSQL prête pour les vecteurs.""" conn = psycopg.connect(DSN, autocommit=True) conn.execute("CREATE EXTENSION IF NOT EXISTS vector") register_vector(conn) # adapte le type vector côté Python return connCREATE EXTENSION IF NOT EXISTS vector active pgvector, une seule fois par base. Et register_vector apprend à psycopg à convertir les vecteurs Python ↔ PostgreSQL.
Créer une table vectorielle
Section intitulée « Créer une table vectorielle »Une table de RAG est une table PostgreSQL ordinaire, avec une colonne d'un type spécial : vector.
DIMENSION = 768 # dimension des vecteurs de nomic-embed-text
conn.execute( f"CREATE TABLE documents (" f"id serial PRIMARY KEY, texte text, sujet text, " f"embedding vector({DIMENSION}))")conn.execute( "CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)")Deux éléments sont spécifiques au vectoriel. La colonne vector(768) déclare la dimension exacte des embeddings, celle du modèle utilisé. Et l'index HNSW accélère la recherche : sans lui, chaque requête parcourrait toute la table ; avec lui, la recherche reste rapide même sur un gros corpus. Le vector_cosine_ops précise que cet index sert une recherche par distance cosinus.
À côté, id, texte, sujet sont des colonnes classiques, c'est tout l'intérêt : le vectoriel cohabite avec le relationnel.
Indexer le corpus
Section intitulée « Indexer le corpus »On insère les documents avec un INSERT ordinaire. Le vecteur, calculé avec Ollama, va dans la colonne embedding.
vecteurs = vectoriser([d["texte"] for d in CORPUS])for doc, vecteur in zip(CORPUS, vecteurs): conn.execute( "INSERT INTO documents (texte, sujet, embedding) VALUES (%s, %s, %s)", (doc["texte"], doc["sujet"], vecteur), )Grâce à register_vector, on passe le vecteur comme une liste Python : la bibliothèque pgvector le convertit au format attendu par la colonne vector. Aucune sérialisation manuelle.
Rechercher par distance cosinus
Section intitulée « Rechercher par distance cosinus »La recherche se fait en SQL. pgvector ajoute l'opérateur <=> : il calcule la distance cosinus entre deux vecteurs. On classe par cette distance croissante, la plus petite distance, le plus proche voisin.
def rechercher(conn, question, k=3): v_question = vectoriser([question])[0] requete = ( "SELECT texte, sujet, embedding <=> %s::vector AS distance " "FROM documents ORDER BY distance LIMIT %s" ) lignes = conn.execute(requete, [v_question, k]).fetchall() return [{"texte": t, "sujet": s, "similarite": 1 - d} for t, s, d in lignes]L'opérateur <=> renvoie une distance, 0 pour des vecteurs identiques. Pour raisonner en similarité, on prend 1 - distance. La clause ORDER BY distance LIMIT k fait tout le travail de recherche des k plus proches.
Combiner recherche vectorielle et SQL
Section intitulée « Combiner recherche vectorielle et SQL »C'est l'atout décisif de pgvector : la recherche vectorielle est du SQL, donc elle se combine librement avec tout le reste du SQL. Filtrer par une colonne classique, c'est ajouter une clause WHERE.
requete = ( "SELECT texte, sujet, embedding <=> %s::vector AS distance " "FROM documents WHERE sujet = %s " "ORDER BY distance LIMIT %s")La recherche sémantique ne s'applique alors qu'aux lignes qui passent le WHERE. Et rien n'empêche d'aller plus loin : une jointure avec une table d'utilisateurs, un filtre sur une date, une agrégation. Là où une base vectorielle dédiée a un langage de filtrage propre et limité, pgvector hérite de tout SQL. Pour un RAG aux règles d'accès complexes, c'est un avantage réel.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
type "vector" does not exist | Extension non activée | CREATE EXTENSION vector |
operator does not exist: vector <=> double precision[] | Cast manquant | Caster le paramètre en %s::vector |
| Recherche lente sur un gros corpus | Pas d'index HNSW | Créer l'index hnsw (... vector_cosine_ops) |
Erreur de dimension à l'INSERT | Colonne et modèle d'embedding divergent | Recréer la colonne vector(N) à la bonne taille |
connection refused | Mauvais port ou conteneur arrêté | Vérifier le port publié et l'état du conteneur |
À retenir
Section intitulée « À retenir »- pgvector ajoute la recherche vectorielle à PostgreSQL, pas un service de plus.
- Le bon choix quand une base PostgreSQL est déjà exploitée et le corpus raisonnable.
- Une table de RAG est une table ordinaire avec une colonne
vector(N). - L'index HNSW rend la recherche rapide ; l'opérateur
<=>mesure la distance cosinus. - Le paramètre de recherche doit être casté en
::vector. - La recherche étant du SQL, elle se combine librement avec
WHERE, jointures et agrégations.
Prochaines étapes
Section intitulée « Prochaines étapes »Pour aller plus loin
Section intitulée « Pour aller plus loin »- Dépôt pgvector, la référence de l'extension et de ses opérateurs.