Elasticsearch retrievers 通常与 Elasticsearch 8.16.0 一起正式发布!

作者:来自 Elastic Panagiotis Bailis

Elasticsearch 检索器经过了重大改进,现在可供所有人使用。了解其架构和用例。

在这篇博文中,我们将再次深入探讨检索器(retrievers)。我们已经在之前的博文中讨论过它们,从介绍到使用检索器进行语义重新排序。现在,我们很高兴地宣布,检索器已随 Elasticsearch 8.16.0 已正式发布,在这篇博文中,我们将从技术角度介绍如何实现它们,并有机会讨论新推出的功能!

检索器 - retrievers

检索器(retriever)的主要概念与最初版本相同;检索器是一个框架,它提供可以分层堆叠的基本构建块,以构建多阶段复杂检索和排名管道。例如,一个简单的 standard 检索器,它只返回所有文档:

GET retrievers_example/_search
{"retriever": {"standard": {"query": {"match_all": {}}}}
}

非常简单,对吧?除了 standard 检索器(本质上只是 standard query 搜索 API 元素的包装器)之外,我们还支持以下类型:

  • knn - 从 kNN(k 最近邻)搜索中返回排名靠前的文档
  • rrf - 根据 RRF(倒数排名融合)排名公式组合来自不同检索器的结果
  • text_similarity_reranker - 使用 rerank 类型推断端点对嵌套检索器的排名靠前结果进行重新排名

还可以在 Elasticsearch 文档中找到更多详细信息以及每个检索器的特定参数。

让我们首先简要介绍一些技术细节,这将有助于我们了解架构、发生了什么变化以及为什么所有这些以前的限制现在都已解除!

技术深入研究

我们想要解决的最重要的(也是要求的)问题之一是能够在任何嵌套级别使用任何检索器。无论这意味着将 2 个或更多 text_similarity_reranker 堆叠在一起,还是将 rrf 检索器与 text_similarity_reranker 一起在另一个 rrf 之上运行,或者你能想到的任何组合和嵌套,我们都希望确保这是可以用检索器表达的东西!

为了解决这个问题,我们对检索器执行计划进行了一些重大更改。到目前为止,检索器是作为 standard 搜索执行流程的一部分进行评估的,其中(在简化的场景中为了说明目的)我们两次接触分片(shard):

  • 一次用于查询分片并从每个分片中带回 from + size 文档,
  • 一次用于获取所有字段数据并执行任何其他操作(例如突出显示/highlighting)以获得真正的顶级 [from, from+size] 结果。

这是一个不错的线性执行流程,(相对)容易遵循,但如果我们想要执行多个查询、对不同的结果集进行操作等,就会引入一些重大限制。为了解决这个问题,我们在查询执行的早期阶段就转向了对检索器管道的所有子检索器进行急切评估。这意味着,如果需要,我们会以递归方式将任何检索器查询重写为更简单的形式,具体形式取决于检索器类型。

  • 对于非复合检索器(non-compound retrievers),我们重写的方式与在 standard 查询中类似,因为它们仍然可以遵循线性执行计划。
  • 对于复合检索器(compound retrievers),即在其他检索器之上操作的检索器,我们将它们展平为单个 rank_window_size 结果集,它本质上是一个 <doc, shard> 元组列表,表示此检索器的排名靠前的文档。

让我们通过以下(相当复杂的)检索器请求来看看它实际上是什么样子:

{"retriever": {"rrf": {                                                                                    [1]"retrievers": [{"knn": {                                                                        [2]"field": "emb1","query_vector_builder": {"text_embedding": {"model_id": "my-text-embedding-model","model_text": "LLM applications in information retrieval"}}}},{"standard": {                                                                   [3]"query": {"term": {"topic": "science"}}}},{"rrf": {                                                                        [4]"retrievers": [{"standard": {                                                       [5]"query": {"range": {"year": {"gte": 2020}}}}},{"knn": {                                                            [6]"field": "emb2","query_vector_builder": {"text_embedding": {"model_id": "my-text-embedding-model","model_text": "Vector scale on production systems"}}}}],"rank_window_size": 100,"rank_constant": 10}}],"rank_window_size": 10,"rank_constant": 1}}
}

