​人工智能 | LangChain核心模块Memory:开启AI学习的黄金时代

简介

大多数大模型应用中都包含对话功能,而对话功能的基础就是参与者能够基于已经发生的对话和获取到的知识产生新的对话内容。

更复杂一点的场景中对话者甚至需要具有一个完整的对世界的认知,再根据对话中的信息对认知不断的进行迭代更新。

在LangChain中,这种能力被称为Memory。LangChain根据实际需要的场景,提供了许多用来支持大模型应用实现这种能力的类和方法。

Memory 在设计的过程中,主要考虑以下两个方面:

  1. 上下文管理能力。
  2. Token的消费控制。

上下文管理能力

如上所示,如果具备上下文管理能力,表示在多个轮次的对话中,能够自然而然的反应到前面对话提及的内容。

如果不具备这个能力,那么就如同下面的这个场景一样,每一次的新的对话,都是重新开启和前面的内容不会有任何的关联关系。

Token的消费控制

由于在调用大模型的时候,无论是GPT4 还是GPT3.5均存在以下限制:

1.每条Token都是收费的,每个账号只有一开始拥有5美元的免费Token。

2.即使有的人或公司财大气粗,还会面临第二个问题,每个账号的Token的使用具有上限。

所以,基于以上的限制,Memory 还有一个作用,就是在使用过程中,尽量减少Token的消费。

实践演练

在 LangChain 解决这一问题的过程中,有几个需要考虑到的问题:

  1. 保证完成的上下文。
  2. 需要考虑到Token的消费次数,毕竟Token的花费是巨大的。

基于以上的问题, LongChain 的 Memory 模块设计出来了以下几个场景。

ConversationBufferMemory-简单实现对话

ConversationBufferMemory 的主要作用在于实现基本的对话功能。基本没有什么使用上的限制。而且可以轻松的获取到对话的上下文。

具体的使用操作代码如下:

1.创建ChatOpenAI的实例对象:

# LangChain相关导入
from langchain import ConversationChain
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferMemory

key = "sk-xxx"

# temperature用来设置大模型返回数据的随机性和创造性,较低的数值返回的数据就更贴近现实。
chat = ChatOpenAI(temperature=0.0, openai_api_key=key)

2.通过ConversationBufferMemory创建带有对话记录功能的实例,通过ConversationChain创建对话链实例。

# 创建带有对话记录功能的实例
memory = ConversationBufferMemory()
# 生成对话链实例,用于后续对话使用
# 通过verbose=True参数可以看到LangChain具体的实现的方式
conversation = ConversationChain(llm=chat, memory=memory)

3.通过 Conversation 实例调用Predict方法,将对话链中的信息存放在该实例中。同时可以通过 memory.buffer 获取所有的对话记录信息:

# 几轮对话之后,再询问第一个问题中给出的信息,还是可以正常获取到
res = conversation.predict(input="你好,我叫天马")
print(res)
res = conversation.predict(input="1+1等于几?")
print(res)
res = conversation.predict(input="今天是周二,昨天是周几?")
print(res)
res = conversation.predict(input="我的名字是什么?")
print(res)
# 输出已经保存的对话记录
print(memory.buffer)

最后再来看一下完整版本的代码:

# LangChain相关导入
from langchain import ConversationChain
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferMemory

key = "sk-xxx"
# 创建openai调用实例
# temperature用来设置大模型返回数据的随机性和创造性,较低的数值返回的数据就更贴近现实。
chat = ChatOpenAI(temperature=0.0, openai_api_key=key)
# 创建带有对话记录功能的实例
memory = ConversationBufferMemory()
# 生成对话链实例,用于后续对话使用
# 通过verbose=True参数可以看到LangChain具体的实现的方式
conversation = ConversationChain(llm=chat, memory=memory)
# 几轮对话之后,再询问第一个问题中给出的信息,还是可以正常获取到
res = conversation.predict(input="你好,我叫天马")
print(res)
res = conversation.predict(input="1+1等于几?")
print(res)
res = conversation.predict(input="今天是周二,昨天是周几?")
print(res)
res = conversation.predict(input="我的名字是什么?")
print(res)
# 输出已经保存的对话记录
print(memory.buffer)

CONVERSATIONBUFFERMEMORY - 预制信息功能

在开发人工智能应用的过程中,常常需要开发者提前给用户明确角色信息。例如我们现在开发的是一个人工智能的答疑小助手,那它就有一个非常明确的身份-答疑小助手。

而这个角色声明的部分,一般可以通过 LangChain 的save_context方法提前塞入一些提示词。

首先先来看没有添加预制信息的代码以及其返回信息:

# LangChain相关导入
from langchain import ConversationChain
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferMemory

key = "sk-xxx"

# 创建openai调用实例
# temperature用来设置大模型返回数据的随机性和创造性,较低的数值返回的数据就更贴近现实。
chat = ChatOpenAI(temperature=0.0, openai_api_key=key)
# 创建带有对话记录功能的实例
memory = ConversationBufferMemory()
# 生成对话链实例,用于后续对话使用
# 通过verbose=True参数可以看到LangChain具体的实现的方式
conversation = ConversationChain(llm=chat, memory=memory)
print('无预制信息对话开始:')
# 无预制信息时,相关对话结果是模型直接生成的数据
res = conversation.predict(input="你叫什么")
print(res)
res = conversation.predict(input="你从哪来?")
print(res)
print('无预制信息对话结束')
# 清空之前的对话数据,避免数据互相干扰
memory.clear()

返回值信息为:

无预制信息对话开始:
我叫OpenAI,我是一个人工智能助手。有什么我可以帮助你的吗?
我是由OpenAI开发的,所以可以说我来自OpenAI。OpenAI是一个人工智能研究实验室,致力于推动人工智能技术的发展和应用。我们的团队由一群研究人员和工程师组成,他们致力于开发出更智能、更有用的人工智能系统。
无预制信息对话结束

接下来我们来看一下添加了预制信息配置的代码,以及其效果

#...省略数据声明的部分...
print('有预制信息对话开始:')
# 内置生成对话记录,可以给对话内置一些信息,用于引导AI在后续对话时使用预置的数据回答
memory.save_context(inputs={'input': "你现在模拟的是一位隶属于霍格沃兹测试开发学社的智能小助手,名字是: 小助手-赫敏"},
                    outputs={'output': '好的'})
# 几轮对话之后,再询问第一个问题中给出的信息,还是可以正常获取到
# 通过verbose=True参数可以看到具体的实现过程
res = conversation.predict(input="你叫什么")
print(res)
res = conversation.predict(input="你是哪个组织的?")
print(res)

返回值信息为:

有预制信息对话开始:
我叫小助手-赫敏。
我隶属于霍格沃兹测试开发学社。

保存过多的上下文对话,虽然会带来比较好的聊天体验,但是会导致每次请求api的数据量过大,不仅会导致消耗更多的token(花更多的钱),也会导致请求的数据很容易超过模型的最大输入token限制(GPT3.5普通模型最大为4096个token)。所以接下来其他的 Memory 都是为了解决此问题。

ConversationBufferWindowMemory-带有条数限制的Memory

如果已经能够明确每次对话的轮次,可以使用 ConversationBufferWindowMemory 。因为它可以在实例化的时候指定上下文保存的数量。

  • ConversationBufferWindowMemory(k=1)获取一个只保存一条数据的实例信息:
memory = ConversationBufferWindowMemory(k=1)

输出已经保存的对话记录,虽然代码中保存了两条对话数据,但是实际保存下来的只有最后一条:

memory.save_context(inputs={'input': "你好,我叫天马"}, outputs={'output': '好的'})
memory.save_context(inputs={'input': "今天天气很不错"}, outputs={'output': '是的,今天晴空万里,适合出游'})
print("输出已经保存的对话数据")
print(memory.buffer)

查看完整代码示例:

# LangChain相关导入
from langchain import ConversationChain
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferWindowMemory

key = "sk-xxx"

# 创建openai调用实例
# temperature用来设置大模型返回数据的随机性和创造性,较低的数值返回的数据就更贴近现实。
chat = ChatOpenAI(temperature=0.0, openai_api_key=key)
# 创建带有对话记录功能的实例,k=1表示只保存最近一轮的对话记录
memory = ConversationBufferWindowMemory(k=1)
memory.save_context(inputs={'input': "你好,我叫天马"}, outputs={'output': '好的'})
memory.save_context(inputs={'input': "今天天气很不错"}, outputs={'output': '是的,今天晴空万里,适合出游'})
print("输出已经保存的对话数据")
# 输出已经保存的对话记录,虽然代码中保存了两条对话数据,但是实际保存下来的只有最后一条
print(memory.buffer)
# 清空之前的对话数据,避免数据互相干扰
memory.clear()
# 生成对话链实例,用于后续对话使用
# 通过verbose=True参数可以看到LangChain具体的实现的方式
conversation = ConversationChain(llm=chat, memory=memory)
print(f"对话开始:")
# 几轮对话之后,再询问第一个问题中给出的信息,发现信息已经丢失了
res = conversation.predict(input="你好,我叫天马")
print(res)
res = conversation.predict(input="1+1等于几?")
print(res)
# 大模型已经无法获取到一条对话记录之前的信息,说明k=1的限制生效了
res = conversation.predict(input="我的名字是什么?")
print(res)
print("输出已经保存的对话数据")
# 输出已经保存的对话记录
print(memory.buffer)

如果在实际使用过程中,既不能确定每次对话的轮次,同时又需要节省Token的次数。那么就需要使用其他的两个方法:

  • ConversationTokenBufferMemory
  • ConversationSummaryBufferMemory

ConversationTokenBufferMemory:带有Token数限制的Memory

由于大多数大模型对于输入的数据存在长度上的限制,以及计费时使用的单位都是Token,所以限制Token数量可以达到以下两个目的:

1.更直观的对保存上下文的数量进行控制。

2.避免某条对话过长导致整体长度过大。

  • 通过ConversationTokenBufferMemory(llm=chat, max_token_limit=50) 限制Token的数量为50条。
  • 输出已经保存的对话记录,虽然代码中预置了两条对话数据,但是第一条对话的输入内容因为超过token已经从记录中被移除了。
memory.save_context(inputs={'input': "你好,我叫天马"}, outputs={'output': '好的'})
memory.save_context(inputs={'input': "今天天气很不错"}, outputs={'output': '是的,今天晴空万里,适合出游'})
print("输出已经保存的对话数据")
print(memory.buffer)

查看完整代码示例:

# LangChain相关导入
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationTokenBufferMemory

key = "sk-xxx"

# 创建openai调用实例
# temperature用来设置大模型返回数据的随机性和创造性,较低的数值返回的数据就更贴近现实。
chat = ChatOpenAI(temperature=0.0, openai_api_key=key)
# 创建带有对话记录功能的实例,max_token_limit设置了能够保存的最大token数量,
# LangChain会根据token限制,在保证完整性的情况下删除掉超过token的对话记录(不会截断对话内容)
# 由于不同的模型对于文本转换为token的规则不完全相同,所以在使用ConversationTokenBufferMemory时需要指定模型实例
memory = ConversationTokenBufferMemory(llm=chat, max_token_limit=50)
memory.save_context(inputs={'input': "你好,我叫天马"}, outputs={'output': '好的'})
memory.save_context(inputs={'input': "今天天气很不错"}, outputs={'output': '是的,今天晴空万里,适合出游'})
print("输出已经保存的对话数据")
# 输出已经保存的对话记录,虽然代码中预置了两条对话数据,但是第一条对话的输入内容因为超过token已经从记录中被移除了
print(memory.buffer)

