LangChain教程 - 构建对话式检索增强生成(Conversational RAG)

系列文章索引
LangChain教程 - 系列文章

在许多问答应用中,我们希望允许用户进行多轮对话,这需要应用具备某种形式的“记忆”,以便能够在当前回答中整合过去的问题和答案。本教程将介绍如何通过两种方法实现这一目标:

  1. Chains(链式方法):每次执行检索步骤。
  2. Agents(智能体方法):赋予大语言模型(LLM)判断力,让它决定是否执行检索步骤或多个步骤。

前提条件

本指南假定您已经熟悉以下概念:

  • 聊天历史
  • 聊天模型
  • 嵌入向量(Embeddings)
  • 向量存储
  • 检索增强生成(RAG)
  • 工具和智能体

环境设置

我们将使用 OpenAI 嵌入向量和 Chroma 向量存储库。以下是所需的依赖包:

pip install --upgrade langchain langchain-community langchainhub langchain-chroma beautifulsoup4
设置 API 密钥

我们需要设置 OPENAI_API_KEY 环境变量,可以直接手动输入或者从 .env 文件中加载:

import getpass
import osif not os.environ.get("OPENAI_API_KEY"):os.environ["OPENAI_API_KEY"] = getpass.getpass()

基础流程:构建检索增强生成(RAG)

在本节中,我们将构建一个基础的 RAG 系统,它从外部博客文章中检索信息并基于检索结果生成答案。

加载文档并构建检索器

首先,我们需要加载文档并将其拆分成较小的片段,以便可以在需要时进行高效检索。然后,我们将使用 OpenAI 嵌入向量和 Chroma 向量存储库创建一个检索器。

from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import WebBaseLoaderimport osos.environ['USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'# 1. 加载博客文章
loader = WebBaseLoader(web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
)
docs = loader.load()# 2. 文本拆分
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)# 3. 构建向量存储库并创建检索器
vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())
retriever = vectorstore.as_retriever()
构建问答链

接下来,我们将构建一个基于检索结果的问答链。该问答链使用预定义的系统提示,基于检索的上下文回答用户的问题。

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain# 4. 使用 GPT-4 模型生成回答
llm = ChatOpenAI(model="gpt-4")# 5. 定义系统提示
system_prompt = ("你是一个帮助用户回答问题的助手。使用以下检索到的内容作为答案的依据。如果你不知道答案,就说你不知道。请用三句话简洁作答。""\n\n""{context}"
)prompt = ChatPromptTemplate.from_messages([("system", system_prompt),("human", "{input}"),]
)# 6. 构建问答链
question_answer_chain = create_stuff_documents_chain(llm, prompt)
rag_chain = create_retrieval_chain(retriever, question_answer_chain)

现在,我们有了一个基础的 RAG 系统,它可以从外部文档中检索信息并生成答案。下一步是增加对话历史支持。

增加聊天历史支持

在多轮对话中,用户的后续问题可能依赖于先前的上下文。为了让系统能够更好地理解这些问题,我们需要在问答链中整合聊天历史。

创建支持历史的检索器

为了支持聊天历史,我们需要创建一个特殊的检索器,它能将历史消息和当前用户问题结合起来,重新表述问题,并根据这个重构后的问题进行检索。我们使用 create_history_aware_retriever 来实现这一点。

from langchain.chains import create_history_aware_retriever
from langchain_core.prompts import MessagesPlaceholder# 7. 定义用于重构问题的提示
contextualize_q_system_prompt = ("根据聊天历史和最新的用户问题,如果问题涉及历史信息,请将其重新表述成独立的问题。"
)contextualize_q_prompt = ChatPromptTemplate.from_messages([("system", contextualize_q_system_prompt),MessagesPlaceholder("chat_history"),("human", "{input}"),]
)# 8. 创建历史感知检索器
history_aware_retriever = create_history_aware_retriever(llm, retriever, contextualize_q_prompt
)
构建包含聊天历史支持的问答链

