跳转至

Agent Memory:四种记忆

旅游助手现在能查资料、能从失败中学习。但这些能力散落在不同地方:对话历史在 messages 里,检索的知识在向量库里,Reflexion 的教训在 KV 存储里,偏好规则硬编码在 prompt 里。

这一页不引入新的运行机制。它做的是分类:Agent 的记忆有几种、各自存在哪里、什么时候读写。

四种记忆

类型 对标人类记忆 Agent 里的表现 存储
Working Memory 工作记忆 当前 messages 列表 运行时变量
Episodic Memory 情节记忆 Reflexion 的教训、过去的任务摘要 KV / 文件 / 数据库
Semantic Memory 语义记忆 RAG 的文档库、知识图谱 向量库 / 搜索引擎
Procedural Memory 程序记忆 工具定义、技能模板、固定规则 代码 / 配置文件
flowchart TD
  WM["Working Memory
当前 messages"] --> EP["Episodic Memory
过去的经验教训"] WM --> SM["Semantic Memory
外部文档/知识"] WM --> PM["Procedural Memory
工具/技能/规则"] EP -.->|"读教训到 prompt"| WM SM -.->|"检索到 context"| WM PM -.->|"工具定义到 system prompt"| WM

Working Memory:当前对话

就是 messages 列表。每一轮模型调用都读它。

  • 容量有限(context window)。
  • 运行结束就没了(除非显式持久化)。
  • 前六章讲的对话历史就是 working memory。

Episodic Memory:过去发生过什么

Reflexion 的教训是最典型的 episodic memory。还包括:

  • 过去任务的摘要。
  • 用户偏好("这个用户不喜欢太多景点")。
  • 上一次会话的关键决策。

Semantic Memory:世界知识

RAG 的文档库是 semantic memory。包括:

  • 结构化知识库。
  • 向量索引的文档片段。
  • 知识图谱。

Procedural Memory:怎么做

工具定义、技能模板、固定规则。包括:

  • 工具的 JSON schema。
  • prompt 模板。
  • 业务规则("订票前必须确认预算")。

存储选项

存储方式 适合什么 优点 代价
运行时变量(dict) Working Memory 最快 进程结束就没
KV 存储(InMemoryKV / JsonFileKV) Episodic:教训、偏好 简单、可持久化 不支持语义检索
文件系统(JSON / JSONL) Session 状态、配置 可版本控制 并发读写要自己管
向量数据库(FAISS / Pinecone / pgvector) Semantic:文档检索 语义搜索 需要嵌入模型,运维成本
关系数据库(SQLite / PostgreSQL) 结构化的 Episodic / Semantic 事务、查询灵活 Schema 设计

完整实现:带长期记忆的旅游助手

下面的例子把四种记忆整合在一个旅游助手里:

"""agent_memory_travel.py — 四种记忆的旅游助手"""
from __future__ import annotations

import json
from dataclasses import dataclass, field
from typing import Any


# ── 极简运行时 ──────────────────────────────────────────

@dataclass(frozen=True)
class Message:
    role: str
    content: str

class MockLLM:
    def __init__(self, responses: list[str]):
        self._responses = list(responses)
        self._idx = 0
    def complete(self, messages: list[Message]) -> str:
        if self._idx >= len(self._responses):
            raise RuntimeError("MockLLM 回复用完了")
        out = self._responses[self._idx]
        self._idx += 1
        return out


# ── 记忆存储 ────────────────────────────────────────────

class InMemoryKV:
    """Episodic memory 的 KV 存储。"""
    def __init__(self):
        self._data: dict[str, Any] = {}
    def get(self, key: str, default=None) -> Any:
        return self._data.get(key, default)
    def set(self, key: str, value: Any) -> None:
        self._data[key] = value


@dataclass(frozen=True)
class Document:
    doc_id: str
    text: str

class SemanticStore:
    """Semantic memory:简单的词频检索。"""
    def __init__(self, docs: list[Document]):
        self._docs = docs
    def search(self, query: str, k: int = 3) -> list[Document]:
        terms = self._tokenize(query)
        if not terms:
            return []
        scored = []
        for doc in self._docs:
            doc_tokens = self._tokenize(doc.text)
            score = sum(1 for t in terms for d in doc_tokens if t == d)
            if score > 0:
                scored.append((doc, score))
        scored.sort(key=lambda x: x[1], reverse=True)
        return [doc for doc, _ in scored[:k]]
    @staticmethod
    def _tokenize(text: str) -> list[str]:
        tokens, buf = [], ""
        for ch in text.lower():
            if ch.isalnum() or '\u4e00' <= ch <= '\u9fff':
                buf += ch
            else:
                if buf:
                    tokens.append(buf)
                    buf = ""
        if buf:
            tokens.append(buf)
        return tokens


# ── Procedural Memory:工具和规则 ───────────────────────

TOOLS = {
    "get_weather": "查询城市天气。参数:city, date。",
    "search_places": "搜索景点。参数:query, k。",
}

RULES = [
    "订票前必须确认用户预算。",
    "轻松步行行程不超过 3 个景点。",
    "下雨时优先推荐室内景点。",
]


# ── 带四种记忆的 Agent ──────────────────────────────────

def travel_agent(
    model: MockLLM,
    user_query: str,
    *,
    episodic: InMemoryKV,
    semantic: SemanticStore,
) -> str:
    """
    一个带四种记忆的旅游 Agent:
    - Working: messages 列表
    - Episodic: 教训和用户偏好(KV)
    - Semantic: 文档检索(向量库)
    - Procedural: 工具定义和规则(代码)
    """

    # ── 1. Procedural Memory → system prompt ──
    tools_text = "\n".join(f"- {name}: {desc}" for name, desc in TOOLS.items())
    rules_text = "\n".join(f"- {r}" for r in RULES)
    system_prompt = (
        "你是杭州旅游助手。\n\n"
        f"可用工具:\n{tools_text}\n\n"
        f"必须遵守的规则:\n{rules_text}"
    )

    # ── 2. Episodic Memory → 读教训和偏好 ──
    lessons = episodic.get("lessons", [])
    preferences = episodic.get("user_preferences", {})

    episodic_context = ""
    if lessons:
        episodic_context += "历史教训:\n" + "\n".join(f"- {l}" for l in lessons) + "\n\n"
    if preferences:
        episodic_context += "用户偏好:\n" + "\n".join(
            f"- {k}: {v}" for k, v in preferences.items()
        ) + "\n\n"

    print(f"[Episodic] 教训 {len(lessons)} 条,偏好 {len(preferences)} 项")

    # ── 3. Semantic Memory → 检索相关文档 ──
    docs = semantic.search(user_query, k=3)
    semantic_context = ""
    if docs:
        semantic_context = "相关资料:\n" + "\n".join(
            f"- [{d.doc_id}] {d.text[:80]}" for d in docs
        ) + "\n\n"
    print(f"[Semantic] 检索到 {len(docs)} 条文档")

    # ── 4. Working Memory → 构建 messages ──
    messages: list[Message] = [
        Message(role="system", content=system_prompt),
        Message(
            role="user",
            content=(
                f"{episodic_context}"
                f"{semantic_context}"
                f"用户问题:{user_query}"
            ),
        ),
    ]
    print(f"[Working] messages 共 {len(messages)} 条")

    # ── 生成回答 ──
    answer = model.complete(messages)

    # ── 5. 写回 Episodic Memory ──
    # 保存这次交互的摘要
    history = episodic.get("interaction_history", [])
    history.append({"query": user_query, "answer_snippet": answer[:60]})
    episodic.set("interaction_history", history)
    print(f"[Episodic] 交互历史更新,共 {len(history)} 条")

    return answer


# ── 运行 ────────────────────────────────────────────────

def main() -> None:
    # 初始化 Episodic Memory(模拟之前积累的教训和偏好)
    episodic = InMemoryKV()
    episodic.set("lessons", [
        "轻松步行要求时,景点不超过 3 个。",
        "推荐室内景点时要注明开放时间。",
    ])
    episodic.set("user_preferences", {
        "步行偏好": "轻松,不赶路",
        "饮食": "喜欢茶和素食",
    })

    # 初始化 Semantic Memory
    semantic = SemanticStore([
        Document("tea_museum", "中国茶叶博物馆,9:00-17:00,免费,室内,适合雨天。"),
        Document("tea_village", "龙井村采茶体验,户外,步行友好。"),
        Document("veggie", "杭州素食推荐:灵隐寺素面、龙井虾仁(可做素版)。"),
        Document("west_lake", "西湖全天开放,免费,环湖步行约 3 小时。"),
    ])

    model = MockLLM([
        (
            "根据您的偏好(轻松步行、喜欢茶和素食),推荐:\n"
            "1. 上午:龙井村采茶体验(步行友好,约 2 小时)[tea_village]\n"
            "2. 中午:灵隐寺素面 [veggie]\n"
            "3. 下午:中国茶叶博物馆(室内,9:00-17:00,免费)[tea_museum]\n"
            "全程 3 个景点,不赶路。"
        ),
    ])

    answer = travel_agent(
        model,
        "杭州一日游,喜欢茶文化",
        episodic=episodic,
        semantic=semantic,
    )
    print(f"\n回答:\n{answer}")

    # 验证记忆状态
    print(f"\n记忆状态:")
    print(f"  教训:{episodic.get('lessons')}")
    print(f"  偏好:{episodic.get('user_preferences')}")
    print(f"  历史:{episodic.get('interaction_history')}")


if __name__ == "__main__":
    main()

运行日志

[Episodic] 教训 2 条,偏好 2 项
[Semantic] 检索到 3 条文档
[Working] messages 共 2 条

回答:
根据您的偏好(轻松步行、喜欢茶和素食),推荐:
1. 上午:龙井村采茶体验(步行友好,约 2 小时)[tea_village]
2. 中午:灵隐寺素面 [veggie]
3. 下午:中国茶叶博物馆(室内,9:00-17:00,免费)[tea_museum]
全程 3 个景点,不赶路。

记忆状态:
  教训:['轻松步行要求时,景点不超过 3 个。', '推荐室内景点时要注明开放时间。']
  偏好:{'步行偏好': '轻松,不赶路', '饮食': '喜欢茶和素食'}
  历史:[{'query': '杭州一日游,喜欢茶文化', 'answer_snippet': '根据您的偏好(轻松步行、喜欢茶和素食),推荐:\n1. 上午:龙井村采茶体验(步行友好,约 2 小时)[tea_'}]

调试:trace 里看什么

要看的 怎么看 说明什么
Episodic 读取 prompt 里有没有教训和偏好 没有 = 记忆没被注入
Semantic 命中 检索到几条 0 条 = 检索有问题
Procedural 规则 system prompt 里有没有规则 规则缺失 = 行为不受控
Working memory 长度 messages 数 太长 = context 溢出风险
记忆写回 Episodic 更新了吗 没写回 = 下次会丢失

工程笔记

每种记忆的读写时机

记忆类型 什么时候读 什么时候写
Working 每次模型调用 每次工具返回后追加
Episodic 任务开始时 任务结束时(教训、摘要、偏好)
Semantic 检索时 索引构建时、新文档入库时
Procedural Agent 初始化时 部署时更新代码/配置

存储方案选择

场景 推荐存储 原因
快速原型 InMemoryKV + 内存列表 零依赖
单用户持久化 JsonFileKV + JSONL 文件即数据库
多用户生产 PostgreSQL + pgvector 事务 + 向量搜索
大规模文档 专用向量数据库 分布式检索

常见坑

现象 修法
Episodic 记忆无限增长 prompt 越来越长 加过期、摘要、上限
跨用户记忆泄漏 A 用户看到 B 的偏好 按用户隔离命名空间
Semantic 检索污染 检索到无关文档 更好的分块、更好的嵌入
Procedural 规则过时 旧规则不适用新场景 规则版本化
Working memory 溢出 context window 超限 摘要旧消息、滑动窗口

读完以后

Agent Memory 不是一个新模式,而是理解"记忆住在哪里、什么时候读写"的框架。

如果想了解怎么从失败中写教训,读 Reflexion。 如果想了解怎么检索外部知识,读 RAGAgentic RAG。 如果想了解对话历史怎么管理,回到前面的章节。