Implementing Hybrid Search

Query
 ├── Dense embed ──→ semantic match (meaning)
 └── Sparse embed ─→ lexical match  (keywords)
                ↓
        weighted sum (alpha)
                ↓
            top-K results
      
ComponentProvides
DenseSemantic similarity
SparseTerm/keyword relevance
AlphaWeight between two signals

2. Setting Up Hybrid-Enabled Indexes

Example: Create Hybrid Index

pc.create_index(
    name="hybrid-docs",
    dimension=1536,
    metric="dotproduct",    # required
    spec=ServerlessSpec(cloud="aws", region="us-east-1"),
)

3. Generating Dense Embeddings

Example: OpenAI Dense

from openai import OpenAI
client = OpenAI()

resp = client.embeddings.create(
    model="text-embedding-3-small",
    input="vector databases",
)
dense = resp.data[0].embedding  # len=1536
ProviderModelDim
OpenAItext-embedding-3-small1536
Cohereembed-v31024
Pineconemultilingual-e5-large1024

4. Generating Sparse Embeddings

Example: BM25 Sparse

from pinecone_text.sparse import BM25Encoder

bm25 = BM25Encoder().fit(corpus)
sparse = bm25.encode_queries("vector databases")
# {"indices": [...], "values": [...]}

5. Combining Query Vectors

Example: Weighted Combination

def weight(dense, sparse, alpha):
    # alpha=1: pure dense; alpha=0: pure sparse
    hdense = [v * alpha for v in dense]
    hsparse = {
        "indices": sparse["indices"],
        "values": [v * (1 - alpha) for v in sparse["values"]],
    }
    return hdense, hsparse

dq, sq = weight(dense, sparse, alpha=0.7)
res = index.query(vector=dq, sparse_vector=sq, top_k=10)

6. Tuning Alpha Parameter

AlphaBehavior
1.0Pure semantic (dense only)
0.7–0.8Default for general RAG
0.5Balanced
0.2–0.3Keyword-heavy (code, IDs, jargon)
0.0Pure lexical (sparse only)

7. Upserting Hybrid Vectors

Example: Hybrid Upsert

index.upsert(vectors=[{
    "id": "doc1",
    "values": dense,
    "sparse_values": sparse_doc,
    "metadata": {"text": "..."},
}])

8. Understanding Hybrid Scoring Mechanism

StepFormula
Combined scorescore = dot(d_q, d_v) + dot(s_q, s_v)
Alpha pre-scalingApplied client-side to query vectors

9. Implementing BM25 Integration

Example: Fit + Encode

from pinecone_text.sparse import BM25Encoder

bm25 = BM25Encoder()
bm25.fit(corpus)
bm25.dump("bm25_params.json")
# later: BM25Encoder().load("bm25_params.json")

doc_sparse = bm25.encode_documents(["doc 1 text", "doc 2 text"])
q_sparse   = bm25.encode_queries("query text")

10. Using SPLADE Models

Example: SPLADE

from pinecone_text.sparse import SpladeEncoder

splade = SpladeEncoder()
sparse = splade.encode_queries("deep learning")
ModelProCon
BM25Fast, no GPUNo semantic expansion
SPLADELearned term expansionSlower, needs GPU

11. Optimizing Hybrid Performance

TipEffect
Cache sparse encoderAvoid repeated init
Prune sparseKeep top-100 terms
Co-locate encoder + indexLower RTT
Batch encodeGPU utilization

12. Benchmarking Hybrid vs Dense-Only

WorkloadBest Approach
General Q&A RAGDense or hybrid (alpha=0.7)
Code/jargon searchHybrid (alpha=0.3–0.5)
Exact identifier lookupSparse-heavy (alpha=0.2)
MultilingualDense (sparse term mismatch)