Agent 记忆系统:对话管理、向量检索与长期记忆
Agent 记忆系统:对话管理、向量检索与长期记忆
一、概念速查
短期记忆 vs 长期记忆 对比表
| 维度 | 短期记忆(STM) | 长期记忆(LTM) |
|---|---|---|
| 存储位置 | 上下文窗口 / 内存 | 向量数据库 / 知识图谱 |
| 生命周期 | 会话绑定,结束即清理 | 跨会话持久化 |
| 容量限制 | 模型上下文窗口(4K-128K tokens) | 理论上无限(外部存储) |
| 更新频率 | 每次交互实时更新 | 选择性巩固,低频率 |
| 访问延迟 | 毫秒级(内存) | 10-200ms(网络+检索) |
| 实现方案 | LangGraph Checkpointer / 滑动窗口 | Mem0 / Zep / Chroma + Embedding |
记忆检索策略速查
| 策略 | 原理 | 适用场景 | 缺陷 |
|---|---|---|---|
| 相似度检索 | 查询向量与记忆向量做余弦距离排序 | 模糊语义联想 | 不感知时间、不处理否定 |
| 时间衰减排序 | score = sim * e^(-λ * days) |
时效敏感场景(价格、库存) | 可能压制高频低质记忆 |
| 重要性加权 | 记忆入库时 LLM 预打分(1-10) | 用户偏好、关键事实 | 打分成本高,依赖 LLM 质量 |
| 混合搜索(BM25 + 向量) | 关键词精确匹配 + 语义检索融合 | 通用生产环境 | 实现复杂度中等 |
向量数据库选型对比
| 产品 | 运维成本 | 扩展上限 | 混合搜索 | 关系查询 | 适用阶段 |
|---|---|---|---|---|---|
| Chroma | 极低(pip install) | 百万级 | 不支持 | 弱 | 本地开发 / 原型验证 |
| pgvector | 低(PG 扩展) | 千万级 | 需自行组合 | 强(SQL) | 已有 PG 的中小规模 |
| Milvus | 高(需 etcd+MinIO) | 十亿级+ | 支持 | 弱 | 大规模生产,有运维团队 |
| Qdrant | 中(Rust 单二进制) | 亿级 | 支持 | 中(过滤强) | 高性能过滤场景 |
| Pinecone | 极低(SaaS) | 亿级 | 内置 | 弱 | 快速上线,无 DBA 团队 |
选型路径:原型用 Chroma → 生产初期用 pgvector(有 PG 基础)或 Pinecone(无运维)→ 规模增长后评估 Milvus / Qdrant。
Embedding 模型选型要点
Embedding 模型直接决定语义检索的天花板。同一段文本经不同模型编码后,检索召回率可能相差 15%-30%。
| 模型 | 维度 | 最大输入 | 语言 | 适用场景 |
|---|---|---|---|---|
| text-embedding-3-small | 1536 | 8K tokens | 多语言 | 通用,性价比最高 |
| text-embedding-3-large | 3072 | 8K tokens | 多语言 | 高精度需求,成本翻倍 |
| BGE-M3 | 1024 | 8K tokens | 多语言 | 开源,支持稠密+稀疏双路 |
| multilingual-e5 | 1024 | 512 tokens | 多语言 | 开源,短文本匹配强 |
| m3e | 768 | 512 tokens | 中文为主 | 国内中文场景首选 |
选型原则:精度优先场景用 text-embedding-3-large(3072 维),成本敏感用 text-embedding-3-small(1536 维)。中文场景在 Recall@5 上 m3e 约 89%,BGE-M3 约 92%,OpenAI 系列约 93%-95%。
代码示例:向量库记忆存储与检索
import chromadb
from chromadb.utils import embedding_functions
client = chromadb.Client()
ef = embedding_functions.DefaultEmbeddingFunction()
collection = client.create_collection(name="agent_memory", embedding_function=ef)
collection.add(
documents=["用户偏好:喜欢简洁回复,不要解释"],
metadatas=[{"user_id": "u1", "importance": 8, "timestamp": "2025-01-01"}],
ids=["mem_001"]
)
results = collection.query(
query_texts=["用户希望回复风格如何?"],
n_results=3
)
print(results["documents"])
二、底层原理
混合记忆架构
生产级 Agent 不依赖单一记忆层,而是短期 + 长期并行检索后融合:
短期记忆维护最近 N 轮对话,保证基础连贯性。长期记忆从向量数据库检索跨会话的相关事实。两者经 Rerank 模型精排后合并,送入 LLM。回复生成后,新产生的关键事实异步写回长期记忆。
这种架构在 LoCoMo 基准测试上的数据显示:Mem0 用 1.44 秒 p95 延迟 + 1,800 tokens 达到了全量上下文 72.9% 准确率的 91.8%(66.9%),换来了 13 倍提速和 90% Token 节省。作为对比,图谱增强版 Mem0g 仅在时序推理等特定场景带来边际收益(综合提升 1.5 分),表明结构化记忆的实际增益集中在常规对话场景,图谱并非普适银弹。
混合架构在实际落地上还需要解决一个关键问题:短期记忆与长期记忆之间的冲突消解。当长期记忆携带的上下文与当前对话窗口中的信息存在分歧时(如用户偏好在近期发生变化),系统需要为短期信息赋予更高优先级。实践中通常采用"短期优先、长期参考"策略——向量检索结果作为补充而非替代,与滑动窗口合并时做去重和置信度排序。
滑动窗口 vs 摘要压缩的场景选择
| 场景 | 推荐策略 | 原因 |
|---|---|---|
| 客服对话(用户反复问同一产品) | 滑动窗口 | 最新上下文最重要,信息重复度高 |
| 医疗问诊(早期描述关键症状) | 摘要压缩 | 早期信息不可丢失,需压缩保留 |
| 代码审查助手 | Token 预算 + 摘要 | 长对话中早期决策理由需可追溯 |
| 通用场景 | 组合策略 | 滑动窗口兜底 + 高价值信息压缩 |
窗口策略实现参数对比
| 策略 | 实现方式 | Token 开销 | 信息损失 | 典型窗口大小 | 冷启动耗时 |
|---|---|---|---|---|---|
| 固定滑动窗口 | 保留最近 N 轮对话,丢弃超期 | 低(N×平均轮 tokens) | 高(丢弃的早期信息永久丢失) | 10-50 轮 | 无 |
| Token 预算窗口 | 保留最近对话至 M tokens,超出后截断 | 中(精确控制) | 中(可能截断中间轮次) | 4K-32K tokens | 无 |
| 增量摘要 | 每 N 轮对过期轮次执行 LLM 摘要 | 高(摘要推理成本) | 低(关键信息压缩保留) | 取决于压缩比 | 单次摘要 0.5-2s |
| 分级窗口 | L1 保留 N 轮完整 + L2 摘要存档 | 中高 | 极低 | L1=3-5 轮,L2=20+ 轮 | 首次摘要延迟 |
固定滑动窗口实现成本最低,但信息损失不可控。Token 预算窗口是生产中最常用的折中方案——设置 8K-16K 的 hard limit,超出后优先保留最近轮次和含工具调用结果的轮次。分级窗口在追求高准确率的场景(如医疗、法律)中表现最优。
代码示例:滑动窗口 vs 摘要压缩
from collections import deque
class SlidingWindow:
def __init__(self, max_rounds: int = 10):
self.buffer = deque(maxlen=max_rounds)
def add(self, user: str, assistant: str):
self.buffer.append({"user": user, "assistant": assistant})
def get_context(self) -> list:
return list(self.buffer)
class SummaryCompressor:
def __init__(self, max_rounds: int = 10):
self.max_rounds = max_rounds
self.history = []
self.summary = ""
def add(self, user: str, assistant: str):
self.history.append({"user": user, "assistant": assistant})
if len(self.history) >= self.max_rounds:
self._summarize()
def _summarize(self):
self.summary = f"[摘要压缩] 共 {len(self.history)} 轮对话已压缩"
self.history = []
def get_context(self) -> dict:
return {"summary": self.summary, "recent": self.history}
win = SlidingWindow(3)
cmp = SummaryCompressor(3)
for i in range(5):
win.add(f"第{i}轮用户", f"第{i}轮助手")
cmp.add(f"第{i}轮用户", f"第{i}轮助手")
print("滑动窗口上下文轮数:", len(win.get_context()))
print("摘要压缩 - 摘要:", cmp.get_context()["summary"])
print("摘要压缩 - 近期轮数:", len(cmp.get_context()["recent"]))
多用户记忆隔离方案
三种隔离级别:
- Collection 隔离:每用户独立向量集合。安全性最高,资源利用率最低。适合金融、医疗等合规强场景。
- Metadata 过滤:共享 collection,每条向量携带
user_id标签,查询时强制过滤。资源利用率最高,需防范查询注入。 - RLS 行级安全:仅 pgvector 支持,PostgreSQL 原生行级权限控制,兼顾资源利用与安全。
隔离方案的选择取决于合规要求与资源预算。物理隔离的上限是 Collection 数(Milvus 建议不超过 65536),逻辑隔离的上限是过滤性能。
三、架构设计原则
记忆写入时机
| 时机 | 写入内容 | 策略 |
|---|---|---|
| 每轮对话结束 | 用户偏好、关键决策 | 异步写,不阻塞主流程 |
| 任务完成 | 执行路径、成功/失败标记 | 批量写入 + 元数据标注 |
| 工具调用后 | 工具返回结果摘要 | LLM 摘要后再写入,避免冗余 |
| LLM 空闲期 | 短期记忆→长期记忆巩固 | 后台进程扫描 STM 做摘录 |
核心原则:写入异步化,不阻塞推理路径。高频写入采用批量 buffer,每 100 条或每 30 秒 flush 一次。
写入内容的质量控制
并非所有信息都值得写入长期记忆。写入前需经过"感知→判断→提炼"三步过滤:感知层识别潜在有价值信息(工具返回结果、用户明确表达偏好、决策变更),判断层用轻量级分类器(或 LLM 单次调用)过滤掉寒暄、重复和噪声内容,提炼层对原始文本做摘要压缩后入库。一套经过调优的过滤链路通常可以削减 50%-70% 的无效写入。
检索策略组合
生产环境起点是混合搜索(BM25 + 向量)+ Rerank。这以大约 20% 的额外复杂度换取了关键词精确匹配的显著增益。只有在处理高度关联的知识库且需要多跳推理时,才引入 GraphRAG。
检索策略对比总结
| 策略 | 召回率(Recall@5) | 精确率 | 延迟(p95) | 实现难度 |
|---|---|---|---|---|
| 纯向量余弦相似度 | 60-75% | 中等 | 50-100ms | 低 |
| BM25 全文检索 | 50-65% | 高(关键词精确匹配) | 10-30ms | 低 |
| 混合搜索(BM25 + 向量) | 75-90% | 高 | 60-120ms | 中 |
| 混合 + Rerank | 80-93% | 极高 | 80-200ms | 中高 |
| 混合 + Rerank + MMR 去重 | 78-90% | 极高(结果多样性强) | 100-250ms | 高 |
MMR(最大边界相关性)在 Top-20 候选结果中做多样性重排序,避免检索结果高度同质化。加入 MMR 通常会使 Recall@5 略微下降 2-3%,但显著提升生成质量——因为 LLM 收到的输入信息面更广。
记忆过期清理
三种遗忘策略组合使用:
- 时间衰减:指数函数
importance_now = original * e^(-λ * days)。λ 按记忆类型配置:用户偏好 λ=0.01(衰减慢),对话印象 λ=0.1(衰减快)。 - 重要性评分:写入时 LLM 打分(1-10 分制,低价值如"用户打了个招呼"2 分,优先淘汰)。
- 相似度合并:新记忆与已有记忆余弦相似度 >0.85 时触发 LLM 驱动的增量合并,避免同一事实的冗余副本。
不同遗忘策略的存储影响数据
| 策略 | 30 天累积记忆量 | 检索延迟(p95) | 信息利用率 |
|---|---|---|---|
| 无遗忘 | 100%(基准) | 500ms-2s | 呈下降趋势 |
| 仅 TTL 过期 | 40-60% | 120-300ms | 可能误删有用信息 |
| TTL + 重要性评分 | 25-40% | 80-150ms | 高价值信息保留 |
| TTL + 重要性 + 合并去重 | 15-30% | 60-100ms | 无冗余,精炼 |
从第一天就设计遗忘机制,不要等到存储爆炸再补。没有遗忘的记忆系统最终会淹没在信息噪声中,检索质量随记忆量增长逆向下降。
更多推荐

所有评论(0)