MemoryOS 代码解析

MemoryOS 是北邮百家团队开发的 Agent Memory 管理开源系统,他们将记忆分为三类:

  1. 短期 STM,一个明文存储的问答对
  2. 中期 MTM,中期记忆由一组会话构成,每个 Session 包含多个问答对和一些权重和摘要信息
  3. 长期 LPM,重新组织生成的一个高纬度摘要信息

各种记忆的数据格式

短期记忆

python 复制代码
[
  {
    "user_input": "用户输入内容",
    "agent_response": "AI助手的回复",
    "timestamp": "时间戳"
  },
  // 更多问答对...
]

📎sample_short_term.json

中期记忆

由 LLM 生成的会话摘要 (summary)、摘要的向量 (summary_embedding)、关键词 (summary_keywords)

python 复制代码
{
  "sessions": {
    "session_id": {
      "id": "会话ID",
      "summary": "会话摘要",
      "summary_keywords": ["关键词1", "关键词2", ...],
      "summary_embedding": [向量数据],
      "details": [
        {
          "page_id": "页面ID",
          "user_input": "用户输入",
          "agent_response": "助手回复",
          "timestamp": "时间戳",
          "preloaded": false,
          "analyzed": false,
          "pre_page": "前一个页面ID",
          "next_page": "后一个页面ID",
          "meta_info": "页面元信息",
          "page_embedding": [页面向量],
          "page_keywords": ["页面关键词"]
        }
      ],
      "L_interaction": 交互数量,
      "R_recency": 近期访问度量,
      "N_visit": 访问次数,
      "H_segment": 会话热度,
      "timestamp": "创建时间戳",
      "last_visit_time": "最后访问时间",
      "access_count_lfu": LFU访问计数
    }
  },
  "access_frequency": {
    "session_id": 访问频率计数
  }
}

📎sample_mid_term.json

长期记忆

python 复制代码
{
  "user_profiles": {
    "user_id": {
      "data": "用户画像数据",
      "last_updated": "最后更新时间"
    }
  },
  "knowledge_base": [
    {
      "knowledge": "知识条目内容",
      "timestamp": "时间戳",
      "knowledge_embedding": [知识向量]
    }
  ],
  "assistant_knowledge": [
    {
      "knowledge": "助手知识条目",
      "timestamp": "时间戳",
      "knowledge_embedding": [知识向量]
    }
  ]
}

📎sample_long_term_assistant.json

📎sample_long_term_user.json

各阶段记忆处理

各个阶段的记忆转换由 updater.py 实现,retriever.py 负责检索和回复

  • Updater:
    • process_short_term_to_mid_term: 负责 STM -> MTM 的流程。它会取出 STM 中的所有对话,调用 LLM 进行多主题摘要(gpt_generate_multi_summary),然后根据摘要结果将这些对话智能地插入或合并到 MTM 的不同 Session 中。
  • Retriever:
    • retrieve_context: 负责为生成回复提供上下文。它会并行地(使用 ThreadPoolExecutor)向 MTM 和 LTM 发起检索请求,高效地收集所有相关信息。

短期记忆

短期记忆处理比较简单,初始化时会根据输入参数创建一个指定长度的 deque 队列。

python 复制代码
def add_memory(self, user_input: str, agent_response: str, timestamp: str = None, meta_data: dict = None):
    if self.short_term_memory.is_full():
            print("Memoryos: Short-term memory full. Processing to mid-term.")
            self.updater.process_short_term_to_mid_term()
    self._trigger_profile_and_knowledge_update_if_needed()
  1. 超过 short term memory 队列长度会通过 process_short_term_to_mid_term触发记忆迁移
  2. 同时每次添加记忆都会触发更新长期记忆

在将短期记忆转移到中期记忆时,系统还会检查对话的连续性:

python 复制代码
is_continuous = check_conversation_continuity(temp_last_page_in_batch, current_page_obj, self.client, model=self.llm_model)

通过 LLM 判断两个对话页面是否连续,使用专门的提示模板:

