
📚 系列:《Agent 从入门到上手》· 30 天连载 · 第 8 篇(能力篇) 🎯 本篇目标:搞清楚 Agent 的两套记忆 (短期 / 长期)怎么分工、记忆该什么时候写、写什么 ,以及上下文塞爆之后怎么压缩、怎么遗忘------最后给你一个纯标准库、能直接跑的「记忆系统实验室」 ⏱️ 阅读时长:约 24 分钟
一、开场:你招了个"每天都是入职第一天"的员工
先说一个几乎所有人都会遇到的场景。
你跟一个 AI 助手聊了半小时,掏心掏肺地把背景交代清楚:
"我叫老张,在天津工作,做智能建造方向的研究。另外我对花生过敏,推荐餐厅记得避开。"
它答得又快又好。然后你第二天打开新会话,问它:
"帮我推荐一家天津的餐厅。"
它热情洋溢地回你:"推荐狗不理包子、煎饼馃子、罾蹦鲤鱼------都是本地名吃!"
然后你满怀期待地点开菜单,第三道菜是宫保鸡丁。
🤡 这时候你会深刻意识到一件事:它不是"记性不好",它是压根没有"记性"这个器官。
这就是 Day 03 里四大件之一「记忆」缺失的典型症状。前六篇我们搭出来的 Agent,本质上都是一个七秒记忆的金鱼------每一轮对话,它看到的都只是"这一轮发生了什么",外加你给它写的那份岗位说明书(Day 07)。至于你是谁、你上次说了什么、你讨厌什么,一概不知。
更魔幻的是,很多人的第一反应是:"那我把之前所有的聊天记录都贴给它不就行了?"
这个想法非常自然,也非常危险。为什么危险,第四节和第六节专门讲------先剧透一句:
🔑 把整个聊天记录塞进上下文,就像搬家时一件东西都不扔:房子是变满了,但你要找的那把钥匙,永远在最底下。
好,今天我们给这个数字员工装一个大脑里的海马体。
二、先分清两套记忆:桌上的便签 vs 楼下的档案柜
Agent 的记忆不是一个东西,是两套完全不同的系统。混为一谈是新手做记忆系统时最常见的翻车起点。

