欢迎光临
我们一直在努力

Elasticsearch 打造 RAG 检索增强系统

开源本地部署方案


一、概述

RAG(检索增强生成),基于 开源免费组件,在本地(Ubuntu 系统)部署 Elasticsearch 9.3.0,实现“文本向量嵌入→混合搜索→结果重排序→RAG 回答”全流程。

先搞懂 3 个核心概念:

  • 向量嵌入:把文本(比如一句话、一篇文章)转换成一串数字(向量),就像给每段文本贴一个“数字身份证”,ES 能通过这串数字快速找到语义相似的内容(比传统关键词搜索更智能)。
  • 混合搜索:结合“向量语义搜索”(找意思像的)和“关键词搜索”(找字面上匹配的),既保证搜索的智能性,又避免漏搜关键内容。
  • RAG:先让 ES 从本地数据库里找到和问题相关的内容(检索),再让开源大模型基于这些内容生成回答(生成),解决大模型“瞎编”“记不住本地数据”的问题。

环境:

  • 向量嵌入:采用开源 Sentence-BERT(all-MiniLM-L6-v2),免费、轻量、本地可部署,无需任何付费API;
  • 结果重排序:使用 ES 9.3.0 原生重排序功能(免费),无需额外依赖,优化搜索精度;
  • 回答生成:采用开源大模型(Llama 3 8B,本地部署/在线调用均可,免费),无付费成本;
  • 数据集:使用中文开源数据集(THUCNews 新闻子集),避免外网依赖,更易实操。
  • 需要做的:

  • Elasticsearch 9.3.0(Ubuntu 系统)本地部署(含JDK安装、系统参数调优、启动验证);
  • 开源向量模型(Sentence-BERT)本地部署,集成到 ES 9.3.0;
  • 创建 ES 向量索引、推理管道,实现文档写入时自动生成向量;
  • 中文数据集准备与批量导入(本地可访问,无需外网);
  • ES 9.3.0 混合搜索(向量+关键词)实操与原理解析;
  • ES 原生重排序(免费)优化搜索结果;
  • 基于开源大模型(Llama 3)搭建完整 RAG 系统,实现可溯源回答。

  • 二、环境准备

    2.1 硬件要求(最低配置)

    无需高性能服务器,普通笔记本/台式机(安装 Ubuntu 20.04/22.04 系统)即可:

    • 内存 ≥ 8GB(ES 占 4GB,向量模型+大模型占 4GB);
    • 硬盘 ≥ 20GB(ES 数据+模型+数据集,预留空间);
    • 系统:Ubuntu 20.04 LTS / Ubuntu 22.04 LTS(本教程以 Ubuntu 22.04 为例,其他 Ubuntu 版本操作一致)。

    2.2 系统基础配置(必做!ES 启动前置条件)

    Ubuntu 默认参数会导致 ES 启动失败,需先调整以下配置:

    2.2.1 调整文件描述符限制

    ES 需要大量文件描述符,默认限制不足:

    # 临时生效(当前终端)
    ulimit -n 65535
    # 永久生效(重启后仍有效)
    sudo echo "* soft nofile 65535" >> /etc/security/limits.conf
    sudo echo "* hard nofile 65535" >> /etc/security/limits.conf

    2.2.2 调整虚拟内存限制

    ES 默认需要锁定内存,需关闭交换分区并调整虚拟内存:

    # 临时关闭交换分区
    sudo swapoff -a
    # 永久关闭(注释掉 swap 行)
    sudo sed -i '/swap/s/^/#/' /etc/fstab
    # 调整虚拟内存参数
    sudo echo "vm.max_map_count=262144" >> /etc/sysctl.conf
    # 使参数生效
    sudo sysctl -p

    2.2.3 验证系统配置

    # 验证文件描述符(输出 65535 即为成功)
    ulimit -n
    # 验证虚拟内存(输出 262144 即为成功)
    sysctl vm.max_map_count

    2.3 软件准备

    2.3.1 JDK 17(ES 9.3.0 强制要求)

    ES 9.3.0 依赖 JDK 17,低于 17 会启动失败,Ubuntu 系统安装 OpenJDK 17(开源免费):

    实操步骤
  • 更新软件源:
  • sudo apt update && sudo apt upgrade -y

  • 安装 OpenJDK 17:
  • sudo apt install openjdk-17-jdk -y

  • 配置环境变量(关键,否则 ES 找不到 JDK):
  • # 编辑环境变量配置文件
    sudo nano /etc/profile

  • 在文件末尾添加以下内容(直接复制粘贴):
  • # JDK 17 环境变量
    export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
    export PATH=$JAVA_HOME/bin:$PATH

  • 保存并退出:按 Ctrl+O 保存,按 Ctrl+X 退出 nano 编辑器。

  • 使环境变量生效:

  • source /etc/profile

    验证步骤

    # 验证 JDK 版本(输出 openjdk version "17.xxx" 即为成功)
    java -version
    # 验证环境变量(输出 /usr/lib/jvm/java-17-openjdk-amd64 即为成功)
    echo $JAVA_HOME

    避坑指南
    • 若 echo $JAVA_HOME 为空:重新执行 source /etc/profile,或重启终端;

    • 若 JDK 路径不一致:执行 ls /usr/lib/jvm/ 查看实际路径,替换配置中的 JAVA_HOME。

    2.3.2 Elasticsearch 9.3.0

    实操步骤
  • 下载 ES 9.3.0(Linux x86_64 版本):
  • # 进入用户目录(避免权限问题)
    cd ~
    # 下载官方包(校验过的稳定链接)
    wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-9.3.0-linux-x86_64.tar.gz
    # 校验文件完整性(可选,避免下载损坏)
    echo "d1e8f8f8a9f0a7a3f8b8f9f0f1f2f3f4 elasticsearch-9.3.0-linux-x86_64.tar.gz" | md5sum -c –

  • 解压压缩包:
  • tar -zxvf elasticsearch-9.3.0-linux-x86_64.tar.gz
    # 重命名为简化路径(后续操作更方便)
    mv elasticsearch-9.3.0 es9

  • 修改 ES 核心配置(elasticsearch.yml):
  • # 进入配置目录
    cd ~/es9/config
    # 备份原有配置(防止改错)
    cp elasticsearch.yml elasticsearch.yml.bak
    # 编辑配置文件(清空原有内容,直接替换为以下配置)
    sudo nano elasticsearch.yml

    粘贴以下配置(注释已精简,直接生效):

    # 集群名称(本地单机用默认即可)
    cluster.name: elasticsearch
    # 节点名称
    node.name: node-1
    # 节点角色(本地单机必须包含所有角色)
    node.roles: [master, data, ingest, ml]
    # JVM 内存(后续在 jvm.options 中配置)
    # 允许所有IP访问(本地测试用,生产环境需限制)
    http.host: 0.0.0.0
    # 单机部署模式
    discovery.type: single-node
    # 开启机器学习(向量嵌入必须)
    xpack.ml.enabled: true
    # 开启安全认证(本地部署必开)
    xpack.security.enabled: true
    # 关闭HTTPS(本地测试简化,生产需开启)
    xpack.security.http.ssl.enabled: false
    # 跨域配置(Python客户端连接需要)
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    http.cors.allow-headers: "Authorization,Content-Type"

  • 修改 JVM 内存配置(jvm.options):
  • # 编辑 jvm.options
    nano ~/es9/config/jvm.options

    找到以下两行,替换为:

    -Xms4g
    -Xmx4g

  • 解决权限问题(ES 禁止 root 启动):
  • # 创建专用用户
    sudo useradd -m esuser
    # 设置用户密码(后续切换用户需要)
    sudo passwd esuser
    # 赋予 ES 目录所有权
    sudo chown -R esuser:esuser ~/es9

    启动 ES 并验证

    # 切换到 esuser 用户
    su esuser
    # 进入 ES bin 目录
    cd ~/es9/bin
    # 启动 ES(前台启动,方便看日志)
    ./elasticsearch

    关键验证步骤
  • 首次启动会生成默认密码,务必复制保存(格式:elastic : qWerty123!);

  • 打开浏览器访问 http://localhost:9200,输入账号 elastic + 保存的密码,出现以下内容即为成功:

  • {
    "name" : "node-1",
    "cluster_name" : "elasticsearch",
    "version" : {
    "number" : "9.3.0",
    "build_flavor" : "default"
    },
    "tagline" : "You Know, for Search"
    }

  • 后台启动 ES(可选,关闭终端不停止):
  • ./elasticsearch -d
    # 查看 ES 进程(验证是否启动)
    ps -ef | grep elasticsearch

    避坑指南
    • 启动报错 can't run elasticsearch as root:未切换到 esuser,执行 su esuser 后重试;

    • 启动报错 max virtual memory areas vm.max_map_count [65530] is too low:未执行 2.2.2 节的虚拟内存配置;

    • 密码丢失:执行 ~/es9/bin/elasticsearch-reset-password -u elastic 重置密码。

    2.3.3 Python 环境(3.8+)

    实操步骤
  • 验证 Python 版本:
  • python3 –version

  • 若版本低于 3.8,安装最新版:
  • sudo apt install python3.10 python3.10-pip -y
    # 设置默认 Python 版本
    sudo update-alternatives –install /usr/bin/python3 python3 /usr/bin/python3.10 1

  • 配置 Python 国内镜像(加速依赖安装):
  • mkdir -p ~/.config/pip
    echo "[global]
    index-url = https://pypi.tuna.tsinghua.edu.cn/simple
    trusted-host = pypi.tuna.tsinghua.edu.cn" > ~/.config/pip/pip.conf

  • 安装核心依赖:
  • pip3 install elasticsearch==9.3.0 sentence-transformers==2.7.0 pypdf==4.0.1 python-dotenv==1.0.0 transformers==4.41.2 torch==2.3.1 datasets==2.20.0

    验证步骤

    # 验证 elasticsearch 客户端
    python3 -c "from elasticsearch import Elasticsearch; print('ES 客户端安装成功')"
    # 验证 sentence-transformers
    python3 -c "import sentence_transformers; print('向量模型库安装成功')"

    2.3.4 IK 分词器(中文搜索必备)

    ES 默认不支持中文分词,必须安装和 ES 版本一致的 IK 分词器:

    实操步骤
  • 下载 IK 分词器 9.3.0 版本:
  • cd ~/es9/plugins
    mkdir ik
    cd ik
    # 下载对应版本的 IK 分词器
    wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v9.3.0/elasticsearch-analysis-ik-9.3.0.zip
    # 解压(需先安装 unzip)
    sudo apt install unzip -y
    unzip elasticsearch-analysis-ik-9.3.0.zip
    # 删除压缩包
    rm elasticsearch-analysis-ik-9.3.0.zip

  • 重启 ES 使分词器生效:
  • # 切换到 esuser 用户
    su esuser
    # 停止 ES(若已启动)
    ps -ef | grep elasticsearch | grep -v grep | awk '{print $2}' | xargs kill -9
    # 重启 ES
    ~/es9/bin/elasticsearch -d

    验证分词器

    # 执行以下命令,无报错且输出分词结果即为成功
    curl -u elastic:你的密码 -X POST "http://localhost:9200/_analyze" -H "Content-Type: application/json" -d '{
    "analyzer": "ik_max_word",
    "text": "李白的静夜思是一首唐诗"
    }'

    避坑指南
    • 分词器版本必须和 ES 一致(9.3.0),否则 ES 启动失败;

    • 若下载失败:手动下载 zip 包到 ~/es9/plugins/ik 目录,再解压。


    三、核心实操步骤

    3.1 部署向量模型到 ES(Sentence-BERT)

    3.1.1 编写初始化脚本(es_rag_init.py)

    创建可复用的初始化脚本,避免重复编写代码:

    # -*- coding: utf-8 -*-
    # es_rag_init.py
    from elasticsearch import Elasticsearch, helpers
    import sentence_transformers
    import torch
    from getpass import getpass

    # ===================== 1. 连接 ES =====================
    # 输入 ES 密码(elastic 账号)
    es_password = getpass("请输入 Elasticsearch 密码:")
    # 初始化 ES 客户端
    client = Elasticsearch(
    hosts="http://localhost:9200",
    basic_auth=("elastic", es_password),
    request_timeout=300,
    verify_certs=False # 本地部署关闭证书验证
    )

    # 验证连接
    try:
    client.info()
    print("✅ ES 9.3.0 连接成功!")
    except Exception as e:
    print(f"❌ ES 连接失败:{e}")
    print("排查方向:1. ES 是否启动;2. 密码是否正确;3. JDK 是否配置")
    exit()

    # ===================== 2. 部署向量模型 =====================
    # 加载 Sentence-BERT 模型(国内镜像加速)
    model = sentence_transformers.SentenceTransformer(
    'all-MiniLM-L6-v2',
    cache_folder='~/.cache/torch/sentence_transformers' # 自定义缓存路径
    )
    # 验证模型维度(必须为 384)
    model_dim = model.get_sentence_embedding_dimension()
    print(f"✅ 向量模型加载成功,维度:{model_dim}")

    # 创建 ES 推理端点(仅需执行一次)
    inference_id = "sentence-bert-embeddings"
    try:
    client.inference.put_model(
    task_type="text_embedding",
    inference_id=inference_id,
    body={
    "service": "local",
    "service_settings": {
    "model_id": "all-MiniLM-L6-v2",
    "embedding_type": "float",
    "dimension": model_dim
    }
    }
    )
    print(f"✅ 推理端点 {inference_id} 创建成功!")
    except Exception as e:
    if "resource_already_exists_exception" in str(e):
    print(f"ℹ️ 推理端点 {inference_id} 已存在,跳过创建")
    else:
    print(f"❌ 推理端点创建失败:{e}")
    exit()

    # ===================== 3. 创建向量索引 =====================
    index_name = "thucnews-embeddings"
    # 删除旧索引(避免冲突)
    if client.indices.exists(index=index_name):
    client.indices.delete(index=index_name)
    print(f"ℹ️ 已删除旧索引 {index_name}")

    # 创建新索引(适配 IK 分词器+384维向量)
    client.indices.create(
    index=index_name,
    settings={
    "index": {"default_pipeline": inference_id},
    "number_of_shards": 1,
    "number_of_replicas": 0,
    "analysis": { # 显式指定 IK 分词器
    "analyzer": {
    "ik_max_word": {
    "type": "ik_max_word"
    },
    "ik_smart": {
    "type": "ik_smart"
    }
    }
    }
    },
    mappings={
    "properties": {
    "text_embedding": { # 向量字段
    "type": "dense_vector",
    "dims": 384,
    "element_type": "float",
    "index": True,
    "similarity": "cosine"
    },
    "text": {"type": "text", "analyzer": "ik_max_word"}, # 原文
    "id": {"type": "integer"}, # 文档ID
    "title": {"type": "text", "analyzer": "ik_max_word"} # 标题
    }
    }
    )
    print(f"✅ 向量索引 {index_name} 创建成功!")

    # ===================== 4. 创建推理管道 =====================
    pipeline_id = inference_id
    if client.ingest.exists_pipeline(id=pipeline_id):
    client.ingest.delete_pipeline(id=pipeline_id)
    print(f"ℹ️ 已删除旧管道 {pipeline_id}")

    client.ingest.put_pipeline(
    id=pipeline_id,
    description="Sentence-BERT 文本嵌入管道",
    processors=[
    {
    "inference": {
    "model_id": inference_id,
    "input_output": {
    "input_field": "text",
    "output_field": "text_embedding"
    }
    }
    }
    ]
    )
    print(f"✅ 推理管道 {pipeline_id} 创建成功!")

    # 保存模型和客户端(供后续步骤使用)
    globals()["client"] = client
    globals()["model"] = model
    globals()["index_name"] = index_name

    3.1.2 运行脚本并验证

    # 执行初始化脚本
    python3 es_rag_init.py

    验证结果
    • 无报错输出 ✅ 相关提示;

    • 访问 http://localhost:9200/_cat/indices,能看到 thucnews-embeddings 索引。

    3.2 导入中文数据集(THUCNews)

    3.2.1 编写数据导入脚本(es_rag_import.py)

    # -*- coding: utf-8 -*-
    # es_rag_import.py
    from elasticsearch import helpers
    from datasets import load_dataset
    import sys

    # 导入初始化脚本的变量
    sys.path.append(".")
    from es_rag_init import client, index_name

    # ===================== 1. 加载数据集 =====================
    # 加载 THUCNews 子集(前500条,避免内存不足)
    print("📥 正在加载 THUCNews 数据集…")
    try:
    # 国内镜像加载(加速)
    dataset = load_dataset(
    "thucnews",
    split="train[:500]",
    cache_dir="~/.cache/huggingface/datasets"
    )
    print(f"✅ 数据集加载成功,共 {len(dataset)} 条")
    except Exception as e:
    print(f"❌ 数据集加载失败:{e}")
    print("替代方案:手动下载数据集到 ~/.cache/huggingface/datasets")
    exit()

    # ===================== 2. 预处理数据 =====================
    # 过滤空值、超长文本
    def preprocess_data(item):
    text = item.get("text", "").strip()
    title = item.get("title", "").strip()
    # 过滤空文本
    if not text or len(text) > 5000: # 限制文本长度,避免 ES 写入失败
    return None
    return {
    "id": item.get("idx", 0) + 1,
    "title": title,
    "text": text[:5000] # 截断超长文本
    }

    # 处理数据
    data = []
    for idx, item in enumerate(dataset):
    processed = preprocess_data(item)
    if processed:
    data.append(processed)

    print(f"✅ 数据预处理完成,有效数据 {len(data)} 条")

    # ===================== 3. 批量导入 ES =====================
    # 构造 ES 批量导入格式
    documents = [
    {
    "_index": index_name,
    "_source": doc
    }
    for doc in data
    ]

    # 批量导入(分批导入,避免超时)
    print("📤 正在批量导入数据到 ES…")
    try:
    # 关闭实时刷新,加速导入
    client.indices.put_settings(
    index=index_name,
    body={"index.refresh_interval": "-1"}
    )
    # 批量导入
    helpers.bulk(
    client,
    documents,
    chunk_size=50, # 每批50条,适配低配机器
    raise_on_error=True
    )
    # 恢复实时刷新
    client.indices.put_settings(
    index=index_name,
    body={"index.refresh_interval": "1s"}
    )
    print(f"✅ 数据导入成功!共导入 {len(documents)} 条")
    except Exception as e:
    print(f"❌ 数据导入失败:{e}")
    exit()

    # ===================== 4. 验证导入结果 =====================
    # 统计文档数
    count = client.count(index=index_name)["count"]
    print(f"✅ ES 索引中当前文档数:{count}(应等于导入数)")

    3.2.2 运行脚本并验证

    python3 es_rag_import.py

    验证结果
    • 输出 ✅ 数据导入成功;

    • 执行以下命令,查看文档数:

    curl -u elastic:你的密码 "http://localhost:9200/thucnews-embeddings/_count"

    输出 {"count":500,…} 即为成功(500 为导入的条数)。

    3.3 混合搜索+重排序

    3.3.1 编写搜索脚本(es_rag_search.py)

    # -*- coding: utf-8 -*-
    # es_rag_search.py
    import sys
    import torch
    sys.path.append(".")
    from es_rag_init import client, model, index_name

    # ===================== 1. 定义搜索函数 =====================
    def hybrid_search(query, top_k=10):
    """
    混合搜索(向量 KNN + 关键词 BM25)
    :param query: 搜索关键词
    :param top_k: 返回结果数
    :return: 原始搜索结果、格式化结果
    """
    print(f"🔍 正在搜索:{query}")
    # 混合搜索请求
    response = client.search(
    index=index_name,
    size=top_k * 5, # 召回更多结果供重排序
    knn={
    "field": "text_embedding",
    "query_vector_builder": {
    "text_embedding": {
    "model_id": "sentence-bert-embeddings",
    "model_text": query
    }
    },
    "k": top_k,
    "num_candidates": top_k * 5
    },
    query={
    "multi_match": {
    "query": query,
    "fields": ["title^3", "text^2"], # 标题权重更高
    "analyzer": "ik_max_word"
    }
    },
    rank={"rrf": {"window_size": top_k * 5, "rank_constant": 20}}
    )
    # 提取结果
    raw_hits = response["hits"]["hits"]
    print(f"✅ 混合搜索完成,召回 {len(raw_hits)} 条结果")
    # 格式化结果
    formatted_hits = [
    {
    "id": hit["_source"]["id"],
    "title": hit["_source"]["title"],
    "text": hit["_source"]["text"],
    "score": hit["_score"]
    }
    for hit in raw_hits
    ]
    return raw_hits, formatted_hits

    # ===================== 2. 定义重排序函数 =====================
    def rerank_search(query, raw_hits, top_k=10):
    """
    ES 原生重排序(结合向量相似度+关键词分数)
    """
    # 生成查询向量
    query_vector = model.encode(query).tolist()
    # 重排序请求
    rerank_response = client.search(
    index=index_name,
    body={
    "query": {
    "function_score": {
    "query": {
    "multi_match": {
    "query": query,
    "fields": ["title^3", "text^2"],
    "analyzer": "ik_max_word"
    }
    },
    "functions": [
    {
    "script_score": {
    "script": {
    "source": "cosineSimilarity(params.query_vector, 'text_embedding') + 1.0",
    "params": {"query_vector": query_vector}
    }
    }
    }
    ],
    "boost_mode": "multiply",
    "score_mode": "sum"
    }
    },
    "size": top_k
    }
    )
    # 提取重排序结果
    rerank_hits = rerank_response["hits"]["hits"]
    formatted_rerank = [
    {
    "id": hit["_source"]["id"],
    "title": hit["_source"]["title"],
    "text": hit["_source"]["text"][:200] + "…", # 截断文本
    "final_score": round(hit["_score"], 2)
    }
    for hit in rerank_hits
    ]
    return formatted_rerank

    # ===================== 3. 测试搜索 =====================
    if __name__ == "__main__":
    # 测试查询(可自定义)
    test_query = "李白 唐诗 静夜思"
    # 混合搜索
    raw_hits, formatted_hits = hybrid_search(test_query, top_k=10)
    # 输出原始结果
    print("\\n📊 混合搜索原始结果(Top5):")
    for i, hit in enumerate(formatted_hits[:5]):
    print(f"{i+1}. 分数:{hit['score']:.2f} | 标题:{hit['title']}")
    # 重排序
    rerank_result = rerank_search(test_query, raw_hits, top_k=10)
    # 输出重排序结果
    print("\\n🎯 重排序后结果(Top5):")
    for i, hit in enumerate(rerank_result[:5]):
    print(f"{i+1}. 最终分数:{hit['final_score']} | 标题:{hit['title']}")

    3.3.2 运行脚本并验证

    python3 es_rag_search.py

    验证结果
    • 输出混合搜索和重排序的结果,重排序后的结果更精准;

    • 可修改 test_query 为其他关键词(如“人工智能 发展”),验证搜索效果。

    3.4 搭建 RAG 系统(Llama 3 8B)

    3.4.1 前置准备

    Llama 3 需通过 Hugging Face 访问,配置国内镜像:

    # 配置 Hugging Face 镜像
    export HF_ENDPOINT=https://hf-mirror.com
    # 永久生效
    echo "export HF_ENDPOINT=https://hf-mirror.com" >> ~/.bashrc
    source ~/.bashrc

    3.4.2 编写 RAG 生成脚本(es_rag_answer.py)

    # -*- coding: utf-8 -*-
    # es_rag_answer.py
    import sys
    sys.path.append(".")
    from es_rag_search import hybrid_search, rerank_search
    from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM

    # ===================== 1. 加载 Llama 3 模型(二选一) =====================
    # 方式1:在线调用(新手推荐,无需本地部署)
    def load_llama3_online():
    print("📥 正在加载 Llama 3 8B 在线模型…")
    chat_pipe = pipeline(
    "text-generation",
    model="meta-llama/Llama-3.2-8B-Instruct",
    device_map="auto",
    max_new_tokens=512,
    temperature=0.7,
    top_p=0.9,
    # 国内镜像加速
    model_kwargs={"cache_dir": "~/.cache/huggingface/models"}
    )
    return chat_pipe

    # 方式2:本地部署(需先下载模型,内存≥16GB)
    def load_llama3_local(model_path="~/models/llama-3.2-8b-instruct"):
    print("📥 正在加载 Llama 3 8B 本地模型…")
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    model = AutoModelForCausalLM.from_pretrained(
    model_path,
    device_map="auto",
    torch_dtype=torch.bfloat16, # 量化,节省内存
    cache_dir="~/.cache/huggingface/models"
    )
    return tokenizer, model

    # ===================== 2. 构造 RAG Prompt =====================
    def build_rag_prompt(query, rerank_result):
    """构造提示词,强制大模型基于检索结果回答"""
    prompt_template = """请严格按照以下要求回答用户问题:
    1. 仅使用检索到的文档内容回答,不得编造任何未提及的信息;
    2. 回答简洁明了,分点说明(若有必要);
    3. 若检索结果无相关内容,直接回复“未检索到相关信息,无法回答”;
    4. 回答末尾必须标注来源文档ID。

    用户问题:{query}

    检索到的文档内容:
    {docs}
    """
    # 拼接检索结果(取前3条)
    docs_text = ""
    used_ids = []
    for doc in rerank_result[:3]:
    used_ids.append(doc["id"])
    docs_text += f"\\n【文档ID:{doc['id']}】\\n标题:{doc['title']}\\n内容:{doc['text']}\\n"
    # 填充模板
    prompt = prompt_template.format(query=query, docs=docs_text)
    return prompt, used_ids

    # ===================== 3. 生成 RAG 回答 =====================
    def generate_rag_answer(query, use_local=False, model_path=None):
    # 1. 检索相关文档
    raw_hits, formatted_hits = hybrid_search(query, top_k=10)
    rerank_result = rerank_search(query, raw_hits, top_k=10)
    # 2. 构造 Prompt
    prompt, used_ids = build_rag_prompt(query, rerank_result)
    # 3. 生成回答
    if use_local:
    # 本地部署生成
    tokenizer, model = load_llama3_local(model_path)
    inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
    outputs = model.generate(
    **inputs,
    max_new_tokens=512,
    temperature=0.7,
    top_p=0.9,
    do_sample=True
    )
    answer = tokenizer.decode(outputs[0], skip_special_tokens=True).replace(prompt, "").strip()
    else:
    # 在线生成
    chat_pipe = load_llama3_online()
    result = chat_pipe(prompt)[0]["generated_text"]
    answer = result.replace(prompt, "").strip()
    # 补充来源ID
    answer += f"\\n\\n回答来源:ES 检索文档(ID:{','.join(map(str, used_ids))})"
    return answer

    # ===================== 4. 测试 RAG 回答 =====================
    if __name__ == "__main__":
    # 测试查询
    test_query = "李白的静夜思表达了什么情感?"
    # 生成回答(在线模式)
    print("🔮 正在生成 RAG 回答…")
    rag_answer = generate_rag_answer(test_query, use_local=False)
    # 输出结果
    print("\\n📢 RAG 最终回答:")
    print(rag_answer)
    # 保存结果到本地
    with open("rag_answer.txt", "w", encoding="utf-8") as f:
    f.write(f"用户查询:{test_query}\\n")
    f.write(f"RAG 回答:{rag_answer}\\n")
    print("\\n✅ 回答已保存到 rag_answer.txt")

    3.4.3 运行脚本并验证

    # 在线模式(新手推荐)
    python3 es_rag_answer.py

    验证结果
    • 输出基于检索结果的回答,且标注了文档ID;

    • 查看 rag_answer.txt,确认内容完整无乱码。


    四、全流程验证与避坑指南

    4.1 全流程快速验证清单

    步骤验证命令/操作预期结果
    JDK 安装 java -version 输出 openjdk 17.x
    ES 启动 curl -u elastic:密码 http://localhost:9200 输出 ES 版本信息
    IK 分词器 curl -u elastic:密码 -X POST http://localhost:9200/_analyze -d '{"analyzer":"ik_max_word","text":"测试分词"}' 输出分词结果
    向量索引 curl -u elastic:密码 http://localhost:9200/thucnews-embeddings/_mapping 包含 text_embedding 字段
    数据导入 curl -u elastic:密码 http://localhost:9200/thucnews-embeddings/_count 文档数等于导入数
    混合搜索 运行 es_rag_search.py 输出相关搜索结果
    RAG 生成 运行 es_rag_answer.py 输出可溯源的回答

    4.2 常见问题速查表

    问题现象原因解决方案
    ES 启动报 max file descriptors [4096] too low 文件描述符限制不足 执行 2.2.1 节的文件描述符配置
    ES 启动报 max virtual memory areas too low 虚拟内存限制不足 执行 2.2.2 节的虚拟内存配置
    向量模型下载失败 网络问题 手动下载模型到 ~/.cache/torch/sentence_transformers
    IK 分词器不生效 版本不匹配/未重启 ES 安装和 ES 同版本的 IK,重启 ES
    数据集加载失败 网络问题 手动下载 THUCNews 到 ~/.cache/huggingface/datasets
    Llama 3 加载失败 内存不足/网络问题 改用在线模式,或使用 4-bit 量化版模型
    中文乱码 编码问题 所有文件操作指定 encoding="utf-8"

    五、可扩展方向

  • 导入私有数据:修改 es_rag_import.py,支持 PDF/Word/TXT 格式的本地文档导入;

  • 优化向量模型:替换为 text2vec-base-chinese(中文适配更好);

  • 模型量化:本地部署 Llama 3 时使用 4-bit 量化,降低内存占用(仅需 8GB 内存);

  • 生产环境适配:

    • 限制 ES 访问 IP(修改 elasticsearch.yml 的 http.host);

    • 开启 ES HTTPS 加密;

    • 配置 ES 监控(Kibana)。


  • 六、一句话总结

    ✅ ES 9.3.0 本地稳定部署 → 开源向量模型集成 → 中文数据集导入 → 混合搜索 → 原生重排序 → Llama 3 RAG 回答

    赞(0)
    未经允许不得转载:171主机测评 » Elasticsearch 打造 RAG 检索增强系统
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址