python 复制代码
CONTINUITY_CHECK_SYSTEM_PROMPT = "You are a conversation continuity detector. Return ONLY 'true' or 'false'."
CONTINUITY_CHECK_USER_PROMPT = ("Determine if these two conversation pages are continuous (true continuation without topic shift).\n"
                                "Return ONLY "true" or "false".\n\n"
                                "Previous Page:\nUser: {prev_user}\nAssistant: {prev_agent}\n\n"
                                "Current Page:\nUser: {curr_user}\nAssistant: {curr_agent}\n\n"
                                "Continuous?")

中期记忆处理

在 MemoryOS 的中期记忆模块中,会话合并是通过 insert_pages_into_session方法实现的。当需要添加新的对话页面时,系统会判断是否应该将这些页面合并到现有会话中,而不是创建新的会话。下面就是合并的具体流程:

  1. 相似性判断:结合语义嵌入和关键词的相似性计算,准确判断会话相关性
  2. 重要性评估:通过热度算法综合考虑访问频率、交互长度和时间因素
  3. 智能合并 :根据相似性阈值决定是合并 到现有会话还是创建新会话
  4. 连续性维护:通过 LLM 检查确保对话的连贯性

这种设计使得系统能够有效地组织和管理对话记忆,既能保持话题的连贯性,又能识别和分离不同的话题,为后续的记忆检索和响应生成提供了良好的基础。

相似性判断机制

相似性判断采用两种方式的组合:

1. 语义相似性(Semantic Similarity)

系统使用嵌入向量计算语义相似性:

python 复制代码
new_summary_vec = get_embedding(
    summary_for_new_pages,
    model_name=self.embedding_model_name,
    **self.embedding_model_kwargs
)
new_summary_vec = normalize_vector(new_summary_vec)

for sid, existing_session in self.sessions.items():
    existing_summary_vec = np.array(existing_session["summary_embedding"], dtype=np.float32)
    semantic_sim = float(np.dot(existing_summary_vec, new_summary_vec))

通过计算两个会话摘要嵌入向量的点积来衡量语义相似性。

2. 关键词相似性(Keyword Similarity)

系统还使用 Jaccard 相似度计算关键词相似性:

python 复制代码
# Keyword similarity (Jaccard index based)
existing_keywords = set(existing_session.get("summary_keywords", []))
new_keywords_set = set(keywords_for_new_pages)
s_topic_keywords = 0
if existing_keywords and new_keywords_set:
    intersection = len(existing_keywords.intersection(new_keywords_set))
    union = len(existing_keywords.union(new_keywords_set))
    if union > 0:
        s_topic_keywords = intersection / union 

overall_score = semantic_sim + keyword_similarity_alpha * s_topic_keywords

最终相似度得分是语义相似度和关键词相似度的加权组合。

重要性评估机制

系统使用热度(Heat)算法来评估会话的重要性,热度值越高表示会话越重要:

python 复制代码
def compute_segment_heat(session, alpha=HEAT_ALPHA, beta=HEAT_BETA, gamma=HEAT_GAMMA, tau_hours=RECENCY_TAU_HOURS):
    N_visit = session.get("N_visit", 0)  # 访问次数
    L_interaction = session.get("L_interaction", 0)  # 交互长度
    
    # 基于上次访问时间计算时间衰减因子
    R_recency = 1.0 # Default if no last_visit_time
    if session.get("last_visit_time"):
        R_recency = compute_time_decay(session["last_visit_time"], get_timestamp(), tau_hours)
    
    session["R_recency"] = R_recency # Update session's recency factor
    return alpha * N_visit + beta * L_interaction + gamma * R_recency

热度计算包含三个因素:

  1. 访问频率(N_visit) :访问次数。一个会话被检索的次数越多,越热。
  2. 交互长度(L_interaction) :交互长度。一个会话包含的对话越多,越热。
  3. 时间衰减因子(R_recency) :新近度。最近访问的会话,热度更高(随时间衰减)。

公式为:Heat = α × N_visit + β × L_interaction + γ × R_recency

其中 α、β、γ 是权重参数,R_recency 使用指数衰减函数计算:

python 复制代码
def compute_time_decay(event_timestamp_str, current_timestamp_str, tau_hours=24):
    from datetime import datetime
    fmt = "%Y-%m-%d %H:%M:%S"
    try:
        t_event = datetime.strptime(event_timestamp_str, fmt)
        t_current = datetime.strptime(current_timestamp_str, fmt)
        delta_hours = (t_current - t_event).total_seconds() / 3600.0
        return np.exp(-delta_hours / tau_hours)
    except ValueError: # Handle cases where timestamp might be invalid
        return 0.1 # Default low recency

会话合并

  1. 会话创建:通过 add_session 方法创建新会话,初始化热度值
  2. 会话合并决策
    • 计算新会话与所有现有会话的相似度得分
    • 找到最高相似度得分的会话
    • 如果最高得分超过阈值,则合并;否则创建新会话
  1. 合并操作
    • 将新页面添加到目标会话的 details 列表中
    • 更新会话的交互长度(L_interaction)
    • 更新最后访问时间
    • 重新计算会话热度
    • 重建热度堆

删除记忆会话

会话长度超过了预设长度(2000)时,根据 lfu 算法丢弃旧的会话记录

python 复制代码
def add_session(self, summary, details, summary_keywords=None):
    if len(self.sessions) > self.max_capacity:
        self.evict_lfu()
        
def evict_lfu(self):
    session_to_delete = self.sessions.pop(lfu_sid) # Remove from sessions
    del self.access_frequency[lfu_sid] # Remove from LFU tracking

长期记忆处理

每次调用 add_memory 时,都会检测是否需要更新长期记忆的内容。包含用户画像、用户私有知识、助手知识,如果需要更新就会调用使用指定的 Prompt 调用 LLM 生成记忆,并调用长期记忆(LTM)的相应方法(update_user_profile, add_user_knowledge 等)将这些高度浓缩的信息存入 LTM。

python 复制代码
def add_memory(self, user_input: str, agent_response: str, timestamp: str = None, meta_data: dict = None):
    # After any memory addition that might impact mid-term, check for profile updates
    self._trigger_profile_and_knowledge_update_if_needed()
    
