RAG 应用 #

RAG(Retrieval-Augmented Generation,检索增强生成)是让 LLM 访问外部知识的关键技术。通过 RAG,你可以构建基于私有数据的问答系统、智能客服、知识库等应用。

RAG 概述 #

text
┌─────────────────────────────────────────────────────────────┐
│                    什么是 RAG?                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  问题:LLM 的知识有限                                        │
│  - 训练数据有时效性                                          │
│  - 无法访问私有数据                                          │
│  - 可能产生幻觉                                              │
│                                                             │
│  RAG 解决方案:                                              │
│  1. 将文档向量化存储                                         │
│  2. 根据问题检索相关内容                                     │
│  3. 将检索结果作为上下文                                     │
│  4. LLM 基于上下文生成答案                                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

RAG 流程 #

text
┌─────────────────────────────────────────────────────────────┐
│                    RAG 工作流程                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  离线阶段(索引):                                          │
│                                                             │
│  文档 ──> 文本分割 ──> 向量化 ──> 存储到向量数据库           │
│                                                             │
│  在线阶段(检索):                                          │
│                                                             │
│  问题 ──> 向量化 ──> 相似度检索 ──> 获取相关文档            │
│                                                             │
│  生成阶段:                                                  │
│                                                             │
│  问题 + 相关文档 ──> 构建提示 ──> LLM 生成答案              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

文档加载 #

文档加载器 #

python
from langchain_community.document_loaders import (
    TextLoader,
    PyPDFLoader,
    CSVLoader,
    JSONLoader,
    UnstructuredMarkdownLoader,
    WebBaseLoader,
    DirectoryLoader,
)

# 加载文本文件
loader = TextLoader("document.txt")
documents = loader.load()

# 加载 PDF
loader = PyPDFLoader("document.pdf")
pages = loader.load()

# 加载 CSV
loader = CSVLoader("data.csv")
rows = loader.load()

# 加载 JSON
loader = JSONLoader(
    file_path="data.json",
    jq_schema=".[]",
    text_content=False
)
data = loader.load()

# 加载 Markdown
loader = UnstructuredMarkdownLoader("readme.md")
docs = loader.load()

# 加载网页
loader = WebBaseLoader("https://example.com/article")
web_docs = loader.load()

# 加载目录下所有文件
loader = DirectoryLoader(
    "./documents",
    glob="**/*.txt",
    loader_cls=TextLoader
)
all_docs = loader.load()

常用文档加载器 #

python
# Word 文档
from langchain_community.document_loaders import Docx2txtLoader
loader = Docx2txtLoader("document.docx")

# Excel
from langchain_community.document_loaders import UnstructuredExcelLoader
loader = UnstructuredExcelLoader("data.xlsx")

# PowerPoint
from langchain_community.document_loaders import UnstructuredPowerPointLoader
loader = UnstructuredPowerPointLoader("presentation.pptx")

# HTML
from langchain_community.document_loaders import BSHTMLLoader
loader = BSHTMLLoader("page.html")

# 代码文件
from langchain_community.document_loaders import PythonLoader
loader = PythonLoader("script.py")

# Notion
from langchain_community.document_loaders import NotionDBLoader
loader = NotionDBLoader(
    integration_token="token",
    database_id="db-id"
)

文本分割 #

分割器类型 #

python
from langchain_text_splitters import (
    CharacterTextSplitter,
    RecursiveCharacterTextSplitter,
    TokenTextSplitter,
    MarkdownHeaderTextSplitter,
    CodeTextSplitter,
)

# 递归字符分割器(推荐)
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,      # 每块最大字符数
    chunk_overlap=200,    # 块之间的重叠字符数
    length_function=len,  # 长度计算函数
    separators=["\n\n", "\n", " ", ""]  # 分隔符优先级
)

chunks = text_splitter.split_text(long_text)

# 字符分割器
char_splitter = CharacterTextSplitter(
    separator="\n\n",
    chunk_size=1000,
    chunk_overlap=200
)

# Token 分割器
token_splitter = TokenTextSplitter(
    chunk_size=100,  # token 数
    chunk_overlap=20
)

# Markdown 分割器
md_splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=[
        ("#", "Header 1"),
        ("##", "Header 2"),
        ("###", "Header 3")
    ]
)

分割文档 #

python
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 创建分割器
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200
)

# 分割文档
documents = loader.load()
splits = text_splitter.split_documents(documents)

# 查看分割结果
for i, split in enumerate(splits[:3]):
    print(f"Chunk {i}:")
    print(f"Content: {split.page_content[:100]}...")
    print(f"Metadata: {split.metadata}")
    print("-" * 50)
