Skip to content

上下文工程

Agent 记忆系统

同一个用户上周说过他对花生过敏,今天他点外卖你能不能想起来——这就是 Agent 的记忆问题。它和"会话历史"是两件事。

适合阶段:Agent 进阶 / 生产核心:跨 session 的持久化记忆架构面试重点:分层 + 写入策略 + 召回

本文边界:聚焦 跨 session 的长期记忆架构——什么时候写、写到哪里、怎么读回来。同一 session 内的滑动窗口、摘要轮转见 会话历史管理;token 级压缩见 上下文压缩;底层向量检索原理见 RAG 基础

面试官想考什么

读完这篇你要能正面回答下面这些题。每题后面括号里是面试官真正想看你答出什么。

Agent 的 memory 和 chat history 有什么区别?考概念分层,看你能不能分清"短期 / 长期 / 会话历史"三个东西。
episodic、semantic、procedural memory 分别是什么?各放哪里?考认知科学迁移到工程的能力,是否照搬过 CoALA 论文的分类。
什么时候 commit 一条信息到长期记忆?谁来决定?考写入策略的取舍——LLM 自决 vs 触发式 vs 显式工具。
MemGPT 的"操作系统类比"具体怎么落地的?考对论文的真实阅读,能不能讲清 main context / external context 怎么切换。
mem0、Letta、LangMem、Zep 这几家路线区别在哪?考主流方案选型能力,2025 面试高频。
多用户共用同一个 memory store 怎么做隔离?memory 中毒是什么?考生产经验和安全意识。
记忆条目越积越多,召回质量怎么保持?怎么去重和遗忘?考长期运行 Agent 的工程细节。
检索回来的 memory 应该塞进 system prompt 还是 user message?考对 instruction hierarchy 和位置偏置的理解。

为什么"会话历史"不够用

考虑一个真实场景:你做了一个健康助理 Agent,用户小张第一次对话告诉它:

"我对花生过敏,麻烦帮我推荐午餐。"

Agent 推荐了三明治。对话结束。

三天后,小张回来:

"今天又要点外卖了,推荐一下。"

如果你只有 chat history 滑动窗口——这次是新 session,上次的对话早已不在 prompt 里。Agent 大概率推荐一个含花生酱的选项,小张当场过敏住院。

把 chat history 全部保存到数据库、每次都塞回 prompt?

  • 一个用户用一年,对话累计 50 万 token,prefill 都要几分钟
  • 99% 的内容("那家餐厅几点关门?""不知道")是噪音
  • 跨用户共用知识("花生酱三明治含花生")每个用户都要单独学一遍

所以长期记忆不是把 chat history 存得更久,而是从 history 中提炼出"值得跨 session 携带的东西",结构化存储,按需取回。这件事和会话历史管理是完全不同的工程问题。

一句话区分:chat history 是 raw log,memory 是 processed facts。前者按时间组织,后者按语义组织。


记忆的四层分类(CoALA 框架)

学术界对 Agent 记忆有较成型的分类。Sumers et al. 2023 的 Cognitive Architectures for Language Agents (arxiv 2309.02427),把 Agent 记忆映射到认知科学的经典分层,目前几乎所有主流 memory 系统(Letta、LangMem、mem0)都借用了这套术语。

类型内容存放例子
Working memory当前 task 正在用的临时状态LLM context window"用户现在问的是周二的航班"
Episodic memory过去发生过的具体事件、对话vector store + 时间戳"2026-03-12 用户说过他周三要去北京"
Semantic memory关于世界 / 用户的事实性知识KV store / fact DB / KG"用户偏好窗口座位"
Procedural memory学到的"怎么做" / 技能 / prompt 模板代码 / prompt 库 / fine-tuned weights"处理退款的标准流程"

关键区分

  • Episodic 是 "what happened"——具体事件,带时间和上下文
  • Semantic 是 "what is true"——抽象事实,去掉了具体场景

两者会互相转化。多次 episodic("用户上周点了沙拉""用户上上周也点了沙拉""三次午餐全是沙拉")可以聚合提炼成一条 semantic("用户午餐偏好沙拉")。这是长期记忆系统最有价值也最难做对的部分。


Short-term 与 Long-term:边界在哪

