0Pricing
AI Engineering Academy · Lekcja

Wyszukiwanie hybrydowe w Pinecone i pgvector

Skonfiguruj wyszukiwanie hybrydowe w Pinecone z użyciem trybu indeksu sparse-dense oraz w pgvector za pomocą równoległych zapytań i łączenia RRF, a następnie porównaj jakość wyszukiwania na swoim zbiorze danych.

Wyszukiwanie hybrydowe w Pinecone i pgvector to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Engineering Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Natywne wyszukiwanie hybrydowe w bazach wektorowych

Wyszukiwanie hybrydowe można zaimplementować samodzielnie, korzystając z dwóch oddzielnych indeksów i łączenia RRF, jednak produkcyjne bazy wektorowe coraz częściej oferują wbudowane wyszukiwanie hybrydowe, które obsługuje wyszukiwanie rzadkie i gęste w ramach jednego zapytania. Zarówno Pinecone, jak i pgvector obsługują tryby hybrydowe, ale stosują różne rozwiązania architektoniczne i wiążą się z innymi kompromisami. Zrozumienie obu opcji pozwala wybrać narzędzie odpowiednie dla Państwa infrastruktury.

Tryb indeksu sparse-dense w Pinecone

Pinecone obsługuje indeks sparse-dense, w którym każdy rekord wektorowy przechowuje zarówno gęste embedding (jako tablicę liczb zmiennoprzecinkowych), jak i wektor rzadki (jako słownik mapujący identyfikator tokenu na wagę). Zapytania mogą określać zarówno gęsty wektor zapytania, jak i rzadki wektor zapytania, a Pinecone łączy wyniki za pomocą ważonej kombinacji liniowej. Różni się to od RRF — Pinecone wykorzystuje łączenie surowych wyników z konfigurowalnymi wagami nazywanymi 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')

Generowanie rzadkich wektorów za pomocą SPLADE

Aby korzystać z trybu sparse-dense w Pinecone, należy wygenerować rzadkie wektory dla każdego dokumentu. Najskuteczniejszym podejściem jest SPLADE (Sparse Lexical and Expansion) — model neuronowy, który tworzy wyuczone rzadkie reprezentacje z ekspansją; dodaje on do rzadkiego wektora terminy powiązane semantycznie, które nie muszą dosłownie występować w tekście. Alternatywnie można użyć prostych wag terminów BM25 jako rzadkich wektorów.

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}

Wstawianie hybrydowych wektorów do Pinecone

Każdy rekord Pinecone w indeksie hybrydowym przechowuje element id, gęstą tablicę values, słownik sparse_values zawierający elementy indices i values oraz opcjonalne metadata. Rekordy należy wstawiać partiami po 100, aby zmaksymalizować przepustowość i jednocześnie zmieścić się w limitach rozmiaru żądania 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}')

Odpytywanie hybrydowego indeksu Pinecone

Zapytanie hybrydowe przekazuje do API zapytań Pinecone zarówno gęsty vector, jak i sparse_vector. Parametr alpha (od 0 do 1) steruje proporcjami: alpha=1.0 oznacza wyłącznie wyszukiwanie gęste, alpha=0.0 — wyłącznie rzadkie, a alpha=0.5 nadaje obu typom równą wagę. Wartość alpha należy dostrajać na zbiorze walidacyjnym — typowe optymalne wartości mieszczą się w zakresie od 0.3 do 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: wyszukiwanie wektorowe w PostgreSQL

Rozszerzenie pgvector dodaje do PostgreSQL typ kolumny vector oraz operatory odległości. W przypadku wyszukiwania hybrydowego uruchamiają Państwo równolegle dwa zapytania — zapytanie approximate nearest neighbor dotyczące kolumny wektorowej oraz zapytanie wyszukiwania pełnotekstowego wykorzystujące wbudowane w PostgreSQL typy tsvector i tsquery — a następnie łączą wyniki za pomocą RRF w kodzie aplikacji.

-- 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);

Uruchamianie zapytań gęstych i rzadkich w pgvector

W pgvector należy uruchomić zapytanie gęste z użyciem operatora odległości cosinusowej <=>, aby znaleźć najbliższych sąsiadów embeddingu zapytania, oraz zapytanie rzadkie z użyciem ts_rank_cd względem kolumny tsvector, aby ocenić dopasowania słów kluczowych. Oba zbiory wyników należy pobrać niezależnie, a następnie połączyć za pomocą RRF w Pythonie.

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)

Stosowanie RRF do wyników pgvector

Po zebraniu uporządkowanych wierszy z obu zapytań PostgreSQL należy zastosować RRF, wyodrębniając identyfikatory dokumentów w kolejności ich pozycji z każdego zbioru wyników i uruchamiając algorytm łączenia. Ostateczna połączona lista zawiera dokumenty top-K, które są najbardziej istotne zarówno pod względem znaczenia semantycznego, jak i pokrycia słów kluczowych.

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]]

Testowanie jakości wyszukiwania

Po zaimplementowaniu wyszukiwania hybrydowego należy je rygorystycznie przetestować porównawczo względem bazowego wyszukiwania wyłącznie gęstego. Proszę użyć złotego zbioru testowego obejmującego co najmniej 100 zapytań ze znanymi istotnymi dokumentami. Należy zmierzyć NDCG@5, MRR oraz hit rate@3. Typowa poprawa dzięki wyszukiwaniu hybrydowemu wynosi od 5 do 20 procent dla NDCG@5, przy czym największe zyski występują w przypadku zapytań zawierających dokładne terminy techniczne, których modele gęste nie uwzględniają.

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),
    }

Dostrajanie parametru alpha w trybie hybrydowym Pinecone

Parametr alpha w hybrydowym zapytaniu Pinecone wymaga systematycznego dostrajania. Przydatnym podejściem jest stratyfikacja według typu zapytania: należy sklasyfikować zapytania testowe jako głównie semantyczne (parafrazy, pojęcia) albo głównie leksykalne (dokładne terminy, kody), a następnie osobno zmierzyć optymalną wartość alpha dla każdej grupy. Niektóre systemy produkcyjne stosują dynamiczny wybór alpha, który klasyfikuje zapytanie w czasie działania i automatycznie dobiera odpowiednią wartość alpha.

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

Wybór między hybrydowym Pinecone a pgvector

Proszę użyć hybrydowego Pinecone, gdy potrzebują Państwo zarządzanej infrastruktury i automatycznego skalowania oraz chcą przenieść złożoność generowania rzadkich wektorów do jednego API. Proszę użyć hybrydowego pgvector, gdy mają już Państwo infrastrukturę PostgreSQL, potrzebują spójności transakcyjnej między dokumentami a ich metadanymi, chcą korzystać z pełnej elastyczności SQL przy filtrowaniu lub muszą uniknąć uzależnienia od dostawcy. Oba podejścia zapewniają doskonałą jakość wyszukiwania hybrydowego po odpowiednim dostrojeniu.

Szybkie sprawdzenie

Proszę sprawdzić zrozumienie implementacji wyszukiwania hybrydowego na podstawie tej lekcji.

Podsumowanie lekcji

W tej lekcji poznali Państwo następujące zagadnienia: tryb sparse-dense Pinecone przechowuje dla każdego rekordu zarówno gęste, jak i rzadkie wektory oraz łączy je za pomocą konfigurowalnego parametru alpha, wyszukiwanie hybrydowe pgvector łączy wyszukiwanie pełnotekstowe SQL z zapytaniami wektorowymi, a wyniki są scalane za pomocą RRF w kodzie aplikacji, natomiast dostrajanie alpha na zbiorze walidacyjnym jest niezbędne do znalezienia optymalnych proporcji dla rozkładu zapytań. W następnej części omówimy wyszukiwanie dwuetapowe z ponownym szeregowanie.

Często zadawane pytania

Czy lekcja „Wyszukiwanie hybrydowe w Pinecone i pgvector” jest bezpłatna?

Tak — pełny tekst „Wyszukiwanie hybrydowe w Pinecone i pgvector” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Engineering Academy, przejdź na CoddyKit PRO. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Wyszukiwanie hybrydowe w Pinecone i pgvector”?

Skonfiguruj wyszukiwanie hybrydowe w Pinecone z użyciem trybu indeksu sparse-dense oraz w pgvector za pomocą równoległych zapytań i łączenia RRF, a następnie porównaj jakość wyszukiwania na swoim zbi… Ćwiczysz AI Engineering Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Engineering Academy?

Nie wymagamy żadnego doświadczenia. AI Engineering Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Wyszukiwanie hybrydowe w Pinecone i pgvector”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Engineering Academy?

Tak. Każda lekcja AI Engineering Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Wyszukiwanie gęste a rzadkie: kompromisy
  2. Implementacja wyszukiwania słów kluczowych BM25
  3. Reciprocal Rank Fusion do łączenia wyników
  4. Wyszukiwanie hybrydowe w Pinecone i pgvector
← Powrót do AI Engineering Academy