Skip to main content

Add message history (memory)

RunnableWithMessageHistory 类让我们可以向某些链中添加消息历史记录。它包装另一个 Runnable 并管理其聊天消息历史记录。

具体来说,它可用于任何接受其中之一作为输入的Runnable

  • 一系列的基本消息
  • 一个包含以序列形式接受 BaseMessage 的键的字典。
  • 一个包含一个将最新消息作为字符串或序列的键的字典,以及一个专门用于保存历史消息的键。

并且输出其中之一

  • 一个可以视为 AIMessage 内容的字符串。
  • 一系列的 基本信息
  • 一个包含一系列 BaseMessage 的键的字典

让我们看几个例子,看看它是如何工作的。首先,我们构建一个可运行的对象(此处接受字典作为输入并返回消息作为输出):

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai.chat_models import ChatOpenAI

model = ChatOpenAI()
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You're an assistant who's good at {ability}. Respond in 20 words or fewer",
),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
]
)
runnable = prompt | model

管理消息历史记录,我们需要:1. 这个可运行项;2. 一个返回 BaseChatMessageHistory 实例的可调用项。

请查看内存集成页面,使用 Redis 和其他提供程序实现聊天消息历史记录的方法。在这里,我们展示使用内存中的 ChatMessageHistory,以及使用 RedisChatMessageHistory 更持久的存储。

In-memory

下面我们展示一个简单的例子,在这个例子中,聊天记录存储在内存中,通过一个全局的 Python 字典实现。

我们构建一个可调用的 get_session_history 函数,引用这个字典 来返回一个 ChatMessageHistory 的实例。可以通过在运行时向 RunnableWithMessageHistory 传递配置来指定可调用的参数。默认情况下,配置参数预期为一个单字符串 session_id 。可以通过 history_factory_config 关键字参数进行调整。

使用单参数默认值:

from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

store = {}


def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]


with_message_history = RunnableWithMessageHistory(
runnable,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)

请注意,我们已经指定了 `input_messages_key`(要视为最新输入消息的键)和 `history_messages_key`(用来添加历史消息的键)。

在调用这个新的可运行对象时,我们通过配置参数指定对应的聊天记录。

with_message_history.invoke(
{"ability": "math", "input": "What does cosine mean?"},
config={"configurable": {"session_id": "abc123"}},
)
AIMessage(content='Cosine is a trigonometric function that calculates the ratio of the adjacent side to the hypotenuse of a right triangle.')
# Remembers
with_message_history.invoke(
{"ability": "math", "input": "What?"},
config={"configurable": {"session_id": "abc123"}},
)
AIMessage(content='Cosine is a mathematical function used to calculate the length of a side in a right triangle.')
# New session_id --> does not remember.
with_message_history.invoke(
{"ability": "math", "input": "What?"},
config={"configurable": {"session_id": "def234"}},
)
AIMessage(content='I can help with math problems. What do you need assistance with?')

我们跟踪消息历史记录的配置参数可以通过向 history_factory_config 参数传递 ConfigurableFieldSpec 对象列表来自定义。在下面,我们使用两个参数:一个 user_id 和一个 conversation_id。

from langchain_core.runnables import ConfigurableFieldSpec

store = {}


def get_session_history(user_id: str, conversation_id: str) -> BaseChatMessageHistory:
if (user_id, conversation_id) not in store:
store[(user_id, conversation_id)] = ChatMessageHistory()
return store[(user_id, conversation_id)]


with_message_history = RunnableWithMessageHistory(
runnable,
get_session_history,
input_messages_key="input",
history_messages_key="history",
history_factory_config=[
ConfigurableFieldSpec(
id="user_id",
annotation=str,
name="User ID",
description="Unique identifier for the user.",
default="",
is_shared=True,
),
ConfigurableFieldSpec(
id="conversation_id",
annotation=str,
name="Conversation ID",
description="Unique identifier for the conversation.",
default="",
is_shared=True,
),
],
)
with_message_history.invoke(
{"ability": "math", "input": "Hello"},
config={"configurable": {"user_id": "123", "conversation_id": "1"}},
)

Examples with runnables of different signatures

以上可运行的代码以字典作为输入,并返回一个BaseMessage。下面我们展示一些替代方案。

Messages input, dict output

from langchain_core.messages import HumanMessage
from langchain_core.runnables import RunnableParallel

chain = RunnableParallel({"output_message": ChatOpenAI()})


def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]


with_message_history = RunnableWithMessageHistory(
chain,
get_session_history,
output_messages_key="output_message",
)

