【AI Agent教程】【MetaGPT】由易到难,深入源码:看MetaGPT的长短时记忆如何实现

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 【AI Agent教程】【MetaGPT】由易到难,深入源码:看MetaGPT的长短时记忆如何实现
  • 大家好,我是同学小张,日常分享AI知识和实战案例
  • 欢迎 点赞 + 关注 👏,持续学习持续干货输出
  • 一起交流💬,一起进步💪。
  • 微信公众号也可搜【同学小张】 🙏

本站文章一览:


这两天看了不少关于AI大模型应用中长短时记忆的实现方法。突然想到,一直在学习和使用的MetaGPT(开源AI Agent多智能框架),我竟然没有注意过它里面的关于记忆的实现方法。今天,补上这一课,深入其源码,看它的 Memory 模块是如何实现的。

0. 关于大模型记忆的前期文章

感兴趣的可以看下我前面的几篇关于大模型记忆的文章,废了点功夫从网上搜集了目前比较常用和前沿的长短时记忆的实现方法:

1. 先用起来 - 使用 Memory

在MetaGPT中,Memory类是智能体的记忆的抽象。当初始化时,Role初始化一个Memory对象作为self.rc.memory 属性,它将在之后的 _observe 中存储每个 Message,以便后续的检索。简而言之,Role的记忆是一个含有 Message 的列表。

我们前面学习MetaGPT的系列文章中其实每次都在用这个Message。例如这篇文章中的代码:【AI Agent系列】【MetaGPT多智能体学习】3. 开发一个简单的多智能体系统,兼看MetaGPT多智能体运行机制

get_memories 函数就是用来获取记忆的。

你可以通过前面MetaGPT系列文章中的任一个Demo代码去体验这个函数的使用。

2. 再深入看源码

2.1 Memory模块总览

下图是 MetaGPT 中 Memory 部分的代码文件:

memory.py 是基础的 memory 基类,也是最常用的 Memory 类型。longterm_memory.py 是长时记忆的实现。memory_storage.py 是辅助 longterm_memory.py 来进行记忆的持久化存储和查询的类。brain_memory.py 看描述是具有长时记忆和记忆压缩能力,但是并没有看到应用的地方。

2.2 基础记忆

这部分功能在 memory.py 文件中,这是记忆模块的基类。

class Memory(BaseModel):
    """The most basic memory: super-memory"""
    storage: list[SerializeAsAny[Message]] = []
    index: DefaultDict[str, list[SerializeAsAny[Message]]] = Field(default_factory=lambda: defaultdict(list))
    ignore_id: bool = False

其 storage 是一个 list 列表,所以是保存在内存中的,关机、重启之后,记忆消失,无法持久化存储。

2.2.1 接口说明

(1)add:往记忆中添加一条记忆

实现中,还增加了 self.index[message.cause_by].append(message) 的处理,让使用者可以通过 message.cause_by 参数快速获取指定动作的记忆。

def add(self, message: Message):
    """Add a new message to storage, while updating the index"""
    if self.ignore_id:
        message.id = IGNORED_MESSAGE_ID
    if message in self.storage:
        return
    self.storage.append(message)
    if message.cause_by:
        self.index[message.cause_by].append(message)

(2)add_batch:添加一系列消息

(3)get_by_role:返回指定Role的记忆,判定条件 message.role == role

(4)get_by_content:查找带有 content 的记忆,类似关键字检索?判定条件 content in message.content

(5)delete_newest:删除最新的一条记忆

(6)delete:删除指定的一条记忆

(7)clear:清空记忆

(8)count:记忆数量

(9)try_remember:查找带有 keyword 的记忆,与 get_by_content 有点重复?keyword in message.content

(10)get:获取最近的K条记忆,K=0时返回全部的记忆

(11)find_news:找出想观察但上次没观察到的消息?没想象出怎么使用,给大家看下源码理解一下吧:

def find_news(self, observed: list[Message], k=0) -> list[Message]:
    """find news (previously unseen messages) from the the most recent k memories, from all memories when k=0"""
    already_observed = self.get(k)
    news: list[Message] = []
    for i in observed:
        if i in already_observed:
            continue
        news.append(i)
    return news