text
┌─────────────────────────────────────────────────────────────┐
│                    文本分割策略                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  原始文档:                                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 这是第一段内容...                                    │   │
│  │                                                     │   │
│  │ 这是第二段内容...                                    │   │
│  │                                                     │   │
│  │ 这是第三段内容...                                    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  分割后(chunk_size=100, overlap=20):                      │
│                                                             │
│  Chunk 1: [第一段 + 第二段开头]                             │
│  ┌──────────────────────────────────────┐                  │
│  │ 这是第一段内容...这是第二段开头...    │                  │
│  └──────────────────────────────────────┘                  │
│                    ↓ overlap                                │
│  Chunk 2: [第二段开头 + 第二段 + 第三段开头]                │
│  ┌──────────────────────────────────────┐                  │
│  │ 这是第二段开头...这是第二段...        │                  │
│  └──────────────────────────────────────┘                  │
│                                                             │
│  重叠确保上下文连续性                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

向量存储 #

创建向量存储 #

python
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings

# 创建嵌入模型
embeddings = OpenAIEmbeddings()

# 从文档创建向量存储
vectorstore = Chroma.from_documents(
    documents=splits,
    embedding=embeddings,
    persist_directory="./chroma_db"  # 持久化目录
)

# 从文本列表创建
texts = ["文本1", "文本2", "文本3"]
vectorstore = Chroma.from_texts(
    texts=texts,
    embedding=embeddings
)

常用向量数据库 #

python
# FAISS(本地,快速)
from langchain_community.vectorstores import FAISS
vectorstore = FAISS.from_documents(documents, embeddings)

# Pinecone(云端)
from langchain_community.vectorstores import Pinecone
import pinecone
pinecone.init(api_key="key", environment="env")
vectorstore = Pinecone.from_documents(
    documents, embeddings, index_name="my-index"
)

# Weaviate
from langchain_community.vectorstores import Weaviate
vectorstore = Weaviate.from_documents(
    documents, embeddings, weaviate_url="http://localhost:8080"
)

# Milvus
from langchain_community.vectorstores import Milvus
vectorstore = Milvus.from_documents(
    documents, embeddings, connection_args={"host": "localhost", "port": "19530"}
)

# Qdrant
from langchain_community.vectorstores import Qdrant
vectorstore = Qdrant.from_documents(
    documents, embeddings, url="http://localhost:6333"
)

检索器 #

基础检索 #

python
# 相似度检索
results = vectorstore.similarity_search(
    query="什么是机器学习?",
    k=4  # 返回最相似的 4 个文档
)

# 带分数的检索
results_with_scores = vectorstore.similarity_search_with_score(
    query="什么是机器学习?",
    k=4
)

for doc, score in results_with_scores:
    print(f"Score: {score}")
    print(f"Content: {doc.page_content[:100]}...")
    print("-" * 50)

创建检索器 #

python
# 从向量存储创建检索器
retriever = vectorstore.as_retriever(
    search_type="similarity",  # 检索类型
    search_kwargs={"k": 4}     # 检索参数
)

# 也可以使用其他搜索类型
retriever = vectorstore.as_retriever(
    search_type="mmr",  # 最大边际相关性
    search_kwargs={"k": 4, "fetch_k": 20}
)

retriever = vectorstore.as_retriever(
    search_type="similarity_score_threshold",
    search_kwargs={"score_threshold": 0.8, "k": 4}
)

# 使用检索器
docs = retriever.invoke("什么是机器学习?")

高级检索器 #

python
from langchain.retrievers import (
    ContextualCompressionRetriever,
    EnsembleRetriever,
    MultiQueryRetriever,
)

# 多查询检索器
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o-mini")

multi_query_retriever = MultiQueryRetriever.from_llm(
    retriever=retriever,
    llm=model
)

# 集成检索器(混合检索)
from langchain_community.retrievers import BM25Retriever

bm25_retriever = BM25Retriever.from_documents(documents)
ensemble_retriever = EnsembleRetriever(
    retrievers=[retriever, bm25_retriever],
    weights=[0.5, 0.5]
)

# 上下文压缩检索器
from langchain.retrievers.document_compressors import LLMChainExtractor

compressor = LLMChainExtractor.from_llm(model)
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=retriever
)

构建 RAG 链 #

基础 RAG 链 #

python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

model = ChatOpenAI(model="gpt-4o-mini")

# 定义提示模板
prompt = ChatPromptTemplate.from_template("""根据以下上下文回答问题。如果上下文中没有相关信息,请说"我不知道"。

上下文:
{context}

问题:{question}

答案:""")

# 构建 RAG 链
def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

# 使用
answer = rag_chain.invoke("什么是机器学习?")
print(answer)

带来源的 RAG 链 #

python
from langchain_core.runnables import RunnableParallel

# 并行获取答案和来源
rag_chain_with_source = RunnableParallel(
    answer=(
        {"context": retriever | format_docs, "question": RunnablePassthrough()}
        | prompt
        | model
        | StrOutputParser()
    ),
    sources=retriever
)

result = rag_chain_with_source.invoke("什么是机器学习?")
print("答案:", result["answer"])
print("\n来源:")
for doc in result["sources"]:
    print(f"- {doc.metadata.get('source', '未知')}")

对话式 RAG #

python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage, AIMessage

# 带历史的提示
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个有帮助的助手。根据以下上下文回答问题。\n\n上下文:{context}"),
    MessagesPlaceholder(variable_name="chat_history"),
    ("human", "{question}")
])

# 简单的历史管理
chat_history = []

def chat(question: str) -> str:
    # 获取相关文档
    docs = retriever.invoke(question)
    context = format_docs(docs)
    
    # 调用链
    chain = prompt | model | StrOutputParser()
    answer = chain.invoke({
        "context": context,
        "chat_history": chat_history,
        "question": question
    })
    
    # 更新历史
    chat_history.append(HumanMessage(content=question))
    chat_history.append(AIMessage(content=answer))
    
    return answer

# 使用
print(chat("什么是机器学习?"))
print(chat("它有哪些应用?"))  # 会记住之前的对话

完整示例 #

文档问答系统 #

python
from langchain_community.document_loaders import PyPDFLoader, DirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# 1. 加载文档
loader = DirectoryLoader(
    "./documents",
    glob="**/*.pdf",
    loader_cls=PyPDFLoader
)
documents = loader.load()

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

# 3. 创建向量存储
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(
    documents=splits,
    embedding=embeddings,
    persist_directory="./chroma_db"
)

# 4. 创建检索器
retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 4}
)

# 5. 构建 RAG 链
model = ChatOpenAI(model="gpt-4o-mini")

prompt = ChatPromptTemplate.from_template("""你是一个专业的问答助手。请根据以下上下文回答问题。

上下文:
{context}

问题:{question}

请提供准确、详细的回答。如果上下文中没有相关信息,请明确说明。

答案:""")

def format_docs(docs):
    return "\n\n---\n\n".join(doc.page_content for doc in docs)

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

# 6. 使用
def ask(question: str) -> str:
    # 获取相关文档
    docs = retriever.invoke(question)
    
    # 生成答案
    answer = rag_chain.invoke(question)
    
    # 打印来源
    print(f"问题: {question}")
    print(f"\n答案: {answer}")
    print(f"\n参考来源:")
    for i, doc in enumerate(docs):
        source = doc.metadata.get('source', '未知')
        page = doc.metadata.get('page', '未知')
        print(f"  [{i+1}] {source} (第 {page} 页)")
    
    return answer

# 测试
ask("文档中提到了哪些重要内容?")

RAG 优化技巧 #

1. 改进检索质量 #

python
# 使用更好的分割策略
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,     # 较小的块
    chunk_overlap=100,  # 适当的重叠
    separators=["\n\n", "\n", "。", "!", "?", " ", ""]
)

# 使用混合检索
ensemble_retriever = EnsembleRetriever(
    retrievers=[
        vectorstore.as_retriever(search_kwargs={"k": 4}),
        BM25Retriever.from_documents(splits, k=4)
    ],
    weights=[0.6, 0.4]
)

# 使用重排序
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain.retrievers import ContextualCompressionRetriever

reranker = CrossEncoderReranker(model_name="cross-encoder/ms-marco-MiniLM-L-6-v2")
compression_retriever = ContextualCompressionRetriever(
    base_compressor=reranker,
    base_retriever=retriever
)

2. 改进提示 #

python
prompt = ChatPromptTemplate.from_template("""你是一个专业的问答助手。请根据以下上下文回答问题。

回答要求:
1. 只使用上下文中的信息
2. 如果上下文不足,明确说明
3. 引用具体的上下文来源
4. 回答要准确、完整

上下文:
{context}

问题:{question}

答案:""")

3. 添加元数据过滤 #

python
# 存储时添加元数据
documents = [
    Document(
        page_content="内容...",
        metadata={
            "source": "doc1.pdf",
            "category": "技术文档",
            "date": "2024-01-01"
        }
    )
]

# 检索时过滤
from langchain_community.vectorstores import Chroma

results = vectorstore.similarity_search(
    query="问题",
    k=4,
    filter={"category": "技术文档"}
)

下一步 #

最后更新:2026-03-30