大模型:检索增强生成(RAG)
简单来说,RAG就是让大模型在回答问题之前先"查资料"。传统的模型只能依靠训练时学到的知识,而RAG可以让模型实时检索外部知识库,获取最新、最准确的信息,然后基于这些信息生成答案。这样既解决了模型知识更新的问题,又能避免模型"胡编乱造"。
什么是RAG?
简单来说,RAG就是让大模型在回答问题之前先"查资料"。传统的模型只能依靠训练时学到的知识,而RAG可以让模型实时检索外部知识库,获取最新、最准确的信息,然后基于这些信息生成答案。这样既解决了模型知识更新的问题,又能避免模型"胡编乱造"。
一、加载文档
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("file2.pdf")
docs = loader.load()
print(docs[0].page_content)
这是RAG流程的起点。使用PyPDFLoader加载了一个PDF文件,把文档内容读入内存。docs是一个包含多个Document对象的列表,每个Document对象都有page_content(文本内容)和metadata(元数据)两个属性。通过打印第一个文档的内容,我可以确认文件是否加载成功。
from langchain_community.document_loaders import WikipediaLoader
loader = WikipediaLoader(query="周杰伦", load_max_docs=3, lang="zh")
docs = loader.load()
# 查看第一个Document元素的文本内容
print(docs[1].page_content)
代码中注释掉的WikipediaLoader部分展示了另一个数据源——可以从维基百科加载"周杰伦"的相关信息。这说明RAG的数据源可以是多种多样的,本地文件、网页、百科都可以。
二、文本分割
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ",", "、", ""]
)
texts = text_splitter.split_documents(docs)
print(texts[0].page_content)
加载完文档后,直接使用整个文档会有问题。大模型对输入长度有限制,而且检索时如果文档太大,可能会包含太多无关信息。所以需要对文档进行分割。
这里设置了chunk_size=500,意思是每个文本块大约500个字符。chunk_overlap=50让相邻的块有50个字符的重叠,这样可以避免在分割点处切断重要的语义信息。separators参数定义了分割的优先级,从段落、句子到词语,确保分割尽可能保持语义完整。
三、文本向量化
from langchain_openai import OpenAIEmbeddings
from langchain_community.embeddings import DashScopeEmbeddings
embeddings = DashScopeEmbeddings(
model="text-embedding-v2",
dashscope_api_key="sk********************"
)
text = "This is a test document."
query_result = embeddings.embed_query(text)
print("文本向量长度:", len(query_result
计算机无法直接理解文本,需要把文字转换成数字——这就是向量化。这段代码使用阿里云DashScope的嵌入模型,把文本转换成向量。
关键代码解析:
-
embed_query():将单个查询文本转为向量 -
embed_documents():将多个文档批量转为向量
运行结果会显示向量的维度,比如1536维。text-embedding-v3模型支持指定向量维度,这在存储和计算效率上会更有优势。
四、构建向量库和检索
from langchain_community.vectorstores import FAISS
db = FAISS.from_documents(texts, embeddings_model)
retriever = db.as_retriever()
retrieved_docs = retriever.invoke("是否可以使用闪光灯")
print(retrieved_docs[0].page_content)
这是RAG的核心步骤。我使用FAISS(Facebook AI Similarity Search)创建了一个向量数据库,把之前分割好的文档块和对应的向量存入其中。
当用户提问时,系统会:
- 把问题向量化
- 在FAISS中搜索最相似的文档向量
- 返回最相关的文档块
在这个例子中,我询问"是否可以使用闪光灯",检索器从《卢浮宫.pdf》中找出了相关内容,打印出最相关的那段文本。
五、带记忆的对话系统
更进阶RAG,带记忆功能的对话式RAG系统。这就像是给我们的RAG系统装上了"大脑",让它能够记住对话的上下文,实现真正的多轮对话。
1. 导入必要的模块
from langchain.chains import ConversationalRetrievalChain # 对话检索链,RAG的核心
from langchain.memory import ConversationBufferMemory # 记忆组件,存储对话历史
from langchain_community.document_loaders import PyPDFLoader # PDF加载器
from langchain_community.vectorstores import FAISS # 向量数据库
from langchain_openai import ChatOpenAI # 大模型接口
from langchain_community.embeddings import DashScopeEmbeddings # 阿里云向量模型
from langchain_text_splitters import RecursiveCharacterTextSplitter # 文本分割器
这里导入了构建RAG系统所需的所有组件。特别注意ConversationalRetrievalChain是专门为对话设计的检索增强生成链,它整合了检索、记忆和生成三大功能。
2. 加载PDF文档
loader = PyPDFLoader("./卢浮宫.pdf")
docs = loader.load()
使用PyPDFLoader加载本地的"卢浮宫.pdf"文件。load()方法返回一个Document对象列表,每个Document包含page_content(文本内容)和metadata(元数据,如页码等)。
3. 文本分割
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个文本块大小500字符
chunk_overlap=50, # 相邻块重叠50字符,避免切断语义
separators=["\n", "。", "!", "?", ",", "、", ""] # 分割优先级
)
texts = text_splitter.split_documents(docs)
将长文档分割成适合检索的小块。为什么需要重叠?假设一句话被切在两块之间,重叠能保证这句话至少完整地出现在某一个块中。
4. 创建向量数据库
embeddings_model = DashScopeEmbeddings(
model="text-embedding-v2",
dashscope_api_key="sk-450489d77b3c46f28ebc4f9ac8403e19",
)
db = FAISS.from_documents(texts, embeddings_model) # 灌库
retriever = db.as_retriever()
-
DashScopeEmbeddings:使用阿里云的向量模型,将文本转换成向量 -
FAISS.from_documents:创建向量数据库,把文档块和对应的向量存入FAISS索引 -
as_retriever():将向量数据库包装成检索器,方便后续调用
5. 初始化大模型
model = ChatOpenAI(
model="qwen3.5-plus",
openai_api_key="sk-********************",
openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
这里有个巧妙之处:虽然用的是ChatOpenAI类,但实际上调用的是阿里云通义千问的API。因为阿里云提供了兼容OpenAI接口的服务,所以可以用同一套代码调用不同的模型。
6. 设置记忆组件
memory = ConversationBufferMemory(
return_messages=True, # 以消息列表形式返回历史
memory_key='chat_history', # 在prompt中使用的变量名
output_key='answer' # 指定哪个输出需要保存
)
记忆组件是对话系统的灵魂。它会在每次对话后自动保存问题和答案,在下一次对话时把这些历史信息加入到prompt中,让模型理解上下文。
7. 构建对话检索链
qa = ConversationalRetrievalChain.from_llm(
llm=model,
retriever=retriever,
memory=memory
)
将三大核心组件组装在一起:
-
llm:负责生成回答 -
retriever:负责检索相关文档 -
memory:负责管理对话历史
8. 开始多轮对话
question = "卢浮宫这个名字怎么来的?"
print(qa.invoke({"chat_history": memory, "question": question}))
question = "对应的拉丁语是什么呢?"
print(qa.invoke({"chat_history": memory, "question": question}))
这就是带记忆的RAG最神奇的地方!当问"对应的拉丁语是什么呢?",系统会自动理解这里的"对应的"指的是上一个问题中讨论的"卢浮宫"这个名字。因为记忆组件已经把第一轮对话的内容传递给模型了。
注意看invoke的参数:虽然传入了"chat_history": memory,但实际上ConversationalRetrievalChain会自动从memory对象中获取历史消息。这是LangChain的一个特性。
9. 带来源显示的版本
qa = ConversationalRetrievalChain.from_llm(
llm=model,
retriever=retriever,
memory=memory,
return_source_documents=True # 显示回答参考的原片段
)
question = "卢浮宫博物馆位于什么地方?"
print(qa.invoke({"chat_history": memory, "question": question}))
设置return_source_documents=True后,返回结果中会包含source_documents字段,告诉我们这个回答是基于哪些原始文档块生成的。这在需要验证答案准确性时特别有用。
通过这个代码示例,我们看到了一个完整的、带记忆功能的RAG系统是如何工作的。它不再是简单的"检索+生成",而是"检索+记忆+生成"的有机结合。正是这个记忆组件,让机器能够像人一样理解对话的上下文,实现真正自然的交流。
更多推荐


所有评论(0)