痛点:为什么你的AI Agent总是"失忆"?

用过 AI Agent 的同学一定遇到过这种情况:

上一轮对话你告诉它「我的项目叫 XXX」,下一轮它就忘了。

你让它先查文件、再写代码、再测试,它全做完了,但最后问你「测试结果是什么」时,它一脸茫然——因为它不记得自己刚才做了什么。

这不是 Bug,这是记忆缺失

大模型(LLM)本身是无状态的(Stateless):每一次 API 调用都是独立的,模型不会「记住」之前发生的事。

那么问题来了——如何让 AI Agent 拥有记忆?

今天我们从原理到实现,系统性地拆解 LLM Agent 的记忆系统设计。


一、记忆系统的本质:让模型「看到」历史

先厘清一个核心概念:所谓 AI Agent 的「记忆」,本质上就是把历史上下文塞进下一轮 Prompt 里

模型自己没有记忆,我们要做的是:

graph LR
    A["用户输入"] --> B["检索记忆"]
    B --> C["拼接 Context"]
    C --> D["LLM 推理"]
    D --> E["模型输出"]
    E --> F["写入新记忆"]
    F --> B

整个流程闭环:检索 → 拼接 → 推理 → 写入 → 下一轮检索

所以记忆系统的核心模块只有三个:

  1. 记忆存储(Memory Storage)—— 记忆怎么存?
  2. 记忆检索(Memory Retrieval)—— 需要用时怎么找?
  3. 记忆写入(Memory Write)—— 新信息怎么记录?

二、记忆存储:三种主流方案

2.1 方案一:全量上下文(Full Context)

最暴力的方案——把历史对话全部塞进 Context Window。

messages = [
    {"role": "system", "content": "你是助手"},
    {"role": "user", "content": "第一轮对话"},
    {"role": "assistant", "content": "第一轮回复"},
    {"role": "user", "content": "第二轮对话"},
    # ... 所有历史
]
response = chat(messages)

优点:实现简单,零额外逻辑 缺点:Context 长度有限(4K~128K),对话一长就爆;每次调用都重复发送历史,成本线性增长

适用场景:短期单轮对话、演示 Demo

2.2 方案二:摘要式记忆(Summarized Memory)

对话过程中定期对历史做摘要,保留「要点」丢弃「细节」:

graph TD
    A["原始对话日志"] --> B{"是否达到摘要阈值?"}
    B -->|否| Z[继续累积]
    B -->|是| C["LLM 摘要生成"]
    C --> D["摘要存储"]
    D --> E["原始日志可删除"]
    E --> F["用于下一轮检索"]
def summarize_if_needed(conversation_history: list, threshold: int = 10):
    if len(conversation_history) >= threshold:
        summary_prompt = f"""请摘要以下对话的核心要点,保留关键信息:
        {conversation_history}"""
        summary = llm.chat([{"role": "user", "content": summary_prompt}])
        return {"summary": summary, "original_count": len(conversation_history)}
    return None

优点:控制 Context 长度,适合长对话 缺点:摘要损失信息;摘要本身也消耗 Token;摘要时机难以把握

适用场景:长程对话(客服、长文档分析)

2.3 方案三:向量数据库 + RAG(推荐方案)

这是目前最主流的 Agent 记忆架构——用向量数据库做语义检索:

graph TD
    A["新对话"] --> B["Embedding 模型"]
    B --> C["Query 向量"]
    C --> D["向量数据库检索"]
    D --> E["Top-K 相关记忆"]
    E --> F["拼入 Context"]
    F --> G["LLM 推理"]

    H["新经验"] --> I["Embedding"]
    I --> J["写入向量数据库"]

核心流程

  1. 写入时:对话内容 → Embedding → 向量存储
  2. 读取时:当前问题 → Embedding → 相似度搜索 → Top-K 召回 → 拼入 Prompt
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings

# 写入记忆
def write_memory(text: str, vectorstore: Chroma):
    vectorstore.add_texts([text])

# 检索记忆
def retrieve_memory(query: str, vectorstore: Chroma, k: int = 5) -> list[str]:
    docs = vectorstore.similarity_search(query, k=k)
    return [doc.page_content for doc in docs]

优点:语义检索准确、可扩展、支持多种向量数据库 缺点:引入额外组件(Milvus/Chroma/Pinecone);检索质量依赖 Embedding 模型

适用场景:生产级 Agent、需要长期记忆的场景


三、记忆分层架构:MOS Memory Architecture

一个完善的 Agent 记忆系统通常采用三层记忆结构

