正在加载

Retrievers 示例

Elastic Stack Serverless

了解如何在这些动手示例中组合不同的检索器。

首先,让我们创建 retrievers_example 索引,并向其中添加一些文档。我们将为示例设置 number_of_shards=1,以确保一致且可重现的排序。

 PUT retrievers_example {
    "settings": {
        "number_of_shards": 1
    },
   "mappings": {
       "properties": {
           "vector": {
               "type": "dense_vector",
               "dims": 3,
               "similarity": "l2_norm",
               "index": true,
               "index_options": {
                    "type": "flat"
               }
           },
           "text": {
               "type": "text"
           },
           "year": {
               "type": "integer"
           },
           "topic": {
               "type": "keyword"
           },
           "timestamp": {
               "type": "date"
           }
       }
   }
}

POST /retrievers_example/_doc/1
{
 "vector": [0.23, 0.67, 0.89],
 "text": "Large language models are revolutionizing information retrieval by boosting search precision, deepening contextual understanding, and reshaping user experiences in data-rich environments.",
 "year": 2024,
 "topic": ["llm", "ai", "information_retrieval"],
 "timestamp": "2021-01-01T12:10:30"
}

POST /retrievers_example/_doc/2
{
 "vector": [0.12, 0.56, 0.78],
 "text": "Artificial intelligence is transforming medicine, from advancing diagnostics and tailoring treatment plans to empowering predictive patient care for improved health outcomes.",
 "year": 2023,
 "topic": ["ai", "medicine"],
 "timestamp": "2022-01-01T12:10:30"
}

POST /retrievers_example/_doc/3
{
 "vector": [0.45, 0.32, 0.91],
  "text": "AI is redefining security by enabling advanced threat detection, proactive risk analysis, and dynamic defenses against increasingly sophisticated cyber threats.",
 "year": 2024,
 "topic": ["ai", "security"],
 "timestamp": "2023-01-01T12:10:30"
}

POST /retrievers_example/_doc/4
{
 "vector": [0.34, 0.21, 0.98],
 "text": "Elastic introduces Elastic AI Assistant, the open, generative AI sidekick powered by ESRE to democratize cybersecurity and enable users of every skill level.",
 "year": 2023,
 "topic": ["ai", "elastic", "assistant"],
 "timestamp": "2024-01-01T12:10:30"
}

POST /retrievers_example/_doc/5
{
 "vector": [0.11, 0.65, 0.47],
 "text": "Learn how to spin up a deployment on Elastic Cloud and use Elastic Observability to gain deeper insight into the behavior of your applications and systems.",
 "year": 2024,
 "topic": ["documentation", "observability", "elastic"],
 "timestamp": "2025-01-01T12:10:30"
}

POST /retrievers_example/_refresh

现在我们有了文档,让我们尝试使用检索器运行一些查询。

首先,让我们研究如何组合两种不同类型的查询:kNN 查询和 query_string 查询。虽然这些查询可能会在不同的范围内产生分数,但我们可以使用倒数排名融合 (rrf) 来组合结果并生成合并的最终结果列表。

为了在检索器框架中实现这一点,我们从顶层元素开始:我们的 rrf 检索器。此检索器在另外两个检索器之上运行:knn 检索器和 standard 检索器。我们的查询结构如下所示

 GET /retrievers_example/_search {
    "retriever": {
        "rrf": {
            "retrievers": [
                {
                    "standard": {
                        "query": {
                            "query_string": {
                                "query": "(information retrieval) OR (artificial intelligence)",
                                "default_field": "text"
                            }
                        }
                    }
                },
                {
                    "knn": {
                        "field": "vector",
                        "query_vector": [
                            0.23,
                            0.67,
                            0.89
                        ],
                        "k": 3,
                        "num_candidates": 5
                    }
                }
            ],
            "rank_window_size": 10,
            "rank_constant": 1
        }
    },
    "_source": false
}

这将返回基于每个结果的最终 rrf 分数的回应。

提供混合搜索的一种不同的、更直观的方法是,使用原始分数的加权总和来线性组合不同检索器的顶部文档。由于如上所述,分数可能位于不同的范围内,因此我们还可以指定一个 normalizer,以确保检索器排名前列的文档的所有分数都位于特定范围内。

为了实现这一点,我们定义一个 linear 检索器,以及一组将生成我们将要组合的异构结果集的检索器。我们将通过合并 standardknn 检索器的结果来解决与上述类似的问题。由于 standard 检索器的分数基于 BM25 并且没有严格的界限,我们还将定义一个 minmax 归一化器以确保分数位于 [0, 1] 范围内。我们将对 knn 也应用相同的归一化器,以确保我们捕获每个文档在结果集中的重要性。

