RAG完整指南2025:检索增强生成从入门到生产实战

🔍 AI开发教程 📅 2025年5月4日 ⏱️ 阅读约18分钟 👁️ 61.8k次浏览
📌 本文覆盖内容:

🔍 什么是RAG,为什么需要它?

RAG(Retrieval-Augmented Generation,检索增强生成)是一种将外部知识库与大语言模型结合的技术架构。它解决了LLM的两大核心痛点:

LLM原生问题RAG如何解决
知识截止日期(无最新信息)实时从知识库检索最新文档
幻觉(编造不存在的事实)答案有文档来源,可溯源验证
无法处理私有数据将企业内部文档入库后即可问答
上下文窗口有限只注入相关片段,不需要全文
模型更新成本高只更新知识库,无需重训模型

RAG vs Fine-tuning:怎么选?

场景推荐方案原因
企业知识库问答RAG数据频繁更新,无需重训
特定领域写作风格Fine-tuning风格/格式偏好,非知识问题
实时信息检索RAGFine-tuning无法更新知识
专业术语理解两者结合术语理解用FT,详情用RAG
客服问答系统RAGFAQ/政策文档随时更新

⚙️ RAG完整流水线(5个核心阶段)

📄
文档加载
PDF/Word/网页
✂️
文档切片
Chunking策略
🔢
向量嵌入
Embedding模型
🗄️
向量存储
向量数据库
🔍
相似检索
Top-K召回
🤖
LLM生成
上下文注入

💻 LangChain完整RAG实现(PDF问答系统)

pip install langchain langchain-community langchain-anthropic \
            langchain-chroma pypdf sentence-transformers
from langchain_community.document_loaders import PyPDFLoader, DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_anthropic import ChatAnthropic
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
import os

# ════════════════════════════════════════════════
# 阶段1:文档加载
# ════════════════════════════════════════════════
# 加载单个PDF
loader = PyPDFLoader("company_handbook.pdf")

# 或加载整个文件夹
# loader = DirectoryLoader("./docs/", glob="**/*.pdf", loader_cls=PyPDFLoader)

documents = loader.load()
print(f"加载了 {len(documents)} 页文档")

# ════════════════════════════════════════════════
# 阶段2:文档切片(关键步骤)
# ════════════════════════════════════════════════
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,      # 每块约1000个字符
    chunk_overlap=200,    # 块间重叠200字符(保持上下文连贯)
    separators=["\n\n", "\n", "。", "!", "?", " ", ""],  # 中文优先级
    length_function=len,
)
chunks = text_splitter.split_documents(documents)
print(f"切分为 {len(chunks)} 个文本块")

# ════════════════════════════════════════════════
# 阶段3+4:向量嵌入 + 存储(Chroma本地)
# ════════════════════════════════════════════════
# 使用开源嵌入模型(免费,支持中文)
embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-m3",          # 多语言,中英文都很好
    # model_name="BAAI/bge-large-zh",  # 中文专优版本
    model_kwargs={"device": "cpu"},
    encode_kwargs={"normalize_embeddings": True}
)

# 创建/加载向量数据库
PERSIST_DIR = "./chroma_db"
if os.path.exists(PERSIST_DIR):
    # 复用已有数据库
    vectorstore = Chroma(
        persist_directory=PERSIST_DIR,
        embedding_function=embeddings
    )
else:
    # 首次创建
    vectorstore = Chroma.from_documents(
        documents=chunks,
        embedding=embeddings,
        persist_directory=PERSIST_DIR
    )
    print("向量数据库创建完成")

retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 5}  # 检索最相关的5块
)

# ════════════════════════════════════════════════
# 阶段5+6:检索 + 生成(RAG Chain)
# ════════════════════════════════════════════════
llm = ChatAnthropic(model="claude-3-7-sonnet-20250219", temperature=0)

# RAG提示词模板
rag_prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个专业的文档问答助手。请严格基于以下检索到的文档内容回答问题。
如果文档中没有相关信息,请明确说明"根据提供的文档,我无法找到相关信息",不要编造答案。

