AI Engineering Academy · Leçon

Recherche hybride avec Pinecone et pgvector

Configurez la recherche hybride dans Pinecone avec le mode d’indexation sparse-dense et dans pgvector avec des requêtes parallèles et une fusion RRF, puis évaluez la qualité de la recherche sur votre jeu de données.

Leçon 4 sur 413 étapes

Recherche hybride avec Pinecone et pgvector est une leçon AI Engineering Academy gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage AI Engineering Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours AI Engineering Academy comprend 4 leçons au total.

Recherche hybride native dans les bases de données vectorielles

Vous pouvez implémenter vous-même la recherche hybride à l'aide de deux index distincts et d'une fusion RRF, mais les bases de données vectorielles destinées à la production proposent de plus en plus une recherche hybride intégrée qui gère la recherche creuse et dense au sein d'une seule requête. Pinecone et pgvector prennent tous deux en charge les modes hybrides, mais avec des choix d'architecture et des compromis différents. Comprendre ces deux options vous permet de choisir l'outil adapté à votre infrastructure.

Mode d'index creux-dense de Pinecone

Pinecone prend en charge un index creux-dense dans lequel chaque enregistrement vectoriel stocke à la fois une représentation dense (sous forme de tableau de nombres flottants) et un vecteur creux (sous forme de dictionnaire associant un identifiant de jeton à un poids). Les requêtes peuvent spécifier un vecteur de requête dense et un vecteur de requête creux, puis Pinecone fusionne les résultats à l'aide d'une combinaison linéaire pondérée. Cette méthode diffère de RRF : Pinecone utilise une fusion des scores bruts avec des pondérations configurables appelées alpha.

from pinecone import Pinecone, ServerlessSpec

pc = Pinecone(api_key='YOUR_API_KEY')

# Create a sparse-dense index
pc.create_index(
    name='hybrid-index',
    dimension=1536,          # dense vector dimension
    metric='dotproduct',     # must use dotproduct for hybrid
    spec=ServerlessSpec(cloud='aws', region='us-east-1'),
)

index = pc.Index('hybrid-index')

Générer des vecteurs creux avec SPLADE

Pour utiliser le mode creux-dense de Pinecone, vous devez générer des vecteurs creux pour chaque document. L'approche la plus efficace repose sur SPLADE (Sparse Lexical and Expansion), un modèle neuronal qui produit des représentations creuses apprises avec expansion : il ajoute au vecteur creux des termes sémantiquement associés qui ne figurent pas nécessairement littéralement dans le texte. Vous pouvez aussi utiliser de simples pondérations de termes BM25 comme vecteurs creux.

from transformers import AutoTokenizer, AutoModelForMaskedLM
import torch

# Load SPLADE model
tokenizer = AutoTokenizer.from_pretrained('naver/splade-cocondenser-ensembledistil')
model = AutoModelForMaskedLM.from_pretrained('naver/splade-cocondenser-ensembledistil')

def generate_sparse_vector(text: str) -> dict:
    tokens = tokenizer(text, return_tensors='pt', truncation=True, max_length=512)
    with torch.no_grad():
        output = model(**tokens)
    logits = output.logits
    # Max-pool over sequence length and apply log1p activation
    sparse = torch.max(torch.log1p(torch.relu(logits)), dim=1).values.squeeze()
    # Return non-zero indices and values as a sparse dict
    nz_indices = sparse.nonzero().squeeze().tolist()
    nz_values = sparse[nz_indices].tolist()
    return {'indices': nz_indices, 'values': nz_values}

Insérer des vecteurs hybrides dans Pinecone

Chaque enregistrement Pinecone d'un index hybride stocke un id, un tableau dense values, un dictionnaire sparse_values contenant indices et values, ainsi que des metadata facultées. Insérez les enregistrements par lots de 100 afin de maximiser le débit tout en respectant les limites de taille des requêtes de Pinecone.

def upsert_hybrid_docs(index, documents: list[dict], batch_size: int = 100):
    records = []
    for doc in documents:
        dense_vec = embed(doc['text'])        # OpenAI embedding
        sparse_vec = generate_sparse_vector(doc['text'])  # SPLADE
        records.append({
            'id': doc['id'],
            'values': dense_vec,
            'sparse_values': sparse_vec,
            'metadata': {'text': doc['text'], 'source': doc['source']},
        })

    for i in range(0, len(records), batch_size):
        batch = records[i:i + batch_size]
        index.upsert(vectors=batch)
        print(f'Upserted batch {i//batch_size + 1}')

Interroger l'index hybride de Pinecone

Une requête hybride transmet à l'API de requête Pinecone à la fois un vector dense et un sparse_vector. Le paramètre alpha (de 0 à 1) contrôle l'équilibre : alpha=1.0 correspond à une recherche entièrement dense, alpha=0.0 à une recherche entièrement creuse et alpha=0.5 attribue le même poids aux deux. Ajustez alpha sur un ensemble de validation : les valeurs optimales courantes se situent entre 0.3 et 0.7.

