Spec-Zone.ru › Elasticsearch 8
›Руководство по Elasticsearch [8.17] ›REST API ›API поиска

Слияние рангов с обратной величиной

Слияние рангов с обратной величиной (RRF) — это метод объединения нескольких наборов результатов с различными показателями релевантности в единый набор результатов. RRF не требует настройки, и различные показатели релевантности не обязательно должны быть взаимосвязаны для достижения высоких результатов.

RRF использует следующую формулу для определения оценки ранжирования каждого документа:

score = 0.0
for q in queries:
    if d in result(q):
        score += 1.0 / ( k + rank( result(q), d ) )
return score

# where
# k is a ranking constant
# q is a query in the set of queries
# d is a document in the result set of q
# result(q) is the result set of q
# rank( result(q), d ) is d's rank within the result(q) starting from 1

API слияния рангов с обратной величиной

Новая справка по API

Для получения самых последних данных по API обратитесь к API поиска.

Вы можете использовать RRF в рамках поиска для объединения и ранжирования документов, используя отдельные наборы лучших документов (наборы результатов) от комбинации подключаемых извлекателей с помощью извлекателя RRF. Для ранжирования требуется как минимум два подключаемых извлекателя.

Извлекатель RRF — это необъект, определённый в качестве части параметра retriever запроса поиска. Объект извлекателя RRF содержит следующие параметры:

retrievers

(Обязательный, массив объектов извлекателей)

Список подключаемых извлекателей, указывающий, к каким наборам возвращённых лучших документов будет применяться формула RRF. Каждый подключаемый извлекатель имеет равный вес в рамках формулы RRF. Требуется два или более подключаемых извлекателя.

rank_constant

(Необязательный, целое число)

Это значение определяет, насколько сильно документы в отдельных наборах результатов каждого запроса влияют на итоговый ранжированный набор результатов. Более высокое значение означает, что документы с более низким рангом оказывают большее влияние. Это значение должно быть больше или равно 1. По умолчанию 60.

rank_window_size

(Необязательный, целое число)

Это значение определяет размер отдельных наборов результатов для каждого запроса. Более высокое значение улучшит релевантность результатов за счёт производительности. Итоговый ранжированный набор результатов усекается до параметра запроса поиска size. rank_window_size должно быть больше или равно size и больше или равно 1. По умолчанию параметр size.

Пример запроса с использованием RRF:

resp = client.search(
    index="example-index",
    retriever={
        "rrf": {
            "retrievers": [
                {
                    "standard": {
                        "query": {
                            "term": {
                                "text": "shoes"
                            }
                        }
                    }
                },
                {
                    "knn": {
                        "field": "vector",
                        "query_vector": [
                            1.25,
                            2,
                            3.5
                        ],
                        "k": 50,
                        "num_candidates": 100
                    }
                }
            ],
            "rank_window_size": 50,
            "rank_constant": 20
        }
    },
)
print(resp)
const response = await client.search({
  index: "example-index",
  retriever: {
    rrf: {
      retrievers: [
        {
          standard: {
            query: {
              term: {
                text: "shoes",
              },
            },
          },
        },
        {
          knn: {
            field: "vector",
            query_vector: [1.25, 2, 3.5],
            k: 50,
            num_candidates: 100,
          },
        },
      ],
      rank_window_size: 50,
      rank_constant: 20,
    },
  },
});
console.log(response);
GET example-index/_search
{
    "retriever": {
        "rrf": { 
            "retrievers": [
                {
                    "standard": { 
                        "query": {
                            "term": {
                                "text": "shoes"
                            }
                        }
                    }
                },
                {
                    "knn": { 
                        "field": "vector",
                        "query_vector": [1.25, 2, 3.5],
                        "k": 50,
                        "num_candidates": 100
                    }
                }
            ],
            "rank_window_size": 50,
            "rank_constant": 20
        }
    }
}

В приведенном выше примере мы выполняем knn и standard извлекатели независимо друг от друга. Затем мы используем rrf извлекатель для объединения результатов.

Сначала мы выполняем поиск kNN, заданный извлекателем knn, чтобы получить его глобальные 50 лучших результатов.

Во-вторых, мы выполняем запрос, заданный извлекателем standard, чтобы получить его глобальные 50 лучших результатов.