(12)get_by_action 、 get_by_actions:返回指定动作产生的记忆

2.3 longterm_memory.py

长时记忆的实现,启动时恢复记忆,变化时更新记忆。

class LongTermMemory(Memory):
    """
    The Long-term memory for Roles
    - recover memory when it staruped
    - update memory when it changed
    """
    model_config = ConfigDict(arbitrary_types_allowed=True)
    memory_storage: MemoryStorage = Field(default_factory=MemoryStorage)
    rc: Optional[RoleContext] = None
    msg_from_recover: bool = False

2.3.1 接口说明

(1)add:添加一条记忆

def add(self, message: Message):
    super().add(message)
    for action in self.rc.watch:
        if message.cause_by == action and not self.msg_from_recover:
            # currently, only add role's watching messages to its memory_storage
            # and ignore adding messages from recover repeatedly
            self.memory_storage.add(message)

首先是调用super也就是基础Memory的add接口,添加记忆到内存中。

然后通过self.memory_storage.add(message)保存记忆。memory_storage的add接口,是调用 FAISS 向量数据库去进行向量化和存储。

def add(self, message: Message) -> bool:
    """add message into memory storage"""
    self.faiss_engine.add_objs([message])
    logger.info(f"Role {self.role_id}'s memory_storage add a message")

(2)recover_memory:恢复记忆

def recover_memory(self, role_id: str, rc: RoleContext):
    self.memory_storage.recover_memory(role_id)
    self.rc = rc
    if not self.memory_storage.is_initialized:
        logger.warning(f"It may the first time to run Role {role_id}, the long-term memory is empty")
    else:
        logger.warning(f"Role {role_id} has existing memory storage and has recovered them.")
    self.msg_from_recover = True
    # self.add_batch(messages) # TODO no need
    self.msg_from_recover = False

通过 memory_storagerecover_memory,最终也是通过 FAISS 数据库进行记忆查询:

def recover_memory(self, role_id: str) -> list[Message]:
    self.role_id = role_id
    self.role_mem_path = Path(DATA_PATH / f"role_mem/{self.role_id}/")
    self.role_mem_path.mkdir(parents=True, exist_ok=True)
    self.cache_dir = self.role_mem_path
    if self.role_mem_path.joinpath("default__vector_store.json").exists():
        self.faiss_engine = SimpleEngine.from_index(
            index_config=FAISSIndexConfig(persist_path=self.cache_dir),
            retriever_configs=[FAISSRetrieverConfig()],
            embed_model=self.embedding,
        )
    else:
        self.faiss_engine = SimpleEngine.from_objs(
            objs=[], retriever_configs=[FAISSRetrieverConfig()], embed_model=self.embedding
        )
    self._initialized = True

这里恢复的是指定Role的记忆,通过 self.role_id 来区分。

(3)find_news

与基础Memory不同的是,它首先在基础Memory中找,然后在 long-term memory 中找,将两者的结果结合作为最终结果返回。

async def find_news(self, observed: list[Message], k=0) -> list[Message]:
    """
    find news (previously unseen messages) from the the most recent k memories, from all memories when k=0
        1. find the short-term memory(stm) news
        2. furthermore, filter out similar messages based on ltm(long-term memory), get the final news
    """
    stm_news = super().find_news(observed, k=k)  # shot-term memory news
    if not self.memory_storage.is_initialized:
        # memory_storage hasn't initialized, use default `find_news` to get stm_news
        return stm_news
    ltm_news: list[Message] = []
    for mem in stm_news:
        # filter out messages similar to those seen previously in ltm, only keep fresh news
        mem_searched = await self.memory_storage.search_similar(mem)
        if len(mem_searched) == 0:
            ltm_news.append(mem)
    return ltm_news[-k:]

(4)persist

将数据持久化存储。

def persist(self):
    if self.faiss_engine:
        self.faiss_engine.retriever._index.storage_context.persist(self.cache_dir)

2.4 brain_memory.py

class BrainMemory(BaseModel):
    history: List[Message] = Field(default_factory=list)
    knowledge: List[Message] = Field(default_factory=list)
    historical_summary: str = ""
    last_history_id: str = ""
    is_dirty: bool = False
    last_talk: Optional[str] = None
    cacheable: bool = True
    llm: Optional[BaseLLM] = Field(default=None, exclude=True)

