前面的教程中,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
embedder = SentenceTransformer("BAAI/bge-small-zh-v1.5")
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: results = collection.query( query_embeddings=[embedder.encode(question).tolist()], n_results=3 ) context = "\n".join(results["documents"][0]) prompt = f"""基于以下知识回答问题。如果知识中不包含相关信息, 请明确说"知识库中没有相关信息"。
知识: {context}
问题:{question}
回答:""" 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, 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"}, 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) 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")
initial_results = fast_search(query, n_results=20)
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。