基于LangChain的LLM应用开发3——记忆
此情可待成追忆,只是当时已惘然。我们人类会有很多或美好或痛苦的回忆,有的回忆会渐渐模糊,有的回忆午夜梦醒,会浮上心头。
然而现在的大语言模型都是没有记忆的,都是无状态的,大语言模型自身不会记住和你对话之间的历史消息。根本用不着“时时勤拂拭”,天然就是“本来无一物”。每一次的请求交互、api调用都是独立的,完全没有关联。那些聊天机器人看起来有记忆,是因为借助代码的帮助,提供历史消息作为和LLM对话的上下文。嗯,就跟我们大脑不太够用了,要拿小本本或者打开Obsidian/Notion/语雀……来查找一样。(你去拜访某些单位,还可以看到前台拿着一本已经翻到包浆的小本子来查电话。)
所以,现在的大语言模型,就跟福尔摩斯一样,可能作为推理引擎更加好用:只要提供足够的上下文信息,那么即使坐在家中,也比愚蠢的苏格兰警探更清楚案情。(可以考虑打造一个叫“夏洛克”的大语言模型? )运筹帷幄之中,决胜千里之外。
本节我们就来看一下LangChain提供的4种Memory(记忆)组件(Vector data memory和Entity memory不展开),每种组件都有其适用场景。
主要的记忆组件
- ConversationBufferMemory
这个记忆组件允许储存对话的消息,并且可以把消息抽取到一个变量。
- ConversationBufferWindowMemory
这个记忆会保持K轮对话的列表。只保存最近的K轮对话。旧对话会清除。
- ConversationTokenBufferMemory
这个记忆组件跟ConversationBufferWindowMemory差不多,同样把旧对话清除,只是是按Token的长度限制。
- ConversationSummaryMemory
这个记忆组件会调用大语言模型,对旧的会话进行总结。
- Vector data memory
这个组件把文本(来自会话或者其他地方的)保存到向量数据库,检索最相关的文本块。
- Entity memories
调用LLM,记住关于特定实体的细节信息。
可以同时使用多个记忆组件,如调用会话记忆+实体记忆来检索个人信息。还可以将会话内容保存到传统数据库(如键值存储Redis或者关系数据库mysql等等),应用要落地这个是必不可少的。
下面来具体看每个组件的例子。
同样是先通过.env文件初始化环境,具体操作参考上一篇。
import os
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv()) # read local .env file
import warnings
warnings.filterwarnings('ignore')
deployment = "gpt-35-turbo"
model = "gpt-3.5-turbo"
ConversationBufferMemory
# from langchain.chat_models import ChatOpenAI
from langchain.chat_models import AzureChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
llm = AzureChatOpenAI(temperature=0.0, model_name=model, deployment_name=deployment)
memory = ConversationBufferMemory()
conversation = ConversationChain(
llm=llm,
memory = memory,
verbose=True #设置为True,可以看到对话的详细过程
)
conversation.predict(input="你好,我是西滨。")
conversation.predict(input="1+1等于多少?")
conversation.predict(input="你还记得我的名字?")
这里会创建ConversationChain,Chain是LangChain的核心概念,后面会详细讲述,这里先不管。memory = ConversationBufferMemory() 创建一个ConversationBufferMemory传给ConversationChain,我们打开verbose,看一下具体的输出。
一开始LangChain自动发送一段提示(***The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.)***过去开始对话,后面我们可以看到,每次对话的信息都会自动发过去,经过一轮对话之后,再问Ai“你还记得我的名字?”,Ai毫不犹豫的回答:“当然记得!你是西滨。”
上面我们用memory这个变量来保存记忆,如果输出memory.buffer,可以看到对话的所有消息:
Human: 你好,我是西滨。
AI: 你好,西滨!很高兴认识你。我是一个AI助手,可以回答你的问题和提供帮助。有什么我可以帮你的吗?
Human: 1+1等于多少?
AI: 1+1等于2。
Human: 你还记得我的名字?
AI: 当然记得!你是西滨。
可以手工调用save_context方法来把上下文信息传进去:
memory = ConversationBufferMemory()
memory.save_context({"input": "Hi"},
{"output": "What's up"})
memory.save_context({"input": "Not much, just hanging"},
{"output": "Cool"})
memory.load_memory_variables({})
调用load_memory_variables({})来查看对应的记忆内容。(load_memory_variables中的花括号{}是一个空词典,可以在这里传递额外的参数进行高级定制)
ConversationBufferMemory可以存储到目前为止的对话消息,看起来很完美,但是随着对话越来越长,所需的记忆存储量也变得非常大,而向LLM发送大量Token的成本也会增加(现在大模型一般按照Token的数量收费,而且还是双向收费,你懂的)。
解决这个问题,LangChain有三个不同的记忆组件来处理。
ConversationBufferWindowMemory
ConversationBufferWindowMemory 只保留一个窗口的记忆,也就是只保留最后若干轮对话消息。注意,这个跟微软的Bing Chat不太一样,微软是每个话题保留30轮,30轮对话一到,自动转向新话题;ConversationBufferWindowMemory的策略就是计算机算法典型的“滑动窗口”,永远都是保留最新的若干轮对话。
from langchain.memory import ConversationBufferWindowMemory
memory = ConversationBufferWindowMemory(k=1)
memory.save_context({"input": "Hi"},
{"output": "What's up"})
memory.save_context({"input": "Not much, just hanging"},
{"output": "Cool"})
memory.load_memory_variables({})
上面的k设为了1,那只保留最后1轮对话,对话信息就只剩下:
{'history': 'Human: Not much, just hanging\nAI: Cool'}
前面的”Human:Hi?\nAI: What’s up“ 已经去掉了。
实际应用,我们会更多的采用ConversationBufferWindowMemory(K通常会设得比较大,需要根据具体情景调整),而不是ConversationBufferMemory,可以防止记忆存储量随着对话的进行而无限增长,同时也有比较好的效果。。
ConversationTokenBufferMemory
ConversationTokenBufferMemory通过另一种方式来解决记忆存储量增长的问题:限制保存在记忆的令牌数量。
要先安装tiktoken,底层用于计算Token数目:
!pip install tiktoken
from langchain.memory import ConversationTokenBufferMemory
llm = AzureChatOpenAI(temperature=0.0, model_name=model, deployment_name=deployment)
memory = ConversationTokenBufferMemory(llm=llm, max_token_limit=30)
memory.save_context({"input": "AI is what?!"},
{"output": "Amazing!"})
memory.save_context({"input": "Backpropagation is what?"},
{"output": "Beautiful!"})
memory.save_context({"input": "Chatbots are what?"},
{"output": "Charming!"})
memory.load_memory_variables({})
注意上面token的限制设为了30,则最终保留下来的消息只有这些,保证总的消息内容长度不超过设置的令牌限制值max_token_limit。(这里涉及到计算Token的算法,不能按字符数计算。每种LLM计算Token的算法都不一样,所以调用ConversationTokenBufferMemory要把llm传进去。):
显然,没有保留最近两轮完整的对话消息,所以这个组件的效果可能没有ConversationBufferWindowMemory好,但是调用Api的性价比高一点。
ConversationSummaryMemory
ConversationSummaryMemory可以算是ConversationTokenBufferMemory的变体,同样是按令牌数限制,但是当它发现令牌数超了,不是把旧的消息丢掉,而是把当前所有的消息进行摘要,直到摘要的文本令牌数不超过设置的限制。
from langchain.memory import ConversationSummaryBufferMemory
# create a long string
schedule = """There is a meeting at 8am with your product team.
You will need your powerpoint presentation prepared.
9am-12pm have time to work on your LangChain
project which will go quickly because Langchain is such a powerful tool.
At Noon, lunch at the italian resturant with a customer who is driving
from over an hour away to meet you to understand the latest in AI.
Be sure to bring your laptop to show the latest LLM demo."""
memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=400)
memory.save_context({"input": "Hello"}, {"output": "What's up"})
memory.save_context({"input": "Not much, just hanging"},
{"output": "Cool"})
memory.save_context({"input": "What is on the schedule today?"},
{"output": f"{schedule}"})
memory.load_memory_variables({})
如果max_token_limit设置为400,因为400个令牌足以存储所有的文本,可以看到ConversationSummaryMemory没有做任何的动作,把所有的消息都原样保存:
但是如果把max_token_limit设置为100,ConversationSummaryMemory会调用LLM,把消息保存为下面的摘要:
{'history': 'System: The human and AI exchange greetings. The human mentions that they are not doing much and the AI responds with a casual remark. The human then asks about their schedule for the day. The AI provides a detailed schedule, including a meeting with the product team, working on the LangChain project, and a lunch meeting with a customer interested in AI. The AI emphasizes the importance of bringing a laptop to showcase the latest LLM demo during the lunch meeting.'}
如果是问没那么精确的问题,LLM仍然可以回答:
conversation = ConversationChain(
llm=llm,
memory = memory,
verbose=False
)
conversation.predict(input="What would be a good demo to show?")
'A good demo to show would be the latest Language Learning Model (LLM) demo. It showcases real-time translations, pronunciation feedback, grammar suggestions, interactive exercises, and quizzes. These features make it an ideal way to highlight the capabilities of our AI technology in the education and language learning industry.’
但是再问具体的信息,由于摘要已经丢失了详细信息,所以LLM开始胡说八道。
conversation.predict(**input="When will the meeting hold today?"**)
'The meeting with the product team is scheduled for 10:00 AM today.’
展望
虽然LangChain提供了不同的记忆组件,但是真正用起来还是有点麻烦,好消息是根据路透社报道,11月6日在OpenAI的开发者大会上,ChatGPT将推出带有记忆能力的大模型,也就是有状态的API接口。[3]
和大模型一次对话的内容量称之为Context,是一个很重要的指标。Context越大,意味着你可以和大模型对话的次数越多,传递的信息越多,那么大模型反馈给你的结果才会更加准确。如果Context不够大,那么你只能抛弃一些信息,自然拿到的结果就会产生偏差。
现在GPT-4默认的Context是8K,如果要支持32K的Context,则价格直接翻倍。Claude大模型支持的Context更大,可以支持100K的Context,所以Claude对于很多PDF文档阅读支持得很好。还有国产的Kimi Chat,据说支持约 20 万汉字的上下文,2.5 倍于 Anthropic 公司的 Claude-100k(实测约 8 万字),8 倍于 OpenAI 公司的 GPT-4-32k(实测约 2.5 万字)。可以说Context容量大小,也是大模型的核心竞争力之一。
但这一切即将成为过去,GPT即将支持记忆能力。也就是GPT会通过缓存的方式记录之前和用户的对话。你不需要那么大的Context容量了,多次对话的性能和单次对话都是一样的。而且在大模型端,通过缓存的方式,可以极大降低应用的开销,让成本直接节省到二十分之一。
期待……
参考
- 短课程:https://learn.deeplearning.ai/langchain/lesson/3/memory
- 文档:https://python.langchain.com/docs/modules/memory/
- Report: OpenAI to Introduce Updates to Make AI Models More Affordable: https://www.pymnts.com/news/artificial-intelligence/2023/openai-introduce-updates-make-ai-models-more-affordable/
基于LangChain的LLM应用开发3——记忆的更多相关文章
- 基于CkEditor实现.net在线开发之路(7)列表页面开发动作介绍
一个列表页面不止是查询,它也包含了很多业务上功能的实现,这些业务功能的实现的逻辑我称之为动作.如触发单击按钮删除数据,更改业务表数据,调用webService,调用WCF接口,弹出新窗体新增.修改.查 ...
- 基于ionic+angulajs的混合开发实现地铁APP
基于ionic+angulajs的混合开发实现地铁APP 注:本博文为博主原创,转载时请注明出处. 项目源码地址:https://github.com/zhangxy1035/SubwayMap 一. ...
- 基于ThinkPHP3的微信平台开发_1
微信公众平台是个好东西,具体的就不说了,我直接说技术>_< 下图为目录结构一览: 微信开发 - 文件目录结构 平台功能: 此次开发的平台是面向多微信公众号.微信多公众号主(下面简称号主)的 ...
- Python黑帽编程1.2 基于VS Code构建Python开发环境
Python黑帽编程1.2 基于VS Code构建Python开发环境 0.1 本系列教程说明 本系列教程,采用的大纲母本为<Understanding Network Hacks Atta ...
- 基于CkEditor实现.net在线开发之路(1)
我以前的公司使用office sharepoint designer为界面设计器,嵌套各种自定义控件,进行各种管理软件,工作流的开发,遇到比较复杂的逻辑,则采用本地写类库,生成DLL上传到服务器,通过 ...
- 基于Eclipse的Hadoop应用开发环境配置
基于Eclipse的Hadoop应用开发环境配置 我的开发环境: 操作系统ubuntu11.10 单机模式 Hadoop版本:hadoop-0.20.1 Eclipse版本:eclipse-java- ...
- 基于ArcGIS Viewer for Flex开发的一款跨平台的应用程序
特点: 1.基于ArcGIS Viewer for Flex开发的一款跨平台的应用程序: -(IBAction) showTOC:(id)sender { if (_tocViewController ...
- 基于webpack的前端工程化开发解决方案探索(一):动态生成HTML(转)
1.什么是工程化开发 软件工程的工程化开发概念由来已久,但对于前端开发来说,我们没有像VS或者eclipse这样量身打造的IDE,因为在大多数人眼中,前端代码无需编译,因此只要一个浏览器来运行调试就行 ...
- 【转贴】-- 基于QT的跨平台应用开发
原帖地址:http://www.cnblogs.com/R0b1n/p/4106613.html 1 Qt简介 Qt是1991年奇趣科技开发的一个跨平台的C++图形用户界面应用程序框架.它提供给应用程 ...
- 基于AgileEAS.NET企业应用开发平台的分布式解决方案
开篇 分布式应用 AgileEAS.NET基于Microsoft .Net构件技术而构建,Microsoft .Net最吸引人的莫过于分布式应用技术,基已经提供了XML WebService. .Ne ...
随机推荐
- Python + unittest + ddt + HTMLTestRunner + log + excel + mysql + 企业微信通知, 接口自动化框架V2.0,支持多业务处理,仅需维护 excel 用例,无需要编写代码
Python + unittest + ddt + HTMLTestRunner + log + excel + mysql + 企业微信通知 + Jenkins 实现的接口自动化框架. 项目介绍 接 ...
- ASP.NET MVC4 学习笔记-1
初学ASP.NET MVC,通过博客来记录自己的学习笔记! 创建一个新的 ASP.NET MVC 项目 在新建项目中选择 ASP.NET MVC4 Web Application,项目类型为空,视图引 ...
- SpringBoot 使用 Sa-Token 实现账号封禁、分类封禁、阶梯封禁
一.需求分析 之前的章节中,我们学习了 踢人下线 和 强制注销 功能,用于清退违规账号.在部分场景下,我们还需要将其 账号封禁,以防止其再次登录. Sa-Token 是一个轻量级 java 权限认证框 ...
- Java stream流使用
1.使用filter()过滤List //查找身高在1.8米及以上的学生 List<StudentInfo> boys = studentList.stream().filter(s-&g ...
- CSS3属性 2D转换
* { margin: 0; padding: 0 } table { border-spacing: 0; border-collapse: collapse; margin: 10px auto ...
- 在 ubuntu 中安装或升级最新版本的 Elasticsearch
1. 安装 java 8 首先最新版本的elasticsearch需要java8的支持,那先来安装一下. $ sudo add-apt-repository -y ppa:webupd8team/ja ...
- 使用logrotate定期切割nginx日志
前言 默认情况下,nginx的日志都会写到access.log文件中,访问流量大的话,日志文件很快就会膨胀到几十G,不方便分析处理,也占用硬盘空间.借助linux自带的logrotate工具可以很方便 ...
- 最常用的Linux命令
1. tar 创建一个新的tar文件 $ tar cvf archive_name.tar dirname/ 解压tar文件 $ tar xvf archive_name.tar 查看tar文件 $ ...
- 搭建LNMP 架构
搭建LNMP 架构 环境准备 lnmp 需要 安装 nginx mysql php 软件 #关闭防火墙 systemctl disable --now firewalld #临时禁用SELinux的强 ...
- Redis系列22:Redis 的Pub/Sub能力
Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) Redis系列5: ...