Aller au contenu principal

Bases de données vectorielles (Pinecone, Qdrant, Weaviate)

Mis à jour le 29 juillet 2026

Objectifs

  • Comprendre pourquoi les bases vectorielles sont nécessaires
  • Comparer Pinecone, Qdrant, Weaviate, Chroma et pgvector
  • Implémenter une recherche avec chacune d’entre elles

Pourquoi une base vectorielle ?

L’index NumPy de la leçon précédente fonctionne parfaitement pour quelques milliers de documents, parce qu’un balayage exhaustif de dix mille vecteurs reste imperceptible. À un million, le même calcul devient une seconde de latence par requête, et la matrice ne tient plus confortablement en mémoire.

Quatre besoins apparaissent alors ensemble. La recherche ANN (Approximate Nearest Neighbors) abandonne la garantie du résultat exact au profit d’une réponse en millisecondes sur des millions de vecteurs — un compromis presque toujours acceptable, puisque le cinquième meilleur document au lieu du quatrième ne change rien à l’expérience. Le filtrage par métadonnées combiné à la recherche vectorielle permet de restreindre la requête à une catégorie, une langue ou une plage de dates sans post-traitement. La persistance et la mise à jour en temps réel évitent de reconstruire un fichier entier pour ajouter un document. Le scaling horizontal, enfin, est ce qui distingue un prototype d’un service de production.

Pinecone

Pinecone est un service managé : vous ne provisionnez aucun serveur, vous créez un index en indiquant sa dimension et sa métrique, et le reste est pris en charge. Remarquez le filtre categorie passé directement à query — la recherche vectorielle et le filtre s’exécutent ensemble, pas l’un après l’autre.

from pinecone import Pinecone
from openai import OpenAI

pc = Pinecone(api_key="votre-cle-pinecone")
openai_client = OpenAI()

# Créer un index
pc.create_index(
    name="mon-corpus",
    dimension=3072,
    metric="cosine",
    spec={"serverless": {"cloud": "aws", "region": "eu-west-1"}}
)

index = pc.Index("mon-corpus")

# Indexer des documents
documents = [
    {"id": "doc1", "texte": "Guide d'installation Python",
     "categorie": "tutoriel"},
    {"id": "doc2", "texte": "Déployer une application Flask",
     "categorie": "tutoriel"},
    {"id": "doc3", "texte": "Rapport trimestriel Q1 2026",
     "categorie": "rapport"},
]

# Générer les embeddings
textes = [d["texte"] for d in documents]
response = openai_client.embeddings.create(
    input=textes,
    model="text-embedding-3-large"
)

# Upserter dans Pinecone
vecteurs = [
    {
        "id": doc["id"],
        "values": emb.embedding,
        "metadata": {"texte": doc["texte"], "categorie": doc["categorie"]}
    }
    for doc, emb in zip(documents, response.data)
]
index.upsert(vectors=vecteurs)

# Rechercher
query_emb = openai_client.embeddings.create(
    input="comment installer Python",
    model="text-embedding-3-large"
).data[0].embedding

resultats = index.query(
    vector=query_emb,
    top_k=3,
    include_metadata=True,
    filter={"categorie": {"$eq": "tutoriel"}}
)

for match in resultats.matches:
    print(f"[{match.score:.3f}] {match.metadata['texte']}")

La dimension déclarée à la création est définitive : passer de text-embedding-3-large à text-embedding-3-small impose de recréer l’index. Décidez donc du modèle avant, pas après.

Qdrant

Qdrant est open source, s’auto-héberge et offre d’excellentes performances. Le vocabulaire diffère — on parle de collection, de points et de payload là où Pinecone parlait d’index, de vecteurs et de métadonnées — mais le schéma reste identique. Notez que l’exemple appelle l’API d’embedding document par document dans la boucle : c’est acceptable sur trois documents, mais pensez à repasser en lots dès que le corpus grandit.

from qdrant_client import QdrantClient
from qdrant_client.models import (
    Distance, VectorParams, PointStruct, Filter,
    FieldCondition, MatchValue
)
from openai import OpenAI

qclient = QdrantClient(url="http://localhost:6333")
openai_client = OpenAI()

# Créer une collection
qclient.create_collection(
    collection_name="mon-corpus",
    vectors_config=VectorParams(
        size=3072,
        distance=Distance.COSINE
    )
)

# Indexer des documents
points = []
for i, doc in enumerate(documents):
    emb = openai_client.embeddings.create(
        input=doc["texte"],
        model="text-embedding-3-large"
    ).data[0].embedding

    points.append(PointStruct(
        id=i,
        vector=emb,
        payload={"texte": doc["texte"], "categorie": doc["categorie"]}
    ))