这个Memory没太搞清楚具体是干啥的,但是看到里面是用Redis存储的,并且里面有summary信息的步骤,可以学习一下这种记忆思路。

看源码,它是将历史对话信息用大模型进行总结,然后存储到Redis里面。

async def summarize(self, llm, max_words=200, keep_language: bool = False, limit: int = -1, **kwargs):
    if isinstance(llm, MetaGPTLLM):
        return await self._metagpt_summarize(max_words=max_words)
    self.llm = llm
    return await self._openai_summarize(llm=llm, max_words=max_words, keep_language=keep_language, limit=limit)
async def _openai_summarize(self, llm, max_words=200, keep_language: bool = False, limit: int = -1):
    texts = [self.historical_summary]
    for m in self.history:
        texts.append(m.content)
    text = "\n".join(texts)
    text_length = len(text)
    if limit > 0 and text_length < limit:
        return text
    summary = await self._summarize(text=text, max_words=max_words, keep_language=keep_language, limit=limit)
    if summary:
        await self.set_history_summary(history_summary=summary, redis_key=config.redis_key)
        return summary
    raise ValueError(f"text too long:{text_length}")

它上面还有一层函数 get_title,又将上面Summary的信息抽出了一个标题:

async def get_title(self, llm, max_words=5, **kwargs) -> str:
    """Generate text title"""
    if isinstance(llm, MetaGPTLLM):
        return self.history[0].content if self.history else "New"
    summary = await self.summarize(llm=llm, max_words=500)
    language = config.language
    command = f"Translate the above summary into a {language} title of less than {max_words} words."
    summaries = [summary, command]
    msg = "\n".join(summaries)
    logger.debug(f"title ask:{msg}")
    response = await llm.aask(msg=msg, system_msgs=[], stream=False)
    logger.debug(f"title rsp: {response}")
    return response

这是不是算分层存储了?

3. 总结

总得看下来,MetaGPT的记忆模块其实实现的很简单,主要是在短时记忆上面进行了实现。对于长时记忆,只是简单的实现了将其存入了向量数据库,可以选择持久化的基础能力。对于长上下文这种的记忆能力,并没有特别实现。感觉longterm_memory并没有写完的样子,没看到实际使用。其brain_memory是第一次见,也没看到实际使用,但里面的对历史对话进行总结存储,对总结内容再进行标题化存储的方法值得借鉴。

参考

  1. https://docs.deepwisdom.ai/main/zh/guide/tutorials/use_memories.html
  2. https://github.com/geekan/MetaGPT

如果觉得本文对你有帮助,麻烦点个赞和关注呗 ~~~


  • 大家好,我是 同学小张,日常分享AI知识和实战案例
  • 欢迎 点赞 + 关注 👏,持续学习持续干货输出
  • 一起交流💬,一起进步💪。
  • 微信公众号也可搜【同学小张】 🙏