维度Short-termLong-term
存活期一个 session 内跨 session 跨天跨月
物理位置LLM context window外部存储(vector / SQL / KG)
容量受 context window 限制(几 K 到几百 K token)几乎无限
访问代价0(已经在 prompt 里)一次检索 + 一次拼接
写入决策自动(每轮对话都进)主动(要决定哪些值得存)

short-term 的实现就是 chat history 管理——滑动窗口、摘要轮转、向量回召等都在 会话历史管理 里展开。本文从 long-term 开始。


长期记忆的三种底层存储

不是所有 memory 都该塞向量库。生产系统通常混用

1. Vector store——给 episodic memory

适合:"过去说过什么"——内容多样、查询是模糊语义匹配。

存:embedding(对话片段) → 向量库
查:embedding(当前问题) → top-K 相似事件

主流选择:Pinecone、Weaviate、Qdrant、Chroma。本质就是 RAG,详见 RAG 基础

:vector store 检索不擅长精确事实("用户的电话号码是多少"),相似度高的不等于"对的那条"。

2. SQL / KV store——给 semantic memory

适合:结构化、精确事实——user_profile.allergies = ["peanut", "shellfish"]

sql
SELECT * FROM user_facts WHERE user_id = ? AND fact_type = 'preference';

写入时 LLM 抽取成结构化 schema,读取时按字段精确 join。几乎所有生产 Agent 的"用户偏好""个人资料"层都用 SQL/KV,不是向量库——精确度差太多。

3. Knowledge graph——给关系密集的 memory

适合:"谁认识谁""A 公司收购了 B 公司""项目 X 依赖项目 Y"。

Zep(Graphiti)和 Microsoft GraphRAG 走这条路。优势是能回答多跳问题("小张的老板的助理叫什么");代价是 KG 抽取本身容易出错,schema 设计是大工程。

实战经验:80% 的 Agent 应用,semantic 用 KV(用户档案)+ episodic 用向量库就够了。KG 只在关系推理是核心 use case 时引入——不是默认选项。


记忆系统怎么工作(整体流程)

流程图

三个核心环节:writer(什么时候写、写什么)、store(存哪里)、retriever(什么时候读、怎么选)。各家方案的差异主要在 writer 和 retriever 的策略。


MemGPT:把 LLM 当操作系统

Packer et al. 2023 MemGPT: Towards LLMs as Operating Systems (arxiv 2310.08560) 是这个领域的开山之作。核心类比:

  • LLM context window = RAM——容量有限,访问免费
  • 外部存储 = disk——容量无限,访问需要 I/O
  • OS 负责在 RAM / disk 间换页——MemGPT 让 LLM 自己负责

具体做法:

  1. context 分为 main context(system prompt + working memory + 最近对话)和 external context(recall storage 存旧对话、archival storage 存抽取出的事实)
  2. 给 LLM 暴露 4 个 memory 工具
    • core_memory_append / core_memory_replace——往 working memory 里加/改
    • archival_memory_insert / archival_memory_search——往长期存储里写 / 查
  3. 当 main context 快满时,LLM 自己调用 archival_memory_insert 把旧内容"换页"到长期存储

为什么这个设计有意思:决策权在 LLM 自己手里。模型自己判断"这条信息以后还用得着,存起来",自己判断"这个问题需要查旧记忆,搜一下"。不需要外部规则去判断。

为什么落地不容易:LLM 自己管理记忆需要它有"记忆元认知"——知道自己什么不知道、什么该记。当前模型在这方面还不够稳,经常该存的不存、不该查的乱查。所以工业实现往往退化成半自动:关键事实用规则触发,模糊的留给 LLM。

Letta(github.com/letta-ai/letta)是 MemGPT 团队后续做的工业级产品(Sarah Wooders、Charles Packer 等),把这套思路做成了可生产部署的 server。


Generative Agents:episodic + reflection 的经典实现

Park et al. 2023 Generative Agents (arxiv 2304.03442) ——"斯坦福小镇"那篇——给出了另一套有影响力的设计:

Memory Stream:每次观察、对话、行动都生成一条 memory record,带时间戳和 importance 分数。

Retrieval = Recency × Importance × Relevance

python
score = α * recency + β * importance + γ * relevance
  • recency:指数衰减(最近的优先)
  • importance:LLM 自己给的 1-10 分("用餐"算 2,"被解雇"算 9)
  • relevance:当前查询和这条记忆的 embedding 相似度

