什么是RAG?
RAG(Retrieval-Augmented Generation,检索增强生成)是一种让大语言模型能够访问外部知识库的技术,通过在生成答案前先检索相关文档,从根本上解决了大模型的三大痛点。
大模型的三大痛点
痛点1:知识截止日期
erlang
用户:今天的新闻头条是什么?
纯LLM回答:抱歉,我的知识截止到2024年1月,无法回答当前新闻...
问题:大模型在训练完成后,知识就"冻结"了,无法获取最新信息。
痛点2:幻觉(Hallucination)
erlang
用户:我们公司的休假政策是什么?
纯LLM回答:根据劳动法规定,员工每年享有5天带薪年假...
(实际上:你们公司可能是10天,模型是"猜"的)
问题:当模型不知道答案时,它会基于统计规律"编造"一个听起来合理的答案。
痛点3:领域知识缺失
erlang
用户:帮我分析一下我们公司2024年Q3的销售数据
纯LLM回答:抱歉,我无法访问您公司的内部数据...
问题:私有数据、专业领域知识无法被纳入预训练语料。
RAG如何解决这些问题?
RAG的核心思想非常直观:把相关信息直接"塞"到Prompt里。
python
用户问题:我们公司的休假政策是什么?
步骤1:检索(Retrieval)
→ 从公司知识库检索相关文档
→ 找到:"员工手册第5章:每位员工享有10天带薪年假"
步骤2:增强(Augmentation)
→ 把检索到的文档加入Prompt
→ 构造增强后的Prompt:
"""
参考文档:员工手册第5章:每位员工享有10天带薪年假
问题:我们公司的休假政策是什么?
请基于参考文档回答。
"""
步骤3:生成(Generation)
→ 大模型基于增强后的Prompt生成答案
→ 回答:"根据员工手册第5章,每位员工享有10天带薪年假"
优势:
- 信息可靠:来自真实文档,不是"猜"的
- 实时更新:更新知识库即可,无需重新训练模型
- 可追溯:能够标注信息来源
RAG的核心架构
RAG系统通常包含四个核心组件:
javascript
┌─────────────────────────────────────────────────┐
│ 用户问题 │
│ "明天北京天气怎么样?" │
└───────────────────┬─────────────────────────────┘
↓
┌───────────────────────────────────────────────────┐
│ 1. Query重写与理解(Query Rewriter) │
│ - 改写问题使其更适合检索 │
│ - 提取关键词 │
│ - 生成多个查询变体 │
└───────────────────┬───────────────────────────────┘
↓
┌───────────────────────────────────────────────────┐
│ 2. 检索器(Retriever) │
│ - 从向量数据库检索相关文档 │
│ - 返回Top-K个最相关片段 │
└───────────────────┬───────────────────────────────┘
↓
┌───────────────────────────────────────────────────┐
│ 3. 重排序器(Reranker) │
│ - 对检索结果进行精细排序 │
│ - 过滤掉不相关的文档 │
└───────────────────┬───────────────────────────────┘
↓
┌───────────────────────────────────────────────────┐
│ 4. 生成器(Generator) │
│ - 大语言模型基于检索到的文档生成答案 │
│ - 标注引用来源 │
└───────────────────┬───────────────────────────────┘
↓
┌─────────┐
│ 答案 │
└─────────┘
组件1:文档准备与向量化
在RAG系统运行之前,需要先构建知识库。
文档分块(Chunking)
大文档需要切分成小片段,原因:
- 上下文窗口限制:不能把整本书都塞进Prompt
- 检索精度:小片段更容易匹配用户问题
- 成本控制:减少Token消耗
常见分块策略:
策略1:固定大小分块
python
def fixed_size_chunking(text, chunk_size=512, overlap=50):
"""
按固定字符数分块
参数:
- chunk_size: 每块大小(字符数)
- overlap: 块之间的重叠(避免语义被切断)
"""
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunk = text[start:end]
chunks.append(chunk)
start = end - overlap # 重叠部分
return chunks
# 示例
text = "人工智能是...(省略5000字)"
chunks = fixed_size_chunking(text, chunk_size=512, overlap=50)
# 结果:10个chunk,每个约512字符,相邻chunk重叠50字符
优点 :简单、快速 缺点:可能在句子中间切断,破坏语义
策略2:语义分块
python
def semantic_chunking(text, max_chunk_size=512):
"""
按语义单元分块(段落、句子)
"""
paragraphs = text.split('\n\n') # 按段落分
chunks = []
current_chunk = ""
for para in paragraphs:
if len(current_chunk) + len(para) <= max_chunk_size:
current_chunk += para + "\n\n"
else:
if current_chunk:
chunks.append(current_chunk.strip())
current_chunk = para + "\n\n"
if current_chunk:
chunks.append(current_chunk.strip())
return chunks
优点 :保持语义完整性 缺点:可能产生大小不均的chunk
策略3:递归字符分块(LangChain默认)
python
# 按优先级尝试不同的分隔符
separators = ["\n\n", "\n", "。", ".", " "]
# 先按段落分,如果段落太大再按句子分,如此递归
优点 :平衡了大小和语义 缺点:实现较复杂
向量化(Embedding)
将文本chunk转换为数值向量,使计算机能够"理解"语义相似度。
Embedding模型的选择
| 模型 | 维度 | 语言 | 适用场景 |
|---|---|---|---|
| OpenAI text-embedding-3-small | 1536 | 多语言 | 通用场景,性价比高 |
| OpenAI text-embedding-3-large | 3072 | 多语言 | 高精度需求 |
| bge-large-zh | 1024 | 中文优化 | 中文场景首选 |
| bge-m3 | 1024 | 多语言 | 多语言混合场景 |
| Cohere embed-multilingual | 768 | 100+语言 | 全球化产品 |
Embedding原理
python
# 示例:将文本转为向量
chunk1 = "今天天气很好"
chunk2 = "今日天气不错"
chunk3 = "量子计算机的原理"
embedding1 = embedding_model.encode(chunk1)
# → [0.23, 0.45, -0.12, ..., 0.78] (1024维向量)
embedding2 = embedding_model.encode(chunk2)
# → [0.25, 0.43, -0.10, ..., 0.76] (语义相近,向量也相近)
embedding3 = embedding_model.encode(chunk3)
# → [-0.54, 0.12, 0.89, ..., -0.32] (语义不同,向量差异大)
语义相似度计算:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> similarity ( v 1 , v 2 ) = v 1 ⋅ v 2 ∣ ∣ v 1 ∣ ∣ ⋅ ∣ ∣ v 2 ∣ ∣ \text{similarity}(v_1, v_2) = \frac{v_1 \cdot v_2}{||v_1|| \cdot ||v_2||} </math>similarity(v1,v2)=∣∣v1∣∣⋅∣∣v2∣∣v1⋅v2
这就是余弦相似度(Cosine Similarity),值范围为[-1, 1]:
- 1:完全相同
- 0:无关
- -1:完全相反
python
# 计算相似度
similarity(embedding1, embedding2) = 0.95 # 很相似
similarity(embedding1, embedding3) = 0.12 # 不相关
向量数据库(Vector Database)
存储和检索向量的专用数据库。
主流向量数据库对比:
| 数据库 | 类型 | 特点 | 适用场景 |
|---|---|---|---|
| Pinecone | 云服务 | 全托管,性能强 | 快速上线,不想管理基础设施 |
| Weaviate | 开源/云 | 功能全面,支持混合搜索 | 需要高级功能(过滤、图谱) |
| Milvus | 开源 | 性能强,规模大 | 大规模数据(百万级以上) |
| Chroma | 开源 | 轻量级,易上手 | 小项目、快速原型 |
| Qdrant | 开源/云 | Rust实现,高性能 | 注重性能和可靠性 |
| Faiss | 开源库 | Meta开发,算法多 | 学术研究、自建系统 |
向量数据库的核心操作:
python
# 1. 创建索引
import chromadb
client = chromadb.Client()
collection = client.create_collection(name="company_docs")
# 2. 插入向量
collection.add(
embeddings=[embedding1, embedding2, embedding3],
documents=[chunk1, chunk2, chunk3],
ids=["doc1", "doc2", "doc3"]
)
# 3. 检索(查询)
query = "今天天气如何"
query_embedding = embedding_model.encode(query)
results = collection.query(
query_embeddings=[query_embedding],
n_results=2 # 返回Top-2
)
# 返回:
# [
# {"id": "doc1", "document": "今天天气很好", "distance": 0.05},
# {"id": "doc2", "document": "今日天气不错", "distance": 0.07}
# ]
组件2:检索器(Retriever)
检索器负责从向量数据库中找到与用户问题最相关的文档片段。
检索方法对比
方法1:稠密检索(Dense Retrieval)
原理:用Embedding模型将问题和文档都转为向量,通过向量相似度检索。
python
query = "什么是Transformer的注意力机制?"
query_embedding = embedding_model.encode(query)
# 在向量数据库中搜索
results = vector_db.search(query_embedding, top_k=5)
优点:
- 语义理解强:能匹配不同表述的相同含义
- 效果好:当前主流方案
缺点:
- 依赖Embedding质量
- 对罕见词、专有名词效果差
方法2:稀疏检索(Sparse Retrieval)
原理:传统关键词搜索(BM25、TF-IDF)。
python
# BM25算法
from rank_bm25 import BM25Okapi
corpus = ["文档1", "文档2", "文档3"]
tokenized_corpus = [doc.split() for doc in corpus]
bm25 = BM25Okapi(tokenized_corpus)
query = "Transformer 注意力"
scores = bm25.get_scores(query.split())
# 返回每个文档的BM25分数
优点:
- 精确匹配:对专有名词、代码、ID等效果好
- 可解释性强:能看到匹配的关键词
缺点:
- 无语义理解:无法匹配同义词
- 对长尾问题效果差
方法3:混合检索(Hybrid Retrieval)
原理:结合稠密和稀疏检索,取长补短。
python
# 1. 分别执行两种检索
dense_results = dense_retrieval(query, top_k=10)
sparse_results = sparse_retrieval(query, top_k=10)
# 2. 融合结果(Reciprocal Rank Fusion)
def reciprocal_rank_fusion(results1, results2, k=60):
scores = {}
for rank, doc_id in enumerate(results1):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
for rank, doc_id in enumerate(results2):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
# 按分数排序
final_results = sorted(scores.items(), key=lambda x: x[1], reverse=True)
return final_results[:5] # 返回Top-5
实际效果对比:
| 场景 | 稠密检索 | 稀疏检索 | 混合检索 |
|---|---|---|---|
| "什么是注意力机制?" | ✅ 90分 | ⚠️ 70分 | ✅ 95分 |
| "Attention mechanism" | ✅ 85分 | ⚠️ 65分 | ✅ 92分 |
| "产品ID: A1234-B56" | ⚠️ 40分 | ✅ 95分 | ✅ 98分 |
| "如何优化推理速度" | ✅ 88分 | ⚠️ 72分 | ✅ 93分 |
结论:混合检索在大多数场景下表现最佳。
Query改写(Query Rewriting)
用户的原始问题往往不是最佳检索查询,需要改写优化。
技术1:HyDE(Hypothetical Document Embeddings)
思路:让大模型先生成一个"假想答案",用这个答案去检索。
python
# 用户问题
query = "如何优化Transformer的推理速度?"
# Step 1: 生成假想答案
hypothetical_answer = llm.generate(
f"请详细回答:{query}"
)
# 输出:"优化Transformer推理速度的方法包括:
# 1. 使用KV Cache减少重复计算
# 2. 量化模型参数到INT8或INT4
# 3. 采用FlashAttention算法..."
# Step 2: 用假想答案去检索(而不是原问题)
results = vector_db.search(
embedding_model.encode(hypothetical_answer),
top_k=5
)
为什么有效?
- 问题往往很短、信息少
- 答案包含更多关键词和上下文
- 答案和文档的语义空间更接近
技术2:多查询生成(Multi-Query)
思路:生成多个变体查询,分别检索后合并结果。
python
# 原问题
query = "Transformer如何处理长文本?"
# 生成多个变体
queries = llm.generate(
f"为以下问题生成3个不同角度的变体:{query}"
)
# 输出:
# 1. "Transformer模型在处理长序列时会遇到什么问题?"
# 2. "如何扩展Transformer的上下文窗口长度?"
# 3. "哪些技术可以让Transformer支持更长的输入?"
# 分别检索
all_results = []
for q in queries:
results = vector_db.search(embedding_model.encode(q), top_k=3)
all_results.extend(results)
# 去重合并
final_results = deduplicate_and_rank(all_results, top_k=5)
技术3:查询扩展(Query Expansion)
思路:添加同义词、相关术语。
python
query = "AI Agent"
# 扩展同义词
expanded_query = query + " 智能体 autonomous agent ReAct"
# 用扩展后的查询检索
results = vector_db.search(embedding_model.encode(expanded_query), top_k=5)
组件3:重排序(Reranking)
检索器返回的Top-K结果可能不够精确,重排序器对结果进行二次排序。
为什么需要重排序?
检索器的局限:
- 基于向量相似度,可能不够精确
- 没有考虑问题和文档的交互关系
- 效率优先,牺牲了部分准确性
重排序器的优势:
- 使用更复杂的模型(Cross-Encoder)
- 深度理解问题和文档的匹配度
- 只对Top-K结果排序,成本可控
重排序模型
架构对比:Bi-Encoder vs Cross-Encoder
Bi-Encoder(检索器常用):
markdown
Query → Encoder → Query Vector
→ 计算相似度
Doc → Encoder → Doc Vector
- 问题和文档分别编码
- 速度快,适合大规模检索
- 但问题和文档之间没有交互
Cross-Encoder(重排序器常用):
csharp
[Query + Doc] → Encoder → 相关性分数
- 问题和文档拼接后一起编码
- 能捕捉深层交互关系
- 更准确,但慢(只用于重排少量候选)
实现示例
python
from sentence_transformers import CrossEncoder
# 1. 检索阶段(Bi-Encoder,快速)
retrieval_results = vector_db.search(query_embedding, top_k=100)
# 2. 重排序阶段(Cross-Encoder,精确)
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-12-v2')
# 计算每个文档与问题的相关性分数
pairs = [[query, doc] for doc in retrieval_results]
scores = reranker.predict(pairs)
# 按分数重新排序
reranked_results = sorted(
zip(retrieval_results, scores),
key=lambda x: x[1],
reverse=True
)[:5] # 取Top-5
常用重排序模型
| 模型 | 大小 | 语言 | 性能 | 速度 |
|---|---|---|---|---|
| bge-reranker-large | 560M | 中英文 | ⭐⭐⭐⭐⭐ | 中等 |
| bge-reranker-base | 279M | 中英文 | ⭐⭐⭐⭐ | 快 |
| cohere-rerank-multilingual | API | 100+语言 | ⭐⭐⭐⭐⭐ | API |
| cross-encoder/ms-marco-MiniLM | 66M | 英文 | ⭐⭐⭐ | 很快 |
效果提升示例:
markdown
原始检索Top-5(只看向量相似度):
1. 相关性:60%
2. 相关性:55%
3. 相关性:50%
4. 相关性:95% ← 最相关的排在第4!
5. 相关性:45%
重排序后Top-5:
1. 相关性:95% ← 正确排序
2. 相关性:60%
3. 相关性:55%
4. 相关性:50%
5. 相关性:45%
组件4:生成器(Generator)
生成器是大语言模型,负责基于检索到的文档生成最终答案。
Prompt设计
基础RAG Prompt模板:
python
prompt_template = """
你是一个专业的AI助手。请根据以下参考文档回答用户问题。
【重要规则】
1. 答案必须基于参考文档,不要编造信息
2. 如果文档中没有相关信息,请明确说明"根据提供的文档无法回答"
3. 引用文档时请标注来源
参考文档:
{retrieved_documents}
用户问题:
{user_question}
请给出答案:
"""
示例:
css
参考文档:
[文档1] 员工手册第5章:每位员工享有10天带薪年假,工作满5年后增加到15天。
[文档2] 请假流程:需提前3天向直属主管提交申请,经批准后生效。
用户问题:
我工作2年了,有多少天年假?
AI回答:
根据员工手册第5章,每位员工享有10天带薪年假。由于您工作了2年,
尚未满足"工作满5年"的条件,因此您当前享有10天带薪年假。
如需请假,请参考请假流程:提前3天向直属主管提交申请。
【来源】员工手册第5章、请假流程文档
高级Prompt技巧
技巧1:引导思维链(Chain-of-Thought)
python
prompt = """
参考文档:{documents}
用户问题:{question}
请按以下步骤回答:
1. 首先,确定问题的关键点
2. 然后,从文档中找到相关信息
3. 最后,综合信息给出答案
你的回答:
"""
技巧2:多文档推理
python
prompt = """
你需要综合以下多个文档的信息来回答问题。
文档A:{doc_a}
文档B:{doc_b}
文档C:{doc_c}
问题:{question}
请注意:
- 如果文档之间有冲突,请指出并说明
- 如果需要综合多个文档,请明确说明
"""
技巧3:不确定性表达
python
prompt = """
参考文档:{documents}
问题:{question}
回答要求:
- 如果文档中有明确答案,给出确定的回答
- 如果文档中信息不完整,说明"部分信息缺失"
- 如果文档完全无关,说明"无法根据提供的文档回答"
你的回答:
"""
引用标注(Citation)
让模型标注答案来源,提高可信度。
python
prompt = """
参考文档:
[1] 文档标题A:内容...
[2] 文档标题B:内容...
[3] 文档标题C:内容...
问题:{question}
回答格式要求:
答案:【你的答案】
来源:[引用的文档编号]
示例:
答案:根据公司政策,员工享有10天年假。
来源:[1]
"""
RAG的高级技巧
技巧1:自查询(Self-Querying)
问题:用户问题中可能包含元数据过滤条件。
markdown
用户问题:"2023年Q4的销售报告"
传统RAG:直接检索 → 可能返回2022年、2024年的报告
自查询RAG:
1. 提取过滤条件:year=2023, quarter=Q4, type=销售报告
2. 在向量检索时应用过滤器
3. 只在符合条件的文档中检索
实现:
python
# 1. 让LLM提取元数据
extraction_prompt = f"""
从以下问题中提取结构化信息:
问题:{user_question}
输出JSON格式:
{{
"semantic_query": "语义查询部分",
"filters": {{
"year": ...,
"quarter": ...,
"type": ...
}}
}}
"""
metadata = llm.extract(extraction_prompt)
# 2. 应用过滤器检索
results = vector_db.search(
query_embedding=embedding_model.encode(metadata["semantic_query"]),
filters=metadata["filters"],
top_k=5
)
技巧2:分层检索(Hierarchical Retrieval)
问题:有些文档有层次结构(章节、段落)。
方案:
- 先检索文档级别(找到相关文档)
- 再检索段落级别(找到具体片段)
python
# 层级1:检索相关文档
doc_results = vector_db.search_documents(query_embedding, top_k=3)
# 层级2:在选中的文档内检索段落
paragraph_results = []
for doc in doc_results:
paras = vector_db.search_paragraphs(
query_embedding,
document_id=doc.id,
top_k=2
)
paragraph_results.extend(paras)
技巧3:时间衰减(Temporal Decay)
问题:新信息应该比旧信息更重要。
python
import datetime
def time_weighted_score(similarity_score, document_date):
"""
结合相似度和时间新鲜度的混合评分
"""
days_old = (datetime.now() - document_date).days
time_decay = math.exp(-days_old / 365) # 一年衰减到约37%
final_score = 0.7 * similarity_score + 0.3 * time_decay
return final_score
技巧4:多跳推理(Multi-Hop Reasoning)
问题:有些问题需要多次检索才能回答。
arduino
问题:"GPT-4的作者之前发表过哪些著名论文?"
步骤1:检索 "GPT-4的作者" → 找到 "OpenAI团队,主要贡献者包括..."
步骤2:检索 "某某研究员的论文" → 找到论文列表
步骤3:综合回答
实现(结合ReAct Agent):
python
def multi_hop_rag(question):
thoughts = []
context = []
# 迭代式检索
for step in range(max_steps):
# 生成下一步的查询
next_query = llm.generate(
f"问题:{question}\n已知信息:{context}\n下一步应该查询什么?"
)
# 检索
results = vector_db.search(next_query)
context.append(results)
# 判断是否足够回答
is_sufficient = llm.check(
f"问题:{question}\n已知:{context}\n信息是否足够?"
)
if is_sufficient:
break
# 生成最终答案
answer = llm.generate(
f"问题:{question}\n参考信息:{context}\n请给出答案:"
)
return answer
RAG的评估指标
如何衡量RAG系统的好坏?
评估维度
1. 检索质量
指标:
- Recall@K:Top-K结果中包含正确答案的比例
- MRR(Mean Reciprocal Rank):正确答案平均排名的倒数
- NDCG(Normalized Discounted Cumulative Gain):考虑排序质量
python
# 示例:计算Recall@5
def recall_at_k(retrieved_docs, relevant_docs, k=5):
"""
retrieved_docs: 检索到的文档ID列表(按相关性排序)
relevant_docs: 真正相关的文档ID集合
"""
retrieved_top_k = set(retrieved_docs[:k])
relevant_set = set(relevant_docs)
overlap = retrieved_top_k & relevant_set
recall = len(overlap) / len(relevant_set)
return recall
# 示例
retrieved = ["doc1", "doc3", "doc7", "doc2", "doc9"]
relevant = ["doc1", "doc2", "doc5"]
recall = recall_at_k(retrieved, relevant, k=5)
# 结果:2/3 = 0.67(检索到了doc1和doc2,漏了doc5)
2. 生成质量
指标:
- Faithfulness(忠实度):答案是否基于检索到的文档
- Answer Relevancy(相关性):答案是否回答了问题
- Context Relevancy(上下文相关性):检索到的文档是否相关
自动评估(使用LLM作为评判):
python
def evaluate_faithfulness(answer, retrieved_docs):
"""
评估答案是否忠实于文档
"""
eval_prompt = f"""
参考文档:{retrieved_docs}
AI回答:{answer}
问题:AI的回答是否完全基于参考文档,没有编造信息?
评分标准:
1分:回答包含文档中没有的信息
2分:回答大部分基于文档,但有少量推测
3分:回答完全基于文档
给出评分(1-3)和理由:
"""
score = llm.evaluate(eval_prompt)
return score
3. 端到端质量
指标:
- Answer Correctness:答案是否正确
- Latency:响应时间
- Cost:每次查询的成本(Token消耗)
评估框架:RAGAS
RAGAS(RAG Assessment)是专门用于评估RAG系统的框架。
python
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_relevancy,
context_recall
)
# 准备评估数据
data = {
"question": ["问题1", "问题2", ...],
"answer": ["AI回答1", "AI回答2", ...],
"contexts": [["检索doc1", "检索doc2"], ...],
"ground_truth": ["正确答案1", "正确答案2", ...]
}
# 运行评估
result = evaluate(
dataset=data,
metrics=[
faithfulness,
answer_relevancy,
context_relevancy,
context_recall
]
)
print(result)
# 输出:
# {
# "faithfulness": 0.92,
# "answer_relevancy": 0.88,
# "context_relevancy": 0.85,
# "context_recall": 0.79
# }
RAG vs 微调(Fine-tuning)
什么时候用RAG,什么时候用微调?
| 对比维度 | RAG | 微调(Fine-tuning) |
|---|---|---|
| 适用场景 | 需要实时更新的知识 | 需要改变模型行为/风格 |
| 成本 | 低(无需训练) | 高(需要GPU训练) |
| 更新速度 | 实时(更新知识库即可) | 慢(需重新训练) |
| 知识准确性 | 高(来自真实文档) | 中(可能产生幻觉) |
| 响应速度 | 慢(需要检索) | 快(直接生成) |
| 适用知识类型 | 事实性知识、文档内容 | 任务模式、领域语言风格 |
| 可解释性 | 强(可追溯来源) | 弱(黑盒) |
实际案例:
| 任务 | 推荐方案 | 原因 |
|---|---|---|
| 企业知识库问答 | RAG | 文档频繁更新,需要引用来源 |
| 客服对话风格 | 微调 | 需要学习特定的对话模式 |
| 医疗诊断辅助 | RAG | 需要基于最新医学文献 |
| 代码生成 | 微调 | 需要学习特定编程风格 |
| 法律文档分析 | RAG + 微调 | 结合:微调学习法律术语,RAG检索具体案例 |
最佳实践:RAG + 微调组合
markdown
1. 微调:让模型学习领域知识和对话风格
2. RAG:提供具体、最新的事实信息
3. 结果:既有专业性,又有准确性
RAG的常见问题与解决方案
问题1:检索不到相关文档
可能原因:
- Embedding模型不匹配(英文模型处理中文)
- 文档分块不合理(切断了关键信息)
- 用户问题表述与文档差异大
解决方案:
- 使用与文档语言匹配的Embedding模型
- 优化分块策略(语义分块 + 重叠)
- 使用Query改写技术(HyDE、多查询)
问题2:检索到的文档不相关
可能原因:
- Top-K设置太大(引入噪声)
- 没有使用重排序
- 向量相似度≠语义相关
解决方案:
- 减小K值(比如从20降到5)
- 添加重排序层(Cross-Encoder)
- 使用混合检索(稠密+稀疏)
问题3:模型不基于文档回答
可能原因:
- Prompt设计不当
- 模型倾向于使用自身知识
- 检索到的文档质量差
解决方案:
- 强化Prompt指令:"必须基于文档回答"
- 使用更强的推理模型(如GPT-4)
- 提高检索质量
问题4:响应速度慢
瓶颈分析:
| 步骤 | 耗时占比 | 优化方案 |
|---|---|---|
| Embedding查询 | 5-10% | 使用小模型(384维vs1024维) |
| 向量检索 | 10-20% | 优化索引(HNSW、IVF) |
| 重排序 | 20-30% | 只重排Top-10而非Top-100 |
| LLM生成 | 40-60% | 使用更快的模型(Haiku vs Sonnet) |
优化示例:
python
# 优化前:2000ms
results = vector_db.search(query_emb, top_k=100) # 200ms
reranked = reranker.rerank(results) # 500ms
answer = llm.generate(prompt) # 1300ms
# 优化后:800ms
results = vector_db.search(query_emb, top_k=20) # 100ms
reranked = reranker.rerank(results[:10]) # 150ms
answer = faster_llm.generate(prompt) # 550ms
问题5:成本过高
成本分解:
markdown
单次RAG查询成本:
- Embedding(query):$0.0001
- 向量数据库查询:$0.0001
- Reranking:$0.0001
- LLM生成(2K tokens):$0.01
----------------------------------------
总计:约 $0.01 / 次
优化策略:
- 缓存:相同问题直接返回缓存答案
- 批处理:多个查询一起处理
- 模型选择:非关键场景用小模型
- 智能路由:简单问题不走RAG
python
# 智能路由示例
def should_use_rag(question):
"""
判断问题是否需要RAG
"""
# 简单问题直接回答
if is_simple_question(question): # 如"你好"
return False
# 通用知识问题,模型内部知识足够
if is_general_knowledge(question): # 如"什么是Python"
return False
# 需要特定/实时信息,使用RAG
return True
RAG的未来方向
1. Graph RAG
传统RAG局限:只能基于文本相似度检索,无法理解实体关系。
Graph RAG:构建知识图谱,理解实体关系。
rust
问题:"北京和上海之间有直达高铁吗?"
传统RAG:检索包含"北京"和"上海"的文档
Graph RAG:
1. 识别实体:北京(城市)、上海(城市)
2. 查询关系:(北京)-[直达高铁]->(上海)
3. 返回:是,京沪高铁
2. Agentic RAG
结合Agent能力:让RAG系统能主动规划、多轮检索。
python
# 传统RAG:一次检索
results = retrieve(question)
answer = generate(results)
# Agentic RAG:多轮交互
agent = RAGAgent()
while not agent.is_done():
thought = agent.think() # 规划下一步
action = agent.act() # 检索或生成
observation = agent.observe() # 评估结果
3. Multimodal RAG
不仅检索文本,还检索图片、视频、音频。
diff
问题:"这个产品的外观是什么样的?"
检索结果:
- 文本:产品描述文档
- 图片:产品照片
- 视频:产品展示视频
多模态LLM综合生成答案
4. Personalized RAG
根据用户历史和偏好个性化检索。
python
# 考虑用户上下文
user_context = {
"role": "前端工程师",
"history": ["之前问过React相关问题"],
"preference": "喜欢代码示例"
}
# 个性化检索
results = retrieve(
question=question,
user_context=user_context,
boost_fields=["code_examples"] # 提升代码示例权重
)
总结
RAG(检索增强生成)通过"检索相关文档 + 增强Prompt"的方式,让大模型能够:
- ✅ 访问最新信息(突破知识截止日期)
- ✅ 减少幻觉(基于真实文档)
- ✅ 利用私有数据(企业知识库)
- ✅ 提供引用来源(可信度高)
核心组件:
- 文档准备:分块、向量化、入库
- 检索器:稠密/稀疏/混合检索
- 重排序:Cross-Encoder精细排序
- 生成器:LLM基于文档生成答案
关键技术:
- Query改写(HyDE、多查询)
- 混合检索(向量+关键词)
- 重排序(提高Top-K质量)
- Prompt工程(引导模型基于文档回答)
实践建议:
- 从简单架构开始(基础RAG)
- 根据评估结果迭代优化
- 考虑RAG + 微调组合
- 关注成本和响应速度平衡
RAG已成为大模型应用的标配技术,掌握RAG是构建生产级AI应用的关键能力!