Skip to main content
Applies to: Embedded ✅, Enterprise ✅ Hybrid search combines vector similarity with keyword matching. It is useful when a query needs both: for example, finding products with a particular model number whose descriptions match what the customer is looking for. LanceDB runs a vector search and a full-text search, then merges their results with a reranker. Reciprocal Rank Fusion (RRF) is the default. It combines the positions of records in the two ranked lists. Before you query, store vectors and create an FTS index on the text column.
These examples use three sample records and hand-written vectors, so no embedding service is needed. Each program creates a temporary local table and removes it when finished.
  • Python: Install with pip install lancedb, save as hybrid_search.py, then run python hybrid_search.py.
  • TypeScript: Install with npm install @lancedb/lancedb, save as hybrid_search.mts, then run node hybrid_search.mts with Node.js 22.18 or later.
  • Rust: Download and extract the complete Rust examples, then run cargo run --locked --bin hybrid_search from the extracted directory.
The puppy record ranks first because both searches place it first. The query returns _relevance_score, with higher values first. This small example shows the mechanics; evaluate retrieval quality with embeddings and queries from your own application.

1. Setup

The following walkthrough uses Sentence Transformers in Python and OpenAI in TypeScript. Install the corresponding provider packages before starting. Python downloads a local model; TypeScript calls OpenAI. Install lancedb sentence-transformers pandas for Python, or @lancedb/lancedb apache-arrow openai for TypeScript. Set your OpenAI API key as described in the OpenAI embedding setup. Run the steps for your chosen language in order, choosing one connection option.

2. Connect to LanceDB

Choose a LanceDB Embedded connection or use your Enterprise connection details. Embedded
Enterprise Replace the placeholders with your Enterprise database URI, API key, region and endpoint. In application code, load credentials from environment variables or a secrets manager.

3. Configure Embedding Model

Configure the model used for both document and query embeddings.

4. Create Table and Schema

Define the data structure for your documents, including both the text content and its vector representation.

5. Add Data

Insert sample documents into your table, which will be used for both semantic and keyword search.

6. Build Full Text Index

Create a full-text search index on the text column to enable keyword-based search capabilities.

7. Set Reranker [Optional]

RRF is the default. The examples below configure it explicitly. Other rerankers and their language support are described in Reranking.
Perform a hybrid search query that combines semantic similarity with keyword matching, using the specified reranker to merge and rank the results.

9. Supply a vector and text separately

You can supply the vector and text separately. The vector must use the same model and dimensions as the stored embeddings. This example reuses the model configured above; the complete example at the top also demonstrates this pattern in TypeScript and Rust.

Control the candidate results

Use a result limit, metadata filters and vector distance bounds to control which candidates are available to the reranker. The examples below show each option on the query builder.
Set limit() to the number of results your application needs. The default is 10. A larger candidate set can help a reranker find relevant records, but adds work.

Returning row IDs

Pass with_row_id(True) (Python) or withRowId() (TypeScript) to include the internal _rowid column in the results. This is useful for joining hybrid results back to a primary table, or for deduping across multiple queries:

Bounding vector distance

distance_range(lower, upper) (Python) and distanceRange(lower, upper) (TypeScript) constrain the vector half of the hybrid query to the half-open interval [lower, upper). This is helpful when you want to cap how far semantic candidates can drift from the query vector before reranking:
Either bound can be omitted to leave that side unbounded.

Prefilter vs. postfilter

A hybrid query applies its metadata filter to both the vector and full-text searches. Prefiltering, the default, restricts eligible rows before retrieval. Postfiltering removes candidates after retrieval and can leave fewer results than requested. See Filtering.
In Python, use explain_plan on a hybrid query to see whether the filter pushed into the scan or ran as a separate FilterExec step.

Choose and evaluate a reranker

Start with RRF, then compare alternatives on representative queries. Review which relevant rows reach the candidate set before evaluating how a reranker orders them. Record latency as well as relevance: increasing candidate counts or applying a model-based reranker adds work. Reranking runs in the application for both Embedded and Enterprise connections. See Reranking for model-free fusion, model-based rerankers, custom implementations and score interpretation. In synchronous Python, .rerank(...) accepts a reranker and a normalize option ("score" or "rank"). Normalization matters for methods that blend raw scores; RRF uses rank positions. These are Python query options, not a shared signature across every SDK.