
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.