news 2026/4/21 6:13:29

【手搓 AI Agent 从 0 到 1】第七课:记忆——让 Agent 跨对话记住信息

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【手搓 AI Agent 从 0 到 1】第七课:记忆——让 Agent 跨对话记住信息

📌前置知识:已完成第一课至第六课
🎯本课目标:让 AI 在多次独立交互之间记住信息,不再每次都"失忆"
💡核心概念:记忆存储 / 记忆检索 / 上下文整合 / 显式记忆管理


前言

上节课,我们给 Agent 加了循环。它终于能在一次任务里反复思考、逐步推进了。

但有一个问题——

# 第一次调用agent.run_loop("我的名字叫小明",max_steps=3)# Agent 处理完毕,状态清零# 第二次调用agent.run_loop("我叫什么名字?",max_steps=3)# Agent:???

Agent 不知道你叫什么。因为在第二次调用时,AgentState被重置了,所有信息清空。它不记得上一次对话里发生了什么。

这不奇怪——第六课的状态只在一次循环内有效。循环一结束,state.reset()一调,全部归零。

但真实的助手不是这样的。你告诉 ChatGPT “我正在学 Python”,半小时后再问"我在学什么",它还记得。因为它有跨对话的记忆

这就是第七课要解决的问题。


一、上下文 vs 记忆:两件完全不同的事

在动手之前,先理清一个很容易混淆的概念。

1.1 上下文(Context)

上下文就是当前 Prompt 里的所有内容。

第二课的多轮对话就是上下文——你把历史消息存进self.history,每次调用时拼进 prompt:

messages=[{"role":"system","content":self.system_prompt}]messages.extend(self.history)# ← 这就是上下文messages.append({"role":"user","content":user_input})

AI 能"记住"这次对话的前几轮,靠的就是上下文。

但上下文是临时的。程序一重启,self.history就没了。即使程序不重启,一旦你调了clear_history(),记忆也全清了。

1.2 记忆(Memory)

记忆是跨对话持久化存储的数据。

和上下文的区别:

上下文(Context)记忆(Memory)
生命周期当前会话内跨会话持久化
存储位置内存中的列表独立的存储(文件/数据库/对象)
何时加载每次对话自动带上需要时主动检索加载
典型例子“刚才你说了 X”“上次你告诉我你喜欢 Python”

上下文是"短期记忆"——这次对话里的事,AI 知道。

记忆是"长期记忆"——上一次对话里的事,AI 还知道。

本课要做的就是:给 Agent 加上长期记忆。


二、记忆系统要解决什么?

一个记忆系统,不管简单还是复杂,都要回答三个问题:

2.1 存什么?

用户告诉 Agent 的事实。比如:

  • “我叫小明”
  • “我在学 Python”
  • “我喜欢用 Vim”
  • “我的项目 deadline 是下周五”

这些都是值得长期记住的信息。

2.2 怎么存?

最简单的方式:一个字符串列表。

classAgentMemory:def__init__(self):self.memories:list[str]=[]

就这样。一个列表,存字符串。不需要向量数据库,不需要 embedding,不需要语义检索。先从最简单的开始。

2.3 什么时候存?

这是最关键的设计决策。

方案 A:自动存——每次对话结束,把整段对话存起来。

简单,但低效。90% 的对话内容不值得长期记忆。"帮我写个排序函数"这件事,下次对话不需要知道。

方案 B:让 AI 自己决定存什么。

在 AI 的输出里加一个字段:save_to_memory。当 AI 认为某条信息值得长期记住时,把它放进去。如果没什么值得记的,就返回null

// 用户说 "我叫小明"{"reply":"你好,小明!","save_to_memory":"用户的名字是小明"}// 用户说 "帮我写个排序函数"{"reply":"好的,这是排序函数...","save_to_memory":null}

这就是本课采用的方式。AI 控制存储,你控制执行。和第五课"请求与执行分离"的思路一脉相承。


三、代码实现

3.1 记忆类:AgentMemory

打开agent/agent.py,找到新增的AgentMemory类:

classAgentMemory:""" 智能体记忆系统(第七课引入) 最简单的记忆实现:一个字符串列表。 存储 AI 认为值得长期记住的事实。 """def__init__(self):self.memories:list[str]=[]defadd(self,fact:str)->None:"""存储一条记忆"""iffactandfact.strip()andfactnotinself.memories:self.memories.append(fact.strip())print(f"🧠 存入记忆:{fact.strip()}")defget_all(self)->list[str]:"""获取所有记忆"""returnself.memories.copy()defsearch(self,keyword:str)->list[str]:"""按关键词搜索记忆(简单版)"""keyword=keyword.lower()return[mforminself.memoriesifkeywordinm.lower()]defremove(self,fact:str)->bool:"""删除一条记忆"""iffactinself.memories:self.memories.remove(fact)print(f"🗑️ 删除记忆:{fact}")returnTruereturnFalsedefclear(self)->None:"""清空所有记忆"""self.memories.clear()print("🧹 所有记忆已清空")defcount(self)->int:"""记忆条数"""returnlen(self.memories)