用人话讲:
| 🧾 短期记忆(Short-term / Working Memory) | 🗄️ 长期记忆(Long-term Memory) | |
|---|---|---|
| 存什么 | 本轮对话的原文、工具返回的原始结果、刚做的每一步 | 用户画像(身份/偏好/禁忌)、历史结论、领域知识 |
| 存在哪 | 内存里,就是一个 list[messages] |
硬盘 / 数据库 / 文件 |
| 多大 | 受上下文窗口限制,装满了必须腾位置 | 理论上无上限 |
| 多快 | 毫秒级,直接拼在提示词里,模型"抬眼就能看见" | 要检索才能拿到,慢一点 |
| 多贵 | 每轮都重发一遍,长度 × 轮数 = 账单 | 只发检索出来的一小撮,长期看反而省钱 |
| 会丢吗 | 进程一关就没了,会话结束即清零 | 持久化,换个会话、换台机器都还在 |
| 对应什么 | 办公桌上摊开的便签本 | 楼下的档案柜 |
一个特别好用的判断口诀:
🔑 短期记忆负责"连贯",长期记忆负责"认识你"。
- 只做短期记忆 → 一次会话里能接得上话,但每次都像第一次见面("请问怎么称呼您?");
- 只做长期记忆 → 存了一堆,但检索不回来就等于没存(档案柜锁着,钥匙丢了)。
顺带讲清三个被说滥的词
| 术语 | 人话翻译 | 一句话解释 |
|---|---|---|
| Context Window(上下文窗口) | 办公桌的桌面大小 | 模型一次最多能"同时看见"多少字,超出的它真的看不见 |
| Token(词元) | 计费和占位的小格子 | 中文大约 1 个字 1 个 token;它是收费单位,也是桌面面积的度量单位 |
| 上下文工程(Context Engineering) | 桌面收纳术 | 怎么在有限的桌面上,摆上对当前这一步最有用的东西(Day 13 专门展开) |
三、短期记忆:那张越摊越乱的办公桌
先讲短期记忆,因为它是你不用做任何额外工作就已经在用的那一套------每次你调一次大模型,你都在用短期记忆。
3.1 它本质上就是一个列表
messages = [
{"role": "system", "content": "你是「小张助理」......"}, # Day 07 的岗位说明书
{"role": "user", "content": "我叫老张......"},
{"role": "assistant", "content": "好的老张!"},
{"role": "user", "content": "帮我推荐一家餐厅"},
]
每一轮对话,你把这个列表整体再发一遍 。模型是无状态 的(stateless)------它不记得上一次你发过什么,它只知道"这次你发给我的这段文字"。所谓的"它记得",其实是你每次都把全文重发了一遍。
这一点极其重要,它带来三个直接后果:
🔑 ① 记忆的成本是"平方级"的。 第 1 轮发 1 轮内容,第 10 轮要发 10 轮内容,第 50 轮要发 50 轮内容。一轮 500 token,50 轮就是一万多 token------而这只是"让它看起来记得住"的代价。
🔑 ② 塞得越满,它越笨。 上下文越长,模型对中间部分的注意力越弱(Day 07 提过的 "lost in the middle")。你辛辛苦苦塞进去的第 7 轮的某个关键约定,可能在第 30 轮已经被它"视而不见"了。
🔑 ③ 塞爆了会直接报错。 超过窗口上限,API 会直接返回错误,你的 Agent 当场去世。
3.2 桌面经济学的三条账
| 账 | 怎么算 | 省钱的姿势 |
|---|---|---|
| 钱 | 每轮 token × 轮数 × 单价 | 只留必要的历史,其余压缩 |
| 速度 | 首字延迟 ≈ 输入长度的线性函数 | 输入越短,用户等得越短 |
| 质量 | 越长越容易"顾此失彼" | 重要的放两头(开头和结尾最容易被遵守) |
🤡 一个真实的吐槽:很多人优化 Agent 的第一反应是"换个更强的模型"。但一半的情况下,问题根本不是模型不够强,而是你往它桌上堆了 30 页材料,然后问它"第 12 页第三行写的啥"。换了爱因斯坦来,他也得先翻一阵。
3.3 一个反直觉的结论
短期记忆不是越多越好,是"刚好够用"最好。
一个常见的错误做法是:为了保证"什么都不丢",把每轮的原文、工具的完整返回、报错堆栈全部保留。三天后你的上下文是 4 万 token,Agent 变慢、变贵、变笨,而它真正需要的信息可能只有 300 字。
正确的思路是:让"流水账"留在短期记忆里(用完即焚),让"结论"沉淀到长期记忆里(永久保存)。 这就是下一节的事。
四、长期记忆:存什么、怎么存、怎么找回来
长期记忆才是"记住你是谁"的关键。它分三层,这三层对应三种完全不同的东西:
| 记忆类型 | 人话翻译 | 存什么 | 例子 | 什么时候用 |
|---|---|---|---|---|
| 情景记忆 Episodic | 日记本 | 发生过的事、具体经历 | "上次他让我改那份报告,改了三版才过" | 用户说"上次那个......" |
| 语义记忆 Semantic | 百科知识卡片 | 提炼后的事实与知识 | "他叫老张,在天津,研究智能建造" | 每次都要用的画像 |
| 程序记忆 Procedural | 肌肉记忆 | 做事的方法与流程 | "他要求报告结论先行,别写流水账" | 决定"怎么做"而不是"做什么" |
🎯 一个非常好用的判据 :如果一条记忆会影响 Agent 怎么做 (风格、格式、禁忌),它是程序记忆;如果它影响 Agent 认为事实是什么 (你是谁、你要什么),它是语义记忆;如果它只是发生过 (历史流水),那是情景记忆------而这第三种,恰恰是最该被压缩掉的。
4.1 存:别存整段聊天,存"抽出来的事实"
新手最爱干的事:把整轮对话原文存进数据库。 三个月后你的数据库里躺着 20 万条"好的,我明白了""收到""嗯嗯",真正的用户信息埋在里面,检索十条有九条是噪音。
正确的做法是写入前先抽取 :从一轮对话里,只挑出"值得长期保存"的事实(fact),然后带类型、带重要度地存进去。
# 值得存的:跨会话仍然成立、会影响未来决策、用户明确要求记
# 不值得存的:一次性计算、验证码、当时的天气股价、临时文件名
NOISE_PATTERNS = [
r"算(?:一下|一算)?\s*[\d\s\+\-\*/x×]+",
r"验证码",
r"今天(?:天气|气温|几号|星期几)",
r"现在几点",
]
🐟 一个真实的笑话 :我见过最离谱的一条"长期记忆"是
上次的临时文件名是 v3_final_真的最终版.docx。它在数据库里躺了 11 个月,每次用户问"帮我写个文档",它都被检索出来,然后 Agent 郑重其事地建议:"您可以命名为 v3_final_真的最终版.docx。"
4.2 找:检索不是"翻箱倒柜",是"按需取一小撮"
存进去只是第一步,能检索回来才算数。检索的核心动作是:
- 用户说了新的话 → 拿它当查询词;
- 在长期记忆里找出最相关的 k 条(通常 3-8 条);
- 把这 k 条拼进提示词的固定位置(通常是 System Prompt 之后、用户消息之前)。
这里的"相关"怎么算?真实系统里用 Embedding(向量化) 做语义相似度------简单说就是"给每段话发一个气味标签,问哪几段味道最像"。这块 Day 10 会专门讲透,今天先记住结论:
🔑 检索的最大陷阱是"贪多"。 你检索 20 条塞进去,等于把档案柜倒在了办公桌上------又回到第三节的问题了。k 取 3-8,宁少勿多;不相关的一律不要。
4.3 合并:同一件事别存成十个版本
用户第 1 轮说"我叫老张",第 30 轮又说"我叫老张"。如果你的实现是 INSERT,你会有两条一样的记忆;如果他说"我改名叫老李了",你会有两条自相矛盾的记忆------然后你的 Agent 会开始精神分裂,今天叫你老张明天叫你老李。
所以写入必须是 upsert(有就更新,没有才插入):
- 同类型 + 内容高度重合 → 更新 ,刷新时间戳、抬高重要度、
version += 1; - 同类型 + 内容冲突 → 覆盖,并保留一条变更记录(谁在什么时候改的,方便追溯);
- 不同类型 → 各自独立。
五、写入策略:不是所有对话都值得记住
这是记忆系统里最容易被跳过、代价却最高的一节。

