🧸 为什么 AI Agent 需要记忆?

如果你用过 ChatGPT、Claude 或任何 LLM,一定遇到过这个场景:

聊到第 50 条消息时,AI 已经忘了第 3 条你告诉它的事情。

这就是 无状态上下文窗口 的局限。当前主流方案是 RAG(检索增强生成)——把所有信息塞进向量数据库,用的时候搜一下。但 RAG 有个致命问题:

它不像人。

人类的记忆不是这样工作的:

  • 你不会把所有事情记成一模一样的"向量"
  • 有些事你记得很清楚,有些事你慢慢忘了
  • 情绪激动的事情更容易记住
  • 常做的事情会变成"下意识"的技能

Memory Bear 就是试图回答一个问题:如果 AI 的记忆系统更像人类大脑,会怎样?


🏛️ 三大认知科学基石

Memory Bear 的设计融合了三个经典认知科学理论:

1. ACT-R 激活扩散模型

ACT-R(Adaptive Control of Thought–Rational)是 Anderson 等人提出的认知架构,其核心公式:

$$A_i = B_i + \sum_j W_j \cdot S_{ji} + \varepsilon$$

  • $A_i$:记忆 $i$ 的总激活值
  • $B_i$:基础激活值(取决于访问频率和时效性)
  • $W_j \cdot S_{ji}$:从关联节点 $j$ 扩散来的激活
  • $\varepsilon$:噪声项(实现中省略以保证确定性)

直觉理解:当你想到"咖啡",与咖啡关联的记忆(比如"你最喜欢的咖啡馆"、“上次和朋友喝咖啡聊天”)都会被"激活"。关联越近,激活越强。

2. 艾宾浩斯遗忘曲线

1885 年,Ebbinghaus 通过自我实验发现了遗忘的规律:

$$S = e^{-t/k}$$

记忆强度随时间指数衰减。但关键在于 间隔重复效应(Spaced Repetition)——每次复习都会让衰减变慢。

Memory Bear 的实现:

$$S = e^{\frac{-k \cdot \Delta t}{1 + 0.5n}} \times (1 + 0.3\sigma)$$

其中 $n$ 是访问次数,$\sigma$ 是情绪显著性。访问越多、情绪越强烈 → 记得越牢。

3. 情绪神经科学

McGaugh (2004) 的研究表明,杏仁核-海马体交互会增强情绪鲜明事件的编码。Memory Bear 使用 Russell (1980) 的 效价-唤醒度二维模型

  • 效价(Valence):积极/消极,范围 [-1, 1]
  • 唤醒度(Arousal):平静/激动,范围 [0, 1]
  • 显著性 = $|valence| \times 0.4 + arousal \times 0.6$

正面和负面高唤醒事件都会被优先编码和巩固。


🏗️ 四层架构设计

O A r p c p h S E l e t n i s o c c t r o a r a d t a g i i t e n o i g n o L n a L L y a a L e y y a r e e y r r e r r A E M e c x u m t p l e i l t m v i i b a c m e t i o r i t d ( o a ) n ( l g / / r a r R p s e e h t c t ) r a r u l i + c l e t ( v I u ) a m r l p e / l d / i c c m o C i e n o t m s n o o s ( r l o p y i l a d i t a d t t a e e t r ( i n ) o s n )

自底向上解读

层级 职责 类比人类
Encoding 感知原始输入,提取标签、检测情绪 感觉器官 + 知觉
Storage 存储显性记忆(事实/事件)和隐性记忆(技能/习惯) 海马体 / 基底神经节
Orchestration 激活扩散、多策略检索、记忆巩固 前额叶皮层执行控制
Application 对外暴露简洁 API 你的主观体验

🔩 核心实现解析

数据模型:Memory

每条记忆是系统中的一等公民:

class Memory(BaseModel):
    id: str                    # 内容哈希,用于去重
    type: MemoryType           # EPISODIC / SEMANTIC / PROCEDURAL / WORKING
    content: str               # 自然语言内容
    tags: list[str]            # 语义标签
    associations: list[str]    # 关联记忆 ID(激活扩散图边)
    emotion: Emotion           # 效价 × 唤醒度
    activation: Activation     # ACT-R 激活状态
    created_at: float          # 创建时间
    last_accessed: float       # 最后访问时间
    access_count: int          # 访问次数
    decay_rate: float = 0.3    # 艾宾浩斯衰减常数
    strength: float = 1.0      # 编码强度

设计要点

  • id 由内容的 SHA-256 哈希生成 → 相同内容自动去重
  • type 对应 Tulving (1972) 的记忆分类法
  • 衰减不是简单的时间函数,而是频率 × 时效性 × 情绪的综合

编码层:从文本到结构化记忆