注意几个细节:

① 去重。add()里检查fact not in self.memories——同一条事实不会重复存储。

② 返回副本。get_all()返回self.memories.copy(),不返回原始列表的引用。外部代码修改返回值不会影响内部数据。

search()是简单版。只做了关键词匹配,没有语义检索。够用就行——复杂检索是以后的事。

3.2 带记忆的对话:run_with_memory()

defrun_with_memory(self,user_input:str)->Optional[dict]:""" 使用记忆上下文运行智能体(第七课核心方法)。 流程: 1. 从记忆中检索所有存储的事实 2. 将记忆拼入 Prompt,让 AI 看到 3. AI 根据记忆和用户输入生成回复 4. 如果 AI 决定存储新信息,自动保存到记忆 Args: user_input: 用户输入 Returns: 包含 reply 和 save_to_memory 的字典,失败则返回 None """memory_context=self.memory.get_all()# 构建记忆上下文字符串ifmemory_context:memory_str="你记住了以下关于用户的信息:\n"+"\n".join(f"-{item}"foriteminmemory_context)else:memory_str="你目前没有关于用户的记忆。"user_prompt=f"""你是一个有记忆能力的智能体助手。根据用户输入和你记住的信息来回复。{memory_str}规则: 1. 只返回有效的 JSON 2. 不要任何解释,不要 Markdown 3. 直接以 {{ 开头,以 }} 结尾 4. 如果用户告诉你新信息(比如名字、偏好、项目信息),请保存到记忆中 5. 如果用户问到你记得的信息,请使用记忆来回答 6. JSON 格式:{{"reply": "你的回复内容", "save_to_memory": "要记住的事实" 或 null}} 示例: - 用户说"我叫小明" → {{"reply": "你好,小明!", "save_to_memory": "用户的名字是小明"}} - 用户问"我叫什么"且你记得"用户的名字是小明" → {{"reply": "你叫小明", "save_to_memory": null}} - 用户说"帮我写个函数" → {{"reply": "好的...", "save_to_memory": null}} 用户输入:{user_input}请返回 JSON:"""forattemptinrange(3):response=self.client.chat.completions.create(model=self.model,messages=[{"role":"system","content":self.system_prompt},{"role":"user","content":user_prompt},],temperature=0.0,)text=response.choices[0].message.content parsed=extract_json_from_text(text)ifparsedand"reply"inparsed:# 如果 AI 决定保存新记忆,自动存入ifparsed.get("save_to_memory"):self.memory.add(parsed["save_to_memory"])returnparsedreturnNone

逐段拆解这段代码的设计考量:

① 记忆检索——self.memory.get_all()

每次对话前,先从记忆系统里取出所有存储的事实。目前是"全部取出",后续可以改成"按相关性检索"。

② 记忆拼入 Prompt

ifmemory_context:memory_str="你记住了以下关于用户的信息:\n"+"\n".join(f"-{item}"foriteminmemory_context)else:memory_str="你目前没有关于用户的记忆。"

模型不能直接访问你的记忆系统。它只能看到 Prompt 里的内容。所以你必须在每次对话时,把记忆"加载"到 Prompt 里——这和第六课把状态拼进 Prompt 是同一个思路。

③ AI 控制存储——save_to_memory字段

AI 决定是否存储,但真正执行存储的是你的代码:

ifparsed.get("save_to_memory"):self.memory.add(parsed["save_to_memory"])

又回到了那个老原则:AI 描述意图,你控制执行。AI 说"我要记住这件事",你的代码决定是否真的存进去(你可以加过滤、加校验、加审计)。

④ Few-shot 示例

Prompt 里给了两个示例,告诉 AI 什么时候该存、什么时候不该存。这对小模型(7B)尤其重要——没有示例,它可能什么都存或者什么都不存。

⑤ 老三样

  • JSON 输出 +extract_json_from_text()—— 第三课以来的标准操作
  • 重试 3 次—— 老规矩
  • temperature=0.0—— 记忆读写需要确定性

四、运行示例

4.1 基础场景:记住名字

