整理:《 Agent 的大脑:长短期记忆架构设计——从 RAG 到 Memory Graph 的工程演进》

今天翻到一篇不错的技术分享,看完之后自己也琢磨了一下,把思路梳理记录下来。

文章结构:

引言:为什么 Agent 需要记忆系统(LLM 的无状态缺陷)
三层架构总览
Working Memory 深度解析
Episodic Memory(向量存储 + 召回)
Semantic Memory(Agentic RAG)
Memory Graph 融合检索
记忆一致性与冲突解决(冲突检测)
生产级工程实践(Embedding 选型、分块策略、可观测性)
技术演进路径
前沿进展(GraphRAG、MemoryOS、隐私保护)
总结

摘要

大语言模型本身是无状态的——每次推理独立进行,上下文窗口一旦关闭,所有"经历"灰飞烟灭。然而真正有价值的 AI Agent
必须具备跨会话的持续学习能力、对历史事件的精准回溯能力,以及对领域知识的深度利用能力。这里系统梳理 Agent
记忆系统的三层架构设计(Working Memory / Episodic Memory / Semantic
Memory),深入剖析每一层的实现机制、工程挑战与优化策略,并通过完整代码示例展示从朴素 RAG 到 Memory Graph
的演进路径,最后结合记忆压缩、遗忘机制、记忆一致性等生产级关键问题给出工程实践建议。

一、为什么 Agent 需要记忆系统

1.1 LLM 的天然缺陷:无状态与遗忘

当前主流大语言模型(GPT-4、Claude、Qwen 等)在架构上本质是一个"无状态函数":输入 token 序列,输出 token 序列,推理结束后不保留任何内部状态。这意味着:

  • 跨会话失忆:用户昨天告诉 Agent “我喜欢 Python,不喜欢 Java”,今天 Agent 完全不记得;
  • 上下文窗口瓶颈:即便在单会话内,当对话长度超过模型上下文窗口(如 128K tokens),早期信息会被截断丢弃;
  • 知识静态化:模型权重在训练后固化,无法实时吸收新知识,只能通过 RAG 或 Fine-tuning 补偿。
这三个问题共同决定了:一个没有外部记忆系统的 LLM,无论参数量多大,都只是一个"健忘的天才"

1.2 记忆能力对 Agent 的战略价值

从产品维度看,记忆能力直接决定 Agent 的"温度"——用户是否感受到被理解、被记住。从技术维度看,记忆系统是 Agent 从"单次问答工具"升级为"持续学习助理"的核心基础设施,也是 Multi-Agent 系统中知识共享、经验传递的关键通道。

1.3 人类记忆的启发

认知科学将人类记忆分为:感觉记忆(Sensory)、短期工作记忆(Working)、长期情节记忆(Episodic)、长期语义记忆(Semantic)、程序性记忆(Procedural)等层次。Agent 记忆系统的架构设计高度借鉴了这一体系,但在工程实现上有其独特的约束与创新点。


二、三层记忆架构总览

如上方架构图所示,主流 Agent 记忆系统可分为三个层次,每层在时效性、容量、访问代价上呈现梯度差异。

2.1 三层架构对比

维度Working MemoryEpisodic MemorySemantic Memory对应概念短期工作记忆情节(事件)记忆语义(知识)记忆存储介质Context Window向量数据库 / KV Store向量DB + 知识图谱时效范围单次会话内历史会话 / 事件日志长期稳定知识容量极小(受限于上下文窗口)中等(可扩展)大(TB 级可行)访问延迟极低(in-memory)中等(向量检索 ~10ms)中高(KG 查询 ~50ms+)更新频率实时(每轮对话)会话结束后批量写入低频(知识库更新)典型实现Prompt 拼接ChromaDB / PineconeNeo4j / Weaviate

2.2 信息流转路径

如上方生命周期流程图所示,一次完整的 Agent 推理过程涉及:

1. 用户输入触发感知层;
2. 并行从 Episodic DB 和 Semantic KB 检索相关记忆片段;
3. 将检索结果注入 Working Memory(Prompt 组装);
4. LLM 基于完整上下文推理、执行工具调用;
5. 会话结束后将新产生的信息压缩、向量化,写回 Episodic Memory;
6. 定期知识提炼将高频稳定知识升华至 Semantic Memory。


三、Working Memory:上下文窗口的精细化管理

3.1 核心挑战:有限空间的最大化利用

Working Memory 对应 LLM 的 Context Window。以 Claude 3.5 为例,其 200K token 的窗口看似充裕,但一旦加入系统提示、工具描述、历史对话、检索内容,实际可用空间往往只剩 20%~40%。

Working Memory 管理的核心命题是:在有限的 token 预算内,放置信息价值密度最高的内容

3.2 Prompt 结构标准化

一个生产级 Working Memory 的 Prompt 结构通常如下:

[System Prompt]          int:
    """
    艾宾浩斯遗忘曲线:删除超龄且低重要性的记忆
    返回删除条数
    """
    cutoff = (
        datetime.utcnow() - timedelta(days=max_age_days)
    ).isoformat()

    # 查询候选删除记忆(低重要性 + 超龄)
    all_metas = self.collection.get(include=["metadatas", "ids"])
    to_delete = []
    for mid, meta in zip(all_metas["ids"], all_metas["metadatas"]):
        ts = meta.get("timestamp", "")
        importance = float(meta.get("importance", 0.5))
        if ts < cutoff and importance < 0.4:
            to_delete.append(mid)

    if to_delete:
        self.collection.delete(ids=to_delete)
    return len(to_delete)


五、Semantic Memory:知识图谱与结构化知识的融合

5.1 语义记忆 vs 情节记忆的本质区别

情节记忆是"具体的事件",语义记忆是"抽象的知识"。比如:

  • 情节记忆:"2024-06-10,用户说他在南京工作,偏好用 Python 做数据分析"
  • 语义记忆:"Python 是一种解释型编程语言,适合数据科学领域"
语义记忆通常来源于:外部知识库(文档、PDF、网页)、情节记忆的长期提炼(高频事实沉淀)、结构化知识图谱。

5.2 从朴素 RAG 到 Agentic RAG

朴素 RAG(Naive RAG)的流程是:将文档切片 → 向量化 → 存储 → Query 时检索最相似片段 → 注入 Prompt。这个方案在简单问答场景效果不错,但在 Agent 场景下有明显短板:

  • 单跳检索不足:繁琐问题需要多个知识片段的组合推理;
  • Query 质量依赖用户输入:用户的自然语言往往不是最优检索词;
  • 无法处理隐式依赖:知识点之间的关联关系未被捕获。
Agentic RAG 引入了 Agent 自主规划检索策略的能力:

class AgenticRAG:
    """
    Agentic RAG:Agent 自主决策检索策略
    支持 Query 改写、多跳检索、自我反思
    """

    def __init__(self, semantic_store, llm_client):
        self.store = semantic_store
        self.llm = llm_client

    def query_rewrite(self, original_query: str) -> List[str]:
        """
        将用户原始 Query 改写为多个更精准的检索词
        """
        prompt = f"""
将以下用户问题改写为 3 个不同角度的检索查询(JSON 数组格式):
原始问题:{original_query}
改写后的查询(只返回 JSON,不要其他内容):"""

        resp = self.llm.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
        )
        import json
        text = resp.choices[0].message.content.strip()
        try:
            queries = json.loads(text)
        except Exception:
            queries = [original_query]
        return queries

    def multi_hop_retrieve(
        self, query: str, max_hops: int = 3
    ) -> List[str]:
        """
        多跳检索:每跳结果作为下一跳的检索种子
        """
        all_results = []
        current_queries = self.query_rewrite(query)

        for hop in range(max_hops):
            new_queries = []
            for q in current_queries:
                results = self.store.similarity_search(q, k=3)
                all_results.extend(results)
                # 从检索结果中提取新的检索线索
                if hop < max_hops - 1:
                    follow_up = self._extract_follow_up_queries(
                        q, results
                    )
                    new_queries.extend(follow_up)
            current_queries = new_queries
            if not current_queries:
                break

        # 去重,按相关性排序
        seen = set()
        unique_results = []
        for r in all_results:
            if r.page_content not in seen:
                seen.add(r.page_content)
                unique_results.append(r)
        return unique_results[:10]

    def _extract_follow_up_queries(
        self, original_q: str, results: List
    ) -> List[str]:
        """从已检索内容提取新的检索方向"""
        context = "\n".join(r.page_content for r in results[:2])
        prompt = f"""
基于问题"{original_q}"和以下检索结果,
提取 1-2 个需要进一步检索的子问题(JSON 数组):
{context}
子问题:"""
        resp = self.llm.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
        )
        try:
            return json.loads(resp.choices[0].message.content.strip())
        except Exception:
            return []

    def self_reflect(
        self, query: str, retrieved_docs: List[str], answer: str
    ) -> dict:
        """
        自我反思:评估答案是否充分,是否需要更多检索
        """
        context = "\n".join(retrieved_docs[:5])
        prompt = f"""
问题:{query}
检索到的资料:{context}
当前答案:{answer}

请评估:
1. 答案是否完整回答了问题?(是/否)
2. 是否还需要补充检索?(是/否)
3. 如果需要,指出缺失的信息点(一句话)

以 JSON 格式返回:{{"complete": bool, "need_more": bool, "missing": str}}"""

        resp = self.llm.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
        )
        try:
            return json.loads(resp.choices[0].message.content.strip())
        except Exception:
            return {"complete": True, "need_more": False, "missing": ""}

