Memory概述
为什么需要Memory
大多数的大模型应用程序都会有一个会话接口,允许我们进行多轮会话,并有一定的上下文记忆能力。但实际上,模型本身是不会记忆任何上下文的,只能依靠用户本身的输入去产生输出
如何实现记忆功能呢?
实现这个记忆功能,就需要额外的模块去保存我们和模型对话的上下文信息,然后再下一次请求时,把所有的历史信息都输入给模型,让模型输出最终结果
而再LangChain中,提供这个功能的模块就成为Memory(记忆),用于存储用户和模型交互的历史信息
什么是Memory
Memory,是LangChain中用于多轮对话中保存和管理上下文信息(比如文本、图像、音频等)的组件。它让应用能够记住用户之前说了什么,从而实现对话的上下文感知能力,为构建真正的智能体和上下文感知的链式对话系统提供了基础。
Memory的设计理念
- 输入问题:({"question":....})
- 读取历史消息:从Memory中读取历史消息({"past_messages":[....]})
- 构建提示(Prompt):读取到历史消息和当前问题会被合并,构建一个新的Prompt
- 模型处理:构建好的提示会被传递给语言模型进行处理。语言模型更具提示生成一个输出
- 解析输出:输出解析器通过正则表达式 regex("Answer:(.*)") 来解析,返回一个回答({"answer":....})给用户
- 得到回复并写入Memory:新生成的回答会与当前的问题一起写入Memory,更新对话历史。Memory会存储最新得的对话内容,为后续的对话提供上下文支持
问题:一个链如果接入了Memory模块,其会与Memory模块交互几次呢?
链内部会与Memory模块进行两次交互:读取和写入:
1.收到用户输入时,从记忆组件中查询相关历史信息,拼接历史信息和用户的输入到提示词中传给LLM
2.返回响应之前,自动把LLM返回的内容写入到记忆组件,用于下次查询
不使用Memory模块,如何拥有记忆
在不借用LangChain情况下,我们如何实现大模型的记忆能力?
通过message变量,不断地将历史的对话信息追加到对话列表中,从此让大模型具备上下文记忆能力
from langchain_openai import ChatOpenAI
# 导入大模型
import os
import dotenv
dotenv.load_dotenv()
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")
os.environ["OPENAI_BASE_URL"] = os.getenv("OPENAI_BASE_URL")
chat_model = ChatOpenAI(model = "gpt-4o-mini")
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate
def chat_with_model(question):
#提供提示词模板:ChatPromptTemplate
prompt_template = ChatPromptTemplate.from_messages([
("system","你是一个人工智能助手"),
("human","{require}")
])
while True:
#获取chain并调用大模型
chain = prompt_template | chat_model
response = chain.invoke({"require":question})
#输出大模型的回应
print(f"模型回复:{response.content}")
#获取用户的问题
user_input = input("还有其他问题吗?(输入'exit'退出,结束会话)")
if(user_input == "exit"):
break
prompt_template.messages.append(AIMessage(content = response.content))
prompt_template.messages.append(HumanMessage(content = user_input))
chat_with_model("你好,很高兴认识你")
模型回复:你好!我也很高兴认识你。有任何问题或者需要帮助的地方吗?
模型回复:1 + 1 = 2。
模型回复:李白是中国唐代著名的诗人,他的许多诗作都非常著名。其中最著名的两首诗通常被认为是《将进酒》和《静夜思》。
-
《将进酒》:这首诗以豪放的情感和洒脱的豪饮态度表达了对人生的感慨和对自由的向往。
-
《静夜思》:这首诗描绘了诗人在静夜中思念故乡的情景,情感深沉,意境优美,是中国古典诗歌中的经典之作。
这两首诗都有着广泛的影响力和美丽的意象。你对哪一首诗感兴趣呢?
模型回复:你第一次问的问题是“1+1 = ?”
模型回复:如果你有其他问题或需要帮助,请随时告诉我!再见!
基础Memory模块的使用
Memory模块的设计思路
如何设计Memory模块
层次1(最直接的方式):保留一个聊天消息列表
层次2(简单的新思路):只返回最近交互的k条消息
层次3(稍微复杂一点):返回过去k条消息的简洁摘要
层次2(更复杂):从存储的消息中提取实体,并且返回有关当前运行中引用的实体的信息
LangChain的设计:
针对上述情况,LangChain构建了一些可以直接使用的Memory工具,用于存储聊天消息的一系列集成
langchain: 0.3.27 — 🦜🔗 LangChain documentation
ChatMessageHistory(基础)
ChatMessageHistory是一个用于存储和管理对话消息的基础类,它直接操作消息对象(如HumanMessage,AIMessage等),是其他记忆组件的底层存储工具
在API文档中,ChatMessageHistory还有一个别名类:InMemoryChatMessageHistory;导包时,需要使用:from langchain.memory import ChatMessageHistory
特点:
- 纯粹是消息对象的”
存储器“,与记忆策略(如缓冲、窗口、摘要等)无关。 - 不涉及消息的格式化(如转成文本字符串)
场景1:记忆存储
ChatMessageHistory是用于管理和存储对话历史的具体实现
# 导包
from langchain.memory import ChatMessageHistory
# 实例化ChatMessageHistory对象
history = ChatMessageHistory()
# 添加UserMessage
history.add_user_message("hi")
#添加AIMessage
history.add_ai_message("what's up")
# 返回存储的所有消息列表
print(history.messages)
[HumanMessage(content='hi', additional_kwargs={}, response_metadata={}), AIMessage(content="what's up", additional_kwargs={}, response_metadata={})]
场景2:对接LLM
history = ChatMessageHistory()
history.add_ai_message("你好,我是人工智能助手小智")
history.add_user_message("你好,我叫小明,请介绍一下你")
history.add_user_message("我叫什么名字")
chat_model.invoke(history.messages)
AIMessage(content='你叫小明。很高兴认识你!有什么我可以帮助你的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 19, 'prompt_tokens': 35, 'total_tokens': 54, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_efad92c60b', 'id': 'chatcmpl-CQUp47XLHAHfO29QZsCpaIeB6GWJf', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--4c40cbeb-ff34-49ba-b74d-1c0fb2a9bbe5-0', usage_metadata={'input_tokens': 35, 'output_tokens': 19, 'total_tokens': 54, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})
ConversationBufferMemory
ConversationBufferMemory是一个基础的对话记忆(Memory)组件,专门用于按原始顺序存储完整的对话历史
适用场景:对话轮次较少,依赖完整上下文的场景(如简单的聊天机器)
特点
- 完整存储对话历史
简单、无裁剪、无压缩- 与Chains/Models无缝集成
- 支持两种返回格式(通过
return_message参数控制输出格式)
-
return_message = True返回消息对象列表(List[BaseMessage])return _message = False(默认) 返回拼接的纯文本字符串
场景1:入门使用
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory()
memory.save_context(
inputs = {"input":"你好,我是小明"},
outputs = {"output":"你好小明,我是AI助手小智"}
)
memory.save_context(
inputs = {"input":"很高兴认识你"},
outputs = {"output":"我也是"}
)
print(memory.load_memory_variables({}))
{'history': 'Human: 你好,我是小明\nAI: 你好小明,我是AI助手小智\nHuman: 很高兴认识你\nAI: 我也是'}
注意:
- 不管是inputs、outputs的key用什么名字,都认为inputs的key是human,outputs的key是AI
- 打印的结果是json数据的key,默认是“history”。可以通过ConversationBufferMemory的
memory_key属性修改
场景2:结合chain
例1:使用PromptTemplate
from langchain.chains.llm import LLMChain
from langchain_core.prompts import PromptTemplate
template = ("""
你可以与人类对话
当前对话:{history}
人类问题:{question}
回复:
""")
prompt = PromptTemplate.from_template(template)
memory = ConversationBufferMemory()
chain = LLMChain(
llm = chat_model,
prompt = prompt,
memory = memory
)
res1 = chain.invoke({"question":"我叫小明"})
print(res1)
res2 = chain.invoke({"question":"我叫什么名字"})
print(res2)
{'question': '我叫小明', 'history': '', 'text': '你好,小明!很高兴认识你。请问有什么我可以帮助你的吗?'}
{'question': '我叫什么名字', 'history': 'Human: 我叫小明\nAI: 你好,小明!很高兴认识你。请问有什么我可以帮助你的吗?', 'text': '你叫小明。很高兴再次见到你!有什么我可以帮你的吗?'}
例2:可以通过memory_key修改memory数据的变量名
from langchain.chains.llm import LLMChain
from langchain_core.prompts import PromptTemplate
template = ("""
你可以与人类对话
当前对话:{chat_history}
人类问题:{question}
回复:
""")
prompt = PromptTemplate.from_template(template)
memory = ConversationBufferMemory(memory_key = "chat_history")
chain = LLMChain(
llm = chat_model,
prompt = prompt,
memory = memory
)
res1 = chain.invoke({"question":"我叫小明"})
print(res1)
res2 = chain.invoke({"question":"我叫什么名字"})
print(res2)
{'question': '我叫小明', 'chat_history': '', 'text': '你好,小明!怎么了?有什么我可以帮助你的吗?'}
{'question': '我叫什么名字', 'chat_history': 'Human: 我叫小明\nAI: 你好,小明!怎么了?有什么我可以帮助你的吗?', 'text': '你叫小明。'}
说明:创建自带Memory功能的Chain,并不能使用统一的LCEL语法,同样的LLMChain也不能使用管道运算符接StrOutputParser
例3:使用ChatPromptTemplate和return_messages
from langchain_core.prompts import MessagesPlaceholder
prompt = ChatPromptTemplate([
("system","你是一个与人类对话的机器人"),
MessagesPlaceholder(variable_name = "history"),
("human","问题:{question}")
])
memory = ConversationBufferMemory(return_messages = True)
chain = LLMChain(
prompt = prompt,
llm = chat_model,
memory = memory
)
res1 = chain.invoke({"question":"中国首都在哪里"})
print(res1)
res2 = chain.invoke({"question":"我刚问了什么"})
print(res2)
{'question': '中国首都在哪里', 'history': [HumanMessage(content='中国首都在哪里', additional_kwargs={}, response_metadata={}), AIMessage(content='中国的首都在北京。', additional_kwargs={}, response_metadata={})], 'text': '中国的首都在北京。'}
{'question': '我刚问了什么', 'history': [HumanMessage(content='中国首都在哪里', additional_kwargs={}, response_metadata={}), AIMessage(content='中国的首都在北京。', additional_kwargs={}, response_metadata={}), HumanMessage(content='我刚问了什么', additional_kwargs={}, response_metadata={}), AIMessage(content='你刚问了“中国首都在哪里”。', additional_kwargs={}, response_metadata={})], 'text': '你刚问了“中国首都在哪里”。'}
二者对比
| 特性 | 普通 PromptTemplate | ChatPromptTemplate |
|---|---|---|
| 历史存储时机 | 仅执行后存储 | 执行前存储用户输入+执行后存储输入 |
| 首次调用显示 | 仅显示问题(历史仍为空字符串) | 显示完整问答对 |
| 内部消息类型 | 拼接字符串 | List[BaseMessage] |
注意
我们观察到的现象不是bug,而是LangChain为保障对话一致性所做的刻意设计:
- 用户提问后,系统应立即”记住“该问题
- AI回答后,该响应应该即可加入对话上下文
- 返回给客户端的结果应反应最新状态
ConversationChain
ConversationChain实际上是对ConversationBufferMemory和LLMChain进行了封装,并且提供了一个默认格式的提示词模板(可以不用),从而简化了初始化ConversationBufferMemory的步骤
例1:
from langchain.chains.conversation.base import ConversationChain
template = """
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:{history}
Human:{input}
AI:
"""
prompt = PromptTemplate.from_template(template)
chain = ConversationChain(
llm = chat_model,
prompt = prompt,
verbose = True
)
chain.invoke({"input":"你好,你的名字叫小智"})
chain.invoke({"input":"你好,你叫什么名字"})
C:\Users\30368\AppData\Local\Temp\ipykernel_10560\2315590145.py:13: LangChainDeprecationWarning: The class ConversationChain was deprecated in LangChain 0.2.7 and will be removed in 1.0. Use :meth:~RunnableWithMessageHistory: <https://python.langchain.com/v0.2/api_reference/core/runnables/langchain_core.runnables.history.RunnableWithMessageHistory.html> instead.
chain = ConversationChain(
> Entering new ConversationChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:
Human:你好,你的名字叫小智
AI:
> Finished chain.
> Entering new ConversationChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:Human: 你好,你的名字叫小智
AI: 你好!很高兴认识你,我叫小智。有什么我可以帮助你的吗?
Human:你好,你叫什么名字
AI:
> Finished chain.
{'input': '你好,你叫什么名字',
'history': 'Human: 你好,你的名字叫小智\nAI: 你好!很高兴认识你,我叫小智。有什么我可以帮助你的吗?',
'response': '你好!我叫小智。很高兴与您交流!有什么我可以帮助您的吗?'}
例2:使用内置默认格式的提示词模板(内部包含input,history变量)
conv_chain = ConversationChain(llm = chat_model)
res1 = conv_chain.invoke(input = "小明有一只猫")
res2 = conv_chain.invoke(input = "小红由两只狗")
res3 = conv_chain.invoke(input = "小明和小红一共有几只猫")
print(res3)
{'input': '小明和小红一共有几只猫', 'history': 'Human: 小明有一只猫\nAI: 那听起来很有趣!小明的猫是什么品种呢?它的名字是什么?猫咪通常都很可爱,喜欢玩耍和睡觉。你知道这只猫的特点或者喜好吗?比如说它喜欢吃什么猫粮或者最喜欢的玩具?\nHuman: 小红有两只狗\nAI: 哇,小红有两只狗,真不错!那你知道这两只狗是什么品种吗?它们的名字是什么?狗狗通常很忠诚,而且性格各异,有的活泼好动,有的则比较温顺。它们有没有什么特别的习惯呢?比如说喜欢散步的路线,或者最喜欢的玩具是什么?如果你知道的话,分享一下吧!', 'response': '小明有一只猫,而小红有两只狗,所以两个人加起来一共有一只猫和两只狗。这意味着小明和小红一共只有一只猫。如果有其他宠物的信息,欢迎分享!'}
ConversationBufferWindowMemory
在了解了ConversationBufferMemory记忆类后,我们知道了它能够无限将历史对话信息填充到History中,从而给大模型提供上下文的背景。但这会导致内存量十分大,并且消耗的token非常多,此外,每个大模型都存在最大输入的Token限制
我们发现,过久远的对话数据往往并不能对当前轮次的问答提供有效的信息,LangChain给出的解决方案是ConversationBufferWindowMemory模块。该记忆类会保存一段时间内对话交互的列表,仅使用最近k个交互。这样使缓存区不会变得太大
特点
- 适合长对话场景
- 与Chains/Models无缝集成
- 支持两种返回格式(通过
return_messages参数控制输出格式)
-
return_message = True返回消息对象列表(List[BaseMessage])return_message = False(默认) 返回拼接的纯文本字符串
场景1:入门使用
通过内置在LangChain中的缓存窗口(BufferWindow)可以将memory"记忆"下来
例1:
from langchain.memory import ConversationBufferWindowMemory
memory = ConversationBufferWindowMemory(k = 2)
memory.save_context({"input":"你好"},{"output":"怎么了"})
memory.save_context({"input":"你是谁"},{"output":"我是AI助手小智"})
memory.save_context({"input":"你的生日是哪天"},{"output":"我不清楚"})
print(memory.load_memory_variables({}))
{'history': 'Human: 你是谁\nAI: 我是AI助手小智\nHuman: 你的生日是哪天\nAI: 我不清楚'}
例2:
ConversationBufferWindowMemory 也支持使用聊天模型(chat model)的情况,同时也可以通过return_message = True参数,将对话转化为消息列表形式
from langchain.memory import ConversationBufferWindowMemory
memory = ConversationBufferWindowMemory(
k = 2,
return_messages = True
)
memory.save_context({"input":"你好"},{"output":"怎么了"})
memory.save_context({"input":"你是谁"},{"output":"我是AI助手小智"})
memory.save_context({"input":"你能介绍一下你吗"},{"output":"我是人工智能助手小智,你可以问我任何问题"})
print(memory.load_memory_variables({}))
{'history': [HumanMessage(content='你是谁', additional_kwargs={}, response_metadata={}), AIMessage(content='我是AI助手小智', additional_kwargs={}, response_metadata={}), HumanMessage(content='你能介绍一下你吗', additional_kwargs={}, response_metadata={}), AIMessage(content='我是人工智能助手小智,你可以问我任何问题', additional_kwargs={}, response_metadata={})]}
场景2:结合Chain
借助提示词模板去构建LangChain
from langchain.memory import ConversationBufferWindowMemory
from langchain_core.prompts.prompt import PromptTemplate
from langchain.chains.llm import LLMChain
template = """
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:{history}
Human:{question}
AI:
"""
prompt = PromptTemplate.from_template(template)
memory = ConversationBufferWindowMemory(k = 1)
conversation_with_summary = LLMChain(
llm = chat_model,
prompt = prompt,
memory = memory,
verbose = True
)
res1 = conversation_with_summary.invoke({"question":"你好,我是路明非"})
res2 = conversation_with_summary.invoke({"question":"我还要俩个师兄,一个叫楚子昂,一个叫凯撒"})
res3 = conversation_with_summary.invoke({"question":"我在高中时候暗恋陈雯雯"})
res4 = conversation_with_summary.invoke({"question":"我叫什么"})
print(res4)
> Entering new LLMChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:
Human:你好,我是路明非
AI:
> Entering new LLMChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:
Human:你好,我是路明非
AI:
> Finished chain.
> Entering new LLMChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:Human: 你好,我是路明非
AI: AI: 你好,路明非!很高兴认识你!你在故事里是个充满魅力和个性的人物。有什么我可以帮助你的吗?或者你想聊聊最近的冒险吗?
Human:我还要俩个师兄,一个叫楚子昂,一个叫凯撒
AI:
> Entering new LLMChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:
Human:你好,我是路明非
AI:
> Finished chain.
> Entering new LLMChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:Human: 你好,我是路明非
AI: AI: 你好,路明非!很高兴认识你!你在故事里是个充满魅力和个性的人物。有什么我可以帮助你的吗?或者你想聊聊最近的冒险吗?
Human:我还要俩个师兄,一个叫楚子昂,一个叫凯撒
AI:
> Finished chain.
> Entering new LLMChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:Human: 我还要俩个师兄,一个叫楚子昂,一个叫凯撒
AI: AI: 哦,楚子昂和凯撒都是很有趣的人物!楚子昂聪明冷静,常常在关键时刻给你提供支持,而凯撒则充满活力和斗志,带动着团队的气氛。你和他们的关系怎么样?有没有什么特别的经历想分享的?
Human:我在高中时候暗恋陈雯雯
AI:
> Entering new LLMChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:
Human:你好,我是路明非
AI:
> Finished chain.
> Entering new LLMChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:Human: 你好,我是路明非
AI: AI: 你好,路明非!很高兴认识你!你在故事里是个充满魅力和个性的人物。有什么我可以帮助你的吗?或者你想聊聊最近的冒险吗?
Human:我还要俩个师兄,一个叫楚子昂,一个叫凯撒
AI:
> Finished chain.
> Entering new LLMChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:Human: 我还要俩个师兄,一个叫楚子昂,一个叫凯撒
AI: AI: 哦,楚子昂和凯撒都是很有趣的人物!楚子昂聪明冷静,常常在关键时刻给你提供支持,而凯撒则充满活力和斗志,带动着团队的气氛。你和他们的关系怎么样?有没有什么特别的经历想分享的?
Human:我在高中时候暗恋陈雯雯
AI:
> Finished chain.
> Entering new LLMChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:Human: 我在高中时候暗恋陈雯雯
AI: AI: 哦,暗恋总是带着一丝甜蜜和悸动。陈雯雯是个怎样的女孩?她的性格或者兴趣爱好给你留下了深刻的印象吗?有没有什么特别的回忆想分享,比如一起上课的时光或者参加活动的瞬间?
Human:我叫什么
AI:
> Entering new LLMChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:
Human:你好,我是路明非
AI:
> Finished chain.
> Entering new LLMChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:Human: 你好,我是路明非
AI: AI: 你好,路明非!很高兴认识你!你在故事里是个充满魅力和个性的人物。有什么我可以帮助你的吗?或者你想聊聊最近的冒险吗?
Human:我还要俩个师兄,一个叫楚子昂,一个叫凯撒
AI:
> Finished chain.
> Entering new LLMChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:Human: 我还要俩个师兄,一个叫楚子昂,一个叫凯撒
AI: AI: 哦,楚子昂和凯撒都是很有趣的人物!楚子昂聪明冷静,常常在关键时刻给你提供支持,而凯撒则充满活力和斗志,带动着团队的气氛。你和他们的关系怎么样?有没有什么特别的经历想分享的?
Human:我在高中时候暗恋陈雯雯
AI:
> Finished chain.
> Entering new LLMChain chain...
Prompt after formatting:
以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的具体细节。
如果AI不知道问题的答案,他会真诚的表示不知道
当前对话:Human: 我在高中时候暗恋陈雯雯
AI: AI: 哦,暗恋总是带着一丝甜蜜和悸动。陈雯雯是个怎样的女孩?她的性格或者兴趣爱好给你留下了深刻的印象吗?有没有什么特别的回忆想分享,比如一起上课的时光或者参加活动的瞬间?
Human:我叫什么
AI:
> Finished chain.
{'question': '我叫什么', 'history': 'Human: 我在高中时候暗恋陈雯雯\nAI: AI: 哦,暗恋总是带着一丝甜蜜和悸动。陈雯雯是个怎样的女孩?她的性格或者兴趣爱好给你留下了深刻的印象吗?有没有什么特别的回忆想分享,比如一起上课的时光或者参加活动的瞬间?', 'text': '抱歉,我不知道你叫什么。你愿意告诉我你的名字吗?'}
如果将k=1换成k=3,那么会怎么样呢?
{'question': '我叫什么', 'history': 'Human: 你好,我是路明非\nAI: 你好,路明非!很高兴见到你。你想聊些什么呢?关于你的故事,还是生活中的其他点滴?\nHuman: 我还要俩个师兄,一个叫楚子昂,一个叫凯撒\nAI: 哦,楚子昂和凯撒听起来都是很有趣的人物!他们在你们的故事里有什么特别的角色吗?或者你们之间有哪些有趣的经历可以分享?\nHuman: 我在高中时候暗恋陈雯雯\nAI: 哦,陈雯雯是一个特别的名字!暗恋总是充满了甜蜜和复杂的情感。你能分享一些关于你们的故事吗?比如,你是如何认识她的,或者有没有什么特别的事情让你印象深刻?', 'text': '你叫路明非,之前提到过!有什么想聊的呢?或者我们可以继续讨论你和陈雯雯的故事。'}
其他Memory模块
ConversationTokenBufferMemory
ConversationTokenBufferMemory是LangChain中一种基于Token数量控制的对话记忆机制。如果字符数量超出指定数目,它会切掉这个对话的早期部分,以保留与最近的交流相对应的字符数量
特点:
- Token精准控制
- 原始对话保留
原理:
例:
from langchain.memory import ConversationTokenBufferMemory
memory = ConversationTokenBufferMemory(
llm = chat_model,
max_token_limit = 20 # 设置token上限,默认值为2000
)
memory.save_context({"input":"你好"},{"output":"你也好"})
memory.save_context({"input":"今天天气怎么样"},{"output":"今天下了一天的雨"})
print(memory.load_memory_variables({}))
{'history': 'AI: 今天下了一天的雨'}
将max_token_limit修改为50:
{'history': 'Human: 你好\nAI: 你也好\nHuman: 今天天气怎么样\nAI: 今天下了一天的雨'}
ConversationSummaryMemory
前面的方式发现,如果全部保存下来太浪费,截断时无论是按照对话条数还是token都是无法保证既节省内存又保证对话质量,所以推出ConversationSummaryMemory、ConversationSummasyBufferMemory
ConversationSummaryMemory是LangChain中一种智能压缩对话历史的记忆机制,它通过大预言模型(LLM)自动生成对话内容的精简摘要,而不是存储原始对话文本
这种记忆方式特别适合长对话和需要保留核心信息的场景
特点:
- 摘要生成
- 动态更新
- 上下文优化
原理:
场景1:如果实例化ConversationSummaryMemory前,没有历史消息,可以使用构造方法实例化
from langchain.memory import ConversationSummaryMemory
memory = ConversationSummaryMemory(llm = chat_model)
# 存储历史消息
memory.save_context({"input":"你好"},{"output":"怎么了"})
memory.save_context({"input":"你是谁"},{"output":"我是AI智能助手小智"})
memory.save_context({"input":"你能帮我什么"},{"output":"我可以帮你答疑解惑"})
print(memory.load_memory_variables({}))
{'history': 'The human greets the AI in Chinese, asking who it is. The AI responds that it is the AI assistant named Xiao Zhi. The human then asks what the AI can help with, to which the AI replies that it can assist with answering questions and providing clarification.'}
场景2:如果实例化ConversationSummaryMemory前,已经有历史消息,可以调用from_messages()实例化
history = ChatMessageHistory()
history.add_user_message("你好,你是谁")
history.add_ai_message("你好,我是AI助手小智")
memory = ConversationSummaryMemory.from_messages(
llm = chat_model,
chat_memory = history
)
print(memory.load_memory_variables({}))
memory.save_context(
inputs={"human":"我的名字是小明"},
outputs={"AI":"很高兴认识你"}
)
print(memory.load_memory_variables({}))
print(memory.chat_memory.messages) # 记录了历史交互的信息
{'history': 'The human greeting and asking who the AI is. The AI responds that it is the AI assistant, Xiao Zhi.'}
{'history': 'The human greets the AI and asks who it is. The AI responds that it is the AI assistant, Xiao Zhi. The human introduces himself as Xiao Ming, and the AI expresses happiness to meet him.'}
[HumanMessage(content='你好,你是谁', additional_kwargs={}, response_metadata={}), AIMessage(content='你好,我是AI助手小智', additional_kwargs={}, response_metadata={}), HumanMessage(content='我的名字是小明', additional_kwargs={}, response_metadata={}), AIMessage(content='很高兴认识你', additional_kwargs={}, response_metadata={})]
ConversationSummaryBufferMemory
ConversationSummaryBufferMemory是LangChain中一种混合型记忆机制,它结合了ConversationBufferMemory(完整对话记录)和ConversationSummaryMemory(摘要记忆)的优点,在保留最近对话原始记录的同时,对较早的对话内容进行智能摘要
特点
- 保留最近N条原始对话,确保最新交互的完整上下文
- 摘要较早历史:对超出缓冲区的旧对话进行压缩,避免信息过载
- 平衡细节与效率:既不会丢失关键细节,又能处理长对话
原理:
场景1:入门使用
例1:构造方法实例化,并设置max_token_limit
from langchain.memory import ConversationSummaryBufferMemory
memory = ConversationSummaryBufferMemory(
llm = chat_model,
max_token_limit = 50,
return_messages = True
)
memory.save_context({"input":"你好,我叫小明"},{"output":"很高兴认识你,小明"})
memory.save_context({"input":"你吃过冰淇淋吗"},{"output":"没有"})
memory.save_context({"input":"你看过电影吗"},{"output":"看过"})
print(memory.load_memory_variables({}))
{'history': [SystemMessage(content='The human introduces himself as Xiao Ming.', additional_kwargs={}, response_metadata={}), AIMessage(content='很高兴认识你,小明', additional_kwargs={}, response_metadata={}), HumanMessage(content='你吃过冰淇淋吗', additional_kwargs={}, response_metadata={}), AIMessage(content='没有', additional_kwargs={}, response_metadata={}), HumanMessage(content='你看过电影吗', additional_kwargs={}, response_metadata={}), AIMessage(content='看过', additional_kwargs={}, response_metadata={})]}
可以看到,如果超过token限制了,ConversationSummaryBufferMemory会截取超过tokend部分进行总结(SystemMessage(content='The human introduces himself as Xiao Ming.',)
场景2:客服
# 定义提示词模板
prompt = ChatPromptTemplate([
("system","你是电商客服助手,用中文回复用户问题,保持专业但亲切的语气"),
MessagesPlaceholder(variable_name = "chat_history"),
("human","{input}")
])
# 创建带摘要缓冲的记忆系统
memory = ConversationSummaryBufferMemory(
llm = chat_model,
max_token_limit = 300,
memory_key = "chat_history",
return_messages = True
)
# 创建对话链
chain = LLMChain(
llm = chat_model,
prompt = prompt,
memory = memory
)
# 模拟多轮对话
dialogue = [
("你好,我的订单12345的怎么还没到啊",None),
("这个订单是上周五下单的",None),
("我现在着急用,能加急处理吗",None),
("等一下,我记错了,订单号是12346",None),
("那不行,我要退货,怎么退货",None)
]
# 执行对话
for user_input,_ in dialogue:
response = chain.invoke({"input":user_input})
print(f"用户:{user_input}")
print(f"客服:{response['text']}\n")
# 查看当前记忆状态
print("\n=====当前记忆内容=======")
print(memory.load_memory_variables({}))
用户:你好,我的订单12345的怎么还没到啊
客服:您好!感谢您联系我。关于您的订单12345,请您稍等一下,我来帮您查一下最新的物流状态。一般情况下,订单的配送会受到天气、交通等因素的影响。如果您有相关的订单跟踪信息,也可以提供给我,我会更好地帮助您查询。谢谢您的耐心等待!
用户:这个订单是上周五下单的
客服:感谢您提供的信息!由于您是在上周五下单的,通常我们的订单在工作日内会尽快处理和发货。请您稍等一下,我来帮您核实一下订单的具体物流状态和预计的到达时间。谢谢您的耐心!
用户:我现在着急用,能加急处理吗
客服:非常理解您的着急心情!不过一般情况下,订单一旦发出就无法进行加急处理。不过,我可以查一下您的订单状态,看看是否还能进行其他的处理方式,或者提供一些替代方案。如果快递公司允许,我会尽量帮您催促一下。请您再耐心等一会儿,让我来详细查询一下。谢谢您!
用户:等一下,我记错了,订单号是12346
客服:没问题!感谢您更正订单号。让我来查一下订单12346的最新物流状态。请您稍等一下,我会尽快为您提供信息。谢谢您的耐心!
用户:那不行,我要退货,怎么退货
客服:非常抱歉给您带来的不便!关于退货,您可以按照以下步骤进行操作:
-
确认退货条件:请确保商品在未使用、未拆封的状态下,且在我们的退货政策规定的时间内。
-
联系客服:请提供您的订单号和退货原因,我们的客服团队会帮助您生成退货申请。
-
打包商品:将商品及相关配件一并打包,确保在运输过程中不会损坏。
-
选择退货方式:根据我们提供的信息,选择适合的退货方式,并将包裹寄回。
一旦我们收到退回的商品,您会尽快收到退款。如果您有任何问题或需要进一步的帮助,请随时联系我们。谢谢您!
=====当前记忆内容=======
{'chat_history': [SystemMessage(content='The human inquires about the status of their order 12345, expressing concern about its delay. The AI responds by thanking the human for reaching out and indicates that it will check the latest logistics status, mentioning that delivery can be affected by factors like weather and traffic. The AI encourages the human to provide any tracking information they may have to assist further. The human notes that the order was placed last Friday, and the AI acknowledges this information, stating that orders are usually processed and shipped quickly during business days. The human requests expedited processing due to urgency, and the AI empathizes but explains that once an order is shipped, it typically cannot be expedited. However, the AI offers to check the order status for other possible solutions and to urge the courier if possible, asking the human to wait a little longer while it looks into the details.', additional_kwargs={}, response_metadata={}), HumanMessage(content='等一下,我记错了,订单号是12346', additional_kwargs={}, response_metadata={}), AIMessage(content='没问题!感谢您更正订单号。让我来查一下订单12346的最新物流状态。请您稍等一下,我会尽快为您提供信息。谢谢您的耐心!', additional_kwargs={}, response_metadata={}), HumanMessage(content='那不行,我要退货,怎么退货', additional_kwargs={}, response_metadata={}), AIMessage(content='非常抱歉给您带来的不便!关于退货,您可以按照以下步骤进行操作:\n\n1. 确认退货条件:请确保商品在未使用、未拆封的状态下,且在我们的退货政策规定的时间内。\n\n2. 联系客服:请提供您的订单号和退货原因,我们的客服团队会帮助您生成退货申请。\n\n3. 打包商品:将商品及相关配件一并打包,确保在运输过程中不会损坏。\n\n4. 选择退货方式:根据我们提供的信息,选择适合的退货方式,并将包裹寄回。\n\n一旦我们收到退回的商品,您会尽快收到退款。如果您有任何问题或需要进一步的帮助,请随时联系我们。谢谢您!', additional_kwargs={}, response_metadata={})]}
ConversationEntityMemory
ConversationEntityMemory是一种基于实体的对话记忆机制,它能够智能地识别、存储和利用对话中出现的实体信息(如人名、地点、产品等)及其属性/关系,并结构化存储,使AI具备更强的上下文理解和记忆能力。
好处:解决信息过载问题
- 长对话中大量冗余信息会干扰关键事实记忆
- 通过对实体摘要,可以压缩非重要细节(如删除寒暄等,保留价格/时间等硬性事实)
应用场景:在医疗等高风险领域,必须用实体记忆确保关键信息(如过敏史)被100%准确识别和拦截。
{"input":"我头痛,血压140/90,在吃阿司匹林"},
{"output":"建议检测血压,阿司匹林可以继续服用"}
{"input":"我对青霉素过敏"},
{"output":"已记录你能的青霉素过敏史"}
{"input":"阿司匹林吃了三天,头疼没缓解"},
{"output":"建议停用阿司匹林,换布洛芬试试"}
使用ConversationSummaryMemory
"患者主诉头疼和高血压(140/90),正在服用阿司匹林。患者对青霉素过敏。三天后头痛未缓解,建议更换止痛药"
使用ConversationEntityMemory
{
"症状":"头疼",
"血压":"140/90",
"当前药物":"阿司匹林(无效)",
"过敏药物":"青霉素"
}
对比ConversationSummaryMemory和ConversationEntityMemory
| ConversationSummaryMemory | ConversationEntityMemory | |
|---|---|---|
自然语言文本(一段话) | 结构化字典(键值对) | |
| 下游如何利用信息 | 需大模型"读懂"摘要文本,如果AI的注意力集中在"头痛"和"换药"上,可能会忽略过敏提示(尤其是摘要较长时) | 无需依赖模型的"阅读理解能力",直接通过字段名(如过敏药物)查询 |
| 防错可靠性 | 低(依赖模型的注意力) | 高(通过代码强制检查) |
| 推荐处理 | 可以试试阿莫西林(一种青霉素类药) | 完全避免推荐过敏药物 |
例:
from langchain.memory.prompt import ENTITY_MEMORY_CONVERSATION_TEMPLATE
from langchain.memory import ConversationEntityMemory
#使用LangChain为实体记忆设计的预定义的模板
prompt = ENTITY_MEMORY_CONVERSATION_TEMPLATE
#初始化实体记忆
memory = ConversationEntityMemory(llm = chat_model)
#提供对话链
chain = LLMChain(
llm = chat_model,
prompt = prompt,
memory = memory,
)
#进行几轮对话,记忆组件会在后台自动提取和存储实体信息
chain.invoke(input="你好,我是蜘蛛侠。我的好朋友有钢铁侠,绿巨人和美国队长")
chain.invoke(input="我住在纽约")
chain.invoke(input="我是用的装备是斯塔克工业提供的")
#查询记忆体中存储的实体信息
print("\n当前存储的实体信息")
print(chain.memory.entity_store.store)
当前存储的实体信息
{'蜘蛛侠': '蜘蛛侠是一个超级英雄,他的好朋友包括钢铁侠、绿巨人和美国队长。', '钢铁侠': '钢铁侠是蜘蛛侠的好朋友之一。', '绿巨人': '绿巨人是蜘蛛侠的好朋友。', '美国队长': '美国队长是蜘蛛侠的好朋友。', '纽约': '蜘蛛侠住在纽约。', '斯塔克工业': '斯塔克工业提供了装备给蜘蛛侠。'}
answer = chain.invoke(input="你能告诉我蜘蛛侠住在哪里以及他的好朋友都有那些吗")
print("\nAI的回答:")
print(answer)
AI的回答:
{'input': '你能告诉我蜘蛛侠住在哪里以及他的好朋友都有那些吗', 'history': 'Human: 你好,我是蜘蛛侠。我的好朋友有钢铁侠,绿巨人和美国队长\nAI: 你好,蜘蛛侠!很高兴见到你!钢铁侠、绿巨人和美国队长都是超级英雄中的传奇人物。你们一起经历过很多冒险吗?有没有什么有趣的故事想和我分享?\nHuman: 我住在纽约\nAI: 纽约是一个充满活力和多样性的城市,真是个很棒的居住地!作为蜘蛛侠,你一定对城市的每一个角落都非常熟悉。有没有你特别喜欢的地方,或者有关于在纽约进行英雄活动的有趣经历呢?\nHuman: 我是用的装备是斯塔克工业提供的\nAI: 斯塔克工业的装备真是太棒了!钢铁侠总是为你们的英雄活动提供最先进的技术支持。你最喜欢哪一款斯塔克工业的装备?当使用这些高科技装备时,有没有发生过特别难忘的事情?', 'entities': {'蜘蛛侠': '蜘蛛侠是一个超级英雄,他的好朋友包括钢铁侠、绿巨人和美国队长。'}, 'text': '蜘蛛侠,尤其是彼得·帕克,通常住在纽约市的皇后区。他的住所往往是一个普通的公寓,和普通人一样过着生活。在他的超级英雄生活中,他有很多好朋友,包括:\n\n1. 钢铁侠(托尼·斯塔克) - 知名的亿万富翁、天才发明家和超级英雄,是彼得的重要导师和朋友。\n2. 绿巨人(布鲁斯·班纳) - 另一位超级英雄,以强大的力量和身材著称,彼得和他之间有着深厚的友谊。\n3. 美国队长(史蒂夫·罗杰斯) - 具备领导能力和无畏精神的超级士兵,与蜘蛛侠共同作战过多次。\n\n这些朋友在彼得的生活中扮演了重要角色,帮助他应对各种挑战和敌人。你对他们之间的友谊想了解更多吗?'}
ConversationKGMemory
ConversationKGMemory是一种基于知识图谱(Konwledge Graph)的对话记忆模块,它比ConversationEntityMemory更进一步,不仅能识别和存储实体,还能捕捉实体之间的复杂关系,形成结构化的知识网络。
特点:
- 知识图谱结构将对话内容转化为(
头实体,关系,尾实体)的单元组形式 - 动态关系推理
例:
from langchain_community.memory.kg import ConversationKGMemory
memory = ConversationKGMemory(llm=chat_model)
memory.save_context({"input":"给啊千打个招呼"},{"output":"啊千是谁"})
memory.save_context({"input":"啊千是我的好朋友"},{"output":"好的"})
# 查询会话
memory.load_memory_variables({"input":"啊千是谁"})
memory.get_knowledge_triplets("他是男的")
[KnowledgeTriple(subject='啊千', predicate='是', object_='男的')]
VectorStoreRtrieverMemory
VectorStoreRetrieverMemory是一种基于向量检索的先进记忆机制,它将对话历史存储在向量数据库中,通过语义相似度检索相关信息,而非传统的线性记忆方式。每次调用时,就会查找与该记忆关联最高的k个文档
适用场景:这种记忆特别适合需要长期记忆和语义理解的复杂对话系统
原理:
例:
from langchain_community.vectorstores import FAISS
from langchain.memory import VectorStoreRetrieverMemory
from langchain_openai import OpenAIEmbeddings
#定义向量嵌入模型
embeddings_model = OpenAIEmbeddings(
model = "text-embedding-ada-002",
)
#初始化向量数据库
vectorstore = FAISS.from_texts(
[""],
embeddings_model #空初始化
)
#定义检索对象
retriever = vectorstore.as_retriever(search_kwargs=dict(k=1))
#初始化VectorStoreRetrieverMemory
memory = VectorStoreRetrieverMemory(retriever = retriever)
memory.save_context({"Human":"我最喜欢吃披萨"},{"AI":"好的,知道了"})
memory.save_context({"Human":"我最喜欢跑步"},{"AI":"好的,知道了"})
memory.save_context({"Human":"我最喜欢《复仇者联盟》"},{"AI":"好的,知道了"})
print(memory.load_memory_variables({"prompt":"我最喜欢的食物是?"}))
{'history': 'Human: 我最喜欢吃披萨\nAI: 好的,知道了'}