上面的 rrf 检索器是一个复合检索器,因为它对其他一些检索器的结果进行操作,因此我们将尝试将其重写为更简单、扁平的 <doc, shard> 元组列表,其中每个元组指定一个文档和它所在的分片。此重写还将强制执行严格的排名,因此当前不支持不同的排序选项。

现在让我们继续识别所有组件并描述如何评估它的过程:

  • [1] 顶级 rrf 检索器;这是所有子检索器的父级,它将最后被重写和评估,因为我们首先需要知道每个子检索器的前 10 个结果(基于 rank_window_size)。
  • [2] 这个 knn 检索器是顶级 rrf 检索器的第一个子级,并使用嵌入服务(my-text-embedding-model)来计算将使用的实际查询向量。这将通过向嵌入服务发出异步请求来计算给定 model_text 的向量,从而将其重写为通常的 knn 查询。
  • [3] standard 检索器,也是顶级 rrf 检索器的子项的一部分,它返回与主题匹配的所有文档:science。
  • [4] 顶级 rrf 检索器的最后一个子项,也是需要展平的 rrf 检索器。
  • [5] [6] 与 [2] 和 [3] 类似,这些检索器是 rrf 检索器的直接子项,我们将为每个检索器获取前 100 个结果(基于 rrf 检索器的 rank_window_size [4]),使用 rrf 公式将它们组合起来,然后重写为真正的前 100 个结果的展平 <doc, shard> 列表。

检索器的更新执行流程现在如下:

  • 我们将从重写所有我们可以重写的叶子开始。这意味着我们将重写 knn 检索器 [2] 和 [6] 来计算查询向量,一旦我们有了它,我们就可以在树中向上移动一层。
  • 在下一个重写步骤中,我们现在准备评估嵌套的 rrf 检索器 [4],我们最终会将其重写为扁平化的 RankDocsQuery 查询(即 <doc, shard> 元组的列表)。
  • 最后,顶级 rrf 检索器 [1] 的所有内部重写步骤都将完成,因此我们应该准备好按照要求合并和排名真正的前 10 个结果。即使是这个顶级 rrf 检索器也会将自身重写为扁平化的 RankDocsQuery,稍后将用于继续执行 standard 线性搜索执行流程。

将以上所有内容可视化,我们有:

查看上面的示例,我们可以看到如何将分层检索器树异步重写为简单的 RankDocsQuery。这种简化为我们带来了良好的(也是我们想要的!)副作用,即最终执行具有明确排名的正常请求,除此之外,我们还可以执行我们选择的任何补充操作。

玩转(golden)检索器!

正如我们上面简要提到的,有了这一重构,我们现在可以支持大量额外的搜索功能!在本节中,我们将介绍一些示例和使用场景,但更多内容也可以在文档中找到。

我们从最受欢迎的功能 —— 组合性开始,也就是说,在检索器树的任何级别都可以使用任意检索器的选项。

组合性 - composabilty

在以下示例中,我们想执行一个语义查询(使用像 ELSER 这样的嵌入服务),然后结合这些结果与 knn 查询,使用 rrf 进行合并。最后,我们希望使用 text_similarity_reranker 检索器进行重新排名。表达上述操作的检索器如下所示:

GET /retrievers_example/_search
{"retriever": {"text_similarity_retriever": {"retriever": {"rrf": {"retrievers": [{"standard": {"query": {"semantic": {"field": "inference_field","query": "Can I use generative AI to identify user intent and improve search relevance?"}}}},{"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_text": "LLM applications on production search applications","inference_id": "my-reranker-model","rank_window_size": 10}},"_source": ["text","topic"]
}

聚合 - aggregation

回想一下,在我们讨论的重做中,我们将复合检索器重写为 RankDocsQuery(即扁平的显式排名结果列表)。然而,这并不妨碍我们计算聚合,因为我们还会跟踪复合检索器中的源查询。这意味着我们可以回退到下面的嵌套 standard 检索器,以根据两个嵌套检索器结果的并集正确计算 topic 字段的聚合。

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": ["text","topic"],"aggs": {"topics": {"terms": {"field": "topic"}}}
}

因此,在上面的例子中,我们将计算 topic 字段的术语聚合,其中年份字段大于 2023,或者文档具有与之关联的主题 elastic。

折叠 - collapsing

除了我们上面讨论的聚合选项之外,我们现在还可以折叠结果,就像我们对 standard 查询请求所做的那样。在下面的示例中,我们计算 rrf 检索器的前 10 个结果,然后将它们折叠在 year 字段下。与 standard 搜索的主要区别在于,这里我们只折叠排名靠前的结果,而不是嵌套检索器中的结果。

GET /retrievers_example/_search
{"retriever": {"rrf": {"retrievers": [{"text_similarity_reranker": {"retriever": {"standard": {"query": {"term": {"topic": "ai"}}}},"field": "text","inference_text": "Can I use generative AI to identify user intent and improve search relevance?","rank_window_size": 10,"inference_id": "my-reranker-model"}},{"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": "year_results","_source": ["text","year"]}},"_source": ["text","topic"]
}

分页 - pagination

正如文档中所述,复合检索器也支持分页。与 standard 查询相比,复合检索器有一个显著的区别,与上面的折叠类似,rank_window_size 参数是我们可以执行导航的整个结果集。这意味着,如果 from + size > rank_window_size,那么我们将不会返回任何结果(但我们仍会返回聚合)。

GET /retrievers_example/_search
{"retriever": {"rrf": {"retrievers": [{"standard": {"query": {"term": {"topic": "elastic"}}}},{"knn": {"field": "vector","query_vector":[0.23,0.67,0.89],"k": 3,"num_candidates": 5}}],"rank_window_size": 10,"rank_constant": 1}},"from": 2,"size": 2"_source": ["text","topic"]
}

在上面的例子中,我们将从两个嵌套检索器(standard 和 knn)的组合中计算前 10 个结果(如 rrf 的 rank_window_size 中定义),然后我们将通过查阅 from 和 size 参数来执行分页。因此,在这种情况下,我们将跳过前 2 个结果(from)并选择接下来的 2 个结果(size)。

现在考虑一个不同的场景,在上面的相同查询中,我们将改为使用 from:10 和 size:2。假设 rank_window_size 为 10,并且这些将是我们可以分页的所有结果,在跳过前 10 个结果后请求获取 2 个结果将超出可导航结果集,因此我们将返回空结果。在 rrf 检索器的文档中还可以找到其他示例和更详细的细分。

解释 - explain

我们知道能力越大,责任越大。鉴于我们现在可以任意组合检索器,因此可能很难理解为什么最终会首先返回某个结果,以及如何优化我们的检索策略。出于这个非常具体的原因,我们努力确保检索器请求的解释输出(即通过指定 explain: true)将传达所有子检索器的所有必要信息,以便我们能够正确理解导致结果最终排名的所有因素。以 Collapsing 部分中相当复杂的查询为例,第一个结果的解释如下所示:

{"_explanation":{"value": 0.8333334,"description": "sum of:","details": [{"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.0011925492,"description": "text_similarity_reranker match using inference endpoint: [my-awesome-rerank-model] on document field: [text] matching on source query ","details": [{"value": 0.3844723,"description": "weight(topic:ai in 1) [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": "doc [1] with an original score of [1.0] is at rank [1] from the following source queries.","details":[{"value": 1,"description": "found vector with calculated similarity: 1.0","details":[]}]}]}]}]}
}

仍然有点冗长,但它传达了有关文档为何位于特定位置的所有必要信息。对于顶级 rrf 检索器,我们指定了 2 个details 信息,每个嵌套检索器一个。第一个是 text_similarity_reranker 检索器,我们可以在其中看到重新排序操作的权重,第二个是 knn 查询,告知我们文档与查询向量的计算相似性。可能需要一点时间才能熟悉,但每个检索器都会确保输出你可能需要评估和优化搜索场景的所有信息!

结论

现在就这些了!我们希望您一直关注我们,并且喜欢这个主题!我们对 retriever 框架的发布以及我们现在可以支持的所有新用例感到非常兴奋!检索器是为了支持从非常简单的搜索到高级 RAG 和混合搜索场景而构建的!如上所述,请关注此空间,更多功能即将推出!

Elasticsearch 包含新功能,可帮助您为你的用例构建最佳搜索解决方案。立即开始免费云试用或在你的本地机器上试用 Elastic。

原文:Elasticsearch retrievers are generally available with Elasticsearch 8.16.0! - Search Labs

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/61199.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CH02_泛型

第2章&#xff1a;泛型 本章目标 理解泛型的概念 掌握泛型方法的定义与使用 掌握泛型类的定义与使用 掌握泛型接口的定义与使用 本章内容 泛型的概念 ​ 泛型(generic)是C# 2.0推出的新语法&#xff0c;并不是语法糖&#xff0c;它是专门为处理多段代码在不同的数据类型…

《设计模式》创建型模式总结

目录 创建型模式概述 Factory Method: 唯一的类创建型模式 Abstract Factory Builder模式 Prototype模式 Singleton模式 最近在参与一个量化交易系统的项目&#xff0c;里面涉及到用java来重构部分vnpy的开源框架&#xff0c;因为是框架的搭建&#xff0c;所以会涉及到像…

c++类对象练习

#include <iostream> #include <cstring>using namespace std;class mystring {char* buf; public:mystring(); //构造函数mystring(const char* str); //构造函数void show(); //输出函数void setmystr(const mystring str); //设置函数const char* getmystr() co…

CH03_反射

第3章&#xff1a;反射 本章目标 掌握反射的原理 熟悉反射的基本运用 本章内容 反射是什么 C# 编译运行过程 首先我们在VS点击编译的时候&#xff0c;就会将C#源代码编译成程序集 程序集以可执行文件 (.exe) 或动态链接库文件 (.dll) 的形式实现 程序集中包含有Microsoft …

多品牌摄像机视频平台EasyCVR视频融合平台+应急布控球:打造城市安全监控新体系

在当今快速发展的智慧城市和数字化转型浪潮中&#xff0c;视频监控技术已成为提升公共安全、优化城市管理、增强应急响应能力的重要工具。EasyCVR视频监控平台以其强大的多协议接入能力和多样化的视频流格式分发功能&#xff0c;为用户提供了一个全面、灵活、高效的视频监控解决…

深入理解 Maven 生命周期与常用命令:从编译到安装

Maven 是 Java 项目管理中不可或缺的工具之一&#xff0c;其核心功能包括依赖管理、项目构建和发布等。本文将围绕 Maven 的生命周期及常用命令&#xff0c;解析从项目编译到安装的完整流程&#xff0c;并结合实际案例帮助读者更好地掌握 Maven 的使用。 1. Maven 生命周期概述…

数据结构 (3)线性表的概念及其抽象数据类型定义

一、线性表的概念 定义&#xff1a;线性表是指具有相同数据类型的n个数据元素的有限序列。可以表示为L(a1,a2,…,ai,…,an)&#xff0c;其中a1是第一个元素&#xff0c;称为表头&#xff1b;an是最后一个元素&#xff0c;称为表尾。 特点&#xff1a; 有序性&#xff1a;线性表…

Java基础——继承和多态

目录 一、继承 继承的定义&#xff1a; 继承的基本用法&#xff1a; 如何调用父类的方法&#xff1f; 二、多态 多态性的好处 多态中的强制类型转换&#xff1a; 包的命名规则——域名倒叙 一、继承 继承的定义&#xff1a; 继承是面向对象编程中的一种机制&#xff0c…

【Zookeeper】一、Zookeeper的使命

摩尔定律揭示了集成电路每18个月计算性能就会增加一倍。 Zookeeper以Fast Paxos算法为基础。 在一个大型应用中&#xff0c;经常会按照功能边界将应用分为多个模块&#xff0c;这些模块可以分别独立部署。而要完成某一项具体的功能&#xff0c;不能仅靠其中一个模块&#xff…

vue3中父div设置display flex,2个子div重叠

在Vue 3中&#xff0c;若要设置父div使用flex布局并且使得2个子div重叠&#xff0c;可以在父div上使用样式display: flex以及position: relative&#xff0c;然后在子div上使用position: absolute来定位。 <template><div class"parent"><div class&…

Elasticsearch面试内容整理-分析与映射

在 Elasticsearch 中,分析(Analysis)和映射(Mapping)是数据处理和存储的核心部分。它们共同决定了数据如何被解析、存储以及如何被有效地搜索和查询。以下是关于分析和映射的详细介绍。 分析(Analysis) 分析是将文本数据转换为可以被 Elasticsearch 搜索的索引格式的过程…

播放器开发之ffmpeg 硬件解码方案

硬件编解码的概念 硬件编解码是⾮CPU通过烧写运⾏视频加速功能对⾼清视频流进⾏编解码&#xff0c;其中⾮CPU可包括GPU、FPGA或者 ASIC等独⽴硬件模块&#xff0c;把CPU⾼使⽤率的视频解码⼯作从CPU⾥分离出来&#xff0c;降低CPU的使⽤负荷&#xff0c;使得平台能 ⾼效且流畅…

Go 编译代码-分平台编译

要针对 Mac, Linux, 和 Windows 编译单个 main.go 文件&#xff0c;可以使用 Go 的交叉编译功能&#xff0c;通过设置环境变量 GOOS 和 GOARCH 来指定目标操作系统和架构。 编译命令 在项目目录下执行以下命令&#xff1a; 1. MacOS 编译 GOOSdarwin GOARCHamd64 go build …

使用Python和OpenCV连接并处理IP摄像头视频流

使用Python和OpenCV连接并处理IP摄像头视频流 随着智能设备的发展&#xff0c;越来越多的家庭和企业开始使用IP摄像头进行安全监控或远程查看。这些摄像头通常可以通过网络访问&#xff0c;提供了丰富的功能&#xff0c;如实时视频流、云台控制等。本文将详细介绍如何利用Pyth…

计算机毕业设计SparkStreaming+Kafka旅游推荐系统 旅游景点客流量预测 旅游可视化 旅游大数据 Hive数据仓库 机器学习 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

【C#】面向对象:矩形类计算周长和面积

文章目录 一、矩形类的设计与实现1.1 矩形类的属性1.2 矩形类的构造函数1.3 矩形类的方法1.4 代码实现1.4.1 运行 一、矩形类的设计与实现 题目&#xff1a;编写一个矩形类&#xff0c;私有数据成员为举行的长(Len)和宽(Wid)&#xff0c;无参构造函数将len和wid设置为0&#x…

上海市计算机学会竞赛平台2024年11月月赛丙组考勤系统

题目描述 在 Carol 的办公楼的入口处有一套刷卡系统&#xff0c;每个员工都有一张唯一的身份卡&#xff0c;他们每次进出大楼都要刷卡&#xff0c;而系统会依次记录每次刷卡的员工编号&#xff0c;员工和他的编号一一对应&#xff0c;且在一天内一共有 nn 次刷卡记录。 一个员…

【PyTorch][chapter 28] 揭秘 Transformer:缩放定律指南

概括 我们介绍了 LLM 的各种缩放定律&#xff0c;研究了模型损失如何随着训练数据和参数数量的增加而变化。讨论包括对用于解释 LLM 缩放定律的 IsoLoss 轮廓和 IsoFLOPs 切片的解释&#xff0c;从而为优化计算资源提供了见解。 最后&#xff0c;我们讨论了 FLOP 和 FLOPS 的概…

Android上运行Opencv(TODO)

在高通安卓平台上&#xff0c;确实可以通过 NDK 使用 OpenCV 并访问摄像头。NDK 提供了更高性能的计算能力&#xff0c;特别是在图像处理和计算密集型任务中&#xff0c;与 OpenCV 结合可以充分利用高通平台的硬件资源&#xff08;如 NEON SIMD 指令集和 GPU 加速&#xff09;。…

【GNU】gcc -g编译选项 -g0 -g1 -g2 -g3 -gdwarf

1、gcc -g的作用 GCC 的 -g 选项用于在编译时生成调试信息&#xff0c;这些信息会嵌入到生成的目标文件或可执行文件中&#xff0c;主要目的是为了支持调试器&#xff08;如 gdb&#xff09;对程序的调试工作。 1.1 生成调试信息 当你在编译代码时使用 -g 选项&#xff0c;GCC…