检索到的相关文档:
{context}"""),
    ("human", "{question}")
])

def format_docs(docs):
    """格式化检索到的文档"""
    return "\n\n".join([
        f"[文档{i+1}] 来源:{doc.metadata.get('source', '未知')}\n{doc.page_content}"
        for i, doc in enumerate(docs)
    ])

# LCEL构建RAG链
rag_chain = (
    {
        "context": retriever | format_docs,
        "question": RunnablePassthrough()
    }
    | rag_prompt
    | llm
    | StrOutputParser()
)

# ── 使用 ─────────────────────────────────────────
questions = [
    "公司的年假政策是什么?",
    "如何申请报销?需要哪些材料?",
    "试用期是多久?"
]

for q in questions:
    print(f"\n问:{q}")
    answer = rag_chain.invoke(q)
    print(f"答:{answer}")
    print("-" * 60)

🦙 LlamaIndex实现(更简洁的方案)

pip install llama-index llama-index-llms-anthropic llama-index-embeddings-huggingface
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.llms.anthropic import Anthropic
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

# 配置全局设置
Settings.llm = Anthropic(model="claude-3-7-sonnet-20250219")
Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-m3")
Settings.chunk_size = 1024
Settings.chunk_overlap = 128

# 加载文档(自动处理PDF/TXT/MD等格式)
documents = SimpleDirectoryReader("./docs").load_data()

# 一行创建索引
index = VectorStoreIndex.from_documents(documents)

# 保存索引(避免重复计算)
index.storage_context.persist(persist_dir="./llamaindex_storage")

# 创建查询引擎
query_engine = index.as_query_engine(
    similarity_top_k=5,
    streaming=True  # 流式输出
)

# 查询
response = query_engine.query("公司的核心价值观是什么?")
response.print_response_stream()  # 流式打印

# 带来源引用的查询
print("\n来源文档:")
for node in response.source_nodes:
    print(f"  - 相似度:{node.score:.3f} | {node.metadata['file_name']}")

🗄️ 向量数据库选型指南

🟢 Chroma(推荐入门)
开源 · 本地嵌入 · Python原生
免费开源 本地运行 无需服务器

最适合快速原型和中小规模项目。数据存本地,零配置,直接pip install就能用。缺点是不支持分布式,百万级向量后性能下降。

🔵 Pinecone(推荐生产)
全托管云服务 · 生产级性能 · 企业首选
全托管 亿级向量 免费层:100万向量

无需运维,高可用,毫秒级查询。有免费层(100万向量),付费从$70/月起。最适合不想管基础设施的团队。

⚡ Qdrant(推荐高性能自托管)
Rust实现 · 高性能 · 开源可自部署
开源免费 Rust高性能 支持过滤搜索

比Chroma快5-10倍,支持有效载荷过滤(按metadata筛选后再搜索),Docker一键部署,推荐需要自托管且有高性能需求的团队。

📦 FAISS(本地批量处理)
Meta开源 · 纯内存 · 研究级性能
免费开源 GPU加速 无持久化(需自己保存)

Meta出品,业界标准。纯内存操作,GPU加速时亿级向量秒级查询。不支持服务化,适合离线批处理场景。

数据库规模部署价格推荐场景
Chroma100万向量内本地免费原型/学习/小项目
FAISS亿级本地免费批处理/研究
Qdrant1000万+自托管/云开源免费高性能自托管
Pinecone亿级全托管免费层+$70/月企业生产
Weaviate亿级自托管/云开源+SaaS多模态+图谱

🔢 嵌入模型(Embedding Model)选型

模型维度中文价格推荐指数
BAAI/bge-m31024极好免费(本地)9.5/10
text-embedding-3-large3072$0.13/1M tokens9.0/10
text-embedding-3-small1536$0.02/1M tokens8.5/10
BAAI/bge-large-zh1024中文最优免费(本地)8.8/10
Voyage-31024$0.06/1M tokens8.7/10
💡 推荐组合:
免费方案:BAAI/bge-m3(本地)+ Chroma → 零成本、支持中英文
生产方案:text-embedding-3-large(OpenAI)+ Pinecone → 高质量+托管
中文专项:BAAI/bge-large-zh + Qdrant → 中文检索最优

🚀 高级RAG优化技术

🔄

HyDE(假设文档嵌入)

先让LLM生成一个"假设性答案",用这个答案去检索而不是用原始问题。显著提升检索召回率。

🏆

Reranker(重排序)

召回20个文档后,用Cross-Encoder重新打分,选取真正最相关的5个。准确率提升15-25%。

🔀

混合搜索(BM25+向量)

关键词搜索(BM25)+ 语义搜索(向量)结合,用RRF算法融合结果,全面覆盖精确匹配和语义理解。

查询扩展(Query Expansion)

用LLM将一个问题扩展为3-5个同义问题,分别检索后合并去重,大幅提高召回覆盖率。

📊

父文档检索器

小块存储用于精准检索,大块上下文用于生成。解决chunk过小导致上下文不足的问题。

🗺️

知识图谱RAG

将文档关系存为图谱,检索时不仅返回相似文档,还返回关联实体。适合复杂关系推理。

HyDE实现示例

from langchain_core.prompts import ChatPromptTemplate
from langchain_anthropic import ChatAnthropic
from langchain_core.output_parsers import StrOutputParser

llm = ChatAnthropic(model="claude-3-7-sonnet-20250219", temperature=0.2)

# HyDE:先生成假设答案再检索
hyde_prompt = ChatPromptTemplate.from_template(
    """请为以下问题生成一个详细的假设性答案(即使你不确定,也要给出合理的答案)。
这个答案将用于文档检索,而不是直接给用户看。

问题:{question}

假设性答案:"""
)

hyde_chain = hyde_prompt | llm | StrOutputParser()

def hyde_retriever(question: str, vectorstore, k: int = 5):
    """使用HyDE技术进行检索"""
    # 生成假设答案
    hypothetical_answer = hyde_chain.invoke({"question": question})
    # 用假设答案去检索(而不是原始问题)
    docs = vectorstore.similarity_search(hypothetical_answer, k=k)
    return docs

# 对比普通检索
normal_docs = vectorstore.similarity_search("公司有什么福利?", k=5)
hyde_docs = hyde_retriever("公司有什么福利?", vectorstore, k=5)
# HyDE通常能检索到更相关的文档

Reranker实现

from sentence_transformers import CrossEncoder

# 加载重排序模型(约500MB)
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")  # 支持中英文

def rerank_documents(query: str, documents, top_k: int = 3):
    """对检索结果重新排序"""
    # 构建查询-文档对
    pairs = [(query, doc.page_content) for doc in documents]
    # 批量打分
    scores = reranker.predict(pairs)
    # 按分数排序
    ranked = sorted(zip(scores, documents), reverse=True)
    # 返回top_k个
    return [doc for _, doc in ranked[:top_k]]

# 使用
raw_docs = retriever.invoke("公司的培训机会有哪些?")  # 召回10个
reranked = rerank_documents("公司的培训机会有哪些?", raw_docs, top_k=3)
# 重排后只取最相关的3个,质量更高

📏 RAG系统评估指标

指标含义目标值评估工具
Faithfulness答案是否忠实于检索文档>0.8RAGAS
Answer Relevancy答案是否回答了问题>0.75RAGAS
Context Precision检索文档的精准率>0.7RAGAS
Context Recall相关文档的召回率>0.7RAGAS
Latency端到端响应时间<3秒LangSmith
Cost per Query每次查询的API费用依预算自定义
pip install ragas
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision
from datasets import Dataset

# 准备评估数据集
eval_data = {
    "question": ["公司年假政策?", "报销需要什么材料?"],
    "answer": ["...", "..."],           # RAG系统输出
    "contexts": [["检索到的文档1..."], ["检索到的文档2..."]],  # 检索的上下文
    "ground_truth": ["正确答案1", "正确答案2"]   # 人工标注的正确答案
}
dataset = Dataset.from_dict(eval_data)

# 一键评估
result = evaluate(
    dataset,
    metrics=[faithfulness, answer_relevancy, context_precision]
)
print(result)
# 输出类似:{'faithfulness': 0.87, 'answer_relevancy': 0.82, 'context_precision': 0.75}

💰 RAG成本优化策略

📊 典型RAG成本构成(每1000次查询):
⚠️ 常见错误与最佳实践:

🔗 延伸阅读