LangChain让LLM带上记忆
最近两年,我们见识了“百模大战”,领略到了大型语言模型(LLM)的风采,但它们也存在一个显著的缺陷:没有记忆。
在对话中,无法记住上下文的 LLM 常常会让用户感到困扰。本文探讨如何利用 LangChain,快速为 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. 长期记忆和短期记忆
在解决 LLM 的记忆问题时,有两种记忆方案,长期记忆和短期记忆。
- 短期记忆:基于内存的存储,容量有限,用于存储临时对话内容。
- 长期记忆:基于硬盘或者外部数据库等方式,容量较大,用于存储需要持久的信息。
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
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({})
from langchain.prompts import ChatPromptTemplate
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个乐于助人的助手。"),
MessagesPlaceholder(variable_name="history"),
("human", "{user_input}"),
]
)
chain = prompt | model
user_input = "你知道我的名字吗?"
history = memory.load_memory_variables({})["history"]
chain.invoke({"user_input": user_input, "history": history})
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})
执行结果如下:

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": "你好,我的名字是张三,我是一名程序员。"})
res['response']
res = chain.invoke({"input":"南京是哪个省?"})
res['response']
res = chain.invoke({"input":"我告诉过你我的名字,是什么?,我的职业是什么?"})
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": "今天你好,我的名字是张三,我是你的老板"})
res['response']
res = chain.invoke({"input": "帮我安排一场今天晚上的高规格的晚饭"})
res['response']
res = chain.invoke({"input": "你还记得我叫什么名字吗?"})
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,
# 使用redis存储聊天记录
lambda session_id: RedisChatMessageHistory(
session_id, url="redis://10.22.11.110:6379/3"
),
input_messages_key="question",
history_messages_key="history",
)
# 每次调用都会保存聊天记录,需要有对应的session_id
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. 总结
本文介绍了 LLM 缺乏记忆功能的固有缺陷,以及记忆组件的原理,还讨论了如何利用 LangChain 给 LLM 装上记忆组件,让 LLM 能够在对话中更好地保持上下文。希望对你有帮助!
======>>>>>> 关于我 <<<<<<======
本篇完结!欢迎点赞 关注 收藏!!!
原文链接:https://mp.weixin.qq.com/s/bWZsP5CXYxsO6dARd1LtFQ