fromagent.agentimportAgent agent=Agent(model="qwen2.5:7b")# 第一次交互:告诉 Agent 你的名字print("=== 对话 1 ===")r1=agent.run_with_memory("我叫小明,是一名后端开发工程师")ifr1:print(f"Agent:{r1['reply']}")# 第二次交互:问它记不记得print("\n=== 对话 2 ===")r2=agent.run_with_memory("你还记得我的名字和职业吗?")ifr2:print(f"Agent:{r2['reply']}")# 查看记忆内容print(f"\n🧠 当前记忆({agent.memory.count()}条):")forminagent.memory.get_all():print(f" -{m}")

预期输出(类似):

=== 对话 1 === 🧠 存入记忆:用户的名字是小明 🧠 存入记忆:用户是一名后端开发工程师 Agent: 你好小明!很高兴认识你,作为一名后端开发工程师... === 对话 2 === Agent: 当然记得!你的名字是小明,职业是后端开发工程师。 🧠 当前记忆(2 条): - 用户的名字是小明 - 用户是一名后端开发工程师

注意:两次run_with_memory()之间没有传递任何上下文。Agent 知道你叫小明,不是因为上一次对话的历史,而是因为记忆系统里存了这条事实。

4.2 累积记忆

# 第三次交互:告诉 Agent 更多信息print("=== 对话 3 ===")r3=agent.run_with_memory("我主要用 Python 和 Go,最近在学 AI Agent")ifr3:print(f"Agent:{r3['reply']}")# 第四次交互:综合提问print("\n=== 对话 4 ===")r4=agent.run_with_memory("帮我推荐一个学习路线,结合我的技术栈")ifr4:print(f"Agent:{r4['reply']}")# 查看全部记忆print(f"\n🧠 当前记忆({agent.memory.count()}条):")forminagent.memory.get_all():print(f" -{m}")

记忆会逐步累积。每条值得记住的信息都被 Agent 主动存入,后续对话中自动加载。

4.3 记忆管理

# 按关键词搜索记忆print("搜索 'Python':",agent.memory.search("Python"))# 删除某条记忆agent.memory.remove("用户是一名后端开发工程师")# 清空全部记忆agent.memory.clear()

五、与第六课的本质区别

把两课放在一起对比:

第六课(Agent Loop——循环内状态):

循环开始 → 步骤1 → 步骤2 → 步骤3 → 循环结束 → 状态清零 ↕ ↕ ↕ 状态共享 状态共享 状态共享

状态在循环内部共享,但循环一结束就没了。

第七课(记忆——跨对话持久化):

对话1:你说 "我叫小明" → Agent 存入记忆 ↓ 记忆系统 ["用户的名字是小明"] ↓ 对话2:你问 "我叫什么" → Agent 从记忆中加载 → "你叫小明" ↓ 对话3:你说 "我用 Python" → Agent 存入新记忆 ↓ 记忆系统 ["用户的名字是小明", "用户使用 Python"] ↓ 对话4:你问 "我会什么" → Agent 从记忆中加载 → "你会 Python"

记忆在完全独立的对话之间持久化。程序重启都没关系(只要你把记忆保存到文件里)。

第六课(状态)第七课(记忆)
生命周期循环内跨对话
存储方式内存中的对象独立的存储系统
谁控制读写代码自动管理AI 决定存什么,代码决定怎么存
重置时机每次循环开始手动或程序重启
典型内容步骤计数、动作历史用户偏好、个人信息、项目上下文

状态解决"这次任务做到哪一步了"。记忆解决"这个用户是什么样的"。


六、关键洞察

6.1 记忆是数据存储,不是思考

这是本课最重要的洞察:

记忆 = 数据存储。不是意识,不是推理,不是隐藏的认知能力。

它就是一个列表,里面存着字符串。你可以get_all()看到全部内容,可以remove()删除,可以clear()清空。没有什么"AI 的内心世界"——就是朴实无华的数据。

这意味着记忆是完全可控的。你可以审计 AI 存了什么、删掉不该存的东西、在加载时做过滤。记忆对 AI 来说只是一个信息来源,和你从数据库里查一条记录没有本质区别。

6.2 AI 控制存储,你控制执行

和第五课"请求与执行分离"一样,记忆系统也遵循这个原则。

AI 通过save_to_memory字段建议存储什么,但真正执行存储的是self.memory.add()——你的代码。你想在存储前加过滤?加。想去重?加。想限制记忆条数?加。

AI 是建议者,你的代码是决策者。

6.3 模型不直接访问记忆

模型看不到你的AgentMemory对象。它只能看到你在 Prompt 里放的内容。

memory_str="你记住了以下关于用户的信息:\n"+"\n".join(f"-{item}"foriteminmemory_context)

这段代码做的事情,就是把记忆"翻译"成模型能理解的文字,塞进 Prompt。模型"记住了"你的名字,其实是因为它在 Prompt 里看到了"用户的名字是小明"这句话。

理解这一点很重要——记忆的真正能力来自你的代码,不是来自模型。

6.4 简单就是强大

本课的记忆系统就是一个字符串列表。没有向量数据库,没有 embedding,没有语义检索。

但就这么简单的东西,已经能解决大部分跨对话记忆的需求。“用户叫什么”“喜欢什么”“项目是什么”——这些信息不需要复杂的检索,全部加载到 Prompt 里就够了。

先让功能跑起来,再考虑优化。记忆太多导致 Prompt 过长?到时候加截断。记忆太杂导致检索不准?到时候加语义检索。但第一步,就是把"存和取"这个基本能力建立起来。


七、常见问题

Q:Agent 不保存信息到记忆怎么办?

A:检查几件事:① Prompt 里是否有明确的 few-shot 示例,教 AI 什么时候该存?②save_to_memory字段的验证逻辑是否正确?③ 用户输入里是否确实包含值得记住的信息("帮我写个函数"确实不需要记)。给小模型(7B)加清晰的示例尤其重要。

Q:Agent 记住了错误的信息怎么办?

A:直接用memory.remove()删除错误记忆。记忆是显式存储,你可以随时检查和修改。这就是"记忆 = 数据存储"的好处——透明、可控。

Q:记忆太多,Prompt 太长怎么办?

A:三个方案:① 限制最大记忆条数,超过就删最旧的;② 只加载和当前输入相关的记忆(search()方法);③ 用摘要代替全量加载。本课的get_all()是最简单的方式,适合记忆不多(< 20 条)的场景。

Q:怎么让记忆持久化到文件?

A:很简单——在AgentMemory里加save_to_file()load_from_file()方法,用json.dump()json.load()就行。下次课程可以考虑加上。

Q:记忆和第二课的多轮对话 history 有什么区别?

A:self.history是上下文——本次会话内的对话记录,会话结束就清空。self.memory是长期记忆——跨会话持久化存储。两者互补:history 让 AI 记得"刚才说了什么",memory 让 AI 记得"上次聊了什么"。


八、下期预告

第八课:规划——让 Agent 学会拆解复杂任务

前七课,Agent 可以做决策、调工具、循环执行、记住信息了。但它的每一步决策都是"当场想"的——没有提前规划。

当你给 Agent 一个复杂任务:“帮我写一个网页爬虫,抓取新闻标题,按日期分类,存入数据库”——它不会先规划"第一步做什么、第二步做什么",而是直接开始随机行动。

下一课,我们给 Agent 加上规划能力——在动手之前先想好步骤,然后按计划执行。这是 Agent 从"能干活"到"会干活"的关键一步。

敬请期待!


完整代码获取

本课涉及的完整代码包括:

  • AgentMemory类——轻量级记忆存储系统
  • run_with_memory()方法——带记忆的对话执行
  • complete_example.py——演示模式 + 交互模式

关注公众号「开源情报局」,回复「Agent」获取。


标签

#Python#AI Agent#LLM#记忆系统#Ollama#Qwen#大模型#手搓Agent


本文为《手搓 AI Agent 从 0 到 1》系列教程第 7 课

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/21 6:12:36

告别算力焦虑:硅基流动“弹性 GPU”公测上线

过去两年&#xff0c;我们通过 MaaS 落地了“Token 工厂”构想&#xff0c;帮助开发者与企业客户加速 AI 应用落地。我们的公有云 MaaS 已服务近 1,000 万用户及 10,000 家企业客户&#xff0c;日均生成数千亿 Token&#xff1b;私有化 MaaS 在能源、金融、互联网等多个行业落地…

作者头像 李华
网站建设 2026/4/21 5:58:13

如何让 RTX 5090 开启 PCIE P2P 以加速多卡通信

如何让 RTX 5090 开启 PCIE P2P 以加速多卡通信 一、 背景与原理:为什么要折腾这个? 1. 什么是 PCIE P2P? 2. 为什么 RTX 5090 默认不支持? 3. 性能提升有多大? 二、 准备工作与环境 三、 操作步骤 阶段 1:BIOS 设置 阶段 2:操作系统准备 阶段 3:提取 NVIDIA-SMI 工具 …

作者头像 李华
网站建设 2026/4/21 5:57:37

Mac版飞秋:打破局域网通信壁垒的开源解决方案

Mac版飞秋&#xff1a;打破局域网通信壁垒的开源解决方案 【免费下载链接】feiq 基于qt实现的mac版飞秋&#xff0c;遵循飞秋协议(飞鸽扩展协议)&#xff0c;支持多项飞秋特有功能 项目地址: https://gitcode.com/gh_mirrors/fe/feiq 你是否在Mac上工作&#xff0c;却经…

作者头像 李华