Agent 三层记忆机制:短期记忆、情景记忆与语义记忆

目录

三层记忆模型

LLM 每次调用都是无状态的,如果消息历史没有落库,服务器一停,所有对话历史都会消失。要让 Agent 跨会话"记住"用户,光靠上下文窗口不够,需要一套记忆系统。

Agent 的记忆可以分成三层,每一层解决不同的问题:

在这里插入图片描述

层级 存什么 生命周期 访问方式
短期记忆 当前会话的完整消息 会话结束即销毁 直接读 messages 数组
情景记忆 历史会话的对话片段 持久化,跨会话保留 RAG 检索
语义记忆 从对话中提炼的事实 持久化,持续更新 直接读取用户画像

第一层记忆:短期记忆

短期记忆就是我们熟悉的上下文窗口,Agent 当前会话中能看到的所有消息。这部分在之前的博客里已经详细讨论过,这里只做一个简单的回顾。

# 短期记忆:就是这个 messages 数组
messages = [{"role": "user", "content": "帮我排查连接池问题"}]

while True:
    response = call_llm(messages)
    messages.append(response)

    if not response.has_tool_use():
        break

    result = execute_tool(response)
    messages.append(result)

短期记忆的特点是无成本、高保真。不需要额外的存储和检索,直接读就行。但它有两个限制:

  1. 窗口有上限。 对话太长时,早期消息会被压缩或丢弃。
  2. 会话结束就没了。 下次打开新会话,一切从零开始。

所以短期记忆只够处理"当前正在做的事",跨会话的持久化需要另外两层来支撑。

第二层记忆:情景记忆

情景记忆解决的是"以前聊过什么"的问题。它会把历史会话的对话片段存下来,下次需要时通过 RAG 检索回来。

存储

每次会话结束后,Agent 把对话内容做一个总结,存到向量数据库里:

import chromadb

client = chromadb.PersistentClient(path="./agent_memory")
collection = client.get_or_create_collection("episodic_memory")

def save_episodic_memory(session_id: str, messages: list):
    """会话结束后,把对话摘要存入向量数据库"""
    # 用 LLM 生成对话摘要
    summary = summarize_conversation(messages)

    # 计算摘要的 embedding,存入向量数据库
    collection.add(
        documents=[summary],
        ids=[session_id],
        metadatas=[{
            "timestamp": get_current_time(),
            "topic": extract_topic(messages),
        }]
    )

注意这里存的是摘要,不是原始对话。原因很简单:原始对话可能有几十上百条消息,全部存下来既浪费空间,检索时噪声也大。摘要保留了关键信息,体积小得多。

检索

新会话开始时,Agent 根据当前话题去向量数据库里检索相关的历史会话:

def retrieve_episodic_memory(query: str, top_k: int = 3) -> list[str]:
    """根据当前话题,检索相关的历史会话"""
    results = collection.query(
        query_texts=[query],
        n_results=top_k,
    )
    return results["documents"]

检索回来的历史摘要会被注入到当前会话的上下文中。这样 Agent 就知道"上周三你问过某某问题了"。

写入时机

情景记忆不是每次对话都值得存。写入情景记忆的时机通常是:

  • 会话结束时:自动保存本次对话的摘要
  • 用户主动要求时:“记住我们刚才讨论的方案”
  • 对话中出现关键决策时:Agent 判断当前讨论有长期价值,主动存储

一个实际的写入判断:

def should_save(messages: list) -> bool:
    """判断当前对话是否值得存入情景记忆"""
    # 对话太短,没什么信息量
    if len(messages) < 4:
        return False

    # 用 LLM 判断:这段对话有没有长期价值?
    judgment = llm_judge(
        prompt="这段对话是否包含值得长期记住的信息?"
               "比如技术方案、架构决策、问题排查过程。"
               "回答 yes 或 no。",
        context=messages
    )
    return judgment == "yes"

检索的挑战

向量检索当情景记忆积累到几百条之后,会出现几个实际问题。

语义漂移。 用户问"怎么优化 SQL",向量检索可能返回一段关于"SQL 注入防护"的历史对话。两者都和 SQL 相关,但完全不是一回事。解决办法是给每条记忆打上结构化标签(时间、话题、涉及的技术),检索时同时用语义相似度和标签过滤。

时效性衰减。 三个月前讨论的方案可能已经被推翻了。可以给每条记忆加一个权重,越近期的权重越高,检索时按相似度和时间加权排序。

存储膨胀。 每天都用 Agent,几个月下来可能有上千条记忆。全量检索既慢又不准。实际做法通常是设定一个上限,比如只保留最近 200 条,或者按重要性评分淘汰低分条目。

第三层记忆:语义记忆

