type
status
date
slug
summary
tags
category
icon
password
comment
Status
在许多问答形式的应用程序中,允许用户进行多轮对话,这意味着应用程序需要记忆过去问题和答案,并且按照一定的方法将它们整合到当前对话中。
在本指南中,我们着重添加整合历史消息的逻辑。有关聊天历史管理的更多细节,请参阅这里。
我们将介绍两种方法:
- 链式方法,其中我们总是执行检索步骤;
- agent方法,在这种方法中,我们让LLM自行决定是否以及如何执行检索步骤(或多个步骤)。
对于外部知识来源,我们将使用同一篇文章,来自Lilian Weng的LLM动力自主代理博客,来自RAG教程。
设置
依赖
我们需要设置环境变量
OPENAI_API_KEY
,可以直接设置,也可以从.env
文件中加载,方法如下:LangSmith
使用LangChain构建的许多应用程序将都包含多个步骤和多次调用LLM调用。随着这些应用程序变得越来越复杂,能够检查chain或agent内部发生的细节变得至关重要。我们可以使用LangSmith查看应用程序的内部细节。
请注意,LangSmith并非必需,它只是在我们开发调试应用的时候非常有用。如果想使用可以在官网注册后申请秘钥,每个月都会有一定的免费使用额度,足够我们学习和测试,将key设置在的环境变量中就可以轻松使用LangSmith,
Chains
首先让我们看一下上一讲提到的问答应用,本地知识库的文章还是使用LLM Powered Autonomous Agents 这篇文章。
API 调用:create_retrieval_chain | create_stuff_documents_chain | WebBaseLoader | ChatPromptTemplate | OpenAIEmbeddings | RecursiveCharacterTextSplitter
我们使用了内置的chain构造函数
create_stuff_documents_chain
和create_retrieval_chain
, rag_chain
的组成成员:- retriever检索器;
- prompt提示词模板;
- LLM。
这将简化合并聊天记录的过程。
添加历史对话
上面我们构建的链是使用输入问题来检索知识库相关的上下文,但在对话环境中,用户的问题可能是基于对话的上下文才。例如:
Human: "What is Task Decomposition?"AI: "Task decomposition involves breaking down complex tasks into smaller and simpler steps to make them more manageable for an agent or model."Human: "What are common ways of doing it?"
为了理解第二个问题,我们的应用需要理解 "it" 代指的是 "Task Decomposition."
We'll need to update two things about our existing app:
在我们现有的应用代码中我们需要更改两块内容:
- Prompt: 更新我们的提示词模板,去支持历史消息作为输入。
- Contextualizing questions(将问题放在上下文中重新表述): 添加一个子链,它获取最新的用户问题,并将其放在聊天记录的上下文中重新表述。这可以简单地被看作是构建一个新的“历史对话”的检索器。
之前的流程:
query
->retriever
之后的流程:
(query, conversation history)
->LLM
->rephrased query
->retriever
将问题放在上下文中重新表述
首先,我们需要定义一个子链,它接收历史消息和最新用户问题,并且如果问题中涉及历史信息,就重新表述问题。
我们将需要传入一个包含名为“chat_history”的
MessagesPlaceholder
提示词模板变量。这样,我们可以使用“chat_history”输入键将消息列表传递给提示词模板,并且插入这些消息在系统消息之后和最新问题之前。我们在代码中使用了
create_history_aware_retriever
函数,组成的retriever链会依次调用 prompt | llm | StrOutputParser() | retriever
。调用链需要传入 input
和 chat_history
参数,他的输出形式与retriever
相同。这个链中,在执行本地知识库检索器的前面,添加根据历史对话重新生成的问题表述,以便检索过程能够整合对话的上下文。
现在我们可以建立完整的问答链。只需要更新检索器为我们的新
history_aware_retriever
。我们还是使用
create_stuff_documents_chain
来生成一个 question_answer_chain
,这个链其实只需要关注模型输入输出的内容,关于知识库具体的查询逻辑并不关心。需要传入的参数:知识库检索上下文 context
, 历史对话chat_history
和输入问题 input
。我们使用
create_retrieval_chain
方法构建最终的 rag_chain
。构建方法需要传入history_aware_retriever
和question_answer_chain
。调用rag_chain
时需要传入:问题input
和历史对话chat_history
,输出包括:问题input
、历史对话chat_history
、知识库检索到的上下文context
和最终回答 answer
。让我们尝试调用一下。下面我们提出一个问题和一个需要上下文的后续问题,看看是否能够返回正确的回答。
langsmith
聊天记录状态管理
上面我们介绍了如何向
rag_chain
中加入历史输出中,但我们仍然是手动更新聊天记录。在一个真正的问答应用程序中,我们希望有一种持久化聊天记录的方式,并且有一种自动插入和更新它的方式。这里我们可以使用:
- BaseChatMessageHistory: 保存聊天的历史记录
- RunnableWithMessageHistory: 将
BaseChatMessageHistory
包装成可以加入rag_chain
链中的对象,实现自动将聊天记录注入到提示词模板,并在每次调用后更新聊天记录。
要详细了解如何添加的历史消息并加入链中的,可以查看具体的官方文档How to add message history (memory)。
以下,我们使用将聊天记录存储在一个简单的字典
store
中。LangChain 也支持与Redis和其他技术的内存集成。接下来,我们定义一个
RunnableWithMessageHistory
的实例来帮助管理聊天记录。他需要一个配置参数,使用一个键(默认为“session_id”)来指定要获取的对话历史,并将其添加到输入的问题之前,同时将模型的输出追加到相同的对话历史中。我们可以查询一下字典:
整合