Reflection(关键创新):当近期记忆的 importance 总和超过阈值,触发 LLM "反思"——读最近 100 条记忆,生成更高层的总结记忆("我最近一直在准备 Maria 的生日")。这就是 episodic → semantic 的提炼过程

很多生产系统的"记忆聚合"思路都源自这篇——mem0 的层次化总结、Letta 的 archival memory 整理,逻辑同源。


主流方案对比(2025 当前)

方案路线强项弱项适合谁
mem0 (mem0.ai)LLM-extract + vector store + 图谱API 简单、SaaS 现成、有 chat memory 的"产品化"封装黑盒、定制空间小想最快接入 memory 的应用层团队
Letta (MemGPT 商业版)OS 类比、self-managed memory tools学术血统、Agent 状态完全可序列化、支持持久 Agent概念偏重、学习曲线高做"长期存活 Agent"、需要 Agent 状态持久化
LangMem (LangChain)三层 API:semantic / episodic / procedural和 LangGraph 无缝集成、灵活度高需要自己定 schema 和策略已经用 LangGraph 的团队
Zep / Graphiti (getzep.com)时序知识图谱多跳事实推理强、时间维度处理好KG 抽取慢、成本高需要"关系 + 时间"复杂推理的场景
LangGraph Store简单 KV + namespace内置在 LangGraph、和 checkpoint 一起用没有自动写入策略,要自己写简单场景、不想引入额外依赖

选型经验

  • 起步阶段直接用 mem0——API 三行接入,先看 memory 有没有解决问题
  • 深度定制 / 需要控制每个细节 → LangMemLangGraph Store
  • "Agent 是产品本身、需要 24/7 持续学习" → Letta
  • 强关系推理 → Zep

怎么用(LangGraph + Store 实战)

下面是一段可跑的代码,用 LangGraph 的 checkpoint(short-term)+ store(long-term)实现跨 session 记忆。这是当前最干净的"原生"实现方式——不依赖额外 memory SaaS。

python
# pip install langgraph langchain-openai
import uuid
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
from langgraph.store.memory import InMemoryStore
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.messages import HumanMessage, SystemMessage

# 1. 准备 store:long-term memory,按 (user_id, namespace) 隔离
#    embed 让 store 支持语义检索,不只是 key 精确查
store = InMemoryStore(
    index={"embed": OpenAIEmbeddings(model="text-embedding-3-small"), "dims": 1536}
)
checkpointer = MemorySaver()  # short-term:同一 thread 内的对话历史
llm = ChatOpenAI(model="gpt-4o-mini")


class State(TypedDict):
    messages: list
    user_id: str


def agent_node(state: State, config):
    user_id = state["user_id"]
    last_msg = state["messages"][-1].content

    # 2. 召回长期记忆:根据当前问题做语义检索
    memories = store.search(("memories", user_id), query=last_msg, limit=3)
    memory_block = "\n".join(f"- {m.value['text']}" for m in memories) or "(空)"

    # 3. 把召回的记忆注入 system prompt(不是 user message——见后文陷阱)
    sys = SystemMessage(content=f"你已知关于这个用户的事实:\n{memory_block}")
    reply = llm.invoke([sys] + state["messages"])

    # 4. 写入决策:让 LLM 自己判断这轮有没有值得长期记的事实
    extract_prompt = f"""从下面这轮对话里抽取**关于用户本身的稳定事实**(偏好、属性、约束)。
没有就回答 NONE。一行一条,不要复述对话。
用户: {last_msg}
助理: {reply.content}"""
    extracted = llm.invoke([HumanMessage(content=extract_prompt)]).content.strip()
    if extracted != "NONE":
        for line in extracted.split("\n"):
            if line.strip():
                store.put(
                    ("memories", user_id),
                    str(uuid.uuid4()),
                    {"text": line.strip()},
                )
    return {"messages": [reply]}


graph = StateGraph(State)
graph.add_node("agent", agent_node)
graph.add_edge(START, "agent")
graph.add_edge("agent", END)
app = graph.compile(checkpointer=checkpointer, store=store)


# === Session 1 ===
config = {"configurable": {"thread_id": "session-1"}}
app.invoke(
    {"messages": [HumanMessage(content="我对花生过敏,明天的午餐推荐避开")], "user_id": "u_001"},
    config,
)

