想要让你的 AI 应用访问私有数据?本文详解如何用 LangChain 框架搭建检索增强生成(RAG)系统,让大模型基于你的文档回答问题。

什么是 RAG?为什么你需要它?

检索增强生成(Retrieval-Augmented Generation,简称 RAG)是一种将信息检索与文本生成相结合的技术架构。简单来说,RAG 让大语言模型在回答问题之前,先从你的私有文档库中检索相关信息,然后基于这些信息进行回答。

为什么 RAG 如此重要?

  1. 解决幻觉问题:大模型可能会”编造”信息,RAG 通过提供真实文档作为上下文,大幅减少幻觉
  2. 访问私有数据:无需微调模型,即可让 AI 理解你的内部文档、知识库、代码库等
  3. 成本效益高:相比微调,RAG implementation 成本更低,更新更灵活
  4. 可追溯性:每个回答都可以追溯到具体的文档来源,便于验证和审计

LangChain 框架简介

LangChain 是目前最流行的 LLM 应用开发框架之一,它提供了模块化、可组合的抽象层,让构建 RAG 应用变得简单。

核心组件:

  • Document Loaders:从各种数据源加载文档
  • Text Splitters:将长文档切分成适合模型处理的小块
  • Embeddings:将文本转换为向量表示
  • Vector Stores:存储和检索向量化的文档块
  • Retrievers:从向量存储中检索相关文档
  • Chains:组合多个组件形成完整的工作流

第一步:环境准备与安装

开始之前,确保你已安装 Python 3.9+ 和 pip。创建虚拟环境并安装必要依赖:

# 创建项目目录
mkdir langchain-rag-demo
cd langchain-rag-demo

# 创建虚拟环境
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

# 安装 LangChain 及相关依赖
pip install langchain langchain-community langchain-openai
pip install chromadb  # 本地向量数据库
pip install python-dotenv  # 环境变量管理

创建 .env 文件存储 API 密钥:

OPENAI_API_KEY=your_openai_api_key_here

第二步:加载与处理文档

RAG 的第一步是将你的文档加载到系统中。LangChain 支持数十种文档加载器,包括 PDF、Word、Markdown、网页等。

from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 加载文档(假设你有一个 docs/ 文件夹存放 Markdown 文件)
loader = DirectoryLoader(
    './docs',
    glob='**/*.md',
    loader_cls=TextLoader,
    loader_kwargs={'encoding': 'utf-8'}
)
documents = loader.load()

print(f"加载了 {len(documents)} 个文档")

# 文本分割:将长文档切分成小块
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,      # 每块最大字符数
    chunk_overlap=200,    # 块之间重叠字符数(保持上下文连贯)
    length_function=len,
    separators=['\n\n', '\n', '。', '!', '?', ' ', '']
)

texts = text_splitter.split_documents(documents)
print(f"分割成 {len(texts)} 个文本块")

分割策略建议:

  • 代码文档:使用更小的 chunk_size(500-800),因为代码结构紧凑
  • 技术文章:1000-1500 字符较为合适
  • 法律/合同文档:可能需要更大的 chunk(2000+)以保持条款完整性
  • 重叠设置:15-20% 的重叠率通常效果最佳

第三步:向量化与存储

将文本块转换为向量并存储到向量数据库中。这里我们使用 ChromaDB(本地、无需配置):

from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

# 初始化嵌入模型
embeddings = OpenAIEmbeddings(
    model='text-embedding-3-small',  # 性价比高
    # model='text-embedding-3-large'  # 精度更高,成本也更高
)

# 创建向量存储
vectorstore = Chroma.from_documents(
    documents=texts,
    embedding=embeddings,
    persist_directory='./chroma_db'  # 持久化存储路径
)

print(f"向量数据库已创建,包含 {vectorstore._collection.count()} 个向量")

向量模型选择指南:

模型维度成本适用场景
text-embedding-3-small1536$一般用途,性价比高
text-embedding-3-large3072$$高精度需求,复杂语义
text-embedding-ada-0021536$$旧模型,已不推荐

其他向量数据库选项:

  • ChromaDB:轻量级,适合开发和小型部署
  • FAISS:Facebook 开源,性能优秀,适合大规模
  • Pinecone:托管服务,开箱即用
  • Weaviate:支持混合搜索(向量 + 关键词)
  • Qdrant:Rust 编写,性能出色

第四步:构建检索器与问答链

现在创建检索器,并组合成完整的问答链:

from langchain_openai import ChatOpenAI
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate

# 初始化 LLM
llm = ChatOpenAI(
    model='gpt-4o-mini',  # 性价比高
    temperature=0,        # RAG 场景建议低温,减少幻觉
    # model='gpt-4o'      # 更高精度
)

# 创建检索器
retriever = vectorstore.as_retriever(
    search_type='similarity',
    search_kwargs={'k': 4}  # 每次检索返回 4 个最相关文档块
)

# 自定义系统提示词
system_prompt = '''你是一位专业的技术助手,基于提供的上下文回答用户问题。

请遵循以下规则:
1. 只根据提供的上下文回答问题
2. 如果上下文中没有答案,明确告知用户
3. 引用相关文档来源
4. 回答要简洁、准确、有条理

上下文:
{context}
'''

prompt = ChatPromptTemplate.from_messages([
    ('system', system_prompt),
    ('human', '{input}')
])

# 创建文档组合链
question_answer_chain = create_stuff_documents_chain(llm, prompt)

# 创建完整的 RAG 链
rag_chain = create_retrieval_chain(retriever, question_answer_chain)

第五步:测试与优化

现在测试你的 RAG 系统:

# 测试问答
question = '如何在 LangChain 中设置自定义提示词?'
result = rag_chain.invoke({'input': question})

print('问题:', question)
print('回答:', result['answer'])
print('来源文档:')
for doc in result['context']:
    print(f'  - {doc.metadata.get("source", "未知")}')

优化技巧:

1. 调整检索参数

# 增加检索数量
retriever = vectorstore.as_retriever(search_kwargs={'k': 8})

# 使用相似度阈值过滤
retriever = vectorstore.as_retriever(
    search_type='similarity_score_threshold',
    search_kwargs={'score_threshold': 0.7}
)

# 混合搜索(向量 + 关键词)
retriever = vectorstore.as_retriever(search_type='mmr', search_kwargs={'k': 4, 'fetch_k': 20})

2. 优化提示词

system_prompt = '''你是一位专业的技术文档助手。

回答规则:
- 严格基于提供的上下文
- 如果信息不足,说"根据提供的文档,无法找到相关信息"
- 使用 Markdown 格式组织回答
- 包含代码示例时使用代码块
- 列出引用来源

相关文档:
{context}

用户问题:{input}

回答:'''

3. 添加对话历史(多轮对话)

from langchain.chains import create_history_aware_retriever
from langchain_core.prompts import MessagesPlaceholder

# 支持多轮对话的提示词
contextualize_q_system_prompt = '''给定聊天历史和最新用户问题,
如果最新问题指代了历史中的内容,将其改写为独立的问题。
否则,原样返回最新问题。

聊天历史:{chat_history}
最新问题:{input}

独立问题:'''

contextualize_q_prompt = ChatPromptTemplate.from_messages([
    ('system', contextualize_q_system_prompt),
    MessagesPlaceholder('chat_history'),
    ('human', '{input}')
])

# 创建历史感知检索器
history_aware_retriever = create_history_aware_retriever(
    llm,
    retriever,
    contextualize_q_prompt
)

完整示例代码

将以上步骤整合为一个完整的可运行脚本:

#!/usr/bin/env python3
"""
LangChain RAG 应用完整示例
"""

import os
from dotenv import load_dotenv
from langchain_community.document_loaders import DirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate

# 加载环境变量
load_dotenv()

def setup_rag(docs_path='./docs'):
    """初始化 RAG 系统"""
    # 加载文档
    loader = DirectoryLoader(docs_path, glob='**/*.md')
    documents = loader.load()

    # 分割文本
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=200
    )
    texts = text_splitter.split_documents(documents)

    # 创建向量存储
    embeddings = OpenAIEmbeddings(model='text-embedding-3-small')
    vectorstore = Chroma.from_documents(
        documents=texts,
        embedding=embeddings,
        persist_directory='./chroma_db'
    )

    # 创建检索器
    retriever = vectorstore.as_retriever(search_kwargs={'k': 4})

    # 初始化 LLM
    llm = ChatOpenAI(model='gpt-4o-mini', temperature=0)

    # 创建提示词
    system_prompt = '''基于以下上下文回答问题。如果信息不足,请说明。

上下文:
{context}
'''
    prompt = ChatPromptTemplate.from_messages([
        ('system', system_prompt),
        ('human', '{input}')
    ])

    # 创建 RAG 链
    qa_chain = create_stuff_documents_chain(llm, prompt)
    rag_chain = create_retrieval_chain(retriever, qa_chain)

    return rag_chain

def ask_question(rag_chain, question):
    """提问并获取回答"""
    result = rag_chain.invoke({'input': question})
    return result['answer'], result['context']

if __name__ == '__main__':
    # 初始化
    print('正在初始化 RAG 系统...')
    rag = setup_rag()
    print('初始化完成!\n')

    # 交互式问答
    while True:
        question = input('请输入问题(输入 q 退出): ')
        if question.lower() == 'q':
            break

        answer, sources = ask_question(rag, question)
        print(f'\n回答:{answer}\n')
        print('参考来源:')
        for src in sources:
            print(f'  - {src.metadata.get("source", "未知")}')
        print()

常见问题解答

Q1: RAG 回答不准确怎么办?

可能原因与解决方案:

  1. 检索到的文档不相关 → 调整 chunk_size 或增加 k 值
  2. 提示词不够明确 → 优化 system prompt,添加更具体的指令
  3. 嵌入模型不合适 → 尝试不同的 embedding 模型
  4. 文档质量差 → 预处理文档,去除噪声

Q2: 如何处理大型文档库(10 万 + 文档)?

  1. 使用 FAISS 或 Qdrant 等高性能向量数据库
  2. 实现分层检索(先分类,再检索)
  3. 添加元数据过滤(按日期、类别等筛选)
  4. 考虑分布式部署

Q3: 如何降低 API 成本?

  1. 使用更小的模型(gpt-4o-mini 而非 gpt-4o)
  2. 减少检索的 k 值
  3. 缓存常见问题的回答
  4. 使用本地嵌入模型(如 sentence-transformers)

Q4: 如何评估 RAG 系统质量?

关键指标:

  • 检索准确率:检索到的文档是否相关
  • 回答准确性:回答是否正确且基于上下文
  • 响应时间:端到端延迟
  • 用户满意度:收集用户反馈

总结

通过本文的 5 个步骤,你已经掌握了使用 LangChain 构建 RAG 应用的核心技能:

  1. ✅ 环境准备与依赖安装
  2. ✅ 文档加载与文本分割
  3. ✅ 向量化与向量存储
  4. ✅ 检索器与问答链构建
  5. ✅ 测试与优化技巧

RAG 是构建企业级 AI 应用的关键技术,掌握它将让你能够创建基于私有数据的智能助手、知识库问答系统、代码助手等强大应用。

下一步学习建议:

  • 尝试不同的向量数据库(FAISS、Pinecone)
  • 探索多模态 RAG(图像 + 文本)
  • 学习 Agent 框架,让 AI 自主调用工具
  • 研究高级检索策略(HyDE、多查询检索)

参考资源:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注