Затем на координационном узле мы объединяем лучшие документы поиска kNN с лучшими документами запроса и ранжируем их на основе формулы RRF, используя параметры из извлекателя rrf, чтобы получить объединённые лучшие документы, используя по умолчанию size из 10.

Обратите внимание, что если k из поиска knn больше, чем rank_window_size, результаты усекаются до rank_window_size. Если k меньше, чем rank_window_size, результаты имеют размер k.

Поддерживаемые функции слияния рангов с обратной величиной

Извлекатель rrf поддерживает:

  • агрегации
  • from
  • подсказки
  • выделение
  • сжатие
  • профилирование

Извлекатель rrf в настоящее время не поддерживает:

  • список
  • сортировка
  • переоценка

Использование неподдерживаемых функций в рамках поиска с извлекателем rrf приводит к исключению.

Лучше избегать указания момента времени в запросе, так как RRF создаёт его внутренне и он общий для всех подключаемых извлекателей, чтобы обеспечить согласованные результаты.

Слияние рангов с обратной величиной, используя несколько стандартных извлекателей

Извлекатель rrf предоставляет способ объединения и ранжирования нескольких standard извлекателей. Основной случай использования — объединение лучших документов из традиционного запроса BM25 и запроса ELSER для достижения улучшенной релевантности.

Пример запроса с использованием RRF с несколькими стандартными извлекателями:

resp = client.search(
    index="example-index",
    retriever={
        "rrf": {
            "retrievers": [
                {
                    "standard": {
                        "query": {
                            "term": {
                                "text": "blue shoes sale"
                            }
                        }
                    }
                },
                {
                    "standard": {
                        "query": {
                            "sparse_vector": {
                                "field": "ml.tokens",
                                "inference_id": "my_elser_model",
                                "query": "What blue shoes are on sale?"
                            }
                        }
                    }
                }
            ],
            "rank_window_size": 50,
            "rank_constant": 20
        }
    },
)
print(resp)
const response = await client.search({
  index: "example-index",
  retriever: {
    rrf: {
      retrievers: [
        {
          standard: {
            query: {
              term: {
                text: "blue shoes sale",
              },
            },
          },
        },
        {
          standard: {
            query: {
              sparse_vector: {
                field: "ml.tokens",
                inference_id: "my_elser_model",
                query: "What blue shoes are on sale?",
              },
            },
          },
        },
      ],
      rank_window_size: 50,
      rank_constant: 20,
    },
  },
});
console.log(response);
GET example-index/_search
{
    "retriever": {
        "rrf": { 
            "retrievers": [
                {
                    "standard": { 
                        "query": {
                            "term": {
                                "text": "blue shoes sale"
                            }
                        }
                    }
                },
                {
                    "standard": { 
                        "query": {
                            "sparse_vector":{
                                "field": "ml.tokens",
                                "inference_id": "my_elser_model",
                                "query": "What blue shoes are on sale?"
                            }
                        }
                    }
                }
            ],
            "rank_window_size": 50,
            "rank_constant": 20
        }
    }
}

В приведенном выше примере мы выполняем каждый из двух standard извлекателей независимо друг от друга. Затем мы используем rrf извлекатель для объединения результатов.

Сначала мы запускаем извлекатель standard, указывая термин-запрос для blue shoes sales, используя стандартный алгоритм подсчёта очков BM25.

Затем мы запускаем извлекатель standard, указывая запрос sparse_vector для What blue shoes are on sale?, используя наш алгоритм подсчёта очков ELSER.

Извлекатель rrf позволяет объединить два набора лучших документов, сгенерированных совершенно независимыми алгоритмами подсчёта очков, с равным весом.

Это не только избавляет от необходимости определения подходящего весового коэффициента с использованием линейной комбинации, но RRF также демонстрирует улучшение релевантности по сравнению с любым запросом по отдельности.

Слияние рангов с обратной величиной с использованием подзапросов

Слияние рангов с обратной величиной с использованием подзапросов больше не поддерживается. Используйте API извлекателей вместо этого. Смотрите примеры с несколькими стандартными извлекателями.

Полный пример слияния рангов обратной зависимости

