文章目录
- 【71.Python+AI】Agent记忆系统:短期记忆、长期记忆与外部知识库的三层架构
-
- 导入语
- [1 ~> 三层记忆架构总览](#1 ~> 三层记忆架构总览)
-
- [1.1 三层分工对照](#1.1 三层分工对照)
- [2 ~> 短期记忆:对话历史的管理](#2 ~> 短期记忆:对话历史的管理)
-
- [2.1 核心矛盾:窗口有限,对话无限](#2.1 核心矛盾:窗口有限,对话无限)
- [2.2 实现:窗口+摘要混合](#2.2 实现:窗口+摘要混合)
- [3 ~> 长期记忆:用户画像的持久化](#3 ~> 长期记忆:用户画像的持久化)
-
- [3.1 从对话中提炼"关于用户的事实"](#3.1 从对话中提炼"关于用户的事实")
- [3.2 画像的使用时机](#3.2 画像的使用时机)
- [4 ~> 外部知识库:经验库的沉淀](#4 ~> 外部知识库:经验库的沉淀)
-
- [4.1 与RAG的区别](#4.1 与RAG的区别)
- [4.2 写入时机的把关](#4.2 写入时机的把关)
- [5 ~> 三层记忆的读写时机总表](#5 ~> 三层记忆的读写时机总表)
- [思考 && 总结](#思考 && 总结)
- 结尾
【71.Python+AI】Agent记忆系统:短期记忆、长期记忆与外部知识库的三层架构
📖 文章简介: 本文系统讲解Agent记忆系统的三层架构设计:短期记忆(对话历史的Token预算管理)、长期记忆(用户画像与偏好的跨会话积累)和外部知识库(向量数据库中沉淀的"经验库")。文章拆解每层的存储介质、读写时机和淘汰策略------短期记忆用滑动窗口+摘要压缩、长期记忆用结构化Profile持久化、经验库用向量检索按需召回,并给出一个三层记忆整合的Python实现骨架。配以Mermaid架构图展示记忆读写时机与数据流向,适合正在开发多轮对话Agent、被"聊着聊着就忘了"问题困扰的开发者。

🎬 个人主页: 源码骑士
❄ 专栏传送门: 《Android开发基础》《python基础课程》
⭐️热衷从源码视角拆解技术底层原理,将复杂架构讲得通俗易懂
🎬 源码骑士的简介:
5年Android Framework系统开发经验,曾主导多项系统级性能优化专项
技术栈覆盖Android系统全链路(Binder/Handler/AMS/WMS/启动流程)及Java后端全家桶(Spring + MyBatis + Redis + Oracle)
累计产出原创技术文章100+篇,文章以流程图为特色,被读者评价为"看一篇胜过啃一周源码"
导入语
你跟AI助手聊了半小时,它还记得你第一句说的"我对海鲜过敏"吗?下周再打开它,它还能叫出你的名字吗?
如果两个问题的答案都是"不能",那你的Agent就没有真正的记忆系统------它只是一个"金鱼脑":所有的上下文都活在当前的对话窗口里,窗口满了就丢,会话结束就清零。
真正可用的Agent需要三层记忆:对话内记住说过什么、跨会话记住你是谁、永久记住它积累的经验。 这篇文章就把这三层怎么设计、怎么配合讲清楚。
1 ~> 三层记忆架构总览
#mermaid-svg-BsSjcVnUkZF2aB1m{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BsSjcVnUkZF2aB1m .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BsSjcVnUkZF2aB1m .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BsSjcVnUkZF2aB1m .error-icon{fill:#552222;}#mermaid-svg-BsSjcVnUkZF2aB1m .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BsSjcVnUkZF2aB1m .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BsSjcVnUkZF2aB1m .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BsSjcVnUkZF2aB1m .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BsSjcVnUkZF2aB1m .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BsSjcVnUkZF2aB1m .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BsSjcVnUkZF2aB1m .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BsSjcVnUkZF2aB1m .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BsSjcVnUkZF2aB1m .marker.cross{stroke:#333333;}#mermaid-svg-BsSjcVnUkZF2aB1m svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BsSjcVnUkZF2aB1m p{margin:0;}#mermaid-svg-BsSjcVnUkZF2aB1m .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-BsSjcVnUkZF2aB1m .cluster-label text{fill:#333;}#mermaid-svg-BsSjcVnUkZF2aB1m .cluster-label span{color:#333;}#mermaid-svg-BsSjcVnUkZF2aB1m .cluster-label span p{background-color:transparent;}#mermaid-svg-BsSjcVnUkZF2aB1m .label text,#mermaid-svg-BsSjcVnUkZF2aB1m span{fill:#333;color:#333;}#mermaid-svg-BsSjcVnUkZF2aB1m .node rect,#mermaid-svg-BsSjcVnUkZF2aB1m .node circle,#mermaid-svg-BsSjcVnUkZF2aB1m .node ellipse,#mermaid-svg-BsSjcVnUkZF2aB1m .node polygon,#mermaid-svg-BsSjcVnUkZF2aB1m .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BsSjcVnUkZF2aB1m .rough-node .label text,#mermaid-svg-BsSjcVnUkZF2aB1m .node .label text,#mermaid-svg-BsSjcVnUkZF2aB1m .image-shape .label,#mermaid-svg-BsSjcVnUkZF2aB1m .icon-shape .label{text-anchor:middle;}#mermaid-svg-BsSjcVnUkZF2aB1m .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-BsSjcVnUkZF2aB1m .rough-node .label,#mermaid-svg-BsSjcVnUkZF2aB1m .node .label,#mermaid-svg-BsSjcVnUkZF2aB1m .image-shape .label,#mermaid-svg-BsSjcVnUkZF2aB1m .icon-shape .label{text-align:center;}#mermaid-svg-BsSjcVnUkZF2aB1m .node.clickable{cursor:pointer;}#mermaid-svg-BsSjcVnUkZF2aB1m .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-BsSjcVnUkZF2aB1m .arrowheadPath{fill:#333333;}#mermaid-svg-BsSjcVnUkZF2aB1m .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BsSjcVnUkZF2aB1m .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BsSjcVnUkZF2aB1m .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BsSjcVnUkZF2aB1m .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-BsSjcVnUkZF2aB1m .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BsSjcVnUkZF2aB1m .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-BsSjcVnUkZF2aB1m .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BsSjcVnUkZF2aB1m .cluster text{fill:#333;}#mermaid-svg-BsSjcVnUkZF2aB1m .cluster span{color:#333;}#mermaid-svg-BsSjcVnUkZF2aB1m div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BsSjcVnUkZF2aB1m .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BsSjcVnUkZF2aB1m rect.text{fill:none;stroke-width:0;}#mermaid-svg-BsSjcVnUkZF2aB1m .icon-shape,#mermaid-svg-BsSjcVnUkZF2aB1m .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BsSjcVnUkZF2aB1m .icon-shape p,#mermaid-svg-BsSjcVnUkZF2aB1m .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-BsSjcVnUkZF2aB1m .icon-shape .label rect,#mermaid-svg-BsSjcVnUkZF2aB1m .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BsSjcVnUkZF2aB1m .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-BsSjcVnUkZF2aB1m .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-BsSjcVnUkZF2aB1m :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户输入
记忆读取层
短期记忆
对话历史窗口
长期记忆
用户画像 Profile
外部知识库
向量经验库
LLM 推理
Agent 响应
记忆写入层
追加对话历史
更新用户画像
沉淀有价值的经验
1.1 三层分工对照
| 层级 | 存什么 | 存活周期 | 存储介质 | 类比 |
|---|---|---|---|---|
| 短期记忆 | 当前对话的上下文 | 单次会话 | 内存/Redis | 人类的"工作记忆" |
| 长期记忆 | 用户偏好、习惯、画像 | 永久 | 数据库(结构化) | 你对老朋友的了解 |
| 外部知识库 | 任务经验、领域知识 | 永久 | 向量数据库 | 笔记本和资料库 |
2 ~> 短期记忆:对话历史的管理
2.1 核心矛盾:窗口有限,对话无限
LLM的上下文窗口是硬约束(比如128K tokens)。对话一长,必须做取舍:
| 策略 | 做法 | 缺点 |
|---|---|---|
| 滑动窗口 | 只保留最近N轮 | 早期关键信息被丢弃 |
| 摘要压缩 | 老对话交给LLM总结成摘要 | 摘要本身消耗Token和调用次数 |
| 窗口+摘要 | 最近N轮原文 + 更早的摘要 | 实现稍复杂,但效果最好(推荐) |
2.2 实现:窗口+摘要混合
python
class ShortTermMemory:
def __init__(self, window_size=10, llm=None):
self.window_size = window_size
self.llm = llm
self.recent = [] # 最近N轮原文
self.summary = "" # 更早历史的压缩摘要
def add_turn(self, user_msg, ai_msg):
self.recent.append({"user": user_msg, "assistant": ai_msg})
if len(self.recent) > self.window_size:
# 挤出的最早一轮,合并进摘要
overflow = self.recent.pop(0)
self.summary = self.llm.invoke(
f"把这段对话合并进现有摘要,保持关键信息:\n"
f"现有摘要:{self.summary}\n新增对话:{overflow}"
).content
def get_context(self):
return f"历史摘要:{self.summary}\n最近对话:{self.recent}"
3 ~> 长期记忆:用户画像的持久化
3.1 从对话中提炼"关于用户的事实"
每轮对话结束后,让LLM提取值得长期记住的信息:
python
EXTRACT_PROMPT = """从以下对话中提取关于用户的持久化事实(偏好/身份/习惯),
以JSON列表返回,没有则返回[]。
已有画像:{existing_profile}
新对话:{conversation}
只返回新增的、不重复的事实,如:
["用户对海鲜过敏", "用户是Python后端开发", "用户偏好简洁的回答"]"""
class LongTermMemory:
def __init__(self, db_path="profiles.db"):
self.db = sqlite3.connect(db_path)
def update_profile(self, user_id, new_facts):
"""增量合并新事实到用户画像"""
profile = self.load_profile(user_id)
for fact in new_facts:
if fact not in profile["facts"]:
profile["facts"].append(fact)
self.save_profile(user_id, profile)
3.2 画像的使用时机
画像不是每轮都全部注入------而是在生成回复前,作为System Prompt的一部分:
python
system_prompt = f"""你是用户的私人助手。
已知用户信息:{profile['facts']}
请在回答时考虑这些背景,但不要主动复述。"""
4 ~> 外部知识库:经验库的沉淀
4.1 与RAG的区别
普通RAG的知识是"别人写的文档",经验库的知识是**"Agent自己干过的事"**:
python
class ExperienceMemory:
def __init__(self, vector_store):
self.store = vector_store
def save_experience(self, task, solution, success):
"""把一次成功任务的解法沉淀进经验库"""
if success:
self.store.add_texts([f"任务:{task}\n解法:{solution}"])
def recall(self, new_task, top_k=3):
"""遇到相似任务时召回历史经验"""
return self.store.similarity_search(new_task, k=top_k)
# 使用
exp = ExperienceMemory(chroma_store)
# 执行新任务前,先查经验
similar = exp.recall("批量处理1000条客服对话并分类")
if similar:
prompt = f"参考历史经验:{similar}\n\n新任务:..."
4.2 写入时机的把关
经验库最大的坑是垃圾进垃圾出------失败的尝试、错误的结论如果被沉淀,会污染后续召回。两条原则:
- 只有用户确认满意的结果才入库(用户给了正反馈才save)
- 入库前让LLM做一次泛化(把具体的"1000条客服对话"抽象为"大批量文本分类任务")
5 ~> 三层记忆的读写时机总表
| 时机 | 短期记忆 | 长期记忆 | 经验库 |
|---|---|---|---|
| 收到新输入时 | 读取全文 | 读取画像 | 相似任务召回 |
| 生成回复时 | 注入上下文 | 注入System Prompt | 注入参考经验 |
| 每轮对话后 | 追加+压缩 | 提炼新事实 | 不写入 |
| 任务完成被认可时 | --- | --- | 沉淀成功经验 |
思考 && 总结
- 记忆不是单一概念,而是三种不同生命周期的系统: 短期管"这次对话"、长期管"这个用户"、经验库管"这个Agent的成长"。
- 短期记忆的最优解是窗口+摘要: 单纯滑动窗口会丢关键信息,纯摘要又会丢失细节,混合策略是工程上的最佳平衡。
- 长期记忆的关键是"增量提炼"而非"全文存储": 把对话原文全存下来既浪费又难用,提炼成事实列表才有价值。
- 经验库要严格把关写入质量: 宁缺毋滥------一条错误的经验,会污染之后无数次相似任务的召回。
没有记忆系统的Agent只是聊天机器人,有三层记忆的Agent才开始像一个"越用越懂你的助手"。
结尾
各位小伙伴,本文的内容到这里就全部结束了,源码骑士在这里再次感谢您的阅读!
源码骑士 --- Android Framework & 全栈开发
👀 关注:跟博主一起从源码视角深耕底层原理,见证每一次成长
❤️ 点赞:让优质内容被更多人看见,让知识传递更有力量
⭐ 收藏:把核心知识点存好,在需要时随时查、随时用
💬 评论:分享你的经验或疑问,评论区一起交流避坑
🔄 一键四连:不要忘记给博主"一键四连"哦!
🗡️ 寄语:技术之路难免有困惑,但同行的人会让前进更有方向
结语:人类靠记忆建立信任,Agent也一样。把三层记忆搭好,你的Agent就从"每次见面都重新认识"进化成了"老朋友"。不要忘记给博主"一键四连"哦!