从零搭建 RAG 系统:Milvus 向量数据库 + 大模型完整实战指南

从零搭建 RAG 系统:Milvus 向量数据库 + 大模型完整实战指南

本文融合了 RAG 核心概念、技术选型决策树、Milvus 实践和完整代码,帮你系统掌握检索增强生成技术。

目录

  1. [什么是 RAG?为什么要用 RAG?](#什么是 RAG?为什么要用 RAG?)
  2. [RAG 系统核心架构](#RAG 系统核心架构)
  3. [向量数据库:Milvus 入门与实战](#向量数据库:Milvus 入门与实战)
  4. Embedding:文本向量化详解
  5. 文档切片:容易被忽视的关键步骤
  6. 检索方法全解析与选型指南
  7. 索引与度量类型:性能与精度的权衡
  8. Rerank:什么时候值得用?
  9. [完整代码实践:一个可运行的 RAG 系统](#完整代码实践:一个可运行的 RAG 系统)
  10. 综合决策树:如何为你的场景选择最佳配置
  11. 总结与进阶方向

1. 什么是 RAG?为什么要用 RAG?

RAG(Retrieval-Augmented Generation,检索增强生成) 是一种结合信息检索文本生成的 AI 技术框架。它的名字拆解如下:

  • 检索(Retrieval):从外部知识源(向量数据库、搜索引擎等)中查找与问题相关的信息。
  • 增强(Augmented):将检索到的信息拼接到 LLM 的输入中,丰富上下文。
  • 生成(Generation):LLM 基于增强后的提示词生成最终答案。

为什么叫"增强"而不是"加"?

"增强"一词强调检索结果融入生成过程,成为生成决策的依据,而不是两个独立的步骤。类比:开卷考试时允许查阅资料(检索),然后结合自己的理解写出答案(生成)→ "查阅资料增强了你的答题能力"。

为什么需要 RAG?

尽管大语言模型(如 GPT-4、Qwen)表现惊艳,但它们在落地中存在硬伤:

问题 说明 RAG 如何解决
幻觉 模型会编造不存在的事实 强制基于检索到的真实文档回答
知识时效 训练数据截止后的事件一无所知 检索最新文档库,实时更新
私有数据 无法访问企业内部文档 将私有文档向量化后检索
成本高 微调大模型成本高昂 无需微调,只更新知识库即可

2. RAG 系统核心架构

复制代码
┌─────────────────────────────────────────────────────────────┐
│                        用户提问                              │
└─────────────────────────────┬───────────────────────────────┘
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  1. 查询向量化(Embedding)                                  │
│     "什么是机器学习?" → [0.12, -0.45, 0.78, ...]           │
└─────────────────────────────┬───────────────────────────────┘
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  2. 向量检索(Vector Search)                                │
│     在 Milvus 中查找最相似的 Top‑K 文档片段                   │
└─────────────────────────────┬───────────────────────────────┘
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  3. (可选)重排序(Rerank)                                 │
│     用 Cross‑Encoder 对结果精排,提升准确率                   │
└─────────────────────────────┬───────────────────────────────┘
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  4. 构建提示词(Prompt)                                     │
│     "请根据以下信息回答:\n[文档1]\n[文档2]\n问题:..."       │
└─────────────────────────────┬───────────────────────────────┘
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  5. LLM 生成答案                                             │
│     输出基于事实的准确回答                                   │
└─────────────────────────────────────────────────────────────┘

3. 向量数据库:Milvus 入门与实战

3.1 什么是向量数据库?

传统数据库按精确值 查找(如 WHERE name = '张三'),而向量数据库按相似度查找。它存储 Embedding 向量,并通过索引加速相似度计算。

Milvus 是开源的向量数据库标杆,支持亿级向量毫秒级检索。

3.2 安装与连接

bash 复制代码
# 推荐使用 Docker 部署(完整功能)
docker run -d --name milvus_standalone -p 19530:19530 milvusdb/milvus:v2.5.0

# 或使用 Milvus Lite(仅限 Linux/Mac)
pip install milvus-lite

连接代码:

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(uri="http://localhost:19530")
print(client.get_server_version())  # 输出如 "2.5.0"

3.3 创建集合(Collection)

python 复制代码
client.create_collection(
    collection_name="knowledge_base",
    dimension=768,          # 向量维度,需与 Embedding 模型匹配
    auto_id=True,           # 主键自动生成
    metric_type="COSINE"    # 相似度度量
)

3.4 插入与检索

python 复制代码
# 插入
data = [{"content": "机器学习是人工智能的核心技术。", "vector": [0.1, -0.2, ...]}]
client.insert("knowledge_base", data)

# 检索
results = client.search(
    collection_name="knowledge_base",
    data=[query_vector],
    limit=5,
    output_fields=["content"]
)

4. Embedding:文本向量化详解

4.1 什么是 Embedding?

Embedding 将文本映射到高维向量空间,语义相近的文本在空间中距离也近。

复制代码
"苹果" → [0.8, -0.2, 0.5, ...]
"香蕉" → [0.7, -0.1, 0.6, ...]   ← 距离近
"手机" → [-0.5, 0.8, -0.3, ...]  ← 距离远

4.2 常用 Embedding 模型对比

模型 维度 语言 特点
bge-large-zh-v1.5 1024 中文 效果最佳,需本地部署
bge-base-zh-v1.5 768 中文 平衡方案
bge-small-zh-v1.5 512 中文 轻量快速
text-embedding-v4 (阿里云) 1024 中英文 API 调用,无需部署
text-embedding-3-small (OpenAI) 1536 多语言 效果好,有费用

4.3 Embedding 模型选型决策树

复制代码
数据可以上传云端吗?
├─ 不能 → 使用本地模型
│        ├─ 有 GPU/大内存(>2GB)→ bge-large-zh-v1.5
│        ├─ 资源受限 → bge-small-zh-v1.5
│        └─ 平衡 → bge-base-zh-v1.5
│
└─ 可以 → 年调用量 > 50 万次?
         ├─ 是 → 本地部署(长期更省钱)
         └─ 否 → 云端 API(阿里云或 OpenAI)

⚠️ 铁律 :文档向量化和查询向量化必须使用同一个 Embedding 模型。


5. 文档切片:容易被忽视的关键步骤

5.1 切片方法对比

方法 原理 优点 缺点 推荐场景
固定字符数 每 N 字符一刀切 简单快速 可能切断句子 快速原型
滑动窗口 固定大小 + 重叠 保留上下文连续性 数据冗余 通用首选
按段落/句子 利用自然分隔 语义完整 依赖文档格式 结构良好文档
AI 语义切片 LLM 识别主题边界 质量最高 成本高、慢 高质量 RAG

5.2 滑动窗口切片代码

python 复制代码
def sliding_window_chunking(text, chunk_size=500, overlap=50):
    step = chunk_size - overlap
    chunks = []
    start = 0
    while start < len(text):
        end = min(start + chunk_size, len(text))
        chunks.append(text[start:end])
        start += step
        if end == len(text):
            break
    return chunks

5.3 参数建议

  • chunk_size:300~500 字符(中文)或 200~300 单词(英文)
  • overlap:50~100 字符(10%~20% 重叠)
  • 原因:过小丢失语义,过大引入噪声;重叠防止边界信息丢失。

6. 检索方法全解析与选型指南

6.1 方法对比

方法 原理 优势 劣势
标量查询 精确字段筛选 精确、快速 无法处理文本语义
向量检索 语义相似度 理解同义词、跨语言 对专有名词不敏感
BM25(关键字) 词频+逆文档频率 精确匹配专有名词 无法理解语义
混合检索 向量 + BM25 + RRF 融合 综合两者优势 实现稍复杂

6.2 什么时候用哪种?

复制代码
问题中包含明显的专有名词/型号/代码?
├─ 是 → 必须加入 BM25 关键字检索
└─ 否 → 向量检索即可

是否需要高召回率(企业搜索、法律文档)?
├─ 是 → 混合检索(向量+BM25+RRF)
└─ 否 → 单一向量检索够用

6.3 混合检索的 RRF 融合算法

向量得分和 BM25 得分尺度不同,不能直接加权平均。RRF(倒数排名融合) 是标准方案:

python 复制代码
def rrf_fusion(vector_results, keyword_results, k=60):
    scores = {}
    for rank, (doc_id, _) in enumerate(vector_results):
        scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
    for rank, (doc_id, _) in enumerate(keyword_results):
        scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
    return sorted(scores.items(), key=lambda x: x[1], reverse=True)

7. 索引与度量类型:性能与精度的权衡

7.1 索引类型选择

索引 数据量 检索精度 速度 内存 适用场景
FLAT < 1 万 100% 测试、小规模
IVF_FLAT 1 万 ~ 100 万 95~98% 通用生产
HNSW 任意 98~99% 最快 高精度、高内存
IVF_PQ > 100 万 90~95% 很快 超大规模

决策树

  • 数据量 < 1 万 → FLAT
  • 1 万 ~ 100 万 → IVF_FLAT(推荐)
  • 100 万 → 内存足用 HNSW,内存紧用 IVF_PQ

7.2 度量类型

类型 公式 判断标准 适用场景
COSINE `A·B / ( A
L2 欧氏距离 越小越相似 向量未归一化
IP 内积 越大越相似 归一化后与 COSINE 等价

✅ 90% 的语义检索场景用 COSINE。BGE、OpenAI、阿里云输出均已归一化。


8. Rerank:什么时候值得用?

8.1 两阶段检索架构

复制代码
用户查询 → [第一阶段] 召回 50~100 条候选(快)→ [第二阶段] Rerank 精排 Top‑5(准)→ LLM 生成

8.2 启用条件

条件 建议
召回数量 > 30 条 ✅ 值得用
对精度要求极高(医疗、法律) ✅ 必须用
实时性要求极高(< 100ms) ❌ 跳过
知识库 < 5000 条 ❌ 直接检索已足够

8.3 模型选择

python 复制代码
from FlagEmbedding import FlagReranker

# 中文场景
reranker = FlagReranker('BAAI/bge-reranker-large')  # 效果最好,需 GPU
# 轻量级
reranker = FlagReranker('BAAI/bge-reranker-base')

9. 完整代码实践:一个可运行的 RAG 系统

python 复制代码
import os
from pymilvus import MilvusClient
from sentence_transformers import SentenceTransformer
import jieba
from rank_bm25 import BM25Okapi

class SimpleRAG:
    def __init__(self, milvus_uri="http://localhost:19530", collection_name="rag_demo"):
        self.client = MilvusClient(uri=milvus_uri)
        self.collection_name = collection_name
        self.embed_model = SentenceTransformer('BAAI/bge-small-zh-v1.5')
        self.dim = 512
        self.bm25 = None
        self.raw_docs = []
        
        if not self.client.has_collection(collection_name):
            self.client.create_collection(
                collection_name=collection_name,
                dimension=self.dim,
                auto_id=True,
                metric_type="COSINE"
            )
    
    def _chunk_text(self, text, chunk_size=300, overlap=50):
        step = chunk_size - overlap
        chunks = []
        start = 0
        while start < len(text):
            end = min(start + chunk_size, len(text))
            chunk = text[start:end].strip()
            if chunk:
                chunks.append(chunk)
            start += step
            if end == len(text):
                break
        return chunks
    
    def add_documents(self, texts):
        all_chunks = []
        for text in texts:
            chunks = self._chunk_text(text)
            all_chunks.extend(chunks)
        
        vectors = self.embed_model.encode(all_chunks)
        data = [{"content": chunk, "vector": vec.tolist()} 
                for chunk, vec in zip(all_chunks, vectors)]
        self.client.insert(self.collection_name, data)
        
        # 构建 BM25 索引(用于混合检索)
        self.raw_docs = all_chunks
        tokenized_docs = [list(jieba.cut(doc)) for doc in all_chunks]
        self.bm25 = BM25Okapi(tokenized_docs)
        
        print(f"已添加 {len(all_chunks)} 个文档片段")
    
    def search(self, query, top_k=5, use_hybrid=True):
        query_vec = self.embed_model.encode(query).tolist()
        
        # 向量检索
        vector_results = self.client.search(
            collection_name=self.collection_name,
            data=[query_vec],
            limit=top_k * 2,
            output_fields=["content"]
        )[0]
        
        if not use_hybrid or self.bm25 is None:
            return [hit["entity"]["content"] for hit in vector_results[:top_k]]
        
        # BM25 检索
        tokenized_query = list(jieba.cut(query))
        bm25_scores = self.bm25.get_scores(tokenized_query)
        bm25_ranked = sorted(enumerate(bm25_scores), key=lambda x: x[1], reverse=True)[:top_k*2]
        
        # RRF 融合
        k = 60
        rrf_scores = {}
        for rank, (doc_idx, _) in enumerate(vector_results):
            rrf_scores[doc_idx] = rrf_scores.get(doc_idx, 0) + 1 / (k + rank + 1)
        for rank, (doc_idx, _) in enumerate(bm25_ranked):
            rrf_scores[doc_idx] = rrf_scores.get(doc_idx, 0) + 1 / (k + rank + 1)
        
        sorted_docs = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)[:top_k]
        return [self.raw_docs[idx] for idx, _ in sorted_docs]

# 使用示例
rag = SimpleRAG()
rag.add_documents(["机器学习是人工智能的核心技术。深度学习是机器学习的子集。"])
results = rag.search("什么是机器学习?", use_hybrid=True)
for doc in results:
    print(doc)

10. 综合决策树:如何为你的场景选择最佳配置

复制代码
开始
 │
 ├─ 1. 数据是否敏感?
 │    ├─ 是 → 本地 Embedding(BGE 系列)
 │    └─ 否 → 调用量 >50 万/年? → 是:本地 / 否:API
 │
 ├─ 2. 文档结构是否清晰?
 │    ├─ 是 → 按段落切片
 │    └─ 否 → 滑动窗口(size=500, overlap=50)
 │
 ├─ 3. 查询是否包含专有名词?
 │    ├─ 是 → 混合检索(向量 + BM25 + RRF)
 │    └─ 否 → 纯向量检索
 │
 ├─ 4. 数据量级?
 │    ├─ <1 万 → FLAT 索引
 │    ├─ 1~100 万 → IVF_FLAT
 │    └─ >100 万 → HNSW(内存足)或 IVF_PQ(内存紧)
 │
 └─ 5. 精度要求是否极高?
      ├─ 是 → 启用 Rerank(召回 50→精排 5)
      └─ 否 → 跳过 Rerank

不同场景的推荐配置

场景 文档量 敏感 实时 推荐配置
企业内部知识库 10 万 本地 BGE-large + 滑动窗口 + IVF_FLAT + 混合检索 + Rerank
电商商品搜索 100 万 API + 固定切片 + HNSW + 混合检索
个人学习助手 1000 本地 bge-small + 滑动窗口 + FLAT + 向量检索
医疗/法律问答 5 万 本地 BGE-large + AI 语义切片 + IVF_FLAT + 混合检索 + 必须 Rerank
实时客服机器人 2 万 极高 API + 段落切片 + IVF_FLAT + 向量检索

11. 总结与进阶方向

核心要点

  • RAG = 检索 + 增强 + 生成,核心是用外部知识增强 LLM 的生成能力。
  • Milvus 是向量数据库的首选,支持高效相似度搜索。
  • Embedding 模型必须统一,维度要匹配;选型取决于隐私、成本、效果。
  • 切片策略 显著影响检索质量,滑动窗口(500/50)是通用推荐。
  • 检索方法 从标量→向量→混合→Rerank,逐级提升精度但增加成本。
  • 索引与度量:默认 IVF_FLAT + COSINE,大数据量用 HNSW/PQ。
  • 终极建议:从最简单配置开始,按效果迭代优化。

进阶方向

  1. 知识图谱增强 RAG:结合实体链接,提升复杂问题理解。
  2. Agentic RAG:让 LLM 自主决定检索时机、重写查询、多跳检索。
  3. 评估体系:使用 RAGAS、ARES 等框架评估检索和生成质量。
  4. 缓存策略:对高频查询缓存检索结果,降低延迟和成本。

如果本文对你有帮助,欢迎点赞、收藏、评论。更多 Milvus 实战项目可参考 Milvus Bootcamp

相关推荐
环流_1 小时前
Redis ZSet
数据库·redis·缓存
m0_631529822 小时前
如何在非组件文件中动态读取 Redux Store 中的值以配置主题颜色
jvm·数据库·python
m0_733565462 小时前
SQL如何统计每日新增用户数_窗口函数与日期维度的结合
jvm·数据库·python
古怪今人2 小时前
etcd分布式键值存储系统 Windows下搭建etcd集群
数据库·分布式·etcd
我科绝伦(Huanhuan Zhou)2 小时前
oracle linux8.8一键部署oracle 11g
数据库·oracle
lolo大魔王2 小时前
Go语言数据库操作之GORM框架从入门到生产实战(完整版)
开发语言·数据库·golang
2303_821287382 小时前
Redis如何监控系统QPS的变化趋势
jvm·数据库·python
dinglu1030DL2 小时前
uni-app怎么接极光推送 uni-app消息推送App端接入【教程】
jvm·数据库·python
神明9312 小时前
Go语言如何用logrus_Go语言logrus日志框架教程【技巧】
jvm·数据库·python