Начнем с создания отображения для индекса с текстовым полем, векторным полем и целочисленным полем, а также индексирования нескольких документов. В этом примере мы будем использовать вектор только с одной размерностью, чтобы упростить объяснение ранжирования.

resp = client.indices.create(
    index="example-index",
    mappings={
        "properties": {
            "text": {
                "type": "text"
            },
            "vector": {
                "type": "dense_vector",
                "dims": 1,
                "index": True,
                "similarity": "l2_norm",
                "index_options": {
                    "type": "hnsw"
                }
            },
            "integer": {
                "type": "integer"
            }
        }
    },
)
print(resp)

resp1 = client.index(
    index="example-index",
    id="1",
    document={
        "text": "rrf",
        "vector": [
            5
        ],
        "integer": 1
    },
)
print(resp1)

resp2 = client.index(
    index="example-index",
    id="2",
    document={
        "text": "rrf rrf",
        "vector": [
            4
        ],
        "integer": 2
    },
)
print(resp2)

resp3 = client.index(
    index="example-index",
    id="3",
    document={
        "text": "rrf rrf rrf",
        "vector": [
            3
        ],
        "integer": 1
    },
)
print(resp3)

resp4 = client.index(
    index="example-index",
    id="4",
    document={
        "text": "rrf rrf rrf rrf",
        "integer": 2
    },
)
print(resp4)

resp5 = client.index(
    index="example-index",
    id="5",
    document={
        "vector": [
            0
        ],
        "integer": 1
    },
)
print(resp5)

resp6 = client.indices.refresh(
    index="example-index",
)
print(resp6)
const response = await client.indices.create({
  index: "example-index",
  mappings: {
    properties: {
      text: {
        type: "text",
      },
      vector: {
        type: "dense_vector",
        dims: 1,
        index: true,
        similarity: "l2_norm",
        index_options: {
          type: "hnsw",
        },
      },
      integer: {
        type: "integer",
      },
    },
  },
});
console.log(response);

const response1 = await client.index({
  index: "example-index",
  id: 1,
  document: {
    text: "rrf",
    vector: [5],
    integer: 1,
  },
});
console.log(response1);

const response2 = await client.index({
  index: "example-index",
  id: 2,
  document: {
    text: "rrf rrf",
    vector: [4],
    integer: 2,
  },
});
console.log(response2);

const response3 = await client.index({
  index: "example-index",
  id: 3,
  document: {
    text: "rrf rrf rrf",
    vector: [3],
    integer: 1,
  },
});
console.log(response3);

const response4 = await client.index({
  index: "example-index",
  id: 4,
  document: {
    text: "rrf rrf rrf rrf",
    integer: 2,
  },
});
console.log(response4);

const response5 = await client.index({
  index: "example-index",
  id: 5,
  document: {
    vector: [0],
    integer: 1,
  },
});
console.log(response5);

const response6 = await client.indices.refresh({
  index: "example-index",
});
console.log(response6);
PUT example-index
{
    "mappings": {
        "properties": {
            "text" : {
                "type" : "text"
            },
            "vector": {
                "type": "dense_vector",
                "dims": 1,
                "index": true,
                "similarity": "l2_norm",
                 "index_options": {
                     "type": "hnsw"
                 }
            },
            "integer" : {
                "type" : "integer"
            }
        }
    }
}

PUT example-index/_doc/1
{
    "text" : "rrf",
    "vector" : [5],
    "integer": 1
}

PUT example-index/_doc/2
{
    "text" : "rrf rrf",
    "vector" : [4],
    "integer": 2
}

PUT example-index/_doc/3
{
    "text" : "rrf rrf rrf",
    "vector" : [3],
    "integer": 1
}

PUT example-index/_doc/4
{
    "text" : "rrf rrf rrf rrf",
    "integer": 2
}

PUT example-index/_doc/5
{
    "vector" : [0],
    "integer": 1
}

POST example-index/_refresh

Теперь выполним поиск с помощью ретривера rrf с ретривером standard, задающим запрос BM25, ретривером knn, задающим поиск kNN, и агрегацией терминов.

