晨光里的AI

动手落地AI

深度解析Memory本质:Agent和Chatbot的差异

从代码底层看 Agent 记忆:所谓记忆不过是拼接和重传。拆解多轮对话、Function Call、Chatbot 的存储与遗忘策略,以及 Agent 与多 Agent 的记忆设计。

Written by 晨旭发布于 约 12 分钟同步发布

TL;DR

一直以来我对 Agent 记忆这个概念都像雾里看花,直到去看底层代码实现。 这篇从代码视角复盘:多轮对话的本质是每次把完整历史重新塞回模型,Function Call 的本质是「暂停→外部执行→拼接历史→重发」, 而 save_memory / search_memory 与调用天气 API 在原理上没有任何区别——记忆不在模型脑子里,它只是一个特殊的检索源。 在此之上,Chatbot 靠 Redis + 向量库 + NoSQL 的组合解决「存什么、存哪儿、何时读、何时忘」; Agent 还要额外增加行为记忆和任务级草稿纸;多 Agent 则是在这套体系上加一维「角色 + 作用域」的权限控制。

目录

一直以来,我对 Agent 记忆这个概念都感觉像是雾里看花。

最近在学习底层实现后我才发现:相比于看技术文档或二手拆解文章,直接看代码带来的理解冲击力是完全不同的。

这篇文章,就是我从代码底层视角出发,对理解 Agent 记忆机制的一次复盘。(如果对多轮对话以及 Function Call 的基础实现已经很熟悉,可以直接跳到第二部分)

本文会依次讲四件事:记忆的本质、Chatbot 的长短期记忆机制、Agent 记忆的核心差异、多 Agent 记忆设计思路。

记忆的本质:拼接和重传

多轮对话

首先,为什么 ChatGPT 能接上话?

因为在这个对话系统的后台,有一个中间人(应用程序代码),它把你之前说过的所有话,打包成一个长长的列表,每一次都完整地塞回给模型。

这就像是你和一个失忆症患者聊天,每次你想问新问题,都必须把之前的聊天记录打印出来递给他,他看完这叠厚厚的纸,才能回答你的新问题。

第一轮:用户问「北京天气怎么样?」
-> 系统发送 [User: 北京天气] -> 模型回答。
第二轮:用户问「那上海呢?」
-> 系统必须发送 [User: 北京天气, AI: ...(上一轮AI的回复), User: 那上海呢?]。
第 N 轮:系统发送 [User: ..., AI: ..., (无数轮)..., User: 新问题]。

工具调用

上面是 LLM 基于自身的知识对话的情况。当涉及到工具调用的时候,这时的工作流程是怎么样的呢?是模型和人一样去根据 URL 访问网页,获得结果然后回复吗?

其实不是这样的。(在我深入学习代码之前,我就是这么觉得的——或者说,我都没有仔细去想过这中间的过程。)

第一回合:LLM 的决策时刻

(1)定义工具

在代码里,需要定义一个 tools 列表(JSON Schema),告诉 LLM:「有一个叫 get_weather 的工具,需要 latitudelongitude 两个参数」。

注意:这时候什么都没发生,只是定好了规则。

(2)发起第一轮请求

用户问:「What’s the weather like in Paris today?」

程序把这句话封装进 messages 列表发给 LLM。关键点:在 client.chat.completions.create 中,同时传入了 messagestools

(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 代码在干活:

  • 解析字符串:代码 json.loads(...) 把 LLM 给的字符串解析成参数。
  • 执行函数:代码执行 get_weather(...)。这一步就是根据模型提供的信息去调外部 API。
  • 拿到结果:程序拿到了 "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(读取记忆),原理是一模一样的:

  • Agent Memory 不是大模型脑子里的东西。
  • 它是大模型在第一回合发出 search_memory 的指令(JSON)。
  • Python 程序去数据库里捞出数据。
  • Python 程序把捞出的记忆伪装成 tool 的消息,追加到对话历史里。
  • 大模型在第二回合看到这条历史,才想起了这件事。

在很多 Memory 设计里,search_memory 这一步就是一个小型的 RAG:用当前对话作为 query,在外部记忆库中做语义检索,再把结果拼回上下文。

普通 Chatbot 的记忆机制

所谓的拥有记忆,在代码层面,其实就是像上述那样一次次笨拙但有效的拼接和重传过程。记忆本质上就是一个特殊的检索源。要在工程上实现记忆需要解决四个问题:存什么、存哪儿、什么时候读、什么时候忘

下面拿一个 RAG 架构的企业问答助手为例。

一、核心架构:双重检索机制

在引入记忆模块后,RAG 的工作流就不再是单线条的了,而是变成了一个闭环系统。最核心的变化在于引入了双重检索。

  • 静态知识检索:去向量数据库里查企业文档、Wiki 等固定知识。
  • 动态记忆检索:去记忆模块里查对话历史、用户偏好、中间推理结果等动态上下文。

此时工作流变成了这样:

用户提问 -> 双重检索(知识库 + 记忆库) -> 信息融合 -> 模型生成 -> 记忆更新(写入新一轮对话)

这种架构让模型既有博学的一面(查资料),又有贴心的一面(查过往)。

二、存储层:记忆放在哪里?

这并不是一个简单的存数据库就能解决的,根据时效性,通常采用组合方案。

(1)短期记忆:内存 / 键值存储(Redis)

  • 场景:就像人的工作记忆,只记最近几轮对话,随用随取。
  • 技术:Redis 等内存数据库。
  • 优点:速度极快,延迟低,适合高频读写。通常通过 Session ID 来存取。
  • 缺点:容量有限,非持久化。一旦会话结束或超时,可能就清空了。而且只能按 Key 查,不能按语义查。

(2)长期 / 语义记忆:向量数据库(Vector DB)

  • 场景:就像人的长期回忆,比如你问「我上次说的那个项目方案是什么?」,模型需要跨越几十轮对话去捞取信息。
  • 技术:向量数据库,如 Pinecone、Weaviate。
  • 优点:支持语义搜索。把记忆转化成向量,当用户说话时,系统计算语义相似度,去库里捞出语义相关的往事。
  • 缺点:这种方式能实现长程回忆,但引入了额外的计算开销(Embedding + 搜索),且可能偶尔检索出语义相似但语境不相关的错误记忆。

(3)用户画像:NoSQL 数据库

  • 场景:存用户画像、历史决策日志等结构化数据。
  • 技术:MongoDB、DynamoDB。
  • 作用:作为兜底的持久化存储,容量大且便宜,适合存那些不需要频繁语义检索的档案信息。比如构建用户画像来提炼用户的个性,让模型在任何时候都能调取背景偏好。

三、管理层:如何避免脑容量爆炸?

这是最需要关注的地方。如果把所有对话一股脑都塞给大模型,Token 费用会爆炸,模型也会因为上下文太长而变笨。所以需要记忆管理策略。

(1)选择性遗忘

人类的大脑会遗忘,AI 也要学。

  • 滑动窗口:最简单粗暴,只保留最近 N 轮,旧的直接扔掉。
  • 艾宾浩斯遗忘曲线:模拟人类记忆规律,对很久没被提及(检索)的记忆降低权重,慢慢淡忘;对经常被提起的记忆进行加固。

(2)层次化摘要

别让模型读几十页的聊天记录,给它看摘要。

  • 操作:定期将详细的对话日志提炼成简短的要点。
  • 效果:既保留了事情的梗概,又节省了 Token。新对话发生时,模型看的是「近期详细记录 + 远期摘要」。

(3)用户画像持久化

这是实现个性化的关键。

  • 操作:从对话中提取事实。比如用户说「我海鲜过敏」,系统将其转化为结构化数据 {Topic: Food, Preference: No Seafood} 存入专门的静态记忆块。
  • 好处:无论过了多久,只要涉及吃饭场景,这个画像就会被调出来注入 Prompt,无需重复检索海量历史。

单 Agent 的记忆机制:从说话到做事

前面讲述的普通 Chatbot 记忆(向量库 + Redis),解决的核心问题是「如何像人一样聊天」。但 Agent 和 Chatbot 有两个关键差异,决定了它不能只靠对话历史:

  • 它需要多步推理。
  • 它的一次任务可能跨越很多轮,涉及多次工具调用。

所以,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)。

可以拆成两个问题来想:

  • 每个 Agent 自己的记忆如何隔离?
  • 哪些东西要共享,怎么共享才不会乱?

所以,多 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", // 谁写的
"scope": "task" // 作用域:user / task / agent
}

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_iduser_id)是为了对齐目标,确保大家在一个频道上对话。

最后

理解了这些底层的拼接逻辑,我们在设计产品时才能更准确地预估 Token 成本、更合理地设计记忆策略。

记忆不是越长越好,而是越准越好。

希望我们也可以找准自己的定位,能够在曲折的路途中遇到任何问题都可以坚定自我地走下去。

核心结论

标注「判断」「假设」的是我的看法而非事实;标注「已推翻」的保留在这里,不删除。

  • 所谓「大模型有记忆」,在代码层面就是每一轮都把完整历史重新拼接、重新发送给模型。模型本身始终是失忆的,记忆只是一个挂在外面的特殊检索源。#

    把握较大

  • Function Call 的本质是四步:模型暂停并输出一张调用条子,程序在外部执行,把结果伪装成 tool 消息追加进历史,再重发一次。把 get_weather 换成 search_memory,原理一模一样。#

    把握较大

  • Agent 和 Chatbot 的关键差异在于,Chatbot 只需记「谁说了什么」,Agent 还必须记「我做过什么」——否则会在失败的工具调用上陷入死循环。#

    判断

  • 多 Agent 记忆不是什么群体意识,工程上就是给每条记忆打上 user / task / agent 作用域标签的读写权限系统。隔离为了降噪,共享为了对齐。#

    判断

  • 记忆不是越长越好,而是越准越好。#

    判断 · 把握较大

引用本文

晨旭,《深度解析Memory本质:Agent和Chatbot的差异》,晨光里的AI,2025-11-28

[晨旭:《深度解析Memory本质:Agent和Chatbot的差异》](https://chenxu.xin/writing/agent-memory-essence)

带进你的 AI 继续追问

这篇文章有一份干净的 Markdown 原文,可以直接交给任何模型读,不用复制粘贴。