前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。
在我们通过 web 端的聊天界面与 AI 对话时,AI 会记住你说过的话。这样,你可以在对话中引用之前的话语,或者在之后的对话中提到之前的话语。
但是如果我们像下面这样调用 API 的时候,就会发现 AI 不会记住我们之前说过的话:
from langchain_openai import ChatOpenAI
chat = ChatOpenAI(
model="yi-large",
temperature=0.3,
max_tokens=200,
api_key='your key',
base_url="https://api.lingyiwanwu.com/v1",)
response = chat.invoke('今天广州天气晴朗,26~35摄氏度。')print(response.content)
response = chat.invoke('今天广州适合穿什么?')print(response.content)
输出:
这句话的意思是今天广州的天气非常好,晴朗无云,气温在26摄氏度到35摄氏度之间。这是一个适合户外活动的好天气,但也要注意防晒和补水,因为气温较高。
很抱歉,我无法提供实时天气信息或建议。要了解今天的广州适合穿什么,您可以查看当地的天气预报,了解当前的气温、湿度和天气状况,然后根据这些信息选择合适的衣物。
通常,广州属于亚热带季风气候,夏季炎热潮湿,冬季温和,春秋季节宜人。根据季节和天气预报,您可以选择穿短袖、长袖、薄外套或厚外套等。
别忘了查看是否需要携带雨具,因为广州的降雨量也比较丰富。
虽然我们告诉了 LLM 今天广州的天气,但是在第二次调用的时候,AI 并没有记住我们之前说过的话,所以不能依据当前的天气状况给我提供穿衣建议。
为什么 AI 不会记住我说过的话
这是因为大模型本身并不具备记忆功能。在我们每次使用大模型的 API 的时候,它都是基于训练模型时候的数据以及我们传递的信息来进行推理的。
如果让大模型记住我们说过的话,那么它需要存储的信息量会非常庞大,这样的成本是非常高昂的。
同时,如果每一次调用的时候,都在一个庞大的上下文中进行推理,那么推理的时间也会非常长,消耗的资源会非常多。
所以,大模型通常是不会记住我们说过的话的。
解决办法:我们自己记住
既然大模型记不住我们说过的话,那唯一的办法就是我们自己记住,然后下次调用的时候,将之前的话语传递给 AI。
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
chat = ChatOpenAI(
model="yi-large",
temperature=0.3,
max_tokens=200,
api_key='your key',
base_url="https://api.lingyiwanwu.com/v1",)
messages =[
HumanMessage('今天广州天气晴朗,26~35摄氏度。'),]
response = chat.invoke(messages)
messages.append(AIMessage(response.content))
messages.append(HumanMessage('今天广州适合穿什么?'))print(messages)
response = chat.invoke(messages)print(response.content)
输出:
[
HumanMessage(content='今天广州天气晴朗,26~35摄氏度。'),
AIMessage(content='这句话的意思是,今天广州的天气非常好,晴朗无云,气温在26摄氏度到35摄氏度之间。这是一个非常适合户外活动的天气,既不太热也不太冷。'),
HumanMessage(content='今天广州适合穿什么?')
]
根据您提供的信息,今天广州的天气晴朗,气温在26到35摄氏度之间。这个温度范围适合穿着轻薄、透气的衣物。以下是一些建议:
1. 上衣:可以选择短袖T恤、薄衬衫或棉质衣物,以保持凉爽。
2. 下装:可以穿短裤、七分裤或轻薄的牛仔裤。
3. 鞋子:舒适的凉鞋、帆布鞋或运动鞋都是不错的选择。
4. 配件:如果需要外出,可以戴上一顶遮阳帽和太阳镜,以保护皮肤和眼睛不受紫外线伤害。
5. 防晒:由于天气晴朗,紫外线可能较强,建议涂抹防晒霜以保护皮肤。
请根据您的个人舒适度和活动需求来调整着装。如果您的活动包括室内外结合,可以准备一件轻薄的外套或披肩,以
输出的第一部分是我们问第二个问题的上下文,第二部分是 AI 的回答。
在这个例子中,我们将之前的对话保存在了
messages
中,然后在下一次调用的时候,将之前的对话传递给 AI。
当然,这里只是举个例子。真实使用中,我们可能会将一大段信息交给 LLM,然后让它来帮我们进行分析推理,然后可以问它问题从而得到答案。
对话内容记忆的抽象
上一个例子中,我们是每一次请求和响应的内容都保存在了
messages
中,然后传递给 AI。
这样操作可能会比较麻烦,因为消息历史会逐渐增长,直到达到 LLM 的最大上下文长度。
这个时候,我们就需要删除一部分历史消息,从而保证 LLM 可以正常处理我们的请求。
除了最大上下文限制的原因,太长的上下文也会带来大量的
token
消耗,这样会增加我们的成本。
因此,我们非常需要定期对历史消息进行处理,删除一部分意义不大的历史消息,或者删除那些最久远的消息,只保留最近的消息。
这跟人类的记忆一样,我们对近期发生的事情记忆深刻,而对很久远的事情记忆模糊。
ConversationBufferWindowMemory
为了解决这个问题,
langchain
提供了一些处理历史消息的工具。
比如适合我上面说的这个场景的
RunnableWithMessageHistory
,它可以记住指定次数的消息,然后在超过指定次数的时候,删除最早的消息。
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import AIMessage
from langchain_core.runnables import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationBufferWindowMemory
llm = ChatOpenAI(
model="yi-large",
temperature=0.3,
max_tokens=200,
api_key='your key',
base_url="https://api.lingyiwanwu.com/v1",)
store ={}# memory is maintained outside the chaindefget_session_history(session_id:str)-> InMemoryChatMessageHistory:if session_id notin store:
store[session_id]= InMemoryChatMessageHistory()return store[session_id]
memory = ConversationBufferWindowMemory(
chat_memory=store[session_id],
k=10,
return_messages=True,)assertlen(memory.memory_variables)==1
key = memory.memory_variables[0]
messages = memory.load_memory_variables({})[key]
store[session_id]= InMemoryChatMessageHistory(messages=messages)return store[session_id]
chain = RunnableWithMessageHistory(llm, get_session_history)
conf ={"configurable":{"session_id":"1"}}
response = chain.invoke('今天广州天气晴朗,26~35摄氏度。', config=conf)# type: AIMessageprint(f"response 1: {response.content}")
response = chain.invoke('今天广州适合穿什么?', config=conf)# type: AIMessageprint(f"response 2: {response.content}")
输出:
response 1: 这句话的意思是今天广州的天气非常好,晴朗无云,气温在26摄氏度到35摄氏度之间。这个温度范围对于夏天来说是比较舒适的,适合户外活动。
response 2: 根据您提供的信息,今天广州的天气晴朗,气温在26到35摄氏度之间。这个温度范围适合穿着轻薄、透气的衣物。以下是一些建议:
1. 上衣:可以选择短袖T恤、薄衬衫或棉质衣物,避免穿得过多导致出汗后衣服湿透。
2. 下装:可以穿短裤、七分裤或轻薄的牛仔裤。如果是在室内或者空调环境中,可以考虑带一件长裤以防着凉。
3. 鞋子:舒适的凉鞋、帆布鞋或运动鞋都是不错的选择。
4. 配件:可以戴一顶遮阳帽和太阳镜来保护皮肤和眼睛免受紫外线伤害。如果需要长时间在户外,可以考虑涂抹防晒霜。
5. 携带物品:由于气温较高,建议随身携带水瓶以保持水分,同时可以携带
相比之下,现在我们并不需要每次都手动保存历史消息,而是交给
ConversationBufferWindowMemory
来处理。
这样,我们就可以更加专注于对话的内容,而不用担心历史消息的处理。
在上面这个例子中,我们指定了
k=10
,也就是说,只保存最近的 20 条消息,
超过 20 条的消息之后会删除最早的消息(这是因为在底层实现中,会使用
k * 2
,而不是
k
)。
我们可以指定
k=1
来验证一下():
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import AIMessage
from langchain_core.runnables import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationBufferWindowMemory
llm = ChatOpenAI(
model="yi-large",
temperature=0.3,
max_tokens=200,
api_key='your key',
base_url="https://api.lingyiwanwu.com/v1",)
store ={}# memory is maintained outside the chaindefget_session_history(session_id:str)-> InMemoryChatMessageHistory:if session_id notin store:
store[session_id]= InMemoryChatMessageHistory()return store[session_id]
memory = ConversationBufferWindowMemory(
chat_memory=store[session_id],
k=1,
return_messages=True,)assertlen(memory.memory_variables)==1
key = memory.memory_variables[0]# 返回最新的 k * 2 条消息
messages = memory.load_memory_variables({})[key]
store[session_id]= InMemoryChatMessageHistory(messages=messages)return store[session_id]
chain = RunnableWithMessageHistory(llm, get_session_history)
conf ={"configurable":{"session_id":"1"}}
response = chain.invoke('今天广州天气晴朗,26~35摄氏度。', config=conf)# type: AIMessageprint(f"response 1: {response.content}")
response = chain.invoke('这是一条无用的消息,请你忽略。', config=conf)# type: AIMessageprint(f"response 2: {response.content}")
response = chain.invoke('今天广州适合穿什么?', config=conf)# type: AIMessageprint(f"response 3: {response.content}")
输出:
response 1: 这句话的意思是,今天广州的天气非常好,晴朗无云,气温在26摄氏度到35摄氏度之间。这是一个非常适合户外活动的天气,既不太热也不太冷。
response 2: 好的,我会忽略这条消息。如果您有其他问题或需要帮助,请随时告诉我!
response 3: 很抱歉,我无法提供实时数据或当前的天气情况。........<一大堆>
因为我们使用了
k=1
,所以当我们交谈了三次之后,第一次发送的内容就会被删除了。
所以当我们问第三个问题的时候,AI 并没有记住我们之前说过的话。
本文例子用到的一些类的介绍
InMemoryChatMessageHistory
没有特殊功能,只有一个
messages
属性,用于保存消息,是
list
类型。
ConversationBufferWindowMemory
- 它有一个
chat_memory
属性,用于保存历史消息。 - 当我们从它的实例中获取消息的时候(调用它的
load_memory_variables
方法的时候),它只会返回最近的k * 2
条历史消息。
ConversationSummaryBufferMemory
除了
ConversationBufferWindowMemory
,
langchain
还提供了
ConversationSummaryBufferMemory
,它会将历史消息进行摘要(当超过了指定长度的时候),然后保存摘要:
defprune(self)->None:"""Prune buffer if it exceeds max token limit"""buffer= self.chat_memory.messages
curr_buffer_length = self.llm.get_num_tokens_from_messages(buffer)if curr_buffer_length > self.max_token_limit:
pruned_memory =[]while curr_buffer_length > self.max_token_limit:
pruned_memory.append(buffer.pop(0))
curr_buffer_length = self.llm.get_num_tokens_from_messages(buffer)
self.moving_summary_buffer = self.predict_new_summary(
pruned_memory, self.moving_summary_buffer
)
prune
方法会在超过指定长度的时候,将历史消息进行摘要,然后保存摘要。
优缺点
优点:
- 控制了缓存内容大小
- 尽量记忆了对话的内容
缺点:
- 在缓存内容超出限制后,为控制缓存的大小,会持续通过大模型来总结较早的内容。
- 相应延迟增加很多
- 成本增加
总结
在使用 LLM 的时候,我们需要注意到 LLM 并不会记住我们之前说过的话。
但是我们可以自行保存历史消息,然后在下一次调用的时候,将之前的消息传递给 AI。
为了方便处理历史消息,
langchain
提供了
ConversationBufferWindowMemory
这个工具,可以帮助我们保存历史消息,并在超过指定数量的时候删除最早的消息。
版权归原作者 白如意i 所有, 如有侵权,请联系我们删除。