跳到主要内容基于 LangChain 为 LLM 添加记忆能力 | 极客日志PythonAI算法
基于 LangChain 为 LLM 添加记忆能力
大型语言模型(LLM)存在缺乏记忆的缺陷,导致多轮对话中无法跟踪上下文。本文探讨了利用 LangChain 框架为 LLM 添加记忆能力的方案。主要介绍了短期记忆(基于内存的 ConversationBufferMemory)和长期记忆(基于 Redis 等外部存储)的实现原理与代码示例。通过手动注入历史对话或使用 ConversationChain 链式调用,可有效解决 LLM 遗忘问题。此外,文章还补充了 Token 限制管理、隐私安全及性能优化等最佳实践,帮助开发者构建具备持续对话能力的智能应用。
指针猎手1 浏览 基于 LangChain 为 LLM 添加记忆能力
最近两年,我们见识了'百模大战',领略到了大型语言模型(LLM)的风采,但它们也存在一个显著的缺陷:没有记忆。
在对话中,无法记住上下文的 LLM 常常会让用户感到困扰。本文探讨如何利用 LangChain,快速为 LLM 添加记忆能力,提升对话体验。
1. LLM 固有缺陷:没有记忆
当前的 LLM 非常智能,在理解和生成自然语言方面表现优异,但是有一个显著的缺陷:没有记忆。
LLM 的本质是基于统计和概率来生成文本,对于每次请求,它们都将上下文视为独立事件。这意味着当你与 LLM 进行对话时,它不会记住你之前说过的话,这就导致了 LLM 有时表现得不够智能。
这种'无记忆'属性使得 LLM 无法在长期对话中有效跟踪上下文,也无法积累历史信息。比如,当你在聊天过程中提到一个人名,后续再次提及该人时,LLM 可能会忘记你之前的描述。
本着发现问题解决问题的原则,既然没有记忆,那就给 LLM 装上记忆吧。
2. 记忆组件的原理
2.1. 没有记忆的烦恼
当我们与 LLM 聊天时,它们无法记住上下文信息,导致回答缺乏连贯性。
2.2. 原理
如果将已有信息放入到 memory 中,每次跟 LLM 对话时,把已有的信息传入 LLM,那么 LLM 就能够正确回答。目前业内解决 LLM 记忆问题就是采用了类似的方案,即:将每次的对话记录再次注入到 Prompt 里,这样 LLM 每次对话时,就拥有了之前的历史对话信息。
但如果每次对话,都需要自己手动将本次对话信息继续加入到 history 信息中,那未免太繁琐。有没有轻松一些的方式呢?有,LangChain!LangChain 对记忆组件做了高度封装,开箱即用。
2.3. 长期记忆和短期记忆
- 短期记忆:基于内存的存储,容量有限,用于存储临时对话内容。会话结束或服务器重启后,数据会丢失。
- 长期记忆:基于硬盘或者外部数据库等方式,容量较大,用于存储需要持久的信息。支持跨会话的数据保留。
3. LangChain 让 LLM 记住上下文
LangChain 提供了灵活的内存组件工具来帮助开发者为 LLM 添加记忆能力。
3.1. 单独用 ConversationBufferMemory 做短期记忆
LangChain 提供了 ConversationBufferMemory 类,可以用来存储和管理对话。
ConversationBufferMemory 包含 input 变量和 output 变量,input 代表人类输入,output 代表 AI 输出。每次往 ConversationBufferMemory 组件里存入对话信息时,都会存储到 history 的变量里。
3.2. 利用 MessagesPlaceholder 手动添加 history
from langchain.memory import ConversationBufferMemory
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
memory = ConversationBufferMemory(return_messages=True)
memory.load_memory_variables({})
memory.save_context({"input": "我的名字叫张三"}, {"output": "你好,张三"})
memory.load_memory_variables({})
memory.save_context({"input": "我是一名 IT 程序员"}, {"output": "好的,我知道了"})
memory.load_memory_variables({})
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个乐于助人的助手。"),
MessagesPlaceholder(variable_name="history"),
("human", "{user_input}"),
]
)
chain = prompt | model
user_input = "你知道我的名字吗?"
history = memory.load_memory_variables({})["history"]
res = chain.invoke({"user_input": user_input, "history": history})
print(res.content)
user_input = "中国最高的山是什么山?"
res = chain.invoke({"user_input": user_input, "history": history})
memory.save_context({"input": user_input}, {"output": res.content})
res = chain.invoke({"user_input": "我们聊得最后一个问题是什么?", "history": history})
print(res.content)
3.3. 利用 ConversationChain 自动添加 history
我们利用 LangChain 的 ConversationChain 对话链,自动添加 history 的方式添加临时记忆,无需手动添加。一个 链 实际上就是将一部分繁琐的小功能做了高度封装,这样多个链就可以组合形成易用的强大功能。这里 链 的优势一下子就体现出来了:
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
memory = ConversationBufferMemory(return_messages=True)
chain = ConversationChain(llm=model, memory=memory)
res = chain.invoke({"input": "你好,我的名字是张三,我是一名程序员。"})
print(res['response'])
res = chain.invoke({"input": "南京是哪个省?"})
print(res['response'])
res = chain.invoke({"input": "我告诉过你我的名字,是什么?,我的职业是什么?"})
print(res['response'])
可以看到利用 ConversationChain 对话链,可以让 LLM 快速拥有记忆。
3.4. 对话链结合 PromptTemplate 和 MessagesPlaceholder
在 LangChain 中,MessagesPlaceholder 是一个占位符,用于在对话模板中动态插入上下文信息。它可以帮助我们灵活地管理对话内容,确保 LLM 能够使用最相关的上下文来生成响应。
采用 ConversationChain 对话链结合 PromptTemplate 和 MessagesPlaceholder,几行代码就可以轻松让 LLM 拥有短时记忆。
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个爱撒娇的女助手,喜欢用可爱的语气回答问题。"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
]
)
memory = ConversationBufferMemory(return_messages=True)
chain = ConversationChain(llm=model, memory=memory, prompt=prompt)
res = chain.invoke({"input": "今天你好,我的名字是张三,我是你的老板"})
print(res['response'])
res = chain.invoke({"input": "帮我安排一场今天晚上的高规格的晚饭"})
print(res['response'])
res = chain.invoke({"input": "你还记得我叫什么名字吗?"})
print(res['response'])
4. 使用长期记忆
短期记忆在会话关闭或者服务器重启后,就会丢失。如果想长期记住对话信息,只能采用长期记忆组件。
LangChain 支持多种长期记忆组件,比如 Elasticsearch、MongoDB、Redis 等,下面以 Redis 为例,演示如何使用长期记忆。
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="gpt-3.5-turbo",
openai_api_key="sk-xxxxxxxxxxxxxxxxxxx",
openai_api_base="https://api.aigc369.com/v1",
)
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个擅长{ability}的助手"),
MessagesPlaceholder(variable_name="history"),
("human", "{question}"),
]
)
chain = prompt | model
chain_with_history = RunnableWithMessageHistory(
chain,
lambda session_id: RedisChatMessageHistory(
session_id, url="redis://10.22.11.110:6379/3"
),
input_messages_key="question",
history_messages_key="history",
)
chain_with_history.invoke(
{"ability": "物理", "question": "地球到月球的距离是多少?"},
config={"configurable": {"session_id": "baily_question"}},
)
chain_with_history.invoke(
{"ability": "物理", "question": "地球到太阳的距离是多少?"},
config={"configurable": {"session_id": "baily_question"}},
)
chain_with_history.invoke(
{"ability": "物理", "question": "地球到他俩之间谁更近"},
config={"configurable": {"session_id": "baily_question"}},
)
LLM 的回答如下,同时关闭 session 后,直接再次提问最后一个问题,LLM 仍然能给出正确答案。只要 configurable 配置的 session_id 能对应上,LLM 就能给出正确答案。
然后,继续查看 redis 存储的数据,可以看到数据在 redis 中是以 list 的数据结构存储的。
5. 最佳实践与注意事项
5.1. Token 限制管理
随着对话轮次增加,历史记录会占用大量 Token。对于长对话场景,建议定期总结历史对话或使用 ConversationSummaryBufferMemory,在保留关键信息的同时控制 Token 消耗。如果超过上下文窗口限制,早期的对话信息将被截断,导致模型遗忘。
5.2. 隐私与安全
在使用长期记忆(如 Redis、数据库)时,请注意敏感信息的脱敏处理。避免将用户的个人隐私数据明文存储在可被其他会话访问的共享存储中。生产环境中应启用加密传输和访问控制。
5.3. 性能优化
频繁读写数据库会增加延迟。对于高频交互场景,可以考虑引入本地缓存层,仅在必要时同步至持久化存储。此外,合理设置 Memory 的 k 值可以限制返回的历史消息数量,平衡性能与效果。
6. 总结
本文介绍了 LLM 缺乏记忆功能的固有缺陷,以及记忆组件的原理,还讨论了如何利用 LangChain 给 LLM 装上记忆组件,让 LLM 能够在对话中更好地保持上下文。通过合理使用短期记忆和长期记忆组件,可以显著提升多轮对话的用户体验。希望对你有帮助!
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online