with_message_history.invoke(
[HumanMessage(content="What did Simone de Beauvoir believe about free will")],
config={"configurable": {"session_id": "baz"}},
)
{'output_message': AIMessage(content="Simone de Beauvoir believed in the existence of free will. She argued that individuals have the ability to make choices and determine their own actions, even in the face of social and cultural constraints. She rejected the idea that individuals are purely products of their environment or predetermined by biology or destiny. Instead, she emphasized the importance of personal responsibility and the need for individuals to actively engage in creating their own lives and defining their own existence. De Beauvoir believed that freedom and agency come from recognizing one's own freedom and actively exercising it in the pursuit of personal and collective liberation.")}
with_message_history.invoke(
[HumanMessage(content="How did this compare to Sartre")],
config={"configurable": {"session_id": "baz"}},
)
{'output_message': AIMessage(content='Simone de Beauvoir\'s views on free will were closely aligned with those of her contemporary and partner Jean-Paul Sartre. Both de Beauvoir and Sartre were existentialist philosophers who emphasized the importance of individual freedom and the rejection of determinism. They believed that human beings have the capacity to transcend their circumstances and create their own meaning and values.\n\nSartre, in his famous work "Being and Nothingness," argued that human beings are condemned to be free, meaning that we are burdened with the responsibility of making choices and defining ourselves in a world that lacks inherent meaning. Like de Beauvoir, Sartre believed that individuals have the ability to exercise their freedom and make choices in the face of external and internal constraints.\n\nWhile there may be some nuanced differences in their philosophical writings, overall, de Beauvoir and Sartre shared a similar belief in the existence of free will and the importance of individual agency in shaping one\'s own life.')}

Messages input, messages output

RunnableWithMessageHistory(
ChatOpenAI(),
get_session_history,
)

Dict with single key for all messages input, messages output

from operator import itemgetter

RunnableWithMessageHistory(
itemgetter("input_messages") | ChatOpenAI(),
get_session_history,
input_messages_key="input_messages",
)

Persistent storage

在许多情况下,保留对话历史是首选。RunnableWithMessageHistory 不关心 get_session_history 可调用器以何种方式检索其聊天消息历史记录。请参见这里以查看一个使用本地文件系统的示例。下面我们演示如何使用 Redis。查看 memory integrations 页面以了解使用其他提供商实现聊天消息历史记录的方法。

Setup

我们需要安装 Redis,如果尚未安装:

%pip install --upgrade --quiet redis

如果我们没有现有的 Redis 部署可连接,则启动本地 Redis Stack 服务器。

docker run -d -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
REDIS_URL = "redis://localhost:6379/0"

LangSmith

LangSmith对于像消息历史记录注入这样的操作尤其有用,否则很难理解链条中各部分的输入是什么。

请注意,LangSmith 不是必需的,但它是有帮助的。如果您想使用 LangSmith,在上面的链接注册后,请确保取消下面的注释,并设置您的环境变量开始记录跟踪。

# os.environ["LANGCHAIN_TRACING_V2"] = "true"
# os.environ["LANGCHAIN_API_KEY"] = getpass.getpass()

更新消息历史记录的实现只需要我们定义一个新的可调用对象,这次返回一个 RedisChatMessageHistory 的实例:

from langchain_community.chat_message_histories import RedisChatMessageHistory


def get_message_history(session_id: str) -> RedisChatMessageHistory:
return RedisChatMessageHistory(session_id, url=REDIS_URL)


with_message_history = RunnableWithMessageHistory(
runnable,
get_message_history,
input_messages_key="input",
history_messages_key="history",
)

我们可以像以前一样调用:

with_message_history.invoke(
{"ability": "math", "input": "What does cosine mean?"},
config={"configurable": {"session_id": "foobar"}},
)
AIMessage(content='Cosine is a trigonometric function that represents the ratio of the adjacent side to the hypotenuse in a right triangle.')
with_message_history.invoke(
{"ability": "math", "input": "What's its inverse"},
config={"configurable": {"session_id": "foobar"}},
)
AIMessage(content='The inverse of cosine is the arccosine function, denoted as acos or cos^-1, which gives the angle corresponding to a given cosine value.')
tip

朗斯密斯 追踪

观察第二通电话的 Langsmith 追踪,我们可以看到在构建提示时,已注入了一个“history”变量,这是包含两条消息(我们的第一个输入和第一个输出)的列表。


Help us out by providing feedback on this documentation page: