---
title: "Reranking Search Results"
sidebarTitle: "Reranking"
description: "Choose a reranker, apply it to search candidates and evaluate relevance and latency."
icon: "sort-amount-down"
keywords: ["re-ranking", "reranker", "rerank"]
availability:
oss: available
enterprise: available
summary: >-
Reranking is available with OSS and Enterprise.
---
import {
PyRerankingLinearCombinationUsage,
PyRerankingRrfUsage,
PyRerankingMrrUsage,
PyRerankingCrossEncoderUsage,
} from '/snippets/integrations.mdx';
import { PyRerankingCohereUsage } from '/snippets/provider_examples.mdx';
import { PyHybridSearch, TsHybridSearchBody, TsHybridSearchTail } from '/snippets/search_examples.mdx';
import { RsExampleBody as RsHybridSearchBody, RsExampleTail as RsHybridSearchTail } from '/snippets/search_hybrid_search.mdx';
Reranking changes the order of records returned by an initial search. It can
combine several ranked lists or use a model to score each candidate against
the query. It cannot recover records that the initial searches did not retrieve.
## Choose a reranker
- **Rank fusion:** start with RRF for hybrid search. It combines vector and
full-text rankings without a model or a provider API call.
- **Model-based reranking:** try a cross encoder or hosted reranking model
when you need a different relevance judgment over the retrieved text.
Compare the relevance gain with added latency and provider cost.
- **Custom ranking:** implement the reranker interface when your application
needs its own scoring policy.
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 `rerank_search.py`, then run `python rerank_search.py`.
- **TypeScript:** Install with `npm install @lancedb/lancedb`, save as `rerank_search.mts`, then run `node rerank_search.mts` with Node.js 22.18 or later.
- **Rust:** Download and extract the [complete Rust examples](/static/code/search-rust.zip), then run `cargo run --locked --bin hybrid_search` from the extracted directory.
{PyHybridSearch}
{TsHybridSearchBody + "\n" + TsHybridSearchTail}
{RsHybridSearchBody + "\n" + RsHybridSearchTail}
The result includes `_relevance_score` in descending order. RRF is already the
hybrid-search default; passing it explicitly shows where to substitute another
reranker. These scores express a ranking, not a probability of correctness.
## Supported rerankers {#supported-rerankers}
Use rank fusion to combine search results without a model, or a model-based
reranker to score each document against the query. Reranking runs in your
application after retrieval, including when the table is in Enterprise.
| Reranker | Method | SDK |
|:---------|:-------|:----|
| `RRFReranker` | Reciprocal rank fusion | Python, TypeScript, Rust |
| `MRRReranker` | Weighted reciprocal rank fusion | Python |
| `LinearCombinationReranker` (deprecated) | Blend normalized scores | Python |
| `CrossEncoderReranker` | Score query-document pairs with a local cross encoder | Python |
| `ColbertReranker`, `AnswerdotaiRerankers` | Local model reranking | Python |
| Provider rerankers | Call a hosted model, such as Cohere, Jina or Voyage AI | Python |
Local model rerankers need their model runtime packages. Hosted rerankers need
provider credentials and make API calls. See [reranking integrations](/integrations/reranking)
for provider setup and model selection; specify a model supported by your provider.
Rerankers add `_relevance_score` and return rows ordered by descending relevance. Python rerankers
accept a `return_score` option; use `"all"` where supported to keep the
original vector distance or FTS score columns for debugging. Model-based rerankers read from
`column="text"` by default, so either return that column in the search results or pass a different
column.
`refine_factor` expands the vector index's internal candidate pool and rescores
those candidates using full vectors before keeping the requested limit. It
can improve the candidates available for reranking, but it does not multiply
the final result count. Compare retrieval recall and total query latency.
**SDK coverage differs across languages**
The provider-specific rerankers in the table above
(`CohereReranker`, `CrossEncoderReranker`, `ColbertReranker`, and others under `lancedb.rerankers`)
are currently **Python-only**. The TypeScript and Rust SDKs currently expose hybrid reranking through
the generic `Reranker` interface (`rerankHybrid` / `rerank_hybrid`) and the built-in `RRFReranker`.
In TypeScript, create the built-in RRF reranker with `await RRFReranker.create(k)`. To use a
model-based reranker from TypeScript or Rust, you must implement the hybrid reranker interface
yourself.
### Multi-vector reranking {#multi-vector-reranking}
Use `rerank_multivector()` to combine result sets from several vector queries.
The example below assumes `table` has three vector columns with embedding
metadata and uses a cross encoder to rerank their candidates.
```python Python icon="python"
from lancedb.rerankers import CrossEncoderReranker
reranker = CrossEncoderReranker()
query = "hello"
# `deduplicate=True` requires `_rowid` on every input result set,
# so call `.with_row_id(True)` on each search before passing it in.
res1 = table.search(query, vector_column_name="vector").limit(3).with_row_id(True)
res2 = table.search(query, vector_column_name="text_vector").limit(3).with_row_id(True)
res3 = table.search(query, vector_column_name="meta_vector").limit(3).with_row_id(True)
reranked = reranker.rerank_multivector([res1, res2, res3], deduplicate=True)
```
- Passing `deduplicate=True` to `rerank_multivector(...)` raises a `ValueError` if any of the
input result sets is missing the `_rowid` column. Therefore, it's recommended to add `.with_row_id(True)` to every
`table.search(...)` call before reranking, or omit `deduplicate=True` if you don't need it.
- `RRFReranker.rerank_multivector(...)` always requires `_rowid` on its inputs, regardless of
the `deduplicate` flag.
## Implement a custom ranking policy {#creating-custom-rerankers}
Python custom rerankers implement `rerank_hybrid()` and can optionally implement
`rerank_vector()` and `rerank_fts()`. TypeScript and Rust expose hybrid reranking
interfaces. See [the interface requirements](#custom) before implementing one.
## Algorithm options
The following sections describe the Python constructor options and examples.
Provider and local-model examples require their additional packages and model
or credential setup. They are separate programs, not steps to concatenate.
## Reciprocal rank fusion {#rrf}
**Reciprocal Rank Fusion (RRF)** is a model-free way to merge several ranked result lists into a
single ordering. Rather than comparing raw similarity scores (which aren't directly comparable
across, say, a vector search and a full-text search), RRF looks only at each document's *rank
position* in each list. It scores every document with the formula `1 / (rank + K)`, sums those
contributions across the lists, and re-sorts by the total. Documents that rank highly in more than
one search rise to the top. Because there's no model to load or call, it's fast and cheap, which is
why it's the default reranker for LanceDB hybrid search. The implementation follows the
[Cormack et al. paper](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf).
> **Supported query types:** hybrid search and [multi-vector reranking](/reranking#multi-vector-reranking).
> Because RRF fuses two or more ranked lists, it can't rerank a single vector or full-text result set
> on its own. Calling `rerank_vector` or `rerank_fts` on an `RRFReranker` raises `NotImplementedError`.
{PyRerankingRrfUsage}
### Python arguments {#rrf-accepted-arguments}
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `K` | `int` | `60` | A constant used in the RRF formula (default is 60). Larger values reduce the difference in contribution between nearby rank positions. |
| `return_score` | `str` | `"relevance"` | Options are "relevance" or "all". The type of score to return. If "relevance", will return only the `_relevance_score`. If "all", will return all scores from the vector and FTS search along with the relevance score. |
`K` must be greater than `0`. In TypeScript, construct the built-in reranker with
`await RRFReranker.create(k)` before passing it to `.rerank(...)`.
### Multi-vector reranking {#rrf-multi-vector-reranking}
`RRFReranker` can also fuse the results of several vector searches with `rerank_multivector`, applying
the same rank-fusion algorithm across more than two lists. Every input result set must include the
`_rowid` column, so add `.with_row_id(True)` to each `table.search(...)` call before reranking,
otherwise the call raises a `ValueError`. See [multi-vector reranking](/reranking#multi-vector-reranking)
for a full example.
### Supported Scores for each query type {#rrf-supported-scores-for-each-query-type}
You can specify the type of scores you want the reranker to return. The following are the supported scores for each query type:
#### Hybrid Search {#rrf-hybrid-search}
|`return_score`| Status | Description |
| --- | --- | --- |
| `relevance` | ✅ Supported | Keeps `_relevance_score` as the score column alongside the selected data columns. |
| `all` | ✅ Supported | Returned rows have vector(`_distance`) and FTS(`_score`) along with Hybrid Search score(`_relevance_score`). |
## Linear combination {#linear-combination}
> **Note:** This reranker is deprecated. Use `RRFReranker` to combine vector and full-text rankings.
The Linear Combination Reranker combines the results of semantic and full-text search using a linear combination of the scores. The weights for the linear combination can be specified, and defaults to 0.7, i.e, 70% weight for semantic search and 30% weight for full-text search.
> **Note:** Supported query type: Hybrid search only.
{PyRerankingLinearCombinationUsage}
### Python arguments {#linear-combination-accepted-arguments}
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `weight` | `float` | `0.7` | The weight to use for the semantic search score. The weight for the full-text search score is `1 - weight`. |
| `fill` | `float` | `1.0` | Score used when a result is missing from one side of the hybrid query. The default strongly penalizes missing vector or FTS matches. |
| `return_score` | `str` | `"relevance"` | Options are "relevance" or "all". The type of score to return. If "relevance", returns only `_relevance_score`. If "all", returns all scores from the vector and FTS search along with the relevance score. |
`weight` must be between `0` and `1`. If either the vector or FTS side returns no rows, the reranker
returns the non-empty side with `_relevance_score` attached rather than failing.
### Supported Scores for each query type {#linear-combination-supported-scores-for-each-query-type}
You can specify the type of scores you want the reranker to return. The following are the supported scores for each query type:
#### Hybrid Search {#linear-combination-hybrid-search}
|`return_score`| Status | Description |
| --- | --- | --- |
| `relevance` | ✅ Supported | Keeps `_relevance_score` alongside the selected data columns |
| `all` | ✅ Supported | Results have vector(`_distance`) and FTS(`_score`) along with Hybrid Search score(`_relevance_score`) |
## MRR {#mrr}
This reranker uses the Mean Reciprocal Rank (MRR) algorithm to combine and rerank search results from vector and full-text search. You can use this reranker by passing `MRRReranker()` to the `rerank()` method. The MRR algorithm calculates the average of reciprocal ranks across different search results, providing a balanced way to merge results from multiple ranking systems.
> **Note:** Supported query types: Hybrid and Multivector search.
{PyRerankingMrrUsage}
### Python arguments {#mrr-accepted-arguments}
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `weight_vector` | `float` | `0.5` | Weight for vector search results (0.0 to 1.0). |
| `weight_fts` | `float` | `0.5` | Weight for FTS search results (0.0 to 1.0). |
| `return_score` | `str` | `"relevance"` | Options are "relevance" or "all". The type of score to return. If "relevance", will return only the `_relevance_score`. If "all", will return all scores from the vector and FTS search along with the relevance score. |
**Note:** `weight_vector` + `weight_fts` must equal 1.0.
For multivector reranking, input result sets need `_rowid` so LanceDB can identify the same row
across the ranked lists. Add `.with_row_id(True)` to each vector search before passing the results
to the reranker.
### Supported Scores for each query type {#mrr-supported-scores-for-each-query-type}
You can specify the type of scores you want the reranker to return. The following are the supported scores for each query type:
#### Hybrid Search {#mrr-hybrid-search}
|`return_score`| Status | Description |
| --- | --- | --- |
| `relevance` | ✅ Supported | Keeps `_relevance_score` alongside the selected data columns. |
| `all` | ✅ Supported | Results have vector(`_distance`) and FTS(`_score`) along with Hybrid Search score(`_relevance_score`). |
#### Multivector Search {#mrr-multivector-search}
|`return_score`| Status | Description |
| --- | --- | --- |
| `relevance` | ✅ Supported | Keeps `_relevance_score` alongside the selected data columns. |
| `all` | ✅ Supported | Results have vector distances from all searches along with `_relevance_score`. |
## Cross encoder {#cross-encoder}
This reranker uses Cross Encoder models from sentence-transformers to rerank the search results. You can use this reranker by passing `CrossEncoderReranker()` to the `rerank()` method.
> **Note:** Supported query types: Hybrid, Vector, and FTS.
{PyRerankingCrossEncoderUsage}
### Python arguments {#cross-encoder-accepted-arguments}
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| `model_name` | `str` | `"cross-encoder/ms-marco-TinyBERT-L-6"` | The name of the reranker model to use.|
| `column` | `str` | `"text"` | The name of the column to use as input to the cross encoder model. |
| `device` | `str` | `None` | The device to use for the cross encoder model. If None, will use "cuda" if available, otherwise "cpu". |
| `trust_remote_code` | `bool` | `True` | Passed to Sentence Transformers model loading. Set this to `False` when you only want to load models that do not require custom repository code. |
| `return_score` | `str` | `"relevance"` | Options are "relevance" or "all". The type of score to return. If "relevance", returns only `_relevance_score`. If "all" is supported, returns relevance score along with the vector and/or FTS scores depending on query type. |
The reranker loads the model locally through `sentence-transformers`, so install the local model
runtime dependencies you need, such as PyTorch and any device-specific acceleration packages.
### Supported Scores for each query type {#cross-encoder-supported-scores-for-each-query-type}
You can specify the type of scores you want the reranker to return. The following are the supported scores for each query type:
#### Hybrid Search {#cross-encoder-hybrid-search}
|`return_score`| Status | Description |
| --- | --- | --- |
| `relevance` | ✅ Supported | Keeps `_relevance_score` alongside the selected data columns. |
| `all` | ✅ Supported | Keeps `_distance`, `_score` and `_relevance_score` alongside the data columns. |
#### Vector Search {#cross-encoder-vector-search}
|`return_score`| Status | Description |
| --- | --- | --- |
| `relevance` | ✅ Supported | Keeps `_relevance_score` alongside the selected data columns. |
| `all` | ✅ Supported | Results have vector(`_distance`) along with Hybrid Search score(`_relevance_score`). |
#### FTS Search {#cross-encoder-fts-search}
|`return_score`| Status | Description |
| --- | --- | --- |
| `relevance` | ✅ Supported | Keeps `_relevance_score` alongside the selected data columns. |
| `all` | ✅ Supported | Results have FTS(`_score`) along with Hybrid Search score(`_relevance_score`). |
## Building a custom reranker {#custom}
You can build your own custom reranker in LanceDB by subclassing the base `Reranker` class. At a
minimum, you need to implement `rerank_hybrid()`, which is the logic that combines vector and
full-text search results. Beyond that, you can optionally implement `rerank_vector()` and
`rerank_fts()` if you want your reranker to also handle pure vector or pure full-text searches.
Implement the hooks your query paths call. The base class raises
`NotImplementedError` for vector-only or FTS-only reranking unless you
implement those methods.
The Python base class exposes hybrid, vector-only, and FTS-only rerank hooks. TypeScript and Rust
currently expose the custom reranker interface for hybrid reranking. In Rust, a custom reranker must
also satisfy the trait bounds `Debug + Send + Sync`.
### Interface {#custom-interface}
The `Reranker` base interface comes with a `merge_results()` method that can be used to combine the
results of semantic and full-text search. This is a vanilla merging algorithm that simply concatenates
the results and removes the duplicates without taking the scores into consideration. It only keeps the
first copy of the row encountered. This works well in cases that don't require the scores of semantic
and full-text search to combine the results. If you want to use the scores or want to support
`return_score="all"`, you'll need to implement your own merging algorithm. The base
`return_score` option accepts only `"relevance"` and `"all"`.
Whichever methods you override, your reranker has one job on the way out: attach a
`_relevance_score` column with the most relevant rows at the top. LanceDB will reject the result
if that column is missing, and downstream `.limit(...)` calls trust the order you return, so
sort descending before handing the table back.
For vector-only reranking in Python, pass a text query to `.rerank(..., query_string="...")`.
The vector query itself may be numeric, but `rerank_vector(query, vector_results)` still receives
a string query so your reranker can score each candidate against the user's text intent.
The method contract is:
| Method | Input | Required output |
|:-------|:------|:----------------|
| `rerank_hybrid` | Query text, vector results and FTS results | A merged Arrow table ordered by descending `_relevance_score` |
| `rerank_vector` | Query text and vector results | An Arrow table ordered by descending `_relevance_score` |
| `rerank_fts` | Query text and FTS results | An Arrow table ordered by descending `_relevance_score` |
Implement only the single-search hooks your application uses. The base class
raises `NotImplementedError` for those hooks until they are implemented.
### Extend an existing reranker {#custom-example}
This class definition extends `CohereReranker` to exclude text patterns after
reranking. It requires the Cohere package and credentials when instantiated.
It can return fewer results because exclusions happen after retrieval; use a
query predicate instead when a metadata filter expresses the same restriction.
```python Python icon="python"
from typing import List, Union
import pyarrow as pa
from lancedb.rerankers import CohereReranker
class ModifiedCohereReranker(CohereReranker):
def __init__(self, filters: Union[str, List[str]], **kwargs):
super().__init__(**kwargs)
filters = filters if isinstance(filters, list) else [filters]
self.filters = filters
def rerank_hybrid(self, query: str, vector_results: pa.Table, fts_results: pa.Table)-> pa.Table:
combined_result = super().rerank_hybrid(query, vector_results, fts_results)
df = combined_result.to_pandas()
for filter in self.filters:
df = df.query("not text.str.contains(@filter)")
return pa.Table.from_pandas(df)
def rerank_vector(self, query: str, vector_results: pa.Table)-> pa.Table:
vector_results = super().rerank_vector(query, vector_results)
df = vector_results.to_pandas()
for filter in self.filters:
df = df.query("not text.str.contains(@filter)")
return pa.Table.from_pandas(df)
def rerank_fts(self, query: str, fts_results: pa.Table)-> pa.Table:
fts_results = super().rerank_fts(query, fts_results)
df = fts_results.to_pandas()
for filter in self.filters:
df = df.query("not text.str.contains(@filter)")
return pa.Table.from_pandas(df)
```
The reranker receives and returns PyArrow tables. Use `to_pandas()` for a
DataFrame transformation and `pa.Table.from_pandas()` to convert the result
back, preserving `_relevance_score` and the desired row order.
## Evaluate relevance, latency and cost {#eval}
Compare rerankers on the same candidate inputs so you can distinguish ranking
changes from retrieval changes. For hybrid retrieval, prepare the text index
and query vectors before collecting those candidates.
### Reranking strategies {#eval-reranking-strategies}
Compare these approaches:
- **Rank fusion:** RRF and MRR combine rank positions from multiple result lists.
- **Score blending:** linear combination mixes normalized vector and text scores.
- **Model-based reranking:** a model scores each query-document pair.
Hybrid search uses RRF by default. You do not need to call `rerank()` to select
it. Pass a reranker when you want a different algorithm or configuration.
By default, rerankers return `_relevance_score`. Pass `return_score="all"` when a reranker
supports it, and you also need the original vector or FTS scores for debugging.
Evaluation code can rely on returned rows being ordered by descending `_relevance_score`. Empty
reranked result sets still include the `_relevance_score` column.
The synchronous Python hybrid `rerank(...)` method also accepts a `normalize` argument that controls how the raw
vector and FTS scores are made comparable before reranking:
- `normalize="score"` (the default); normalizes the raw vector and FTS scores directly.
- `normalize="rank"`; converts each result list to ranks first, then normalizes.
Normalization matters for algorithms that use raw scores, such as linear
combination. RRF uses rank positions instead. Compare normalization settings
only when the chosen reranker uses those scores.
For score-based evaluation, the main built-in knobs are:
- `RRFReranker(K=60)`: rank fusion with a positive smoothing constant.
- `MRRReranker(weight_vector=0.5, weight_fts=0.5)`: weighted reciprocal rank fusion where the weights must sum to `1.0`.
- `LinearCombinationReranker(weight=0.7, fill=1.0)`: blends vector and FTS scores directly; `fill` controls how strongly to penalize a result missing from one side.
A result on one dataset does not establish which reranker will work best on
another. Include representative queries and measure both retrieval quality
and ranking quality.
### Historical evaluation example {#eval-example-evaluation}
The tables below preserve an earlier experiment comparing multiple rerankers on
~800 hybrid search queries. This is a modified version of an evaluation script by
[LlamaIndex](https://github.com/run-llama/finetune-embedding/blob/main/evaluate.ipynb) that measures
hit-rate \@ top-k. The recorded results do not establish performance for
current models, SDK releases or your dataset; treat them as an example of an
evaluation format.
#### Using OpenAI `text-embedding-ada-002` {#eval-using-openai-text-embedding-ada-002}
Vector Search baseline: **0.64**
| Reranker | Top-3 | Top-5 | Top-10 |
| --- | --- | --- | --- |
| Linear Combination | `0.73` | `0.74` | `0.85` |
| Cross Encoder | `0.71` | `0.70` | `0.77` |
| Cohere | `0.81` | `0.81` | `0.85` |
| ColBERT | `0.68` | `0.68` | `0.73` |
#### Using OpenAI `text-embedding-3-small` {#eval-using-openai-text-embedding-3-small}
Vector Search baseline: **0.59**
| Reranker | Top-3 | Top-5 | Top-10 |
| --- | --- | --- | --- |
| Linear Combination | `0.68` | `0.70` | `0.84` |
| Cross Encoder | `0.72` | `0.72` | `0.79` |
| Cohere | `0.79` | `0.79` | `0.84` |
| ColBERT | `0.70` | `0.70` | `0.76` |
### Compare results on your workload {#eval-conclusion}
Build a set of representative queries with known relevant records. Compare
retrieval alone with each reranker using the same candidate inputs and result
limit. Record a relevance metric such as recall or nDCG alongside latency and
provider cost. Include queries with no useful matches and queries containing
exact names or identifiers.
If the relevant records never reach the reranker, adjust retrieval first.
Then change one ranking setting at a time and retain the configuration that
meets your application's quality and latency requirements.