ConversationSummaryBufferMemory-使用大模型总结Summary

前几个 Memory 存在以下几个问题:

1.ConversationBufferWindowMemory可能会因为每条对话的数据量过大而导致超过token限制,而且对话的轮次也是设置不够灵活

2.基于token数量限制的memory又可能因为最新的消息中内容过长,丢失前几次对话中的一些重要信息。

所以LangChain提供了一种基于保存上下文概括信息的memory,这个memory利用大模型本身擅长处理分析对话消息的能力,将历史对话数据使用大模型概括成简短的文本,在尽量少丢失关键信息的情况下,同时也减少了历史信息占用的token数量。

  • 通过 ConversationSummaryBufferMemory(llm=chat, max_token_limit=100) 限制Token的数量为100条。
  • 输出已经保存的对话记录,由于之前的对话由于长度过长,已经通过LLM将对话信息进行了总结,并保存到了System中:
memory.save_context(inputs={'input': "你好,我叫天马"}, outputs={'output': '好的'})
memory.save_context(inputs={'input': "今天天气很不错"}, outputs={'output': '是的,今天晴空万里,适合出游'})
schedule = "今天的行程安排如下:早上6点起床,洗漱完毕。7点开始做饭,8点吃完饭准备出门,骑自行车大约1小时,9点到达公司,上午进行大模型教程的编写,大约3小时。中午12点吃饭后午休"
memory.save_context(inputs={'input': "今天的日程安排是什么?"}, outputs={'output': schedule})
print("输出已经保存的对话数据")
print(memory.buffer)
# 通过读取memory里面保存的全部数据,可以发现history中存在System数据,保存了已经被总结过的对话信息
print(memory.load_memory_variables({}))

查看完整代码示例:

# LangChain相关导入
from langchain import ConversationChain
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationSummaryBufferMemory

key = "sk-xxx"

# 创建openai调用实例
# temperature用来设置大模型返回数据的随机性和创造性,较低的数值返回的数据就更贴近现实。
chat = ChatOpenAI(temperature=0.0, openai_api_key=key)
# 创建带有对话记录功能的实例,max_token_limit设置了能够保存的最大token数量
# LangChain会根据token限制,在保证完整性的情况下将超出的
# 由于不同的模型对于文本转换为token的规则不完全相同,所以在使用ConversationTokenBufferMemory时需要指定模型实例
memory = ConversationSummaryBufferMemory(llm=chat, max_token_limit=100)
memory.save_context(inputs={'input': "你好,我叫天马"}, outputs={'output': '好的'})
memory.save_context(inputs={'input': "今天天气很不错"}, outputs={'output': '是的,今天晴空万里,适合出游'})
schedule = "今天的行程安排如下:早上6点起床,洗漱完毕。7点开始做饭,8点吃完饭准备出门,骑自行车大约1小时,9点到达公司,上午进行大模型教程的编写,大约3小时。中午12点吃饭后午休"
memory.save_context(inputs={'input': "今天的日程安排是什么?"}, outputs={'output': schedule})
print("输出已经保存的对话数据")
# 输出已经保存的对话记录,会发现保存的对话数据为空,因为之前的对话由于长度过长,已经通过LLM将对话信息进行了总结,并保存到了System中
print(memory.buffer)
# 通过读取memory里面保存的全部数据,可以发现history中存在System数据,保存了已经被总结过的对话信息
print(memory.load_memory_variables({}))
conversation = ConversationChain(llm=chat, memory=memory)
print(f"对话开始:")
res = conversation.predict(input="我的名字是什么?")
print(res)
# 之前的上下文数据通过system+简述的方式传递给大模型,保证了关键信息不丢失
res = conversation.predict(input="我几点吃的早饭?")
print(res)
# 进行两轮对话之后,由于本次对话数据较短,没有触发token的限制,所以对话数据还是以原本的方式保存在history中
print(memory.buffer)
print(memory.load_memory_variables({}))

更多Python基础语法趣味学习视频,请点击!

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值