正文
相比专门的向量库,ES作为向量库有它自己的优点,比如可以方便的使用全文检索及混合检索。当然,性能和数据规模方面可能有所不如,不过,对于中等规模的应用,倒不是太大的问题。
向量索引创建与查询
比如我要建一个名为knowledge的向量索引,相关创建与查询DSL如下(kibana dev-tool工作台里输入即可):
PUT knowledge
{
"mappings": {
"properties": {
"content": { "type": "text","analyzer": "standard" },
"content_vector": {
"type": "dense_vector",
"dims": 1024,
"index": true,
"similarity": "cosine" // 距离函数:l2_norm / cosine / dot_product
}
}
}
}
// 查看索引信息
GET /knowledge/_mapping
// 全量搜索
GET /knowledge/_search
{
"size": 10,
"fields": ["content"],
"query": { "match_all": {} }
}
// 文本搜索
GET /knowledge/_search
{
"query": {
"match": {
"content": "accept"
}
}
}
// 向量搜索
GET /knowledge/_search
{
"knn": {
"field": "content_vector",
"query_vector": [ -2.12,0.06,1.07,-0.90,......2.40,0.30,0.51 ], //这里省略了长向量内容
"k":5,
"num_candidates": 100
}
}
这里有个小疑问:我们能用knowledge索引存储不同嵌入模型生成的向量吗?比如有的模型向量维度是768、有的是384。
答案是否定的。向量长度dims必须等于嵌入模型生成的向量长度。
ES作为RAG的向量库
保存向量到ES
使用前文的knowledge索引存储向量,代码如下:
es = Elasticsearch("http://localhost:9200")
vectorstore = ElasticsearchStore(
es_connection=es,
index_name=VEC_INDEX,
embedding=embeddings, # 关键:指定本地模型,本例中是bge-m3
strategy="DenseVectorStrategy", # 纯稠密向量
## 必须明确指定文本和对应的向量字段,否则默认使用text作为文本字段名,vector作为向量字段名
query_field=TXT_FLD, # 文本字段
vector_query_field=VEC_FLD, # 向量字段
)
# 4. 写入
vectorstore.add_documents(splits)
LOGGER.info("persist to vectordb success")
用ES做向量检索
ES向量索引建好后,使用langchain就可以很方便的挂接到LLM上了,代码如下:
vectorstore = ElasticsearchStore(
es_connection=Elasticsearch("http://localhost:9200"),
index_name=VEC_INDEX,
embedding=embeddings, # 关键:指定本地模型,本例中是bge-m3
query_field=TXT_FLD, # 文本字段
vector_query_field=VEC_FLD, # 向量字段
)
# --------- 检索器 ---------
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
......
# --------- LLM(Ollama 本地 CPU 可跑) ---------
llm = Ollama(model="qwen2.5:1.5b")
# --------- 构建 RAG 链 ---------
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # stuff简单合并上下文
retriever=retriever,
return_source_documents=True)
ElasticsearchStore是langchain对ES作为向量库的封装,与其它类型的向量库(如chroma等)统一接口。
langchain内部使用前文提到的ES knn查询先得到一些知识,再将这些知识简单合并,组装为prompt提供给LLM,让LLM自己组织语言回答问题,这就是RAG。
用ES做混合检索
所谓混合检索,就是“文本检索+向量检索”同时进行的检索方式,当然,只是简单的合并两种检索的结果,效果并不好,因为两种方式的打分维度都不一样。一般说来,要利用所谓RRF(倒数排名融合)算法对两种检索的结果进行融合。
但这个RRF在ES8.11里是收费特性,直接使用会报错:
current license is non-compliant for [Reciprocal Rank Fusion (RRF)]
网上说升级到ES8.16+版本,用retriever机制可以解决:
"retriever": {
"rrf": {
"retrievers": [
{
"standard": {
"query": {
"match": {
"content": {
"query": q
}
}
}
}
},
{
"knn": {
"field": "content_vector",
"query_vector": vec,
"k": 3,
"num_candidates": 100
}
}
],
"rank_window_size": 100,
"rank_constant": 60
}
}
但ES升级并实测下来,依然报相同的错误。估计,RRF确实就是个收费特性。
当然,rrf算法本身不复杂,实现上,完全可以查两次ES,在内存里重排,这是示例代码:
from typing import List, Dict
def rrf(text_hits: List[Dict], vector_hits: List[Dict],
k: int = 60, top_n: int = 10) -> List[str]:
scores = {} # doc_id -> RRF 分数
for rank, hit in enumerate(text_hits, start=1):
scores[hit['_id']] = scores.get(hit['_id'], 0) + 1.0 / (k + rank)
for rank, hit in enumerate(vector_hits, start=1):
scores[hit['_id']] = scores.get(hit['_id'], 0) + 1.0 / (k + rank)
# 按分数倒序,取 top_n
return sorted(scores, key=lambda x: scores[x], reverse=true)[:top_n]
也可使用python的ranx三方库,该库提供了排名评估与融合的算法。
附录
正文的例子都是基于本地搭建的ES环境。
docker搭建ES+Kibana环境
参考网上的例子配置国内镜像仓:
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://docker.1panel.live",
"https://docker.m.daocloud.io",
"https://hub.rat.dev",
"https://docker.1panel.top",
"https://docker.ketches.cn",
"https://docker.chenby.cn",
"https://dockerpull.org",
"https://dockerhub.icu",
"https://docker.unsee.tech",
"https://mirrors.ustc.edu.cn",
"https://mirror.azure.cn"
]
}
接着拉取镜像,例如:
docker pull docker.1ms.run/elasticsearch:8.11.1
docker pull docker.1ms.run/kibana:8.11.1
最后,两个镜像用docker-compose组合运行(需先用apt install提前安装docker-compose):
# docker-compose.yml
version: "2.4"
services:
es:
image: docker.1ms.run/elasticsearch:8.11.1
container_name: es8
environment:
- discovery.type=single-node # 单节点
- ES_JAVA_OPTS=-Xms1g -Xmx1g
- xpack.security.enabled=false # 测试环境关闭 TLS/密码
volumes:
- esdata:/usr/share/elasticsearch/data
ports:
- "9200:9200"
networks:
- elk
kibana:
image: docker.1ms.run/kibana:8.11.1
container_name: kib8
depends_on:
- es
environment:
- ELASTICSEARCH_HOSTS=http://es:9200
ports:
- "5601:5601"
networks:
- elk
volumes:
esdata:
networks:
elk:
执行命令:
docker-compose up -d
-d表示后台执行,不加的话,信息都打在控制台上了。
通过下面网址登kibana即可:
http://localhost:5601/app/home#/
停掉服务用:
docker-compose stop
停掉服务,还要删掉容器、网络和卷,用:
docker-compose down
有时候,我们要使用同一组件的新版本,就需要用down命令把老的容器、网络和卷都删掉,仅用stop是不行的。
查看所有服务状态,用:
docker-compose ps
1071

被折叠的 条评论
为什么被折叠?



