一直以来,我对Agent记忆这个概念都感觉像是雾里看花。😶🌫️
最近在学习底层实现后我才发现:
相比于看技术文档或二手拆解文章,直接看代码带来的理解冲击力是完全不同的。
这篇文章,就是我从代码底层视角出发,对理解Agent记忆机制的一次复盘。文章组织如下:
-
• 记忆的本质 -
• Chatbot的长短期记忆机制 -
• Agent记忆的核心差异 -
• 多Agent记忆设计思路
如果对多轮对话以及Function call的基础实现已经很熟悉,可以直接跳到第二部分
一、记忆的本质:拼接和重传
1、多轮对话
首先,为什么ChatGPT能接上话?
因为在这个对话系统的后台,有一个中间人🧑💻(应用程序代码),它把你之前说过的所有话,打包成一个长长的列表,每一次都完整地塞回给模型。
这就像是你和一个失忆症患者聊天,每次你想问新问题,都必须把之前的聊天记录打印出来递给他,他看完这叠厚厚的纸,才能回答你的新问题。
用户问“北京天气怎么样?” -> 系统发送 [User: 北京天气] -> 模型回答。
第二轮:
用户问“那上海呢?”-> 系统必须发送 [User: 北京天气, AI: ...(上一轮AI的回复), User: 那上海呢?]。
第N轮:
系统发送 [User: ..., AI: ..., (无数轮)..., User: 新问题]。
2、工具调用
上面是 LLM 基于自身的知识对话的情况,当
涉及到工具调用的时候,这时的工作流程是怎么样的呢?是模型和人一样去根据url访问网页,获得结果然后回复吗?
其实不是这样的~👇
第一回合:LLM的决策时刻
(1) 定义工具
在代码里,需要定义一个 tools 列表(JSON Schema),告诉LLM:“有一个叫 get_weather 的工具,需要 latitude 和 longitude 两个参数”。
👉 注意:这时候什么都没发生,只是定好了规则。
(2) 发起第一轮请求
用户问:“What's the weather like in Paris today?”
程序把这句话封装进 messages 列表发给LLM
关键点:在 client.chat.completions.create 中,同时传入了 messages 和 tools
(3) LLM的思考与停顿
👆 对应流程图中的「需要调用函数?」菱形判断框。
LLM收到请求后,发现自己不知道巴黎实时的天(知识截止),但它发现手边的 get_weather 的工具
这时LLM不会直接回答问题,而是返回一个特殊的指令。👇
(4)第一轮输出(JSON Output)
LLM返回的内容不是自然语言,而是一段JSON数据,大概长这样:
{
"name":"get_weather",
"arguments":
"{
'latitude': 48.85,
'longitude': 2.35
}"
}
👉 这一步非常关键:
LLM到此为止就收工了。它没有去查天气,它只是生成了一个条子,上面会写着要调用什么函数,以及调用这个函数所需的信息参数。
中场休息:Python程序的跑腿时刻
这就到了程序执行环节。这里完全没有LLM的事,全是Python代码在干活:
-
1. 解析字符串:代码 json.loads()把LLM给的字符串解析成参数。 -
2. 执行函数:代码执行 get_weather()这一步就是根据模型提供的信息去调外部API。 -
3. 拿到结果:程序拿到了 14.2°C这个结果。
第二回合:带上结果,重新排队
这一步就是把历史记录全部重新塞给LLM。为了让LLM能够根据查询结果回答用户,必须构造一个新的上下文。
(1)追加LLM提供的信息:
messages.append(completion.choices[0].message)
必须把第一回合LLM说的“我要调用工具...”这句话记入历史,否则LLM待会会因为不知道自己发起了调用而感到莫名其妙。
(2)追加Tool的结果:
messages.append({ "role": "tool", "content": "14.2°C" })
这里把Python跑出来的 14.2°C 塞进了历史记录。
(3)发起第二轮请求:
再次调用 client.chat.completions.create。此时发给LLM的是三条信息:
User: 巴黎天气咋样?
Assistant: (发出指令)调用 get_weather 参数是巴黎...
Tool: 14.2°C
👉 LLM看到这三句话,终于恍然大悟:"原来我查到了是 14.2 度。"
(4)最终输出
LLM生成自然语言回复:“The current temperature in Paris is 14.2°C.”
👇 总结一下,Function Call的本质就是
暂停 -> 外部执行 -> 拼接历史 -> 重发
如果把这里的 get_weather 换成 save_memory(写入记忆)或 search_memory(读取记忆),原理是一模一样的:
-
1. Agent Memory 不是大模型脑子里的东西。 -
2. 它是大模型在第一回合发出 search_memory 的指令(JSON)。 -
3. Python程序去数据库里捞出数据。 -
4. Python程序把捞出的记忆伪装成 tool 的消息,追加到对话历史里。 -
5. 大模型在第二回合看到这条历史,才想起了这件事。
💡 在很多 Memory 设计里,search_memory 这一步就是一个小型的RAG:用当前对话作为query,在外部记忆库中做语义检索,再把结果拼回上下文。
二、普通 Chatbot 的记忆机制
所谓的拥有记忆,在代码层面,其实就是像上述那样一次次笨拙但有效的拼接和重传过程。记忆本质上就是一个特殊的检索源。
要在工程上实现记忆需要解决:存什么、存哪儿、什么时候读、什么时候忘
👇 拿一个RAG架构的企业问答助手为例~
1、核心架构:双重检索机制
在引入记忆模块后,RAG的工作流就不再是单线条的了,而是变成了一个闭环系统。最核心的变化在于引入了双重检索。
-
1. 静态知识检索:去向量数据库里查企业文档、Wiki等固定知识。 -
2. 动态记忆检索:去记忆模块里查对话历史、用户偏好、中间推理结果等动态上下文。
此时工作流变成了这样:
用户提问 -> 双重检索 (知识库+记忆库) -> 信息融合 -> 模型生成 -> 记忆更新 (写入新一轮对话)
这种架构让模型既有博学的一面(查资料)🧐,又有贴心的一面(查过往)🥰
2、 存储层:记忆放在哪里?
这并不是一个简单的存数据库就能解决的,根据时效性,通常采用组合方案:
(1)短期记忆:内存/键值存储 (Redis)
-
• 场景:就像人的工作记忆,只记最近几轮对话,随用随取。 -
• 技术:Redis等内存数据库。 -
• 优点:速度极快,延迟低,适合高频读写。通常通过Session ID来存取。 -
• 缺点:容量有限,非持久化。一旦会话结束或超时,可能就清空了。而且只能按Key查,不能按语义查。
(2)长期/语义记忆:向量数据库 (Vector DB)
-
• 场景:就像人的长期回忆,比如你问“我上次说的那个项目方案是什么?”,模型需要跨越几十轮对话去捞取信息。 -
• 技术:向量数据库Pinecone, Weaviate等。 -
• 优点:支持语义搜索。把记忆转化成向量。当用户说话时,系统计算语义相似度,去库里捞出语义相关的往事 。 -
• 缺点:这种方式能实现长程回忆,但引入了额外的计算开销(Embedding+搜索),且可能偶尔检索出语义相似但语境不相关的错误记忆。
(3)用户画像:NoSQL数据库
-
• 场景:存用户画像、历史决策日志等结构化数据。 -
• 技术:MongoDB, DynamoDB。 -
• 作用:作为兜底的持久化存储,容量大且便宜,适合存那些不需要频繁语义检索的档案信息。适合存储结构化、持久的信息。比如构建用户画像来提炼用户的个性,让模型在任何时候都能调取背景偏好。
3、管理层:如何避免脑容量爆炸?🤯
这是最需要关注的地方。如果把所有对话一股脑都塞给大模型,Token 费用会爆炸,模型也会因为上下文太长而变笨。
👇 所以需要记忆管理策略:
(1)选择性遗忘
人类的大脑会遗忘,AI也要学。
-
• 滑动窗口:最简单粗暴,只保留最近N轮,旧的直接扔掉。 -
• 艾宾浩斯遗忘曲线:模拟人类记忆规律,对很久没被提及(检索)的记忆降低权重,慢慢淡忘;对经常被提起的记忆进行加固。
(2)层次化摘要
别让模型读几十页的聊天记录,给它看摘要。
-
• 操作:定期将详细的对话日志提炼成简短的要点。 -
• 效果:既保留了事情的梗概,又节省了Token。新对话发生时,模型看的是「近期详细记录 + 远期摘要」。
(3)用户画像持久化
这是实现个性化的关键。🌟
-
• 操作:从对话中提取事实。比如用户说“我海鲜过敏”,系统将其转化为结构化数据 {Topic: Food, Preference: No Seafood}存入专门的静态记忆块。 -
• 好处:无论过了多久,只要涉及吃饭场景,这个画像就会被调出来注入 Prompt,无需重复检索海量历史。
三、单 Agent 的记忆机制:从说话到做事
前面讲述的普通 Chatbot 记忆(向量库+Redis),解决的核心问题是如何像人一样聊天。 但Agent和Chatbot有两个关键差异,决定了它不能只靠对话历史:
-
1. 它需要多步推理。 -
2. 它的一次任务可能跨越很多轮,涉及多次工具调用。
所以,Agent 的记忆设计,必须在对话内容之外,额外增加两类核心记忆:
(1)行为记忆(工具调用日志)
👉 普通Chatbot记的是“你说了什么,我说了什么”,但Agent必须记住“我做过什么”。
-
• 痛点:如果Agent不记得自己调用过
Google Search 且失败了,它可能会在一个死循环里不断重试。 -
• 解决:需要记录 (tool_name, input, output, success_flag)。 -
• 进阶:在任务结束后,将踩坑经验写入长期向量库。这样下次遇到类似任务,Agent不仅记得用户说过什么,还能记得「上次怎么做才成功」。
(2)任务级临时记忆
👉 这是Agent的草稿纸
-
• 场景:比如写代码,Agent需要拆解步骤:Step 1 建库,Step 2 写接口... -
• 区别:这些中间思考过程对于任务执行至关重要,但对于用户来说是噪音。 -
• 策略:为每个 task_id 维护独立的短期记忆区。 -
• 任务进行中:完整保留,确保逻辑连贯。 -
• 任务结束后:只提炼关键结论存入长期记忆,把草稿纸直接扔掉,防止Token爆炸。
👇 基于上述差异,一个成熟的单Agent存储布局应该是以下的组合:
-
• 短期记忆 (Redis):存当前任务的「思考链、工具调用结果」 -
• 长期记忆 (Vector DB):存「用户事实、反思、最佳实践」 -
• 持久化 (NoSQL):存结构化的「用户配置、工具日志」
👇 读写策略可以总结为以下核心内容:
写入:
-
• 每轮对话:抽取新的事实,并实时更新用户画像。 -
• 任务结束:生成总结,丢弃过程草稿,沉淀经验。
读取:
-
• 新任务启动:按「用户ID+任务类型」检索,加载背景偏好与历史同类任务摘要。 -
• 任务执行中:按「当前子任务语义」检索,动态查找历史错题集与最佳实践。
四、多Agent记忆机制
在搞懂了单Agent的拼接重传机制后,多Agent的记忆管理其实就很好理解了。
多Agent的记忆,本质上是在前面那套「用户、任务、Agent」记忆体系上,再加一维:角色(agent_id)+ 作用域(scope)
👇 可以拆成两个问题来想:
-
1. 每个Agent自己的记忆如何隔离? -
2. 哪些东西要共享,怎么共享才不会乱?
所以,多Agent记忆设计的核心,本质上就是在单Agent的基础上,加了一个权限管理的维度。
1、记忆的作用域:给记忆打上标签
在代码层面,要在多Agent场景下复用我们之前的记忆系统,其实只需要在数据库里多加几个字段(Tag)。可以把记忆分成这四层:
-
• Global(全局知识):公司的图书馆。产品文档、API手册,所有人随时都能查,基本不怎么变。 -
• User(用户画像):客户档案。“用户只用 Python”、“用户是金融背景”,这些信息对于Planner、Coder都是通用的,谁接待用户谁就能看到。 -
• Task(任务白板):会议室的白板。这是多Agent协作的灵魂。当大家为了同一个 task_id 努力时,Planner写下的计划、Researcher查到的关键摘要,都要写在这个scope里。所有参与这个任务的Agent都能读到白板上的内容,这样Coder才知道Planner到底规划了什么。 -
• Agent(私有笔记):员工的私人笔记本。这是最容易被忽视的一点。Planner可以在这里记下“最近这类的任务拆解容易出错”,Coder可以记下“这个库的某个函数有问题”。关键点:Coder的私人笔记,Planner不需要看,也看不懂。隔离这些噪音,能极大节省Token。
2、怎么存?其实就是多加一列ID
不管是存Redis还是向量库,本质上就是在每一条记忆上打标签。
👉 以前单Agent可能长这样:{ content: "...", time: "..." }
👇 多Agent只需要扩展成:
{
"content": "...",
// 属于哪个用户
"user_id": "u_001",
// 属于哪个任务(如果是跨任务记忆则为空)
"task_id": "t_999",
// 谁写的
"agent_id": "coder_a",
// 作用域:user / task / agent
"scope": "task"
}
3、一个协作的例子(带入场景)
下面用一个简单形象的流程说明一下三个 Agent(Planner、Researcher、Coder)是怎么配合的:
第一步:Planner 入场
-
• 它向记忆模块发起查询: scope=user + scope=agent(planner) -
• 它看到了用户说“我要写Python”,也看到了自己以前总结的“拆解报表任务的模版”。 -
• 它写下一份 Plan,标记为 scope = task。这意味着这份 Plan 被贴到了会议室白板上。
第二步:Researcher 入场
-
• 它不需要看 Planner 的私有经验,它只需要看白板( scope = task)。 -
• 它发现白板上写着:Step 1: 查一下某 API 文档。 -
• 于是它去执行,查完后,把结果摘要也贴回白板( scope = task)。
第三步:Coder入场
-
• 轮到它干活时,它看一眼白板,上面已经有了Plan和API文档摘要。 -
• 它结合这些信息写出代码。如果报错了,它把这个坑记录在自己的 scope = agent里,下次它再遇到类似问题就能避坑,但不会去打扰 Planner。
👇 总结一下~
所以,多 Agent 记忆机制并不是什么玄学的群体意识,它在工程上就是一个带权限控制的读写系统。
-
• 隔离(依靠 agent_id)是为了减少噪音,让每个Agent专注自己的专业领域。 -
• 共享(依靠 task_id 和 user_id)是为了对齐目标,确保大家在一个频道上对话。
最后
理解了这些底层的拼接逻辑,我们在设计产品时才能更准确地预估Token成本、更合理地设计记忆策略
记忆不是越长越好,而是越准越好。
希望我们也可以找准自己的定位,能够在曲折的路途中遇到任何问题都可以坚定自我地走下去 😊

