Skip to content

5.3 记忆系统 Memory:短期记忆与长期记忆

在上一节中,我们详细讨论了 Agent 的规划(Planning)能力——如何将一个复杂目标分解为可执行的子任务,并通过 ReAct、Plan-and-Execute 等策略逐步推进。规划解决了"做什么、按什么顺序做"的问题,但一个仅有规划能力的 Agent 仍然存在一个致命缺陷:它没有记忆。每次对话都从零开始,无法积累经验,也无法根据用户的历史偏好做出个性化响应。本节我们将聚焦 Agent 架构中的第三大核心组件——记忆系统(Memory),探讨如何让 Agent 像人脑一样,既能在当前对话中保持上下文连贯,又能在跨会话之间积累和检索长期知识。

5.3.1 从人脑记忆说起

理解 Agent 的记忆系统,最直观的方式是类比人类大脑的记忆机制。

人类的记忆分为短期记忆长期记忆两大类。短期记忆就像大脑的"便签纸"——你在交谈时能记住对方刚说过的几句话,在心算时能临时记住中间数字,但容量有限,一旦注意力转移或信息过载,这些内容很快就会被遗忘。心理学研究表明,人类的短期记忆容量大约只有 7±2 个信息单元(即著名的"米勒定律")。

长期记忆则截然不同。它更像大脑的"图书馆"——存储着你多年前学过的知识、经历过的往事、养成的习惯。当你需要某条信息时,大脑不是逐条翻阅所有记忆,而是通过联想快速定位到相关内容。比如有人提到"海边度假",你脑海中会自动浮现出上次去三亚的画面、吃过的海鲜、晒伤的经历——这些信息并非按时间线性排列,而是通过语义关联被检索出来的。

Agent 的记忆系统正是对这一机制的工程化模拟:

维度人脑记忆Agent 记忆
短期记忆工作记忆,容量约 7±2 个单元上下文窗口,容量 4K~200K tokens
长期记忆海马体编码存储,联想检索向量数据库存储,语义相似度检索
遗忘机制时间衰减、干扰遗忘LRU 淘汰、重要性评分衰减
记忆类型情景/语义/程序性记忆交互日志/知识库/工作流模板

5.3.2 记忆系统架构总览

一个完整的 Agent 记忆系统通常包含三层结构,它们各司其职、协同工作:

┌─────────────────────────────────────────────────────────┐
│                   Agent 记忆系统架构                      │
│                                                         │
│  ┌──────────────────────────────────────────────────┐   │
│  │              短期记忆(Short-Term)                │   │
│  │  · 当前对话上下文                                  │   │
│  │  · 最近 N 轮交互历史                               │   │
│  │  · 容量:受上下文窗口限制(4K~200K tokens)         │   │
│  │  · 生命周期:单次会话                              │   │
│  └──────────────────────┬───────────────────────────┘   │
│                         │ 摘要/压缩                     │
│                         ▼                               │
│  ┌──────────────────────────────────────────────────┐   │
│  │              长期记忆(Long-Term)                  │   │
│  │  · 向量数据库(Chroma / Pinecone / Weaviate)      │   │
│  │  · 知识图谱(Neo4j / Graphiti)                    │   │
│  │  · 关系数据库(PostgreSQL + pgvector)             │   │
│  │  · 生命周期:跨会话持久化                           │   │
│  └──────────────────────┬───────────────────────────┘   │
│                         │                               │
│                         ▼                               │
│  ┌──────────────────────────────────────────────────┐   │
│  │              工作记忆(Working)                    │   │
│  │  · 当前任务状态                                    │   │
│  │  · 中间计算结果                                    │   │
│  │  · 工具调用结果缓存                                │   │
│  └──────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘
  • 短期记忆对应 LLM 的上下文窗口,负责维持当前对话的连贯性。它的生命周期仅限于一次会话,会话结束后即被清空。
  • 长期记忆通过向量数据库等外部存储实现,负责跨会话的知识积累。它是 Agent "记住"用户偏好、历史交互和领域知识的核心载体。
  • 工作记忆是一个中间层,记录当前任务的执行状态、中间计算结果和工具调用的返回值,类似于人脑在解一道数学题时临时记住的中间步骤。

下面我们逐一展开讨论。

5.3.3 短期记忆与上下文管理

短期记忆直接对应 LLM 的上下文窗口(Context Window)。2024—2025 年,主流大语言模型的上下文窗口已大幅扩展:

模型上下文窗口
GPT-4o128K tokens
Claude 3.5 Sonnet200K tokens
Gemini 1.5 Pro1M tokens
DeepSeek-V3128K tokens

乍看之下,200K 甚至 1M tokens 的窗口似乎已经"足够大"了,但上下文窗口并非越大越好。长上下文会带来三个实际挑战:

  1. 成本线性增长:输入 token 数量与 API 调用费用直接成正比。一个每轮都携带 100K tokens 历史的 Agent,其成本是只携带 4K tokens 的 25 倍。
  2. 注意力稀释:研究表明,模型对上下文中间部分的注意力会出现明显衰减——这就是学术界所说的"Lost in the Middle"问题。关键信息放在中间,模型反而容易"看漏"。
  3. 推理延迟增加:更长的上下文意味着更多的计算量,首 token 响应时间(TTFT)随之上升,影响用户体验。

因此,即使模型支持超长上下文,我们也需要主动管理短期记忆,而非简单地把所有历史都塞进窗口。

上下文管理策略

常见的短期记忆管理策略有三种:滑动窗口摘要压缩混合策略

python
# 导入 LangChain 的记忆管理组件
# ConversationBufferWindowMemory:滑动窗口记忆,只保留最近 K 轮对话
# ConversationSummaryMemory:摘要记忆,将历史对话压缩为摘要
from langchain.memory import (
    ConversationBufferWindowMemory,   # 滑动窗口记忆类
    ConversationSummaryMemory,        # 摘要记忆类
    ConversationSummaryBufferMemory,   # 混合策略记忆类
)
from langchain_openai import ChatOpenAI  # OpenAI 模型封装

# 初始化 LLM,temperature=0 表示确定性输出,适合记忆管理场景
llm = ChatOpenAI(model="gpt-4o", temperature=0)

# ── 策略 1:滑动窗口 ──
# 只保留最近 K 轮对话,更早的历史被直接丢弃
# 优点:实现简单,Token 消耗可控
# 缺点:丢失了早期对话中的重要信息
window_memory = ConversationBufferWindowMemory(
    k=5,                    # 只保留最近 5 轮对话(一轮 = 一次用户输入 + 一次助手回复)
    return_messages=True,   # 以 Message 对象列表形式返回,而非拼接的字符串
)

# ── 策略 2:摘要压缩 ──
# 调用 LLM 将整个对话历史压缩为一段摘要
# 优点:保留了长期信息的关键要点
# 缺点:每次压缩都需要额外的 LLM 调用,增加延迟和成本
summary_memory = ConversationSummaryMemory(
    llm=llm,                      # 指定用于生成摘要的 LLM
    max_token_limit=500,          # 摘要的最大 token 数,超过则触发再次压缩
    return_messages=True,         # 以 Message 对象列表形式返回
)

# ── 策略 3:混合策略(近期对话 + 早期摘要) ──
# 综合了滑动窗口和摘要压缩的优点
# 最近几轮对话保留原文,更早的历史被压缩为摘要
# 优点:既保留了近期细节,又不会完全丢失长期上下文
hybrid_memory = ConversationSummaryBufferMemory(
    llm=llm,                      # 指定用于压缩摘要的 LLM
    max_token_limit=2000,         # 总 token 预算,超过时触发早期对话的摘要压缩
    return_messages=True,         # 以 Message 对象列表形式返回
)

Token 预算控制是短期记忆管理的核心思想。在实际工程中,我们需要像管理财务预算一样,为上下文窗口中的不同信息分配 token 配额。一个典型的预算分配方案如下:

总预算:4000 tokens
├── 系统提示词:      500 tokens   ← 定义 Agent 角色和行为规范
├── 最近 3 轮对话:  1500 tokens   ← 滑动窗口保留的近期交互
├── 历史摘要:       1000 tokens   ← 更早对话的压缩摘要
├── 检索到的相关记忆: 800 tokens   ← 从长期记忆中检索出的信息
└── 预留响应空间:    200 tokens   ← 为模型输出预留的余量