因此,现在让我们指定 linear 检索器,其最终分数计算如下

score = weight(standard) * score(standard) + weight(knn) * score(knn)
score = 2 * score(standard) + 1.5 * score(knn)
 GET /retrievers_example/_search {
    "retriever": {
        "linear": {
            "retrievers": [
                {
                    "retriever": {
                        "standard": {
                            "query": {
                                "query_string": {
                                    "query": "(information retrieval) OR (artificial intelligence)",
                                    "default_field": "text"
                                }
                            }
                        }
                    },
                    "weight": 2,
                    "normalizer": "minmax"
                },
                {
                    "retriever": {
                        "knn": {
                            "field": "vector",
                            "query_vector": [
                                0.23,
                                0.67,
                                0.89
                            ],
                            "k": 3,
                            "num_candidates": 5
                        }
                    },
                    "weight": 1.5,
                    "normalizer": "minmax"
                }
            ],
            "rank_window_size": 10
        }
    },
    "_source": false
}

这将返回基于每个结果的归一化加权分数的回应。

通过对分数进行归一化和利用 function_score 查询,我们还可以实现更复杂的排名策略,例如基于时间戳对结果进行排序,将时间戳分配为分数,然后将此分数归一化为 [0, 1]。然后,我们可以轻松地将上述内容与 knn 检索器结合起来,如下所示

 GET /retrievers_example/_search {
    "retriever": {
        "linear": {
            "retrievers": [
                {
                    "retriever": {
                        "standard": {
                            "query": {
                                "function_score": {
                                    "query": {
                                        "term": {
                                            "topic": "ai"
                                        }
                                    },
                                    "functions": [
                                        {
                                            "script_score": {
                                                "script": {
                                                    "source": "doc['timestamp'].value.millis"
                                                }
                                            }
                                        }
                                    ],
                                    "boost_mode": "replace"
                                }
                            },
                            "sort": {
                                "timestamp": {
                                    "order": "asc"
                                }
                            }
                        }
                    },
                    "weight": 2,
                    "normalizer": "minmax"
                },
                {
                    "retriever": {
                        "knn": {
                            "field": "vector",
                            "query_vector": [
                                0.23,
                                0.67,
                                0.89
                            ],
                            "k": 3,
                            "num_candidates": 5
                        }
                    },
                    "weight": 1.5
                }
            ],
            "rank_window_size": 10
        }
    },
    "_source": false
}

这将返回以下结果

在我们的结果集中,我们有许多具有相同 year 值的文档。我们可以使用 collapse 参数与我们的检索器一起清理此问题。与标准 collapse 功能一样,这可以按任何字段对结果进行分组,并仅返回每个组中得分最高的文档。在此示例中,我们将基于 year 字段折叠我们的结果。

 GET /retrievers_example/_search {
    "retriever": {
        "rrf": {
            "retrievers": [
                {
                    "standard": {
                        "query": {
                            "query_string": {
                                "query": "(information retrieval) OR (artificial intelligence)",
                                "default_field": "text"
                            }
                        }
                    }
                },
                {
                    "knn": {
                        "field": "vector",
                        "query_vector": [
                            0.23,
                            0.67,
                            0.89
                        ],
                        "k": 3,
                        "num_candidates": 5
                    }
                }
            ],
            "rank_window_size": 10,
            "rank_constant": 1
        }
    },
    "collapse": {
        "field": "year",
        "inner_hits": {
            "name": "topic related documents",
            "_source": [
                "year"
            ]
        }
    },
    "_source": false
}

这将返回带有折叠结果的回应。

突出显示现在也适用于嵌套子检索器匹配项。例如,考虑与上述相同的 rrf 检索器,其中 knnstandard 检索器作为其子检索器。我们可以指定一个 highlight 部分,如 highlighting 文档中所定义的那样,并计算顶部结果的突出显示。

 GET /retrievers_example/_search {
    "retriever": {
        "rrf": {
            "retrievers": [
                {
                    "standard": {
                        "query": {
                            "query_string": {
                                "query": "(information retrieval) OR (artificial intelligence)",
                                "default_field": "text"
                            }
                        }
                    }
                },
                {
                    "knn": {
                        "field": "vector",
                        "query_vector": [
                            0.23,
                            0.67,
                            0.89
                        ],
                        "k": 3,
                        "num_candidates": 5
                    }
                }
            ],
            "rank_window_size": 10,
            "rank_constant": 1
        }
    },
    "highlight": {
        "fields": {
            "text": {
                "fragment_size": 150,
                "number_of_fragments": 3
            }
        }
    },
    "_source": false
}

这将根据 standard 检索器生成的匹配项突出显示 text 字段。然后,突出显示的代码段将像往常一样包含在回应中,即在每个搜索命中下。