def hybrid_query_pinecone(index, query: str, alpha: float = 0.5, top_k: int = 5):
    dense_vec = embed(query)
    sparse_vec = generate_sparse_vector(query)

    # Scale vectors by alpha for weighted fusion
    scaled_dense = [v * alpha for v in dense_vec]
    scaled_sparse = {
        'indices': sparse_vec['indices'],
        'values': [v * (1 - alpha) for v in sparse_vec['values']],
    }

    results = index.query(
        vector=scaled_dense,
        sparse_vector=scaled_sparse,
        top_k=top_k,
        include_metadata=True,
    )
    return results.matches

pgvector : recherche vectorielle dans PostgreSQL

L'extension pgvector ajoute à PostgreSQL un type de colonne vector et des opérateurs de distance. Pour effectuer une recherche hybride, vous exécutez deux requêtes en parallèle : une requête de plus proches voisins approximatifs sur la colonne vectorielle et une requête de recherche en texte intégral utilisant tsvector et tsquery, intégrés à PostgreSQL, puis vous fusionnez les résultats avec RRF dans le code de votre application.

-- Setup: enable extensions
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- Create hybrid-capable table
CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    content TEXT NOT NULL,
    embedding vector(1536),
    content_tsv tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED
);

-- Indexes for both search modes
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
CREATE INDEX ON documents USING GIN (content_tsv);

Exécuter des requêtes denses et creuses dans pgvector

Avec pgvector, exécutez la requête dense à l'aide de l'opérateur de distance cosinus <=> pour trouver les plus proches voisins de la représentation vectorielle de la requête, et la requête creuse à l'aide de ts_rank_cd sur la colonne tsvector afin d'évaluer les correspondances de mots-clés. Récupérez séparément les deux ensembles de résultats, puis fusionnez-les avec RRF en Python.

import psycopg2
import json

def pgvector_hybrid_search(conn, query: str, top_k: int = 5):
    dense_vec = embed(query)
    tsquery = ' & '.join(query.split())  # simple AND query

    # Dense query
    dense_sql = '''
        SELECT id, content, 1 - (embedding <=> %s::vector) AS score
        FROM documents
        ORDER BY embedding <=> %s::vector
        LIMIT 20
    '''
    # Sparse query
    sparse_sql = '''
        SELECT id, content, ts_rank_cd(content_tsv, to_tsquery('english', %s)) AS score
        FROM documents
        WHERE content_tsv @@ to_tsquery('english', %s)
        ORDER BY score DESC
        LIMIT 20
    '''
    with conn.cursor() as cur:
        cur.execute(dense_sql, [json.dumps(dense_vec), json.dumps(dense_vec)])
        dense_rows = cur.fetchall()
        cur.execute(sparse_sql, [tsquery, tsquery])
        sparse_rows = cur.fetchall()

    return fuse_with_rrf(dense_rows, sparse_rows, top_k)

Appliquer RRF aux résultats de pgvector

Après avoir récupéré les lignes classées issues des deux requêtes PostgreSQL, appliquez RRF en extrayant les identifiants des documents dans l'ordre de leur classement pour chaque ensemble de résultats, puis en exécutant l'algorithme de fusion. La liste finale fusionnée vous fournit les K premiers documents les plus pertinents à la fois par leur sens sémantique et par le chevauchement des mots-clés.

def fuse_with_rrf(dense_rows, sparse_rows, top_k: int = 5, k: int = 60):
    from collections import defaultdict

    doc_texts = {}
    scores = defaultdict(float)

    for rank, row in enumerate(dense_rows, start=1):
        doc_id, text, _ = row
        scores[doc_id] += 1.0 / (k + rank)
        doc_texts[doc_id] = text

    for rank, row in enumerate(sparse_rows, start=1):
        doc_id, text, _ = row
        scores[doc_id] += 1.0 / (k + rank)
        doc_texts[doc_id] = text

    ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)
    return [{'id': doc_id, 'text': doc_texts[doc_id], 'rrf_score': s}
            for doc_id, s in ranked[:top_k]]

Évaluer la qualité de la recherche

Après avoir implémenté la recherche hybride, évaluez-la rigoureusement par rapport à votre recherche de référence uniquement dense. Utilisez un ensemble de test de référence d'au moins 100 requêtes dont les documents pertinents sont connus. Mesurez NDCG@5, MRR et le taux de réussite@3. Les améliorations typiques de la recherche hybride vont de 5 à 20 % pour NDCG@5, avec les gains les plus importants pour les requêtes contenant des termes techniques exacts que les modèles denses ne détectent pas.