这种预算分配方式确保了上下文窗口中既有近期对话的细节,又有历史信息的概要,还有从长期记忆中检索出的相关知识,同时为模型留出足够的生成空间。

5.3.4 长期记忆:向量数据库与语义检索

长期记忆是 Agent 记忆系统中最具技术深度的部分。它使 Agent 能够跨会话记住信息——今天的对话结束后,明天再开启新对话时,Agent 依然记得你上次说过的话、你的偏好和历史交互。

为什么需要向量数据库?

回到人脑的类比。人类回忆往事时,并不是从头到尾逐条扫描所有记忆,而是通过联想来检索——你听到"海边的风",就能想起上次在沙滩上的经历。这种"联想"的本质是语义相似度:两个内容在含义上越接近,越容易被关联起来。

向量数据库正是对这一机制的工程实现。它的核心思路是:

  1. 写入阶段:用 Embedding 模型将文本转换为一个高维向量(例如 1536 维的浮点数数组),这个向量捕获了文本的语义信息。语义相近的文本,其向量在空间中的距离也相近。然后将向量存入向量数据库。
  2. 检索阶段:将用户的查询同样转换为向量,然后在数据库中搜索与之距离最近的若干向量,返回对应的原始文本。
写入流程:
用户信息 ──▶ Embedding 模型 ──▶ 高维向量 ──▶ 向量数据库持久化存储

检索流程:
用户查询 ──▶ Embedding 模型 ──▶ 查询向量 ──▶ 相似度搜索 ──▶ 返回最相关的 N 条记忆

这种检索方式的优势在于语义匹配而非关键词匹配。例如,用户存储了"我喜欢用 Python 做数据分析",之后查询"用户偏好什么编程语言?"——虽然两者的关键词并不完全一致,但语义高度相关,向量检索能够准确命中。

使用 Chroma 实现长期记忆

下面通过一个完整的代码示例,展示如何使用 Chroma 向量数据库构建 Agent 的长期记忆系统:

python
# ── 导入所需组件 ──
# VectorStoreRetrieverMemory:LangChain 提供的向量检索记忆封装
# OpenAIEmbeddings:OpenAI 的文本嵌入模型,将文本转为向量
# Chroma:轻量级向量数据库,支持本地持久化
# Document:LangChain 的文档对象,包含文本内容和元数据
from langchain.memory import VectorStoreRetrieverMemory
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_core.documents import Document

# 1. 初始化 Embedding 模型
# text-embedding-3-small 是 OpenAI 的高性价比嵌入模型
# 它将任意文本映射为 1536 维的浮点数向量,语义相近的文本向量距离更近
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# 2. 创建向量数据库实例
# collection_name 类似关系数据库中的"表名",用于区分不同类型的记忆
# embedding_function 指定用哪个模型将文本转为向量
# persist_directory 指定数据持久化路径,程序重启后记忆不会丢失
vectorstore = Chroma(
    collection_name="agent_long_term_memory",   # 记忆集合名称
    embedding_function=embeddings,              # 嵌入函数
    persist_directory="./agent_memory_db"       # 持久化存储目录
)

# 3. 创建可检索器
# 将向量数据库包装为检索器接口,方便与 LangChain 链集成
# search_kwargs 中的 k=5 表示每次检索返回最相关的 5 条记忆
retriever = vectorstore.as_retriever(
    search_kwargs={"k": 5}   # 返回 Top-5 最相关的记忆条目
)

# 4. 创建向量检索记忆对象
# memory_key 是在 Prompt 模板中引用记忆的变量名
# return_docs=True 表示返回完整的 Document 对象(含元数据),而非仅文本
memory = VectorStoreRetrieverMemory(
    retriever=retriever,          # 上一步创建的检索器
    memory_key="relevant_history",# Prompt 中的变量名
    return_docs=True,             # 返回完整文档对象
)