我们还可以定义 inner_hits 以在任何子检索器上计算,并将这些计算传播到顶级复合检索器。例如,让我们创建一个新索引,其中包含一个 knn 字段,该字段嵌套在 nested_field 字段下,并索引几个文档。

 PUT retrievers_example_nested {
    "settings": {
         "number_of_shards": 1
     },
    "mappings": {
        "properties": {
            "nested_field": {
                "type": "nested",
                "properties": {
                    "paragraph_id": {
                        "type": "keyword"
                    },
                    "nested_vector": {
                        "type": "dense_vector",
                        "dims": 3,
                        "similarity": "l2_norm",
                        "index": true,
                        "index_options": {
                            "type": "flat"
                        }
                    }
                }
            },
            "topic": {
                "type": "keyword"
            }
        }
    }
}

POST /retrievers_example_nested/_doc/1
{
    "nested_field": [
        {
            "paragraph_id": "1a",
            "nested_vector": [
                -1.12,
                -0.59,
                0.78
            ]
        },
        {
            "paragraph_id": "1b",
            "nested_vector": [
                -0.12,
                1.56,
                0.42
            ]
        },
        {
            "paragraph_id": "1c",
            "nested_vector": [
                1,
                -1,
                0
            ]
        }
    ],
    "topic": [
        "ai"
    ]
}

POST /retrievers_example_nested/_doc/2
{
    "nested_field": [
        {
            "paragraph_id": "2a",
            "nested_vector": [
                0.23,
                1.24,
                0.65
            ]
        }
    ],
    "topic": [
        "information_retrieval"
    ]
}

POST /retrievers_example_nested/_doc/3
{
    "topic": [
        "ai"
    ]
}

POST /retrievers_example_nested/_refresh

现在我们可以运行 rrf 检索器查询,并且还可以基于指定的 knn 查询,为 nested_field.nested_vector 字段计算 内部命中

 GET /retrievers_example_nested/_search {
    "retriever": {
        "rrf": {
            "retrievers": [
                {
                    "standard": {
                        "query": {
                            "nested": {
                                "path": "nested_field",
                                "inner_hits": {
                                    "name": "nested_vector",
                                    "_source": false,
                                    "fields": [
                                        "nested_field.paragraph_id"
                                    ]
                                },
                                "query": {
                                    "knn": {
                                        "field": "nested_field.nested_vector",
                                        "query_vector": [
                                            1,
                                            0,
                                            0.5
                                        ],
                                        "k": 10
                                    }
                                }
                            }
                        }
                    }
                },
                {
                    "standard": {
                        "query": {
                            "term": {
                                "topic": "ai"
                            }
                        }
                    }
                }
            ],
            "rank_window_size": 10,
            "rank_constant": 1
        }
    },
    "_source": [
        "topic"
    ]
}

这将把为 knn 查询定义的 inner_hits 传播到 rrf 检索器,并为 rrf 的顶部结果计算内部命中。

注意:如果使用多个 inner_hits,我们需要为每个 inner_hits 提供自定义名称,以便它们在请求中的所有检索器中都是唯一的。

检索器支持可组合性和大多数标准 _search 功能。例如,我们可以使用 rrf 检索器计算聚合。使用复合检索器时,聚合是基于其嵌套检索器计算的。在以下示例中,topic 字段的 terms 聚合将包括所有结果,而不仅仅是来自 2 个嵌套检索器的顶部 rank_window_size,即 year 字段大于 2023 并且 topic 字段与术语 elastic 匹配的所有文档。

 GET retrievers_example/_search {
    "retriever": {
        "rrf": {
            "retrievers": [
                {
                    "standard": {
                        "query": {
                            "range": {
                                "year": {
                                    "gt": 2023
                                }
                            }
                        }
                    }
                },
                {
                    "standard": {
                        "query": {
                            "term": {
                                "topic": "elastic"
                            }
                        }
                    }
                }
            ],
            "rank_window_size": 10,
            "rank_constant": 1
        }
    },
    "_source": false,
    "aggs": {
        "topics": {
            "terms": {
                "field": "topic"
            }
        }
    }
}

通过向请求添加 explain: true,每个检索器现在将提供一个详细的解释,说明计算最终分数所需的所有步骤和计算。在 explain 的上下文中完全支持可组合性,并且每个检索器都将提供自己的解释,如下例所示。

 GET /retrievers_example/_search {
    "retriever": {
        "rrf": {
            "retrievers": [
                {
                    "standard": {
                        "query": {
                            "term": {
                                "topic": "elastic"
                            }
                        }
                    }
                },
                {
                    "rrf": {
                        "retrievers": [
                            {
                                "standard": {
                                    "query": {
                                        "query_string": {
                                            "query": "(information retrieval) OR (artificial intelligence)",
                                            "default_field": "text"
                                        }
                                    }
                                }
                            },
                            {
                                "knn": {
                                    "field": "vector",
                                    "query_vector": [
                                        0.23,
                                        0.67,
                                        0.89
                                    ],
                                    "k": 3,
                                    "num_candidates": 5
                                }
                            }
                        ],
                        "rank_window_size": 10,
                        "rank_constant": 1
                    }
                }
            ],
            "rank_window_size": 10,
            "rank_constant": 1
        }
    },
    "_source": false,
    "size": 1,
    "explain": true
}

虽然输出有点冗长,但它将提供所有必要的信息来帮助调试和推理排名。

为了演示检索器的完整功能,以下示例还需要访问使用 Elastic 推理 API 设置的 语义重排序模型

在此示例中,我们将设置一个重排序服务,并将其与 text_similarity_reranker 检索器一起使用,以对我们的顶部结果进行重排序。

 PUT _inference/rerank/my-rerank-model {
 "service": "cohere",
 "service_settings": {
   "model_id": "rerank-english-v3.0",
   "api_key": "{{COHERE_API_KEY}}"
 }
}

首先,让我们对先前示例中 rrf 检索器的结果进行重排序。

 GET retrievers_example/_search {
    "retriever": {
        "text_similarity_reranker": {
            "retriever": {
                "rrf": {
                    "retrievers": [
                        {
                            "standard": {
                                "query": {
                                    "query_string": {
                                        "query": "(information retrieval) OR (artificial intelligence)",
                                        "default_field": "text"
                                    }
                                }
                            }
                        },
                        {
                            "knn": {
                                "field": "vector",
                                "query_vector": [
                                    0.23,
                                    0.67,
                                    0.89
                                ],
                                "k": 3,
                                "num_candidates": 5
                            }
                        }
                    ],
                    "rank_window_size": 10,
                    "rank_constant": 1
                }
            },
            "field": "text",
            "inference_id": "my-rerank-model",
            "inference_text": "What are the state of the art applications of AI in information retrieval?"
        }
    },
    "_source": false
}

对于此示例,我们将使用先前配置的 my-rerank-model 重排序器,将 rrf 的 standard 检索器替换为 text_similarity_reranker 检索器。由于这是一个重排序器,因此它需要一个初始的文档池才能工作。在这种情况下,我们将对与 ai 主题匹配的顶部 rank_window_size 文档进行重排序。

 GET /retrievers_example/_search {
    "retriever": {
        "rrf": {
            "retrievers": [
                {
                    "knn": {
                        "field": "vector",
                        "query_vector": [
                            0.23,
                            0.67,
                            0.89
                        ],
                        "k": 3,
                        "num_candidates": 5
                    }
                },
                {
                    "text_similarity_reranker": {
                        "retriever": {
                            "standard": {
                                "query": {
                                    "term": {
                                        "topic": "ai"
                                    }
                                }
                            }
                        },
                        "field": "text",
                        "inference_id": "my-rerank-model",
                        "inference_text": "Can I use generative AI to identify user intent and improve search relevance?"
                    }
                }
            ],
            "rank_window_size": 10,
            "rank_constant": 1
        }
    },
    "_source": false
}

完全的可组合性意味着我们可以将同一类型的多个检索器链接在一起。例如,假设我们有一个计算成本高昂的重排序器,该重排序器专门用于 AI 内容。我们可以使用另一个 text_similarity_reranker 检索器对 text_similarity_reranker 的结果进行重排序。每个重排序器可以在不同的字段上运行和/或使用不同的推理服务。

 GET retrievers_example/_search {
    "retriever": {
        "text_similarity_reranker": {
            "retriever": {
                "text_similarity_reranker": {
                    "retriever": {
                        "knn": {
                            "field": "vector",
                            "query_vector": [
                                0.23,
                                0.67,
                                0.89
                            ],
                            "k": 3,
                            "num_candidates": 5
                        }
                    },
                    "rank_window_size": 100,
                    "field": "text",
                    "inference_id": "my-rerank-model",
                    "inference_text": "What are the state of the art applications of AI in information retrieval?"
                }
            },
            "rank_window_size": 10,
            "field": "text",
            "inference_id": "my-other-more-expensive-rerank-model",
            "inference_text": "Applications of Large Language Models in technology and their impact on user satisfaction"
        }
    },
    "_source": false
}

请注意,我们的示例应用了两个重排序步骤。首先,我们使用 my-rerank-model 重排序器对来自 knn 搜索的前 100 个文档进行重排序。然后,我们选择前 10 个结果,并使用更精细的 my-other-more-expensive-rerank-model 对其进行重排序。

© . All rights reserved.