How to

How do I do hybrid search in Python?

Hybrid search combines more than one signal. The usual pair is dense similarity plus a structured filter so a semantic hit still respects the row metadata. BM25-plus-dense fusion is a different product: a dedicated search engine that mixes lexical and vector scores. You need an index; you need that engine only when fusion is the contract.

Updated · Part of How do I ... in Python?

Before you start

About 15 min.

  • Python 3.11+
  • pip install -U 'pixeltable[serve]'
  • pxt init in the project root
  • sentence-transformers

1. Keep the vector and the filter on one row

Hybrid search fails when the embedding lives in one store and the tenant id lives in another. Put content, category, and the index on the same table.

import pixeltable as pxt
from pixeltable.functions.huggingface import sentence_transformer
TableModel = pxt.model_base()
text_embed = sentence_transformer.using(model_id='all-MiniLM-L6-v2')
class Notes(TableModel, name='notes'):
title: pxt.String
content: pxt.String
category: pxt.String
__indexes__ = [
pxt.EmbeddingIndex(content, string_embed=text_embed),
]

2. Filter metadata, then rank by similarity

where() restricts the rows. similarity(string=) ranks the rest. That is hybrid search as dense plus structured predicates, documented as combining semantic search with metadata filters.

@pxt.query
def hybrid_search(query: str, category: str, limit: int = 5):
sim = Notes.content.similarity(string=query)
return (
Notes.where(Notes.category == category)
.order_by(sim, asc=False)
.limit(limit)
.select(Notes.title, Notes.category, Notes.content, sim)
)

3. Keep BM25 fusion on the engine that owns it

Weaviate query.hybrid fuses BM25 with dense similarity (alpha balances the two). That is a different contract. Do not invent a BM25 API on the table. Hand vectors to Weaviate when that fusion is the product.

# Pixeltable: dense similarity + metadata on the source row (this file).
# Weaviate: query.hybrid(query=..., alpha=0.5) on a collection.
# See /compare/pixeltable-vs-weaviate for that split.

Two meanings of hybrid search

Two meanings of hybrid search
Dense plus metadataBM25 plus dense fusion
SignalsEmbedding score and structured filtersLexical score fused with embedding score
Where it runsThe table that holds the passageA search engine of objects and vectors
Typical APIwhere() then similarity(string=)query.hybrid with alpha
Choose it whenTenant, date, or category must constrain hitsKeyword recall plus meaning is the product

Where Pixeltable fits

Pixeltable does hybrid search as similarity plus where() on the source row. It does not ship BM25-plus-dense fusion. Keep Weaviate when that engine is the serving contract; the compare page states the split. A separate vector database is still optional for ordinary RAG.

import pixeltable as pxt
from pixeltable.functions.huggingface import sentence_transformer
TableModel = pxt.model_base()
text_embed = sentence_transformer.using(model_id='all-MiniLM-L6-v2')
class Notes(TableModel, name='notes'):
content: pxt.String
category: pxt.String
__indexes__ = [
pxt.EmbeddingIndex(content, string_embed=text_embed),
]
@pxt.query
def hybrid_search(query: str, category: str, limit: int = 5):
sim = Notes.content.similarity(string=query)
return (
Notes.where(Notes.category == category)
.order_by(sim, asc=False)
.limit(limit)
.select(Notes.content, Notes.category, sim)
)

Questions

Does Pixeltable implement BM25 hybrid fusion?
No. Hybrid search here means dense similarity plus metadata filters on the same table. BM25-plus-dense fusion is Weaviate query.hybrid. Pixeltable vs Weaviate is the page for that engine.
Can I filter before ranking?
Yes. where() restricts rows; order_by on similarity ranks the rest. That is the documented pattern for combining semantic search with metadata filters.
Is hybrid search the same as two embedding models?
Not necessarily. Two models on two columns are two indexes you query separately. Hybrid as used here is one dense index plus structured predicates. CLIP plus a caption index is a different, valid pattern.