语义记忆解决的是"关于这个用户我知道什么"的问题。它不存具体的对话内容,而是从对话中提炼出结构化的事实和偏好。

和情景记忆的区别

情景记忆 语义记忆
存什么 完整对话片段(摘要形式) 提炼后的事实/偏好
形态 原始记录,近似原文 结构化条目,高度压缩
举例 “2026年7月1日用户问过连接池配置” “用户使用 HikariCP + SpringBoot”
大,每次会话都可能存 小,只有值得沉淀的才存
检索方式 按话题相似度 按实体、关系、类别

可以把情景记忆理解成你的日记本,记录了每天发生的事;语义记忆是你脑子里对一个人的印象——他喜欢用什么框架、技术水平怎么样、沟通风格是什么。你不会把每次和他聊天的记录都背下来,但你会记住关于他的关键信息。

实现

语义记忆的核心是一个用户画像,通常用 JSON 或 KV 结构存储:

import json

USER_PROFILE_PATH = "./user_profile.json"

def load_user_profile() -> dict:
    """加载用户画像"""
    try:
        with open(USER_PROFILE_PATH, "r", encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError:
        return {"facts": [], "preferences": {}, "tech_stack": []}

def save_user_profile(profile: dict):
    """保存用户画像"""
    with open(USER_PROFILE_PATH, "w", encoding="utf-8") as f:
        json.dump(profile, f, ensure_ascii=False, indent=2)

画像的结构可以是这样:

{
  "facts": [
    "使用 SpringBoot 2.7 + Java 17",
    "数据库用 MySQL 8.0",
    "连接池用 HikariCP"
  ],
  "preferences": {
    "code_style": "简洁,不喜欢过多抽象",
    "explanation_depth": "中等,喜欢类比但不要啰嗦"
  },
  "tech_stack": ["SpringBoot", "MySQL", "Redis", "HikariCP"]
}

提炼过程

语义记忆需要一个提炼过程。每轮对话结束后,Agent 检查有没有新的事实值得加入画像:

def extract_facts(messages: list, current_profile: dict) -> dict:
    """从对话中提取新的事实,更新用户画像"""
    prompt = f"""
    当前用户画像:
    {json.dumps(current_profile, ensure_ascii=False, indent=2)}

    最近的对话:
    {format_messages(messages)}

    请从对话中提取新的事实或偏好,更新用户画像。
    只输出新增或修改的部分,不需要重复已有的信息。
    如果没有新的发现,返回空对象 {{}}。
    """
    new_facts = call_llm(prompt)
    return merge_profile(current_profile, new_facts)

注意两个细节:

  1. 对比已有画像再提取。 把当前画像传给 LLM,让它只提取新增的部分,避免重复存储。
  2. 提炼频率低于情景记忆。 不是每轮对话都要提炼,可能几轮对话才值得更新一次画像。

信息冲突

语义记忆有一个绕不开的问题:新信息和旧信息冲突了怎么办。

比如用户上个月说"我用 MySQL",这个月说"我们迁到 PostgreSQL 了"。画像里如果同时存在两条,Agent 就不知道该信哪个。

处理方式取决于信息的类型:

  • 可覆盖型信息(技术栈、配置、版本号):新信息直接覆盖旧信息。"数据库从 MySQL 迁到 PostgreSQL"意味着 PostgreSQL 是当前状态,MySQL 变成历史记录。
  • 累积型信息(技能水平、项目经验):新信息追加到旧信息后面。"会 Java"和"最近在学 Go"不冲突,两条都留着。

实现上,可以在提取事实时让 LLM 判断每条信息属于哪种类型,然后决定是覆盖还是追加。

三层怎么协作

这三层拆开来看都不复杂,放在一起怎么配合才是重点。来看一个完整的流程:

在这里插入图片描述

整个过程对用户是透明的。他只看到 Agent "记住了"昨天的事,背后的检索和组装他感知不到。

数据流

会话结束
    │
    ├─→ 摘要 → 存入情景记忆(向量数据库)
    │
    └─→ 提炼 → 更新语义记忆(用户画像)

新会话开始
    │
    ├─→ 读取语义记忆 → 注入 system prompt
    │
    └─→ 检索情景记忆 → 注入 system prompt
         │
         ▼
    Agent 开始推理(同时拥有三层记忆)

小结

Agent 记忆机制的核心是三层分工:短期记忆管当前会话,情景记忆管历史回溯,语义记忆管用户画像。三层各司其职,通过 RAG 检索和 system prompt 注入把信息送到 Agent 眼前。这个模型可以让Agent越用越懂你,模型本身其实没有变化,但它的记忆层在持续积累关于你的信息。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