为了方便查看,我们整合一下上面的代码:
API 调用:create_history_aware_retriever | create_retrieval_chain | create_stuff_documents_chain | ChatMessageHistory | WebBaseLoader | BaseChatMessageHistory | ChatPromptTemplate | MessagesPlaceholder | RunnableWithMessageHistory | ChatOpenAI | OpenAIEmbeddings | RecursiveCharacterTextSplitter
上面我们搭建好了一个拥有记忆功能并检索本地知识库的应用,但是这个问答应用不管我们询问什么问题都会检索本地知识库,其实我们在实际的使用过程中可能只有某几个相关的问题才有必要使用本地知识库检索,那我们能不能让大模型自己判断回答我们的问题是不是需要执行检索?
Agents 智能体
Agents智能体 可以利用LLM的推理能力在执行过程中做出决策。使用智能体可以让我们在检索过程中由大模型自主判断是否需要检索。尽管大模型的行为与定义好的链相比更加不可预测,但是这也提供了一些优势。
- Agents智能体直接生成输入以供检索器使用,而不一定需要我们明确构建上下文;
- 智能体可以根据情况查询执行多个检索步骤,或完全不执行检索步骤(例如,响应用户的问候语)。
Retrieval tool 检索器工具
Agents 可以访问并使用工具,我们将上面的本地知识库检索器
retriever
封装成智能体可以使用的工具。API 调用:create_retriever_tool
定义好的工具实现了 Runnables 相关的接口,可以直接使用
invoke
等方法调用构造 Agent智能体
现在我们已经定义了工具和大型语言模型(LLM),可以创建智能体。我们使用LangGraph来构建这个代理。目前,我们直接使用一个高级接口来构建智能体,但LangGraph的优势在于,这个高级接口背后有一个低级的、高度可控制的API,以便在需要时可以修改代理的逻辑。
LangGraph自带持久性功能,所以不需要使用
ChatMessageHistory
。我们可以直接将一个检查点器( checkpointer
)传递给我们的LangGraph代理。我们尝试执行一下agent,如果我们输入的查询不需要执行检索步骤,那么智能体就不会执行检索步骤。
此外,如果我们输入的查询需要执行检索步骤,agent将生成输入到工具中。
在上面的例子中,agent 并没有直接将我们的查询原封不动地插入到工具中,而是去除了像“what”和“is”这样的不必要的词汇。
这一相同的原则允许代理在需要时利用对话的上下文。
请注意,agent能够推断出我们查询中的“它”指的是“任务分解”,并因此生成了一个合理的搜索查询——在这个例子中是“任务分解的常见方法”。
langsmith
整合
API 调用:AgentExecutor | create_tool_calling_agent | create_retriever_tool | ChatMessageHistory | WebBaseLoader | BaseChatMessageHistory | ChatPromptTemplate | MessagesPlaceholder | RunnableWithMessageHistory | ChatOpenAI | OpenAIEmbeddings | RecursiveCharacterTextSplitter
总结
在本文中,在RAG链式调用的基础上添加了整合历史消息的逻辑。然后构建了一个能够自主决定是否调用知识库查询工具的agent智能体。
官网地址:
- Author:小灰
- URL:https://blog.opencontent.cn//article/langchain-chat-history
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!