概述
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"]
以上是本文的全部内容,感谢阅读。