resp = client.search(
    index="example-index",
    retriever={
        "rrf": {
            "retrievers": [
                {
                    "standard": {
                        "query": {
                            "term": {
                                "text": "rrf"
                            }
                        }
                    }
                },
                {
                    "knn": {
                        "field": "vector",
                        "query_vector": [
                            3
                        ],
                        "k": 5,
                        "num_candidates": 5
                    }
                }
            ],
            "rank_window_size": 5,
            "rank_constant": 1
        }
    },
    size=3,
    aggs={
        "int_count": {
            "terms": {
                "field": "integer"
            }
        }
    },
)
print(resp)
const response = await client.search({
  index: "example-index",
  retriever: {
    rrf: {
      retrievers: [
        {
          standard: {
            query: {
              term: {
                text: "rrf",
              },
            },
          },
        },
        {
          knn: {
            field: "vector",
            query_vector: [3],
            k: 5,
            num_candidates: 5,
          },
        },
      ],
      rank_window_size: 5,
      rank_constant: 1,
    },
  },
  size: 3,
  aggs: {
    int_count: {
      terms: {
        field: "integer",
      },
    },
  },
});
console.log(response);
GET example-index/_search
{
    "retriever": {
        "rrf": {
            "retrievers": [
                {
                    "standard": {
                        "query": {
                            "term": {
                                "text": "rrf"
                            }
                        }
                    }
                },
                {
                    "knn": {
                        "field": "vector",
                        "query_vector": [3],
                        "k": 5,
                        "num_candidates": 5
                    }
                }
            ],
            "rank_window_size": 5,
            "rank_constant": 1
        }
    },
    "size": 3,
    "aggs": {
        "int_count": {
            "terms": {
                "field": "integer"
            }
        }
    }
}

И получим ответ с отсортированными hits и результатом агрегации терминов. У нас есть возможность отображения наших высокоранжированных документов как с использованием ранжировщика, так и с использованием _rank варианта.

{
    "took": ...,
    "timed_out" : false,
    "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
    },
    "hits" : {
        "total" : {
            "value" : 5,
            "relation" : "eq"
        },
        "max_score" : ...,
        "hits" : [
            {
                "_index" : "example-index",
                "_id" : "3",
                "_score" : 0.8333334,
                "_source" : {
                    "integer" : 1,
                    "vector" : [
                        3
                    ],
                    "text" : "rrf rrf rrf"
                }
            },
            {
                "_index" : "example-index",
                "_id" : "2",
                "_score" : 0.5833334,
                "_source" : {
                    "integer" : 2,
                    "vector" : [
                        4
                    ],
                    "text" : "rrf rrf"
                }
            },
            {
                "_index" : "example-index",
                "_id" : "4",
                "_score" : 0.5,
                "_source" : {
                    "integer" : 2,
                    "text" : "rrf rrf rrf rrf"
                }
            }
        ]
    },
    "aggregations" : {
        "int_count" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
                {
                    "key" : 1,
                    "doc_count" : 3
                },
                {
                    "key" : 2,
                    "doc_count" : 2
                }
            ]
        }
    }
}

Давайте разберем, как были ранжированы эти совпадения. Мы начинаем с выполнения ретривера standard, задающего запрос, и ретривера knn, задающего поиск kNN, по отдельности, чтобы получить их индивидуальные совпадения.

Сначала рассмотрим совпадения для запроса от ретривера standard.

"hits" : [
    {
        "_index" : "example-index",
        "_id" : "4",
        "_score" : 0.16152832,              
        "_source" : {
            "integer" : 2,
            "text" : "rrf rrf rrf rrf"
        }
    },
    {
        "_index" : "example-index",
        "_id" : "3",                        
        "_score" : 0.15876243,
        "_source" : {
            "integer" : 1,
            "vector" : [3],
            "text" : "rrf rrf rrf"
        }
    },
    {
        "_index" : "example-index",
        "_id" : "2",                        
        "_score" : 0.15350538,
        "_source" : {
            "integer" : 2,
            "vector" : [4],
            "text" : "rrf rrf"
        }
    },
    {
        "_index" : "example-index",
        "_id" : "1",                        
        "_score" : 0.13963442,
        "_source" : {
            "integer" : 1,
            "vector" : [5],
            "text" : "rrf"
        }
    }
]

ранг 1, _id 4

ранг 2, _id 3

ранг 3, _id 2

ранг 4, _id 1

Обратите внимание, что для нашего первого совпадения нет значения для поля vector. Теперь посмотрим на результаты поиска kNN от ретривера knn.

"hits" : [
    {
        "_index" : "example-index",
        "_id" : "3",                   
        "_score" : 1.0,
        "_source" : {
            "integer" : 1,
            "vector" : [3],
            "text" : "rrf rrf rrf"
        }
    },
    {
        "_index" : "example-index",
        "_id" : "2",                   
        "_score" : 0.5,
        "_source" : {
            "integer" : 2,
            "vector" : [4],
            "text" : "rrf rrf"
        }
    },
    {
        "_index" : "example-index",
        "_id" : "1",                   
        "_score" : 0.2,
        "_source" : {
            "integer" : 1,
            "vector" : [5],
            "text" : "rrf"
        }
    },
    {
        "_index" : "example-index",
        "_id" : "5",                   
        "_score" : 0.1,
        "_source" : {
            "integer" : 1,
            "vector" : [0]
        }
    }
]

ранг 1, _id 3

ранг 2, _id 2

ранг 3, _id 1

ранг 4, _id 5

Теперь мы можем взять два набора результатов, ранжированных по отдельности, и применить к ним формулу RRF, используя параметры от ретривера rrf, чтобы получить окончательный ранг.

# doc  | query     | knn       | score
_id: 1 = 1.0/(1+4) + 1.0/(1+3) = 0.4500
_id: 2 = 1.0/(1+3) + 1.0/(1+2) = 0.5833
_id: 3 = 1.0/(1+2) + 1.0/(1+1) = 0.8333
_id: 4 = 1.0/(1+1)             = 0.5000
_id: 5 =             1.0/(1+4) = 0.2000

Мы ранжируем документы на основе формулы RRF с rank_window_size 5, отбрасывая нижние 2 документы в нашем наборе результатов RRF с size 3. В результате мы получаем _id: 3 как _rank: 1, _id: 2 как _rank: 2, и _id: 4 как _rank: 3. Этот ранг соответствует ожидаемому набору результатов из исходного поиска RRF.

Объяснение в RRF

Помимо отдельных деталей оценки запроса, мы можем использовать параметр explain=true, чтобы получить информацию о том, как вычислялись оценки RRF для каждого документа. Используя пример выше и добавив explain=true в запрос поиска, мы получим ответ, который будет выглядеть следующим образом:

{
    "hits":
    [
        {
            "_index": "example-index",
            "_id": "3",
            "_score": 0.8333334,
            "_explanation":
            {
                "value": 0.8333334,                                                                                                                                               
                "description": "rrf score: [0.8333334] computed for initial ranks [2, 1] with rankConstant: [1] as sum of [1 / (rank + rankConstant)] for each query",            
                "details":                                                                                                                                                        
                [
                    {
                        "value": 2,                                                                                                                                               
                        "description": "rrf score: [0.33333334], for rank [2] in query at index [0] computed as [1 / (2 + 1]), for matching query with score: ",
                        "details":                                                                                                                                                
                        [
                            {
                                "value": 0.15876243,
                                "description": "weight(text:rrf in 0) [PerFieldSimilarity], result of:",
                                "details":
                                [
                                    ...
                                ]
                            }
                        ]
                    },
                    {
                        "value": 1,                                                                                                                                              
                        "description": "rrf score: [0.5], for rank [1] in query at index [1] computed as [1 / (1 + 1]), for matching query with score: ",
                        "details":
                        [
                            {
                                "value": 1,
                                "description": "within top k documents",
                                "details":
                                []
                            }
                        ]
                    }
                ]
            }
        }
        ...
    ]
}

окончательная оценка RRF для документа с _id=3

описание того, как эта оценка вычислялась на основе рангов этого документа в каждом отдельном запросе

подробности о том, как вычислялась оценка RRF для каждого из запросов

value здесь указывает rank этого документа в конкретном запросе

стандартный вывод explain от базового запроса, описывающий сопоставляемые термины и веса

value здесь указывает rank этого документа для второго (knn) запроса

