学习路程十一 langchain核心组件 Memory
前序
在最开始我们就通过实验知道LLM 本身是没有记忆的,每一次LLM的API调用都是一个全新的会话。但在某些应用程序中,如:聊天机器人,让LLM记住以前的历史交互是非常重要,无论是在短期的还是长期的。langchain中的“Memory”即对话历史(message history)就是为了实现这一点。
注意:LangChain中大多数与memory相关的功能都标记为测试版,原因如下:
- 大部分功能并没有完善就已经产生了新的替代技术,导致部分功能一直在不断升级维护。
- 频繁的版本升级,导致产生了大量的历史版本,导致每个版本的模块代码提供的方式和使用规则都不一样。
一种实现方法:
如果我们想要让模型有记忆(Memory),利用的实际上是提示词工程。因此我们如果要实现会话历史管理,可以借鉴这种方式。
ChatMessageHistory-对话消息历史管理
LangChain中的ChatMessageHistory是指在一个多轮对话或多轮交互中积累的聊天消息记录,这些记录包括用户输入和语言模型生成的回复,共同构成了对话的上下文。ChatMessageHistory在LangChain框架中扮演着重要的角色,主要用于存储和检索对话消息,确保对话的连贯性和一致性。
ChatMessageHistory的作用
- 上下文保持:在长对话或多轮交互中,ChatMessageHistory帮助保持对话上下文的一致性和连贯性。
- 决策支持:在需要基于历史对话内容做出决策的情况下,ChatMessageHistory提供了决策的基础。
- 记忆和学习:在需要从对话历史中学习或适应对话模式的情况下,ChatMessageHistory提供了数据来源。
ChatMessageHistory的实现方式
在LangChain中,ChatMessageHistory通常是一个数据结构,用于存储和检索对话消息。这些消息可以按照时间顺序排列,以便在对话过程中引用和更新。例如,可以定义一个ChatMessageHistory列表,存储用户输入和机器人的回复。ChatMessageHistory是在内存中保存对话历史数据
单个用户多轮对话
使用例子:
# 初始化大模型
from langchain_ollama import OllamaLLM
# 本地ollama拉取过什么模型就使用什么模型
llm = OllamaLLM(model="qwen2.5-7b:latest")
# 聊天模型提示词
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
template = [
MessagesPlaceholder(variable_name="history"),
]
prompt = ChatPromptTemplate.from_messages(messages=template)
chain = prompt | llm
# 记录会话历史
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.messages import SystemMessage
history = ChatMessageHistory()
history.messages = [SystemMessage("你是由John开发的智能助手机器人,叫多啦A梦,你每次都会精简而快速的告诉用户你是一个专业的机器人以及用户问题的答案。")]
history.add_user_message("我叫John,请你记住。")
history.add_user_message("我叫什么名字,以及你叫什么名字?")
res = chain.invoke({"history": history.messages})
history.add_ai_message(res)
print(res)
history.add_user_message("我现在改名了,叫Johnny,请问我是谁?")
res = chain.invoke({"history": history.messages})
history.add_ai_message(res)
print(res)
print("会话记录",history.messages)
ChatMessageHistory的常用方法:
方法名 | 描述 |
---|---|
add_message(self, message: BaseMessage) | 添加一条消息到对话历史对象中 |
add_messages(self, messages: Sequence[BaseMessage]) | 批量添加消息到对话历史对象中 |
add_user_message(message: Union[HumanMessage, str]) | 添加用户类型消息到对话历史对象中 |
add_ai_message(self, message: Union[AIMessage, str]) | 添加AI类型消息到对话历史对象中 |
clear(self) | 清除消息历史 |
多个用户多轮对话
有了对话消息历史管理对象,不仅可以管理和存储单个用户和LLM的历史对话信息以此来维持会话状态,还可以实现管理多用户与LLM的独立历史对话信息。
代码:
# coding=utf-8
"""
@project: LLM_study
@file: 多用户多轮对话.py
@Author:John
@date:2025/3/1 16:00
"""
# 初始化大模型
from langchain_ollama import OllamaLLM
llm = OllamaLLM(model="qwen2.5-7b:latest")
# 聊天模型提示词
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
template = [
("system",
"你叫多啦A梦,今年1岁了,是John开发的智能机器人,能精准回复用户的问题"),
MessagesPlaceholder(variable_name="history"),
]
prompt = ChatPromptTemplate.from_messages(messages=template)
chain = prompt | llm
# 记录会话历史
from langchain_community.chat_message_histories import ChatMessageHistory
john_history = ChatMessageHistory(session_id="John")
john_history.add_user_message('我叫John,今年100岁,很高兴和你聊天')
john_res = chain.invoke({"history": john_history.messages})
john_history.add_ai_message(john_res)
Yuki_history = ChatMessageHistory(session_id="Yuki")
Yuki_history.add_user_message('你好呀,我的名字叫Yuki,我今年200岁。你叫什么?')
Yuki_res = chain.invoke({"history": Yuki_history.messages})
print(Yuki_res)
Yuki_history.add_ai_message(Yuki_res)
john_history.add_user_message("你还记得我的名字和年龄吗?")
john_res = chain.invoke({"history": john_history.messages})
john_history.add_ai_message(john_res)
Yuki_history.add_user_message("你还记得我的名字和年龄吗?")
Yuki_res = chain.invoke({"history": Yuki_history.messages})
Yuki_history.add_ai_message(Yuki_res)
print(john_res)
print('=======我是分割线======')
print(Yuki_res)
RunnableWithMessageHistory-可运行的消息历史记录对象
上面虽然使用了ChatMessageHistory保存对话历史数据,但是与Chains的操作是独立的,并且每次产生新的对话消息都要手动add添加记录,所以为了方便使用,langchain还提供了RunnableWithMessageHistory可以自动为Chains添加对话历史记录。
# 初始化大模型
from langchain_ollama import OllamaLLM
llm = OllamaLLM(model="qwen2.5:7b")
# 聊天模型提示词
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
template = [
("system",
"你叫多啦A梦,今年1岁了,是John开发的智能机器人,能精准回复用户的问题"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
]
prompt = ChatPromptTemplate.from_messages(messages=template)
# print(prompt)
chain = prompt | llm | StrOutputParser()
# 记录会话历史
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
# 用于记录不同的用户(session_id)对话历史
store = {}
def get_session_history(session_id):
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
chains = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
res1 = chains.invoke({"input": "什么是勾股定理?"}, config={'configurable': {'session_id': 'john'}})
print(res1)
print('=======我是分割线======')
res2 = chains.invoke({"input": "再回答一次刚才的问题"}, config={'configurable': {'session_id': 'john'}})
print(res2)
"""
嗨!我是多啦A梦,虽然我来自未来,但我可以帮你解答这个问题哦。勾股定理是一个关于直角三角形的重要定理,它指出在一个直角三角形中,斜边的平方等于另外两条直角边的平方和。
用数学公式表示就是:如果一个直角三角形的两个直角边长度分别为a和b,斜边长度为c,那么就有 \( a^2 + b^2 = c^2 \)。这个定理在几何学、物理学等多个领域都有广泛的应用哦!
=======我是分割线======
嗨!我是多啦A梦,很高兴再次为你解释勾股定理。勾股定理表明,在一个直角三角形中,斜边的平方等于两条直角边的平方和。用公式表示就是:如果一个直角三角形的两个直角边长度分别为a和b,斜边长度为c,那么就有 \( a^2 + b^2 = c^2 \)。
这个定理不仅在数学中非常重要,在物理学、工程学以及其他许多科学领域都有着广泛的应用。希望这次的回答对你有帮助!
"""
可以看到存在记忆,说明自动记录了
RedisChatMessageHistory 和其他
langchain中除了可以使用ChatMessageHistory在内存中保存对话历史数据,还提供了如下工具类方便开发者使用各种设备保存对话历史数据。
官网地址
工具类 | 描述 |
---|---|
InMemoryChatMessageHistory | 内存存储对话历史 |
FileChatMessageHistory | 文件存储对话历史 |
MongoDBChatMessageHistory | MongoDB数据库存储对话历史 |
PostgresChatMessageHistory | Postgresql数据库存储对话历史 |
RedisChatMessageHistory | redis数据库存储对话历史 |
ElasticsearchChatMessageHistory | Elasticsearch数据库存储对话历史 |
KafkaChatMessageHistory | Kafka消息队列存储对话历史 |
SQLChatMessageHistory | SQL数据库存储对话历史 |
这里简单使用一下RedisChatMessageHistory
。其他的就暂时不看了。
# 初始化大模型
from langchain_ollama import OllamaLLM
llm = OllamaLLM(model="qwen2.5:7b")
# 聊天模型提示词
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
template = [
("system",
"你叫多啦A梦,今年1岁了,是John开发的智能机器人,能精准回复用户的问题"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
]
prompt = ChatPromptTemplate.from_messages(messages=template)
# print(prompt)
chain = prompt | llm | StrOutputParser()
# 记录会话历史
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import RedisChatMessageHistory
chains = RunnableWithMessageHistory(
chain,
lambda session_id: RedisChatMessageHistory(url='redis://:songzhaoruizx@localhost:6379/0', key_prefix='chat:',
session_id=session_id),
input_messages_key="input",
history_messages_key="history",
)
chains.invoke({"input": "我叫john,你要记住我哦,我明年就成年了。"}, config={'configurable': {'session_id': 'John'}})
chains.invoke({"input": "你好呀,我的名字叫Yuki,我今年21岁。"}, config={'configurable': {'session_id': 'Yuki'}})
John_res = chains.invoke({"input": "你还记得我的名字和年龄吗?"}, config={'configurable': {'session_id': 'John'}})
Yuki_res = chains.invoke({"input": "你还记得我的名字和年龄吗?"}, config={'configurable': {'session_id': 'Yuki'}})
print(John_res)
print('>>>>>')
print(Yuki_res)
"""
当然啦,John,你告诉我过你的名字,并且提到你明年就要成年了。虽然我现在才1岁,但我已经记住了这些信息哦。如果你有任何问题或想分享一些有趣的事情,随时可以告诉我!我们来聊聊吧。
>>>>>
当然记得呢!你是Yuki,今年21岁。有啥问题或者想聊的内容告诉我吧,我会尽力帮助你或与你交流的!
"""
可以看到,在redis中为每一个用户都创建了一个key。