外观
本页尚未翻译
本页内容仍为英文原文,源自官方文档 reranking/index。中文翻译正在分批进行中。
Reranking Search Results
Choose a reranker, apply it to search candidates and evaluate relevance and latency.
Applies to: Embedded ✅, Enterprise ✅
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.
Run a complete RRF example in Python, TypeScript or Rust
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 asrerank_search.py, then runpython rerank_search.py. - TypeScript: Install with
npm install @lancedb/lancedb, save asrerank_search.mts, then runnode rerank_search.mtswith Node.js 22.18 or later. - Rust: Download and extract the complete Rust examples, then run
cargo run --locked --bin hybrid_searchfrom the extracted directory.
python
table = db.create_table(
"documents",
[
{
"id": 1,
"text": "A puppy playing in the park",
"category": "animals",
"vector": [1.0, 0.0],
},
{
"id": 2,
"text": "A dog resting at home",
"category": "animals",
"vector": [0.9, 0.1],
},
{
"id": 3,
"text": "A guide to growing tomatoes",
"category": "gardening",
"vector": [0.0, 1.0],
},
],
)
from lancedb.rerankers import RRFReranker
table.create_index("text", config=FTS())
rows = (
table.search(
query_type="hybrid", vector_column_name="vector", fts_columns="text"
)
.vector([1.0, 0.0])
.text("puppy")
.rerank(RRFReranker())
.limit(2)
.to_list()
)
print(rows)typescript
const lancedb = await import("@lancedb/lancedb");
const { mkdtemp, rm } = await import("node:fs/promises");
const { tmpdir } = await import("node:os");
const { join } = await import("node:path");
const uri = await mkdtemp(join(tmpdir(), "lancedb-search-"));
try {
const db = await lancedb.connect(uri);
const table = await db.createTable("documents", [
{
id: 1,
text: "A puppy playing in the park",
category: "animals",
vector: [1.0, 0.0],
},
{
id: 2,
text: "A dog resting at home",
category: "animals",
vector: [0.9, 0.1],
},
{
id: 3,
text: "A guide to growing tomatoes",
category: "gardening",
vector: [0.0, 1.0],
},
]);
await table.createIndex("text", { config: lancedb.Index.fts() });
const reranker = await lancedb.rerankers.RRFReranker.create();
const rows = await table
.vectorSearch([1.0, 0.0])
.fullTextSearch("puppy", { columns: ["text"] })
.rerank(reranker)
.limit(2)
.toArray();
console.log(rows);
} finally {
await rm(uri, { recursive: true, force: true });
}rust
use arrow_array::types::Float32Type;
use arrow_array::{FixedSizeListArray, Int64Array, RecordBatch, RecordBatchIterator, StringArray};
use arrow_schema::{DataType, Field, Schema};
use futures::TryStreamExt;
use lance_index::scalar::FullTextSearchQuery;
use lancedb::index::{Index, scalar::FtsIndexBuilder};
use lancedb::query::{ExecutableQuery, QueryBase};
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let directory = tempfile::tempdir()?;
let db = lancedb::connect(directory.path().to_str().unwrap())
.execute()
.await?;
let schema = Arc::new(Schema::new(vec![
Field::new("id", DataType::Int64, false),
Field::new("text", DataType::Utf8, false),
Field::new("category", DataType::Utf8, false),
Field::new(
"vector",
DataType::FixedSizeList(Arc::new(Field::new("item", DataType::Float32, true)), 2),
true,
),
]));
let batch = RecordBatch::try_new(
schema.clone(),
vec![
Arc::new(Int64Array::from(vec![1, 2, 3])),
Arc::new(StringArray::from(vec![
"A puppy playing in the park",
"A dog resting at home",
"A guide to growing tomatoes",
])),
Arc::new(StringArray::from(vec!["animals", "animals", "gardening"])),
Arc::new(
FixedSizeListArray::from_iter_primitive::<Float32Type, _, _>(
[[1.0, 0.0], [0.9, 0.1], [0.0, 1.0]]
.into_iter()
.map(|v| Some(v.into_iter().map(Some).collect::<Vec<_>>())),
2,
),
),
],
)?;
let reader = RecordBatchIterator::new(vec![Ok(batch)].into_iter(), schema);
let table = db
.create_table(
"documents",
Box::new(reader) as Box<dyn arrow_array::RecordBatchReader + Send>,
)
.execute()
.await?;
table
.create_index(&["text"], Index::FTS(FtsIndexBuilder::default()))
.execute()
.await?;
let query = table
.vector_search(&[1.0_f32, 0.0])?
.full_text_search(FullTextSearchQuery::new("puppy".into()).with_column("text".into())?)
.rerank(Arc::new(lancedb::rerankers::rrf::RRFReranker::default()))
.limit(2);
let rows: Vec<RecordBatch> = query.execute().await?.try_collect().await?;
println!("{rows:?}");
Ok(())
}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
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 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
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
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=Truetorerank_multivector(...)raises aValueErrorif any of the input result sets is missing the_rowidcolumn. Therefore, it's recommended to add.with_row_id(True)to everytable.search(...)call before reranking, or omitdeduplicate=Trueif you don't need it. RRFReranker.rerank_multivector(...)always requires_rowidon its inputs, regardless of thededuplicateflag.
Implement a custom ranking policy
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 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 options and examples
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.
Supported query types: hybrid search and 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_vectororrerank_ftson anRRFRerankerraisesNotImplementedError.
python
import tempfile
from pathlib import Path
import lancedb
from lancedb.embeddings import get_registry
from lancedb.pydantic import LanceModel, Vector
from lancedb.rerankers import RRFReranker
embedder = get_registry().get("sentence-transformers").create()
db = lancedb.connect(str(Path(tempfile.mkdtemp()) / "rrf-rerank"))
class Schema(LanceModel):
text: str = embedder.SourceField()
vector: Vector(embedder.ndims()) = embedder.VectorField()
data = [
{"text": "hello world"},
{"text": "goodbye world"},
]
tbl = db.create_table("test", schema=Schema)
tbl.add(data)
reranker = RRFReranker()
# Run hybrid search with a reranker
tbl.create_fts_index("text", replace=True)
result = (
tbl.search("hello", query_type="hybrid").rerank(reranker=reranker).to_list()
)Python 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
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 for a full example.
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
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 options (deprecated)
Note: This reranker is deprecated. Use
RRFRerankerto 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.
python
import tempfile
from pathlib import Path
import lancedb
from lancedb.embeddings import get_registry
from lancedb.pydantic import LanceModel, Vector
from lancedb.rerankers import LinearCombinationReranker
embedder = get_registry().get("sentence-transformers").create()
db = lancedb.connect(str(Path(tempfile.mkdtemp()) / "linear-combination-rerank"))
class Schema(LanceModel):
text: str = embedder.SourceField()
vector: Vector(embedder.ndims()) = embedder.VectorField()
data = [
{"text": "hello world"},
{"text": "goodbye world"},
]
tbl = db.create_table("test", schema=Schema)
tbl.add(data)
reranker = LinearCombinationReranker()
# Run hybrid search with a reranker
tbl.create_fts_index("text", replace=True)
result = (
tbl.search("hello", query_type="hybrid").rerank(reranker=reranker).to_list()
)Python 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
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
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 options and examples
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.
python
import tempfile
from pathlib import Path
import lancedb
from lancedb.embeddings import get_registry
from lancedb.pydantic import LanceModel, Vector
from lancedb.rerankers import MRRReranker
embedder = get_registry().get("sentence-transformers").create()
db = lancedb.connect(str(Path(tempfile.mkdtemp()) / "mrr-rerank"))
class Schema(LanceModel):
text: str = embedder.SourceField()
vector: Vector(embedder.ndims()) = embedder.VectorField()
data = [
{"text": "hello world"},
{"text": "goodbye world"},
]
tbl = db.create_table("test", schema=Schema)
tbl.add(data)
reranker = MRRReranker(weight_vector=0.7, weight_fts=0.3)
# Run hybrid search with a reranker
tbl.create_fts_index("text", replace=True)
result = (
tbl.search("hello", query_type="hybrid").rerank(reranker=reranker).to_list()
)
# Run multivector search across multiple vector columns
rs1 = tbl.search("hello").limit(10).with_row_id(True).to_arrow()
rs2 = tbl.search("greeting").limit(10).with_row_id(True).to_arrow()
combined = MRRReranker().rerank_multivector([rs1, rs2])Python 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
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
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
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 options and examples
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.
python
import tempfile
from pathlib import Path
import lancedb
from lancedb.embeddings import get_registry
from lancedb.pydantic import LanceModel, Vector
from lancedb.rerankers import CrossEncoderReranker
embedder = get_registry().get("sentence-transformers").create()
db = lancedb.connect(str(Path(tempfile.mkdtemp()) / "cross-encoder-rerank"))
class Schema(LanceModel):
text: str = embedder.SourceField()
vector: Vector(embedder.ndims()) = embedder.VectorField()
data = [
{"text": "hello world"},
{"text": "goodbye world"},
]
tbl = db.create_table("test", schema=Schema)
tbl.add(data)
reranker = CrossEncoderReranker()
# Run vector search with a reranker
result = tbl.search("hello").rerank(reranker=reranker).to_list()
# Build the full-text index before either FTS or hybrid search.
tbl.create_fts_index("text", replace=True)
# Run FTS search with a reranker
result = tbl.search("hello", query_type="fts").rerank(reranker=reranker).to_list()
# Run hybrid search with a reranker
result = (
tbl.search("hello", query_type="hybrid").rerank(reranker=reranker).to_list()
)Python 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
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
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
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
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
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
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
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
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
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
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 to1.0.LinearCombinationReranker(weight=0.7, fill=1.0): blends vector and FTS scores directly;fillcontrols 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
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 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
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
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
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.