整理不易,请不要令色你的赞和收藏。
1. 前言
这篇文章将介绍如何使用 docker-compose 安装 ES 和 Kibana,如何使用 ES 存储和查询向量数据。这里有全网最详细的 ES 作为向量库参数配置介绍,并且在文章最后会介绍如何使用 Langgraph 搭建一个文本嵌入,并实现相似性查询的工作流。
Elasticsearch 使用倒排索引来加速向量检索,快速定位包含特定向量的文档。在向量检索过程中,Elasticsearch 会根据查询向量的特征,通过倒排索引匹配相似的向量。一旦匹配到倒排索引中的文档,Elasticsearch会计算查询向量与匹配文档向量之间的相似度。常用的相似度计算方法包括余弦相似度、欧几里得距离等。
Elasticsearch 本身也是一个成熟的全文搜索引擎,支持 BM25、TF-IDF 等文本检索方法,结合向量搜索(k-NN、HNSW、ANN) 可作为提升 RAG 的查询精确度的一种方式。
2. 前提条件
- 已安装 Docker、docker compose。
3. 安装Elasticsearch
为了方便管理和功能,我们使用docker compose安装 Elasticsearch 、 Kibana 以及 IK分词器,如果你需要安装其他版本,请确保 Kibana 的版本和 ES 的版本一致。
3.1 创建docker-compose文件
首先我们先创建一个 docker-compose.yml 文件,文件内容如下:
PS:Docker Compose v2 默认使用最新的 Compose 文件格式,因此不再需要显式指定 version。
javascript
# version "3.9"
services:
elasticsearch:
image: elasticsearch:8.16.0
container_name: elasticsearch
environment:
- discovery.type=single-node
- ES_JAVA_OPTS=-Xms512m -Xmx512m
- xpack.security.enabled=false
volumes:
- es_data:/usr/share/elasticsearch/data
- ./plugins:/usr/share/elasticsearch/plugins # 挂载插件目录
ports:
- target: 9200
published: 9200
networks:
- elastic
command: >
bash -c "
if [ -d /usr/share/elasticsearch/plugins/analysis-ik ]; then
echo 'Removing existing analysis-ik plugin...';
rm -rf /usr/share/elasticsearch/plugins/analysis-ik;
fi;
./bin/elasticsearch-plugin install https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-8.16.0.zip -b &&
./bin/elasticsearch
"
kibana:
image: kibana:8.16.0
container_name: kibana
ports:
- target: 5601
published: 5601
depends_on:
- elasticsearch
networks:
- elastic
volumes:
es_data:
driver: local
networks:
elastic:
name: elastic
driver: bridge
docker-compose 中的参数解析请参考我之前的文章,这里不在赘述。
IK分词器通过 command 命令安装,二次安装的时候需要先手动删除 plungs 挂载目录,不然可能会报错。
3.2 启动服务
我们先验证配置是否正确:
bash
docker-compose -f docker-compose.yml config
启动服务:
bash
docker-compose up -d
查看是否启动成功:
bash
docker-compose ps
访问 Kibana:http://127.0.0.1:5601/