在此步骤中,我们将使用 history_aware_retriever 替换原来的 retriever,这样系统就能够利用聊天历史生成更加精准的回答。

# 9. 构建问答链
rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)

现在,我们的问答链能够利用聊天历史重新表述问题,从而更好地理解用户的多轮对话。

状态化管理聊天历史

在实际应用中,您需要一种方式来管理聊天历史。我们可以使用 LangChain 的 RunnableWithMessageHistory 类,帮助自动更新聊天历史并在每次调用时传递给模型。

使用 RunnableWithMessageHistory 管理聊天历史

我们通过 ChatMessageHistory 来存储聊天历史,并使用 RunnableWithMessageHistory 自动管理和更新它。

from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory# 10. 创建一个字典来存储聊天历史
store = {}def get_session_history(session_id: str) -> BaseChatMessageHistory:if session_id not in store:store[session_id] = ChatMessageHistory()return store[session_id]# 11. 构建支持聊天历史的问答链
conversational_rag_chain = RunnableWithMessageHistory(rag_chain,get_session_history,input_messages_key="input",history_messages_key="chat_history",output_messages_key="answer",
)

通过这种方式,系统会自动管理聊天历史,并在每次生成回答时传递相关的历史记录。

测试多轮对话功能

最后,我们可以测试整个系统。在以下示例中,系统会先回答一个问题,然后基于聊天历史回答后续问题。

# 定义会话历史
chat_history = []# 第一个问题
question = "什么是任务分解?"
ai_msg_1 = conversational_rag_chain.invoke({"input": question, "chat_history": chat_history},config={"configurable": {"session_id": "session_abc123"}}  # 指定 session_id
)
chat_history.extend([HumanMessage(content=question),AIMessage(content=ai_msg_1["answer"]),]
)# 第二个问题
second_question = "有哪些常见的分解方法?"
ai_msg_2 = conversational_rag_chain.invoke({"input": second_question, "chat_history": chat_history},config={"configurable": {"session_id": "session_abc123"}}  # 指定相同的 session_id
)print(ai_msg_2["answer"])

完整代码实例

import os
import getpass
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.chains import create_retrieval_chain, create_history_aware_retriever
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.messages import AIMessage, HumanMessage# 设置 API 密钥
os.environ["OPENAI_API_KEY"] = getpass.getpass("请输入您的 OpenAI API 密钥: ")# Step 1: 构建检索器
# 加载博客文章并分割为可检索的块
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)
vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())
retriever = vectorstore.as_retriever()# Step 2: 创建包含聊天历史支持的检索器
contextualize_q_system_prompt = ("根据聊天历史和最新的用户问题,如果问题涉及历史信息,请将其重新表述成独立的问题。"
)contextualize_q_prompt = ChatPromptTemplate.from_messages([("system", contextualize_q_system_prompt),MessagesPlaceholder("chat_history"),("human", "{input}"),]
)
llm = ChatOpenAI(model="gpt-4")history_aware_retriever = create_history_aware_retriever(llm, retriever, contextualize_q_prompt
)# Step 3: 创建问答链
system_prompt = ("你是一个帮助用户回答问题的助手。使用以下检索到的内容作为答案的依据。""如果你不知道答案,就说你不知道。请用三句话简洁作答。""\n\n""{context}"
)qa_prompt = ChatPromptTemplate.from_messages([("system", system_prompt),MessagesPlaceholder("chat_history"),("human", "{input}"),]
)question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)# Step 4: 管理聊天历史
store = {}def get_session_history(session_id: str) -> BaseChatMessageHistory:if session_id not in store:store[session_id] = ChatMessageHistory()return store[session_id]conversational_rag_chain = RunnableWithMessageHistory(rag_chain,get_session_history,input_messages_key="input",history_messages_key="chat_history",output_messages_key="answer",
)# Step 5: 模拟对话并添加聊天历史
session_id = "abc123"
chat_history = get_session_history(session_id)# 问题 1
question = "什么是任务分解?"
ai_msg_1 = conversational_rag_chain.invoke({"input": question},config={"configurable": {"session_id": "abc123"}}  # 指定 session_id
)
print(f"AI: {ai_msg_1['answer']}")# 问题 2(基于上下文的后续问题)
second_question = "有哪些常见的分解方法?"
ai_msg_2 = conversational_rag_chain.invoke({"input": second_question},config={"configurable": {"session_id": "abc123"}}  # 指定 session_id
)
print(f"AI: {ai_msg_2['answer']}")# Step 6: 查看整个聊天历史
print("\n聊天历史:")
for message in chat_history.messages:if isinstance(message, AIMessage):prefix = "AI"else:prefix = "User"print(f"{prefix}: {message.content}\n")

总结

通过本教程,您学习了如何使用 LangChain 构建一个对话式的检索增强生成系统。我们详细介绍了如何加载文档、创建检索器、构建问答链,并在系统中加入聊天历史支持。最后,我们使用 RunnableWithMessageHistory 实现了对聊天历史的状态化管理,使系统能够自动处理多轮对话。

通过这些步骤,您可以创建一个支持复杂对话和上下文处理的智能系统,用于回答用户问题并提供精准的内容检索和生成功能。

附1:代码实例 LLM 调用解析

在构建支持聊天历史的 Conversational RAG 系统中,LLM(大语言模型)的调用贯穿了问题的重构和答案的生成过程。为了清楚理解 LLM 的调用时机及作用,我们可以将整个过程分为以下四个步骤,详细解析每次 LLM 的调用。

1. 第一次调用:格式化第一个问题

当用户提出第一个问题(例如:"什么是任务分解?")时,系统首先会调用 history_aware_retriever 进行问题的重新格式化。这是为了确保当前问题在检索前是完整的、独立的问题,即使没有任何历史消息也可以理解。

位置

history_aware_retriever = create_history_aware_retriever(llm, retriever, contextualize_q_prompt
)

在此过程中,LLM 被调用来基于当前的聊天历史生成完整的问题格式。这是整个流程中的第一次 LLM 调用

2. 第二次调用:生成第一个问题的答案

在第一个问题被重新格式化并提交给检索器后,系统会调用 LLM 来基于检索到的相关文档生成答案。LLM 根据系统提示,结合检索到的内容,为第一个问题生成简洁的回答。

位置

ai_msg_1 = conversational_rag_chain.invoke({"input": question, "chat_history": chat_history})

这一步是 LLM 的第二次调用,负责根据检索结果为第一个问题生成具体的答案。

3. 第三次调用:格式化第二个问题

当用户提出第二个问题(例如:"有哪些常见的分解方法?")时,系统会再次调用 history_aware_retriever。这次,LLM 会根据历史消息和当前用户的问题,重新格式化这个问题,使其在检索时可以被正确理解。例如,这个问题的“它”可能指代前面的“任务分解”。

位置

history_aware_retriever = create_history_aware_retriever(llm, retriever, contextualize_q_prompt
)

这是 LLM 的第三次调用,它帮助将第二个问题重新构造成完整、独立的问题,避免历史上下文的混淆。

4. 第四次调用:生成第二个问题的答案

在第二个问题被重新格式化并提交检索后,系统再次调用 LLM,基于检索结果生成针对该问题的答案。LLM 会根据系统的提示和检索到的上下文,为第二个问题提供具体的回答。

位置

ai_msg_2 = conversational_rag_chain.invoke({"input": second_question, "chat_history": chat_history})

这是 LLM 的第四次调用,负责生成第二个问题的答案。

LLM 调用总结

在这个支持聊天历史的 Conversational RAG 系统中,LLM 总共被调用了 4 次

  1. 第一次 LLM 调用:通过 history_aware_retriever 格式化第一个问题,确保其独立于聊天历史。
  2. 第二次 LLM 调用:生成第一个问题的答案,基于检索结果返回信息。
  3. 第三次 LLM 调用:通过 history_aware_retriever 格式化第二个问题,结合历史上下文重新构建问题。
  4. 第四次 LLM 调用:生成第二个问题的答案,基于检索到的内容提供解答。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/148675.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

从趋势到常态:TikTok定制化产品的崛起与变革

随着数字化和TikTok的发展,定制化产品在消费者日常生活中愈发普及,逐渐从一种时尚潮流转变为常态。这一转变不仅改变了消费者的购物方式,也重塑了市场的供需关系、产品设计理念和商业模式。本文Nox聚星将和大家探讨TikTok定制化产品的未来发展…

Windows系统及Ubuntu系统安装Java

Java语言简介 Java是一种高级编程语言,Java语言的创始可以追溯到1990年代初,当时任职于Sun Microsystems(后来被甲骨文公司收购)的詹姆斯高斯林(James Gosling)等人开始开发一种名为“Oak”(名字来源于詹姆…

大模型时代:AI引领企业创新升级的全面爆发

人工智能(AI)正在以惊人的速度改变企业的运营模式,成为企业效率提升与创新的强大驱动力。随着AI技术的不断发展,企业正面临前所未有的机遇与挑战,如何有效利用这些技术已成为决定企业未来成败的关键。 首先&#xff0c…

抖音生活服务入局攻略曝光!普通人也能抓住风口!

当前,抖音生活服务的热度持续飙升,让不少人都有了入局的打算,与之相关的各类话题如抖音生活服务的入局途径有哪些等也因此成为了人们热议的对象。而从这些话题的讨论情况来看,绝大多数讨论者只知道抖音生活服务火爆,却…

基于SpringBoot和协同过滤算法的电商购物平台

文未可获取一份本项目的java源码和数据库参考。 选题的目的和意义: 在今天的科技发展现状中,所体现出的高速发展的信息技术,人们的生活水平及生活方式也随之发生着变化,以往人们已经习惯了多年养成的购物习惯,那就是到…

无人机之工作温度篇

无人机的工作温度是一个相对复杂的问题,因为它受到多种因素的影响,包括无人机的类型(如民用、军用)、设计规格、应用场景以及环境条件等。以下是对无人机工作温度范围的详细解析: 一、正常工作温度范围 一般来说&…

结合HashMap与Java 8的Function和Optional消除ifelse判断

shigen坚持更新文章的博客写手,记录成长,分享认知,留住感动。个人IP:shigen 在文章的开头我们先从这些场景进入本期的问题: 业务代码中各种if-else有遇到过吗,有什么好的优化方式;java8出来这么…

可编辑PPT | 能源企业数字化框架、数字化运营及数字化平台建设方案

项目背景及需求理解 首先提出了全球能源互联网的概念,强调了清洁能源和电能替代的重要性,并介绍了德国工业4.0战略以及泛在电力物联网的创新。文档探讨了信息化与工业化的深度融合,以及云计算、大数据、物联网和移动应用等新技术在能源行业的…

超详细超实用!!!AI编程之cursor编写一个官网(二)

云风网 云风笔记 云风知识库 一、新建html文件 选中添加index.html,输入编写官网要求&#xff0c;自动生成代码&#xff0c;先来个简单的。 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"…

博图安装(版本:V18)

目录 一、软件安装 Ⅰ、博图编程软件安装 Ⅱ、仿真环境安装 二、许可证安装 安装教程 工作中常用的是编程软件&#xff0c;组态软件以及仿真&#xff0c;编程软件和组态软件是一起的&#xff0c;因此先演示编程软件的安装 软件安装总共分为两部分&#xff0c;安装软件和安…

对比评测5款实用在线翻译工具,包括有道在线翻译

大家好&#xff0c;今天咱们来聊聊在线翻译工具。在这个信息爆炸的时代&#xff0c;语言不再是沟通的障碍&#xff0c;多亏了这些强大的翻译神器。今天&#xff0c;我将带大家比较五款热门的在线翻译工具&#xff0c;究竟谁更胜一筹呢&#xff1f;让我们一探究竟&#xff01; …

STM32 通过软件模拟 I2C 驱动 24Cxx 系列存储器

目录 一、AT24CXXX 系列存储器介绍1、基本信息2、寻址方式3、页地址与页内单元地址4、I2C 地址5、AT24CXX 的数据读写5.1 写操作5.1.1 按字节写5.1.2 按页写 5.2 读操作5.2.1 当前地址读取5.2.2 随机地址读取5.2.3 顺序读取 二、代码实现1、ctl_i2c2、at24c3、测试程序 I2C 相关…

Linux复习--网络基础(OSI七层、TCP三次握手与四次挥手、子网掩码计算)

一、ISO/OSI七层模型的分层与作用 1、ISO/OSI的七层模型 2、作用 应用层&#xff1a;为用户提供服务&#xff0c;给用户一个操作界面表示层&#xff1a;数据提供表示&#xff1b;加密&#xff1b;压缩&#xff1b;会话层&#xff1a;确定数据是否需要进行网络传递传输层&…

从0开始linux(5)——vim

欢迎来到博主的专栏&#xff1a;从0开始linux 博主ID&#xff1a;代码小豪 文章目录 vim的多种模式底行模式命令命令模式视块模式&#xff08;visual block&#xff09; vim的配置 vim是linux系统的文本编辑器。就像windows的记事本一样。 使用vim指令可以使用vim打开一个文本文…

5G Multicast/Broadcast Services(MBS) (四)

这篇是有关MBS RRC相关的一些基本内容,内容不多,但是感觉很关键,主要包括SI,MBS网络侧相关的内容,L2 协议架构,cell reselection prioritity以及MBS接收的一些内容,希望有帮助。 SI 在MBS场景中,SI和常规5G一样分为Minimum SI和Other SI。Minimum SI是MIB和SIB1,Min…

程序员画图神器,开源

https://github.com/jgraph/drawio 开源、免费、简洁、方便的画图工具&#xff0c;可作为visio或亿图的替代品 drawio简介 在流程图、思维导图、UML图等图表绘制领域,draw.io可以说是一款优秀和受欢迎的在线作图工具。draw.io由JGraph开发,完全开源,拥有超过3万个GitHub Star…

芯片仓管系统主要适用场景有哪些

随着科技产业的飞速发展&#xff0c;芯片作为电子设备的核心部件&#xff0c;其库存管理成为了企业运营中不可或缺的一环。芯片仓管系统&#xff0c;作为专为高效、精准管理芯片库存而设计的信息化工具&#xff0c;正逐步在多个关键领域展现出其不可或缺的价值。那么&#xff0…

APScheduler、Django、Python实现定时任务,以及任务操作

环境&#xff1a;Windows 11、python 3.12.3、Django 4.2.11、 APScheduler 3.10.4 背景&#xff1a;工作需要使用且用法较为复杂&#xff0c;各种功能基本都使用了 事件&#xff1a;20240920 说明&#xff1a;记录&#xff0c;方便后期自己查找 1、搭建基础环境 文件结构图…

免费分享一套SpringBoot+Vue火车票订票管理系统【论文+源码+SQL脚本】,帅呆了~~

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringBootVue火车票订票管理系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringbootVue火车票订票管理系统 Java毕业设计_哔哩哔哩_bilibili 项目介绍 传统办法管理信息首先需要花费的时间比较多&…

基于Springboot个性化图书推荐系统JAVA|VUE|SSM计算机毕业设计源代码+数据库+LW文档+开题报告+答辩稿+部署教+代码讲解

源代码数据库LW文档&#xff08;1万字以上&#xff09;开题报告答辩稿 部署教程代码讲解代码时间修改教程 一、开发工具、运行环境、开发技术 开发工具 1、操作系统&#xff1a;Window操作系统 2、开发工具&#xff1a;IntelliJ IDEA或者Eclipse 3、数据库存储&#xff1a…