首页 > 其他分享 >基于LangChain的LLM应用开发3——记忆

基于LangChain的LLM应用开发3——记忆

时间:2023-10-22 19:04:05浏览次数:73  
标签:context input AI LangChain 对话 LLM memory 记忆

此情可待成追忆,只是当时已惘然。我们人类会有很多或美好或痛苦的回忆,有的回忆会渐渐模糊,有的回忆午夜梦醒,会浮上心头。

然而现在的大语言模型都是没有记忆的,都是无状态的,大语言模型自身不会记住和你对话之间的历史消息。根本用不着“时时勤拂拭”,天然就是“本来无一物”。每一次的请求交互、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的LLM应用开发3——记忆_LangChain

一开始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中的花括号{}是一个空词典,可以在这里传递额外的参数进行高级定制) {'history': "Human: Hi\nAI: What's up\nHuman: Not much, just hanging\nAI: Cool"}

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传进去。): {'history': 'AI: Beautiful!\nHuman: Chatbots are what?\nAI: Charming!'}

显然,没有保留最近两轮完整的对话消息,所以这个组件的效果可能没有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没有做任何的动作,把所有的消息都原样保存: {'history': "Human: Hello\nAI: What's up\nHuman: Not much, just hanging\nAI: Cool\nHuman: What is on the schedule today?\nAI: There is a meeting at 8am with your product team. \nYou will need your powerpoint presentation prepared. \n9am-12pm have time to work on your LangChain \nproject which will go quickly because Langchain is such a powerful tool. \nAt Noon, lunch at the italian resturant with a customer who is driving \nfrom over an hour away to meet you to understand the latest in AI. \nBe sure to bring your laptop to show the latest LLM demo."}

但是如果把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容量了,多次对话的性能和单次对话都是一样的。而且在大模型端,通过缓存的方式,可以极大降低应用的开销,让成本直接节省到二十分之一。

期待……

参考

  1. 短课程:https://learn.deeplearning.ai/langchain/lesson/3/memory
  2. 文档:https://python.langchain.com/docs/modules/memory/
  3. 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/

标签:context,input,AI,LangChain,对话,LLM,memory,记忆
From: https://blog.51cto.com/u_15416285/7978084

相关文章

  • 基于LangChain的LLM应用开发3——记忆
    此情可待成追忆,只是当时已惘然。我们人类会有很多或美好或痛苦的回忆,有的回忆会渐渐模糊,有的回忆午夜梦醒,会浮上心头。然而现在的大语言模型都是没有记忆的,都是无状态的,大语言模型自身不会记住和你对话之间的历史消息。根本用不着“时时勤拂拭”,天然就是“本来无一物”。每一次的......
  • 大语言模型LLM-三种模型架构
    架构:由Transformer论文衍生出来的大语言模型,主要有三种模型架构预训练目标:FLM,PLM,MLM调整:微调:Transformertransfomer可以并行地计算?  transformer中encoder模块是完全并行的,而decoder不是完全并行的。模型结构  使用原文表达如下:theencodermapsaninputsequence......
  • 使用TensorRT-LLM进行高性能推理
    LLM的火爆之后,英伟达(NVIDIA)也发布了其相关的推理加速引擎TensorRT-LLM。TensorRT是nvidia家的一款高性能深度学习推理SDK。此SDK包含深度学习推理优化器和运行环境,可为深度学习推理应用提供低延迟和高吞吐量。而TensorRT-LLM是在TensorRT基础上针对大模型进一步优化的加速推理......
  • LLM探索:为ChatGLM2的gRPC后端增加连续对话功能
    前言之前我做AIHub的时候通过gRPC的方式接入了ChatGLM等开源大模型,对于大模型这块我搞了个StarAI框架,相当于简化版的langchain,可以比较方便的把各种大模型和相关配套组合在一起使用。主要思路还是用的OpenAI接口的那套,降低学习成本,但之前为了快速开发,就只搞了个简单......
  • 《动手学深度学习 Pytorch版》 9.2 长短期记忆网络(LSTM)
    解决隐变量模型长期信息保存和短期输入缺失问题的最早方法之一是长短期存储器(longshort-termmemory,LSTM)。它与门控循环单元有许多一样的属性。长短期记忆网络的设计比门控循环单元稍微复杂一些,却比门控循环单元早诞生了近20年。9.2.1门控记忆元为了记录附加的信息,长短期记......
  • langchain
    对超长文本进行总结假如我们想要用openaiapi对一个段文本进行总结,我们通常的做法就是直接发给api让他总结。但是如果文本超过了api最大的token限制就会报错。这时,我们一般会进行对文章进行分段,比如通过tiktoken计算并分割,然后将各段发送给api进行总结,最后将各段的总......
  • 解密Prompt系列17. LLM对齐方案再升级 WizardLM & BackTranslation & SELF-ALIGN
    话接上文的指令微调的样本优化方案,上一章是通过多样性筛选和质量过滤,对样本量进行缩减,主打经济实惠。这一章是通过扩写,改写,以及回译等半监督样本挖掘方案对种子样本进行扩充,提高种子指令样本的多样性和复杂度,这里我们分别介绍Microsoft,Meta和IBM提出的三个方案。Microsoft:WizardL......
  • 七个 LLM 的狼人杀之夜;马斯克的星链残骸会“砸死人”?OpenAI 安全漏洞曝光丨RTE开发者
    开发者朋友们大家好:这里是「RTE开发者日报」,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享RTE(RealTimeEngagement)领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、「有看点的会议」,但内容仅代表编辑的个人观点,欢迎大家留......
  • LLM采样后处理总结:LLM的后处理的cpp实现
    LLM采样后处理总结:LLM的后处理的cpp实现在经过LLM的lm_head之后,会得到[batch,vocab_size]大小的矩阵向量,此时需要对输出的逻辑张量进行采样,除了beam_search的贪心策略,还有repetition_penalty、temperature、top_k、top_p等几种控制采样的方法。repetition_penaltyrepetition_p......
  • Graph RAG: 知识图谱结合 LLM 的检索增强
    本文为大家揭示NebulaGraph率先提出的GraphRAG方法,这种结合知识图谱、图数据库作为大模型结合私有知识系统的最新技术栈,是LLM+系列的第三篇,加上之前的图上下文学习、Text2Cypher这两篇文章,目前NebulaGraph+LLM相关的文章一共有3篇。GraphRAG在第一篇关于上下文......