目录
- 引言
- 一、LangChain实现RAG多轮问答核心机制
- 1. 对话历史管理(Memory)
- 2. 问题重写(Query Rewriting)
- 3. 检索增强生成(RAG Core)
- 4. 链式工作流(Chain)
- 二、关键设计特点
- 三、完整示例代码
- 四、优化建议
引言
小马之所以写今天这篇文章是因为在上一篇文章《多轮问答与指代消解》中的第一章节有提到“LangChain是怎么实现的多轮问答”,但很显然没有提到它在RAG下是怎么实现的多轮问答,毕竟这两者还是有区别的。前一篇文章刚好提到自己实现RAG下的多轮问答要怎么实现,唯独没有提到LangChain在RAG下是怎么实现的多轮。
前篇文章中提到的MultiQueryRetriever
小马已经在文中声明,这个并不是为了解决RAG多轮的机制。
庆幸的是,我们回过头来看LangChain实现RAG多轮问答的方式其实和我们自己实现的思路不能说一模一样,简直是毫无区别。只是LangChain封装了组件,我们直接调用就能实现了,省去了自己写工程代码来实现的过程。接下来我们有必要一起看下的,不过看过上一篇的同学其实看这篇文章就很好理解了。
一、LangChain实现RAG多轮问答核心机制
在 LangChain 中实现 RAG(Retrieval-Augmented Generation)的多轮问答主要通过以下核心机制完成,确保对话历史被有效利用:
1. 对话历史管理(Memory)
LangChain 使用 Memory 组件 存储和传递上下文:
ConversationBufferMemory
存储完整的原始对话历史(用户输入 + AI 响应)。ConversationSummaryMemory
用 LLM 压缩历史为摘要,避免 token 超限。ConversationBufferWindowMemory
仅保留最近 K 轮对话,控制上下文长度。
from langchain.memory import ConversationBufferMemorymemory = ConversationBufferMemory(memory_key="chat_history", # 存储对话的键名return_messages=True # 返回消息列表而非字符串
)
2. 问题重写(Query Rewriting)
将当前问题结合历史重写为独立查询,使检索更准确:
ConversationalRetrievalChain
内置condense_question
步骤:- 输入:当前问题
+
对话历史 - 输出:重写后的独立问题(如将代词替换为具体实体)
- 输入:当前问题
from langchain.chains import ConversationalRetrievalChainqa_chain = ConversationalRetrievalChain.from_llm(llm=chat_model, # 如 ChatOpenAIretriever=vectorstore.as_retriever(), # 向量检索器memory=memory,condense_question_llm=condense_model # 可选:专用重写模型
)
3. 检索增强生成(RAG Core)
多轮流程如下:
- 检索上下文
用重写后的查询从向量库检索相关文档片段。 - 组装提示
将以下内容组合为最终提示:- 检索到的上下文
- 对话历史(原始或摘要)
- 当前问题
- 生成答案
LLM 基于完整上下文生成连贯回复。
提示词示例:
[检索到的文档]
对话历史:用户: 量子计算是什么?AI: 量子计算利用量子比特...
当前问题:它有什么优势?
最终答案:量子计算的优势包括...
4. 链式工作流(Chain)
ConversationalRetrievalChain
自动处理整个流程:
# 第一轮
result = qa_chain({"question": "什么是RAG?"})
print(result["answer"])# 第二轮(自动携带历史)
result = qa_chain({"question": "它如何解决幻觉问题?"}) # "它"指代RAG
print(result["answer"])
二、关键设计特点
组件 | 作用 |
---|---|
Memory | 持久化历史对话,支持多种存储策略(原始/摘要/滑动窗口) |
Query Rewriting | 解决指代消解(如“它”指代上文的 RAG),提升检索准确性 |
Contextual Prompt | 动态组装提示,包含历史 + 检索结果 + 当前问题 |
检索器优化 | 支持自定义检索器(如过滤元数据、调整相似度阈值) |
三、完整示例代码
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain# 初始化向量库和模型
vectorstore = FAISS.load_local("rag_vector_db", OpenAIEmbeddings())
llm = ChatOpenAI(model="gpt-4-turbo")
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)# 构建链
qa_chain = ConversationalRetrievalChain.from_llm(llm=llm,retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),memory=memory
)# 模拟多轮对话
questions = ["LangChain是什么?","它的核心组件有哪些?", # "它" 指代LangChain"如何用RAG实现多轮对话?"
]
for q in questions:result = qa_chain.invoke({"question": q})print(f"问题: {q}\n答案: {result['answer']}\n")
四、优化建议
- 历史摘要:长对话时使用
ConversationSummaryMemory
避免 token 超限。 - 重写模型:用轻量级模型(如 GPT-3.5)专门处理问题重写,降低成本。
- 检索过滤:基于元数据(如对话 ID)过滤无关文档。
- 提示工程:定制提示模板明确区分历史/当前问题/检索内容。
通过组合 Memory 管理、智能查询重写和 上下文感知的 RAG 流程,LangChain 有效实现了多轮问答的连贯性与准确性。
本篇文章旨在补充上一篇文章的内容,建议结合上一篇文章食用,口感效果更加。