Score ranker processor
The score-ranker-processor is a search phase results processor that runs between the query and fetch phases of
search execution. Like the normalization processor, it combines the results of the
sub-queries in a hybrid query — but it combines them by rank rather than by score, using Reciprocal Rank
Fusion (RRF).
The processor name is hyphenated: score-ranker-processor. score_ranker_processor is rejected with
Invalid processor type.
Choosing between rank fusion and score normalization
Both processors answer the same question — "these sub-queries returned different documents with incomparable scores, so what is the final order?" — and they answer it differently.
score-ranker-processor | normalization-processor | |
|---|---|---|
| Combines on | Position in each result list | Score value, after normalizing each list |
| Sensitive to score scale | No | Yes |
| Sensitive to score gaps | No — only order survives | Yes — a runaway top score dominates |
| Needs tuning per corpus | Rarely | Often, via weights and bounds |
| Good default when | Sub-query scores are not comparable (BM25 vs. cosine distance) | Sub-query scores are comparable, or you want gap size to matter |
The practical difference is that RRF rewards agreement. A document ranked second by both sub-queries can beat a document ranked first by one and last by the other, because RRF never sees how far ahead that first-place score was. The example below shows exactly that happening.
Request fields
| Field | Data type | Required/Optional | Description |
|---|---|---|---|
combination | Object | Optional | Combination settings. If omitted, the processor behaves as if technique were rrf with default settings. |
combination.technique | String | Optional | One of rrf, arithmetic_mean, geometric_mean, harmonic_mean, or bayesian. Default is rrf. The normalization-only techniques min_max and l2 are not valid here and are rejected with provided combination technique is not supported. |
combination.rank_constant | Integer | Optional | The k in the RRF formula. Must be between 1 and 10000. Default is 60. Lower values make the top few ranks dominate; higher values flatten the contribution of rank position. |
combination.parameters.weights | Array of floats | Optional | Per-sub-query weights, in the order the sub-queries appear in the hybrid query. The only key parameters accepts — anything else is rejected with supported parameters are [weights]. |
tag | String | Optional | An identifier for the processor. |
description | String | Optional | A description of the processor. |
ignore_failure | Boolean | Optional | Whether the pipeline continues if this processor fails. Default is false. |
How the score is calculated
For each document, the processor sums one term per sub-query that returned it:
score = Σ 1 / (rank_constant + rank)
rank is the document's 1-based position within that sub-query's result list. A sub-query that did not return the
document contributes nothing. With the default rank_constant of 60, a document ranked first in one list and
absent from the other scores 1/61 = 0.01639.
Example
The following example uses a four-document index whose vectors are small enough to read, so the arithmetic can be checked by hand.
Creating the index
PUT /articles
{
"settings": { "index": { "knn": true, "number_of_shards": 1, "number_of_replicas": 0 } },
"mappings": {
"properties": {
"title": { "type": "text" },
"embedding": { "type": "knn_vector", "dimension": 4, "space_type": "l2" }
}
}
}
Create the index before you index documents. A bulk request against a missing index creates it with a dynamic
mapping, which types embedding as a plain float array rather than a knn_vector, and the k-NN sub-query then
fails to match anything.
Indexing the documents
POST /_bulk?refresh=true
{"index":{"_index":"articles","_id":"1"}}
{"title":"Once-daily dosing and adherence in hypertension","embedding":[0.0,0.0,0.9,0.1]}
{"index":{"_index":"articles","_id":"2"}}
{"title":"Cardioselective agents in post-infarction management","embedding":[0.9,0.1,0.0,0.0]}
{"index":{"_index":"articles","_id":"3"}}
{"title":"Adherence patterns in long-term combination therapy","embedding":[0.7,0.3,0.1,0.0]}
{"index":{"_index":"articles","_id":"4"}}
{"title":"Peripheral oedema in calcium channel therapy","embedding":[0.1,0.2,0.8,0.1]}
What each sub-query returns on its own
The lexical sub-query matches only two documents:
| Rank | Document | Score |
|---|---|---|
| 1 | 1 Once-daily dosing and adherence in hypertension | 1.83924 |
| 2 | 3 Adherence patterns in long-term combination therapy | 0.67200 |
The vector sub-query returns all four, in a different order:
| Rank | Document | Score |
|---|---|---|
| 1 | 2 Cardioselective agents in post-infarction management | 1.00000 |
| 2 | 3 Adherence patterns in long-term combination therapy | 0.91743 |
| 3 | 4 Peripheral oedema in calcium channel therapy | 0.43478 |
| 4 | 1 Once-daily dosing and adherence in hypertension | 0.37879 |
Note that document 3 is second in both lists and first in neither.
Creating the search pipeline
PUT /_search/pipeline/rrf-pipeline
{
"description": "Reciprocal rank fusion for hybrid search",
"phase_results_processors": [
{
"score-ranker-processor": {
"combination": {
"technique": "rrf",
"rank_constant": 60
}
}
}
]
}
Using the search pipeline
GET /articles/_search?search_pipeline=rrf-pipeline
{
"query": {
"hybrid": {
"queries": [
{ "match": { "title": "adherence dosing" } },
{ "knn": { "embedding": { "vector": [0.9, 0.1, 0.0, 0.0], "k": 4 } } }
]
}
}
}
The fused order:
| Rank | Document | Score | Where it came from |
|---|---|---|---|
| 1 | 3 Adherence patterns in long-term combination therapy | 0.03226 | 1/62 + 1/62 — second in both lists |
| 2 | 1 Once-daily dosing and adherence in hypertension | 0.03202 | 1/61 + 1/64 — first lexically, last by vector |
| 3 | 2 Cardioselective agents in post-infarction management | 0.01639 | 1/61 — first by vector, no lexical match |
| 4 | 4 Peripheral oedema in calcium channel therapy | 0.01587 | 1/63 — third by vector, no lexical match |
Document 3 wins without having led either sub-query. It is the only document both sub-queries agree is
near the top, and rank fusion rewards that agreement. Document 1's commanding lexical score of 1.83924 buys it
nothing beyond first place in that one list.
The same query with score normalization
Running the identical query through a normalization-processor instead produces a different winner:
PUT /_search/pipeline/norm-pipeline
{
"phase_results_processors": [
{
"normalization-processor": {
"normalization": { "technique": "min_max" },
"combination": { "technique": "arithmetic_mean" }
}
}
]
}
| Rank | Document | Score |
|---|---|---|
| 1 | 1 Once-daily dosing and adherence in hypertension | 0.50050 |
| 2 | 2 Cardioselective agents in post-infarction management | 0.50000 |
| 3 | 3 Adherence patterns in long-term combination therapy | 0.43404 |
| 4 | 4 Peripheral oedema in calcium channel therapy | 0.04507 |
Here the two documents that led a list finish first and second, and the consensus document 3 drops to third.
Neither ordering is correct in the abstract — pick the processor whose behavior matches what you want relevance to
mean for your corpus.
Weighting the sub-queries
weights scales each sub-query's contribution, in the order the sub-queries appear in the hybrid query:
PUT /_search/pipeline/rrf-weighted
{
"phase_results_processors": [
{
"score-ranker-processor": {
"combination": {
"technique": "rrf",
"rank_constant": 60,
"parameters": { "weights": [0.2, 0.8] }
}
}
}
]
}
Weights scale the fused scores but change the order only when they are large enough to overcome a rank difference.
In the example above, weighting the vector sub-query at 0.8 leaves the order unchanged and simply rescales the
scores. Verify the effect on your own data rather than assuming a weight change has reordered anything.
Next steps
- Normalization processor — the score-based alternative, with its normalization techniques and bounds
- Vector and semantic search — where hybrid queries fit in a retrieval pipeline
- Query embedding processor — generate the query vector inside the cluster instead of passing one in