Elasticsearch 在 RAG 中的检索机制

前言:

最近负责rag检索模块的开发中引入了es进行rag检索,现在就来更深入一点的了解一下es,本文将从底层数据结构出发,逐步深入到代码层面的查询构建,起完整的 ES 知识体系。内容基于真实项目实践,涵盖倒排索引、中文分词、向量检索、KNN 与 BM25 混合排序,以及文档级权限控制。

ES 是什么?

ES 是文档型数据库

ES 属于文档型数据库,数据以 JSON 文档形式存储。我比较熟悉mysql所以我进行对比和 MySQL 对应关系如下:

MySQLES
数据库集群(Cluster)
表(Table)索引(Index)
行(Row)文档(Document)
列(Column)字段(Field)
主键

ES 的字段类型一旦确定就很难修改。比如 textContent 定义成 text 就不能改成 keyword;向量维度定义成 2048 就不能改成 1024。所以创建索引前,必须通过 mapping 文件把结构定义清楚。

之前我对es索引其实有误解,其实es索引有两层概念对于上层来说,es索引可以类比于数据库表,索引其实就是一张表。对于底层来说倒排索引,HNSW索引才是用来加快检索。

Mapping 相当于 MySQL 的建表语句

  • 索引(Index):是 ES 里实际存储数据的容器,里面装着很多 JSON 文档。相当于 MySQL 的一张表。

  • Mapping:是描述这个索引里有哪些字段、每个字段是什么类型、要不要分词、要不要建索引的“设计图”。相当于 MySQL 的 CREATE TABLE 语句。

在项目中我把mapping放在了resource下,把 mapping 放 resources 下,是因为它是程序启动时要读取的配置文件,不是业务逻辑代码。这样可读、可版本管理、可自动初始化,也方便多环境保持一致。

属性用在哪作用
type所有字段字段类型,决定怎么存、怎么查
indexanchorTextfalsevectortrue是否建索引。text/keyword 默认 true;dense_vector 默认 false,必须手动开
analyzertextContentik_max_word写入时的分词器,切得细,索引更全
search_analyzertextContentik_smart查询时的分词器,切得粗,减少无关召回
dimsvector 设 2048向量维度,必须和 Embedding 模型输出一致
similarityvectorcosine向量相似度算法,用余弦相似度计算距离

type 定类型,index 管要不要建索引,analyzer/search_analyzer 管分词,dimssimilarity 管向量。

ES 为什么能检索得又快又准?

ES 实现毫秒级全文搜索的核心是 倒排索引,它建立“词 → 文档”的映射,避免全表扫描。

文档1:公司财务报销制度
文档2:财务报表分析

用中文分词器切分后,建立倒排索引:

"公司" → [文档1]
"财务" → [文档1, 文档2]
"报销" → [文档1]
"制度" → [文档1]
"报表" → [文档2]
"分析" → [文档2]

中文分词:IK 分词器的两种模式

中文没有空格,ES 默认分词器会把整句当成一个词,导致全文检索失效。IK 分词器解决此问题,包含两种模式:

模式用途切分粒度示例(“中华人民共和国人民大会堂”)
ik_max_word写入时用最细粒度中华人民共和国,中华人民,中华,华人,人民共和国,人民大会堂,人民大会,大会堂
ik_smart查询时用最粗粒度中华人民共和国,人民大会堂

写入用 ik_max_word:索引尽可能多的词,保证搜什么都能命中,提高召回率。
查询用 ik_smart:避免切太碎导致召回大量无关文档,提高精确率。

向量检索:语义相似度计算

ES 支持 dense_vector 类型字段,用于 KNN 相似度检索。

  • 写入时:文档切片通过 Embedding 模型转成向量(如 2048 维),存入 dense_vector 字段,建立 HNSW 向量索引。

  • 查询时:用户问题也必须通过同一个 Embedding 模型转成向量,然后用 knn 查询计算余弦相似度。

KNN 与 BM25 简介

在混合检索中,有两个核心算法:

KNN(K 近邻)
基于向量的语义检索。文档和问题都转成向量,计算相似度(如余弦相似度),返回最相似的 K 个。ES 底层用 HNSW 图索引加速,把复杂度从 O(N) 降到接近 O(log N)。KNN 管“意思像不像”,理解语义,同义词也能召回,但可能召回关键词不精确的文档。

BM25(Best Matching 25)
ES 默认的关键词相关性打分算法。根据词频(TF)、逆文档频率(IDF)和文档长度归一化计算得分。BM25 管“词对不对得上”,精确匹配关键词、计算快,但不理解语义,同义词搜不到。

为什么两者结合?
只用 KNN 可能召回语义相近但关键词不精确的文档;只用 BM25 则同义词搜不到。混合检索的思路:第一阶段用 KNN 扩大召回窗口,保证语义相关的文档都进候选集;第二阶段用 BM25 重新打分,让关键词精确匹配的文档排在前面。既保证召回率,又保证精确率。

构建混合检索查询

下面是一个完整的混合检索查询示例,包含 KNN 召回、关键词匹配、权限过滤和 BM25 重打分:

SearchResponse<EsDocument> response = esClient.search(s -> {
    s.index("knowledge_base");

    // ===== 1. KNN 向量召回 =====
    int recallK = topK * 30; // 扩大召回窗口
    s.knn(kn -> kn
        .field("vector")
        .queryVector(queryVector)
        .k(recallK)
        .numCandidates(recallK)
    );

    // ===== 2. 关键词匹配 + 权限过滤 =====
    s.query(q -> q.bool(b -> b
        .must(m -> m.match(ma -> ma
            .field("textContent").query(query)))
        .filter(f -> f.bool(bf -> bf
            .should(s1 -> s1.term(t -> t.field("isPublic").value(true)))
            .should(s2 -> s2.term(t -> t.field("userId").value(userDbId)))
            .should(s3 -> s3.terms(t -> t.field("allowedRoles").terms(tv -> tv.value(userRoles))))
            .should(s4 -> s4.terms(t -> t.field("allowedDepts").terms(tv -> tv.value(userDepts))))
            .should(s5 -> s5.terms(t -> t.field("allowedPositions").terms(tv -> tv.value(userPositions))))
            .minimumShouldMatch("1")
        ))
    ));

    // ===== 3. BM25 重打分 =====
    s.rescore(r -> r
        .windowSize(recallK)
        .query(rq -> rq
            .queryWeight(0.2d)          // 保留 20% KNN 分数
            .rescoreQueryWeight(1.0d)   // BM25 主导排序
            .query(rqq -> rqq.match(m -> m
                .field("textContent").query(query).operator(Operator.And)))
        )
    );

    s.size(topK);
    return s;
}, EsDocument.class);

总结

检索方式写入时查询时底层索引
关键词检索ik_max_word 细粒度分词ik_smart 粗粒度分词倒排索引
向量检索Embedding 模型转向量同一模型转向量HNSW 向量索引

ES 的混合检索能力让它成为 RAG 系统中不可替代的检索组件。理解倒排索引、分词器、向量索引、KNN 与 BM25 的底层原理,才能在遇到检索效果问题时有的放矢地优化。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值