针对自己博客定制的私域知识问答系统

12/8/2024 RAGMilvusBGE-M3-EmbeddingBGE-ReRanker-V2-M3流式输出

# 1. 项目简介

# 1.1 基本介绍

针对自己的 Quantum Bit (opens new window) 博客构建的私域知识问答系统,主要特性如下:

  • 数据切片:针对博客文章的特性进行定制化的精准解析与分割,可得到高质量的数据切片,支持增量构建索引。
  • 检索系统:采用Milvus向量检索库,利用bge-m3、bge-reranker-v2-m3检索和重排序,有较高检索准确率。
  • 基座模型:支持接入OpenAI格式的大模型服务作为问答核心引擎,提供vLLM推理加速和Nginx负载均衡的支持。
  • 安全机制:使用到的中间件都配置了账号验证,部署的大模型和RAG服务也都开启了密钥验证,保证其安全性。
  • 界面展示:支持分析步骤的展示与流式输出、Markdown格式渲染、参考博客链接的返回,具有较好的页面体验。

blog-rag界面效果展示

本项目已经在Github上开源,项目地址为:https://github.com/Logistic98/blog-rag (opens new window)

# 1.2 核心流程

问题重写——问题扩展——问题相关判断——多数投票——检索文档数据——重排序——文档相关判断——大模型总结

blog-rag核心流程

# 1.3 目录结构

数据文件仅保留了一个示例,项目的目录结构如下:

.
├── data                        // 数据文件
│   ├── blog_input
│   └── blog_output
├── data_process                // 数据处理
│   ├── config.py
│   ├── build_index.py
│   └── parse_md.py
├── model_weight                // 模型文件
│   ├── QwQ-32B
│   ├── Qwen2.5-7B-Instruct
│   ├── bge-m3
│   └── bge-reranker-v2-m3
├── llm_service                 // 大模型服务
│   ├── llm_server.py
│   ├── llmtuner
│   └── requirements.txt
├── nginx_balance               // 负载均衡
│   ├── Dockerfile
│   ├── build.sh
│   ├── nginx.conf
│   ├── nginx_balance.conf
│   └── proxy.conf
├── milvus                      // 向量检索库
│   └── standalone_embed.sh
├── rag_chat                    // 问答页面
│   ├── .env
│   ├── babel.config.js
│   ├── jsconfig.json
│   ├── package.json
│   ├── public
│   ├── src
│   └── vue.config.js
├── rag_service                // RAG服务
│   ├── requirements.txt
│   ├── config.py
│   ├── fastapi_app.py
│   ├── knowledge_base.py
│   ├── llm.py
│   ├── logging_setup.py
│   ├── models.py
│   ├── prompts.py
│   ├── rag_server.py
│   └── validators.py
└── script                     // 实用脚本
│   ├── download_models.py
│   ├── milvus_password.py
│   ├── requirements.txt
│   └── rag_service_test.sh
├── build.sh 
├── start.sh
├── Dockerfile
└── README.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 2. 技术架构

# 2.1 技术选型

# 2.1.1 基础开发框架

  • 后端服务:FastAPI(高性能服务框架,基于Starlette和Pydantic构建而成,具有较好并发支持)
  • 前端界面:Vue(目前最主流的三大前端开发框架之一,它是渐进式的,具备简洁易用的特性)

# 2.1.2 高性能中间件

  • 向量检索库:Milvus(高性能、高扩展性的向量数据库,适于处理海量向量数据的实时召回)
  • 高速缓存:Redis(基于内存的数据存储系统,能提供极快速度,适用于需要快速响应的场景)

# 2.1.3 算法模型文件

  • 大模型基座:Qwen2.5-7B-Instruct、QWQ-32B(可接入任意大模型服务,是OpenAI格式即可)
  • Embedding模型:bge-m3(同时支持密集检索、多向量检索、稀疏检索,有较好混合召回效果)
  • 重排序模型:bge-reranker-v2-m3(搜到的文档不都是相关的,重排序可让更相关的文档排前面)