def _trigger_profile_and_knowledge_update_if_needed(self):
    # 获取现有的 profile,然后和 unanalyzed_pages 内容一起调用 LLM 处理
    existing_profile = self.user_long_term_memory.get_raw_user_profile(self.user_id
    gpt_user_profile_analysis(unanalyzed_pages, self.client, model=self.llm_model, existing_user_profile=existing_profile)

    gpt_knowledge_extraction(unanalyzed_pages, self.client, model=self.llm_model)
    # 使用并行任务执行                
    with ThreadPoolExecutor(max_workers=2) as executor:
        # 提交两个主要任务
        future_profile = executor.submit(task_user_profile_analysis)
        future_knowledge = executor.submit(task_knowledge_extraction)
        
        # 等待结果
        try:
            updated_user_profile = future_profile.result()  # 直接是更新后的完整画像
            knowledge_result = future_knowledge.result()

    self.user_long_term_memory.update_user_profile(self.user_id, updated_user_profile, merge=False)  # 直接替换为新的完整画像
    self.user_long_term_memory.add_user_knowledge(line.strip())
    self.assistant_long_term_memory.add_assistant_knowledge(line.strip()) # Save to dedicated assistant LTM
python 复制代码
def gpt_user_profile_analysis(conversation_str: str, client: OpenAIClient, model="gpt-4o-mini", existing_user_profile="None"):
    """
    Analyze and update user personality profile from a conversation string.
    """
    messages = [
        {"role": "system", "content": prompts.PERSONALITY_ANALYSIS_SYSTEM_PROMPT},
        {"role": "user", "content": prompts.PERSONALITY_ANALYSIS_USER_PROMPT.format(
            conversation=conversation_str,
            existing_user_profile=existing_user_profile
        )}
    ]
    print("Calling LLM for user profile analysis and update...")
    result_text = client.chat_completion(model=model, messages=messages)
    try:
        return json.loads(result_text)


def gpt_knowledge_extraction(conversation_str: str, client: OpenAIClient, model="gpt-4o-mini"):
    """Extract user private data and assistant knowledge from a conversation string"""
    messages = [
        {"role": "system", "content": prompts.KNOWLEDGE_EXTRACTION_SYSTEM_PROMPT},
        {"role": "user", "content": prompts.KNOWLEDGE_EXTRACTION_USER_PROMPT.format(
            conversation=conversation_str
        )}
    ]
    print("Calling LLM for knowledge extraction...")
    result_text = client.chat_completion(model=model, messages=messages)     
    
    return {
        "private": private_data if private_data else "None", 
        "assistant_knowledge": assistant_knowledge if assistant_knowledge else "None"
    }

总结

核心原理

系统通过模拟人类记忆机制,将信息在三个层级中进行处理、沉淀和检索:

  1. 短期记忆 (STM):一个高速、小容量的 FIFO(先进先出)队列,用于暂存最近的对话。
  2. 中期记忆 (MTM):系统的核心。它将从 STM 传来的对话通过 LLM 进行语义聚类,组织成按主题划分的"会话(Session)"。每个会话都有一个动态计算的 "热度(Heat)" 值(基于访问频率、新近度和交互长度),并采用 LFU(最不常用)策略进行淘汰,确保记忆库的有效性。
  3. 长期记忆 (LTM):当 MTM 中的某个会话"热度"达到阈值时,系统会调用 LLM 对其进行深度分析,提炼出高度浓缩的 用户画像(User Profile) 和 关键知识点(Knowledge),并存入 LTM。LTM 的知识库采用 滚动更新(Rolling Update) 机制,自动淘汰最旧的知识。

整个信息流由 Updater(更新器) 负责从低级向高级"提炼"和写入,由 Retriever(检索器) 负责在生成回复时从所有层级"并行"拉取上下文。

优点

  • 上下文深度:能够跨越非常长的时间周期,关联和检索相关记忆,实现真正意义上的长期个性化对话。
  • 结构化记忆:通过智能聚类和分层,记忆内容不再是杂乱的流水账,而是高度组织化的,显著提升了检索的准确性。
  • 高效的运行机制:检索过程并行化,核心搜索依赖高效的 faiss 向量索引,同时各层级的自动淘汰机制保证了系统不会无限膨胀,性能稳定。
  • 另外 user profile 的 90 种定义也很有意思。

缺点

  • 高昂的 LLM 调用开销:系统在记忆的组织、提炼和检索过程中大量依赖 LLM 进行分析(如连续性判断、摘要生成、知识提取),这将导致显著的 API 调用成本和一定的处理延迟。
  • 行为的不可预测性:由于记忆的组织在很大程度上依赖 LLM 的"判断",其效果与 LLM 的能力强相关,有时可能出现难以精确控制的"黑盒"行为。

适合的使用场景

该项目非常适合需要与用户建立 长期、深度、个性化关系 的 AI 应用,典型场景包括:

  • AI 伴侣或虚拟朋友:需要记住用户的点滴、性格和长期经历。
  • 个人专属助理:例如个人研究助理、学习伙伴或工作秘书,需要长期跟进特定项目和用户偏好。
  • 高端定制化客服:例如私人银行顾问、VIP 客户经理等,需要记住高价值用户的历史记录和特定需求。
  • 个性化教育导师:能够跟踪学生长期的学习进度、知识薄弱点和学习习惯。
相关推荐
展信佳_daydayup17 分钟前
0-1 深度学习基础——文件读取
算法
高斯林.神犇20 分钟前
冒泡排序实现以及优化
数据结构·算法·排序算法
Github项目推荐25 分钟前
跨平台Web服务开发的新选择(5802)
算法·架构
Jolyne_2 小时前
树节点key不唯一的勾选、展开状态的处理思路
前端·算法·react.js
秋难降2 小时前
正则表达式:为什么它成了程序员的 “分水岭”?
python·算法·正则表达式
没有bug.的程序员3 小时前
《常见高频算法题 Java 解法实战精讲(1):链表与数组》
java·算法·链表·数组
范特西_3 小时前
矩阵中的最长递增路径-记忆化搜索
c++·算法
都叫我大帅哥3 小时前
🌊 限流算法百科全书:从原理到实践,一篇搞定高并发流量管控
java·算法
醉卧红尘的鱼4 小时前
随机向量正交投影定理(Orthogonal Projection Theorem, OPT)_学习笔记
学习·算法