《AI Agent智能应用从0到1定制开发》学习笔记:使用RAG技术增强大模型能力,实现与各种文档的对话
思维导图
📚 引言
大型语言模型(如ChatGPT)虽然功能强大,但它们存在一些明显的局限性。这些模型的知识库更新较慢,无法实时学习最新内容,而且对私有数据或特定领域的专业知识了解有限。例如,ChatGPT的知识截止到特定时间点,无法感知用户本地电脑或内网中的数据。这就是为什么当我们询问非常具体或专业的内容时,它的回答可能显得泛泛而谈。
那么,如何让大模型变得更"聪明",能够获取最新知识并回答更专业的问题呢?这就是本文要介绍的RAG(检索增强生成)技术。
🔍 RAG技术概述
什么是RAG?
RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合检索和生成两种技术的方法,旨在帮助计算机更好地理解和回答问题。简单来说,它让AI模型能够在回答问题前先"查阅资料",从而提供更准确、更专业的回答。
RAG的基本流程
RAG技术的典型流程包括:
- 加载数据(Loader):从各种来源加载文档数据
- 处理文档(Transform):对文档进行切割、整理等处理
- 向量化(Embedding):将文本转换为向量表示
- 存储(Store):将向量数据存储在向量数据库中
- 检索(Retrieve):根据问题检索相关文档片段
- 生成回答(Generate):基于检索结果生成答案
🔧 LangChain中的RAG实现
Loader:让大模型具备实时学习的能力
LangChain包装了各种Loader,使大模型能够加载各种格式的文档:
- CSV Loader:加载表格数据
- Directory Loader:加载整个目录的文件
- HTML Loader:加载网页内容
- JSON Loader:加载JSON格式数据
- Markdown Loader:加载Markdown文档
- PDF Loader:加载PDF文件
除此之外,LangChain还支持超过100种不同的数据源接口,包括B站、YouTube、GitHub等平台的数据。
# 加载Markdown文件示例
from langchain.document_loaders import UnstructuredMarkdownLoader
loader = UnstructuredMarkdownLoader("path/to/file.md")
data = loader.load()
# 加载CSV文件示例
from langchain.document_loaders import CSVLoader
loader = CSVLoader("path/to/file.csv")
data = loader.load()
# 加载目录中的文件
from langchain.document_loaders import DirectoryLoader
loader = DirectoryLoader("path/to/directory", glob="**/*.pdf")
data = loader.load()
文档转换:切割、总结和翻译
加载文档后,通常需要对其进行处理,以便更好地利用文档内容:
文档切割
文档切割的目的是:
- 降低成本,适应大模型的上下文窗口限制
- 将文档转换为结构化数据,便于查询
LangChain提供了多种切割器:
# 字符串分割
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
separator="\n\n",
chunk_size=1000,
chunk_overlap=200,
length_function=len
)
docs = text_splitter.split_documents(documents)
# 按代码分割
from langchain.text_splitter import RecursiveCharacterTextSplitter
python_splitter = RecursiveCharacterTextSplitter.from_language(
language="python",
chunk_size=1000,
chunk_overlap=200
)
文档总结、精炼和翻译
LangChain还提供了文档总结、精炼和翻译的功能:
# 文档总结
from langchain.chains.summarize import load_summarize_chain
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(temperature=0)
chain = load_summarize_chain(llm, chain_type="map_reduce")
summary = chain.run(docs)
# 文档翻译
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
template = """Translate the following text from {source_language} to {target_language}:
{text}"""
prompt = PromptTemplate(
input_variables=["source_language", "target_language", "text"],
template=template
)
chain = LLMChain(llm=llm, prompt=prompt)
translated = chain.run(source_language="English", target_language="Chinese", text=text)
解决"Lost in the Middle"问题
研究表明,当处理长文本时,检索性能会出现"迷失在中间"的现象:当相关信息位于输入上下文的开头或结尾时,模型表现最佳;而当相关信息位于中间位置时,即使对于专门设计的上下文模型,性能也会显著下降。
解决方案包括:
- 通过检索找到相关文本块
- 对检索结果进行重新排序,将最相关的内容放在开头和结尾
- 使用排序后的文本进行生成
# 排序检索结果的示例代码
def reorder_results(results):
# 按相关性排序
sorted_results = sorted(results, key=lambda x: x.score, reverse=True)
# 重新排列,将高相关度的放在头尾
reordered = []
for i in range(len(sorted_results)):
if i % 2 == 0:
reordered.append(sorted_results[i//2])
else:
reordered.append(sorted_results[len(sorted_results) - 1 - i//2])
return reordered
文本向量化
为什么需要文本向量化?主要有两个原因:
- 纯文本形式难以进行语义相关性搜索
- 文本处理效率低且占用空间大
文本向量化(Embedding)将文本转换为高维向量空间中的点,使得语义相似的文本在向量空间中距离更近。
# 使用OpenAI的文本嵌入模型
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
query_vector = embeddings.embed_query("How does RAG work?")
# 使用HuggingFace的文本嵌入模型
from langchain_huggingface import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
向量数据库:存储和检索
向量数据库是RAG技术中不可或缺的组成部分,它能够高效地存储和检索高维向量数据。向量数据库的主要作用包括:
- 管理数据:以原始形式有效管理数据
- 存储向量:存储AI需要的高维数据
- 高效检索:快速找到语义相似的内容
市面上有多种向量数据库可供选择:
- Milvus:开源向量数据库,功能完善,性能强大
- Pinecone:云原生向量数据库,易于使用
- Chroma:轻量级向量数据库,适合本地开发
- FAISS:Facebook AI开发的相似性搜索库
- Qdrant:开源向量搜索引擎
# 使用Chroma向量数据库
from langchain_community.vectorstores import Chroma
vectordb = Chroma.from_documents(
documents=docs,
embedding=embeddings,
persist_directory="./chroma_db"
)
# 检索相似文档
docs = vectordb.similarity_search("What is RAG technology?", k=3)
🛠️ 实战项目:ChatDoc智能文档助手
下面,我们将学习如何构建一个名为ChatDoc的智能文档助手,它能够:
- 加载PDF、DOCX或Excel等格式的文档
- 对文档进行适当切分
- 使用OpenAI进行向量化
- 使用Chroma DB实现本地向量存储
- 实现与文档的对话功能
实现步骤
1. 初始化ChatDoc类
import os
from langchain.document_loaders import PDFMinerLoader, CSVLoader
from langchain.document_loaders import Docx2txtLoader, UnstructuredExcelLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
class ChatDoc:
def __init__(self):
self.file_path = None
self.file_content = None
self.db = None
self.embeddings = OpenAIEmbeddings()
self.llm = ChatOpenAI(temperature=0)
2. 实现文件加载功能
def get_file(self, file_path):
self.file_path = file_path
_, file_extension = os.path.splitext(file_path)
try:
if file_extension == '.pdf':
loader = PDFMinerLoader(file_path)
elif file_extension == '.csv':
loader = CSVLoader(file_path)
elif file_extension == '.docx':
loader = Docx2txtLoader(file_path)
elif file_extension == '.xlsx' or file_extension == '.xls':
loader = UnstructuredExcelLoader(file_path)
else:
print(f"不支持的文件格式: {file_extension}")
return None
self.file_content = loader.load()
print(f"文件 {file_path} 加载成功!")
return self.file_content
except Exception as e:
print(f"加载文件时出错: {e}")
return None
3. 文本切分与向量化
def separate_and_embed(self):
if self.file_content is None:
print("没有加载文件内容,请先加载文件。")
return None
# 文本切分
text_splitter = CharacterTextSplitter(
separator="\n",
chunk_size=1000,
chunk_overlap=200,
length_function=len
)
chunks = text_splitter.split_documents(self.file_content)
# 向量化存储
self.db = Chroma.from_documents(
documents=chunks,
embedding=self.embeddings,
persist_directory="./chroma_db"
)
print(f"文本已切分为 {len(chunks)} 个块并完成向量化存储。")
return self.db
4. 实现基本问答功能
def ask_and_find(self, question):
if self.db is None:
print("向量数据库未初始化,请先处理文档。")
return None
# 检索相关文档
docs = self.db.similarity_search(question, k=3)
# 构建上下文
context = "\n\n".join([doc.page_content for doc in docs])
# 创建提示
prompt = f"""
根据以下信息回答问题:
{context}
问题: {question}
"""
# 生成回答
response = self.llm.predict(prompt)
return response
5. 使用多重查询优化检索效果
from langchain.retrievers.multi_query import MultiQueryRetriever
def improved_ask_and_find(self, question):
if self.db is None:
print("向量数据库未初始化,请先处理文档。")
return None
# 创建多重查询检索器
retriever = MultiQueryRetriever.from_llm(
retriever=self.db.as_retriever(),
llm=self.llm
)
# 进行检索
docs = retriever.get_relevant_documents(question)
# 构建上下文
context = "\n\n".join([doc.page_content for doc in docs])
# 创建提示
prompt = f"""
根据以下信息回答问题:
{context}
问题: {question}
"""
# 生成回答
response = self.llm.predict(prompt)
return response
6. 实现对话功能
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
def chat_with_doc(self):
if self.db is None:
print("向量数据库未初始化,请先处理文档。")
return None
# 创建对话记忆
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True
)
# 创建对话链
qa_chain = ConversationalRetrievalChain.from_llm(
llm=self.llm,
retriever=self.db.as_retriever(),
memory=memory
)
# 开始对话
print("开始与文档对话,输入'exit'退出")
while True:
question = input("你: ")
if question.lower() == 'exit':
break
result = qa_chain({"question": question})
print(f"AI: {result['answer']}")
使用示例
# 使用ChatDoc
chat_doc = ChatDoc()
# 加载文档
chat_doc.get_file("example.pdf")
# 处理文档
chat_doc.separate_and_embed()
# 问答示例
response = chat_doc.ask_and_find("这个文档主要讲了什么?")
print(response)
# 启动交互式对话
chat_doc.chat_with_doc()
📈 RAG技术的优化方法
多重查询优化
多重查询(Multi-query)通过扩展原始问题,从不同角度生成多个相关查询,然后综合检索结果。这种方法可以提高检索的召回率和准确性。
# 多重查询示例
from langchain.retrievers.multi_query import MultiQueryRetriever
retriever = MultiQueryRetriever.from_llm(
retriever=vectordb.as_retriever(),
llm=ChatOpenAI(temperature=0)
)
# 原始问题
question = "如何实现RAG技术?"
# 生成的多重查询可能包括:
# 1. "RAG技术的实现步骤是什么?"
# 2. "如何在项目中使用检索增强生成?"
# 3. "实现RAG需要哪些组件?"
# 4. "RAG的代码实现示例"
上下文压缩
上下文压缩(Context compression)通过筛选和压缩检索到的文档,确保只有最相关的内容被包含在上下文中,从而提高回答质量并降低成本。
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain.retrievers import ContextualCompressionRetriever
# 创建压缩器
compressor = LLMChainExtractor.from_llm(llm)
# 创建压缩检索器
compression_retriever = ContextualCompressionRetriever(
base_retriever=vectordb.as_retriever(),
doc_compressor=compressor
)
# 使用压缩检索器获取文档
compressed_docs = compression_retriever.get_relevant_documents(query)
基于重排序的优化
针对"迷失在中间"问题,可以对检索结果进行重新排序,将最相关的文档放在上下文的开头和结尾:
def rerank_documents(docs, query):
# 计算每个文档与查询的相关性分数
# 这里可以使用更复杂的相关性评分方法
scored_docs = [(doc, doc.metadata.get("score", 0)) for doc in docs]
# 按分数排序
sorted_docs = sorted(scored_docs, key=lambda x: x[1], reverse=True)
# 重新排列文档,使最相关的在开头和结尾
result = []
for i in range(len(sorted_docs)):
if i % 2 == 0 and i//2 < len(sorted_docs):
result.append(sorted_docs[i//2][0])
elif i//2 < len(sorted_docs) // 2:
result.append(sorted_docs[len(sorted_docs) - 1 - i//2][0])
return result
🌟 总结与展望
通过本文,我们深入学习了RAG技术及其在LangChain中的实现。RAG技术通过将检索与生成相结合,有效解决了大模型知识时效性和专业性不足的问题,使AI能够访问最新的、专业的、私有的信息。
我们详细探讨了RAG的实现步骤:
- 使用各种Loader加载文档
- 通过文本切割、总结等方法处理文档
- 将文本向量化并存储在向量数据库中
- 根据用户问题检索相关文档
- 基于检索结果生成回答
同时,我们还介绍了多重查询、上下文压缩和重排序等优化技术,以提高RAG系统的性能。
最后,我们通过构建ChatDoc智能文档助手,将学到的知识应用到实践中,实现了与任意文档对话的功能。
未来,随着大模型和向量数据库技术的不断发展,RAG技术将变得更加高效、准确,并在更多领域发挥作用。通过掌握RAG技术,我们可以充分发挥大模型的潜力,构建更智能、更专业的AI应用。
💡 小提示:如果你想要进一步提升RAG系统的性能,可以考虑:
- 尝试不同的文本嵌入模型
- 优化文档切割策略
- 使用混合检索方法
- 引入用户反馈机制
希望本文能够帮助你理解和应用RAG技术,构建属于自己的智能文档助手!
🔗 参考资料:
- AI Agent智能应用从0到1定制开发
- LangChain官方文档:https://python.langchain.com/docs/
- “Lost in the Middle: How Language Models Use Long Contexts” 研究论文
- 向量数据库官方文档:Milvus、Chroma、Pinecone等