【LLM】Elasticsearch作为向量库入门指南

整理不易,请不要令色你的赞和收藏。

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 测试

代码运行结果:

6. 参考文档

相关推荐
帅帅梓1 小时前
docker图形化管理
docker·容器·eureka
努力搬砖的咸鱼8 小时前
容器之间怎么通信?Docker 网络全解析
网络·docker·云原生·容器
starandsea9 小时前
gitlab解决传过大文件后删除导致pack过大问题
大数据·elasticsearch·gitlab
大海绵啤酒肚11 小时前
EL(F)K日志分析系统
运维·elasticsearch·云计算
二向箔reverse11 小时前
用langchain搭建简单agent
人工智能·python·langchain
元直数字电路验证12 小时前
ASP.NET Core Web APP(MVC)开发中无法全局配置 NuGet 包,该怎么解?
前端·javascript·ui·docker·asp.net·.net
chinesegf14 小时前
Docker篇6-项目app.py和flask_app.service配置和映射到docker中
docker·容器·flask
闲人编程15 小时前
Docker化你的Python应用:从开发到生产
python·docker·eureka·开发·生产·codecapsule
水中加点糖15 小时前
使用LangChain+LangGraph自定义AI工作流,实现音视频字幕生成工具
人工智能·ai·langchain·工作流·langgraph
serve the people15 小时前
LangChain 提示模板之少样本示例(二)
langchain