qclient.upsert(collection_name="mon-corpus", points=points)

# Rechercher avec filtre
query_emb = openai_client.embeddings.create(
    input="déployer une application web",
    model="text-embedding-3-large"
).data[0].embedding

resultats = qclient.search(
    collection_name="mon-corpus",
    query_vector=query_emb,
    limit=3,
    query_filter=Filter(
        must=[FieldCondition(
            key="categorie",
            match=MatchValue(value="tutoriel")
        )]
    )
)

for r in resultats:
    print(f"[{r.score:.3f}] {r.payload['texte']}")

Weaviate

Weaviate se distingue par sa recherche hybride native, sur laquelle nous reviendrons en leçon 9. Son modèle de données est plus structuré que celui de ses concurrents : on déclare explicitement les propriétés et leurs types avant d’insérer quoi que ce soit, ce qui rapproche l’expérience d’une base classique.

import weaviate
from openai import OpenAI

wclient = weaviate.connect_to_local()
openai_client = OpenAI()

# Créer une collection
from weaviate.classes.config import Property, DataType

collection = wclient.collections.create(
    name="Document",
    properties=[
        Property(name="texte", data_type=DataType.TEXT),
        Property(name="categorie", data_type=DataType.TEXT),
    ]
)

# Indexer
for doc in documents:
    emb = openai_client.embeddings.create(
        input=doc["texte"],
        model="text-embedding-3-large"
    ).data[0].embedding

    collection.data.insert(
        properties={"texte": doc["texte"], "categorie": doc["categorie"]},
        vector=emb
    )

# Rechercher
query_emb = openai_client.embeddings.create(
    input="installer Python",
    model="text-embedding-3-large"
).data[0].embedding

resultats = collection.query.near_vector(
    near_vector=query_emb,
    limit=3
)

Chroma et pgvector

Chroma vise le prototypage : un client persistant pointé sur un dossier local, une collection créée à la volée, aucune infrastructure. C’est l’option qui vous fait passer de l’index NumPy à une vraie base sans changer d’environnement de travail.

import chromadb
from openai import OpenAI

chroma = chromadb.PersistentClient(path="./chroma_db")
openai_client = OpenAI()

collection = chroma.get_or_create_collection("mon-corpus")

# Indexer
for doc in documents:
    emb = openai_client.embeddings.create(
        input=doc["texte"],
        model="text-embedding-3-large"
    ).data[0].embedding

    collection.add(
        ids=[doc["id"]],
        embeddings=[emb],
        documents=[doc["texte"]],
        metadatas=[{"categorie": doc["categorie"]}]
    )

# Rechercher
query_emb = openai_client.embeddings.create(
    input="comment installer Python",
    model="text-embedding-3-large"
).data[0].embedding

resultats = collection.query(query_embeddings=[query_emb], n_results=3)

pgvector répond à une situation très différente : vos documents sont déjà dans PostgreSQL, avec leurs jointures, leurs droits d’accès et leurs sauvegardes. Plutôt que d’introduire un second système à synchroniser, l’extension ajoute un type vector et des opérateurs de distance à la base existante. L’opérateur <=> calcule la distance cosinus, et l’index HNSW est ce qui rend la requête utilisable au-delà de quelques milliers de lignes.

-- Extension pgvector
CREATE EXTENSION vector;

CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    texte TEXT,
    categorie TEXT,
    embedding vector(3072)
);

-- Index HNSW pour la performance
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops);

-- Recherche des 5 plus proches
SELECT id, texte, 1 - (embedding <=> $1::vector) AS score
FROM documents
ORDER BY embedding <=> $1::vector
LIMIT 5;

Comparatif

CritèrePineconeQdrantWeaviateChromapgvector
TypeManagéOpen sourceOpen sourceOpen sourceExtension
HébergementCloudSelf-hosted/CloudSelf-hosted/CloudLocal/EmbedPostgreSQL
Recherche hybride✅ NativeVia SQL
Production⚠️ Prototypage
Facilité✅✅✅✅

Le choix se décide moins sur les performances brutes, très proches en pratique, que sur votre contexte : l’équipe qui exploite déjà PostgreSQL a tout intérêt à rester dessus, celle qui n’a pas d’ops choisira le managé, et celle qui doit héberger en Europe pour des raisons réglementaires se tournera vers l’auto-hébergé.

Résumé

  • Chroma pour le prototypage rapide
  • pgvector si vous avez déjà PostgreSQL
  • Pinecone pour du managé sans infrastructure
  • Qdrant pour l’auto-hébergement performant
  • Weaviate pour la recherche hybride native