LangChain让LLM带上记忆的更多相关文章
- 【HDU 4940】Destroy Transportation system(无源无汇带上下界可行流)
Description Tom is a commander, his task is destroying his enemy’s transportation system. Let’s repr ...
- 记得ajax中要带上AntiForgeryToken防止CSRF攻击
经常看到在项目中ajax post数据到服务器不加防伪标记,造成CSRF攻击 在Asp.net Mvc里加入防伪标记很简单在表单中加入Html.AntiForgeryToken()即可. Html.A ...
- ZOJ 2314 带上下界的可行流
对于无源汇问题,方法有两种. 1 从边的角度来处理. 新建超级源汇, 对于每一条有下界的边,x->y, 建立有向边 超级源->y ,容量为x->y下界,建立有向边 x-> 超级 ...
- Excel等外部程序点击链接会带上IE信息的bug
今天碰到一个问题,在Excel内点击链接到默认浏览器Chrome打开,奇怪的是服务端收到的Session一直对不上. 查了很久发现这个Excel到Chrome的跳转竟然带上了IE的Cookie 和 U ...
- 切记ajax中要带上AntiForgeryToken防止CSRF攻击
在程序项目中经常看到ajax post数据到服务器没有加上防伪标记,导致CSRF被攻击,下面小编通过本篇文章给大家介绍ajax中要带上AntiForgeryToken防止CSRF攻击,感兴趣的朋友一起 ...
- echo json数据给ajax后, 需要加上exit,防止往下执行,带上其他数据,到时ajax失败
01返回json数据给ajax后需要加上exit.返回json数据前不能有其他输出 function apply(){ if(IS_POST){$info['status'] = 1; echo js ...
- idhttp提交post带参数并带上cookie
有这么一个提交连接 http://www.XXXXXX.com/test.php?p1=411328&p2=1&d1=HeroSkinList 一共有三个参数[p1] [p2] [ ...
- Django 如何让ajax的POST方法带上CSRF令牌
问题 大家知道,在大前端领域,有一种叫做ajax的东东,即“Asynchronous Javascript And XML”(异步 JavaScript 和 XML),它被用来在不刷新页面的情况下,提 ...
- 利用DNSLOG获取看不到的信息(给盲注带上眼镜)
一.前言 本文原创作者:sucppVK,本文属i春秋原创奖励计划,未经许可禁止转载! 毕业设计总算搞得差不多了,这个心累啊.这不,完成了学校的任务,赶紧回来给蛋总交作业.今天给大家分享一个姿势吧,不是 ...
- BZOJ2150 部落战争 【带上下界最小流】
题目链接 BZOJ2150 题解 复习: 带上下界网络流两种写法: 不建\(T->S\)的\(INF\)的边,即不考虑源汇点,先求出此时超级源汇的最大流,即无源汇下最大的自我调整,再加入该边,求 ...
随机推荐
- OpenSergo & CloudWeGo 共同保障微服务运行时流量稳定性
简介: 流控降级与容错是微服务流量治理中的重要的一环,同时 MSE 还提供更广范围.更多场景的微服务治理能力,包括全链路灰度.无损上下线.微服务数据库治理.日志治理等一系列的微服务治理能力. 作者:宿 ...
- RocketMQ 消息集成:多类型业务消息——定时消息
简介: 本篇将继续业务消息集成的场景,从使用场景.应用案例.功能原理以及最佳实践等角度介绍 RocketMQ 的定时消息功能. 作者:凯易.明锻 引言 Apache RocketMQ 诞生至今 ...
- 这种精度高,消耗资源少的大模型稀疏训练方法被阿里云科学家找到了!已被收录到IJCAI
简介: 论文通过减少模型稀疏训练过程中需要更新的参数量,从而减少大模型稀疏训练的时间以及资源开销,是首个大模型参数高效的稀疏训练算法PST. 作者:李深.李与超 近日,阿里云机器学习PAI关于大模型稀 ...
- NBF事件中心架构设计与实现
简介:NBF是阿里巴巴供应链中台的基础技术团队打造的一个技术PaaS平台,她提供了微服务FaaS框架,低代码平台和中台基础设施等一系列的PaaS产品,旨在帮助业务伙伴快速复用和扩展中台能力,提升研发 ...
- Serverless Devs 的官网是如何通过 Serverless Devs 部署的
简介: 只有自己吃自己的狗粮,自己做的东西才不"".Serverless Devs 自发展之处到现在,已经经历了几个月的时间,在这几个月,Serverless Devs 的成长是迅 ...
- Dataphin核心功能(四):安全——基于数据权限分类分级和敏感数据保护,保障企业数据安全
简介:<数据安全法>的发布,对企业的数据安全使用和管理提出了更高的要求.Dataphin提供基于数据分级分类和数据脱敏的敏感数据识别和保护能力,助力企业建立合规的数据安全体系,保障企业数据 ...
- Roslyn 分析器 EnforceExtendedAnalyzerRules 属性的作用
在开始编写 dotnet 的 Roslyn 分析器项目时,会被 VisualStudio 通过 RS1036 要求在项目文件配置上 EnforceExtendedAnalyzerRules 属性,本文 ...
- dotnet 通过 DockerfileContext 解决项目放在里层文件夹导致 VisualStudio 构建失败
本文告诉大家,如何解决 csproj 项目文件放入到里层的文件夹,不放在 sln 所在文件夹的第一层子文件夹,导致 VisualStudio 2022 在构建 docker 映像提示找不到文件的问题 ...
- ESP32 I2C 总线主模式通信程序
一.概述 这里主要是记录 ESP32 中进行 I2C 通行的基本程序,也可以说是 I2C 总线驱动程序,当然这里只是作为主模式,从模式我还没需要这个需求,以后有机会贴上.此笔记的主要目的是防止以后写 ...
- 02、Linux 排查
Linux 分析排查 1.敏感文件信息 1.1.tmp 目录 /tmp:临时目录文件,每个用户都可以对它进行读写操作.因此一个普通用户可以对 /tmp 目录执行读写操作(ls -alt) 筛查 /tm ...