内存¶
什么是内存?¶
在大型语言模型和 AI 应用的语境下,内存指的是处理、保留和利用来自过去交互或数据源的信息的能力。例如:
- 管理发送到聊天模型的消息(例如,来自长消息历史记录),以限制令牌使用量
- 总结过去的对话,以提供来自先前交互的上下文
- 选择少样本示例(例如,来自数据集),以指导模型响应
- 在多个聊天会话中维护持久数据(例如,用户偏好)
- 允许大型语言模型使用过去的信息更新其提示(例如,元提示)
- 从长期存储系统中检索与对话或问题相关的的信息
下面,我们将详细讨论这些示例。
管理消息¶
编辑消息列表¶
聊天模型通过 消息 接受指令,这些指令可以是通用指令(例如,系统消息)或用户提供的指令(例如,人机消息)。在聊天应用中,消息通常在人类输入和模型响应之间交替出现,并随着时间的推移积累成一个列表。由于上下文窗口有限,并且令牌丰富的消息列表可能很昂贵,因此许多应用可以从主动管理消息的方法中受益。
最直接的方法是从列表中删除特定消息。可以使用 RemoveMessage 根据消息 id
(每个消息的唯一标识符)来完成此操作。在下面的示例中,我们只保留列表中的最后两条消息,使用 RemoveMessage
根据它们的 id
删除旧的消息。
from langchain_core.messages import RemoveMessage
# Message list
messages = [AIMessage("Hi.", name="Bot", id="1")]
messages.append(HumanMessage("Hi.", name="Lance", id="2"))
messages.append(AIMessage("So you said you were researching ocean mammals?", name="Bot", id="3"))
messages.append(HumanMessage("Yes, I know about whales. But what others should I learn about?", name="Lance", id="4"))
# Isolate messages to delete
delete_messages = [RemoveMessage(id=m.id) for m in messages[:-2]]
print(delete_messages)
[RemoveMessage(content='', id='1'), RemoveMessage(content='', id='2')]
由于聊天模型的上下文窗口以令牌表示,因此根据我们要保留的令牌数量来修剪消息列表可能很有用。为此,我们可以使用 trim_messages
并指定要从列表中保留的令牌数量,以及 strategy
(例如,保留最后的 max_tokens
)。
from langchain_core.messages import trim_messages
trim_messages(
messages,
# Keep the last <= n_count tokens of the messages.
strategy="last",
# Remember to adjust based on your model
# or else pass a custom token_encoder
token_counter=ChatOpenAI(model="gpt-4o"),
# Most chat models expect that chat history starts with either:
# (1) a HumanMessage or
# (2) a SystemMessage followed by a HumanMessage
# Remember to adjust based on the desired conversation
# length
max_tokens=45,
# Most chat models expect that chat history starts with either:
# (1) a HumanMessage or
# (2) a SystemMessage followed by a HumanMessage
start_on="human",
# Most chat models expect that chat history ends with either:
# (1) a HumanMessage or
# (2) a ToolMessage
end_on=("human", "tool"),
# Usually, we want to keep the SystemMessage
# if it's present in the original history.
# The SystemMessage has special instructions for the model.
include_system=True,
)
与 LangGraph 的使用¶
在 LangGraph 中构建代理时,我们通常希望管理图状态中的消息列表。由于这是一种非常常见的用例,MessagesState 是一个内置的 LangGraph 状态模式,它包含一个 messages
键,该键是一个消息列表。MessagesState
还包含一个 add_messages
归约器,用于在应用程序运行时更新消息列表。add_messages
归约器允许我们 追加 新的消息到 messages
状态键中,如下所示。当我们使用从 my_node
返回的 {"messages": new_message}
执行状态更新时,add_messages
归约器会将 new_message
追加到现有的消息列表中。
def my_node(state: State):
# Add a new message to the state
new_message = HumanMessage(content="message")
return {"messages": new_message}
add_messages
归约器内置在 MessagesState
中,也可以与我们上面讨论的 RemoveMessage
实用程序一起使用。在这种情况下,我们可以使用 delete_messages
列表执行状态更新,以从 messages
列表中删除特定消息。
def my_node(state: State):
# Delete messages from state
delete_messages = [RemoveMessage(id=m.id) for m in state['messages'][:-2]]
return {"messages": delete_messages}
请参阅此操作指南 指南 和我们 LangChain Academy 课程中的模块 2,了解示例用法。
总结过去的对话¶
上面显示的修剪或删除消息的问题在于,我们可能会因剔除消息队列而丢失信息。因此,一些应用受益于更复杂的方法,即使用聊天模型对消息历史记录进行总结。
简单的提示和编排逻辑可以用来实现这一点。例如,在 LangGraph 中,我们可以扩展 MessagesState 以包含 summary
键。
然后,我们可以使用任何现有的摘要作为下一个摘要的上下文,生成聊天历史记录的摘要。这个 summarize_conversation
节点可以在 messages
状态键中积累一定数量的消息后调用。
def summarize_conversation(state: State):
# First, we get any existing summary
summary = state.get("summary", "")
# Create our summarization prompt
if summary:
# A summary already exists
summary_message = (
f"This is summary of the conversation to date: {summary}\n\n"
"Extend the summary by taking into account the new messages above:"
)
else:
summary_message = "Create a summary of the conversation above:"
# Add prompt to our history
messages = state["messages"] + [HumanMessage(content=summary_message)]
response = model.invoke(messages)
# Delete all but the 2 most recent messages
delete_messages = [RemoveMessage(id=m.id) for m in state["messages"][:-2]]
return {"summary": response.content, "messages": delete_messages}
请参阅此操作指南 此处 和我们 LangChain Academy 课程中的模块 2,了解示例用法。
少样本示例¶
少样本学习是一种强大的技术,大型语言模型可以通过在提示中 "编程" 输入-输出示例来执行各种任务。虽然各种 最佳实践 可用于生成少样本示例,但挑战通常在于根据用户输入选择最相关的示例。
LangChain ExampleSelectors
可用于使用诸如长度、语义相似性、语义 n 元组重叠或最大边际相关性等标准来自动定制少样本示例选择。
如果少样本示例存储在 LangSmith 数据集 中,那么可以使用开箱即用的动态少样本示例选择器来实现相同的目标。LangSmith 会为您索引数据集,并允许您检索与用户输入最相关的少样本示例(使用类似 BM25 的算法 进行基于关键字的相似性)。
请参阅此操作指南 视频,了解 LangSmith 中动态少样本示例选择的示例用法。此外,请参阅此 博客文章,展示了用于提高工具调用性能的少样本提示,以及此 博客文章,它使用少样本示例将大型语言模型与人类偏好相一致。
跨聊天会话维护数据¶
LangGraph 的 持久性层 具有利用各种存储系统的检查点,包括内存中的键值存储或不同的数据库。这些检查点在每个执行步骤中捕获图状态,并在线程中累积,可以使用线程 ID 在稍后访问,以恢复先前的图执行。我们通过将检查点传递给 compile
方法,将持久性添加到我们的图中,如下所示。
# Compile the graph with a checkpointer
checkpointer = MemorySaver()
graph = workflow.compile(checkpointer=checkpointer)
# Invoke the graph with a thread ID
config = {"configurable": {"thread_id": "1"}}
graph.invoke(input_state, config)
# get the latest state snapshot at a later time
config = {"configurable": {"thread_id": "1"}}
graph.get_state(config)
持久性对于维持长时间运行的聊天会话至关重要。例如,用户与 AI 助理之间的聊天可能会被打断。持久性确保用户可以在任何时候继续该特定聊天会话。但是,如果用户开始与助理进行新的聊天会话会发生什么?这会生成一个新线程,并且先前会话(线程)的信息不会保留。这促使我们需要一种能够跨聊天会话(线程)维护数据的内存服务。
元提示¶
元提示使用大型语言模型来生成或改进其提示或指令。这种方法允许系统动态更新和改进其行为,从而可能在各种任务上实现更好的性能。这对于指令难以先验指定的任务尤其有用。
元提示可以使用过去的信息来更新提示。例如,这个 推文生成器 使用元提示来迭代地改进用于生成高质量的论文摘要以供 Twitter 使用的摘要提示。在这种情况下,我们使用 LangSmith 数据集来存储我们想要总结的几篇论文,使用简单的摘要提示生成摘要,手动审查摘要,使用 LangSmith 注释队列捕获来自人类审查的反馈,并将此反馈传递给聊天模型以重新生成摘要提示。该过程在循环中重复,直到摘要在人工审查中满足我们的标准。
从长期存储中检索相关信息¶
跨越许多不同内存用例的中心挑战可以简单地总结为:如何从长期存储系统中检索相关信息并将其传递给聊天模型?例如,假设我们有一个系统,它存储了大量关于用户的信息,但用户提出了一个与餐厅推荐相关的特定问题。如果我们将所有个人用户信息简单地提取出来并传递给聊天模型,那将是成本高昂的。相反,我们希望仅提取与用户当前聊天交互最相关的的信息(例如,食物偏好、位置等),并将其传递给聊天模型。
检索领域有很多工作旨在解决这一挑战。请参阅我们专注于 RAG(检索增强生成) 的教程,我们关于 检索 的概念文档,以及我们关于此主题的 开源存储库 以及 视频。