В дополнение к вышесказанному, объяснение в RRF также поддерживает именованные запросы с помощью параметра _name. Использование именованных запросов позволяет проще и интуитивно понять вычисление оценки RRF, особенно при работе с несколькими запросами. Таким образом, мы теперь имеем:

GET example-index/_search
{
    "retriever": {
        "rrf": {
            "retrievers": [
                {
                    "standard": {
                        "query": {
                            "term": {
                                "text": "rrf"
                            }
                        }
                    }
                },
                {
                    "knn": {
                        "field": "vector",
                        "query_vector": [3],
                        "k": 5,
                        "num_candidates": 5,
                        "_name": "my_knn_query"                           
                    }
                }
            ],
            "rank_window_size": 5,
            "rank_constant": 1
        }
    },
    "size": 3,
    "aggs": {
        "int_count": {
            "terms": {
                "field": "integer"
            }
        }
    }
}

Здесь мы задаем _name для ретривера knn

Ответ теперь будет включать именованный запрос в объяснении:

{
    "hits":
    [
        {
            "_index": "example-index",
            "_id": "3",
            "_score": 0.8333334,
            "_explanation":
            {
                "value": 0.8333334,
                "description": "rrf score: [0.8333334] computed for initial ranks [2, 1] with rankConstant: [1] as sum of [1 / (rank + rankConstant)] for each query",
                "details":
                [
                    {
                        "value": 2,
                        "description": "rrf score: [0.33333334], for rank [2] in query at index [0] computed as [1 / (2 + 1]), for matching query with score: ",
                        "details":
                        [
                            ...
                        ]
                    },
                    {
                        "value": 1,
                        "description": "rrf score: [0.5], for rank [1] in query [my_knn_query] computed as [1 / (1 + 1]), for matching query with score: ",                      
                        "details":
                        [
                           ...
                        ]
                    }
                ]
            }
        }
        ...
    ]
}

Вместо анонимного at index n, мы теперь имеем ссылку на именованный запрос my_knn_query.

Пагинация в RRF

При использовании rrf вы можете странировать результаты, используя параметр from. Поскольку окончательный ранг полностью зависит от первоначальных рангов запроса, чтобы обеспечить согласованность при странировании, мы должны убедиться, что, хотя from изменяется, порядок уже увиденного остается неизменным. Для этого мы используем фиксированный rank_window_size в качестве всего доступного набора результатов, на котором можно странировать. Это по существу означает, что если:

  • from + size ≤ rank_window_size : мы можем получить results[from: from+size] документов из окончательного отсортированного набора результатов rrf
  • from + size > rank_window_size : мы получим 0 результатов, так как запрос выйдет за пределы доступного набора результатов размером rank_window_size.

Важно отметить, что поскольку rank_window_size — это все результаты, которые мы увидим от отдельных компонентов запроса, пагинация гарантирует согласованность, т.е. никакие документы не пропускаются и не дублируются на разных страницах, если rank_window_size остается неизменным. Если rank_window_size изменяется, порядок результатов также может измениться, даже для одинаковых рангов.

Чтобы проиллюстрировать всё вышесказанное, рассмотрим следующий упрощенный пример, где у нас есть два запроса, queryA и queryB, и их отсортированные документы:

     |  queryA   |  queryB    |
_id: |  1        |  5         |
_id: |  2        |  4         |
_id: |  3        |  3         |
_id: |  4        |  1         |
_id: |           |  2         |

Для rank_window_size=5 мы увидим все документы из обоих queryA и queryB. Предполагая rank_constant=1, оценки rrf будут:

# doc   | queryA     | queryB       | score
_id: 1 =  1.0/(1+1)  + 1.0/(1+4)      = 0.7
_id: 2 =  1.0/(1+2)  + 1.0/(1+5)      = 0.5
_id: 3 =  1.0/(1+3)  + 1.0/(1+3)      = 0.5
_id: 4 =  1.0/(1+4)  + 1.0/(1+2)      = 0.533
_id: 5 =    0        + 1.0/(1+1)      = 0.5