def evaluate_retrieval(search_fn, test_queries, golden_relevant, k=5):
    ndcg_scores = []
    hit_count = 0

    for query, relevant_ids in zip(test_queries, golden_relevant):
        results = search_fn(query, top_k=k)
        retrieved_ids = [r['id'] for r in results]

        # Hit rate
        if any(rid in relevant_ids for rid in retrieved_ids):
            hit_count += 1

        # Simplified NDCG (binary relevance)
        dcg = sum(
            1.0 / (i + 1)
            for i, rid in enumerate(retrieved_ids)
            if rid in relevant_ids
        )
        idcg = sum(1.0 / (i + 1) for i in range(min(len(relevant_ids), k)))
        ndcg_scores.append(dcg / idcg if idcg > 0 else 0)

    return {
        'hit_rate': hit_count / len(test_queries),
        'ndcg': sum(ndcg_scores) / len(ndcg_scores),
    }

Ajuster Alpha dans le mode hybride de Pinecone

Le paramètre alpha des requêtes hybrides de Pinecone nécessite un ajustement systématique. La stratification par type de requête est une approche utile : classez vos requêtes de test comme principalement sémantiques (paraphrases, concepts) ou principalement lexicales (termes exacts, codes), puis mesurez séparément la valeur optimale d'alpha pour chaque groupe. Certains systèmes de production mettent en œuvre une sélection dynamique d'alpha qui classe la requête lors de son exécution et choisit automatiquement l'alpha approprié.

def find_optimal_alpha(index, test_queries, golden_relevant, alphas=None):
    if alphas is None:
        alphas = [0.0, 0.2, 0.4, 0.5, 0.6, 0.8, 1.0]

    best_alpha, best_score = 0.5, 0
    for alpha in alphas:
        hits = 0
        for query, relevant in zip(test_queries, golden_relevant):
            results = hybrid_query_pinecone(index, query, alpha=alpha, top_k=5)
            if any(r['id'] in relevant for r in results):
                hits += 1
        hit_rate = hits / len(test_queries)
        print(f'alpha={alpha}: hit_rate={hit_rate:.3f}')
        if hit_rate > best_score:
            best_score, best_alpha = hit_rate, alpha
    return best_alpha

Choisir entre Pinecone hybride et pgvector hybride

Utilisez Pinecone hybride si vous avez besoin d'une infrastructure gérée et d'une mise à l'échelle automatique, et si vous souhaitez déléguer à une seule API la complexité de la génération des vecteurs creux. Utilisez pgvector hybride si vous disposez déjà d'une infrastructure PostgreSQL, si vous avez besoin d'une cohérence transactionnelle entre les documents et leurs métadonnées, si vous souhaitez bénéficier de toute la flexibilité de SQL pour le filtrage ou si vous voulez éviter la dépendance à un fournisseur. Les deux approches offrent une excellente qualité de recherche hybride lorsqu'elles sont correctement ajustées.

Vérification rapide

Vérifiez votre compréhension de l'implémentation de la recherche hybride présentée dans cette leçon.

Récapitulatif de la leçon

Dans cette leçon, vous avez appris que le mode creux-dense de Pinecone stocke des vecteurs denses et creux pour chaque enregistrement et les fusionne avec un paramètre alpha configurable, que la recherche hybride avec pgvector combine la recherche SQL en texte intégral et les requêtes vectorielles, fusionnées par RRF dans le code de l'application, et que l'ajustement d'alpha sur un ensemble de validation est essentiel pour trouver l'équilibre optimal selon la distribution de vos requêtes. Nous allons maintenant explorer la recherche en deux étapes avec reclassement.

Gratuit pour commencer

Apprends Python avec un tuteur IA — gratuit

Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.

Cours
30
Leçons
120

Questions Fréquemment Posées

La leçon « Recherche hybride avec Pinecone et pgvector » est-elle gratuite ?

Oui — le texte complet de « Recherche hybride avec Pinecone et pgvector » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours AI Engineering Academy, passe à CoddyKit PRO. Le cours AI Engineering Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Recherche hybride avec Pinecone et pgvector » ?

Configurez la recherche hybride dans Pinecone avec le mode d’indexation sparse-dense et dans pgvector avec des requêtes parallèles et une fusion RRF, puis évaluez la qualité de la recherche sur votre… Tu pratiques AI Engineering Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer AI Engineering Academy ?

Aucune expérience préalable n'est requise. AI Engineering Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.

Combien de temps prend la leçon « Recherche hybride avec Pinecone et pgvector » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon AI Engineering Academy ?

Oui. Chaque leçon AI Engineering Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Recherche dense ou creuse : compromis
  2. Implémenter une recherche par mots-clés avec BM25
  3. Fusion réciproque des rangs pour combiner les scores
  4. Recherche hybride avec Pinecone et pgvector
← Retour à AI Engineering Academy