graph TD
    subgraph 注意力记忆 "Attention Memory(最近对话)"
        A1["当前 Session\n最近 5-10 轮"] 
    end
    
    subgraph 语义记忆 "Semantic Memory(核心知识)"
        B1["向量数据库\n长期存储"]
        B2["RAG 检索"]
    end
    
    subgraph 程序记忆 "Procedural Memory(系统配置)"
        C1["AGENTS.md"]
        C2["SKILL.md"]
        C3["工具定义"]
    end
    
    A1 --> D["Context 组装"]
    B2 --> D
    C1 --> D
    C2 --> D
    C3 --> D
    D --> E["LLM 推理"]

3.1 注意力记忆(Attention Memory)

定义:当前对话窗口内、模型直接「能看到」的内容

通常保留最近 N 轮对话最近 M Token,实现最简单:

class AttentionMemory:
    def __init__(self, max_turns: int = 10):
        self.messages: list[dict] = []
        self.max_turns = max_turns
    
    def add(self, role: str, content: str):
        self.messages.append({"role": role, "content": content})
        # 超过阈值,删除最早的回合
        if len(self.messages) > self.max_turns * 2:
            self.messages = self.messages[2:]
    
    def get_context(self) -> list[dict]:
        return self.messages

3.2 语义记忆(Semantic Memory)

定义:长期存储的核心知识,通过向量检索召回

通常按记忆类型分类存储:

记忆类型说明召回场景
user_profile用户基本信息、偏好个性化回复
project_context当前项目/任务背景技术决策、代码风格
conversation_summary对话摘要长程任务状态恢复
tool_output工具执行结果缓存避免重复调用
class SemanticMemory:
    def __init__(self, vectorstore: Chroma):
        self.store = vectorstore
        self.metadata_filter = {}  # 可按 type 过滤
    
    def write(self, text: str, memory_type: str, session_id: str):
        self.store.add_texts(
            texts=[text],
            metadatas=[{"type": memory_type, "session_id": session_id}]
        )
    
    def retrieve(self, query: str, memory_types: list[str] | None = None, k: int = 5) -> list[dict]:
        return self.store.similarity_search_with_score(
            query, k=k, filter={"type": {"$in": memory_types}} if memory_types else None
        )

3.3 程序记忆(Procedural Memory)

定义:Agent 的「本能」——系统Prompt、工具定义、工作流配置

这部分不需要检索,直接写入 System Prompt:

SYSTEM_PROMPT = """你是一个专业的 AI 编程助手。

## 系统能力
- 可以使用 Read/Edit/Write 工具操作本地文件
- 可以使用 Exec 工具执行 Shell 命令
- 可以使用 Browser 工具控制浏览器

## 工作流程
当用户提出需求时,你应该:
1. 理解需求并拆解任务步骤
2. 按步骤执行,及时反馈进度
3. 完成后总结做了什么、结果是什么

## 约束
- 不要执行删除、格式化等危险操作
- 遇到错误先分析原因,再尝试修复
"""

四、记忆检索策略:不是召回越多越好

4.1 基础相似度检索的问题

naive 的向量检索有两个典型问题:

问题1:召回「多」但不「准」

比如问「上次那个Python项目的测试怎么跑」,检索可能召回一堆关于「Python」和「测试」的内容,但具体是哪个项目、哪个测试文件,模型还是分不清。

问题2:召回内容「打架」

不同记忆片段对同一件事的说法不一致,模型不知道该信哪个。

4.2 解决方案:混合检索 + 重排序

graph TD
    A["Query"] --> B["关键词检索 BM25"]
    A --> C["向量语义检索"]
    B --> D["融合结果"]
    C --> D
    D --> E["重排序模型 rerank"]
    E --> F["Top-K 精准输出"]
from langchain.retrievers import EnsembleRetriever

# 混合检索
retriever = EnsembleRetriever(
    retrievers=[
        bm25_retriever,  # 关键词
        vectorstore.as_retriever(search_kwargs={"k": 10})  # 向量
    ],
    weights=[0.3, 0.7]  # 权重分配
)
results = retriever.get_relevant_documents(query)

4.3 Context 压缩:让每条记忆更「精」

检索回来的记忆可能很长,直接塞 Context 会浪费 Token。用「Context 压缩器」精简每条记忆:

from langchain.compressors import LLMChainExtractor

compressor = LLMChainExtractor.from_llm(llm)

from langchain.retrievers import ContextualCompressionRetriever
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=retriever
)
# 返回的是压缩后的精简片段
compressed_docs = compression_retriever.get_relevant_documents(query)

五、记忆写入时机:什么时候该「记」?

5.1 被动写入 vs 主动写入

类型触发方式示例
被动写入对话轮次达到阈值每 10 轮对话后写入摘要
被动写入Session 结束时对话关闭前统一摘要
主动写入关键操作完成后「已创建文件 /src/main.py
主动写入用户明确告知「记住,我的名字叫张三」
主动写入检测到重要实体「识别到项目名 code-minions

5.2 主动写入的触发规则

一个实用的规则引擎:

class MemoryWritePolicy:
    def should_write(self, message: dict, context: dict) -> bool:
        content = message["content"]
        
        # 规则1:用户明确声明
        if re.match(r"记住,.*", content):
            return True
        
        # 规则2:执行了外部操作
        if message.get("role") == "system" and "tool_output" in content:
            return True
        
        # 规则3:识别到关键实体(项目名、人名、技术栈)
        entities = extract_entities(content)  # 用 NER 或规则
        if len(entities) > 2:
            return True
        
        # 规则4:对话涉及长期项目上下文
        if context.get("task_type") == "project_work":
            return True
        
        return False

六、实战:实现一个简易记忆系统

完整代码示例:

from langchain.llms import OpenAI
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.memory import ConversationBufferMemory
from langchain.agents import AgentExecutor, ZeroShotAgent
from langchain.prompts import PromptTemplate

class SimpleAgentMemory:
    def __init__(self, llm, vectorstore_path: str = "./memory_db"):
        self.llm = llm
        self.embeddings = OpenAIEmbeddings()
        self.vectorstore = Chroma(
            persist_directory=vectorstore_path,
            embedding_function=self.embeddings
        )
        # 注意力记忆:最近 10 轮
        self.attention = ConversationBufferMemory(memory_key="chat_history", k=10)
    
    def retrieve(self, query: str, k: int = 5) -> str:
        """从向量数据库检索相关记忆"""
        docs = self.vectorstore.similarity_search(query, k=k)
        return "\n".join([f"- {d.page_content}" for d in docs])
    
    def write(self, text: str, memory_type: str = "general"):
        """写入记忆到向量数据库"""
        self.vectorstore.add_texts(
            texts=[text],
            metadatas=[{"type": memory_type}]
        )
        self.vectorstore.persist()
    
    def chat(self, user_input: str) -> str:
        # Step 1: 检索相关记忆
        relevant_memory = self.retrieve(user_input)
        
        # Step 2: 构建 Prompt(注意力记忆 + 语义记忆)
        attention_context = self.attention.load_memory_variables({})["chat_history"]
        
        prompt = f"""你是 AI 助手。以下是相关记忆:
{relevant_memory}

当前对话历史:
{attention_context}

用户:{user_input}
助手:"""
        
        # Step 3: LLM 推理
        response = self.llm(prompt)
        
        # Step 4: 更新注意力记忆
        self.attention.save_context(
            {"input": user_input},
            {"output": response}
        )
        
        # Step 5: 关键内容写入语义记忆
        if self._is_important(user_input) or self._is_important(response):
            self.write(f"用户问:{user_input}\n助手答:{response}")
        
        return response
    
    def _is_important(self, text: str) -> bool:
        # 简单规则:包含项目名、技术决策、用户偏好等
        keywords = ["项目", "记住", "偏好", "配置", "名字", "我的"]
        return any(kw in text for kw in keywords)

# 使用
memory = SimpleAgentMemory(llm=OpenAI(temperature=0))
response = memory.chat("我叫张三,在做一个叫code-minions的项目")
print(response)

七、生产级注意事项

7.1 记忆存储的分桶策略

不要把所有记忆存在一个 collection 里,按会话主题分桶:

# 索引结构设计
collection_name = f"{user_id}_{topic}_{session_id}"
# 例如:user123_code-minions_session-001

7.2 记忆的 TTL(过期策略)

记忆类型TTL说明
用户偏好30天长期有效,定期确认
项目上下文Session级项目结束后可删除
对话摘要7天定期清理过时的摘要
工具输出24小时及时清理陈旧状态

7.3 记忆一致性

多 Agent 共享记忆时,要处理并发写入和版本冲突:

# 乐观锁写入
def write_with_version(vectorstore, text, expected_version):
    current = vectorstore.get_version()
    if current != expected_version:
        raise MemoryConflictError("记忆已被其他 Agent 修改")
    vectorstore.add_texts([text])
    vectorstore.set_version(current + 1)

八、总结

LLM Agent 的记忆系统设计,本质上是三个问题:

  1. 存什么 → 分层记忆(注意力/语义/程序)
  2. 怎么存 → 向量数据库 + 元数据分类
  3. 怎么用 → 混合检索 + 重排序 + Context 压缩

一个好的记忆系统,能让 Agent 从「每轮都失忆的金鱼」,变成「理解上下文的专业助手」。

核心实践经验:

  • 向量检索是基础,但纯相似度召回不够用,必须配合混合检索和重排序
  • 记忆写入要克制,不是所有对话都需要记住,避免向量数据库膨胀
  • 分层设计是关键,注意力记忆管短期,向量数据库管长期,System Prompt 管本能
  • 遗忘也是一种智慧,定期清理过时记忆,让 Agent 保持「清醒」

延伸阅读


原文引用:Lilian Weng - LLM Agent Memory Architecture
欢迎关注收藏我,获取更多硬核技术干货!