class Encoder:
    def encode(self, content, memory_type, tags, emotion, ...):
        # 1. 自动标签提取(CJK + Latin 关键词)
        auto_tags = self._extract_tags(content)

        # 2. 情绪检测(关键词启发式)
        if emotion is None:
            emotion = self._detect_emotion(content)

        # 3. 生成内容哈希 ID
        mem_id = self._generate_id(content, memory_type)

        return Memory(id=mem_id, content=content, tags=all_tags, ...)

自动标签提取同时处理中文和英文:

# 中文:连续 2+ 个汉字作为一个 token
# 英文:2+ 字母的单词
tokens = re.findall(r"[\u4e00-\u9fff]{2,}|[a-zA-Z]{2,}\d*", content.lower())

情绪检测基于关键词词典——正面词(好/棒/excellent)、负面词(错/失败/crash)、高唤醒词(紧急/重要/critical)。虽然简单,但为后续的情绪调节提供了基础信号。

存储层:显性记忆(知识图谱)

显性记忆使用加权邻接表构建知识图谱:

class ExplicitMemory:
    _nodes: dict[str, Memory]              # id → Memory
    _adj: dict[str, dict[str, float]]      # 邻接表(带权重)
    _tag_index: dict[str, set[str]]        # 标签倒排索引

边的构建规则

  1. 共享标签:两个记忆有相同标签 → 建立边,权重随共享标签数对数增长
  2. 显式关联:记忆的 associations 列表 → 直接建边,权重 1.0
# 共享标签权重:log1p 增长,避免边权爆炸
weight = 1.0 + math.log1p(count - 1) if count > 1 else 1.0

图遍历使用 BFS(广度优先搜索),支持多跳邻居查询——这正是激活扩散的基础设施。

存储层:隐性记忆(模式匹配)

隐性记忆模拟"下意识"的技能和习惯:

class ImplicitMemory:
    _patterns: dict[str, [response, confidence]]  # pattern → response

学习机制——重复学习增强信心(饱和更新):

new_conf = old_conf + (1.0 - old_conf) * incoming_confidence

匹配机制——使用 difflib.SequenceMatcher 做模糊匹配:

# "早安" 匹配已学模式 "早上好" → 相似度 0.67 → 触发响应
ratio = SequenceMatcher(None, query, stored_key).ratio()
effective_confidence = confidence * ratio

这意味着即使输入不是精确匹配,相似的模式也能触发已学响应。

编排层:扩散激活引擎

这是 ACT-R 理论的核心实现:

class SpreadingActivation:
    def activate(self, seed_ids, now):
        # BFS 遍历
        queue = deque([(sid, 0) for sid in seed_ids])

        while queue:
            current_id, depth = queue.popleft()
            if depth >= self.max_depth:
                continue

            for nid in neighbors:
                # 激活随深度衰减 × 节点自身记忆强度
                spread = current_activation * self.decay_factor
                received = spread * neighbor.current_strength(now)

                if received >= self.threshold:
                    activation_map[nid] = received
                    queue.append((nid, depth + 1))

关键参数

  • decay_factor = 0.85:每跳衰减 15%
  • max_depth = 3:最多扩散 3 跳
  • threshold = 0.15:低于此激活值不再继续扩散

编排层:多策略检索

检索引擎融合三路信号:

class RetrievalEngine:
    KEYWORD_WEIGHT = 0.4    # 关键词匹配
    TAG_WEIGHT = 0.2        # 标签匹配
    ACTIVATION_WEIGHT = 0.4 # 扩散激活

    def retrieve(self, query, tags, now, limit):
        # 1. 关键词搜索(AND 语义)
        keyword_scores = ...

        # 2. 标签过滤(OR 语义)
        tag_scores = ...

        # 3. 从命中种子扩散激活
        seed_ids = set(keyword_scores) | set(tag_scores)
        activation_scores = self.spreading.activate(seed_ids, now)

        # 4. 分数融合
        for mid in all_ids:
            score = (0.4 * kw + 0.2 * tg + 0.4 * ac)

为什么要三路融合?

策略 擅长 弱点
关键词 精确匹配 语义盲区
标签 粗粒度分类 缺乏关联
扩散激活 发现隐含关联 冷启动问题

三者互补:关键词保证精度,标签提供召回,扩散激活发现"意料之外"的关联。

编排层:记忆巩固(模拟睡眠)

巩固引擎模拟人脑的睡眠整理过程:

class MemoryConsolidation:
    def consolidate(self, now):
        for mid, mem in all_mems.items():
            strength = mem.current_strength(now)

            # 遗忘:强度低于阈值 → 删除
            if strength < 0.05:
                self.explicit.remove(mid)

            # 增强(LTP):强度高 + 访问频繁 → 激活值提升
            if strength > 0.7 and mem.access_count > 3:
                mem.activation.base_level *= 1.2  # 饱和上限 2.0

        # 模式提取:3+ 条相似记忆 → 提炼为隐性模式
        self.extract_pattern(all_mems)

三个过程

  1. 遗忘:长时间不用的记忆自然衰减、最终被清除
  2. LTP(长时程增强):频繁访问的记忆被强化
  3. 模式提取:重复出现的经验被蒸馏为自动化技能

🎯 完整使用示例

from memory_bear import MemoryBear, MemoryType

bear = MemoryBear()

# ── 存储记忆 ──
bear.remember("用户喜欢喝美式咖啡", tags=["偏好", "咖啡"])
bear.remember("用户是软件工程师", tags=["职业"])
bear.remember(
    "项目上线当天出了严重 bug,团队通宵修复",
    tags=["项目", "bug"],
    # 情绪会自动检测到"严重""通宵"等高唤醒词
)

# ── 检索记忆 ──
result = bear.recall("咖啡")
for mem, score in result.memories:
    print(f"  [{score:.2f}] {mem.content}")
# 不仅找到"咖啡"那条,还通过共享标签"偏好"激活了相关记忆

# ── 学习技能(隐性记忆)──
bear.learn_pattern("早上好", "提醒用户喝咖啡")
match = bear.match_pattern("早安")  # 模糊匹配!
# → ("提醒用户喝咖啡", 0.35)

# ── 记忆巩固(模拟睡眠)──
report = bear.consolidate()
print(f"遗忘 {report.forgotten} 条,增强 {report.reinforced} 条")

# ── 持久化 ──
bear.save("/tmp/memories.json")

📐 设计决策与权衡

为什么不用向量数据库?

维度 向量数据库 (RAG) Memory Bear
检索方式 语义相似度 关键词 + 标签 + 图扩散
遗忘 不会遗忘 自然衰减 + 巩固
情绪 效价/唤醒度调节
关联发现 embedding 空间距离 显式图结构(可解释)
依赖 需要嵌入模型 零依赖(纯算法)
适用场景 大规模文档检索 Agent 个人记忆

Memory Bear 不替代 RAG,而是互补:RAG 擅长检索外部知识,Memory Bear 擅长管理 Agent 的"个人经历"

为什么用 Pydantic v2?

  • 类型安全:所有模型都有完整的类型标注和验证
  • 序列化:内置 JSON 序列化/反序列化,持久化零成本
  • 不可变选项ConfigDict(extra="forbid") 防止意外字段注入
  • 计算字段emotion.salience 自动从 valence/arousal 计算

为什么 BFS 而不是 PageRank?

扩散激活需要的是即时性——从种子节点出发,快速评估哪些邻居最相关。PageRank 是全局静态排序,计算成本高且不响应实时查询。BFS + 衰减因子在精确度和性能之间取得了良好平衡。


📊 项目数据

  • 代码量:~1200 行(含注释和文档字符串)
  • 测试覆盖:23 个测试(核心模型 11 + 存储层 12)
  • 依赖:仅 pydantic + numpy
  • Python 版本:3.10+
$ = = p = y = t = h = o = n = 3 = = - = m = = p = y = t = e = s = t = = t = e = s = t = s = / 2 3 p a s s e d i n 0 . 4 2 s = = = = = = = = = = = = = = = = = = = = = = = = =

🚀 未来方向

Memory Bear 目前是一个概念验证级别的实现,有几个明显的扩展方向:

  1. 接入真正的 Embedding:目前 embedding 字段为 None,可以接入 OpenAI/Claude 的嵌入 API,在检索时增加语义相似度这一路信号
  2. 时间感知检索:加入"时间衰减加权"——越近的记忆得分越高
  3. 多模态编码:扩展 Encoder 支持图片、音频输入
  4. 层级化存储:短期记忆(Working Memory)→ 长期记忆的迁移机制
  5. 与 LLM 集成:提供 LangChain/LlamaIndex 适配器,让 Agent 无缝使用

📝 总结

Memory Bear 证明了一件事:给 AI Agent 加上认知科学启发的记忆系统,不仅是可行的,而且代码量可控、效果可感知

核心收获:

  • ✅ ACT-R 激活扩散让记忆检索有了"联想"能力
  • ✅ 艾宾浩斯遗忘曲线让系统自动管理记忆生命周期
  • ✅ 情绪建模让重要事件得到优先处理
  • ✅ 显性/隐性双系统让 Agent 同时拥有"事实记忆"和"技能记忆"

这不是终局,而是一个起点。当 AI Agent 真正走向长期运行、持续交互的场景,记忆系统将成为最核心的基础设施。


Memory Bear — 让 AI Agent 拥有人类般的记忆。

项目灵感来自论文 Memory Bear (arXiv:2512.20651)