如何添加对话历史摘要¶
持久化的最常见用例之一是使用它来跟踪对话历史记录。这很棒——它使继续对话变得容易。但是,随着对话越来越长,对话历史记录会不断积累,并占用越来越多的上下文窗口。这通常是不希望的,因为它会导致更昂贵、更长的 LLM 调用,并可能导致错误。解决此问题的一种方法是创建到目前为止的对话摘要,并将其与过去 N 条消息一起使用。本指南将介绍如何实现这一点的示例。
这将涉及几个步骤
- 检查对话是否太长(可以通过检查消息数量或消息长度来完成)
- 如果是,则创建摘要(需要一个提示)
- 然后删除除最后 N 条消息之外的所有消息
这其中很大一部分是删除旧消息。有关如何执行此操作的深入指南,请参见 本指南
设置¶
首先,让我们设置我们要使用的软件包
在 [ ]
已复制!
%%capture --no-stderr
%pip install --quiet -U langgraph langchain_anthropic
%%capture --no-stderr %pip install --quiet -U langgraph langchain_anthropic
接下来,我们需要为 Anthropic(我们将使用的 LLM)设置 API 密钥
在 [2]
已复制!
import getpass
import os
def _set_env(var: str):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"{var}: ")
_set_env("ANTHROPIC_API_KEY")
import getpass import os def _set_env(var: str): if not os.environ.get(var): os.environ[var] = getpass.getpass(f"{var}: ") _set_env("ANTHROPIC_API_KEY")
构建聊天机器人¶
现在让我们构建聊天机器人。
在 [25]
已复制!
from typing import Literal
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import SystemMessage, RemoveMessage
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import MessagesState, StateGraph, START, END
memory = MemorySaver()
# We will add a `summary` attribute (in addition to `messages` key,
# which MessagesState already has)
class State(MessagesState):
summary: str
# We will use this model for both the conversation and the summarization
model = ChatAnthropic(model_name="claude-3-haiku-20240307")
# Define the logic to call the model
def call_model(state: State):
# If a summary exists, we add this in as a system message
summary = state.get("summary", "")
if summary:
system_message = f"Summary of conversation earlier: {summary}"
messages = [SystemMessage(content=system_message)] + state["messages"]
else:
messages = state["messages"]
response = model.invoke(messages)
# We return a list, because this will get added to the existing list
return {"messages": [response]}
# We now define the logic for determining whether to end or summarize the conversation
def should_continue(state: State) -> Literal["summarize_conversation", END]:
"""Return the next node to execute."""
messages = state["messages"]
# If there are more than six messages, then we summarize the conversation
if len(messages) > 6:
return "summarize_conversation"
# Otherwise we can just end
return END
def summarize_conversation(state: State):
# First, we summarize the conversation
summary = state.get("summary", "")
if summary:
# If a summary already exists, we use a different system prompt
# to summarize it than if one didn't
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:"
messages = state["messages"] + [HumanMessage(content=summary_message)]
response = model.invoke(messages)
# We now need to delete messages that we no longer want to show up
# I will delete all but the last two messages, but you can change this
delete_messages = [RemoveMessage(id=m.id) for m in state["messages"][:-2]]
return {"summary": response.content, "messages": delete_messages}
# Define a new graph
workflow = StateGraph(State)
# Define the conversation node and the summarize node
workflow.add_node("conversation", call_model)
workflow.add_node(summarize_conversation)
# Set the entrypoint as conversation
workflow.add_edge(START, "conversation")
# We now add a conditional edge
workflow.add_conditional_edges(
# First, we define the start node. We use `conversation`.
# This means these are the edges taken after the `conversation` node is called.
"conversation",
# Next, we pass in the function that will determine which node is called next.
should_continue,
)
# We now add a normal edge from `summarize_conversation` to END.
# This means that after `summarize_conversation` is called, we end.
workflow.add_edge("summarize_conversation", END)
# Finally, we compile it!
app = workflow.compile(checkpointer=memory)
from typing import Literal from langchain_anthropic import ChatAnthropic from langchain_core.messages import SystemMessage, RemoveMessage from langgraph.checkpoint.memory import MemorySaver from langgraph.graph import MessagesState, StateGraph, START, END memory = MemorySaver() # 我们将添加一个 `summary` 属性(除了 `messages` 键之外,# MessagesState 已经有了) class State(MessagesState): summary: str # 我们将使用此模型作为对话和摘要模型 model = ChatAnthropic(model_name="claude-3-haiku-20240307") # 定义调用模型的逻辑 def call_model(state: State): # 如果存在摘要,我们将它作为系统消息添加 summary = state.get("summary", "") if summary: system_message = f"Summary of conversation earlier: {summary}" messages = [SystemMessage(content=system_message)] + state["messages"] else: messages = state["messages"] response = model.invoke(messages) # 我们返回一个列表,因为它将被添加到现有列表中 return {"messages": [response]} # 我们现在定义了确定是否结束或总结对话的逻辑 def should_continue(state: State) -> Literal["summarize_conversation", END]: """返回要执行的下一个节点。""" messages = state["messages"] # 如果有超过六条消息,那么我们总结对话 if len(messages) > 6: return "summarize_conversation" # 否则我们可以直接结束 return END def summarize_conversation(state: State): # 首先,我们总结对话 summary = state.get("summary", "") if summary: # 如果已经存在摘要,我们将使用不同的系统提示 # 来总结它,而不是没有摘要的情况 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:" messages = state["messages"] + [HumanMessage(content=summary_message)] response = model.invoke(messages) # 我们现在需要删除不再希望显示的消息 # 我将删除除了最后两条消息之外的所有消息,但您可以更改此操作 delete_messages = [RemoveMessage(id=m.id) for m in state["messages"][:-2]] return {"summary": response.content, "messages": delete_messages} # 定义一个新的图表 workflow = StateGraph(State) # 定义对话节点和总结节点 workflow.add_node("conversation", call_model) workflow.add_node(summarize_conversation) # 将入口点设置为对话 workflow.add_edge(START, "conversation") # 我们现在添加一个条件边 workflow.add_conditional_edges( # 首先,我们定义开始节点。我们使用 `conversation`。 # 也就是说,这些是在调用 `conversation` 节点后采取的边。 "conversation", # 接下来,我们传入一个函数来确定要调用的下一个节点。 should_continue, ) # 我们现在从 `summarize_conversation` 添加一条正常边到 END。 # 也就是说,在调用 `summarize_conversation` 之后,我们将结束。 workflow.add_edge("summarize_conversation", END) # 最后,我们编译它! app = workflow.compile(checkpointer=memory)
使用图表¶
在 [26]
已复制!
def print_update(update):
for k, v in update.items():
for m in v["messages"]:
m.pretty_print()
if "summary" in v:
print(v["summary"])
def print_update(update): for k, v in update.items(): for m in v["messages"]: m.pretty_print() if "summary" in v: print(v["summary"])
在 [27]
已复制!
from langchain_core.messages import HumanMessage
config = {"configurable": {"thread_id": "4"}}
input_message = HumanMessage(content="hi! I'm bob")
input_message.pretty_print()
for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"):
print_update(event)
input_message = HumanMessage(content="what's my name?")
input_message.pretty_print()
for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"):
print_update(event)
input_message = HumanMessage(content="i like the celtics!")
input_message.pretty_print()
for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"):
print_update(event)
from langchain_core.messages import HumanMessage config = {"configurable": {"thread_id": "4"}} input_message = HumanMessage(content="hi! I'm bob") input_message.pretty_print() for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"): print_update(event) input_message = HumanMessage(content="what's my name?") input_message.pretty_print() for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"): print_update(event) input_message = HumanMessage(content="i like the celtics!") input_message.pretty_print() for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"): print_update(event)
================================ Human Message ================================= hi! I'm bob ================================== Ai Message ================================== It's nice to meet you, Bob! I'm an AI assistant created by Anthropic. How can I help you today? ================================ Human Message ================================= what's my name? ================================== Ai Message ================================== Your name is Bob, as you told me at the beginning of our conversation. ================================ Human Message ================================= i like the celtics! ================================== Ai Message ================================== That's great, the Celtics are a fun team to follow! Basketball is an exciting sport. Do you have a favorite Celtics player or a favorite moment from a Celtics game you've watched? I'd be happy to discuss the team and the sport with you.
我们可以看到,到目前为止还没有发生总结——这是因为列表中只有六条消息。
在 [28]
已复制!
values = app.get_state(config).values
values
values = app.get_state(config).values values
Out[28]
{'messages': [HumanMessage(content="hi! I'm bob", id='6534853d-b8a7-44b9-837b-eb7abaf7ebf7'), AIMessage(content="It's nice to meet you, Bob! I'm an AI assistant created by Anthropic. How can I help you today?", response_metadata={'id': 'msg_015wCFew2vwMQJcpUh2VZ5ah', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 12, 'output_tokens': 30}}, id='run-0d33008b-1094-4f5e-94ce-293283fc3024-0'), HumanMessage(content="what's my name?", id='0a4f203a-b95a-42a9-b1c5-bb20f68b3251'), AIMessage(content='Your name is Bob, as you told me at the beginning of our conversation.', response_metadata={'id': 'msg_01PLp8wg2xDsJbNR9uCtxcGz', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 50, 'output_tokens': 19}}, id='run-3815dd4d-ee0c-4fc2-9889-f6dd40325961-0'), HumanMessage(content='i like the celtics!', id='ac128172-42d1-4390-b7cc-7bcb2d22ee48'), AIMessage(content="That's great, the Celtics are a fun team to follow! Basketball is an exciting sport. Do you have a favorite Celtics player or a favorite moment from a Celtics game you've watched? I'd be happy to discuss the team and the sport with you.", response_metadata={'id': 'msg_01CSg5avZEx6CKcZsSvSVXpr', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 78, 'output_tokens': 61}}, id='run-698faa28-0f72-495f-8ebe-e948664d2200-0')]}
现在让我们发送另一条消息
在 [29]
已复制!
input_message = HumanMessage(content="i like how much they win")
input_message.pretty_print()
for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"):
print_update(event)
input_message = HumanMessage(content="i like how much they win") input_message.pretty_print() for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"): print_update(event)
================================ Human Message ================================= i like how much they win ================================== Ai Message ================================== That's understandable, the Celtics have been one of the more successful NBA franchises over the years. Their history of winning championships is very impressive. It's always fun to follow a team that regularly competes for titles. What do you think has been the key to the Celtics' sustained success? Is there a particular era or team that stands out as your favorite? ================================ Remove Message ================================ ================================ Remove Message ================================ ================================ Remove Message ================================ ================================ Remove Message ================================ ================================ Remove Message ================================ ================================ Remove Message ================================ Here is a summary of our conversation so far: - You introduced yourself as Bob and said you like the Boston Celtics basketball team. - I acknowledged that it's nice to meet you, Bob, and noted that you had shared your name earlier in the conversation. - You expressed that you like how much the Celtics win, and I agreed that their history of sustained success and championship pedigree is impressive. - I asked if you have a favorite Celtics player or moment that stands out to you, and invited further discussion about the team and the sport of basketball. - The overall tone has been friendly and conversational, with me trying to engage with your interest in the Celtics by asking follow-up questions.
如果我们现在检查状态,我们可以看到我们有对话摘要,以及最后两条消息
在 [30]
已复制!
values = app.get_state(config).values
values
values = app.get_state(config).values values
Out[30]
{'messages': [HumanMessage(content='i like how much they win', id='bb916ce7-534c-4d48-9f92-e269f9dc4859'), AIMessage(content="That's understandable, the Celtics have been one of the more successful NBA franchises over the years. Their history of winning championships is very impressive. It's always fun to follow a team that regularly competes for titles. What do you think has been the key to the Celtics' sustained success? Is there a particular era or team that stands out as your favorite?", response_metadata={'id': 'msg_01B7TMagaM8xBnYXLSMwUDAG', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 148, 'output_tokens': 82}}, id='run-c5aa9a8f-7983-4a7f-9c1e-0c0055334ac1-0')], 'summary': "Here is a summary of our conversation so far:\n\n- You introduced yourself as Bob and said you like the Boston Celtics basketball team.\n- I acknowledged that it's nice to meet you, Bob, and noted that you had shared your name earlier in the conversation.\n- You expressed that you like how much the Celtics win, and I agreed that their history of sustained success and championship pedigree is impressive.\n- I asked if you have a favorite Celtics player or moment that stands out to you, and invited further discussion about the team and the sport of basketball.\n- The overall tone has been friendly and conversational, with me trying to engage with your interest in the Celtics by asking follow-up questions."}
我们现在可以继续对话了!请注意,即使我们只有最后两条消息,我们仍然可以询问它关于对话中较早提及的内容的问题(因为我们对它们进行了总结)
在 [31]
已复制!
input_message = HumanMessage(content="what's my name?")
input_message.pretty_print()
for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"):
print_update(event)
input_message = HumanMessage(content="what's my name?") input_message.pretty_print() for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"): print_update(event)
================================ Human Message ================================= what's my name? ================================== Ai Message ================================== In our conversation so far, you introduced yourself as Bob. I acknowledged that earlier when you had shared your name.
在 [32]
已复制!
input_message = HumanMessage(content="what NFL team do you think I like?")
input_message.pretty_print()
for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"):
print_update(event)
input_message = HumanMessage(content="what NFL team do you think I like?") input_message.pretty_print() for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"): print_update(event)
================================ Human Message ================================= what NFL team do you think I like? ================================== Ai Message ================================== I don't actually have any information about what NFL team you might like. In our conversation so far, you've only mentioned that you're a fan of the Boston Celtics basketball team. I don't have any prior knowledge about your preferences for NFL teams. Unless you provide me with that information, I don't have a basis to guess which NFL team you might be a fan of.
在 [33]
已复制!
input_message = HumanMessage(content="i like the patriots!")
input_message.pretty_print()
for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"):
print_update(event)
input_message = HumanMessage(content="i like the patriots!") input_message.pretty_print() for event in app.stream({"messages": [input_message]}, config, stream_mode="updates"): print_update(event)
================================ Human Message ================================= i like the patriots! ================================== Ai Message ================================== Okay, got it! Thanks for sharing that you're also a fan of the New England Patriots in the NFL. That makes sense, given your interest in other Boston sports teams like the Celtics. The Patriots have also had a very successful run over the past couple of decades, winning multiple Super Bowls. It's fun to follow winning franchises like the Celtics and Patriots. Do you have a favorite Patriots player or moment that stands out to you? ================================ Remove Message ================================ ================================ Remove Message ================================ ================================ Remove Message ================================ ================================ Remove Message ================================ ================================ Remove Message ================================ ================================ Remove Message ================================ Okay, extending the summary with the new information: - You initially introduced yourself as Bob and said you like the Boston Celtics basketball team. - I acknowledged that and we discussed your appreciation for the Celtics' history of winning. - You then asked what your name was, and I reminded you that you had introduced yourself as Bob earlier in the conversation. - You followed up by asking what NFL team I thought you might like, and I explained that I didn't have any prior information about your NFL team preferences. - You then revealed that you are also a fan of the New England Patriots, which made sense given your Celtics fandom. - I responded positively to this new information, noting the Patriots' own impressive success and dynasty over the past couple of decades. - I then asked if you have a particular favorite Patriots player or moment that stands out to you, continuing the friendly, conversational tone. Overall, the discussion has focused on your sports team preferences, with you sharing that you are a fan of both the Celtics and the Patriots. I've tried to engage with your interests and ask follow-up questions to keep the dialogue flowing.