Elasticsearch 具有全文检索能力和向量检索能力。全文检索适合解决“关键词匹配、分词、相关性排序”的问题,向量检索适合解决“语义相似、意思接近但表达不同”的问题。
在实际的 AI 应用,尤其是 RAG 知识库场景中,这两种检索方式通常不会孤立使用,而是会组合起来使用:
- 全文检索:找到和用户问题中关键词高度相关的内容
- 向量检索:找到语义上相似的内容
- 混合检索:同时利用关键词匹配和语义匹配,提高召回质量
本文单独介绍如何使用 Elasticsearch 实现全文检索和向量检索。
一、为什么 Elasticsearch 既能做全文检索,也能做向量检索?
Elasticsearch 最早是典型的全文搜索引擎,底层基于 Lucene,擅长处理倒排索引、分词、相关性评分等搜索场景。
随着 AI 和大模型应用的发展,Elasticsearch 也支持了向量字段和向量相似度搜索,可以将文本 embedding 之后的向量存储在 ES 中,并通过相似度算法进行检索。
所以现在的 Elasticsearch 可以同时支持两类数据:
{"content": "Elasticsearch 是一个分布式搜索和分析引擎","content_vector": [0.12, 0.31, -0.22]
}
其中:
content用于全文检索content_vector用于向量检索
二、准备索引结构
假设我们要构建一个知识库,每条文档切片包含以下字段:
doc_id:文档 IDchunk_id:切片 IDtitle:标题content:文本内容content_vector:文本向量metadata:额外元数据
可以创建如下索引:
PUT knowledge_chunks
{"mappings": {"properties": {"doc_id": {"type": "keyword"},"chunk_id": {"type": "keyword"},"title": {"type": "text","analyzer": "ik_max_word","search_analyzer": "ik_smart"},"content": {"type": "text","analyzer": "ik_max_word","search_analyzer": "ik_smart"},"content_vector": {"type": "dense_vector","dims": 1024,"index": true,"similarity": "cosine"},"metadata": {"type": "object","enabled": true},"created_at": {"type": "date"}}}
}
这里有几个关键点。
1. text 字段用于全文检索
"content": {"type": "text","analyzer": "ik_max_word","search_analyzer": "ik_smart"
}
如果是中文场景,通常会配合 IK 分词器:
ik_max_word:索引阶段尽可能细粒度分词,提高召回ik_smart:搜索阶段更智能地分词,减少噪音
2. dense_vector 字段用于向量检索
"content_vector": {"type": "dense_vector","dims": 1024,"index": true,"similarity": "cosine"
}
其中:
dims要和 embedding 模型输出维度一致index: true表示支持近似最近邻检索similarity: cosine表示使用余弦相似度
不同 embedding 模型的维度不同,比如 384、768、1024、1536 等,建索引时一定要保持一致。
三、写入文档和向量
在写入 ES 之前,需要先对文档进行切片,然后对每个切片生成 embedding。
例如原始文档是:
Elasticsearch 是一个分布式搜索和分析引擎,支持全文检索、结构化查询、聚合分析和向量检索。
经过 embedding 模型处理后,得到一个向量:
[0.12, -0.04, 0.33]
然后写入 ES:
POST knowledge_chunks/_doc/1
{"doc_id": "doc_001","chunk_id": "chunk_001","title": "Elasticsearch 简介","content": "Elasticsearch 是一个分布式搜索和分析引擎,支持全文检索、结构化查询、聚合分析和向量检索。","content_vector": [0.12, -0.04, 0.33],"metadata": {"source": "es-guide.md","page": 1},"created_at": "2026-06-20T10:00:00"
}
实际使用时,content_vector 会是完整维度的向量,这里只是简化示例。
四、使用 ES 进行全文检索
全文检索主要依赖 match、multi_match、bool 等查询。
1. 单字段全文检索
如果只搜索正文内容,可以这样写:
POST knowledge_chunks/_search
{"query": {"match": {"content": "Elasticsearch 如何进行全文检索"}}
}
ES 会对用户输入进行分词,然后到倒排索引中查找相关文档,并根据 BM25 算法计算相关性得分。
2. 多字段全文检索
在知识库场景中,标题和正文都可能命中,所以更常见的是 multi_match:
POST knowledge_chunks/_search
{"query": {"multi_match": {"query": "Elasticsearch 向量检索怎么做","fields": ["title^2","content"]}}
}
这里的:
"title^2"
表示标题字段权重更高。如果标题命中,得分会比正文命中更高。
3. 带过滤条件的全文检索
如果只想搜索某个文档、某个知识库、某个用户的数据,可以加 filter:
POST knowledge_chunks/_search
{"query": {"bool": {"must": [{"multi_match": {"query": "全文检索和向量检索的区别","fields": ["title^2","content"]}}],"filter": [{"term": {"doc_id": "doc_001"}}]}}
}
must 会影响相关性评分,filter 不参与评分,适合放权限、租户、文档 ID、知识库 ID 这类精确条件。
五、使用 ES 进行向量检索
向量检索的核心思路是:
- 用户输入一个问题
- 使用 embedding 模型将问题转换成向量
- 用问题向量到 ES 中查找最相似的文档向量
例如用户问题是:
ES 能不能做语义搜索?
经过 embedding 模型后得到:
[0.21, -0.13, 0.08]
然后使用 knn 查询:
POST knowledge_chunks/_search
{"knn": {"field": "content_vector","query_vector": [0.21, -0.13, 0.08],"k": 5,"num_candidates": 100},"_source": ["doc_id","chunk_id","title","content","metadata"]
}
参数说明:
field:向量字段名query_vector:用户问题的向量k:最终返回最相似的前 N 条num_candidates:候选数量,越大召回越充分,但查询成本也越高
一般来说:
num_candidates >= k
例如:
k = 5
num_candidates = 100
表示先从近似向量索引中召回 100 个候选,再返回最相似的 5 个。
六、带过滤条件的向量检索
实际业务中,向量检索通常也需要过滤。例如只检索某个知识库、某个用户、某个文件下的内容。
可以这样写:
POST knowledge_chunks/_search
{"knn": {"field": "content_vector","query_vector": [0.21, -0.13, 0.08],"k": 5,"num_candidates": 100,"filter": {"term": {"doc_id": "doc_001"}}},"_source": ["doc_id","chunk_id","title","content","metadata"]
}
这样 ES 只会在符合过滤条件的文档中进行向量相似度检索。
七、全文检索和向量检索的区别
全文检索和向量检索解决的是两类不同问题。
| 对比项 | 全文检索 | 向量检索 |
|---|---|---|
| 核心能力 | 关键词匹配 | 语义相似 |
| 底层方式 | 倒排索引 | 向量相似度 |
| 典型算法 | BM25 | cosine、dot_product、l2_norm |
| 优点 | 精确、可解释、适合关键词 | 能理解语义,适合同义表达 |
| 缺点 | 对同义词、改写表达不敏感 | 结果可解释性较弱,依赖 embedding 质量 |
| 适合场景 | 标题、术语、错误码、编号、关键词 | 问答、知识库、语义搜索、RAG |
举个例子。
用户搜索:
ES 怎么做语义搜索?
如果文档里写的是:
Elasticsearch 支持基于 dense_vector 的相似度召回。
全文检索可能因为关键词不完全匹配而得分不高。
但向量检索可以理解“语义搜索”和“相似度召回”之间的语义关系,从而更容易召回这条内容。
反过来,如果用户搜索:
ERROR_CODE_10086
这种精确错误码,全文检索通常会比向量检索更可靠。
八、混合检索:同时使用全文检索和向量检索
在 RAG 场景中,单独使用全文检索或者单独使用向量检索都可能有问题。
只用全文检索:
- 关键词不匹配时召回不到
- 同义表达、改写表达效果不好
只用向量检索:
- 对专有名词、编号、代码、错误码不一定稳定
- 结果可解释性弱
- embedding 模型质量会直接影响召回效果
所以更推荐使用混合检索。
常见做法有两种。
方案一:分别检索,再合并排序
先执行全文检索:
POST knowledge_chunks/_search
{"size": 10,"query": {"multi_match": {"query": "Elasticsearch 如何实现向量检索","fields": ["title^2","content"]}}
}
再执行向量检索:
POST knowledge_chunks/_search
{"knn": {"field": "content_vector","query_vector": [0.21, -0.13, 0.08],"k": 10,"num_candidates": 100}
}
然后在应用层合并结果:
- 根据
chunk_id去重 - 对全文检索分数和向量检索分数做归一化
- 按加权分数重新排序
- 取 Top N 结果交给大模型
伪代码如下:
List<SearchResult> textResults = elasticsearchFullTextSearch(query);
List<SearchResult> vectorResults = elasticsearchVectorSearch(queryVector);Map<String, SearchResult> merged = new HashMap<>();for (SearchResult result : textResults) {result.setTextScore(normalize(result.getScore()));merged.put(result.getChunkId(), result);
}for (SearchResult result : vectorResults) {SearchResult existing = merged.get(result.getChunkId());if (existing == null) {result.setVectorScore(normalize(result.getScore()));merged.put(result.getChunkId(), result);} else {existing.setVectorScore(normalize(result.getScore()));}
}List<SearchResult> finalResults = merged.values().stream().peek(result -> result.setFinalScore(result.getTextScore() * 0.4 + result.getVectorScore() * 0.6)).sorted(Comparator.comparing(SearchResult::getFinalScore).reversed()).limit(5).toList();
这种方式最灵活,也最容易控制业务逻辑。
方案二:使用 ES 的混合查询
部分 Elasticsearch 版本支持在一次查询中同时使用 query 和 knn:
POST knowledge_chunks/_search
{"size": 5,"query": {"multi_match": {"query": "Elasticsearch 如何实现向量检索","fields": ["title^2","content"]}},"knn": {"field": "content_vector","query_vector": [0.21, -0.13, 0.08],"k": 10,"num_candidates": 100}
}
这种方式写起来更简单,但是不同 ES 版本对混合查询的支持方式可能不同,实际使用时需要结合具体版本确认。
如果业务需要更精细的去重、归一化、重排序、权限过滤,通常还是建议在应用层分别检索后合并。
九、RAG 场景中的推荐流程
在知识库问答系统中,可以采用如下流程:
用户问题|v
问题改写 / 查询增强|v
生成 query embedding|v
全文检索 + 向量检索|v
结果合并去重|v
重排序 rerank|v
拼接上下文|v
调用大模型生成答案
其中 ES 主要负责:
- 存储文档切片
- 存储切片向量
- 执行全文检索
- 执行向量检索
- 支持过滤条件,比如知识库 ID、文件 ID、用户 ID
大模型主要负责:
- 理解用户问题
- 生成 query embedding
- 根据召回内容生成自然语言答案
十、在 Java 中如何调用
如果项目是 Java 或 Spring Boot,可以通过 Elasticsearch Java Client 调用。
全文检索示例:
SearchResponse<KnowledgeChunk> response = client.search(s -> s.index("knowledge_chunks").query(q -> q.multiMatch(m -> m.query("Elasticsearch 如何实现全文检索").fields("title^2", "content"))).size(5),KnowledgeChunk.class
);
向量检索示例:
SearchResponse<KnowledgeChunk> response = client.search(s -> s.index("knowledge_chunks").knn(knn -> knn.field("content_vector").queryVector(queryVector).k(5).numCandidates(100)).source(src -> src.filter(f -> f.includes("doc_id", "chunk_id", "title", "content", "metadata"))),KnowledgeChunk.class
);
如果使用 Spring AI,也可以把 Elasticsearch 作为 VectorStore 的实现,让框架帮我们封装一部分向量写入和相似度搜索逻辑。
十一、实践建议
1. 文档切片不要太大
如果切片太大,召回结果会包含很多无关内容;如果切片太小,又可能缺少上下文。
常见切片大小可以从下面范围开始调试:
chunk size: 500 ~ 1000 tokens
overlap: 50 ~ 150 tokens
具体大小要结合业务文档类型调整。
2. 向量维度必须和索引 mapping 一致
如果 mapping 中定义:
"dims": 1024
那么写入的每个向量都必须是 1024 维,否则会写入失败。
3. 不要只依赖向量检索
向量检索擅长语义召回,但对于以下内容,全文检索通常更可靠:
- 订单号
- 错误码
- 类名
- 方法名
- API 名称
- 专有名词
- 精确短语
所以生产环境更推荐混合检索。
4. 需要做权限过滤
如果是多用户、多租户知识库,检索时必须增加过滤条件,例如:
"filter": {"term": {"user_id": "user_001"}
}
避免用户检索到不属于自己的文档内容。
5. 检索后最好增加 rerank
全文检索和向量检索负责召回,召回结果不一定是最终最适合给大模型的内容。
可以在召回 Top 20 或 Top 50 后,再通过 rerank 模型重新排序,最后取 Top 5 或 Top 10 作为上下文。
十二、总结
Elasticsearch 现在不仅可以做传统的全文检索,也可以做向量检索,非常适合作为 RAG 知识库的检索底座。
全文检索适合解决关键词匹配问题,向量检索适合解决语义相似问题。两者各有优势,也各有短板。
在真实业务中,更推荐使用混合检索:
全文检索负责精确匹配
向量检索负责语义召回
应用层负责合并、去重、重排序
大模型负责基于上下文生成答案
这样既能保证关键词、术语、错误码等精确内容不丢失,也能提升用户自然语言提问时的语义召回效果。
对于 AI 知识库、智能问答、企业文档搜索等场景,Elasticsearch 的全文检索 + 向量检索组合,是一个非常实用且容易落地的方案。
作者:Work Hard Work Smart
出处:http://www.cnblogs.com/linlf03/
欢迎任何形式的转载,未经作者同意,请保留此段声明!
