ByteFisher AI 编程实战(十七):RAG知识库构建入门

前面的教程中,AI 只能基于训练数据回答。RAG(检索增强生成)让 AI 能在回答时检索你的私有数据,实现”AI + 你的知识库”。

一、什么是 RAG

RAG(Retrieval-Augmented Generation,检索增强生成)的工作流程:

1
2
用户提问 → 1. 将问题转为向量 → 2. 在知识库中搜索相关内容
→ 3. 将检索结果+问题拼接为 Prompt → 4. 发给 LLM → 输出答案

相比直接问 AI,RAG 的优势在于 AI 在回答时能参考你的私有数据:

对比 直接问 AI RAG
知识范围 公开训练数据 公开数据 + 你的私有数据
时效性 训练截止日期 即时更新
准确性 可能”幻觉” 基于检索结果,可验证来源
可审计 无法追溯依据 可查看哪些文档被检索到
定制化 无法个性化 可按你的数据定制回答

二、核心概念

2.1 Embedding——文本到向量的转换

Embedding 是将文本转为向量的技术。相似的文本在向量空间中距离更近。

1
2
3
"什么是对象池" → [0.12, 0.45, -0.33, 0.78, 0.01, ...] (768 维向量)
"Unity 对象池" → [0.11, 0.44, -0.31, 0.76, 0.02, ...] (距离近 → 相关)
"如何煮咖啡" → [-0.23, 0.12, 0.56, -0.11, 0.34, ...] (距离远 → 不相关)

Embedding 模型推荐

模型 维度 语言 特点
BAAI/bge-small-zh-v1.5 512 中英 轻量,适合个人项目
BAAI/bge-large-zh-v1.5 1024 中英 精度高,速度慢
text-embedding-3-small 1536 多语言 OpenAI 出品,API 调用
text-embedding-3-large 3072 多语言 最高精度,价格较高

2.2 向量数据库

数据库 场景 特点 部署方式
Chroma 个人/小项目 嵌入式、无需服务、Python API pip install
Milvus 生产环境 高性能、分布式 Docker/K8s
Qdrant 中小规模 Rust 实现、性能好 Docker
Weaviate 中大规模 自带 Embedding Docker
pgvector 已有 PostgreSQL PostgreSQL 插件 数据库扩展

选择建议:个人项目用 Chroma,公司项目从 Qdrant 或 pgvector 开始。

三、搭建最小 RAG 系统

3.1 安装依赖

1
pip install chromadb sentence-transformers openai

3.2 构建知识库

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
import chromadb
from sentence_transformers import SentenceTransformer

# 初始化 Embedding 模型
embedder = SentenceTransformer("BAAI/bge-small-zh-v1.5")

# 创建 Chroma 客户端(持久化存储)
client = chromadb.PersistentClient(path="./knowledge_base")
collection = client.get_or_create_collection("my_docs")

# 准备知识文档
documents = [
"对象池通过复用对象来减少创建和销毁的开销。",
"Unity 中使用 Instantiate 和 Destroy 会触发 GC。",
"对象池适合子弹、粒子、敌人等频繁创建和销毁的对象。",
"对象池的核心是 Get() 和 Return() 两个方法。",
"Pool 的初始大小应基于游戏中的最大并发对象数估算。",
]

# 生成向量并存入
embeddings = embedder.encode(documents).tolist()
collection.add(
documents=documents,
embeddings=embeddings,
ids=[f"doc_{i}" for i in range(len(documents))]
)

print(f"知识库构建完成,共 {len(documents)} 条记录")

3.3 检索并回答

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
import openai

def ask_with_rag(question: str) -> str:
# 1. 检索相关文档
results = collection.query(
query_embeddings=[embedder.encode(question).tolist()],
n_results=3 # 返回最相关的 3 条
)
context = "\n".join(results["documents"][0])

# 2. 构造带上下文的 Prompt
prompt = f"""基于以下知识回答问题。如果知识中不包含相关信息,
请明确说"知识库中没有相关信息"。

知识:
{context}

问题:{question}

回答:"""

# 3. 调用 LLM 生成回答
response = openai.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content

# 使用示例
answer = ask_with_rag("什么是对象池?为什么需要它?")
print(answer)

四、文本分段策略

文本分段质量直接影响检索效果——分得太粗,无关内容干扰检索;分得太细,可能遗漏相关上下文。

策略 适用场景 特点 代码示例
固定长度 通用 简单但可能切断语义 text[i:i+500]
段落分割 文章/文档 语义完整 text.split('\n\n')
递归分割 代码/结构化文本 保持结构 LangChain RecursiveCharacterTextSplitter
语义分割 长文档 最精确 用 Embedding 找断点

LangChain 递归分割示例

1
2
3
4
5
6
7
8
9
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每段大小(字符数)
chunk_overlap=50, # 段与段之间重叠 50 字符
separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""]
)

chunks = splitter.split_text(your_long_text)

分割策略选择:如果是技术博客(段落清晰),用段落分割;如果是代码文档,用递归分割;如果是对话/日志,用固定长度。

五、进阶优化

5.1 元数据过滤

给文档添加 source、date、category 等标签,检索时按条件筛选:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
collection.add(
documents=documents,
embeddings=embeddings,
metadatas=[
{"source": "blog", "date": "2025-01", "category": "Unity"},
{"source": "doc", "date": "2025-03", "category": "C#"},
],
ids=[...]
)

# 检索时只搜索某个范围:
results = collection.query(
query_embeddings=[...],
where={"category": "Unity"}, # 只搜索 Unity 分类
n_results=3
)

5.2 混合检索

向量检索 + 关键词检索加权融合,兼顾语义相似度和关键词匹配:

1
2
3
4
5
6
7
8
9
# 简单混合策略:同时做向量检索和关键词检索,合并结果
def hybrid_search(query: str, alpha: float = 0.7):
# 向量检索
vector_results = vector_search(query)
# 关键词检索(BM25)
keyword_results = bm25_search(query)
# 加权融合
combined = weighted_merge(vector_results, keyword_results, alpha)
return combined

5.3 重排序

第一次快速检索出 Top 20,然后用更精确的模型重新排序,取 Top 3:

1
2
3
4
5
6
7
8
9
10
from sentence_transformers import CrossEncoder

reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")

# 检索 Top 20
initial_results = fast_search(query, n_results=20)
# 重排序取 Top 3
pairs = [[query, doc] for doc in initial_results]
scores = reranker.predict(pairs)
top_3 = [doc for _, doc in sorted(zip(scores, initial_results), reverse=True)][:3]

六、RAG vs 微调——什么时候用哪个

对比维度 RAG 微调(Fine-tuning)
数据更新 实时,更新知识库即可 需要重新训练
开发成本 低,几行代码即可搭建 高,需要训练数据和计算资源
知识精度 高,检索到的内容直接呈现 中,模型可能”遗忘”或”混淆”
幻觉控制 好,基于检索结果回答 差,模型可能自行”补充”
适用场景 问答系统、文档检索 风格适配、指令遵循
维护成本 低,只需维护文档库 高,每次更新需重新训练

建议:先做 RAG。如果 RAG 的效果不够(如需要模型改变输出风格),再考虑微调。

七、RAG 的评估指标

如何衡量你的 RAG 系统好不好用:

指标 含义 测量方法
检索召回率 相关文档被检索到的比例 人工标注测试集,看 Top-K 命中率
检索精度 检索到的文档中相关比例 看 Top-K 结果中有多少是真正相关的
生成准确性 最终回答的准确率 人工评分或 LLM-as-judge 评测
响应时间 从提问到回答的耗时 分步计时:检索→重排序→生成
端到端成功率 用户问题得到满意回答的比例 用户反馈或 A/B 测试

目标值参考:检索召回率 > 85%,端到端成功率 > 80%。

八、RAG 系统架构全景

1
2
3
4
5
6
7
8
9
10
11
┌──────────┐    ┌──────────────┐    ┌─────────┐
│ 文档 │ → │ 分段器 │ → │ Embedder│
└──────────┘ └──────────────┘ └────┬────┘

┌──────────┐ ┌──────────────┐ ┌─────────┐
│ 答案 │ ← │ LLM 生成 │ ← │ 向量库 │
└──────────┘ └──────┬───────┘ └─────────┘

┌────┴────┐
│ 重排序器 │
└─────────┘

本章小结

  • RAG 让 AI 能访问你的私有数据,解决时效性和幻觉问题
  • Embedding 将文本转为向量,相似内容在向量空间中距离近
  • Chroma 适合个人项目,Milvus/Qdrant 适合生产环境
  • 文本分段质量直接影响检索效果——优先用段落分割或递归分割
  • 进阶优化路线:元数据过滤 → 混合检索 → 重排序
  • 本博客的 ByteBot 就是一个基于 RAG 的问答助手

下一篇进入更高级的 AI Agent。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: