在上一篇文章中,我们梳理了 RAG 架构从死板的单路召回,演进到具备路由分发(Router)、多跳推理(Multi-Hop)和网络兜底(Web Fallback)的 Agentic RAG。
但在实际业务中,我们遇到了一些向量检索(Milvus)无法解决的痛点。例如:当用户搜索"马铃薯"时,纯语义检索可能会丢失"土豆"或"tomato"的精确匹配;面对专业术语和精确实体(如特定型号、人名全称),纯语义匹配经常出现偏差。
在本篇中,我们将引入 Elasticsearch (ES) ,构建 混合检索(Hybrid Search) 架构。正如图片笔记中所说: "混合检索 = ES 关键词检索 + Milvus 相似度检索。由模型统一融合多路结果,提升专业场景准确率。"
一、 为什么需要 Elasticsearch?
在 RAG 系统中,Mysql 存放原始数据 ,Milvus 提供语义相似度 ,而 ES 则是专职特种兵(关键词检索) 。
我们可以看一个实际的例子:
- 基于 Milvus 向量检索:用户搜"马铃薯",能召回"土豆"(语义相近)。
- 纯语义检索的缺陷:搜不到"tomato"(跨语言)或需要精确匹配的专业术语。
- MySQL
LIKE的问题:%内容%全表扫描,数据量一大性能就捉急。
因此,我们需要 ES 来解决基于关键词、全文检索的需求。
yaml
bash
# docker-compose.yml 编排配置说明
# docker-compose 是用于将多个容器编排在一起的工具
# 指令说明:
# - docker compose up -d : 启动容器,-d 表示后台运行(后台找目录下的 docker-compose.yml 文件并启动)
# - docker compose down : 停止容器
# - docker compose ps : 查看容器状态
为了让 ES 跑起来,我们通常会使用 Docker Compose。ES 的默认端口是 9200,而 kibana(类似于 MySQL 的 phpMyAdmin)则是它的可视化查看工具,默认端口 5601。
二、 核心原理:倒排索引 vs 正向索引
ES 相比 MySQL 最大的核心优势,在于其底层基于 倒排索引(Inverted Index) 。
-
MySQL(正向索引) :以行为单位存储完整数据。检索文本内容时,需要逐行遍历,逐字匹配。数据量庞大时,模糊/文本搜索越慢,性能极差。
-
ES(倒排索引) :
- 会自动对
text类型字段进行分词(Tokenization) 。 - 拆分成独立词条(Term)。
- 以词条为核心,反向关联所有包含该词条的文档。
- 用户输入关键词时,ES 直接通过词条快速匹配对应的文档,无需全表遍历,实现海量文本下的毫秒级全文检索。
- 会自动对
💡 举个例子 :
输入文本:"可以在浏览器中运行机器学习模型"。
ES 分词后建立索引:[浏览器, 机器学习, 模型, ...]。
用户搜索"机器学习",直接命中索引。
三、 ES 实战:Mapping、分词与 DSL 查询
在理解了原理后,我们直接进入实战。ES 提供了一套 RESTful API,其查询语言被称为 DSL (Domain Specific Language) 。这就如同 MySQL 的 SQL,只不过 ES 是通过 HTTP JSON 来查询的。
1. 创建索引与 Mapping
类似于 MySQL 的建表语句(Schema)。
json
json
// PUT /article (创建索引)
{
"mappings": {
"properties": {
"title": { "type": "text" }, // text 类型:自动分词建倒排索引,用于全文检索
"content": { "type": "text" }, // text 类型:自动分词建倒排索引,用于全文检索
"author": { "type": "keyword" } // keyword 类型:不分词,整体精确匹配(常用于过滤、聚合)
}
}
}
注释 :text 和 keyword 是 ES 中最常用的数据类型。text 会把"AI开发"分成"AI"和"开发",而 keyword 会保留"AI开发"整体,只能精确匹配。
2. 分词器:IK 中文分词器
中文不像英文天然有空格,ES 默认的 standard 分词器对中文极不友好,通常我们需要安装 IK 分词器。IK 分词器有两种模式:
json
json
// POST /_analyze
{
"analyzer": "ik_max_word",
"text": "Elasticsearch RAG 混合检索知识库"
}
注释 :ik_smart 为智能分词,粒度较粗;ik_max_word 则会尽量多的拆分出词组,粒度更细(例如会拆出"知识"、"知识库"),保证更全面的命中率。
3. 检索 API
ES 提供了丰富的检索方式。我们来看最核心的三种:
json
json
// 1. match: 全文检索查询
// 会对查询词进行分词,再去倒排索引中匹配
GET /article/_search
{
"query": {
"match": {
"field": "词"
}
}
}
// 2. term: 精确匹配查询
// 不会分词,要求字段值完全相同(通常用于 keyword 字段)
GET /article/_search
{
"query": {
"term": {
"author": "AI开发"
}
}
}
// 3. multi_match: 多字段版的 match 查询
// 同时在 title 和 content 中检索查询词
GET /article/_search
{
"query": {
"multi_match": {
"query": "RAG 向量检索",
"fields": ["title", "content"]
}
}
}
注释 :GET /_cat/indices 可以输出所有的索引并列表显示,类似于 SHOW TABLES;。
四、 架构落地:将 ES 整合入 Agentic RAG (LangGraph)
回到我们上篇文章的 rag-webfallback.mjs 等 Agentic RAG 架构。在 LangGraph 的 retrieveNode 节点中,我们不再仅仅调用 vectorStore.similaritySearchWithScore,而是并行发起 Milvus 和 ES 的检索。
以下是混合检索节点的伪代码实现逻辑:
javascript
php
// 检索节点:混合检索(向量 + 关键词)
const retrieveNode = async (state) => {
const { question, k } = state;
// 1. 走向量数据库 (Milvus)
const milvusPromise = vectorStore.similaritySearchWithScore(question, k);
// 2. 走 Elasticsearch (关键词检索)
const esPromise = esClient.search({
index: 'ebook_collection',
body: {
query: {
multi_match: {
query: question,
fields: ["title", "content"] // 根据ES的Mapping字段匹配
}
}
}
});
// 3. 并发执行,节省时间
const [vectorResults, esResults] = await Promise.all([milvusPromise, esPromise]);
// 4. 结果融合(融合策略可以是加权打分、RRF Reciprocal Rank Fusion 或交给 Rerank 模型)
const mergedDocs = fuseResults(vectorResults, esResults);
// 去重,保证上下文不冗余
const deduplicatedDocs = mergeUnique(mergedDocs);
return {
documents: deduplicatedDocs,
// ... 其他状态
};
};
在图片笔记中总结得非常到位:"基于 Langgraph 实现闭环的 Agentic RAG,也就是 Agent 自主决策要不要检索,用什么检索 (web search + milvus + es),信息够不够,效果怎么样,要不要重新搜索"。
五、 总结与展望
从简单的向量匹配到引入 Elasticsearch,我们的 RAG 系统脱胎换骨:
- 底层存储:MySQL 存储原始关系数据,ES 基于倒排索引应对全文关键字查询,Milvus 负责语义召回。
- API 交互 :通过 DSL (
match,term,multi_match) 精准控制关键词检索的粒度。 - 架构设计:将 ES 集成入 LangGraph 的节点中,实现混合检索的并发调用,结合评估节点(Evaluator)和网络搜索(Web Search)兜底。
具体业务场景必须设计具体的 Agentic RAG。理解了这个闭环的思路,你就能根据业务特点,像搭积木一样自由组合你的检索武器库,打造出真正能解决用户痛点的大模型应用!