Aller au contenu
Développement medium

pgvector : un RAG sans base vectorielle dédiée

9 min de lecture

logo python

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.

  • 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 vector et un index HNSW.
  • Rechercher par distance cosinus avec l'opérateur <=>.
  • Combiner recherche vectorielle et filtrage SQL.
  • Python 3.10+ et les bases du SQL.
  • PostgreSQL avec pgvector — l'image Docker pgvector/pgvector.
  • Une instance Ollama avec nomic-embed-text.

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.

ContexteChoix conseillé
PostgreSQL déjà en place, corpus raisonnablepgvector
Pas de base relationnelle, prototypeFAISS, Chroma
Très gros volume, service vectoriel dédiéQdrant

pgvector est une extension PostgreSQL. L'image Docker pgvector/pgvector la fournit déjà compilée — il reste à l'activer dans la base.

Fenêtre de terminal
docker run -d -e POSTGRES_PASSWORD=lab -e POSTGRES_DB=ragdb \
-p 5433:5432 pgvector/pgvector:pg17

Cô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 psycopg
from 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 conn

CREATE EXTENSION IF NOT EXISTS vector active pgvector — une seule fois par base. Et register_vector apprend à psycopg à convertir les vecteurs Python ↔ PostgreSQL.

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.

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.

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.

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.

SymptômeCause probableSolution
type "vector" does not existExtension non activéeCREATE EXTENSION vector
operator does not exist: vector <=> double precision[]Cast manquantCaster le paramètre en %s::vector
Recherche lente sur un gros corpusPas d'index HNSWCréer l'index hnsw (... vector_cosine_ops)
Erreur de dimension à l'INSERTColonne et modèle d'embedding divergentRecréer la colonne vector(N) à la bonne taille
connection refusedMauvais port ou conteneur arrêtéVérifier le port publié et l'état du conteneur
  • 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.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn