什么是 RAG
RAG(Retrieval-Augmented Generation,检索增强生成) 是一种让大语言模型(LLM)基于外部知识库回答问题的技术。
打个比方:
- 纯 LLM = 闭卷考试:只能靠训练时记住的知识
- RAG = 开卷考试:先翻书找相关段落,再答题
用户提问:"我们公司的退货政策是什么?"
纯 LLM:我不知道你们公司的退货政策(或瞎编一个)
RAG :[检索] 找到 help_center/returns.md
[生成] 根据该文档回答:7 天无理由退货,需保留吊牌...
为什么需要 RAG
大模型有三个先天缺陷,RAG 逐一解决:
缺陷 1:不知道你的私有数据
LLM 训练数据截止 2024 年,且不包含:
- 你的企业知识库
- 你的产品文档
- 你的客户数据
- 2026 年的最新资讯
RAG 方案:把这些数据做成可检索的知识库,回答时先检索再生成。
缺陷 2:会幻觉
LLM:巴黎和会签订于 1918 年(实际是 1919 年)
原因:模型在"生成"而非"检索",遇到不确定就编
RAG 方案:把权威资料塞进 Prompt,让模型"看着资料回答"而非"凭记忆回答"。
缺陷 3:无法更新
LLM:训练一次几十万美金,更新一次再花几十万
RAG 方案:知识库随时增删改,下次提问即生效,无需重新训练。
RAG 的工作流程
┌─────────────────────────────────────────────────────────────┐
│ RAG 完整流程 │
│ │
│ 【离线阶段:建库】 │
│ 原始文档 → 切块 → Embedding → 向量数据库 │
│ PDF/MD 片段 向量 可检索 │
│ │
│ 【在线阶段:问答】 │
│ 用户问题 → Embedding → 向量检索 → Top-K 片段 → 拼 Prompt │
│ ↓ ↓ ↓ ↓ │
│ 向量 找相似 取前 5 段 喂给 LLM │
│ │
│ 最终输出:基于检索片段的答案 + 引用来源 │
└─────────────────────────────────────────────────────────────┘
Step 1:切块(Chunking)
把长文档切成小片段(通常 200-1000 字),因为:
- 向量检索需要片段足够小才能精准定位
- Prompt 长度有限,塞不下整本书
原始文档(1 万字):
┌──────────────────────────────────┐
│ 第一章 公司简介 │
│ 第二章 退货政策 ← 用户问的就是这 │
│ 第三章 配送说明 │
└──────────────────────────────────┘
切块后:
[chunk 1] 第一章公司简介...
[chunk 2] 第二章 7 天无理由退货...
[chunk 3] 第三章 配送说明...
切块策略:
- 固定长度:每 500 字一切(简单但可能切断语义)
- 按结构切:按标题/段落切(推荐)
- 重叠切:相邻块重叠 50-100 字(保留上下文)
Step 2:Embedding(向量化)
用 Embedding 模型把文本转成向量(高维数组):
"7 天无理由退货" → [0.12, -0.34, 0.56, ..., 0.78] // 1536 维
"7 天退换货" → [0.11, -0.33, 0.55, ..., 0.77] // 相似
"配送时效" → [-0.45, 0.21, -0.67, ..., 0.03] // 不相关
向量相似度 = 语义相似度。这就是为什么"7 天无理由退货"能匹配到"7 天退换货"。
常用 Embedding 模型:
| 模型 | 维度 | 特点 |
|---|---|---|
| OpenAI text-embedding-3-small | 1536 | 性价比高 |
| OpenAI text-embedding-3-large | 3072 | 精度高 |
| BGE-large-zh | 1024 | 中文开源首选 |
| Cohere embed-v3 | 1024 | 多语言 |
Step 3:存入向量数据库
把向量 + 原文片段一起存入向量数据库:
| 数据库 | 特点 |
|---|---|
| Pinecone | 托管 SaaS,开箱即用 |
| Milvus | 开源,支持十亿级向量 |
| Qdrant | Rust 写,快 |
| Chroma | 轻量,适合原型 |
| pgvector | PostgreSQL 扩展,复用现有库 |
Step 4:检索(Query 时)
用户提问时,同样的问题做 Embedding,然后在向量库找最相似的 K 个片段:
query = "怎么退货?"
query_vec = embed(query) # [0.10, -0.32, ...]
# 在向量库找 Top 5 相似片段
results = vector_db.search(query_vec, top_k=5)
# → [chunk 2: "7 天无理由退货...", chunk 7: "退货流程...", ...]
Step 5:生成(拼 Prompt)
把检索到的片段塞进 Prompt,让 LLM 基于这些片段回答:
系统:你是客服。仅基于以下资料回答,不知道就说不知道。
资料:
[1] 7 天无理由退货,需保留吊牌...
[2] 退货流程:登录 → 我的订单 → 申请退货...
[3] 退货运费:质量问题卖家承担,其他买家承担...
用户:怎么退货?运费谁出?
LLM:根据资料[2],退货流程为:登录→我的订单→申请退货。
根据资料[3],质量问题运费卖家承担,其他情况买家承担。
关键:Prompt 里强调"仅基于资料回答",并要求"标注引用",能大幅降低幻觉。
RAG vs 微调(Fine-tuning)
很多人问:既然要让模型懂我的数据,为什么不直接微调?
| 维度 | RAG | 微调 |
|---|---|---|
| 适合知识型数据 | ✅ 强 | ❌ 弱 |
| 适合风格型数据 | ❌ 弱 | ✅ 强 |
| 数据更新 | 即时生效 | 需重新训练 |
| 成本 | 低(只需 Embedding) | 高(需训练) |
| 可解释性 | 高(可看引用来源) | 低(黑盒) |
| 幻觉控制 | 强(有资料兜底) | 弱 |
| 学新技能 | ❌ | ✅ |
经验法则:
- 让模型"知道新事实" → 用 RAG
- 让模型"换种说话风格" → 用微调
- 两者也可叠加:先微调风格,再 RAG 知识
四种检索策略
策略 1:朴素向量检索(基础)
results = vector_db.search(embed(query), top_k=5)
简单快,但有问题:用户问"怎么退货",向量检索可能匹配到"退款"(语义近但非答案)。
策略 2:混合检索(向量 + 关键词)
vec_results = vector_db.search(embed(query), top_k=10)
kw_results = bm25_search(query, top_k=10)
results = rerank(vec_results + kw_results, top_k=5)
向量擅长语义匹配,关键词擅长精确匹配(如产品型号、人名)。混合两者召回更全。
策略 3:重排序(Rerank)
# 先粗召 50 个
candidates = vector_db.search(embed(query), top_k=50)
# 再用 cross-encoder 精排
results = cross_encoder.rerank(query, candidates, top_k=5)
Cross-encoder 比向量相似度更准,但慢,所以先用向量粗筛再用它精排。
策略 4:查询改写(HyDE)
# 让 LLM 先假设一个答案
hypothetical = llm("假设答案:退货流程是...")
# 用假设答案(而非原问题)去检索
results = vector_db.search(embed(hypothetical), top_k=5)
原理:答案的向量比问题的向量更接近目标片段。
完整代码示例
from openai import OpenAI
import chromadb
client = OpenAI()
db = chromadb.PersistentClient(path="./vector_db")
collection = db.get_or_create_collection("docs")
# ===== 离线建库 =====
def build_knowledge_base(docs):
for doc in docs:
# 切块(简化版,按 500 字切)
chunks = [doc[i:i+500] for i in range(0, len(doc), 450)]
for i, chunk in enumerate(chunks):
# 向量化
vec = client.embeddings.create(
input=chunk, model="text-embedding-3-small"
).data[0].embedding
# 存库
collection.add(
ids=[f"{doc['id']}_{i}"],
embeddings=[vec],
documents=[chunk],
metadatas=[{"source": doc["source"], "chunk": i}]
)
# ===== 在线问答 =====
def rag_answer(question, top_k=5):
# 1. 问题向量化
q_vec = client.embeddings.create(
input=question, model="text-embedding-3-small"
).data[0].embedding
# 2. 检索
results = collection.query(query_embeddings=[q_vec], n_results=top_k)
context = "\n\n".join(results["documents"][0])
# 3. 生成
prompt = f"""你是客服。仅基于以下资料回答,不知道就说不知道。
标注引用来源([1][2]...)。
资料:
{context}
问题:{question}"""
return client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}]
).choices[0].message.content
常见坑点
坑 1:切块不当
✗ 每 500 字硬切 → 一个退货政策被切成两半,检索只能召回一半
✓ 按标题/段落切 → 完整保留语义
坑 2:检索 Top-K 太大
✗ top_k=20 → 塞 20 个片段进 Prompt,关键信息被稀释
✓ top_k=3-5 + rerank → 少而精
坑 3:不做查询改写
用户问:"那个东西怎么用"(代词指代不明)
✗ 直接检索 → 召回乱七八糟
✓ 先让 LLM 改写:"用户之前在问咖啡机,改写为:咖啡机怎么用"
坑 4:忽略元数据过滤
✗ 全库检索 → 召回到已下架产品的文档
✓ metadata filter: {"status": "active"} → 只检索有效文档
与本站其他内容的关系
- LLM:RAG 的生成端
- Transformer:LLM 与 Embedding 模型的底层架构
- Filesystem MCP:可读取本地文件喂给 RAG
- Prompt 链式调用:RAG 常作为链路中的"检索步"
小结
RAG 是 2026 年企业 AI 落地最主流的方案——不重新训练模型,靠检索让 LLM 用你的数据回答问题。
它解决了 LLM 三大痛点:不知道私有数据、容易幻觉、无法更新。掌握 RAG,你就能搭出"懂你业务"的 AI 助手,而不仅是个通用聊天机器人。