# 2.2 技术介绍

# 2.2.1 Milvus向量检索库

Milvus 是高性能、高扩展性的向量数据库,它是 LF AI & Data Foundation 下的开源项目,大多数贡献者都是高性能计算(HPC)领域的专家,擅长构建大型系统和优化硬件感知代码。

Milvus架构图

Milvus的亮点:

Milvus为何如此快速?

Milvus从设计之初就是一个高效的向量数据库系统。在大多数情况下,Milvus 的性能是其他向量数据库的 2-5 倍。

  • 硬件感知优化:为了让 Milvus 适应各种硬件环境,我们专门针对多种硬件架构和平台优化了其性能,包括 AVX512、SIMD、GPU 和 NVMe SSD。
  • 高级搜索算法:Milvus 支持多种内存和磁盘索引/搜索算法,包括 IVF、HNSW、DiskANN 等,所有这些算法都经过了深度优化。与 FAISS 和 HNSWLib 等流行实现相比,Milvus 的性能提高了 30%-70%。
  • C++搜索引擎:向量数据库性能的80%以上取决于其搜索引擎。由于C++语言具备高性能、底层优化和高效资源管理的特点,Milvus 使用C++来处理这一关键组件。最重要的是,Milvus集成了大量硬件感知代码优化,从汇编级向量到多线程并行化和调度,以充分利用硬件能力。
  • 面向列:Milvus 是面向列的向量数据库系统。其主要优势来自数据访问模式。在执行查询时,面向列的数据库只读取查询中涉及的特定字段,而不是整行,这大大减少了访问的数据量。此外,对基于列的数据的操作可以很容易地进行向量化,从而可以一次性在整个列中应用操作,进一步提高性能。

# 2.2.2 BGE-M3-Embedding嵌入模型

BGE-M3-Embedding是BAAI开源的嵌入模型,它在多语言性、多功能性和多粒度性方面表现出色。支持超过100种工作语言,支持8192长度的输入文本,同时支持密集检索、多向量检索和稀疏检索,为现实世界中的信息检索应用提供了统一的模型基础,通过这几种检索方式的组合,取得了良好的混合召回效果。

下图是官方与OpenAI模型的对比,整体来看,采用三种方式联合检索的BGE-M3(ALL)在三项评测中全面领先,而 BGE-M3(Dense)稠密检索在多语言、跨语言检索中具有明显优势。

BGE-M3模型

# 2.2.3 BGE-ReRanker-V2-M3检索排序模型

BGE-ReRanker-V2-M3是BAAI开源的检索排序模型,相较于上一代,它支持更多语言,更长文本长度,并在英文检索基准MTEB、中文检索基准C-MTEB、多语言检索基准MIRACL、LLaMA-Index Evaluation等主流基准上取得了state-of-the-art的结果。除此之外,还借助分层自蒸馏策略进一步优化推理效率,适度的开销即可换取显著的性能收益。

BGE-ReRank应用于RAG系统

检索排序模型是信息检索及RAG pipeline中的重要组成部分。与向量模型与稀疏检索模型相比,检索排序模型会利用更加复杂的判定函数以获得更加精细的相关关系。通常,系统会首先借助向量模型(BGE-M3-Dense)与稀疏检索模型(BGE-M3-Sparse)分别从向量数据库与倒排索引中初步获取粗粒度的候选文档。紧接着,系统会进一步利用排序模型(BGE Re-Ranker)进一步过滤候选集,并最终获得精细的文档集,以支持下游大语言模型完成检索增强任务。

# 3. 基础环境

# 3.1 依赖环境安装

使用Conda创建一个Python3.10的环境:

$ conda create -n blog_rag python=3.10
$ conda activate blog_rag
1
2

# 3.2 部署Redis服务

# 3.2.1 拉取镜像并运行容器

$ docker pull redis:6.2.12
$ docker run --name redis -p 6379:6379 -d redis:6.2.12 --requirepass "52497Vr62K94qeksg82679o22kr774ee" --appendonly yes
1
2

注:--requirepass用来设置密码,--appendonly yes用来设置AOF持久化。

# 3.2.2 Redis可视化管理工具

建议使用 AnotherRedisDesktopManager (opens new window) 开源工具进行可视化连接和管理。

# 3.3 部署Milvus服务

# 3.3.1 拉取镜像并运行容器

官方文档里提供了一键脚本进行部署,https://milvus.io/docs/install_standalone-docker.md (opens new window)

$ curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh
$ chmod u+x ./standalone_embed.sh
$ ./standalone_embed.sh start
1
2
3

该脚本还提供了以下管理命令:

$ ./standalone_embed.sh start
$ ./standalone_embed.sh stop
$ ./standalone_embed.sh delete
$ ./standalone_embed.sh upgrade
1
2
3
4

# 3.3.2 开启账号验证并修改密码

官方提供的一键脚本是没有开启账号验证的,生产环境为了安全性考虑需要将其开启。

Step1:开启Milvus的账号验证

$ docker exec -it milvus-standalone /bin/bash
$ apt-get update && apt-get install vim -y
$ vim /milvus/configs/milvus.yaml
1
2
3

按下 / 键进入搜索模式,输入 authorizationEnabled 后按 Enter 键找到其位置,将该参数由 false 改成 true。

...
common:
...
  security:
    authorizationEnabled: true
...
1
2
3
4
5
6

开启Milvus账号验证

$ exit
$ docker restart milvus-standalone
1
2

Step2:修改默认账号的密码

# -*- coding: utf-8 -*-

from pymilvus import connections, utility

connections.connect(
    alias='default',
    host='localhost',
    port='19530',
    user='root',
    password='Milvus'
)

utility.reset_password(
    old_password='Milvus',
    new_password='cG72vdgVWX5ypaWV',
    user='root',
    using='default'
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

注:如果在启用身份验证的情况下连接 Milvus 时未能提供有效令牌,则会收到 gRPC 错误。

# 3.3.3 Milvus可视化管理工具

可以安装开源的 Attu 工具进行可视化管理,可以在Linux上搭建网页端,也可以在Mac、Win上直接安装客户端。

这里我是直接在 Mac 上安装了客户端,会提示“attu.app 已损坏,无法打开”,执行以下命令即可。

$ sudo xattr -rd com.apple.quarantine /Applications/attu.app
1

# 3.4 下载所需的模型

从Huggingface下载本项目所需要的模型文件。

$ mkdir model_weight
$ cd script 
$ python3 download_models.py
1
2
3

download_models.py

# -*- coding: utf-8 -*-

import os
from huggingface_hub import snapshot_download

model_repos = [
    "Qwen/QwQ-32B",
    "Qwen/Qwen2.5-7B-Instruct",
    "BAAI/bge-m3",
    "BAAI/bge-reranker-v2-m3"
]

base_dir = "../model_weight"

for repo_id in model_repos:
    model_name = repo_id.split("/")[-1]
    local_dir = os.path.join(base_dir, model_name)
    if not os.path.exists(local_dir):
        os.makedirs(local_dir)
    print(f"Downloading model {repo_id} to {local_dir}...")
    snapshot_download(repo_id=repo_id, local_dir=local_dir)

print("All models downloaded successfully.")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 4. 模块开发

# 4.1 数据处理

不管RAG系统结构如何设计,它都是数据驱动的,高信噪比的数据是十分重要的,对于效果的影响很大。通常而言,如何从杂乱的非结构化数据里准确的解析和切分是个难点,即便是用了很多优化机制,在不介入人工校正的情况下还是会有很多问题。

我的博客都是Markdown格式的,不需要文件解析。而且按照我个人的写作习惯,是有套统一的规范,所有文章都可以基于规则进行切割,这样数据切分的难点也就迎刃而解了。基于以上两点,我这里没有采用通用的文件解析、数据切分算法,而是定制化编写脚本去处理,这样得到的效果是远远高于那些通用方案的,但是它对于原始数据有很高要求,换一套数据形式就没法去使用了。

# 4.1.1 文件解析

我的博客全都是的Markdown格式,直接读取即可。如果你的原始文件是杂乱无章的各种非结构化数据,则需要使用一些优化算法去处理,详见我的另一篇博客:基于深度学习的非结构化文档解析 (opens new window)

# 4.1.2 数据切分

我的博客都是如下这种目录结构来写的,且每个章节不会太长,自身都具备完整语义,因此按照子章节进行切分就很合适。

## 1.
### 1.1
#### 1.1.1
1
2
3

这里采用基于规则的定制化切割方法,脚本里是根据 # 和数字序号一起去匹配切割的,每个#### 1.1.1层级作为一个切片。

注:需要注意的是,需要让数字序号跟指定数量的#一起匹配,不然的话,Python代码块里的#注释也当成标题进行切割了。

处理后的数据切片是这样的,除了文件章节切片之外,文件元数据和文件目录我也单独处理成切片了。

.
├── Docker容器化及项目环境管理_chunk_1.md
├── Docker容器化及项目环境管理_chunk_2.md
...
├── Docker容器化及项目环境管理_chunk_95.md
├── Docker容器化及项目环境管理_chunk_96.md
├── Docker容器化及项目环境管理_metadata.md
└── Docker容器化及项目环境管理_toc.md
1
2
3
4
5
6
7
8

为了提高后续数据检索的命中率,我对于每个章节,都带上了根节点的标题,例如:

## 1. 项目环境管理
### 1.1 环境管理目标及实现
#### 1.1.1 环境管理实现

开发环境使用Docker进行部署,各组件之间使用Docker Network进行内部通信,将打包好的镜像放置到镜像仓库中,测试、演示、正式环境直接从镜像开始构建服务。

![环境管理实现](https://image.eula.club/quantum/环境管理实现.png)
1
2
3
4
5
6
7

由于内容我会不断的在旧博客里进行增删改,而这个的变动又回影响到切分,因此我的数据切分是全量处理的,它的处理速度很快。

# 4.2 数据入库

得到了数据切片之后,就要将这些数据存入Milvus向量检索库,建立的字段如下,这里会用到 BGE-M3-Embedding 嵌入模型。

        fields = [
            FieldSchema(
                name="chunk_file",
                dtype=DataType.VARCHAR,
                max_length=255,
                is_primary=True,
                description="切片文件,用于唯一标识"
            ),
            FieldSchema(
                name="doc_name",
                dtype=DataType.VARCHAR,
                max_length=255,
                description="文档文件名"
            ),
            FieldSchema(
                name="file_type",
                dtype=DataType.VARCHAR,
                max_length=50,
                description="文件类型(chunk、metadata、toc)"
            ),
            FieldSchema(
                name="chunk_content",
                dtype=DataType.VARCHAR,
                max_length=max_content_length,
                description="切片内容"
            ),
            FieldSchema(
                name="dense_vector",
                dtype=DataType.FLOAT_VECTOR,
                dim=dense_dim,
                description="切片内容的向量化信息"
            )
        ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

注:Milvus的向量索引配置建议采用COSINE。

  • COSINE适用于文本相似度计算等领域,特别是当向量的长度不同但方向相似时,余弦相似度能更好地反映相似性。
  • L2适用于度量实际空间中的距离,例如图像、音频等数据的相似度。
"metric_type": "COSINE"
1

这个入库过程是比较缓慢的,考虑到我会频繁的更新博客内容,因此这里我必须要采用增量构建的方式,我这里是将文件的Hash值存入了Redis中,通过比对Hash的方式,将变动的数据同步进去。

Redis存入文件Hash值用于增量构建

# 4.3 数据检索

检索阶段需要用到 bge-m3 模型,重排序需要用到 bge-reranker-v2-m3 模型。

EMBEDDING_MODEL = '../model_weight/bge-m3'                # 嵌入模型的路径
RETRIEVE_TOPK = 5                                         # 检索的文档数量上限
RERANKING_MODEL = '../model_weight/bge-reranker-v2-m3'    # 重排序模型的路径
USE_RERANKER = True                                       # 是否使用重排序模型进行结果优化,建议将其开启
1
2
3
4

RETRIEVE_TOPK 配置项需要设置一个合理的值,太小会漏数据,太大会影响速度。USE_RERANKER 配置项建议把重排序打开。

# 4.4 LLM服务

大模型服务是核心引擎,它自身能力的好坏对于效果也有较大影响,这里可以采用开源大模型,也可以采用商业大模型API。

LLM_BASE_URL = 'https://xxx.xxx.xxx/v1'                               # 接入LLM服务的基础URL
LLM_API_KEY = 'sk-xxx'                                                # 接入LLM服务的API_KEY
LLM_MODEL = 'gpt-4o-mini'                                             # 接入LLM服务的模型选择
LLM_COT_MODEL = 'deepseek-r1'                                         # 接入推理LLM服务的模型选择
1
2
3
4

# 4.5 RAG服务

# 4.5.1 重写扩展

问题重写:对用户的原始问题进行重写,提高问题描述的准确度,例如将上下文指代消歧。

问题扩展:为了提高问题的召回率,使用 LLM 将一个原问题扩展成多个不同表述的子问题。

QUESTION_REWRITE_ENABLED = True                                       # 是否开启重写重写扩展
QUESTION_REWRITE_NUM = 2                                              # 问题重写扩展数量(额外扩展的问题数量)
1
2

# 4.5.2 是否检索

使用 LLM 判断问题是否与知识库相关,对于跟知识库无关的问题,可以不走检索提高响应速度。

这里采用“知识库描述Prompt”来判断是否相关的,判定要宽松一些,以减少应该用知识库检索的问题却没用的情况。

QUESTION_RETRIEVE_ENABLED = True                                      # 是否开启问题相关性判断
1

# 4.5.3 相关判断

通过 bge-m3 模型的检索,和 bge-reranker-v2-m3 模型的重排序,得到的数据还不够精准,我这里又用大模型进行了相关性判断。后续只让大模型对于判断为相关的数据切片进行总结生成。

STRATEGY = 'llm'                         # 相关性判断策略,可选'llm'或'thres',选择'llm'的判断更精准一些
THRESHOLD = 0.85                         # 使用'thres'策略时的相关性阈值
1
2

# 4.5.4 数据来源

由于大模型的总结依旧可能不够可靠,而且用户可能会想要通过原始文档查看到更多信息,大多数RAG系统会返回原始文档或者切片。而我这里由于博客全部都放在公网上,且链接是根据博客名来的,因此只要返回博客名即可,前端可以自行拼接。

VUE_APP_DOC_URL_TEMPLATE=https://www.eula.club/blogs/{}.html
1

注:由于多个切片可能会来源于同一个博客,因此需要对数据来源进行去重处理。

# 4.5.5 密钥授权

为了保证接口服务的安全性,我这里添加了个简易的密钥授权,在配置里加了个API_KEYS配置项。

  • 如果为空列表,则不进行密钥授权校验,该服务是可以直接被访问的,这样不够安全。
  • 如果不为空,则进行密钥授权校验,里面可以配置多个密钥,其中有一个命中就算通过。
API_KEYS = ['sk-67hBSTsaf0qqvpTN2eA5A4433c2343D3867d0f74D8F0322']     # 本服务允许使用的API_KEY列表
1

# 4.5.6 流式输出

由于RAG系统需要走检索和判断的流程,需要较长时间,为了让用户有更好的使用体验,因此加上了分析步骤的返回与流式输出的支持。

RAG服务接口这里我采用了与OpenAI兼容的格式,结构上尽可能保持统一。密钥授权依旧是采用Authorization的形式,是否开启流式输出通过stream参数来控制,返回值只是在choices里添加了step、message、reference等信息,finish_reason为stop时,流式输出完毕。

curl --location 'http://127.0.0.1:18888/v1/chat/completions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer sk-67hBSTsaf0qqvpTN2eA5A4433c2343D3867d0f74D8F0322' \
--data '{
  "model": "deepseek-r1",
  "messages": [
    {
      "role": "user",
      "content": "docker容器如何迁移"
    }
  ],
  "stream": true
}'
data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "gpt-4o-mini", "choices": [{"index": 0, "delta": {"role": null, "content": null, "reference": [], "step": 1, "message": "调整问题上下文指代信息..."}, "finish_reason": null}]}

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "gpt-4o-mini", "choices": [{"index": 0, "delta": {"role": null, "content": null, "reference": [], "step": 1, "message": "根据上下文重写后的问题为:如何迁移Docker容器?"}, "finish_reason": null}]}

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "gpt-4o-mini", "choices": [{"index": 0, "delta": {"role": null, "content": null, "reference": [], "step": 1, "message": "对问题进行重写扩展..."}, "finish_reason": null}]}

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "gpt-4o-mini", "choices": [{"index": 0, "delta": {"role": null, "content": null, "reference": [], "step": 1, "message": "重写扩展为3个问题"}, "finish_reason": null}]}

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "gpt-4o-mini", "choices": [{"index": 0, "delta": {"role": null, "content": null, "reference": [], "step": 2, "message": "判断是否需检索知识库..."}, "finish_reason": null}]}

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "gpt-4o-mini", "choices": [{"index": 0, "delta": {"role": null, "content": null, "reference": [], "step": 3, "message": "检索文档切片中..."}, "finish_reason": null}]}

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "gpt-4o-mini", "choices": [{"index": 0, "delta": {"role": null, "content": null, "reference": [], "step": 3, "message": "数据相关性分析中..."}, "finish_reason": null}]}

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "gpt-4o-mini", "choices": [{"index": 0, "delta": {"role": null, "content": null, "reference": [], "step": 3, "message": "存在2条相关数据"}, "finish_reason": null}]}

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "deepseek-r1", "choices": [{"index": 0, "delta": {"role": null, "content": null, "reference": [], "step": 4, "message": "正在总结答案..."}, "finish_reason": null}]}

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "deepseek-r1", "choices": [{"index": 0, "delta": {"role": null, "content": "<think>", "step": 4, "message": "正在总结...", "reference": []}, "finish_reason": null}]}

...[省略中间思考过程]

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "deepseek-r1", "choices": [{"index": 0, "delta": {"role": null, "content": "</think>", "step": 4, "message": "正在总结...", "reference": []}, "finish_reason": null}]}

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "deepseek-r1", "choices": [{"index": 0, "delta": {"role": null, "content": "\n\n", "step": 4, "message": "正在总结...", "reference": []}, "finish_reason": null}]}

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "deepseek-r1", "choices": [{"index": 0, "delta": {"role": null, "content": "要将", "step": 4, "message": "正在总结...", "reference": []}, "finish_reason": null}]}

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "deepseek-r1", "choices": [{"index": 0, "delta": {"role": null, "content": "D", "step": 4, "message": "正在总结...", "reference": []}, "finish_reason": null}]}

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "deepseek-r1", "choices": [{"index": 0, "delta": {"role": null, "content": "ocker", "step": 4, "message": "正在总结...", "reference": []}, "finish_reason": null}]}

...[省略最终回复数据]

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "deepseek-r1", "choices": [{"index": 0, "delta": {"role": null, "content": "。", "step": 4, "message": "正在总结...", "reference": []}, "finish_reason": null}]}

data: {"id": "8d5abe47-36c7-4050-b91a-179af96c833c", "model": "deepseek-r1", "choices": [{"index": 0, "delta": {"role": null, "content": null, "reference": ["Docker容器化及项目环境管理"], "step": 4, "message": "回答完成"}, "finish_reason": null}]}

data: [DONE]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# 4.6 展示界面

前端界面需要支持分析步骤与流式输出的展示效果,并且将参考博客拼接成可访问的链接进行展示,与后端的RAG服务配套使用。

支持Markdown格式渲染,这样返回内容就带有样式了,会更加美观,并且涉及图表的可将其展示出来,涉及链接的可以直接打开。

支持解析例如DeepSeek-R1、QWQ-32B推理模型中<think></think>标签里的思考过程,将其与正式回复的内容区分出来格式。

还有个细节问题是要在输入法录入状态时,禁止Enter发送,而未在录入时可使用Enter发送出去,防止中英文混合输入时出现问题。

# 5. 部署使用

运行环境:Macbook Pro 2021,M1 pro芯片,16G内存,1024G存储,macOS Sonoma 14.5 系统,Python3.10环境

# 5.1 数据处理成切片并入库

执行脚本将原始数据处理成切片,并存入Milvus检索库中去。

$ cd data_process 
$ pip3 install -r requirements.txt
$ mv example_config.py config.py
$ vim config.py
1
2
3
4

如果前面的步骤都使用我的默认配置,这里只需要改下Milvus和Redis的IP即可

MILVUS_HOST = "127.0.0.1"                            # Milvus主机地址
MILVUS_PORT = "19530"                                # Milvus端口
MILVUS_COLLECTION_NAME = "vuepress_blog"             # Milvus集合名称
MILVUS_USER = "root"                                 # Milvus用户名
MILVUS_PASSWORD = "cG72vdgVWX5ypaWV"                 # Milvus密码
REDIS_HOST = "127.0.0.1"                             # Redis主机地址
REDIS_PORT = 6379                                    # Redis端口
REDIS_PASSWORD = "52497Vr62K94qeksg82679o22kr774ee"  # Redis密码
REDIS_KEY = "file_hashes"                            # Redis键
BGE_M3_PATH = "../model_weight/bge-m3"               # 模型路径
DATA_DIR = "../data/blog_output"                     # 数据目录
BATCH_SIZE = 5                                       # 批次大小
MAX_CONTENT_LENGTH = 60000                           # 最大内容长度
LOG_FILE = "build_index.log"                         # 日志文件路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14

执行数据切片及入库的程序

$ python3 parse_md.py
$ python3 build_index.py
1
2

Attu查看存入Milvus的知识库数据

# 5.2 手动部署前后端服务

# 5.2.1 修改配置部署RAG服务

大模型服务可以使用 llm_service 目录的代码进行部署,也可以直接使用商业大模型服务,保证是 OpenAI 格式的即可,部署步骤略。

$ cd rag_service 
$ pip3 install -r requirements.txt
$ mv example_config.py config.py
$ vim config.py
1
2
3
4

如果前面的步骤都使用我的默认配置,则这里只需要修改大模型服务配置即可,实测Qwen2.5-7B就可以有比较好的效果。

# LLM相关配置
LLM_BASE_URL = 'https://xxx.xxx.xxx/v1'                               # 接入LLM服务的基础URL
LLM_API_KEY = 'sk-xxx'                                                # 接入LLM服务的API_KEY
LLM_MODEL = 'gpt-4o-mini'                                             # 接入LLM服务的模型选择
LLM_COT_MODEL = 'deepseek-r1'                                         # 接入推理LLM服务的模型选择

# 本服务的授权验证
API_KEYS = ['sk-67hBSTsaf0qqvpTN2eA5A4433c2343D3867d0f74D8F0322']     # 本服务允许使用的API_KEY列表

# Milvus向量数据库
MILVUS_SERVER = '127.0.0.1'                                           # Milvus服务的IP地址
MILVUS_PORT = '19530'                                                 # Milvus服务的端口号
MILVUS_USER = 'root'                                                  # Milvus服务的用户名
MILVUS_PASSWORD = 'cG72vdgVWX5ypaWV'                                  # Milvus服务的密码
MILVUS_KB_NAME = 'vuepress_blog'                                      # Milvus知识库的名称

# 知识库检索及模型
QUESTION_REWRITE_ENABLED = True                                       # 是否开启重写重写扩展
QUESTION_REWRITE_NUM = 2                                              # 问题重写扩展数量(额外扩展的问题数量)
QUESTION_RETRIEVE_ENABLED = True                                      # 是否开启问题相关性判断
EMBEDDING_MODEL = '../model_weight/bge-m3'                            # 嵌入模型的路径
RETRIEVE_TOPK = 5                                                     # 检索的文档数量上限
RERANKING_MODEL = '../model_weight/bge-reranker-v2-m3'                # 重排序模型的路径
USE_RERANKER = True                                                   # 是否使用重排序模型优化结果

# 相关性判断策略
STRATEGY = 'llm'                                                      # 相关性判断策略,可选'llm'或'thres'
THRESHOLD = 0.85                                                      # 使用'thres'策略时的相关性阈值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

说明:LLM_API_KEY是大模型服务的密钥,不要将其泄露出去。而API_KEYS是配置RAG服务的密钥(是可以自定义的),若API_KEYS为空列表,则不开启验证,为了保证安全性,还是建议将其开启。

$ cd rag_service
$ python3 rag_server.py
1
2

# 5.2.2 安装依赖启动前端页面

安装前端依赖并将其启动,在.env文件里是可能需要改动的配置项。

$ cd rag_chat 
$ npm install
$ npm run serve
1
2
3

# 5.3 一键部署前后端服务

部署前后端应用程序可以使用Docker一键脚本,如果有GPU环境可以在build.sh里额外加上--gpus all参数。

$ chmod u+x build.sh
$ ./build.sh
1
2

Dockerfile

# 设置基础镜像
FROM python:3.12-slim

# 设置工作目录并拷贝代码
WORKDIR /app
COPY ./rag_chat /app/rag_chat
COPY ./rag_service /app/rag_service
COPY ./build.sh /app/build.sh
COPY ./start.sh /app/start.sh

# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    nodejs \
    npm \
    yarn \
    && rm -rf /var/lib/apt/lists/*

# 安装后端依赖
RUN pip install -r /app/rag_service/requirements.txt

# 安装前端依赖
RUN npm install --prefix /app/rag_chat --registry=https://registry.npmmirror.com

# 赋予脚本可执行权限
RUN chmod u+x /app/start.sh

# 设置启动命令
ENTRYPOINT ["/app/start.sh"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

start.sh

#!/bin/bash

# 启动后端
cd /app/rag_service
echo "正在启动后端服务..."
uvicorn --host=0.0.0.0 --port=18888 --workers=1 fastapi_app:app &

# 启动前端
cd /app/rag_chat
echo "正在启动前端服务..."
npm run serve
1
2
3
4
5
6
7
8
9
10
11

build.sh

#!/bin/bash

docker build -t blog-rag-image .

base_path=$(cd `dirname $0`; pwd)

docker run -itd --name blog-rag -h blog-rag \
-p 18888:18888 \
-p 28888:28888 \
-v ${base_path}/model_weight/bge-m3:/app/model_weight/bge-m3 \
-v ${base_path}/model_weight/bge-reranker-v2-m3:/app/model_weight/bge-reranker-v2-m3 \
-v ${base_path}/rag_chat/.env:/app/rag_chat/.env \
-v ${base_path}/rag_service/config.py:/app/rag_service/config.py \
blog-rag-image

docker update blog-rag --restart=always
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

使用Chrome浏览器访问 http://localhost:28888 (opens new window) 地址即可使用,实际使用效果如下:

blog-rag界面效果演示

# 6. 参考资料

[1] 关于Milvus from 官方文档 (opens new window)

[2] milvus设置密码 from CSDN (opens new window)

[3] BGE M3-Embedding 模型介绍 from 博客园 (opens new window)

[4] 智源开源最强检索排序模型 BGE Re-Ranker v2.0 from 53AI (opens new window)

[5] 大模型知识库 RAG 服务的 Rerank 调优 from CSDN (opens new window)

[6] 复旦发布:最佳RAG方案 from 微信公众号 (opens new window)

Last Updated: 3/22/2025, 4:15:32 AM