本站文章一览:

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
10天前
|
机器学习/深度学习 人工智能 自然语言处理
Gemini 2.0:谷歌推出的原生多模态输入输出 + Agent 为核心的 AI 模型
谷歌最新推出的Gemini 2.0是一款原生多模态输入输出的AI模型,以Agent技术为核心,支持多种数据类型的输入与输出,具备强大的性能和多语言音频输出能力。本文将详细介绍Gemini 2.0的主要功能、技术原理及其在多个领域的应用场景。
112 20
Gemini 2.0:谷歌推出的原生多模态输入输出 + Agent 为核心的 AI 模型
|
10天前
|
人工智能 API 语音技术
TEN Agent:开源的实时多模态 AI 代理框架,支持语音、文本和图像的实时通信交互
TEN Agent 是一个开源的实时多模态 AI 代理框架,集成了 OpenAI Realtime API 和 RTC 技术,支持语音、文本和图像的多模态交互,具备实时通信、模块化设计和多语言支持等功能,适用于智能客服、实时语音助手等多种场景。
93 15
TEN Agent:开源的实时多模态 AI 代理框架,支持语音、文本和图像的实时通信交互
|
11天前
|
人工智能 自然语言处理 前端开发
Director:构建视频智能体的 AI 框架,用自然语言执行搜索、编辑、合成和生成等复杂视频任务
Director 是一个构建视频智能体的 AI 框架,用户可以通过自然语言命令执行复杂的视频任务,如搜索、编辑、合成和生成视频内容。该框架基于 VideoDB 的“视频即数据”基础设施,集成了多个预构建的视频代理和 AI API,支持高度定制化,适用于开发者和创作者。
74 9
Director:构建视频智能体的 AI 框架,用自然语言执行搜索、编辑、合成和生成等复杂视频任务
|
8天前
|
机器学习/深度学习 人工智能 算法
Meta Motivo:Meta 推出能够控制数字智能体动作的 AI 模型,提升元宇宙互动体验的真实性
Meta Motivo 是 Meta 公司推出的 AI 模型,旨在控制数字智能体的全身动作,提升元宇宙体验的真实性。该模型通过无监督强化学习算法,能够实现零样本学习、行为模仿与生成、多任务泛化等功能,适用于机器人控制、虚拟助手、游戏角色动画等多个应用场景。
35 4
Meta Motivo:Meta 推出能够控制数字智能体动作的 AI 模型,提升元宇宙互动体验的真实性
|
19天前
|
人工智能 自然语言处理 JavaScript
Agent-E:基于 AutoGen 代理框架构建的 AI 浏览器自动化系统
Agent-E 是一个基于 AutoGen 代理框架构建的智能自动化系统,专注于浏览器内的自动化操作。它能够执行多种复杂任务,如填写表单、搜索和排序电商产品、定位网页内容等,从而提高在线效率,减少重复劳动。本文将详细介绍 Agent-E 的功能、技术原理以及如何运行该系统。
66 5
Agent-E:基于 AutoGen 代理框架构建的 AI 浏览器自动化系统
|
14天前
|
存储 人工智能 搜索推荐
整合长期记忆,AI实现自我进化,探索大模型这一可能性
本文探讨了通过整合长期记忆(LTM),AI模型能否实现自我进化,以提升处理新任务和适应环境的能力。LTM能帮助模型存储和利用长期信息,提高决策质量和服务个性化水平。文章还讨论了LTM整合的挑战及解决方案,以及如何借鉴人类记忆机制设计有效的LTM策略。[论文链接](https://arxiv.org/pdf/2410.15665)
59 17
|
3天前
|
机器学习/深度学习 人工智能 自然语言处理
MetaGPT开源SELA,用AI设计AI,效果超越OpenAI使用的AIDE
MetaGPT团队开源了Tree-Search Enhanced LLM Agents(SELA)系统,通过蒙特卡罗树搜索(MCTS)优化AutoML过程,显著提升了机器学习模型的构建效率和性能。SELA在20个数据集上的实验结果表明,其性能优于传统AutoML方法和基于LLM的代理,为AutoML领域带来了新的突破。
23 4
|
1月前
|
存储 人工智能 搜索推荐
Memoripy:支持 AI 应用上下文感知的记忆管理 Python 库
Memoripy 是一个 Python 库,用于管理 AI 应用中的上下文感知记忆,支持短期和长期存储,兼容 OpenAI 和 Ollama API。
94 6
Memoripy:支持 AI 应用上下文感知的记忆管理 Python 库
|
19天前
|
人工智能 自然语言处理 数据挖掘
田渊栋团队新作祭出Agent-as-a-Judge!AI智能体自我审判,成本暴跌97%
田渊栋团队提出Agent-as-a-Judge框架,利用智能体自身评估其他智能体的性能,不仅关注最终结果,还能提供中间反馈,更全面准确地反映智能体的真实能力。该框架在DevAI基准测试中表现出色,成本效益显著,为智能体的自我改进提供了有力支持。
35 7
|
26天前
|
人工智能 自然语言处理 搜索推荐
🤖【多Agent大爆炸】——灵活调用与实践指南,解锁AI协作新技能!
本文深入探讨了单Agent与多Agent在不同场景下的应用及优势,通过实例讲解多Agent如何实现高效协作,涵盖智能物流、教育、医疗等多个领域的实际应用,旨在帮助开发者掌握多Agent系统的调用与实践技巧。
94 5