RAG(Retrieval-Augmented Generation,检索增强生成)是当前 AI 应用开发中最实用的技术之一,能够让 LLM 访问外部知识库,解决知识时效性和幻觉问题,无需微调即可实现领域专业化。
1. RAG 核心概念
1.1 什么是 RAG?
RAG 将传统的信息检索系统与生成式 LLM 结合,工作流程如下:
用户问题 → 向量化 → 检索相关文档 → 拼接到 Prompt → LLM 生成答案
↓
"2024年春节是哪天?"
↓
[向量: [0.23, -0.45, ...]]
↓
检索到:"2024年春节是2月10日(农历正月初一)"
↓
Prompt: "根据以下信息回答:【检索内容】\n\n问题:2024年春节是哪天?"
↓
LLM 输出:"2024年春节是2月10日。"1.2 RAG vs 微调 vs 纯 LLM
| 方案 | 成本 | 知识更新 | 可解释性 | 适用场景 |
|---|---|---|---|---|
| 纯 LLM | 低 | ❌ 知识截止日期固定 | 低 | 通用对话、创意写作 |
| RAG | 中 | ✅ 实时更新文档即可 | 高(可追溯来源) | 企业知识库、客服、文档问答 |
| 微调 | 高 | ❌ 需重新训练 | 低 | 特定风格、行为模式 |
| RAG + 微调 | 最高 | ✅ 分离知识与风格 | 高 | 垂直领域专家系统 |
1.3 RAG 解决的核心问题
- 幻觉问题:LLM 常常一本正经地胡说八道。RAG 通过提供事实依据,显著降低幻觉。
- 知识时效性:LLM 的训练数据是过去的,比如 GPT-4 可能不知道昨天发生的新闻。RAG 可以连接最新的互联网搜索结果或数据库。
- 私有知识访问:LLM 无法访问企业内部文档。RAG 相当于给 LLM 开了个“外挂”,让它能查阅私有资料。
2. RAG 工作流详解
一个完整的 RAG 系统分为**离线(构建知识库)和在线(检索生成)**两个阶段。
2.1 离线阶段:知识库构建
- 文档加载 (Load):读取 PDF, Word, Markdown, 网页等源文件。
- 文本分块 (Chunking):将长文档切分成小的片段(Chunks),例如每块 500 字。这是为了适应 Embedding 模型的窗口限制,并提高检索精准度。
- 向量化 (Embedding):使用 Embedding 模型将文本块转化为高维向量(Vector)。
- 存储 (Indexing):将向量和原始文本存入向量数据库 (Vector DB)。
2.2 在线阶段:检索生成
- 用户提问。
- 查询向量化:将用户问题转化为向量。
- 相似度检索 (Retrieval):在向量库中查找与问题向量最接近的 Top-K 个 Chunks。
- Prompt 组装:将检索到的上下文 (Context) 填充到 Prompt 模板中。
- LLM 生成 (Generation):LLM 根据 Context 回答问题。
2.3 代码示例:基于 LangChain 的简单 RAG
python
from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.llms import OpenAI
# 1. 加载
loader = TextLoader('state_of_the_union.txt')
documents = loader.load()
# 2. 分块
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
# 3. 向量化并存储
embeddings = OpenAIEmbeddings()
db = Chroma.from_documents(texts, embeddings)
# 4. 构建问答链
qa = RetrievalQA.from_chain_type(llm=OpenAI(), chain_type="stuff", retriever=db.as_retriever())
# 5. 提问
query = "What did the president say about Ketanji Brown Jackson"
print(qa.run(query))3. RAG 核心组件详解
3.1 Embedding 模型
决定了检索的准确度。
- OpenAI Ada-002 / text-embedding-3:闭源首选,简单稳定,成本低。
- BGE (BAAI General Embedding):智源开源,中文效果极佳,支持本地部署。
- M3E (Moka Massive Mixed Embedding):另一款优秀的开源中文 Embedding。
3.2 向量数据库 (Vector Database)
- Chroma / FAISS:轻量级,适合本地开发或小规模应用。
- Milvus / Zilliz:云原生分布式,适合海量数据(亿级向量)。
- Pinecone:SaaS 全托管,易用但收费。
- Elasticsearch / PostgreSQL (pgvector):利用现有基础设施扩展向量检索能力。
3.3 检索策略优化
单纯的向量检索有时不够准确,比如搜索专有名词。
- 混合检索 (Hybrid Search):同时结合 向量检索 (Dense) 和 关键词检索 (Sparse / BM25),加权融合结果 (Reciprocal Rank Fusion, RRF)。
- 重排序 (Reranking):先检索 Top 50 粗排,再用 Rerank 模型(如 BGE-Reranker)精细打分,取 Top 5 给 LLM。这能显著提升准确率。
4. RAG 评估指标
如何知道你的 RAG 系统好不好?需要关注检索和生成两个环节。
| 指标 | 说明 | 改善方向 |
|---|---|---|
| 召回率 (Recall) | 相关文档是否被检索到了? | 优化 Embedding,尝试混合检索 |
| 精确率 (Precision) | 检索到的文档中有多少是有用的? | 引入 Rerank 重排序 |
| 忠实度 (Faithfulness) | 答案是否忠实于检索到的上下文? | 优化 Prompt,降低 Temperature |
| 答案相关性 (Answer Relevance) | 答案是否回答了用户的问题? | 优化 Query 理解和改写 |
工具推荐:RAGAS (RAG Assessment) 是一个使用 LLM 来自动评估 RAG 系统的框架。
5. 常见问题与优化技巧
问题 1:检索不准(Missed Content)
检索回来的 Chunk 完美避开了答案。
- 优化分块 (Chunking Strategy):
- 固定大小:简单,但可能切断语义。
- 语义分块:按段落、句号切分。
- 递归分块 (Recursive):LangChain 默认策略,优先按段落切,太长再按句子切。
- 滑动窗口 (Overlap):保留前后一定的重叠(如 Chunk 500字,Overlap 50字),保持上下文连贯。
- 查询改写 (Query Rewriting):将用户模糊的问题改写得更具体,或者生成多个相关问题进行“多路召回”。
问题 2:Context 包含干扰信息(Noise)
检索到了文档,但里面包含 irrelevant 内容,干扰了 LLM。
- Rerank (重排序):把最相关的排在最前面。
- Context Compression:让 LLM 或小模型提取 Chunk 中的关键句子,压缩后再喂给大模型。
问题 3:回答产生幻觉
即使给了 Context,模型还是胡编。
- Prompt 约束:明确指令“请仅根据提供的上下文回答,如果不知道,请说不知道,不要编造。”
- 引用标注:要求模型在回答时标注引用来源(如
[Source 1]),这能倒逼模型“言之有据”。
6. 实战建议与学习路径
Week 1:基础 RAG 实现
- 使用 LangChain + ChromaDB + OpenAI 构建一个简单的文档问答 Demo。
- 尝试加载自己的 PDF 或 Markdown 笔记。
Week 2-3:优化与生产化
- 分块调优:测试不同 chunk_size (256, 512, 1024) 对效果的影响。
- 尝试 Rerank:引入 BGE-Reranker 提升排序质量。
- 混合检索:如果某些关键词搜不到,加入 BM25 检索。
最佳实践清单:
- ✅ 小步迭代:从几十个文档开始,跑通流程再扩展。
- ✅ 重视数据清洗:PDF 解析出的乱码、页眉页脚是干扰源,必须要清洗。
- ✅ Prompt 是关键:精心设计的 Prompt 能解决大部分幻觉问题。
- ✅ 持续评估:建立一套测试集(问题 + 标准答案),每次改动后自动跑一遍分。
7. 现代 RAG 实战代码(2026 版)
上面的 LangChain 示例使用了旧版 API。以下是 2026 年生产级 RAG 的推荐写法。
方式一:LangChain v0.3+(推荐框架方案)
python
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import PGVector
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# 1. 初始化组件
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 2. 连接向量数据库(pgvector 生产推荐)
vectorstore = PGVector(
connection="postgresql://user:pass@localhost:5432/mydb",
collection_name="knowledge_base",
embedding_function=embeddings
)
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 5}
)
# 3. RAG Prompt 模板
rag_prompt = ChatPromptTemplate.from_template("""
基于以下检索到的上下文回答用户问题。
如果上下文中没有相关信息,请直接说"我没有找到相关信息",不要编造。
在关键信息后标注引用来源编号 [1] [2] 等。
上下文:
{context}
问题:{question}
""")
# 4. 构建 RAG Chain(LCEL 链式表达)
def format_docs(docs):
return "\n\n".join(
f"[{i+1}] {doc.page_content}"
for i, doc in enumerate(docs)
)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| rag_prompt
| llm
| StrOutputParser()
)
# 5. 使用
answer = rag_chain.invoke("MCP 协议的核心架构是什么?")
print(answer)
# 流式输出
async for chunk in rag_chain.astream("RAG 和微调有什么区别?"):
print(chunk, end="", flush=True)方式二:原生 OpenAI(无框架,最轻量)
python
from openai import AsyncOpenAI
import asyncpg
import json
client = AsyncOpenAI()
async def rag_query(question: str) -> str:
"""无框架的原生 RAG 实现"""
# 1. 查询向量化
embedding_resp = await client.embeddings.create(
model="text-embedding-3-small",
input=question
)
query_vector = embedding_resp.data[0].embedding
# 2. 向量检索(pgvector)
conn = await asyncpg.connect("postgresql://user:pass@localhost/mydb")
rows = await conn.fetch("""
SELECT content, metadata,
1 - (embedding <=> $1::vector) AS similarity
FROM documents
ORDER BY embedding <=> $1::vector
LIMIT 5
""", json.dumps(query_vector))
await conn.close()
# 3. 组装上下文
context = "\n\n".join(
f"[{i+1}] {row['content']}"
for i, row in enumerate(rows)
)
# 4. LLM 生成
response = await client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": f"""基于以下检索结果回答问题。
如果信息不足,说明"未找到相关信息"。引用格式:[1] [2]
检索结果:
{context}"""},
{"role": "user", "content": question}
],
temperature=0
)
return response.choices[0].message.content
# 使用
answer = await rag_query("向量数据库和传统数据库有什么区别?")8. RAGAS 自动评估实战
python
from ragas import evaluate
from ragas.metrics import (
faithfulness, # 答案是否基于检索内容(防幻觉)
answer_relevancy, # 答案是否回答了问题
context_precision, # 检索结果是否精准
context_recall # 是否检索到了关键信息
)
# 准备评估数据集
eval_dataset = {
"question": [
"RAG 和微调有什么区别?",
"如何优化 RAG 的检索质量?"
],
"answer": [
rag_chain.invoke("RAG 和微调有什么区别?"),
rag_chain.invoke("如何优化 RAG 的检索质量?")
],
"contexts": [
[retriever.invoke("RAG 和微调有什么区别?")[0].page_content],
[retriever.invoke("如何优化 RAG 的检索质量?")[0].page_content]
],
"ground_truth": [
"RAG 通过检索外部知识增强生成,微调通过训练改变模型行为...",
"可以通过混合检索、Reranker、查询改写等方式优化..."
]
}
result = evaluate(dataset=eval_dataset, metrics=[
faithfulness, answer_relevancy, context_precision, context_recall
])
print(result)
# {'faithfulness': 0.92, 'answer_relevancy': 0.88, ...}9. 生产级 RAG 架构
┌─────────────────────────────────────────────────┐
│ 生产级 RAG 系统 │
├─────────────────────────────────────────────────┤
│ │
│ 用户查询 │
│ ↓ │
│ ┌─────────────┐ │
│ │ 查询理解 │ ← 意图分类 + 查询改写 │
│ └──────┬──────┘ │
│ ↓ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 向量检索 │ + │ 关键词检索 │ ← 混合检索 │
│ │ (pgvector) │ │ (BM25) │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ └────────┬────────┘ │
│ ↓ │
│ ┌─────────────────────────┐ │
│ │ Reranker 重排序 │ ← BGE-Reranker │
│ │ Top 20 → Top 5 │ │
│ └───────────┬─────────────┘ │
│ ↓ │
│ ┌─────────────────────────┐ │
│ │ LLM 生成 + 引用标注 │ │
│ └───────────┬─────────────┘ │
│ ↓ │
│ ┌─────────────────────────┐ │
│ │ 输出后处理 │ ← 幻觉检测 + 格式化 │
│ └─────────────────────────┘ │
│ │
├─────────────────────────────────────────────────┤
│ 离线管线:文档解析 → 清洗 → 分块 → Embedding → 入库 │
│ 评估系统:RAGAS 自动评测 → 质量仪表盘 │
└─────────────────────────────────────────────────┘