首页 > 其他分享 >【RAG 项目实战 08】为 RAG 添加历史对话能力

【RAG 项目实战 08】为 RAG 添加历史对话能力

时间:2024-11-27 09:12:13浏览次数:11  
标签:实战 RAG chain cl 08 session id history

【RAG 项目实战 08】为 RAG 添加历史对话能力


NLP Github 项目:


[!NOTE] 为 RAG 添加多轮对话能力

  1. 使用 create_history_aware_retriever 创建 改写链
  2. 使用 create_stuff_documents_chain 创建 问答链
  3. 使用 create_retrieval_chain 创建 RAG链
  4. 查看效果
  5. 添加多轮对话能力,存储对话历史(自动存储)
  6. RunnableWithMessageHistory 包装 Chain 添加对话历史能力
  7. 添加 session_id
  8. 大模型交互配置 session_id ,可以根据 session_id 区分对话历史
  9. 使用流式问答
  10. 使用 LangchainCallbackHandler 监听 Langchain事件,便于在页面进行调试
  11. 添加知识来源
  12. 将大模型更换为千帆对话大模型

[!NOTE] 问题调试

  1. 网络超时
  2. 反应慢
  3. 修改文档块的分割逻辑

一、添加历史信息

添加前:

  • query -> retriever
    添加后:
  • (query, conversation history) -> LLM -> rephrased query -> retriever

添加前:

  • (query, context)  -> LLM -> answer
    添加后:
  • (query, conversation history, context)  -> LLM -> answer

using the "chat_history" input key, and these messages will be inserted after the system message and before the human message containing the latest question.

create_history_aware_retriever
manages the case where chat_history is empty, and otherwise applies prompt | llm | StrOutputParser() | retriever in sequence.

使用记忆组件自动的管理会话历史

二、核心代码

  1. 环境配置
  2. 改写链:结合聊天历史改写用户问题
  3. 问答链:根据问题和参考内容生成答案
  4. RAG链:将改写链和问答链合并成完整的RAG链
  5. 管理聊天历史

2.1 环境配置

# @Author:青松  
# 公众号:FasterAI  
# Python, version 3.10.14  
# Pytorch, version 2.3.0  
# Chainlit, version 1.1.301

2.2 创建改写链

改写链:结合上下文改写用户问题

# 将 Chroma 向量数据库转化为检索器
retriever = vectorstore.as_retriever()

# 通过上下文改写用户的问题
contextualize_q_system_prompt = "给出下面的对话和一个后续问题,用原来的语言将后续问题改写为一个独立的问题。"
contextualize_q_prompt = ChatPromptTemplate.from_messages(
	[
		("system", contextualize_q_system_prompt),
		MessagesPlaceholder("chat_history"),
		("human", "{input}"),
	]
)

# 改写链:结合上下文改写用户问题
history_aware_retriever = create_history_aware_retriever(
	llm, retriever, contextualize_q_prompt
)

2.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)

2.4 创建RAG链

# RAG链:将改写链和问答链合并成完整的RAG链
    rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)

2.5 管理聊天历史

# 管理聊天历史
store = {}


def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

# 在 rag_chain 中添加 chat_history
conversational_rag_chain = RunnableWithMessageHistory(
        rag_chain,
        get_session_history,
        input_messages_key="input",
        history_messages_key="chat_history",
        output_messages_key="answer",
    )

2.6 配置 session_id 使用聊天历史

session_id = cl.user_session.get("session_id")
conversational_rag_chain = cl.user_session.get("conversational_rag_chain")

# 使用 session_id 结合聊天历史响应用户问题
res = conversational_rag_chain.invoke(
	{"input": message.content},
	config=RunnableConfig(
		configurable={"session_id": session_id},
		callbacks=[cl.LangchainCallbackHandler()]),
)

await cl.Message(content=res["answer"]).send()

2.7 使用流式进行响应

msg = cl.Message(content="")

# 使用 session_id 用流式的方式响应用户问题
chain = conversational_rag_chain.pick("answer")  # 只挑选 'answer' 属性输出
async for chunk in chain.astream(
		{"input": message.content},
		config=RunnableConfig(
			configurable={"session_id": session_id},
			callbacks=[cl.LangchainCallbackHandler()])
):
	await msg.stream_token(chunk)

await msg.send()

三、效果展示

四、完整代码

# @Author:青松
# 公众号:FasterAI
# Python, version 3.10.14
# Pytorch, version 2.3.0
# Chainlit, version 1.1.301

import chainlit as cl
from langchain.chains import create_history_aware_retriever
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.chains.retrieval import create_retrieval_chain
from langchain.memory import ChatMessageHistory
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceBgeEmbeddings
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.prompts import MessagesPlaceholder, ChatPromptTemplate
from langchain_core.runnables import RunnableConfig
from langchain_core.runnables.history import RunnableWithMessageHistory

import llm_util
from common import Constants

# 获取大模型实例
llm = llm_util.get_llm(Constants.MODEL_NAME['QianFan'])

# 获取文本嵌入模型
model_name = "BAAI/bge-small-zh"
encode_kwargs = {"normalize_embeddings": True}
embeddings_model = HuggingFaceBgeEmbeddings(
    model_name=model_name, encode_kwargs=encode_kwargs
)

# 配置文件分割器,每个块 1000 个token,重复100个
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)

# 管理聊天历史
store = {}


def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]


@cl.on_chat_start
async def on_chat_start():
    """ 监听会话开始事件 """

    session_id = "abc123"
    cl.user_session.set("session_id", session_id)

    await send_welcome_msg()

    files = None

    # 等待用户上传文件
    while files is None:
        files = await cl.AskFileMessage(
            content="Please upload a text file to begin!",
            accept=["text/plain"],
            max_size_mb=20,
            timeout=180,
        ).send()

    file = files[0]

    # 发送处理文件的消息
    msg = cl.Message(content=f"Processing `{file.name}`...", disable_feedback=True)
    await msg.send()

    with open(file.path, "r", encoding="utf-8") as f:
        text = f.read()

    # 将文件分割成文本块
    texts = text_splitter.split_text(text)

    # 为每个文本块添加元数据
    metadatas = [{"source": f"{i}-pl"} for i in range(len(texts))]

    # 使用异步方式创建 Chroma 向量数据库
    vectorstore = await cl.make_async(Chroma.from_texts)(
        texts, embeddings_model, metadatas=metadatas
    )

    # 将 Chroma 向量数据库转化为检索器
    retriever = vectorstore.as_retriever()

    # 通过上下文改写用户的问题
    contextualize_q_system_prompt = "给出下面的对话和一个后续问题,用原来的语言将后续问题改写为一个独立的问题。"
    contextualize_q_prompt = ChatPromptTemplate.from_messages(
        [
            ("system", contextualize_q_system_prompt),
            MessagesPlaceholder("chat_history"),
            ("human", "{input}"),
        ]
    )

    # 改写链:结合上下文改写用户问题
    history_aware_retriever = create_history_aware_retriever(
        llm, retriever, contextualize_q_prompt
    )

    # 根据参考内容回答用户的问题
    system_prompt = (
        "You are an assistant for question-answering tasks."
        "你需要使用给定的参考内容来回答用户的问题。如果你不知道答案,就说你不知道,不要试图编造答案。参考内容如下:"
        "\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链:将改写链和问答链合并成完整的RAG链
    rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)

    conversational_rag_chain = RunnableWithMessageHistory(
        rag_chain,
        get_session_history,
        input_messages_key="input",
        history_messages_key="chat_history",
        output_messages_key="answer",
    )

    # 在 rag_chain 中添加 chat_history
    cl.user_session.set("conversational_rag_chain", conversational_rag_chain)

    # 通知用户文件已处理完成,更新当前窗口的内容
    msg.content = f"Processing `{file.name}` done. You can now ask questions!"
    await msg.update()


@cl.on_message
async def on_message(message: cl.Message):
    """ 监听用户消息事件 """
    session_id = cl.user_session.get("session_id")
    conversational_rag_chain = cl.user_session.get("conversational_rag_chain")

    msg = cl.Message(content="")

    # 使用 session_id 用流式的方式响应用户问题
    chain = conversational_rag_chain.pick("answer")  # 只挑选 'answer' 属性输出
    async for chunk in chain.astream(
            {"input": message.content},
            config=RunnableConfig(
                configurable={"session_id": session_id},
                callbacks=[cl.LangchainCallbackHandler()])
    ):
        await msg.stream_token(chunk)

    await msg.send()


async def send_welcome_msg():
    image = cl.Image(url="https://qingsong-1257401904.cos.ap-nanjing.myqcloud.com/wecaht.png")

    # 发送一个图片
    await cl.Message(
        content="**青松** 邀你关注 **FasterAI**, 让每个人的 AI 学习之路走的更容易些!立刻扫码开启 AI 学习、面试快车道 **(^_^)** ",
        elements=[image],
    ).send()

【动手学 RAG】系列文章:


【动手部署大模型】系列文章:

本文由mdnice多平台发布

标签:实战,RAG,chain,cl,08,session,id,history
From: https://www.cnblogs.com/fasterai/p/18571471

相关文章

  • (分享源码)计算机毕业设计必看必学 上万套实战教程手把手教学JAVA、PHP,node.js,C++、pyth
    摘 要随着互联网大趋势的到来,社会的方方面面,各行各业都在考虑利用互联网作为媒介将自己的信息更及时有效地推广出去,而其中最好的方式就是建立网络管理系统,并对其进行信息管理。由于现在网络的发达,果园信息统计管理系统的信息通过网络进行信息管理掀起了热潮,所以针对果园信......
  • (分享源码)计算机毕业设计必看必学 上万套实战教程手把手教学JAVA、PHP,node.js,C++、pyth
    摘 要科技进步的飞速发展引起人们日常生活的巨大变化,电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流,人类发展的历史正进入一个新时代。在现实运用中,应用软件的工作规则和开发步骤,采用Java技术建设物......
  • 自动驾驶之心全套Carla-Autoware联合仿真实战
    网课学习的好处有哪些1、学生学习自主性强:当学生在面对电脑时,会有一种自己未来掌握在自己手中的感觉,所有的操作完全由自己掌控,真正发挥学习的主观能动性。资料地址https://pan.baidu.com/s/1GIsMJ9BGgjgbJCrsVNDi6A?pwd=2q492、学生非限性学习:网络学习的学生不受年龄的限制,同......
  • Go实战全家桶之二十:GO RPC CLIENT聚合
    packageclientimport("git.ichub.com/general/webcli120/goconfig/base/basedto""git.ichub.com/general/webcli120/goconfig/gogrpc/gorpcclient"proto"website-grpc/gorpc/proto/hello"websiteproto"website-......
  • 自动驾驶之心全套面向自动驾驶的C++实战教程(视频 答疑)
    网课学习的好处有哪些课程地址https://pan.baidu.com/s/1GIsMJ9BGgjgbJCrsVNDi6A?pwd=2q491、学生学习自主性强:当学生在面对电脑时,会有一种自己未来掌握在自己手中的感觉,所有的操作完全由自己掌控,真正发挥学习的主观能动性。2、学生非限性学习:网络学习的学生不受年龄的限制,同......
  • 实战部署若依项目-前后端分离
    第一步安装数据库安装MySQLyum -yinstall  mysql-server启动数据库并更改密码systemctl start  mysqldgrep 'password' /var/log/mysqld.logmysqladmin  -uroot  -p'初始密码'  password  '新密码'创建数据库,创建授权用户create databa......
  • 【RAG 项目实战 07】替换 ConversationalRetrievalChain(单轮问答)
    【RAG项目实战07】替换ConversationalRetrievalChain(单轮问答)NLPGithub项目:NLP项目实践:fasterai/nlp-project-practice介绍:该仓库围绕着NLP任务模型的设计、训练、优化、部署和应用,分享大模型算法工程师的日常工作和实战经验AI藏经阁:https://gitee.com/fasterai......
  • ABAP开发实战——SMARTFORMS输出字段长度问题
    再smartforms中需要给输出的字段分配所在单元格的长度和宽度,如果,输出长度都不够,数据就无法完全输出,此时可以通过增加输出时的高度,数据会自动换行继续输出,如图所示        同时,这里涉及到了数量的输出问题,数量如果是参考某个数据元素,那么就会有一个初始长度,这里举个例......
  • PyQt6 实战-时间管理应用
    这学期无聊选了个Python程序设计进阶,想去学点东西,大作业就是用pythonGUI库写个电脑端的时间管理应用。课上讲的是tkinter,感受了下,比较原始,以前写个C++的Qt的网络跳棋,那时候是Qt4,当时的代码完全是依托答辩,这次决定用PyQt6来写。希望能把项目的工程化做好,毕设应该最......
  • 金蝶Kingdee Wise ERP 12.3物理机迁移到Hyper-V(Windows Server 2008)
    一、系统迁移详细操作步骤1.数据虚拟化:使用Disk2vhd工具转换磁盘格式为VHD下载Disk2vhd工具:访问Sysinternals官方网站下载Disk2vhd工具,下载地址为:Disk2vhd下载链接。安装并打开Disk2vhd:解压下载的Disk2vhd.zip文件,运行Disk2vhd.exe程序。选择源磁盘并转换为VHD:在Dis......