5.3 知识图谱:结构化关系的语义记忆

向量数据库擅长语义相似度检索,但对实体关系的表达能力较弱。知识图谱(Knowledge Graph,KG)则以图结构存储实体与关系,能够支持繁琐的关系推理。

以 Neo4j 为例,将 Agent 的 Semantic Memory 部分存储在知识图谱中:

from neo4j import GraphDatabase

class MemoryGraph:
    """
    基于 Neo4j 的 Memory Graph:
    存储实体、关系、属性,支持图遍历查询
    """

    def __init__(self, uri: str, user: str, password: str):
        self.driver = GraphDatabase.driver(uri, auth=(user, password))

    def store_entity(self, entity_type: str, name: str, props: dict):
        """写入或更新一个实体节点"""
        with self.driver.session() as session:
            session.run(
                f"""
                MERGE (e:{entity_type} {{name: $name}})
                SET e += $props
                """,
                name=name,
                props=props,
            )

    def store_relation(
        self,
        from_entity: str,
        relation: str,
        to_entity: str,
        props: dict = None,
    ):
        """写入两个实体之间的关系"""
        with self.driver.session() as session:
            session.run(
                f"""
                MATCH (a {{name: $from_name}})
                MATCH (b {{name: $to_name}})
                MERGE (a)-[r:{relation}]->(b)
                SET r += $props
                """,
                from_name=from_entity,
                to_name=to_entity,
                props=props or {},
            )

    def query_neighbors(
        self, entity_name: str, depth: int = 2
    ) -> List[dict]:
        """
        查询某实体的 N 跳邻居:
        用于向 Agent 提供关联知识上下文
        """
        with self.driver.session() as session:
            result = session.run(
                f"""
                MATCH (start {{name: $name}})-[*1..{depth}]-(neighbor)
                RETURN DISTINCT neighbor.name AS name,
                       labels(neighbor) AS types,
                       neighbor AS props
                LIMIT 20
                """,
                name=entity_name,
            )
            return [
                {
                    "name": r["name"],
                    "types": r["types"],
                }
                for r in result
            ]

    def extract_and_store(
        self, text: str, llm_client
    ) -> None:
        """
        从文本中提取实体和关系,自动写入知识图谱
        (Information Extraction 管线)
        """
        prompt = f"""
从以下文本中提取所有实体和关系,以 JSON 格式返回:
{{
  "entities": [{{"name": "...", "type": "Person|Organization|Product|Concept"}}],
  "relations": [{{"from": "...", "relation": "...", "to": "..."}}]
}}
文本:{text}
只返回 JSON,不要其他内容。"""

        resp = llm_client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
        )
        try:
            data = json.loads(resp.choices[0].message.content.strip())
        except Exception:
            return

        for entity in data.get("entities", []):
            self.store_entity(
                entity.get("type", "Entity"),
                entity["name"],
                {},
            )
        for rel in data.get("relations", []):
            self.store_relation(
                rel["from"],
                rel["relation"].upper().replace(" ", "_"),
                rel["to"],
            )


六、Memory Graph:向量与图的融合检索### 6.1 融合检索的动机

单纯的向量检索善于捕捉语义相似性,但对实体关系的推理能力弱;单纯的图谱遍历精准但依赖实体识别质量,对语义模糊的 Query 泛化性差。Memory Graph 的核心思路是将两条检索路径并行执行,再通过融合策略取长补短。

如上图所示,融合后的结果通过 Reciprocal Rank Fusion(RRF)Cross-encoder 重排序 产生最终排名,再注入 Agent 的 Working Memory。

6.2 代码示例:混合检索融合

from typing import List, Tuple

