LangChain RAG 技术深度实战:从原理到生产级优化全链路

5 阅读1分钟

前言

LangChain 在 RAG 领域的地位,就像 React 在前端开发中的地位——争议很多,但绕不开。本文不讨论"要不要用 LangChain",而是聚焦在用 LangChain 实现生产级 RAG 的关键技术点,包括很多官方文档里没说清楚的坑。


一、RAG 系统的质量三角

在动手写代码之前,先理解 RAG 的质量评估框架——RAGAS 三角:

         答案相关性(Answer Relevance)
              ▲
             / \
            /   \
           /     \
上下文相关性 ──── 答案忠实度
(Context    (Faithfulness)
 Relevance)

三个维度缺一不可:

  • 上下文相关性:检索到的片段和问题有多相关?(检索质量)
  • 答案忠实度:生成的答案是否基于检索内容,没有幻觉?(生成质量)
  • 答案相关性:最终回答是否真的回答了用户的问题?(整体质量)

优化顺序建议:先优化上下文相关性,再优化答案忠实度,最后调整答案相关性。


二、文档解析:被忽视的关键环节

大多数 RAG 教程直接从"加载文档"开始,但文档解析质量直接决定 RAG 天花板。

PDF 解析的坑

# 低质量方案(很多教程在用)
from langchain.document_loaders import PyPDFLoader
loader = PyPDFLoader("document.pdf")
docs = loader.load()
# 问题:表格变乱码,多栏排版错位,图表丢失
# 高质量方案:使用 marker 或 docling
# 安装:pip install marker-pdf
from marker.convert import convert_single_pdf
from marker.models import load_all_models

models = load_all_models()
full_text, images, metadata = convert_single_pdf(
    "document.pdf", 
    models,
    batch_multiplier=2
)
# 效果:正确识别表格、多栏排版、公式

语义切片 vs 固定长度切片

固定长度切片(如每 512 tokens 切一刀)会把语义相关的内容切断。

语义切片实现:

from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings

# 使用嵌入模型计算语义边界
text_splitter = SemanticChunker(
    OpenAIEmbeddings(),
    breakpoint_threshold_type="percentile",  # 按相似度百分位切
    breakpoint_threshold_amount=95  # 相似度低于95%分位时切割
)

docs = text_splitter.create_documents([text])

效果:语义切片的 RAG 准确率比固定切片高约 15-20%。


三、混合检索:BM25 + 向量的最优组合

纯向量检索的问题:对精确匹配不友好。 "公司的注册资本是多少?"——用户想要精确数字,向量检索可能返回相关但不精确的内容。

纯 BM25 关键词检索的问题:无法理解语义相近但措辞不同的查询。

解法:混合检索(Hybrid Search)

from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import Qdrant

# 向量检索器
vector_store = Qdrant.from_documents(docs, embeddings)
vector_retriever = vector_store.as_retriever(search_kwargs={"k": 10})

# BM25 检索器
bm25_retriever = BM25Retriever.from_documents(docs)
bm25_retriever.k = 10

# 混合检索:权重 4:6(向量:BM25)
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.4, 0.6]
)

# 使用
docs = ensemble_retriever.invoke("公司注册资本是多少?")

权重调优经验

  • 以精确信息为主(法律、财务):BM25 权重 0.6
  • 以语义理解为主(技术问答):向量权重 0.7
  • 均衡场景:各 0.5

四、查询优化:让 AI 理解用户真实意图

用户的问题往往不是最优的检索查询。

查询重写(Query Rewriting)

from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

rewrite_prompt = ChatPromptTemplate.from_template("""
你是一个查询优化专家。将用户的问题重写为更适合检索的查询。

原始问题:{question}

要求:
1. 扩展关键词(包含同义词、相关术语)
2. 消歧义(明确指代对象)
3. 拆分多意图问题为单一问题

输出格式:重写后的查询(只输出查询,不要解释)
""")

llm = ChatOpenAI(model="gpt-5-mini")
rewriter = rewrite_prompt | llm

rewritten = rewriter.invoke({"question": "这个产品怎么用?"})
# 输出:"{产品名称} 的使用方法、操作步骤和注意事项"

HyDE(假设文档生成)

不用原始问题检索,而是先让 LLM 生成一个"假设答案文档",再用这个文档检索:

from langchain.chains import HypotheticalDocumentEmbedder

hyde_chain = HypotheticalDocumentEmbedder.from_llm(
    llm=ChatOpenAI(model="gpt-5-mini"),
    embeddings=OpenAIEmbeddings(),
    chain_type="stuff"
)

# 生成假设文档并检索
similar_docs = hyde_chain.retrieve("解释 transformer 的注意力机制")

五、Reranker:提升精准度的最后一道关

初始检索(Top-20)→ Reranker 重排序 → 取 Top-5 给 LLM

from langchain.retrievers import ContextualCompressionRetriever
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
from langchain.retrievers.document_compressors import CrossEncoderReranker

# 加载本地 Reranker 模型(BGE-Reranker-v2-m3 是 2026 年中文最佳)
model = HuggingFaceCrossEncoder(
    model_name="BAAI/bge-reranker-v2-m3"
)

# 重排 Top-20 取 Top-5
compressor = CrossEncoderReranker(model=model, top_n=5)

compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=ensemble_retriever
)

docs = compression_retriever.invoke("你的问题")

性能数据:加 Reranker 后,NDCG@5 平均提升 18%,延迟增加约 150ms。


六、生成优化:让 LLM 只说检索到的内容

from langchain.prompts import ChatPromptTemplate

qa_prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个严谨的问答助手。
    
回答规则:
1. 只使用以下检索到的内容回答问题
2. 如果检索内容中没有相关信息,明确说"根据现有资料,无法回答此问题"
3. 在答案末尾标注信息来源(参考文档标题)
4. 不要添加检索内容之外的任何信息

检索内容:
{context}
"""),
    ("human", "{question}")
])

七、完整生产级 RAG 流水线

from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

def format_docs(docs):
    return "\n\n".join([
        f"[来源: {doc.metadata.get('source', '未知')}]\n{doc.page_content}"
        for doc in docs
    ])

# 完整 RAG 链
rag_chain = (
    {
        "context": rewrite_prompt | llm | (lambda x: x.content) | compression_retriever | format_docs,
        "question": RunnablePassthrough()
    }
    | qa_prompt
    | llm
    | StrOutputParser()
)

# 调用
answer = rag_chain.invoke("你的问题")

八、评估与监控

from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_recall,
    context_precision
)

# 准备评估数据集
eval_dataset = Dataset.from_dict({
    "question": questions,
    "answer": answers,
    "contexts": retrieved_contexts,
    "ground_truth": ground_truths
})

# 运行评估
results = evaluate(
    eval_dataset,
    metrics=[faithfulness, answer_relevancy, context_recall, context_precision]
)

print(results)
# 输出:
# faithfulness: 0.87
# answer_relevancy: 0.91
# context_recall: 0.83
# context_precision: 0.79

总结

生产级 RAG 的质量提升路径:

  1. 优先修文档解析(换 marker/docling 代替 PyPDF)→ 召回率 +20%
  2. 换语义切片(SemanticChunker)→ 相关性 +15%
  3. 加混合检索(BM25 + 向量)→ 综合精准度 +15%
  4. 加 Reranker(BGE-Reranker)→ Top-5 精准度 +18%
  5. 加查询重写(HyDE / Query Rewriting)→ 复杂问题 +10%

按优先级依次实施,每一步都有明确的效果提升,别一口气全上。