Memory
2.6.1 简介
在Agent系统中,Memory模块是一个关键的组件,其主要功能是存储和检索信息,以支持agent的学习和决策过程。该模块模拟人类记忆的某些特征,能够动态地保存和更新信息,使agent能够利用过去的经验进行推理和决策。
为什么要有Memory模块?
试想一下,当你和agent交互时,如果agent没有记忆,那就没法进行多轮对话了。你每次提问都相当于重新开始一个对话,对话就不具备连续性。
Memory模块通常包括以下几个核心功能:
-
信息储存:能够高效存储多种形式的数据,包括事实、事件、规则和上下文信息,以便在需要时快速访问。
-
信息检索:支持根据特定查询或上下文快速检索相关信息,帮助agent在需要时做出准确的判断。
-
记忆更新:能够根据新的信息和经验动态更新存储内容,以反映环境或任务的变化。
-
记忆管理:包括老化机制和优先级管理,确保较重要的信息能够长期保留,而不再需要的信息可以被有效清除,以优化存储资源的使用。
CAMEL 中的Memory模块提供了一个灵活的系统,用于存储、检索和管理 Agent的信息。它使Agent能够在对话中维护上下文,并从过去的交互中检索相关信息,从而提高 AI 响应的连贯性和相关性。
2.6.2 ChatHistoryBlock
ChatHistoryBlock 是一个基于键值存储的聊天历史记忆块实现。
-
使用键值存储后端(BaseKeyValueStorage)
-
支持窗口式检索
-
实现消息权重衰减机制
初始化参数
-
storage
: 存储后端,默认使用InMemoryKeyValueStorage
-
keep_rate
: 历史消息权重衰减率,默认 0.9该模块主要实现了以下方法:
-
retrieve()
:使用可选的窗口大小获取最近的聊天记录 -
write_records()
:将新记录写入聊天记录 -
clear()
:删除所有聊天消息
keep_rate概述
keep_rate
是 CAMEL 记忆系统中用于控制历史消息权重衰减的重要参数。它主要用于调整历史消息在上下文中的重要性。
-
取值范围: [0,1]
-
默认值: 0.9
-
作用对象: 非system消息(system消息始终保持 score=1.0)
它的工作原理是在检索历史消息时:
-
最新消息的 score 初始值为 1.0
-
每往前一条消息,score 会乘以 keep_rate
-
最终每条消息的 score 值决定了其在上下文中的重要性
现在假设有5条历史消息,keep_rate=0.9:
消息位置 | Score 计算 | 最终 Score |
---|---|---|
最新消息 | 1.0 | 1.0 |
往前1条 | 1.0 * 0.9 | 0.9 |
往前2条 | 0.9 * 0.9 | 0.81 |
往前3条 | 0.81 * 0.9 | 0.729 |
往前4条 | 0.729 * 0.9 | 0.656 |
实际上,它的工作原理和我们人脑很像,我们对于近期的事情印象会更深刻,而对于久一些的事情反之。以下是一些值得注意的点:
-
score 不影响消息的存储,但它会在总token数超过限制时决定哪些消息在生成下文时应该被保留。
-
system消息不受 score 影响,也就是说在生成下文的时候,system_msg会一直保留。
-
keep_rate 与 window_size 可以配合使用来更好地控制上下文
-
过低的 keep_rate 可能导致有价值的历史信息被过度弱化
- 过高的 keep_rate 可能导致上下文过于冗长
示例用法
我们可以通过以下例子直观感受keep_rate在ChatHistoryBlock中的作用。
python
from camel.memories.blocks import ChatHistoryBlock
from camel.memories.records import MemoryRecord
from camel.types import OpenAIBackendRole
from camel.messages import BaseMessage
# 创建一个 ChatHistoryBlock 实例
chat_history = ChatHistoryBlock(keep_rate=0.8)
# 模拟写入一些消息记录
chat_history.write_records([
MemoryRecord(message=BaseMessage.make_assistant_message(role_name="user", content="Hello,今天感觉怎么样?"), role_at_backend=OpenAIBackendRole.USER),
MemoryRecord(message=BaseMessage.make_user_message(role_name="assistant", content="我很好,谢谢!"), role_at_backend=OpenAIBackendRole.ASSISTANT),
MemoryRecord(message=BaseMessage.make_user_message(role_name="user", content="你能做些什么?"), role_at_backend=OpenAIBackendRole.USER),
MemoryRecord(message=BaseMessage.make_assistant_message(role_name="assistant", content="我可以帮助你完成各种任务。"), role_at_backend=OpenAIBackendRole.ASSISTANT),
])
# 检索最近的 3 条消息
recent_records = chat_history.retrieve(window_size=4)
for record in recent_records:
print(f"消息: {record.memory_record.message.content}, 权重: {record.score}")
>>>
消息: hello,你怎么样?, 权重: 0.40960000000000013
消息: 我很好,谢谢!, 权重: 0.5120000000000001
消息: 你能做些什么?, 权重: 0.6400000000000001
消息: 我可以帮助你完成各种任务。, 权重: 0.8
2.6.3 VectorDBBlock
VectorDBBlock
是一个基于向量数据库的语义记忆块实现。有关向量的部分可以参考第五章。
-
使用向量存储后端(
BaseVectorStorage
) -
支持语义相似度检索
-
实现消息的向量化存储
初始化参数
-
storage
:可选 BaseVectorStorage (默认:QdrantStorage
) -
embedding
:可选 BaseEmbedding(默认值:OpenAIEmbedding
)
该模块主要实现了以下方法:
-
retrieve()
:根据关键字获取相似记录 -
write_records()
:将新记录转换并写入矢量数据库 -
clear()
:从向量数据库中删除所有记录
该模块的工作流程如下:
-
存储过程:
-
将消息内容转换为向量表示
-
生成唯一标识符(UUID)
-
将向量和原始消息存入向量数据库
-
-
检索过程:
-
将查询关键词转换为向量
-
在向量空间中搜索相似向量
-
返回相似度最高的记录
-
示例用法
这里如果不定义VectorDBBlock中的embedding
参数的话,则会调用默认的OpenAI的text-embedding-3-small模型,需要对应的OpenAI的API KEY,贴心的是,CAMEL也为我们提供了一个方便快捷的方式去一键调用我们的本地embedding模型,只需要导入SentenceTransformerEncoder
,然后根据场景选择我们想用的embedding模型(默认为intfloat/e5-large-v2),这里是中文场景,笔者选择了bge-m3作为我们的embedding模型,模型的选择以及更多相关信息可以参考Hugging Face(embedding_model)以及我们的Embedding章节。
python
from camel.memories.blocks.vectordb_block import VectorDBBlock
from camel.memories.records import MemoryRecord
from camel.messages import BaseMessage
from camel.embeddings import SentenceTransformerEncoder
from camel.types import OpenAIBackendRole
# 创建一个 VectorDBBlock 实例
vector_db_block = VectorDBBlock(embedding=SentenceTransformerEncoder(model_name="BAAI/bge-m3"))
# 创建一些示例聊天记录
records = [
MemoryRecord(message=BaseMessage.make_user_message(role_name="user", content="今天天气真好!"), role_at_backend=OpenAIBackendRole.USER),
MemoryRecord(message=BaseMessage.make_user_message(role_name="user", content="你喜欢什么运动?"), role_at_backend=OpenAIBackendRole.USER),
MemoryRecord(message=BaseMessage.make_user_message(role_name="user", content="今天天气不错,我们去散步吧。"), role_at_backend=OpenAIBackendRole.USER),
]
# 将记录写入向量数据库
vector_db_block.write_records(records)
# 使用关键词进行检索
keyword = "天气"
retrieved_records = vector_db_block.retrieve(keyword=keyword, limit=3)
for record in retrieved_records:
print(f"UUID: {record.memory_record.uuid}, Message: {record.memory_record.message.content}, Score: {record.score}")
>>>
UUID: f7519828-afe7-41e4-8331-7bbc4d7dcbd5, Message: 今天天气真好!, Score: 0.779863416845349
UUID: 0fbab391-f0e0-4580-877b-8fa2a837675b, Message: 今天天气不错,我们去散步吧。, Score: 0.6920892191464151
UUID: a640cf33-987b-4b52-ac2b-a987dd474e4e, Message: 你喜欢什么运动?, Score: 0.534536213348924
2.6.4 CAMEL中的具体实践
在CAMEL中目前支持key_value,graph,vector三种形式对于LLM信息进行存储,以供需要的时候检索。大模型生成自然语言文本的核心原理是基于预测。具体来说,语言模型的主要任务是根据给定的上下文预测下一个词。
CAMEL中主要会使用两个chat_history以及context两个数据结构处理记忆信息。其中Chat_history用于规范agent使用过程中的聊天记录,context部分用于从chat_history中获取上下文,由于模型有输入token的限制,因此如何从记录中获取到足够有效且重要的上下文并提供给模型至关重要。CAMEL通过权重的机制从chat_history中筛选重要的部分组成context,从而保证决策能力的有效性。
下面使用一个案例演示一下CAMEL中memory的实现,我们首先创建一个memory对象,之后创建一个agent,并将memory对象赋值给agent的memory属性。
python
from camel.memories import (
LongtermAgentMemory,
MemoryRecord,
ScoreBasedContextCreator,
ChatHistoryBlock,
VectorDBBlock,
)
from camel.messages import BaseMessage
from camel.types import ModelType, OpenAIBackendRole
from camel.utils import OpenAITokenCounter
from camel.embeddings import SentenceTransformerEncoder
# 1. 初始化内存系统
memory = LongtermAgentMemory(
context_creator=ScoreBasedContextCreator(
token_counter=OpenAITokenCounter(ModelType.GPT_4O_MINI),
token_limit=1024,
),
chat_history_block=ChatHistoryBlock(),
vector_db_block=VectorDBBlock(embedding=SentenceTransformerEncoder(model_name="BAAI/bge-m3")),
)
# 2. 创建记忆记录
records = [
MemoryRecord(
message=BaseMessage.make_user_message(
role_name="User",
content="什么是CAMEL AI?"
),
role_at_backend=OpenAIBackendRole.USER,
),
MemoryRecord(
message=BaseMessage.make_assistant_message(
role_name="Agent",
content="CAMEL-AI是第一个LLM多智能体框架,并且是一个致力于寻找智能体 scaling law 的开源社区。"
),
role_at_backend=OpenAIBackendRole.ASSISTANT,
),
]
# 3. 写入记忆
memory.write_records(records)
context, token_count = memory.get_context()
print(context)
print(f'token消耗: {token_count}')
>>>
[{'role': 'user', 'content': '什么是CAMEL AI?'}, {'role': 'assistant', 'content': 'CAMEL-AI是第一个LLM多智能体框架,并且是一个致力于寻找智能体 scaling law 的开源社区。'}]
token消耗: 52
我们首先直接调用agent试一下:
python
from camel.agents import ChatAgent
from camel.models import ModelFactory
from camel.types import ModelPlatformType
import os
from dotenv import load_dotenv
load_dotenv()
# 定义系统消息
sys_msg = "你是一个好奇的智能体,正在探索宇宙的奥秘。"
# 初始化agent 调用在线Qwen/Qwen2.5-72B-Instruct
api_key = os.getenv('QWEN_API_KEY')
model = ModelFactory.create(
model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
model_type="Qwen/Qwen2.5-72B-Instruct",
url='https://api-inference.modelscope.cn/v1/',
api_key=api_key
)
agent = ChatAgent(system_message=sys_msg, model=model)
# 定义用户消息
usr_msg = "告诉我基于我们讨论的内容,哪个是第一个LLM多智能体框架?"
# 发送消息给agent
response = agent.step(usr_msg)
# 查看响应
print(response.msgs[0].content)
>>>
"截至目前(2023年),还没有一个明确被广泛认可为"第一个"LLM多智能体框架的具体项目或平台。然而,随着大语言模型(LLMs)和多智能体系统研
究的快速发展,一些早期尝试将这两者结合起来的项目可以被视为先驱。
例如,"AgentVerse"是一个基于大语言模型的多智能体框架,它允许不同的AI代理相互交互以完成任务或解决问题。虽然不能确切地说它是第一个,但
它确实是在这一领域较早进行探索的一个例子。
此外,还有其他一些研究和项目也在探索如何利用LLMs构建多智能体系统,比如通过模拟社会动态、协作解决问题等。这些工作都在不同程度上推动了
该领域的进步。
如果您有更具体的时间范围或其他标准来定义"第一个",请提供更多信息,这样我可以尝试给出更加准确的答案。"
之后我们将之前设定的memory赋值给agent:
python
# 将memory赋值给agent
agent.memory = memory
# 发送消息给agent
response = agent.step(usr_msg)
# 查看响应
print(response.msgs[0].content)
>>>
"基于我们讨论的内容,CAMEL-AI 是第一个基于大型语言模型(LLM)的多智能体框架。"
可以看到我们新创建的智能体就能够根据设定好的记忆来回答问题了。