class MemoryGraphRetriever:
    """
    Memory Graph 混合检索器:
    向量检索 + 知识图谱 并行,RRF 融合
    """

    def __init__(
        self,
        episodic_store: EpisodicMemoryStore,
        memory_graph: MemoryGraph,
    ):
        self.episodic = episodic_store
        self.graph = memory_graph

    def _reciprocal_rank_fusion(
        self,
        result_lists: List[List[Tuple[str, float]]],
        k: int = 60,
    ) -> List[Tuple[str, float]]:
        """
        Reciprocal Rank Fusion (RRF):
        多路结果融合,k 是平滑参数(通常取 60)
        """
        scores: dict[str, float] = {}
        for result_list in result_lists:
            for rank, (doc_id, _) in enumerate(result_list):
                scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (k + rank + 1)

        sorted_results = sorted(scores.items(), key=lambda x: x[1], reverse=True)
        return sorted_results

    def retrieve(
        self,
        query: str,
        entities: List[str] = None,
        top_k: int = 8,
    ) -> List[dict]:
        """
        并行执行向量检索 + 图谱检索,RRF 融合
        """
        import asyncio

        # --- 路径 1:向量语义检索 ---
        vector_results = self.episodic.recall(query, top_k=top_k * 2)
        vector_ranked = [
            (r["content"], r["score"]) for r in vector_results
        ]

        # --- 路径 2:图谱关系检索 ---
        graph_ranked = []
        if entities:
            for entity in entities:
                neighbors = self.graph.query_neighbors(entity, depth=2)
                # 将图谱结果转换为文本片段(简化示例)
                for neighbor in neighbors:
                    desc = f"{entity} 关联到 {neighbor['name']} (类型: {','.join(neighbor['types'])})"
                    graph_ranked.append((desc, 0.7))  # 固定初始分

        # --- RRF 融合 ---
        fused = self._reciprocal_rank_fusion(
            [vector_ranked, graph_ranked]
        )

        # 取 top_k 并组装返回结构
        final_results = []
        content_map = {r["content"]: r for r in vector_results}
        for doc_id, score in fused[:top_k]:
            base = content_map.get(doc_id, {})
            final_results.append({
                "content": doc_id,
                "score": score,
                "metadata": base.get("metadata", {}),
                "source": "vector" if doc_id in content_map else "graph",
            })

        return final_results


七、记忆一致性与冲突解决

7.1 记忆矛盾问题

随着时间推移,Agent 的记忆库会产生语义冲突

  • 用户某天说"我在北京工作",三个月后说"我已经搬到上海了";
  • 同一知识在不同版本的文档中有不同描述;
  • LLM 总结产生的摘要可能丢失或曲解原始信息。

7.2 记忆冲突检测与解决策略

class MemoryConsistencyChecker:
    """
    记忆一致性检测器:
    写入新记忆前检测是否与已有记忆矛盾,并决策处理方式
    """

    def __init__(self, episodic_store, llm_client):
        self.store = episodic_store
        self.llm = llm_client

    def check_and_resolve(
        self, new_memory: str, existing_memories: List[str]
    ) -> dict:
        """
        返回:
        - action: "insert" | "replace" | "merge" | "discard"
        - reason: 原因说明
        - merged: 合并后内容(若 action=="merge")
        """
        if not existing_memories:
            return {"action": "insert", "reason": "无已有记忆"}

        context = "\n".join(
            f"[{i+1}] {m}" for i, m in enumerate(existing_memories)
        )
        prompt = f"""
新记忆:{new_memory}
已有相关记忆:
{context}

请判断新记忆与已有记忆的关系,返回 JSON:
{{
  "conflict": true/false,
  "action": "insert"|"replace"|"merge"|"discard",
  "reason": "...",
  "merged": "..."  // 仅 action=merge 时填写
}}
判断标准:
- 新记忆是更新的事实 → replace(指定要替换的编号)
- 新记忆提供了补充信息 → merge
- 新记忆完全矛盾且旧的更可信 → discard
- 新记忆是独立新信息 → insert"""

        resp = self.llm.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
        )
        try:
            return json.loads(resp.choices[0].message.content.strip())
        except Exception:
            return {"action": "insert", "reason": "解析失败,默认插入"}


八、生产级工程实践:避坑指南

8.1 Embedding 模型的选择

Embedding 质量直接决定向量检索精度。生产建议:

  • 中文场景:优先选择 text-embedding-3-large(OpenAI)或 bge-large-zh(BAAI,开源);
  • 成本敏感场景text-embedding-3-small 在大多数任务上性价比极高;
  • 离线/私有部署bge-m3(支持多语言多粒度检索)或 stella_en_400M_v5

8.2 分块策略(Chunking)的工程细节

文档切片质量是 RAG 效果的基础,几个常被忽视的要点:

  • 语义分块优于固定长度分块:使用句边界、段落边界切分,避免在句子中间截断;
  • 上下文重叠:相邻 chunk 保留 20% 重叠,避免关键信息落在边界;
  • 层次化分块:大 chunk 用于检索(提供更多上下文),小 chunk 用于精确定位;
  • 元数据富化:在 chunk 中注入文档标题、章节信息,帮助模型理解局部内容的全局位置。

8.3 向量检索的精度优化

  • Hybrid Search:向量检索 + BM25 关键词检索并行,RRF 融合,比纯向量检索通常提升 10%~20% 的 Recall;
  • Reranking:检索后用 Cross-encoder(如 bge-reranker-v2-m3)对 top-50 结果重新打分,取 top-5,精度显著提升;
  • 查询扩展:用 LLM 对用户 Query 进行 HyDE(假设文档嵌入)或多角度改写,增强检索召回率。

8.4 记忆系统的可观测性

生产环境必须对记忆系统进行监控:

  • 检索命中率:监控每次召回的平均分值,分值持续下降说明记忆质量在退化;
  • 记忆库规模:监控向量数量增长,结合遗忘机制控制规模;
  • 检索延迟:P99 延迟超过 100ms 需考虑缓存或索引优化;
  • 幻觉率:定期抽样检查 Agent 输出是否与召回记忆内容一致。

九、从 RAG 到 Memory Graph:演进路径总结### 9.1 四个阶段的核心差异

阶段一(Naive RAG):文档切块→向量化→相似度检索→塞入 Prompt。实现成本极低,但对繁琐推理无能为力,且没有跨会话记忆能力。适合:简单文档问答 MVP。

阶段二(Advanced RAG):引入 Query 改写、混合检索(向量+BM25)、Cross-encoder 重排序。显著提升检索精度,但仍属于"单次无状态检索"。适合:知识库问答生产环境。

阶段三(Episodic RAG):引入多层记忆架构,情节记忆支持跨会话召回,Working Memory 精细化管理。Agent 开始有了"记住你是谁"的能力。适合:有持续用户关系的 Agent 产品。

阶段四(Memory Graph):向量检索与知识图谱融合,支持多跳关系推理,记忆自动提炼与知识图谱动态更新。这是当前学术前沿与工业探索的主战场,代表性工作包含 Microsoft 的 GraphRAG、Meta 的 MemoryOS 等。


十、前沿进展与未来方向

10.1 GraphRAG:微软的图增强 RAG

2024 年微软发布的 GraphRAG 将社区检测算法(Leiden 算法)应用于文档知识图谱构建,通过层次化社区摘要实现"全局语义理解"——这是传统向量 RAG 无法做到的。在"这批文档的整体主题是什么"类问题上,GraphRAG 相比 Naive RAG 有质的飞跃。

10.2 MemoryOS:分层记忆操作系统

MemoryOS(2025 年论文)将操作系统的页面置换算法(LRU、优先级队列)引入 Agent 记忆管理,构建了类 OS 的三级记忆层次(短期缓冲/中期整合/长期存储),并通过记忆热度动态调度,在多任务长对话场景下显著优于固定容量的 Episodic Memory。

10.3 记忆的个性化与隐私

随着记忆系统能力增强,隐私保护成为核心挑战:

  • 差分隐私 Embedding:对用户私有信息的向量化引入噪声,防止原始内容被逆向还原;
  • 记忆权限分层:区分个人记忆(仅本人可访问)与共享记忆(多 Agent 共享),设计细粒度访问控制;
  • 主动遗忘机制:支持用户主动触发特定记忆的删除(GDPR 合规需求)。

10.4 持续学习与记忆的联合优化

长远来看,外部记忆系统与模型权重更新(持续学习/增量微调)的协同设计是终极方向:记忆系统捕捉高频稳定知识后,触发模型的增量微调,将知识"内化"进权重;而权重微调又能提升记忆检索的语义对齐精度——形成正向飞轮。


十一、总结

这里系统梳理了 AI Agent 记忆系统的三层架构,核心结论如下:

  • Working Memory 是 Agent 的即时意识,关键工程任务是 token 预算管理和 Prompt 精细化组装;
  • Episodic Memory 是 Agent 的个人经历档案,向量数据库 + 语义检索是主流实现,遗忘机制和重要性评估是生产必需;
  • Semantic Memory 是 Agent 的知识基础,向量 RAG 解决语义检索,知识图谱解决关系推理,两者融合(Memory Graph)是当前前沿;
  • 从 Naive RAG 到 Memory Graph 的演进,本质是 Agent 从"工具"向"具身认知主体"的升级——它开始有了记忆,有了经验,也开始真正地"认识"用户。
记忆,是智能的基础;也是 AI Agent 从演示走向生产的最关键一步。
这篇笔记就先到这里,后面用到新的思路或者发现有问题再补充。

评论 (0)

暂无评论