Таким образом, окончательный отсортированный набор результатов будет [1, 4, 2, 3, 5], и мы будем странировать по нему, так как rank_window_size == len(results). В этом сценарии у нас будет:

  • from=0, size=2 вернет документы [1, 4] с рангами [1, 2]
  • from=2, size=2 вернет документы [2, 3] с рангами [3, 4]
  • from=4, size=2 вернет документ [5] с рангом [5]
  • from=6, size=2 вернет пустой набор результатов, так как больше результатов для итерации нет

Теперь, если у нас есть rank_window_size=2, мы увидим только документы [1, 2] и [5, 4] для запросов queryA и queryB соответственно. Вычислив, мы увидим, что результаты теперь будут немного отличаться, потому что у нас не будет информации о документах в позициях [3: end] ни для одного запроса.

# doc   | queryA     | queryB         | score
_id: 1 =  1.0/(1+1)  + 0              = 0.5
_id: 2 =  1.0/(1+2)  + 0              = 0.33
_id: 4 =    0        + 1.0/(1+2)      = 0.33
_id: 5 =    0        + 1.0/(1+1)      = 0.5

Окончательный отсортированный набор результатов будет [1, 5, 2, 4], и мы сможем странировать по верхним rank_window_size результатам, т.е. [1, 5]. Таким образом, для тех же параметров, что и выше, у нас будет:

  • from=0, size=2 вернет [1, 5] с рангами [1, 2]
  • from=2, size=2 вернет пустой набор результатов, так как он выйдет за пределы доступных rank_window_size результатов.

Агрегации в RRF

Обработчик rrf поддерживает агрегации из всех указанных под-обработчиков. Важные замечания об агрегациях:

  • Они работают со всем набором результатов от всех под-обработчиков
  • Они не ограничены параметром rank_window_size
  • Они обрабатывают объединение всех совпадающих документов

Например, рассмотрим следующий набор документов:

{
    "_id": 1, "termA": "foo",
    "_id": 2, "termA": "foo", "termB": "bar",
    "_id": 3, "termA": "aardvark", "termB": "bar",
    "_id": 4, "termA": "foo", "termB": "bar"
}

Выполните агрегацию по термину в поле termA, используя обработчик rrf:

{
    "retriever": {
        "rrf": {
            "retrievers": [
                {
                    "standard": {
                        "query": {
                            "term": {
                                "termB": "bar"
                            }
                        }
                    }
                },
                {
                    "standard": {
                        "query": {
                            "match_all": { }
                        }
                    }
                }
            ],
            "rank_window_size": 1
        }
    },
    "size": 1,
    "aggs": {
        "termA_agg": {
            "terms": {
                "field": "termA"
            }
        }
    }
}

Результаты агрегации будут включать все совпадающие документы, независимо от rank_window_size.

{
    "foo": 3,
    "aardvark": 1
}

Выделение в RRF

Используя обработчик rrf, вы можете добавить выделенные фрагменты, чтобы показать релевантные фрагменты текста в результатах поиска. Выделенные фрагменты вычисляются на основе соответствующих текстовых запросов, определенных в под-обработчиках.

Выделение по векторным полям, используя либо обработчик knn, либо запрос knn, не поддерживается.

Более конкретный пример выделения в RRF также можно найти на странице примеров обработчиков.

Внутренние результаты в RRF

Обработчик rrf поддерживает функциональность внутренних результатов, позволяя получать связанные вложенные или родительские/дочерние документы наряду с основными результатами поиска. Внутренние результаты могут быть указаны в качестве части любого вложенного под-обработчика и будут переданы в родительский обработчик верхнего уровня. Обратите внимание, что вычисление внутренних результатов будет выполнено только в конце оценки обработчика rrf по лучшим совпадающим документам, а не в рамках выполнения запроса вложенных под-обработчиков.

При определении нескольких секций inner_hits в под-обработчиках:

  • Каждая секция inner_hits должна иметь уникальное имя
  • Имена должны быть уникальными для всех под-обработчиков в запросе поиска

© 2023-2025 Elasticsearch
As of September 2024, Elasticsearch is available under a choice of three licenses: the Server Side Public License (SSPL), the Elastic License, or the AGPLv3 (OSI approved).
Elasticsearch and the Elasticsearch logo are trademarks of Elasticsearch B.V., registered in the U.S. and in other countries.
https://www.elastic.co/guide/en/elasticsearch/reference/8.17/rrf.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API