先说两个我亲身经历的「失忆事故」。
第一个是刚做客服机器人那会儿,测试同事在第三轮对话里明确说了「别用敬语,直接说事」。机器人答应得好好的。聊到第七轮,它又开始「尊敬的用户您好」。同事把截图甩群里,配了三个字:金鱼脑。
第二个更伤。有个用户上周刚纠正过产品的正确名称,这周再来咨询,机器人又把名字叫错了。用户当场就不乐意了:「我上周是不是白说了?」
这两个问题的根子是同一个:LLM 本身没有记忆。你看到的「对话」全是工程把戏。这篇就把这个把戏的里子拆开,讲讲记忆系统到底怎么搭。
一、为什么 Agent 天生失忆
先纠正一个直觉错误。很多人以为模型聊着聊着就「记住」了,其实完全没有。
LLM 本质上是个无状态函数:给它一段 prompt,它吐一段 completion,调用之间毫无关联。这次调用不知道上次发生了什么,就像每天醒来都失忆的电影主角。
你在 ChatGPT 里感受到的「上下文」,是客户端每轮都把整段历史拼进 prompt 重新发给模型。模型每次都从头读一遍你们的全部聊天记录,然后装作记得你的样子。
这个设计带来两个硬伤。
第一,context window 装不下无限历史。窗口就那么大,聊得久了,最早的内容只能扔掉。扔掉的那一刻,模型就真的忘了。
第二,就算装得下,模型对长上下文中间部分的注意力会衰减。圈里管这个叫 Lost in the Middle:你给模型喂 100 条历史,它对开头和结尾记得牢,中间的经常像没看过一样。所以哪怕历史全在窗口里,该忘还是忘。
所以结论很直白:记忆不是模型的能力,是你要写的工程。模型是那个没有硬盘的 CPU,内存和硬盘得你来配。
二、三层记忆:桌面、笔记本、档案柜
业界对记忆系统的划分基本收敛到三层,我用人脑的方式给你类比。
工作记忆(Working Memory),就是当前 context window 里的东西。像你办公桌上摊开的文件,伸手就能够到,处理速度最快,但桌面就那么大,堆不下太多。
短期记忆(Session Memory),是本次会话的压缩历史和状态。像你手边的笔记本,桌面放不下的草稿记在本子上,今天之内随时翻,明天可能就换新本子了。
长期记忆(Long-term Memory),是跨会话持久化的知识库。像公司的档案柜,真正的知识沉淀,存进去就不丢,但取用要按流程走,也就是检索。
这三层不是孤立的,关键是它们之间会流动:工作记忆装不下了,旧内容压缩进短期记忆;短期记忆里发现重要事实,归档进长期记忆;新对话开始时,从长期记忆里检索出相关内容,取回工作记忆。
记忆系统的大部分复杂度,就在这个流动的规则上:什么该压缩、什么该归档、什么该取回。后面逐个拆。
三、工作记忆:上下文窗口是稀缺资源
工作记忆管理的核心矛盾一句话就说完了:窗口有限,对话无限。主流解法就两个,滑动窗口和摘要压缩,通常组合着用。
3.1 滑动窗口:最简单但只配兜底
只保留最近 N 轮,更早的直接丢。代码没什么技术含量:
def sliding_window(messages, max_turns=10):
system = [m for m in messages if m[“role”] == “system”]
turns = [m for m in messages if m[“role”] != “system”]
return system + turns[-max_turns * 2:] # 一轮 = 一问一答两条
粗暴但有效,适合短平快的场景:FAQ 机器人、单次任务助手,用户也不指望你记得三小时前的话。
问题也明显:被裁掉的内容里如果有关键信息,比如用户第一轮报的手机号,后面就真的丢了。所以滑动窗口只能兜底,不能当主力。
3.2 摘要压缩:目前的主力方案
思路是:对话变长后,把早期内容让 LLM 压缩成一段摘要,摘要加最近几轮组成新的上下文。
SUMMARY_PROMPT = “”"把下面的对话历史压缩成摘要,保留:
- 用户的身份、偏好、明确要求
- 已确认的关键事实和结论
- 未解决的待办事项
控制在 200 字以内。
对话:
{history}“”"
def compress_context(messages, llm, keep_recent=4):
# 消息数没超阈值就不压缩
if len(messages) <= keep_recent * 2 + 1:
return messages
system = messages[0] old\_turns = messages[1:-keep\_recent \* 2] recent = messages[-keep\_recent \* 2:] history\_text = "\n".join( f"{m['role']}: {m['content']}" for m in old\_turns ) summary = llm(SUMMARY\_PROMPT.format(history=history\_text)) return [system, {"role": "system", "content": f"此前对话摘要:{summary}"}, \*recent]压缩后的上下文结构变成这样:
[system prompt]
[system: 此前对话摘要] ← 200 字,浓缩了几十轮
[user / assistant × 最近4轮] ← 原文保留,细节不丢
这个结构的巧妙在于摘要保「质」、原文保「鲜」。早期交互的价值在于沉淀下来的结论和偏好,200 字摘要足够承载;最近几轮的价值在于细节和语气,必须保留原文。
实测数据:一个 30 轮的客服对话,原文约 8000 token,压缩后摘要 260 token 加最近 4 轮约 1200 token,总量砍掉 82%,而关键信息,用户诉求、订单号、情绪倾向,一个没丢。
摘要策略有两个坑提醒一下。一是压缩触发时机:别等窗口快爆了才压,建议水位到 60%-70% 就动手,不然压缩这个动作本身的输入也会顶到上限。二是摘要要滚动更新:每压缩一次,新摘要要基于旧摘要加新内容生成,而不是把旧摘要当普通历史再压一遍。套娃式压缩会让信息层层失真,压三轮下来关键事实就面目全非了。
四、短期记忆:让会话断得开、接得上
短期记忆解决的是另一个问题:进程挂了、连接断了、用户明天回来继续聊,上下文从哪来?
答案是把对话状态持久化。LangGraph 里管这个叫 checkpointer,名字唬人,本质就是一张会话表:
import sqlite3, json, time
class SessionStore:
def __init__(self, db=“sessions.db”):
self.conn = sqlite3.connect(db)
self.conn.execute(“”“CREATE TABLE IF NOT EXISTS sessions(
thread_id TEXT PRIMARY KEY,
updated REAL,
state TEXT)”“”)
def save(self, thread\_id, messages): self.conn.execute( "REPLACE INTO sessions VALUES (?,?,?)", (thread\_id, time.time(), json.dumps(messages, ensure\_ascii=False))) self.conn.commit() def load(self, thread\_id): row = self.conn.execute( "SELECT state FROM sessions WHERE thread\_id=?", (thread\_id,)).fetchone() return json.loads(row[0]) if row else []关键在thread_id这个设计。每个用户、每个会话一个独立 ID,状态按 ID 隔离存取。用户 A 三天前的咨询、用户 B 刚开的对话,互不干扰。
接到 Agent 主循环里就三行:
messages = store.load(thread_id) # 进来先恢复
… 跑一轮对话 …
store.save(thread_id, messages) # 出去前落盘
生产上 SQLite 换成 Redis 或 Postgres 就行,逻辑一模一样。别小看这三行,有了它,服务重启、负载均衡换机器、用户换设备,全都不怕。
五、长期记忆:真正的杀手锏
前面两层管的都是「别忘了当前这场对话」。长期记忆才是跨对话的:用户上周的偏好、上个月的投诉、半年前的承诺。这层做好了,Agent 才谈得上越用越懂你。
5.1 记什么:三种长期记忆
认知科学把人的长期记忆分成几类,搬到 Agent 上意外地合适。
语义记忆:事实和知识。「用户叫张三」「公司报销上限 5000」「产品 A 不支持 IE」。用得最多的一类,回答「是什么」。
情景记忆:带时间戳的事件。「3 月 2 日用户因物流延迟投诉过一次,最后补偿了优惠券」。回答「发生过什么」。对客服场景特别重要,用户再次上门时你能说一句「上次您反馈的物流问题,后来解决了吗」,体验完全不同。
程序性记忆:流程和技能。「处理退款要先查订单状态再核实支付流水」。回答「该怎么做」,通常从成功和失败的历史里提炼。
三类记忆可以共用一个存储,用 type 字段区分就行。真正难的不是存,是下面两个问题:什么时候写,什么时候读。
5.2 记忆写入:显式加隐式
显式写入最简单:用户直接说「记住,我对花生过敏」,你解析指令存下来。好办,但指望用户主动交代不现实,90% 的重要信息藏在日常对话里。
所以主力是隐式提取:每轮对话结束后,让 LLM 判断这一轮有没有值得记的东西。
EXTRACT_PROMPT = “”"从下面这轮对话中,抽取值得长期记住的信息。
只记四类:
- 用户偏好(喜欢/讨厌/习惯)
- 身份信息(姓名/角色/联系方式)
- 明确的业务事实(订单号/约定/承诺)
- 用户纠正过的错误认知
没有就输出 NONE。每条一行,格式:[类型] 内容
用户:{user}
助手:{assistant}“”"
def maybe_remember(user_msg, agent_msg, store, llm):
result = llm(EXTRACT_PROMPT.format(user=user_msg, assistant=agent_msg))
if “NONE” in result:
return
for line in result.strip().split(“\n”):
line = line.strip()
if not line:
continue
importance = 0.9 if line.startswith(“[身份”) else 0.7
store.add(text=line, importance=importance, ts=time.time())
注意 prompt 里「只记四类」和「没有就输出 NONE」这两句。不写清楚,模型会把「今天天气不错」这种废话也当宝贝存下来,记忆库很快就变成垃圾场。
5.3 记忆读取:相关性 × 新近性 × 重要性
存了几百条记忆后,不能全塞进 prompt。检索时给每条记忆打分,只取 Top-K。评分公式借鉴认知心理学的记忆强度模型:
score = α·相关度 + β·新近性 + γ·重要性
import math, time
def recency(mem, now, half_life_days=30):
days = (now - mem.last_access) / 86400
return math.exp(-0.693 * days / half_life_days) # 0.693 = ln2
def retrieve(store, query, top_k=5, a=0.6, b=0.2, c=0.2):
q_emb = embed(query)
now = time.time()
scored = []
for m in store.all():
s = (a * cosine_sim(m.embedding, q_emb)
+ b * recency(m, now)
+ c * m.importance)
scored.append((s, m))
scored.sort(key=lambda x: -x[0])
for _, m in scored[:top_k]:
m.last_access = now # 被想起的记忆,更新新近性
return [m for _, m in scored[:top_k]]
三个因子各有分工。相关度保证当前聊什么取什么;新近性让最近用过的记忆优先,心理学里叫用进废退,被频繁想起的记忆会越记越牢,所以代码里取中后要刷新 last_access;重要性是写入时打的标,保证「用户身份证」这种低频但关键的记忆不会因为聊得少而被挤掉。
权重怎么调?我的默认值是 0.6 / 0.2 / 0.2,相关性主导。如果你的场景用户偏好变化快,比如电商导购,把 β 调大点,让新记忆更快盖过旧记忆。
5.4 记忆更新:冲突怎么办
用户是会变卦的。三月说「我不吃辣」,六月说「最近开始吃辣了」。旧记忆不处理,库里就同时躺着两条矛盾事实,检索出来哪个全看运气。
写入时做一次冲突检测:
CONFLICT_PROMPT = “”“旧记忆:{old}
新事实:{new}
两者关系是「矛盾」还是「补充」还是「无关」?只答一个词。”“”
def add_with_conflict_check(store, new_fact, llm):
for old in store.search(new_fact, top_k=3):
verdict = llm(CONFLICT_PROMPT.format(old=old.text, new=new_fact))
if verdict.strip().startswith(“矛盾”):
store.delete(old.id) # 新事实覆盖旧记忆
store.add(new_fact)
思路就是写之前先查,查到矛盾就删旧存新。这个习惯能让你的记忆库长期保持自洽,不然用一个月后,库里全是自相矛盾的鬼故事。
六、我踩过的四个坑
理论讲完了,说点实战里交过的学费。
坑一:写入泛滥。最早的版本我不设防,LLM 每轮抽三五条往里存,两周后库里 4000 多条,一检索回来一堆「用户说过谢谢」「用户说了再见」。后来加了两道闸:提取 prompt 限定四类,重要性低于 0.5 的不入库。记忆贵在精不在多。
坑二:幻觉入库。有次排查发现库里躺着一条「用户是糖尿病患者」,但翻遍对话记录,用户从没说过。哪来的?模型在某轮回复里自己脑补了一句,下一轮提取时把它当事实抽出来存了。教训:提取时只看用户说的话,或者给每条记忆打上来源标记,user_stated 还是 agent_inferred,检索时优先用 user_stated。
坑三:忘了「忘记」。记忆库只进不出,三年后用户问「我女儿生日该准备什么」,Agent 还按三年前的年龄推荐礼物。情景记忆要定期衰减:重要性低于阈值且超过 N 天没被访问的,归档或删除。
坑四:隐私裸奔。手机号、身份证、住址全明文进了向量库,还被 embedding 成了向量。合规上这是大事。我现在的做法是入库前过一道脱敏层:正则扫身份证、手机号、银行卡号,命中就打码或拒绝入库。
七、完整案例:把三层记忆接进 Agent
把前面的零件装起来,一个带完整记忆的 Agent 主循环长这样:
import threading
def agent_with_memory(user_input, thread_id, store, sessions, llm):
# 1. 恢复短期记忆
messages = sessions.load(thread_id) or [SYSTEM]
# 2. 检索长期记忆,注入上下文 memories = retrieve(store, user\_input, top\_k=5) if memories: mem\_text = "\n".join(f"- {m.text}" for m in memories) messages.append({"role": "system", "content": f"关于这个用户,你记得:\n{mem\_text}"}) # 3. 正常跑一轮对话 messages.append({"role": "user", "content": user\_input}) reply = llm(messages) messages.append({"role": "assistant", "content": reply}) # 4. 工作记忆压缩(防窗口爆炸) messages = compress\_context(messages, llm, keep\_recent=4) # 5. 短期记忆落盘 sessions.save(thread\_id, messages) # 6. 长期记忆提取(异步做,别挡响应) threading.Thread(target=maybe\_remember, args=(user\_input, reply, store, llm)).start() return reply六个步骤对应三层记忆的全部流转:进来时恢复短期、取回长期,跑完压缩工作记忆、落盘短期、提炼长期。这个骨架 30 来行,但已经是一个能投产的记忆系统雏形。
第 6 步注意我开了线程:记忆提取不挡主响应。用户等的是回复,不是你记笔记。生产上这步通常丢消息队列,慢归慢,反正用户无感。
实际效果:第一轮用户说「我对花生过敏,帮我看看这个零食能吃吗」,助手回答并存下偏好。三天后新会话,用户问「推荐点宵夜」,检索层把「对花生过敏」捞出来注入上下文,推荐自动避开花生制品。全程用户没再提过一次过敏的事。记忆的价值就在这:说一次,终身有效。
八、怎么评估:记忆系统也要考试
记忆系统上线前,我习惯给它出套卷子。做法很直接:先往库里植入 20 条事实,模拟历史对话灌进去的,然后开新会话问依赖这些事实的问题,看能不能答对。
评估集要覆盖三种考法:
直接回忆
:植入「用户对花生过敏」,问「推荐零食时要避开什么」
多跳推理
:植入「用户是素食者」加「用户不吃香菜」,问「推荐一家餐厅」
时效判断
:先植入「预算 3000」,后植入「预算改成 5000」,问「按什么预算推荐」。这种考的就是冲突处理
两个核心指标:记忆命中率(该记起来的记起来没)和错误召回率(不该记的瞎记了多少)。后者更容易被忽略,但对体验的杀伤力更大。Agent 信誓旦旦引用一个错误记忆,比它不记得还让用户反感。
学术界的 LongMemEval 基准也是这个思路,专门测聊天助手的长期记忆能力,五大类 500 个问题,可以拿来给自己的系统跑分。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~