4. 向量数据嵌入到ES
4.1 创建索引
首先我们创建一个索引 **es-embedding-test 。**为了方便演示,维度设置为 8 。
javascript
PUT /es-embedding-test
{
"mappings": {
"properties": {
"vector": {
"type": "dense_vector",
"element_type": "float",
"dims": 8,
"index": true,
"similarity": "cosine",
"index_options": {
"type": "int8_hnsw"
}
},
"content": {
"type": "text"
}
}
}
}
4.1.1 参数介绍
索引中包含两个字段 vector (存储向量值)和 content(存储对应文本内容)。ES 中使用 **dense_vector
**数据类型定义。
-
element_type:用于编码向量的数据类型。详细见拓展1。
-
dims:为向量的维度,必须与嵌入模型的向量维度一致,最高支持4096维。常见的向量维度一般为768、1024、1536等。
-
index:设置为 true,用于启用 KNN 查询。
-
similarity:用于比较查询向量和文档向量的相似性函数,当 element_type 为 bit 时,默认使用 l2_norm 作为相似性度量方式,否则,默认使用 cosine。可选值:l2_norm(L2距离/欧几里得距离)、dot_product(计算两向量点积)、cosine(余弦)、max_inner_product(最大内积)。
-
index_options:用于配置 kNN 索引算法,这个参数只有在 index 设置为 true 时生效。HNSW 算法有两个内部参数,影响数据结构的构建方式。这些参数可以调整以提高结果准确性,但会以较慢的索引速度为代价。见拓展2。
4.1.2 拓展1
element_type 支持的数据类型说明:
-
float(默认):每个维度索引一个 4 字节(32 位)浮点数,适用于 高精度向量计算。
-
byte:每个维度索引一个 1 字节(8 位)整数,适用于 存储优化,降低内存占用,但精度可能有所损失。
-
bit:每个维度索引 1 位(二进制位),适用于 超高维向量 或 专门支持比特向量的模型,使用 bit时,维度数必须是 8 的倍数,且表示的是 比特数 而非常规的维度数
4.1.3 拓展2
index_options 高级配置参数说明:
-
type(必选):要使用的 kNN 算法类型,不同类型适用于不同的 搜索需求 和 存储优化。默认:int8_hnsw。见拓展3。
-
m(可选):控制 HNSW 图中每个节点的连接数,默认 16。适用于 type 为 hnsw、int8_hnsw、int4_hnsw 和 bbq_hnsw。
-
ef_construction(可选):控制索引构建时,每个节点考虑的最近邻候选数(默认 100),数值越大,索引构建时间增加,但 查询结果更精确。适用于 type 为 hnsw、int8_hnsw、int4_hnsw 和 bbq_hnsw。
-
confidence_interval(可选):量化向量的置信区间。仅适用于 int8_hnsw 、 int4_hnsw 、 int8_flat 和 int4_flat 索引类型。其可以是 0.90 和 1.0 之间(包括)的任何值或正好为 0 。当值为 0 时,表示应计算动态分位数以优化量化。当介于 0.90 和 1.0 之间时,此值限制在计算量化阈值时使用的值。例如,值为 0.95 时,在计算量化阈值时将仅使用中间 95%的值(例如,将忽略最高和最低 2.5%的值)。对于 int8 量化的向量默认为 1/(dims + 1) ,对于动态分位数计算默认为 0 和 int4 。
4.1.3 拓展3
index_options 的 type 索引类型可选值介绍。
ES 默认使用量化索引(Quantized Index)来优化存储和搜索高维向量。通常情况下高维浮点数向量(float32)会占用大量内存,并且在 最近邻搜索(NN Search) 过程中需要进行大量计算。量化索引通过将浮点向量转换为 更低精度的数据格式(如 int8、int4 或二进制位) 来减少存储需求,并加速搜索计算。
ES 支持以下三种量化策略:
-
int8 量化(1 字节整数):每个浮点数被转换为 int8(1 字节整数)。比原始 float32 版本减少 75%(4 倍)的内存占用。精度损失较小。
-
int4 量化(半字节整数):每个浮点数被转换为 int4(4 位整数,半字节)。比原始 float32 版本减少 87%(8 倍)的内存占用。精度损失较大。
-
bbq 量化(Better Binary Quantization):每个数值量化为 1 bit(二进制位)。比原始 float32 版本 **减少 96%(32 倍)**的内存占用。精度损失最大,但可通过 增加查询时的候选数(oversampling) 和 重排(reranking) 进行补偿。
如果要在 ES 中使用量化索引,可以设置您的索引类型为 int8_hnsw 、 int4_hnsw 或 bbq_hnsw 。当索引 float 向量时,默认索引类型为 int8_hnsw 。
以下是可选值对比:
|---------------|---------------------------------------------------|----------------------|-----------|------------------|
| 索引类型 | 描述 | 支持的 element_type | 存储优化 | 适用场景 |
| hnsw | HNSW(Hierarchical Navigable Small World)近似 kNN 搜索 | float、byte、bit | 无 | 适用于高效、可扩展的向量搜索 |
| int8_hnsw | HNSW + int8 量化 | float | 减少 4x 内存 | 节省存储,适用于大规模 kNN |
| int4_hnsw | HNSW + int4 量化 | float | 减少 8x 内存 | 进一步减少存储,占用更少 |
| bbq_hnsw (预览) | HNSW + 二进制量化 | float | 减少 32x 内存 | 超大规模向量数据,但损失较大精度 |
| flat | 暴力搜索(精确 kNN) | float、byte、bit | 无 | 小数据集,需要高精度匹配 |
| int8_flat | 暴力搜索 + int8 量化 | float | 减少 4x 内存 | 节省存储,但搜索速度较慢 |
| int4_flat | 暴力搜索 + int4 量化 | float | 减少 8x 内存 | 适用于存储受限 但搜索精确的场景 |
| bbq_flat (预览) | 暴力搜索 + 二进制量化 | float | 减少 32x 内存 | 超大规模向量搜索,但精度较低 |
4.2 嵌入索引文档
索引单个文档:
bash
POST /es-embedding-test/_doc
{
"content": "索引单个文档",
"vector": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
}
索引多个文档:
bash
POST /_bulk
{
"index": {
"_index": "es-embedding-test",
"_id": "2"
}
} {
"content": "这个是文档1",
"vector": [0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18]
} {
"index": {
"_index": "es-embedding-test",
"_id": "3"
}
} {
"content": "这个是文档2",
"vector": [0.21, 0.22, 0.23, 0.24, 0.25, 0.26, 0.27, 0.28]
} {
"index": {
"_index": "es-embedding-test",
"_id": "4"
}
} {
"content": "这个是文档3",
"vector": [0.31, 0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38]
} {
"index": {
"_index": "es-embedding-test",
"_id": "5"
}
} {
"content": "这个是文档4",
"vector": [0.41, 0.42, 0.43, 0.44, 0.45, 0.46, 0.47, 0.48]
}
4.3 相似性查询
使用 KNN 检索文档,Elasticsearch 使用 HNSW 算法来支持高效的 kNN 搜索,像大多数 kNN 算法一样,HNSW 是一种近似方法,它牺牲了结果准确性以换取速度的提升。
bash
POST /es-embedding-test/_search
{
"retriever": {
"knn": {
"field": "vector",
"query_vector": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8],
"k": 3,
"num_candidates": 5
}
}
}
参数解析:
-
k:top-k,返回的最相关结果数量。
-
num_candidates:可选,从每个分片选取的候选文档数(越大越精确,但性能开销越高)。
返回示例:
bash
{
"took": 79,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": 0.9999957,
"hits": [
{
"_index": "es-embedding-test",
"_id": "14R115UBPMUIQU8gkFHt",
"_score": 0.9999957,
"_source": {
"content": "索引单个文档",
"vector": [
0.1,
0.2,
0.3,
0.4,
0.5,
0.6,
0.7,
0.8
]
}
},
{
"_index": "es-embedding-test",
"_id": "2",
"_score": 0.9723628,
"_source": {
"content": "这个是文档1",
"vector": [
0.11,
0.12,
0.13,
0.14,
0.15,
0.16,
0.17,
0.18
]
}
},
{
"_index": "es-embedding-test",
"_id": "3",
"_score": 0.9716257,
"_source": {
"content": "这个是文档2",
"vector": [
0.21,
0.22,
0.23,
0.24,
0.25,
0.26,
0.27,
0.28
]
}
}
]
}
}
参数解析:
- _score:为相似度得分(余弦相似度)。
5. 搭建文本嵌入工作流
工作流使用的嵌入模型是阿里百炼的 text-embedding-v3 ,需要申请百炼的 api_key。
5.1 流程图
下面是工作流的流程图:

