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 pxtfrom pixeltable.functions.huggingface import sentence_transformerTableModel = pxt.model_base()text_embed = sentence_transformer.using(model_id='all-MiniLM-L6-v2')class Notes(TableModel, name='notes'):title: pxt.Stringcontent: pxt.Stringcategory: 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.querydef 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
| Dense plus metadata | BM25 plus dense fusion | |
|---|---|---|
| Signals | Embedding score and structured filters | Lexical score fused with embedding score |
| Where it runs | The table that holds the passage | A search engine of objects and vectors |
| Typical API | where() then similarity(string=) | query.hybrid with alpha |
| Choose it when | Tenant, date, or category must constrain hits | Keyword 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 pxtfrom pixeltable.functions.huggingface import sentence_transformerTableModel = pxt.model_base()text_embed = sentence_transformer.using(model_id='all-MiniLM-L6-v2')class Notes(TableModel, name='notes'):content: pxt.Stringcategory: pxt.String__indexes__ = [pxt.EmbeddingIndex(content, string_embed=text_embed),]@pxt.querydef 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.