LangChain入门3 基于历史对话的RAG构建

概述

LangChain 对历史对话进行 RAG 操作的一般步骤:

  • 加载和处理历史对话:首先,需要将历史对话加载到系统中。LangChain 可以使用 ChatMessageHistory 类来记录和处理所有先前的聊天互动。

  • 构建独立的检索问题:对于对话中的后续问题,LangChain 可以使用特定的提示模板(如 CONDENSE_QUESTION_PROMPT),将对话历史和最新问题结合起来,形成一个独立的检索问题。

  • 检索相关文档:使用检索器(Retriever)从存储中检索与当前问题和历史对话相关联的文档片段。LangChain 支持多种向量数据库和检索技术,如 Pinecone 或 Chroma。

  • 生成提示:将检索到的文档和问题结合起来,构建一个提示(Prompt),这个提示将被用来生成答案。LangChain 提供了 ChatPromptTemplate 等工具来帮助构建提示。

  • 使用语言模型生成答案:将构建好的提示传递给语言模型(如 OpenAI 的 GPT 模型),生成答案。LangChain 支持多种语言模型,并允许通过 ChatOpenAI 等类与它们交互。

  • 解析和输出答案:最后,LangChain 可以使用 StrOutputParser 或其他解析器来解析模型的输出,并将最终答案输出给用户。

  • 更新聊天历史:将当前的对话和生成的答案添加到聊天历史中,以便在下一次交互中使用。

代码实现

加载历史对话

首先,我们需要定义一个子链,该子链接受历史消息和最新的用户问题,如果该问题引用了历史信息中的任何信息,则需要重新表述该问题。

我们将使用一个提示,其中包含一个名为“chat_history”的MessagesPlaceholder变量。这允许我们使用“chat_history”输入键将消息列表传递给提示,这些消息将插入到系统消息之后和包含最新问题的人工消息之前。

注意,在这一步中使用了一个助手函数create_history_aware_retriever,它管理chat_history为空的情况,否则将按顺序应用prompt|llm|StrOutputParser()|retriever。

create_history_aware_retriever构建了一个链,该链接受密钥输入和chat_history作为输入,并具有与检索器相同的输出模式。

from langchain.chains import create_history_aware_retriever#创建一个历史对话系统
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder #提示词模版

contextualize_q_system_prompt = """Given a chat history and the latest user question \
which might reference context in the chat history, formulate a standalone question \
which can be understood without the chat history. Do NOT answer the question, \
just reformulate it if needed and otherwise return it as is."""

翻译:
提供聊天历史记录和最新的用户问题
可能参考聊天历史中的上下文,形成一个独立的问题
这可以在没有聊天历史的情况下理解。不要回答问题
如果需要,只需重新制定,否则就按原样返回。

#对话模版 添加了系统和真人
contextualize_q_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", contextualize_q_system_prompt),#加载之前的系统模版
        MessagesPlaceholder("chat_history"),#MessagesPlaceholder 被用来指定一个变量名为 "chat_history",这个变量名将在后续的对话中用来存储历史聊天消息
        ("human", "{input}"),
    ]
)

#创建历史对话巡回器
#加载 大模型,向量数据库,历史对话模版
history_aware_retriever = create_history_aware_retriever(
    llm, retriever, contextualize_q_prompt
)

查看具体的信息
在这里插入图片描述
这个链为检索器准备了输入查询的重新表述,以便检索包含对话的上下文。

构建完整QA链

from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain

qa_system_prompt = """You are an assistant for question-answering tasks. \
Use the following pieces of retrieved context to answer the question. \
If you don't know the answer, just say that you don't know. \
Use three sentences maximum and keep the answer concise.\

{context}"""

"""
你是回答问题的助理\
使用以下检索到的上下文来回答问题\
如果你不知道答案,就说你不知道\
最多使用三句话,并保持答案简明扼要\
"""


qa_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", qa_system_prompt),
        MessagesPlaceholder("chat_history"),
        ("human", "{input}"),
    ]
)

#create_stuff_documents_chain 它接受检索到的上下文以及会话历史和查询,以生成答案。
question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)

#这里附带了历史的设定
rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)
question_answer_chain

在这里插入图片描述

rag_chain

在这里插入图片描述

开始对话

from langchain_core.messages import HumanMessage

chat_history = []

question = "What is Task Decomposition?"
ai_msg_1 = rag_chain.invoke({
    
    "input": question, "chat_history": chat_history})
chat_history.extend([HumanMessage(content=question), ai_msg_1["answer"]])


second_question = "What are common ways of doing it?"
ai_msg_2 = rag_chain.invoke({
    
    "input": second_question, "chat_history": chat_history})
chat_history.extend([HumanMessage(content=question), ai_msg_2["answer"]])

print(ai_msg_2["answer"])

在这里插入图片描述
历史信息:

chat_history

在这里插入图片描述

检查引用的内容

for document in ai_msg_2["context"]:
    print(document)
    print()

这里只是一部分不多展示
在这里插入图片描述
在这里,我们已经介绍了如何添加用于合并历史输出的应用程序逻辑,但我们仍在手动更新聊天历史并将其插入每个输入中。在一个真正的问答应用程序中,我们想要某种方式来保持聊天历史记录,以及某种方式来自动插入和更新它。

为此,我们可以使用: BaseChatMessageHistory:存储聊天历史记录。 RunnableWithMessageHistory:LCEL链和BaseChatMessageHistory的包装器,用于处理将聊天历史注入输入并在每次调用后更新它。

代码合并

依赖加载与实例化

import bs4
from langchain.chains import create_history_aware_retriever, create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_chroma import Chroma
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_community.document_loaders import WebBaseLoader
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
#from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.llms import Ollama

llm = Ollama(model='llama2')
embeding = OllamaEmbeddings()

文档加载

loader = WebBaseLoader(
    web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
    bs_kwargs=dict(
        parse_only=bs4.SoupStrainer(
            class_=("post-content", "post-title", "post-header")
        )
    ),
)
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=embeding)
retriever = vectorstore.as_retriever()

将问题情境化

contextualize_q_system_prompt = """Given a chat history and the latest user question \
which might reference context in the chat history, formulate a standalone question \
which can be understood without the chat history. Do NOT answer the question, \
just reformulate it if needed and otherwise return it as is."""
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
)

回答提问

### Answer question ###
qa_system_prompt = """You are an assistant for question-answering tasks. \
Use the following pieces of retrieved context to answer the question. \
If you don't know the answer, just say that you don't know. \
Use three sentences maximum and keep the answer concise.\

{context}"""
qa_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", qa_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)

加载历史信息

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",
)

回答

回答1

conversational_rag_chain.invoke(
    {
    
    "input": "What is Task Decomposition?"},
    config={
    
    
        "configurable": {
    
    "session_id": "abc123"}
    },  # 在“store”中构造密钥“abc123”.
)["answer"]

返回:
在这里插入图片描述

回答2

conversational_rag_chain.invoke(
    {
    
    "input": "What are common ways of doing it?"},
    config={
    
    "configurable": {
    
    "session_id": "abc123"}},
)["answer"]

在这里插入图片描述

以上是本文的全部内容,感谢阅读。

猜你喜欢

转载自blog.csdn.net/weixin_41870426/article/details/138254348