mirror of
https://github.com/qdrant/landing_page.git
synced 2026-10-03 01:48:32 +02:00
Apply reviewer suggestions to hybrid-queries
Move FormulaQuery from Hybrid Search into Multi-Stage Queries; soften weighted-RRF tuning prose; tighten the RRF-k intro. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
c0128a53bc
commit
a42055e416
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Hybrid Queries
|
||||
short_description: "Combine dense, sparse, and multivector queries in Qdrant with hybrid search, weighted RRF tuning, DBSF, and FormulaQuery custom scoring."
|
||||
description: "Run hybrid queries in Qdrant: fuse dense, sparse, and multivector results with RRF, DBSF, or FormulaQuery, and pick the right fusion method for your data."
|
||||
short_description: "Combine dense, sparse, and multivector queries in Qdrant with hybrid search, weighted RRF tuning, DBSF, and multi-stage rescoring with FormulaQuery."
|
||||
description: "Run hybrid queries in Qdrant: fuse dense, sparse, and multivector results with RRF or DBSF, layer custom scoring with FormulaQuery, and pick the right method for your data."
|
||||
weight: 15
|
||||
aliases:
|
||||
- ../hybrid-queries
|
||||
@@ -66,7 +66,7 @@ Here is an example of RRF for a query containing two prefetches against differen
|
||||
#### Setting RRF Constant k
|
||||
_Available as of v1.16.0_
|
||||
|
||||
To change the value of constant $k$ in the formula, use the dedicated `rrf` query.
|
||||
To set the constant $k$ in the formula:
|
||||
|
||||
{{< code-snippet path="/documentation/headless/snippets/query-points/hybrid-rrf-k/" >}}
|
||||
|
||||
@@ -81,9 +81,9 @@ Weights should be provided as an array of numbers, where each weight is applied
|
||||
|
||||
{{< code-snippet path="/documentation/headless/snippets/query-points/hybrid-rrf-weights/" >}}
|
||||
|
||||
Weights are a hyperparameter, not a free knob. A held-out eval is the most defensible way to set them.
|
||||
Weights are a configuration choice, not something you can tune arbitrarily. The most reliable way to set them is by testing on your data.
|
||||
|
||||
- **With an eval set (queries paired with known-relevant docs):** split your eval queries in two. Search the weight space on the first half, then report metrics on the second half (held out from the search). Reporting on the same set you tuned on inflates the result. The [Choosing a Fusion Method notebook](https://githubtocolab.com/qdrant/examples/blob/master/fusion-methods/Choosing_a_Fusion_Method.ipynb) provides a reusable `tune_rrf_weights` grid-search helper you can adapt to a train/val split. Random search and Bayesian optimization (Optuna, hyperopt) work equally well for two-retriever fusion.
|
||||
- **With an eval set (queries paired with known-relevant docs):** split your eval queries in two. Try different weights on the first half, then measure on the second half. Measuring on the same queries you tuned on inflates the result. The [Choosing a Fusion Method notebook](https://githubtocolab.com/qdrant/examples/blob/master/fusion-methods/Choosing_a_Fusion_Method.ipynb) provides a reusable `tune_rrf_weights` grid-search helper you can adapt to a train/val split.
|
||||
- **Without an eval set:** leave weights at the default `(1.0, 1.0)`. Hand-tuned weights without measurement are unlikely to beat the default reliably.
|
||||
|
||||
Retune when your retrievers change (new embedding model, new chunking), when your corpus drifts substantially, or on a fixed cadence with a fresh eval sample.
|
||||
@@ -104,32 +104,17 @@ DBSF is a reasonable choice when you trust your retrievers' raw scores to carry
|
||||
|
||||
{{< code-snippet path="/documentation/headless/snippets/query-points/hybrid-dbsf/" >}}
|
||||
|
||||
### Custom Fusion with FormulaQuery
|
||||
|
||||
_Available as of v1.14.0_
|
||||
|
||||
`FormulaQuery` lets you write the combining expression explicitly, using scores from prefetches (`$score`), payload fields, and built-in helpers like `ExpDecayExpression` or `GaussDecayExpression`. It is **not** a replacement for tuned weighted RRF. Writing `0.7 * $score[0] + 0.3 * $score[1]` over raw retriever scores reintroduces the same scale problem that breaks naive linear fusion. If the prefetches are themselves `rrf` or `dbsf`, the scores are already on comparable scales and a weighted formula sum works.
|
||||
|
||||
`FormulaQuery` is designed for layering ranking logic **on top of** a fused result: recency decay, popularity boosts, geo decay, or category-conditional multipliers. The canonical pattern is to fuse with RRF (or DBSF) in a prefetch, then wrap the prefetch in a `FormulaQuery` that uses `$score` and payload fields:
|
||||
|
||||
{{< code-snippet path="/documentation/headless/snippets/query-points/hybrid-formula-decay/" >}}
|
||||
|
||||
<aside role="status">Calibrate the decay weight against the scale of your fused <code>$score</code>. RRF scores are small (sums of <code>1/(k+rank)</code> terms), while decay functions return values in <code>[0, 1]</code>, so an unweighted decay term will dominate the fused score unless you multiply it by a smaller coefficient. Wrap the decay in <code>MultExpression(mult=[w, ...])</code> with a <code>w</code> tuned to your workload.</aside>
|
||||
|
||||
The [Choosing a Fusion Method notebook](https://githubtocolab.com/qdrant/examples/blob/master/fusion-methods/Choosing_a_Fusion_Method.ipynb) shows this pattern end-to-end with exponential decay on a `published_at` payload field. For full `FormulaQuery` and decay function syntax, see the [Search Relevance reference](/documentation/search/search-relevance/).
|
||||
|
||||
### Choosing a Fusion Method
|
||||
|
||||
| If you have... | Use |
|
||||
| --- | --- |
|
||||
| Business logic to apply after fusion (recency, boosts, geo) | RRF or DBSF in a prefetch, wrapped in `FormulaQuery` |
|
||||
| An eval set (queries with known-relevant docs) to tune on | Weighted RRF, with weights tuned on a train/val split |
|
||||
| Trust in your retrievers' raw scores and no eval set | DBSF |
|
||||
| Neither an eval set nor strong score priors | RRF (the safe default) |
|
||||
|
||||
For a deeper breakdown of when to prefer each, see the [FAQ on RRF vs. DBSF](/documentation/faq/qdrant-fundamentals/#when-should-i-use-reciprocal-rank-fusion-rrf-vs-distribution-based-score-fusion-dbsf-for-hybrid-search).
|
||||
For a deeper breakdown of when to prefer each, see the [FAQ on RRF vs. DBSF](/documentation/faq/qdrant-fundamentals/#when-should-i-use-reciprocal-rank-fusion-rrf-vs-distribution-based-score-fusion-dbsf-for-hybrid-search). To layer business logic (recency, popularity, geo) on top of a fused result, see [Custom scoring with FormulaQuery](#custom-scoring-with-formulaquery).
|
||||
|
||||
<aside role="status">A common request is "alpha-weighted linear combination of dense and sparse scores." This is unreliable without first normalizing the scores: dense (cosine, bounded) and sparse (BM25, unbounded) scores live on different scales that also shift per query, so a fixed alpha over raw scores tends to be dominated by whichever retriever has larger raw magnitudes on a given query. RRF sidesteps this by using ranks. DBSF sidesteps it by normalizing distributions. <code>FormulaQuery</code> can do it explicitly if you write the normalization yourself.</aside>
|
||||
<aside role="status">A common request is "alpha-weighted linear combination of dense and sparse scores." This is unreliable without first normalizing the scores: dense (cosine, bounded) and sparse (BM25, unbounded) scores live on different scales that also shift per query, so a fixed alpha over raw scores tends to be dominated by whichever retriever has larger raw magnitudes on a given query. RRF sidesteps this by using ranks. DBSF sidesteps it by normalizing distributions.</aside>
|
||||
|
||||
|
||||
## Multi-Stage Queries
|
||||
@@ -166,6 +151,18 @@ You can combine all of these techniques in a single query:
|
||||
|
||||
{{< code-snippet path="/documentation/headless/snippets/query-points/hybrid-rescoring-multistage/" >}}
|
||||
|
||||
### Custom Scoring with `FormulaQuery`
|
||||
|
||||
_Available as of v1.14.0_
|
||||
|
||||
A `FormulaQuery` lets you compose a final score from prefetch scores (`$score`), payload fields, and built-in helpers like `ExpDecayExpression` or `GaussDecayExpression`. The typical pattern is to fuse retrievers with RRF or DBSF in a prefetch, then wrap that prefetch in a `FormulaQuery` that layers ranking logic on top: recency decay, popularity boosts, geo decay, or category-conditional multipliers.
|
||||
|
||||
{{< code-snippet path="/documentation/headless/snippets/query-points/hybrid-formula-decay/" >}}
|
||||
|
||||
<aside role="status">Calibrate the decay weight against the scale of your fused <code>$score</code>. RRF scores are small (sums of <code>1/(k+rank)</code> terms), while decay functions return values in <code>[0, 1]</code>, so an unweighted decay term will dominate the fused score unless you multiply it by a smaller coefficient. Wrap the decay in <code>MultExpression(mult=[w, ...])</code> with a <code>w</code> tuned to your workload.</aside>
|
||||
|
||||
The [Choosing a Fusion Method notebook](https://githubtocolab.com/qdrant/examples/blob/master/fusion-methods/Choosing_a_Fusion_Method.ipynb) shows this pattern end-to-end with exponential decay on a `published_at` payload field. For full `FormulaQuery` and decay function syntax, see the [Search Relevance reference](/documentation/search/search-relevance/).
|
||||
|
||||
## Grouping
|
||||
|
||||
_Available as of v1.11.0_
|
||||
|
||||
Reference in New Issue
Block a user