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\)的边,即不考虑源汇点,先求出此时超级源汇的最大流,即无源汇下最大的自我调整,再加入该边,求 ...
随机推荐
- “让专业的人做专业的事”,畅捷通与阿里云的云原生故事 | 云原生 Talk
简介: 如何借助阿里云强大的 IaaS 和 PaaS 能力去构建新一代的 SaaS 企业应用,从而给客户提供更好.更强的服务,这是畅捷通一直在思考和实践的方向.最终,畅捷通选定阿里云企业级分布式应用服 ...
- 评审恩仇录——IDE也能做代码评审?
简介: 云效Codeup推出了本地IDE插件端的评审,免除了黄药师来回华山的奔波之苦 现代科技公司的同事们平日一起交流开发规约和产品需求,肩上共同扛着业务发展和同行竞争的压力,这份还书贻剑的情谊如何能 ...
- 阿里云边缘云ENS再升级 四大场景应用加速产业数字化落地
简介: 云栖大会 | 于10月21日上午举办的边缘云应用升级与技术创新论坛中,阿里云边缘云ENS产品全面升级,从边缘云产品.技术.行业应用等维度全面阐述阿里云在边缘计算领域的技术积累.产品& ...
- 通过WebRTC简单实现媒体共享
通过WebRTC简单实现媒体共享 媒体协商 在设置本地描述符(offer/answer)前,我们总是需要将媒体添加到连接中,只有这样在描述符中才能包含需要共享的媒体信息,除非你不需要共享媒体. 在实际 ...
- Ollama开发指南
安装必备工具 确保已安装以下软件的正确版本: CMake 3.24 或更高版本 Go 1.22 或更高版本 GCC 11.4.0 或更高版本 使用 Homebrew 安装这些工具(适用于macOS和L ...
- 面向教师的OBS直播速成教程
引言 本文是面向教师讲述的如何使用OBS软件进行课程直播的速成教程. 本文配套视频链接如下️ 面向教师的OBS直播教学速成教程_哔哩哔哩_bilibili 环境准备 1. 下载对应本机系统版本的并安装 ...
- vue子组件给父组件传值
子组件: <template> <div class="app"> <input @click="sendMsg" type=&q ...
- ElasticView 是一款用来监控elasticsearch状态和操作elasticsearch索引的web可视化工具。
ElasticView 是一款用来监控elasticsearch状态和操作elasticsearch索引的web可视化工具. 它由golang开发而成,具有部署方便,占用内存小等优点 es连接树管理( ...
- fastposter v2.9.1 程序员必备海报生成器
fastposter v2.9.1 程序员必备海报生成器 fastposter海报生成器是一款快速开发海报的工具.只需上传一张背景图,在对应的位置放上组件(文字.图片.二维.头像)即可生成海报. 点击 ...
- JDK源码阅读-------自学笔记(十六)(java.util.Random随机数类)
Random类简介 如果使用Math.random()计算过于复杂的话,我们可以使用例外一种方式得到随机数,即Random类,这个类是专门用来生成随机数的,并且Math.random()底层调用的就是 ...