# 5. 定义保存记忆的函数
# 当对话中出现值得长期记住的信息时,调用此函数写入向量数据库
def save_memory(content: str, metadata: dict = None):
    """
    将重要信息保存到长期记忆。

    参数:
        content: 要记忆的文本内容
        metadata: 可选的元数据,如类别、时间戳、来源等
    """
    # 创建 Document 对象,包含文本内容和元数据
    doc = Document(
        page_content=content,           # 记忆的文本内容
        metadata=metadata or {}        # 附加元数据,默认为空字典
    )
    # 将文档添加到向量数据库
    # Chroma 会自动调用 embedding_function 将文本转为向量并存储
    vectorstore.add_documents([doc])
    print(f"已保存记忆: {content[:50]}...")  # 打印保存确认信息

# 6. 定义检索记忆的函数
# 在生成回复前,调用此函数从向量数据库中检索相关历史信息
def retrieve_memories(query: str, k: int = 3):
    """
    检索与查询语义相关的历史记忆。

    参数:
        query: 查询文本,通常是用户的当前输入
        k: 返回的记忆条数,默认 3 条
    返回:
        匹配到的记忆文本列表
    """
    # similarity_search 执行向量相似度搜索
    # 它先将 query 转为向量,再在数据库中找最近的 k 个向量
    docs = vectorstore.similarity_search(query, k=k)
    # 提取每条文档的文本内容,组装为列表返回
    return [doc.page_content for doc in docs]

# ── 示例:保存用户偏好到长期记忆 ──
save_memory("用户偏好 Python 编程语言,喜欢使用 pandas 处理数据")
save_memory("用户上次询问了特斯拉股价分析,关注新能源赛道")
save_memory("用户团队使用 PostgreSQL 数据库,偏好 SQL 查询")

# ── 示例:从长期记忆中检索相关信息 ──
query = "用户喜欢用什么技术栈?"  # 注意:查询中并未出现"Python"一词
memories = retrieve_memories(query)  # 但语义检索能命中相关记忆
print("检索到的相关记忆:")
for i, mem in enumerate(memories, 1):
    print(f"  {i}. {mem}")

在上面的示例中,查询"用户喜欢用什么技术栈?"并未包含"Python"这个关键词,但向量数据库通过语义相似度检索,依然能够准确返回"用户偏好 Python 编程语言"这条记忆——这就是向量检索相对于关键词检索的核心优势。

向量数据库的选型考虑

目前主流的向量数据库各有特点,选型时需要根据项目规模和部署环境权衡:

向量数据库特点适用场景
Chroma轻量级,纯 Python,支持本地持久化开发调试、小型项目、单机部署
Pinecone全托管云服务,无需运维,自动扩缩容生产环境、不想维护基础设施
Weaviate开源,支持混合检索(向量+关键词)需要兼顾精确匹配和语义检索
PostgreSQL + pgvector在关系数据库上扩展向量能力已有 PostgreSQL 基础设施,希望统一数据层
Milvus分布式架构,支持十亿级向量大规模生产环境、超大规模记忆库

对于大多数 Agent 项目,建议在开发阶段使用 Chroma(零配置、即开即用),在生产环境根据规模需求切换到 Pinecone 或 Milvus。

5.3.5 三种记忆类型:情景、语义与程序性

借鉴认知心理学对人类记忆的分类,Agent 的记忆也可以进一步细分为三种类型。理解这三种记忆的区别,有助于我们在设计时选择合适的存储和检索方案。

1. 情景记忆(Episodic Memory)

情景记忆记录的是"何时发生了什么"——即 Agent 在特定时间点经历的具体事件和交互历史。它类似于你回忆"上周三下午和客户的会议中讨论了什么"。

"2025年7月15日,用户询问了比亚迪的财报数据,
 我通过搜索 API 获取了 2025Q1 财报,用户对毛利率数据特别关注。"

实现方式:按时间序列存储的交互日志,支持时间范围查询。通常使用关系数据库(如 SQLite、PostgreSQL)存储,每条记录包含时间戳、用户输入、Agent 回复和工具调用记录。检索时可以按时间窗口筛选,也可以结合向量检索按内容语义匹配。

2. 语义记忆(Semantic Memory)

语义记忆存储的是事实、概念和通用知识——"什么是比特币""Python 3.12 有什么新特性"。它类似于你知道的"地球绕太阳公转"这样的客观知识。

"比特币(BTC)是一种去中心化的数字货币。"
"Python 3.12 引入了新的类型提示语法。"
"OpenAI 于 2024 年发布了 GPT-4o 模型。"

实现方式:向量数据库 + 知识图谱。向量数据库负责语义检索,知识图谱(如 Neo4j)则负责存储实体间的结构化关系。例如,"比特币"与"区块链""去中心化""中本聪"等实体之间存在关联,知识图谱可以记录这些关系,使 Agent 不仅"记住"事实,还能"理解"事实之间的联系。

3. 程序性记忆(Procedural Memory)

程序性记忆存储的是"如何做"的知识——包括工作流程、决策策略和操作步骤。它类似于你"知道怎么骑自行车"这类内化的技能性记忆。

"当用户要求数据分析时,先检查数据格式,再选择合适的可视化方式。"
"遇到 API 调用失败时,先重试 3 次,再切换到备用 API。"

实现方式:Prompt 模板 + 工作流定义 + Few-shot 示例。程序性记忆通常不存储在向量数据库中,而是编码在系统提示词、工具使用规则和 LangGraph 状态机定义中。当 Agent 遇到类似场景时,这些"操作规程"会被激活并指导行为。

三种记忆类型在实际系统中的协作方式如下:情景记忆让 Agent 记住"上次和这个用户发生了什么",语义记忆让 Agent 知道"这个领域的知识是什么",程序性记忆让 Agent 明白"遇到这类任务该怎么做"。

5.3.6 Mem0:新一代记忆基础设施

在上面几节中,我们看到记忆管理涉及大量手工编码:需要自己判断哪些信息值得保存、需要处理记忆的去重和更新、需要设计遗忘策略。这些工作繁琐且容易出错。

2024—2025 年,Mem0 作为 Agent 记忆领域最受关注的开源框架(GitHub 星标突破 6.8 万),正是为了解决这些问题而生。Mem0 的核心创新在于:

  • 自动记忆提取:LLM 自动从对话流中识别出值得记住的信息,无需用户手动触发"记住这个"。
  • 记忆去重与更新:当新记忆与已有记忆描述同一事实时,Mem0 会智能合并,避免冗余。例如,用户先说"我用 Python 3.11",后说"我刚升级到了 Python 3.13",Mem0 会自动更新而非新增。
  • 记忆衰减:模拟人类记忆的遗忘曲线,长期未被访问的记忆其重要性评分会逐渐降低,最终被自动淘汰。

以下是 Mem0 的使用示例:

python
# 导入 Mem0 核心类
from mem0 import Memory

# 初始化 Mem0 实例
# Mem0 内部自动管理向量数据库、嵌入模型和记忆生命周期
m = Memory()

# ── 添加记忆 ──
# Mem0 会自动从文本中提取关键信息并结构化存储
# 无需手动判断"哪些信息值得记住",LLM 会自动完成提取
m.add(
    "我叫张三,是一名 Python 后端工程师,"
    "我们团队正在开发一个 AI Agent 项目,"
    "使用 LangChain 和 LangGraph 框架。",
    user_id="zhangsan"   # user_id 用于隔离不同用户的记忆空间
)

# ── 搜索记忆 ──
# 语义检索:查询"技术栈"能命中"Python 后端工程师"和"LangChain 框架"
results = m.search("张三用什么技术栈?", user_id="zhangsan")
for r in results:
    # r['memory'] 是 Mem0 自动提取的结构化记忆文本
    # r['score'] 是语义相似度分数(0~1,越高越相关)
    print(f"记忆: {r['memory']} (相关度: {r['score']:.2f})")

# ── 获取全部记忆 ──
all_memories = m.get_all(user_id="zhangsan")
print(f"张三共有 {len(all_memories)} 条记忆")

Mem0 的价值在于它将记忆管理从"手工作坊"升级为"自动化工厂"——开发者不再需要手写记忆提取逻辑、去重算法和遗忘策略,Mem0 在底层自动完成了这些工作。

5.3.7 完整实战:构建带短期+长期记忆的 Agent

下面我们综合前面学到的知识,构建一个同时具备短期记忆(对话摘要)和长期记忆(向量数据库)的完整 Agent。这个 Agent 能在当前对话中保持上下文连贯,也能跨会话记住用户的偏好。

python
# ── 导入所需组件 ──
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain.memory import ConversationSummaryBufferMemory
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.documents import Document

# ── 初始化模型 ──
# LLM 用于生成回复和管理记忆摘要
llm = ChatOpenAI(model="gpt-4o", temperature=0.7)
# Embedding 模型用于将文本转为向量,供长期记忆检索
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# ── 长期记忆:向量数据库 ──
# 使用 Chroma 存储跨会话的长期记忆(用户偏好、重要事实等)
vectorstore = Chroma(
    collection_name="user_preferences",       # 集合名称
    embedding_function=embeddings,            # 嵌入函数
    persist_directory="./user_memory"         # 持久化目录,重启后记忆不丢失
)

# ── 短期记忆:摘要缓冲 ──
# 使用混合策略:近期对话保留原文,早期对话压缩为摘要
# max_token_limit=1000 限制短期记忆占用不超过 1000 tokens
short_term = ConversationSummaryBufferMemory(
    llm=llm,                              # 用于压缩摘要的 LLM
    max_token_limit=1000,                 # 总 token 预算
    return_messages=True,                 # 以 Message 对象列表返回
    memory_key="chat_history"             # Prompt 中的变量名
)

# ── 构建提示词模板 ──
# 将长期记忆和短期记忆(对话摘要)都注入到系统提示中
prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个智能助手,能记住用户偏好。

用户长期记忆(相关历史信息):
{long_term_memories}

当前对话摘要:
{chat_history}"""),
    ("human", "{input}")
])

def get_long_term_memories(input_text: str) -> str:
    """
    从向量数据库中检索与当前输入相关的长期记忆。
    返回格式化的记忆字符串,供注入到 Prompt 中。
    """
    # 执行语义检索,返回最相关的 3 条记忆
    docs = vectorstore.similarity_search(input_text, k=3)
    if not docs:
        return "(暂无相关长期记忆)"
    # 将检索到的记忆格式化为列表字符串
    return "\n".join([f"- {doc.page_content}" for doc in docs])

def save_to_long_term(content: str, category: str = "general"):
    """
    将重要信息保存到长期记忆(向量数据库)。
    """
    vectorstore.add_documents([
        Document(page_content=content, metadata={"category": category})
    ])

def chat_with_memory(user_input: str) -> str:
    """
    完整的带记忆对话函数。
    流程:检索长期记忆 → 获取短期记忆 → 生成回复 → 更新短期记忆 → 判断是否保存长期记忆
    """
    # 步骤 1:检索与当前输入相关的长期记忆
    long_term = get_long_term_memories(user_input)

    # 步骤 2:获取短期记忆(当前对话的历史摘要)
    chat_history = short_term.load_memory_variables({})["chat_history"]

    # 步骤 3:将长期记忆、短期记忆和用户输入组装到 Prompt 中,调用 LLM 生成回复
    response = llm.invoke(
        prompt.format(
            long_term_memories=long_term,
            chat_history=chat_history,
            input=user_input
        )
    )

    # 步骤 4:将本轮对话保存到短期记忆
    short_term.save_context(
        {"input": user_input},           # 用户输入
        {"output": response.content}      # Agent 回复
    )

    # 步骤 5:自动判断是否需要保存到长期记忆
    # 简单的关键词触发策略:当用户提到"记住""偏好""喜欢"等词时保存
    if "记住" in user_input or "偏好" in user_input or "喜欢" in user_input:
        save_to_long_term(
            f"用户说过: {user_input}",
            category="preference"
        )
        print("已自动保存到长期记忆")

    return response.content

# ── 测试对话 ──
print("用户: 你好,我喜欢用 Python 做数据分析")
print("助手:", chat_with_memory("你好,我喜欢用 Python 做数据分析"))

print("\n--- 模拟新会话 ---\n")
print("用户: 我之前说过我喜欢什么?")
print("助手:", chat_with_memory("我之前说过我喜欢什么?"))
# 即使是"新会话",Agent 也能从长期记忆中检索出"喜欢 Python 做数据分析"

5.3.8 记忆的自动摘要压缩

当对话历史过长时,直接将全部历史塞入上下文窗口既不经济也不高效。一个实用的做法是对历史对话进行自动摘要压缩——用 LLM 将长对话提炼为关键信息摘要,大幅降低 token 消耗。

python
from langchain_core.messages import HumanMessage, AIMessage

def compress_memory(messages: list, max_tokens: int = 500) -> str:
    """
    将对话历史压缩为摘要。

    参数:
        messages: Message 对象列表,包含完整对话历史
        max_tokens: 摘要的最大 token 数
    返回:
        压缩后的摘要文本
    """
    # 步骤 1:将 Message 列表格式化为可读的对话文本
    formatted = "\n".join([
        f"{'用户' if isinstance(m, HumanMessage) else '助手'}: {m.content}"
        for m in messages
    ])

    # 步骤 2:构造摘要 Prompt
    # 要求 LLM 保留用户偏好、重要决策和未完成任务
    summary_prompt = f"""请将以下对话历史压缩为简洁的摘要,保留关键信息。
要求:不超过 {max_tokens} 个 token,保留用户偏好、重要决策和未完成的任务。

对话历史:
{formatted}

摘要:"""

    # 步骤 3:调用 LLM 生成摘要
    response = llm.invoke([HumanMessage(content=summary_prompt)])
    return response.content

# ── 测试压缩效果 ──
# 模拟一段较长的多轮对话
messages = [
    HumanMessage(content="我想学习 AI Agent 开发"),
    AIMessage(content="很好的选择!AI Agent 是 2025 年的热门方向..."),
    HumanMessage(content="我主要用 Python,有什么推荐框架?"),
    AIMessage(content="推荐 LangChain 和 LangGraph,它们是 Agent 开发的主流框架..."),
    HumanMessage(content="LangGraph 和普通 LangChain 有什么区别?"),
    AIMessage(content="LangGraph 专注于状态管理和图结构编排..."),
    HumanMessage(content="那我需要先学什么基础?"),
    AIMessage(content="建议先掌握 LLM 基础、Prompt Engineering、Function Calling..."),
]

# 执行压缩
summary = compress_memory(messages, max_tokens=300)
print("对话摘要:\n", summary)

# 计算压缩比
original_len = sum(len(m.content) for m in messages)
print(f"\n原对话长度: {original_len} 字符")
print(f"摘要长度: {len(summary)} 字符")
print(f"压缩比: {len(summary) / original_len * 100:.1f}%")

5.3.9 记忆系统设计的核心挑战

记忆系统在工程落地时,会面临一系列实际挑战。以下四个问题最为常见,也最值得在设计阶段提前考虑。

挑战 1:记忆的"遗忘"问题

何时该遗忘?如何遗忘?如果永不遗忘,记忆库会无限膨胀,检索效率和存储成本都会成为问题。但盲目遗忘又可能丢失重要信息。

  • 解决方案:时间衰减 + 重要性评分 + LRU 淘汰。为每条记忆维护一个"重要性分数",初始分数由 LLM 评估(如"用户偏好"得 9 分,"闲聊寒暄"得 1 分)。分数随时间衰减,低于阈值时自动清除。

挑战 2:记忆的"幻觉"问题

向量检索基于语义相似度,但"语义相似"不等于"事实正确"。检索可能返回与当前查询语义相关但实际上过时或矛盾的记忆,导致 Agent 产生幻觉。

  • 解决方案:相关性阈值过滤 + 交叉验证。设置相似度分数阈值(如 0.75 以下不返回),对多条检索结果进行一致性检查,冲突时以最新记忆为准。

挑战 3:记忆的"隐私"问题

用户对话中可能包含敏感个人信息(PII),如身份证号、手机号、密码等。这些信息一旦被持久化到向量数据库,就存在泄露风险。

  • 解决方案:PII 检测 + 加密存储 + 用户控制面板。在写入记忆前先做 PII 脱敏;数据库层面加密存储;提供用户可查看、编辑和删除自身记忆的界面。

挑战 4:记忆的"整合"问题

多条记忆可能描述同一事实的不同侧面,甚至相互矛盾。例如用户先说"我用 PostgreSQL",后来说"我们换成了 MySQL",如果不做整合,两条矛盾的记忆会同时存在。

  • 解决方案:实体解析 + 知识图谱合并 + 冲突解决。通过实体识别判断多条记忆是否指向同一对象,使用知识图谱合并同源信息,新信息自动覆盖旧信息。

5.3.10 常见误区

在 Agent 记忆系统的设计与实现过程中,以下误区尤为常见,值得特别注意。

误区一:把所有对话历史都存入向量数据库

很多初学者认为"记忆越多越好",于是将每一轮对话都原样写入向量数据库。这会导致记忆库快速膨胀,检索质量下降——大量无关紧要的闲聊内容会稀释真正有价值的记忆。

正确做法是:只将值得长期记住的信息(如用户偏好、重要决策、关键事实)写入长期记忆。闲聊内容应留在短期记忆中,会话结束后自然消亡。可以使用 LLM 自动判断对话中哪些信息值得保存,这正是 Mem0 等框架的核心能力。

误区二:认为上下文窗口够大就不需要记忆管理

"模型支持 200K tokens,直接把所有历史塞进去不就行了?"这是最常见的误解之一。如前所述,长上下文带来成本、注意力稀释和延迟三大问题。即使模型支持超长上下文,也应该主动管理记忆,通过摘要压缩和滑动窗口控制 token 消耗。

误区三:忽视记忆的去重和更新

用户在多次对话中可能反复提到同一信息(如"我用 Python"),如果不去重,向量数据库中会堆积大量重复记忆。更严重的是,当信息发生变化时(如用户从 PostgreSQL 换到了 MySQL),如果不做更新,旧的和新的矛盾记忆会同时存在,导致 Agent 回复不一致。

正确做法是:在写入新记忆前,先检索是否已有相似记忆;如有,则判断是更新还是新增。Mem0 在这方面做得很好,它会在写入前自动做去重和冲突检测。

误区四:向量检索结果不加筛选直接使用

向量检索返回的结果按相似度排序,但"最相似"不等于"最相关"。有时检索结果的相关度很低(如只有 0.6),但仍然被返回并注入到 Prompt 中,反而会干扰 LLM 的判断。

正确做法是:设置相似度阈值,低于阈值的结果不返回。同时,对返回的记忆条数(k 值)也要合理控制——通常 3~5 条即可,过多的记忆反而会让 LLM 难以聚焦。

5.3.11 本节小结

本节我们从人脑的记忆机制出发,系统讨论了 Agent 记忆系统的设计与实现。以下是核心要点回顾:

  1. 记忆三层架构:短期记忆(上下文窗口)负责当前对话连贯性,长期记忆(向量数据库)负责跨会话知识积累,工作记忆负责当前任务的中间状态。三者协同工作,共同构成 Agent 的"经验宝库"。

  2. 短期记忆管理:滑动窗口、摘要压缩和混合策略是三大管理手段。核心思想是 Token 预算控制——像管理财务预算一样为上下文窗口中的不同信息分配配额。

  3. 向量数据库与长期记忆:向量数据库通过 Embedding 模型将文本转为高维向量,再通过语义相似度检索实现"联想式"记忆。Chroma 适合开发阶段,Pinecone 和 Milvus 适合生产环境。

  4. 三种记忆类型:情景记忆(经历)、语义记忆(知识)、程序性记忆(方法),分别对应交互日志、知识库和工作流模板,各有不同的存储和检索方式。

  5. Mem0 等新一代框架:实现了自动记忆提取、去重更新和智能衰减,大幅降低了记忆管理的工程复杂度。

  6. 四大核心挑战:遗忘管理、检索幻觉、隐私保护和记忆整合,需要在设计阶段提前规划解决方案。

至此,我们已经讨论了 Agent 架构中的三大核心组件——大脑(LLM)、战略思维(Planning)和经验宝库(Memory)。但 Agent 要真正"做事",还缺少最后一环:执行能力。Agent 如何调用外部工具、如何与 API 交互、如何将规划转化为实际动作?这正是下一节"5.4 工具与执行"将要讨论的内容。