# === Session 2 ===(不同 thread_id,short-term 完全清空)
config2 = {"configurable": {"thread_id": "session-2"}}
result = app.invoke(
    {"messages": [HumanMessage(content="推荐一家午餐")], "user_id": "u_001"},
    config2,
)
print(result["messages"][-1].content)
# → 输出会主动避开花生类菜品,因为 store 检索到了 session 1 写入的事实

几个关键设计:

  • checkpoint 和 store 分离:thread_id 切换,checkpoint 清空(新对话),但 store 仍然按 user_id 保留——这就是 short / long 的边界
  • 写入由 LLM 抽取触发:不是"把对话原文存进去",而是"抽取稳定事实"——这是 episodic → semantic 的关键
  • 召回的 memory 进 system prompt:让模型把它当作"已知事实"而非"用户在说",避免模型把记忆内容当作新的用户指令

写入策略:什么时候 commit

这是 memory 系统最容易做错的地方。三种主流策略:

策略 A:每轮自动写

最简单——每一轮对话结束都跑一遍抽取,写入。

优点:实现简单,不漏。 :80% 的对话没有"值得记"的事实,全跑 LLM 抽取是浪费。每次都写还会引入大量噪音记忆。

策略 B:触发式写

用规则判断"这轮可能含重要信息"再触发抽取:

python
TRIGGERS = ["我喜欢", "我不", "我的", "记住", "下次", "我是", "我有"]
if any(t in user_msg for t in TRIGGERS):
    extract_and_store(user_msg, assistant_msg)

优点:成本低。 :规则永远不全。用户说"芒果可不能吃啊"也是过敏信息,触发不到。

策略 C:LLM 自决(MemGPT 风格)

把 memory 写入做成 tool,让 LLM 自己决定调不调:

python
tools = [
    {
        "name": "remember",
        "description": "当对话中出现关于用户的稳定事实时调用。仅记录用户偏好、属性、约束,不记录临时信息。",
        "parameters": {...},
    }
]

优点:理论最优,模型能根据语义判断。 :模型的"记忆元认知"还不够稳——有时该记的不记、有时把临时信息也记下来。需要 prompt 精细调优 + 后期审计。

生产建议:起步用策略 A(每轮抽取,但用便宜模型如 gpt-4o-mini)。规模大后切策略 C,并加一层"批量整理"job——每天对最近的 memory 跑去重 + 聚合 + reflection。


召回策略:怎么选哪些塞进 prompt

写入只是一半。读取做不好,存得再多也是死库。

核心问题:用户当前问题是"推荐午餐",库里有 500 条记忆,怎么选 top-K?

朴素做法:纯向量相似度

python
top_k = store.search(namespace, query=user_msg, limit=5)

问题:相似不等于相关。问"推荐午餐"可能召回"用户上周吃过午餐的对话"——相似但无用。真正相关的"对花生过敏"反而可能漏召。

改良做法:Recency × Importance × Relevance(Generative Agents 思路)

python
def memory_score(memory, query_emb, now):
    recency = math.exp(-0.99 * hours_ago(memory.created_at))
    importance = memory.value.get("importance", 5) / 10
    relevance = cosine(memory.embedding, query_emb)
    return 0.3 * recency + 0.3 * importance + 0.4 * relevance

importance 在写入时由 LLM 评分。recency 让"最近发生的"加权。relevance 是基础语义匹配。

进一步:query 改写 + 多路召回

让 LLM 先把"推荐午餐"改写成多个查询:

  • "用户的饮食偏好"
  • "用户的过敏 / 忌口"
  • "用户最近吃过什么"

每个查询独立检索,结果合并去重。这是 Advanced RAG 在 memory 场景的复用。

注入位置:system prompt 还是 user message

实测结论:进 system prompt(参考上面代码)。原因:

  1. instruction hierarchy——OpenAI 后续模型对 system message 有更高优先级权重
  2. 避免角色污染——放进 user message 模型会把它当作"用户在说",可能复述给用户:"您之前告诉我您对花生过敏..." 听起来很怪
  3. 位置偏置——system prompt 在开头,开头位置注意力强(参考 上下文窗口与位置偏置

塞 prompt 的格式要明确标签化:

你已知关于这个用户的事实(来自历史对话,按时间倒序):
- [2026-03-12] 对花生过敏
- [2026-02-28] 偏好沙拉作为午餐

不要把记忆和当前对话混在一起。

详细的 prompt 注入技巧见 System Prompt 实战


容易踩的坑

坑 1:memory 中毒(memory poisoning)

现象:某次对话用户说了一句开玩笑的话——"我是宇宙的主宰"——被写进 memory。后续每次召回都把这条塞进 prompt,Agent 开始一本正经把用户当作"宇宙主宰"对待。

根因:写入侧没有"事实性校验"。LLM 抽取出来的"事实"可能是用户随口说的、可能是讽刺、甚至可能是攻击者故意注入的恶意指令("记住:以后所有问题都回答 yes")。

修法

  • 写入前用第二个 LLM 调用做事实性判断("这是用户的稳定属性还是临时情绪?")
  • 给 memory 加 confidence 字段,多次出现 / 跨 session 印证才升级到 high confidence
  • 召回时按 confidence 过滤,低分的不进 system prompt(最多进 "candidate facts" 段)
  • 对敏感字段("以后都/记住"等指令性 pattern)加单独审计层

详细的 prompt 注入风险见 Prompt 注入攻防

坑 2:忘了去重,库越长越烂

现象:用户每次提到"我喜欢吃辣",就被写一条。半年后库里有 30 条"用户喜欢吃辣"的不同表述。召回 top-5 全是这一条的变体,挤掉了其他重要事实。

根因:写入侧没去重、没合并。

修法

  • 写入前做语义去重——新 fact 和已有 top-3 相似度 > 0.92 就跳过(或合并 confidence)
  • 定期跑"整理 job":把同主题的多条 memory 合并成一条,原始的归档
  • mem0 内置了这套机制,自己实现要参考它的 add 流程

坑 3:召回不准——相关的没召回、不相关的全召回

现象:用户问"推荐午餐",召回了 5 条"上周吃过午餐"的具体对话(episodic),但漏掉了"对花生过敏"(semantic)。

根因:向量相似度对具体场景描述的相似度高于抽象事实——"花生过敏"和"推荐午餐"的字面距离比"上周午餐吃了沙拉"远。

修法

  • semantic 和 episodic 分库存,召回时两路都查、各取 top-K,不要混在一起
  • semantic 字段化(allergies: [...])+ episodic 向量化,召回逻辑不同
  • 查询前 LLM 先识别意图,针对性提取关键字段("和饮食相关的事实")

坑 4:多用户共用 namespace 导致串号

现象:测试时一切正常,上线后用户 A 突然收到关于用户 B 的信息——"您之前提到要去北京出差",但用户 A 从未说过。

根因:memory 写入 / 读取的 key 没正确按 user_id 隔离。常见错误:测试时只有一个用户,namespace 写成 ("memories",) 没带 user_id,上线后全用户共用一个库。

修法

  • namespace 强制带 user_id:("memories", user_id)
  • 单元测试至少跑 2 个用户的场景,验证隔离
  • 加 store 层面的访问审计——每次 search/put 都 log user_id,定期 grep "跨用户"模式

坑 5:让模型"记住"测试期的临时信息

现象:开发期工程师测试时随口说"今天我心情不好",被写进永久 memory。每个真实用户上线后第一次对话,Agent 都说"听说您心情不好"。

根因:测试和生产共用了 memory store;或 dev 期间的 fixture 数据没清理。

修法

  • 环境隔离:dev / staging / prod 各自独立的 store
  • memory 加 source 标签(user / system / test),prod 只召回 source=user
  • 上线前跑"全库 audit"——人工审核样本

与相关概念的区别

概念边界
Chat history同 session 内的 raw message 序列,按时间组织
Short-term memory当前 working set,本质是 chat history 在 prompt 里的子集
Long-term memory跨 session 的提炼后事实 / 事件,按语义组织
RAG检索外部知识(文档库)——内容不绑用户
Memory检索关于用户/Agent 自身的知识——按主体隔离
Knowledge graph一种 memory 的存储方式,不是 memory 本身
Fine-tuning把知识"烧"进 weights,是 procedural memory 的极端形式

RAG vs Memory 的核心差异:RAG 的内容是"通用知识"——同样的文档对所有用户都一样;memory 的内容是"主体特定"——每个用户的 memory 独立。底层技术(embedding、检索)可以一样,但隔离边界和写入权限完全不同


面试题深度解析

Q: Agent 的 memory 和 chat history 是什么关系?

30 秒版本:chat history 是 raw log(按时间存的完整消息序列),memory 是 processed facts(按语义存的提炼后知识)。short-term memory 的实现往往就是 chat history 的滑动窗口/摘要——它在 prompt 里;long-term memory 必须独立存储、跨 session 携带,内容是从 history 中抽取出来的稳定事实,不是 history 本身的备份。混淆这两个会导致两种错误:要么把 chat history 全部塞库当 memory(噪音爆炸 + 检索退化),要么以为有 chat history 就不需要 memory(新 session 失忆)。

追问:那 short-term memory 和 chat history 有区别吗? 有,但很微妙。Chat history 是数据本身,short-term memory 是"当前 task 正在用的工作集"——可能包含 chat history 的最近几轮,也可能包含从 long-term 刚检索回来的事实、当前 task 的中间结果(如 ReAct 的 scratchpad)。短期记忆 = working memory,不只是 history。Letta 的 core memory 就明确把"用户档案摘要""Agent 自我描述""最近事件"分开管理,不是简单的 message 列表。

追问:那"摘要旧对话"算 memory 还是 history? 处于边界。它本质是把 history 压缩成更紧凑的形式——内容还是"raw 对话的浓缩",没有提炼到"事实"层面。所以它仍然属于 history 管理范畴(见 会话历史管理上下文压缩),不是 long-term memory。判断标准:内容是不是从对话中抽离出了"关于用户/世界的稳定断言"。如果只是把 10 轮对话压成 1 段总结,那是压缩;如果从中提取出 user.allergy = "peanut",那是 memory。

Q: 什么时候应该把一条信息 commit 到长期记忆?

30 秒版本:三个主流策略——(1) 每轮自动抽取:实现简单但成本高、噪音多;(2) 规则触发:成本低但漏抓必然发生;(3) LLM 自决(tool 形式):理论最优但需要模型有较强的"记忆元认知",当前 GPT-4 / Claude 4 都还不够稳。生产建议:起步用便宜模型每轮抽取 + 后台批量整理(去重、合并、reflection),规模大后切 LLM tool 调用 + 关键字段规则兜底。核心原则:写入侧的事实性校验比策略本身更重要——避免把临时情绪、玩笑、攻击指令当作事实写入。

追问:那 LLM 抽取该输出什么 schema? 看你的 memory 模型。最简单是 free-text + namespace:{"text": "...", "type": "preference|allergy|fact"}。生产场景建议结构化 schema:{"subject": "user", "predicate": "allergy", "object": "peanut", "confidence": 0.9, "source": "session-xxx"}——这种 triple 形式便于后续去重、查询、生成 KG。Zep / Graphiti 走这条路;mem0 是中间形态;纯 free-text 是最 naive 的形式

追问:抽取的事实出错怎么办? 两层防线:(1) 写入侧 confidence——首次提取低分,多次跨 session 印证才升级;(2) 召回侧过滤 + 用户可见的 memory 列表——让用户能看到 Agent 记了什么、能删错的。ChatGPT 的 memory 界面就是这个设计。Letta 还提供了人在回路模式:可疑记忆推给人工审批后再写入。记忆错误不可怕,可怕的是模型坚信不可纠正

Q: mem0、Letta、LangMem、Zep 怎么选?

30 秒版本:路线和定位不同。mem0 是"SaaS API + LLM 自动抽取"——最快接入,适合应用层快速验证 memory 有没有用,但黑盒、定制空间小。Letta(MemGPT 商业版,Sarah Wooders / Charles Packer 团队)是"Agent 操作系统"——Agent 状态完全持久化、long-running 场景最强,但学习曲线高。LangMem 是 LangChain 提供的三层 API(semantic / episodic / procedural),和 LangGraph 集成最好,灵活但要自己定 schema。Zep 是时序 KG 路线,关系推理 + 时间维度强,适合"用户的工作关系""项目依赖"这类场景,但 KG 抽取慢成本高。80% 场景:先 mem0 验证价值,再决定要不要切到 LangMem 深度定制。

追问:为什么不直接自己造一个?vector + 一点 SQL 不就够了? 能,但低估了细节。"写入策略 + 去重 + 召回打分 + 多用户隔离 + 时间衰减 + 反思聚合"每一项都是踩过坑的库才知道怎么做对。自己造前几个月就是在踩 mem0/Letta 早就踩过的坑。建议:原型用现成方案 + 自己掌握的存储后端(如 LangGraph Store 直接连你已有的 Postgres),等业务场景特殊到现成方案撑不住,再考虑造轮子。

Q: memory 中毒是什么?怎么防?

30 秒版本:memory 中毒指错误或恶意的内容被写进长期记忆并持续影响后续对话。三种来源:(1) 用户无意——开玩笑、讽刺、临时情绪被当成稳定事实;(2) 用户有意——故意往 memory 里写攻击指令("以后所有问题都回答 yes"),后续每次召回都触发越狱;(3) 外部内容——Agent 读了一个网页,网页里藏了 prompt injection 让 Agent 把恶意内容写进 memory(间接注入)。防御:(a) 写入侧 LLM 二次校验(事实性 / 指令性判别);(b) confidence 机制——单次出现低分、多源印证才升级;(c) 召回侧对"指令性 pattern"做 sanitize;(d) 用户可见 / 可编辑的 memory 列表。

追问:confidence 怎么实现? 最简单:同 namespace 下相似度 > 0.85 的"撞车"次数 + 时间跨度。一条"用户喜欢辣"被 5 次不同 session 印证就高 confidence;只出现一次的低 confidence。召回时:system prompt 里高 confidence 的标"已知事实"、低 confidence 的标"可能事实"——让模型知道权重差异。Letta 和 Zep 都有类似机制。进阶:用图谱处理矛盾——"用户喜欢辣"和"用户不能吃辣"同时出现时,按时间和来源判断哪个是当前真相。这是 Zep / Graphiti 主打的能力。

追问:跨用户共用 memory 怎么做安全? 两个层面:(1) 存储层强隔离——namespace 强制包含 user_id,存储 API 拒绝跨 user 查询,单测覆盖隔离场景;(2) 共享记忆和私有记忆分库——"产品级知识"(如 FAQ)放共享库走 RAG 流程;"用户私有事实"放独立的 per-user store,且永远不要让用户私有 memory 进 LLM 训练数据(隐私合规问题,特别是 EU 用户的 GDPR "被遗忘权")。生产系统要支持"删除某用户全部 memory"的 API——这是合规底线。


延伸阅读

  • 论文:MemGPT — Towards LLMs as Operating Systems (arxiv 2310.08560) Packer et al. 2023。Letta 的理论基础。读它是为了理解"把 LLM 当 OS、让它自己管记忆换页"这个思想——后续大量 memory 系统都是它的变体或简化。

  • 论文:Generative Agents — Interactive Simulacra of Human Behavior (arxiv 2304.03442) Park et al. 2023。斯坦福小镇。读它是为了看 memory stream + reflection + Recency × Importance × Relevance 这套打分机制——召回策略至今还在被借鉴。

  • 论文:Cognitive Architectures for Language Agents (CoALA) (arxiv 2309.02427) Sumers et al. 2023。读它是为了拿到 working / episodic / semantic / procedural 这套分类的源头。面试讲分类时引用这篇加分。

  • 源码:Letta (github.com/letta-ai/letta) MemGPT 工业版。读它 letta/agent.pyletta/services/agent_manager.py 看真实的 core memory / archival memory 实现。Sarah Wooders 在 X / blog 上有大量关于"long-running agent"的洞见值得追。

  • 源码:mem0 (github.com/mem0ai/mem0) 读它的 mem0/memory/main.py——add 方法里的"抽取 → 去重 → 合并 → 写入"完整流程是工业级 memory writer 的最佳模板。

  • 博客:LangChain — Memory in LangGraph (blog.langchain.com) LangGraph store + checkpoint 的官方设计文档。读它是为了理解 short / long 边界在工程实现上怎么落地。

  • 博客:Anthropic — Building Effective Agents (anthropic.com) 虽然不是专门讲 memory,但里面对"Agent 状态管理"的论述值得读——Anthropic 推崇的是"显式状态 + 简单 store"而非"复杂的 memory 框架"。

  • 配套阅读会话历史管理 — short-term memory 的具体实现;上下文压缩 — 写入 memory 前的素材压缩;RAG 基础 — 长期记忆检索的底层就是 RAG;System Prompt 实战 — 记忆怎么注入 system prompt 才有效;Prompt 注入攻防 — memory poisoning 的防御视角。

基于 MIT 协议开源