流程解析:
-
split_text:文本切割节点,将文档内容切分成文本块,这篇文章使用 Langchain 自带的RecursiveCharacterTextSplitter 切割方式,此外 Langchain 还提供一下方式:
-
store_embedding:向量存储节点,负责文本向量化并存储到 ES 中。
-
search_similar_text:相似性查询节点,负责进行相似性查询。
5.2 准备
5.2.1 安装python包
安装必要 python 包
javascript
# 安装Langchain、Langgraph
pip install langchain==0.3.0
pip install -U langgraph
# 安装langchain es包
pip install -U langchain-elasticsearch
5.2.2 重新建ES索引
上面测试的索引,向量维度为8,我们使用的向量模型维度为1024,需要重新创建。
javascript
# 先删除原来的
DELETE /es-embedding-test
# 新增
PUT /es-embedding-test
{
"mappings": {
"properties": {
"vector": {
"type": "dense_vector",
"dims": 1024,
"index": true,
"similarity": "cosine"
},
"content": {
"type": "text"
},
"chunk_id":{
"type":"integer"
}
}
}
}
5.3 代码
5.3.1 定义向量模型
model 为使用的嵌入模型名,dashscope_api_key 为百炼平台的 api_key。
python
embeddings = DashScopeEmbeddings(
model=embedding_model,
dashscope_api_key=api_key,
)
5.3.2 定义VectorStore
index_name 为 ES 索引名称。
python
vector_store = ElasticsearchStore(
embedding=embeddings,
index_name=index_name,
es_url="http://localhost:9200",
)
5.3.3 完整代码
python
from dataclasses import dataclass, field
from typing import List
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_core.documents import Document
from langchain_elasticsearch import ElasticsearchStore
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langgraph.graph import StateGraph
# 配置参数
api_key = ""
embedding_model = "text-embedding-v3"
index_name = "es-embedding-test"
# 初始化嵌入模型
embeddings = DashScopeEmbeddings(
model=embedding_model,
dashscope_api_key=api_key,
)
# 创建Elasticsearch向量存储
vector_store = ElasticsearchStore(
embedding=embeddings,
index_name=index_name,
es_url="http://localhost:9200",
)
# 定义工作流状态类
@dataclass
class WorkflowState:
query_text: str # 查询文本
doc_content: str # 待处理的文档内容
chunks: List[Document] = field(default_factory=list) # 存储分割后的文档块
results: List[Document] = field(default_factory=list) # 存储相似性搜索结果
# 定义文本分割节点
def split_text(state: WorkflowState) -> WorkflowState:
"""将文档内容分割成多个文本块"""
# 创建文本分割器
text_splitter = RecursiveCharacterTextSplitter(chunk_size=60, chunk_overlap=5)
# 分割文本并创建文档对象
texts = text_splitter.split_text(state.doc_content)
state.chunks = [Document(
page_content=text,
metadata={"chunk_id": i, "content": text}
) for i, text in enumerate(texts)]
print(f"文本分割完成,共生成 {len(state.chunks)} 个文本块")
return state
# 定义存储嵌入向量节点
def store_embedding(state: WorkflowState) -> WorkflowState:
"""将文本块的嵌入向量存储到Elasticsearch"""
try:
if state.chunks:
# 批量添加文档到向量存储
vector_store.add_documents(state.chunks)
print(f"成功存储 {len(state.chunks)} 个文档到Elasticsearch")
except Exception as e:
print(f"存储嵌入向量时出错: {str(e)}")
return state
# 定义相似性搜索节点
def search_similar_text(state: WorkflowState) -> WorkflowState:
"""在Elasticsearch中执行相似性搜索"""
try:
if state.query_text:
# 执行相似性搜索,返回最相似的5个结果
state.results = vector_store.similarity_search(state.query_text, k=5)
print(f"找到 {len(state.results)} 个相似文档")
except Exception as e:
print(f"执行相似性搜索时出错: {str(e)}")
return state
# 创建 LangGraph 工作流
graph = StateGraph(WorkflowState)
graph.add_node("split", split_text)
graph.add_node("store", store_embedding)
graph.add_node("search", search_similar_text)
graph.set_entry_point("split")
graph.add_edge("split", "store")
graph.add_edge("store", "search")
# 编译工作流
workflow = graph.compile()
# 运行工作流
query_text = "LangGraph"
doc_content = """
LangGraph 是一个强大的AI工作流库,用于构建和执行复杂任务。
它支持多种数据处理方式,并且可以与不同的存储系统集成。
LangGraph提供了灵活的工作流定义方式,可以轻松实现各种AI应用场景。
"""
initial_state = WorkflowState(query_text=query_text, doc_content=doc_content)
final_state = workflow.invoke(initial_state)
print("相似文本:", final_state["results"])
5.3.4 测试
代码运行结果:
