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.rhkb.cn/news/429996.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大数据可视化-三元图

三元图是一种用于表示三种变量之间关系的可视化工具,常用于化学、材料科学和地质学等领域。它的特点是将三个变量的比例关系在一个等边三角形中展示,使得每个点的位置代表三个变量的相对比例。 1. 结构 三个角分别表示三个变量的最大值(通常…

爬虫 ----hook

目录 定义: 了解什么是hook? 举例 hook XHR请求 XMLHttpRequest 案例地址: Interceptors-拦截器 HOOK cookie操作 cookie 示范 常见的hook代码总结 1.Hook Cookie 2.Hook Header 3.Hook URL 4.Hook JSON.stringify 5.Hook JSON.parse 6.Ho…

蓝桥杯嵌入式的学习总结

一. 前言 嵌入式竞赛实训平台(CT117E-M4) 是北京国信长天科技有限公司设计,生产的一款 “ 蓝桥杯全国软件与信息技术专业人才大赛-嵌入式设计与开发科目 “ 专用竞赛平台,平台以STM32G431RBT6为主控芯片,预留扩展板接口,可为用户提…

conda环境下module ‘numba.types‘ has no attribute ‘Macro‘问题解决

1 问题描述 conda环境下运行数据处理&#xff0c;报出如下错误&#xff1a; Traceback (most recent call last):File "train_preprocess.py", line 13, in <module>import audioFile "/opt/service/lipsync/audio.py", line 1, in <module>…

橙子质量检测系统源码分享

橙子质量检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

基于yolov5滑块识别破解(一)

由于内容较长&#xff0c;将分为两个部分来说明&#xff0c;本文讲解yolov5的部署与训练。 1.YOLOv5部署 云端部署&#xff08;训练&#xff09; 服务器创建 如果自己的显卡算力不是很好的&#xff0c;或者是核显电脑&#xff0c;可以租用算力&#xff0c;价格还行一块钱左右就…

知乎:从零开始做自动驾驶定位; 注释详解(二)

这个个系统整体分为: 数据预处理 前端里程计 后端优化 回环检测 显示模块。首先来看一下数据预处理节点做的所有事情&#xff1a; 数据预处理节点 根据知乎文章以及代码我们知道: 节点功能输入输出数据预处理1.接收各传感器信息2.传感器数据时间同步 3.点云运动畸变补偿 4.传…

20 基于STM32的温度、电流、电压检测proteus仿真系统(OLED、DHT11、继电器、电机)

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于STM32F103C8T6 采用DHT11读取温度、滑动变阻器模拟读取电流、电压。 通过OLED屏幕显示&#xff0c;设置电流阈值为80&#xff0c;电流小阈值为50&#xff0c;电压阈值为60&#xff0c;温度阈值…

【Qt网络编程】Tcp多线程并发服务器和客户端通信

目录 一、编写思路 1、服务器 &#xff08;1&#xff09;总体思路widget.c&#xff08;主线程&#xff09; &#xff08;2&#xff09;详细流程widget.c&#xff08;主线程&#xff09; &#xff08;1&#xff09;总体思路chat_thread.c&#xff08;处理聊天逻辑线程&…

SQL 多表联查

目录 1. 内联接&#xff08;INNER JOIN&#xff09; 2. 左外联接&#xff08;LEFT JOIN&#xff09; 3. 右外联接&#xff08;RIGHT JOIN&#xff09; 4. 全外联接&#xff08;FULL JOIN&#xff09; 5. 交叉联接&#xff08;CROSS JOIN&#xff09; 6. 自联接&#xff0…

MySQL篇(存储引擎)(持续更新迭代)

目录 一、简介 二、使用存储引擎 1. 建表时指定存储引擎 2. 查询当前数据库支持的存储引擎 三、三种常见存储引擎 1. InnoDB存储引擎 1.1. 简介 1.2. 特点 1.3. 文件格式 1.4. 逻辑存储结构 表空间 段 区 页 行 2. MyISAM存储引擎 2.1. 简介 2.2. 特点 2.3. …

【Linux】入门【更详细,带实操】

Linux全套讲解系列&#xff0c;参考视频-B站韩顺平&#xff0c;本文的讲解更为详细 目录 1、课程内容 2、应用领域 3、概述 4、 Linux和Unix 5、VMware15.5和CentOS7.6安装 6、网络连接三种方式 7、虚拟机克隆 8、虚拟机快照 9、虚拟机迁移删除 10、vmtools 11、目录…

Gartner:中国企业利用GenAI提高生产力的三大策略

作者&#xff1a;Gartner高级首席分析师 雷丝、Gartner 研究总监 闫斌、Gartner高级研究总监 张桐 随着生成式人工智能&#xff08;GenAI&#xff09;风靡全球&#xff0c;大多数企业都希望利用人工智能&#xff08;AI&#xff09;技术进行创新&#xff0c;以收获更多的业务成果…

python是什么语言写的

Python是一种计算机程序设计语言。是一种面向对象的动态类型语言。现今Python语言很火&#xff0c;可有人提问&#xff0c;这么火的语言它的底层又是什么语言编写的呢&#xff1f; python是C语言编写的&#xff0c;它有很多包也是用C语言写的。 所以说&#xff0c;C语言还是很…

SSM+vue音乐播放器管理系统

音乐播放器管理系统 随着社会的发展&#xff0c;计算机的优势和普及使得音乐播放器管理系统的开发成为必需。音乐播放器管理系统主要是借助计算机&#xff0c;通过对首页、音乐推荐、付费音乐、论坛信息、个人中心、后台管理等信息进行管理。减少管理员的工作&#xff0c;同时…

2024年华为杯数学建模E题-高速公路应急车道启用建模-基于YOLO8的数据处理代码参考(无偿分享)

利用YOLO模型进行高速公路交通流量分析 识别效果&#xff1a; 免责声明 本文所提供的信息和内容仅供参考。尽管我尽力确保所提供信息的准确性和可靠性&#xff0c;但我们不对其完整性、准确性或及时性作出任何保证。使用本文信息所造成的任何直接或间接损失&#xff0c;本人…

《深度学习》—— 卷积神经网络(CNN)的简单介绍和工作原理

文章目录 一、卷积神经网络的简单介绍二、工作原理(还未写完)1.输入层2.卷积层3.池化层4.全连接层5.输出层 一、卷积神经网络的简单介绍 基本概念 定义&#xff1a;卷积神经网络是一种深度学习模型&#xff0c;通常用于图像、视频、语音等信号数据的分类和识别任务。其核心思想…

Java笔试面试题AI答之设计模式(5)

文章目录 21. 简述Java什么是适配器模式 ?适配器模式的主要组成部分包括&#xff1a;适配器模式的实现方式主要有两种&#xff1a;适配器模式的优点&#xff1a;适配器模式的缺点&#xff1a;示例说明&#xff1a; 22. 请用Java代码实现适配器模式的案例 &#xff1f; 21. 简述…

【Transformers基础入门篇1】基础知识与环境安装

文章目录 一、自然语言处理基础知识1.1 常见自然语言处理任务1.2 自然语言处理的几个阶段 二、Transformers简单介绍2.1 Transformers相关库介绍2.2 Transformers 相关库安装 三、简单代码&#xff0c;启动NLP应用 一、自然语言处理基础知识 1.1 常见自然语言处理任务 情感分…

2024风湿免疫科常用评估量表汇总,附操作步骤与评定标准!

常笑医学整理了5个风湿免疫科常用的评估量表&#xff0c;包括类风湿关节炎患者病情评价&#xff08;DAS28&#xff09;、系统性狼疮活动性测定&#xff08;SLAM&#xff09;等。这些量表在常笑医学网均支持在线评估、下载和创建项目使用。 01 类风湿关节炎患者病情评价 &#x…