5.1 四条判据
| 判据 | ✅ 该记 | ❌ 不该记 |
|---|---|---|
| 跨会话仍然成立吗? | "我对花生过敏"(下个月还是真的) | "帮我算下 17×25"(算完就完了) |
| 会影响未来的决策吗? | 预算上限、常用地址、禁忌、权限范围 | 今天的心情、随口一句吐槽 |
| 是用户明确要求记的吗? | "记住:以后都这么叫我" | (用户没提,你自作主张) |
| 是会变的吗? | --- | 验证码、实时股价、库存数量、临时文件名 |
🎯 一句话:记忆的是"人"和"规则",不是"事"和"数据"。 事和数据会变,人和规则相对稳定。
5.2 谁来决定"记不记"?三种做法
| 做法 | 怎么实现 | 优点 | 缺点 |
|---|---|---|---|
| ① 规则/正则 | 写一堆模式,命中就抽 | 便宜、快、可预测 | 覆盖不全,容易漏 |
| ② 让模型抽 | 每轮结束后让小模型输出"值得记的事实"JSON | 覆盖全、能理解语义 | 要花钱、要延迟、可能抽歪 |
| ③ 混合(推荐) | 高频类型走规则,兜底交给模型 | 性价比最高 | 两套代码要维护 |
工程上的现实选择:先上规则,跑两周看日志,把规则漏掉的高频场景挑出来,再考虑加模型。 一上来就"每轮都调一次模型抽记忆",你的账单会先于你的产品成熟。
5.3 什么时候写?别阻塞用户
写入动作的时机也有讲究:
- 轮末异步写 :用户这条已经拿到回复了,后台再慢慢抽、慢慢存 → 用户无感,推荐;
- 轮首同步写:下一轮开始前先写 → 会拖慢首字延迟;
- 定时批量写:攒一批一起写 → 最省,但会话中途断了会丢。
🧯 提醒:写入一定要幂等。网络重试导致同一条事实写了两次,不该产生两条记忆------这就是 4.3 节 upsert 的用处。
六、压缩与遗忘:聊到第 50 轮,上下文爆炸了怎么办
现在终于可以回答开头那个问题了:"把整个聊天记录都塞进去"为什么是个坏主意?
三个理由:会爆 (超过窗口直接报错)、会贵 (平方级成本)、会笨(中间被忽略)。
那怎么办?业界主流是三条路,各有各的适用位:
6.1 三条压缩路线
| 策略 | 做法 | 省 | 代价 | 适用 |
|---|---|---|---|---|
| ① 截断 | 只保留最近 N 轮 | 最省 | 丢开头的关键信息(名字、偏好全没了) | 只在"历史真的不重要"时用 |
| ② 摘要压缩 | 把旧对话压成一段事实摘要,保留最近 N 轮原文 | 很省 | 要一次摘要调用;摘要本身可能丢细节 | 默认推荐 |
| ③ 外置检索 | 旧对话存进长期记忆,按需检索回来 | 中等 | 需要检索链路 | 长会话 + 需要"翻旧账" |
我在动手环节里实测了三种策略在同一段 24 轮对话上的表现(预算 800 token):
| 策略 | tokens | 预算 | 关键事实保留率 |
|---|---|---|---|
| A 全量塞入 | 897 | ❌ 溢出 | 5/5(100%) |
| B 截断保留最近 6 轮 | 137 | ✅ 正常 | 0/5(0%) |
| C 摘要压缩 + 最近 6 轮 | 178 | ✅ 正常 | 5/5(100%) |
看清楚 B 那一列 :它最省钱,但把"老张 / 天津 / 智能建造 / 花生过敏 / 结论先行"全丢了。而 C 只比 B 多花 41 个 token,关键事实 100% 存活。
🔑 这 41 个 token,是整个记忆系统里性价比最高的一笔投资。
6.2 摘要怎么做才不丢关键事实
"摘要"不是让模型"总结一下",那是让它自由发挥。要写成有结构的抽取:
请把以下对话压缩成不超过 200 字的摘要,必须包含:
1. 用户的身份信息(姓名/城市/职业/研究方向)------没有就写"未提供"
2. 用户明确提出的所有偏好与禁忌(逐条列出)
3. 已达成的结论与待办事项(带状态)
4. 未完成、被中断或失败的操作
不要包含:寒暄、重复的确认、工具返回的原始数据。
🎯 要点:摘要要用"清单 + 字段",不要用"一段话"。 一段话的自由度太高,模型今天漏掉"花生过敏"你根本发现不了;清单漏了一项,一眼就看得出来。
另一个实战技巧:分层摘要。每 10 轮压一次小摘要,每 5 个小摘要再压一次大摘要。比"一次性压 50 轮"稳得多,也便宜得多(每次输入都短)。
6.3 遗忘:该忘的必须忘得掉
最后讲一个几乎所有教程都不讲、但几乎每个生产系统都会遇到的东西:记忆库会腐烂。
一个只增不减的长期记忆库,最后会变成一屋子过期传单:
- 用户三年前说"我在用 Windows 10"------现在还在里面;
- 用户说过一次"帮我看看 Python 2 的写法"------被当成长期兴趣;
- 检索的时候,这些陈年老账会挤占 k 个名额,把真正重要的挤出去。
解法是给每条记忆算一个强度分数,定期淘汰低于阈值的:
强度 = 重要度 × 0.5 ^ (距上次被唤起的天数 / 有效半衰期)
有效半衰期 = 基础半衰期 × (1 + 被唤起次数 × 0.5) # 越常被想起,忘得越慢
这个公式有两处刻意的设计,值得单独说:
- 衰减看的是"上次被唤起",不是"创建时间"。 一条被反复用到的老记忆不该衰减------就像你天天骑的自行车,不会因为买了三年就变难骑。这就是艾宾浩斯遗忘曲线里"复习重置"的机制。
- 用户画像类(姓名、过敏、核心偏好)应该"钉住",永不衰减。 你不会因为用户 200 天没提"花生过敏"就认为他痊愈了------这条要是被忘掉,是会出医疗事故的。
实测一张表(基础半衰期 30 天,阈值 1.0):
| 记忆内容 | 重要度 | 上次唤起 | 唤起次数 | 有效半衰期 | 强度 | 判定 |
|---|---|---|---|---|---|---|
| 对花生过敏 | 10 | 3 天前 | 25 | 钉住 | 10.00 | ✅ 保留 |
| 我叫老张 | 9 | 5 天前 | 30 | 钉住 | 9.00 | ✅ 保留 |
| 报告要结论先行 | 8 | 20 天前 | 12 | 210 天 | 7.49 | ✅ 保留 |
| 不喜欢香菜 | 7 | 200 天前 | 1 | 45 天 | 0.32 | 🗑️ 淘汰 |
| 回信用三段式 | 3 | 260 天前 | 0 | 30 天 | 0.01 | 🗑️ 淘汰 |
| 临时文件名 v3_final_真的最终版 | 2 | 330 天前 | 0 | 30 天 | 0.00 | 🗑️ 淘汰 |
🐟 注意看第四行 :「不喜欢香菜」是真的 ,但它 200 天没被提起,慢慢淡了。这是遗忘机制的代价 ,也是它的本意------记忆系统的资源是有限的,你不可能什么都留着。如果你觉得某条绝不能忘,就在写入时把它标成"钉住",而不是去调阈值。
🔑 最后一句,也是本篇我最想让你记住的:记忆系统的上限,不是"能存多少",而是"该忘的能不能忘掉"。
七、动手环节:搭一个「记忆系统实验室」
今天不装任何库 ,纯 Python 标准库,也没有随机数(结果可复现)。完整脚本在 temp/day08_memory_lab.py。
我们要做五件事:给短期记忆装上预算 → 抽取值得存的事实 → 建一个会检索的档案柜 → 对比"有记忆 / 没记忆"的跨会话表现 → 看压缩和遗忘。
第 1 步:给短期记忆装一个预算
def approx_tokens(text: str) -> int:
"""粗估 token 数:中文 1 字 1 token,英文单词 1.3 token/词。"""
zh = len(re.findall(r"[\u4e00-\u9fff]", text))
en = len(re.findall(r"[A-Za-z]+", text))
return int(zh * 1.0 + en * 1.3) + 1
class ShortTermMemory:
def __init__(self, budget=800):
self.budget = budget
self.turns = [] # 短期记忆:就是一个 list
self.system = ""
self.summary = "" # 被压缩掉的历史留下的摘要
def tokens(self):
total = approx_tokens(self.system) + approx_tokens(self.summary)
for t in self.turns:
total += approx_tokens(t["content"]) + 4 # 4 ≈ 角色标记开销
return total
def overflow(self):
return self.tokens() > self.budget
关键就是那句 overflow()------没有预算检查的记忆系统,等于没有刹车。绝大多数"Agent 跑着跑着突然报错"的事故,根因都是没人盯着这个数字。
第 2 步:抽取值得存的事实(含噪音过滤)
EXTRACTORS = [
("identity", r"我叫([\u4e00-\u9fffA-Za-z]{1,8})", 9, "我叫{}"),
("allergy", r"我对([\u4e00-\u9fff]{1,6})过敏", 10, "对{}过敏"),
("location", r"在([\u4e00-\u9fff]{2,4})(?:做|搞|从事|工作|上班|生活|读书)", 8, "在{}"),
("domain", r"研究(?:方向是|的是)?([\u4e00-\u9fff]{2,8})", 7, "研究方向:{}"),
("prefer", r"记住[::]?\s*(.{2,28}?)(?:[,。!]|$)", 8, "{}"),
]
NOISE_PATTERNS = [r"算(?:一下|一算)?\s*[\d\s\+\-\*/x×]+", r"验证码", r"现在几点"]
def extract_facts(text):
if is_noise(text): # 一次性信息直接拦在门外
return []
facts = []
for ftype, pattern, imp, tpl in EXTRACTORS:
for m in re.finditer(pattern, text):
facts.append((ftype, tpl.format(m.group(1).strip()), imp))
return facts
🧯 踩坑提示:正则写宽了会误抓。我第一版写了
以后(.+),结果「以后推荐餐厅帮我避开」被当成"长期偏好"存了进去------这就是 4.1 节说的噪音。规则要收紧,收紧不了就加校验。
第 3 步:会合并、会检索、会遗忘的档案柜
class LongTermMemory:
def upsert(self, ftype, content, importance, ts=None, hits=0, last_seen=None):
for it in self.items: # 同类型 + 内容重合 → 合并而不是新增
if it["type"] == ftype and (content in it["content"] or it["content"] in content):
it["content"] = content
it["importance"] = max(it["importance"], importance)
it["version"] += 1
return it, False
self.items.append({...}); return item, True
def search(self, query, k=2):
ranked = sorted(self.items, key=lambda it: -self.score(it, query))
picked = [it for it in ranked if self.score(it, query) > 3][:k]
for it in picked:
it["hits"] += 1
it["last_seen"] = NOW # 被唤起 = 复习一次,遗忘曲线重置
return picked
def strength(self, it):
if it["type"] in PINNED_TYPES: # 用户画像,钉住,永不衰减
return it["importance"]
eff_half = self.half_life * (1 + it["hits"] * 0.5)
return it["importance"] * math.pow(0.5, (NOW - it["last_seen"]) / eff_half)
打分函数 score() 里除了关键词重合,还加了一项意图提示 (TYPE_HINTS):用户问"名字/谁"就往 identity 类加权,问"餐厅/吃"就往 allergy 类加权。
🧯 说明:真实系统里这一步由 Embedding 语义相似度 完成,不需要人工写关键词表。这里用关键词近似,是为了让脚本零依赖可运行------Day 10 讲向量之后,你可以把
score()换成余弦相似度,其他代码一行不用改。
第 4 步:实测------有记忆 vs 没记忆
会话 1(交代背景):
我叫老张,在天津工作,研究方向是智能建造
我对花生过敏,以后推荐餐厅帮我避开
帮我算一下 17 乘以 25 是多少 -> 被判定为一次性信息,不进档案柜
记住:以后给我写报告都用结论先行的结构,别写流水账
抽取结果(5 条记忆,去重后):
[identity] 我叫老张 重要度=9
[location] 在天津 重要度=8
[domain] 研究方向:智能建造 重要度=7
[allergy] 对花生过敏 重要度=10
[prefer] 以后给我写报告都用结论先行的结构 重要度=8
再写一次「我叫老张」-> 条数 5 -> 5(合并,不产生副本)
会话 2(新会话,短期记忆清零)问同一批问题:
| 问题 | 无记忆 Agent | 有记忆 Agent |
|---|---|---|
| 我叫什么名字? | ❌ "请问怎么称呼您?" | ✅ "您叫老张" |
| 我在哪个城市、做什么方向? | ❌ "这个我不太清楚" | ✅ "您的研究方向:智能建造;您在天津" |
| 推荐天津餐厅有啥注意的? | ❌ 直接推了本地名吃(含花生做法) | ✅ "您对花生过敏,已帮您避开相关菜品" |
| 上次说的报告结构是什么? | ❌ "请问您指的是哪份报告?" | ✅ "按您的要求:结论先行" |
📊 命中率:无记忆 0/4 有记忆 4/4
第三行值得单独看一眼 :无记忆 Agent 不是"答不出来",而是给了一个看起来很专业、实际有风险的答案。这正是没有记忆的 Agent 最危险的失败方式------它不会说"我不知道",它会热情地瞎推荐。
第 5 步:实测------压缩与遗忘
压缩策略对比(24 轮对话,预算 800 token)与遗忘曲线的结果,就是第六节那两张表------它们都是这个脚本真跑出来的,不是我编的。
你可以这样玩
- 把
EXTRACTORS换成你自己业务的字段(比如"会员等级""常用收货地址""预算上限"); - 把
SESSION_1换成你真实的一段对话,看看抽取器漏了什么; - 把
k从 2 调到 8,观察"召回变多但噪音也变多"的临界点; - 把
half_life_days从 30 改成 7,看看哪些记忆会突然活不下来。
八、本篇小结(建议截图保存)
- 🧠 记忆分两套:短期记忆是桌上的便签(管"连贯",每轮重发,受窗口限制),长期记忆是楼下的档案柜(管"认识你",持久化,靠检索取回)。
- 💸 短期记忆的成本是平方级的:第 N 轮要发 N 轮内容。而且塞得越满,模型对中间部分越迟钝,塞爆了直接报错。
- 📇 长期记忆分三层 :情景(日记本)/ 语义(百科卡片)/ 程序(肌肉记忆)。最该被压缩掉的是第一种。
- ✍️ 写入要抽取,不要照抄:存"事实"不存"聊天记录"。一次性信息(验证码、临时文件名、实时数据)必须拦在门外。
- 🔁 写入必须 upsert:同类型 + 内容重合就合并,冲突就覆盖并留痕------否则 Agent 会精神分裂。
- 🔍 检索宁少勿多:k 取 3-8。检索 20 条塞进去,等于把档案柜倒在桌子上。
- 🗜️ 压缩选"摘要 + 最近 N 轮" :实测在 24 轮对话上,截断只花 137 token 但关键事实保留 0/5;摘要压缩花 178 token,保留 5/5。多花 41 个 token 换 100% 存活。
- 🐟 遗忘是特性不是 bug :强度 = 重要度 × 时间衰减,衰减看"上次被唤起"而不是创建时间 ,越常用忘得越慢;姓名、过敏这类用户画像要钉住。
- ⚠️ 记忆系统的上限,不是"能存多少",而是"该忘的能不能忘掉"。
- 🧯 埋个伏笔 :本文的检索用关键词近似,真实系统用 Embedding 向量相似度------明天讲 RAG,后天(Day 10)讲向量数据库,到时候把
score()换掉即可。
📝 课后作业
- 动手题 :把
SESSION_1换成你自己的一段真实对话,跑一遍脚本,看看extract_facts()漏抽了哪几条。漏的里面挑一条最重要的,给它补一条正则,再跑一次确认能抽到。 - 思考题 :你的 Agent 跑了三个月,长期记忆库里有 8000 条。用户投诉"它最近老提起一些过时的东西"。请用第六节的思路,写出你的三条整改措施,并说明哪些记忆应该被标成"钉住"。
- 进阶题 :把演示 4 的
strategy_summary()改成"分层摘要"------每 10 轮压一次小摘要,小摘要再压成大摘要。对比单层摘要,看看在 50 轮对话下哪个 token 更省、关键事实保留率更高。
🔮 下集预告
Day 09|RAG 检索增强生成:给 Agent 外挂一个随身图书馆
今天我们让 Agent 记住了你 。但还有一类东西它依然不知道:你公司的产品手册、上周的会议纪要、那本 300 页的行业规范。
这些东西有几个共同点:量大(塞不进上下文)、会变(记死了就过时)、不是"关于用户"的(不该进用户画像)。硬塞进 System Prompt?Day 07 讲过,那是把 300 页文档每轮重发一遍。塞进长期记忆?检索准度又不够。
明天我们讲一个几乎所有生产级 Agent 都离不开的东西------RAG(Retrieval-Augmented Generation,检索增强生成):
- 📚 为什么它是"外挂图书馆"而不是"背下来"
- 🔪 切分怎么切才不把一段完整的意思劈成两半
- 🔢 向量化 → 检索 → 重排 → 生成的完整链路,一步步拆
- 🚑 以及最重要的:什么时候 RAG 也救不了你
剧透一句:RAG 的难点从来不在"检索",在"切分"和"检索不到时怎么办"。 明天 12:00 见。😉
🗺️ 系列导航
| 阶段 | 篇目 | 主题 |
|---|---|---|
| 认知篇 | Day 01-06 | Agent 是什么、四大件、ReAct、Function Calling、概念辨析(✅ 已完结) |
| 能力篇 | Day 07-14 | 提示词、记忆、RAG、向量库、规划、自愈、上下文工程、人设(📍 你在这里:Day 08) |
| 框架篇 | Day 15-21 | LangChain / LangGraph / LlamaIndex / Dify / 手写 mini Agent |
| 多智能体篇 | Day 22-27 | 多智能体架构、MCP、A2A、人机协同 |
| 工程化篇 | Day 28-33 | 评测、可观测、成本优化、安全护栏、生产落地、收官实战 |
已更新 :Day 01|Agent 到底是什么? · Day 02|LLM 和 Agent 的区别 · Day 03|解剖一只 Agent:四大件 · Day 04|ReAct 循环:想→做→看→再想 · Day 05|Function Calling 从零讲透 · Day 06|四个概念辨析与选型 · Day 07|提示词工程(Agent 版)
每天 12:00 更新一篇。 觉得有用的话,点个收藏,别到时候找不到路了 😄 有问题欢迎在评论区留言,每一条我都会看,典型问题会写进后续的加餐篇。