系列专栏:AI Agent 实战(Day 05 / 18) 前置阅读:Day 01 从Chatbot到Agent(Agent Loop 和history 列表) 署名:梅雅达编程笔记
摘要
history列表当记忆有三硬伤:重启失忆、token增长快、无法按相关性检索。本文用dict、list和JSON文件搭建三级记忆架构,支持关键词标签检索,核心约90行代码。
你有没有遇到过这种情况:跟AI聊了半小时,告诉它你的名字、你在做什么项目、你偏好什么风格,然后程序一重启——全忘了。
前四天的Agent都用一个history列表记录对话。它能记住当前会话里发生了什么,但有三个硬伤:程序重启就清空、对话长了token线性增长、没法按相关性检索——只能把全部历史塞给LLM,让它自己在一大段文本里找。
这篇搭一个三级记忆系统,让Agent分清什么该记、什么该忘、什么该存下来。
记忆该怎么分层
人脑不是把所有信息平等存储的。你正在心算的数字只存在几秒钟,刚才聊的话题能记住一阵,但你的名字、专业技能这些是长期存储的。Agent也该这样:
#mermaid-svg-rmsnSEh14L9KU0g8{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rmsnSEh14L9KU0g8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rmsnSEh14L9KU0g8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rmsnSEh14L9KU0g8 .error-icon{fill:#552222;}#mermaid-svg-rmsnSEh14L9KU0g8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rmsnSEh14L9KU0g8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rmsnSEh14L9KU0g8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rmsnSEh14L9KU0g8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rmsnSEh14L9KU0g8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rmsnSEh14L9KU0g8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rmsnSEh14L9KU0g8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rmsnSEh14L9KU0g8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rmsnSEh14L9KU0g8 .marker.cross{stroke:#333333;}#mermaid-svg-rmsnSEh14L9KU0g8 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rmsnSEh14L9KU0g8 p{margin:0;}#mermaid-svg-rmsnSEh14L9KU0g8 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-rmsnSEh14L9KU0g8 .cluster-label text{fill:#333;}#mermaid-svg-rmsnSEh14L9KU0g8 .cluster-label span{color:#333;}#mermaid-svg-rmsnSEh14L9KU0g8 .cluster-label span p{background-color:transparent;}#mermaid-svg-rmsnSEh14L9KU0g8 .label text,#mermaid-svg-rmsnSEh14L9KU0g8 span{fill:#333;color:#333;}#mermaid-svg-rmsnSEh14L9KU0g8 .node rect,#mermaid-svg-rmsnSEh14L9KU0g8 .node circle,#mermaid-svg-rmsnSEh14L9KU0g8 .node ellipse,#mermaid-svg-rmsnSEh14L9KU0g8 .node polygon,#mermaid-svg-rmsnSEh14L9KU0g8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rmsnSEh14L9KU0g8 .rough-node .label text,#mermaid-svg-rmsnSEh14L9KU0g8 .node .label text,#mermaid-svg-rmsnSEh14L9KU0g8 .image-shape .label,#mermaid-svg-rmsnSEh14L9KU0g8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-rmsnSEh14L9KU0g8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rmsnSEh14L9KU0g8 .rough-node .label,#mermaid-svg-rmsnSEh14L9KU0g8 .node .label,#mermaid-svg-rmsnSEh14L9KU0g8 .image-shape .label,#mermaid-svg-rmsnSEh14L9KU0g8 .icon-shape .label{text-align:center;}#mermaid-svg-rmsnSEh14L9KU0g8 .node.clickable{cursor:pointer;}#mermaid-svg-rmsnSEh14L9KU0g8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rmsnSEh14L9KU0g8 .arrowheadPath{fill:#333333;}#mermaid-svg-rmsnSEh14L9KU0g8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rmsnSEh14L9KU0g8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rmsnSEh14L9KU0g8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rmsnSEh14L9KU0g8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rmsnSEh14L9KU0g8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rmsnSEh14L9KU0g8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rmsnSEh14L9KU0g8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rmsnSEh14L9KU0g8 .cluster text{fill:#333;}#mermaid-svg-rmsnSEh14L9KU0g8 .cluster span{color:#333;}#mermaid-svg-rmsnSEh14L9KU0g8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rmsnSEh14L9KU0g8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rmsnSEh14L9KU0g8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-rmsnSEh14L9KU0g8 .icon-shape,#mermaid-svg-rmsnSEh14L9KU0g8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rmsnSEh14L9KU0g8 .icon-shape p,#mermaid-svg-rmsnSEh14L9KU0g8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rmsnSEh14L9KU0g8 .icon-shape .label rect,#mermaid-svg-rmsnSEh14L9KU0g8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rmsnSEh14L9KU0g8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rmsnSEh14L9KU0g8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rmsnSEh14L9KU0g8 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
任务结束清除
超出容量淘汰最旧
重要信息提取
JSON文件持久化
重启后加载
用户输入
工作记忆 working
清除
短期缓存 short_term
自动过期
长期记忆 long_term
磁盘
get_context 组装
送入LLM
工作记忆是个dict,存当前任务的关键变量,任务做完就清。短期缓存是个list,保留最近10轮对话,超了自动淘汰最旧的。长期记忆写到JSON文件,每条带标签和重要程度,重启后还能读回来。
get_context()决定哪些记忆最终进入LLM视野:工作记忆全量给、短期只取最近5条、长期按重要性排序取前N条。不是把所有东西都塞进去,而是按需筛选。
代码怎么写
MemoryManager是核心,三级记忆各用各的数据结构:
import json, os
from datetime import datetime
class MemoryManager:
def __init__(self, file="memory.json", short_cap=10):
self.working = {} # 工作记忆:dict
self.short_term = [] # 短期缓存:list
self._file = file
self._short_cap = short_cap
self._long = self._load() # 长期记忆:JSON文件
def _load(self):
if os.path.exists(self._file):
with open(self._file, "r", encoding="utf-8") as f:
return json.load(f)
return []
def _save(self):
with open(self._file, "w", encoding="utf-8") as f:
json.dump(self._long, f, ensure_ascii=False, indent=2)
remember()根据层级存到不同地方。工作记忆按键值对写入,短期缓存追加到列表末尾并控制容量,长期记忆写入文件:
def remember(self, content, tier="short_term", tags=None, importance=1, key=None):
if tier == "working":
self.working[key or f"k{len(self.working)}"] = {
"content": content, "ts": datetime.now().isoformat()
}
elif tier == "short_term":
self.short_term.append({
"content": content, "tags": tags or [],
"ts": datetime.now().isoformat()
})
if len(self.short_term) > self._short_cap:
self.short_term.pop(0)
elif tier == "long_term":
self._long.append({
"content": content, "tags": tags or [],
"importance": min(5, max(1, importance)),
"ts": datetime.now().isoformat()
})
self._save()
recall()支持按关键词和标签跨层级检索,不指定层级就搜全部:
def recall(self, query=None, tier=None, tags=None, limit=5):
items = []
for t in (["working", "short_term", "long_term"] if not tier else [tier]):
source = (self.working.values() if t == "working"
else self.short_term if t == "short_term"
else self._long)
for e in source:
items.append({"content": e.get("content", ""),
"tags": e.get("tags", []), "ts": e.get("ts", "")})
if query:
q = query.lower()
items = [i for i in items if q in i["content"].lower()]
if tags:
items = [i for i in items if any(t in i["tags"] for t in tags)]
items.sort(key=lambda x: x["ts"], reverse=True)
return items[:limit]
get_context()是记忆系统和LLM之间的桥梁,按优先级拼装,控制token消耗:
def get_context(self, max_long=5):
lines = []
if self.working:
lines.append("【当前任务】")
lines.extend(f" {k}: {v['content']}" for k, v in self.working.items())
if self.short_term:
lines.append("\\n【最近对话】")
lines.extend(f" {e['content']}" for e in self.short_term[–5:])
if self._long:
lines.append("\\n【长期记忆】")
top = sorted(self._long, key=lambda x: x.get("importance", 1), reverse=True)
lines.extend(f" [{', '.join(e.get('tags', []))}] {e['content']}"
for e in top[:max_long])
return "\\n".join(lines) or "(无记忆)"
在外面套一个Agent,每次对话自动走"存短期→建工作记忆→组装上下文→调LLM→存回复→清工作记忆"的流程:
class MemorableAgent:
def __init__(self, memory_file="agent_memory.json"):
self.llm = LLMClient(provider="glm")
self.mem = MemoryManager(file=memory_file)
def chat(self, user_input):
self.mem.remember(f"用户: {user_input}", tier="short_term")
self.mem.remember(user_input, tier="working", key="task")
context = self.mem.get_context()
resp = self.llm.chat(
user_input,
system_prompt=f"你是有助手。已知信息:\\n{context}",
temperature=0.7
)
self.mem.remember(f"助手: {resp}", tier="short_term")
self.mem.working.clear()
return resp
def remember_fact(self, content, tags=None, importance=3):
self.mem.remember(content, tier="long_term", tags=tags, importance=importance)
实际效果
agent = MemorableAgent("demo.json")
# 第一轮:告诉它你是谁
agent.chat("我叫张三,做Python后端的,主要用Django。")
agent.remember_fact("用户张三,Python后端,用Django", tags=["profile"], importance=5)
# 第二轮:聊个技术话题
agent.chat("Python的GIL是什么?")
agent.remember_fact("GIL是全局解释器锁,同一时刻只允许一个线程执行字节码",
tags=["python", "GIL"], importance=3)
# 模拟重启:清空短期缓存和工作记忆
agent.mem.short_term.clear()
agent.mem.working.clear()
# 第三轮:它还记得你
print(agent.chat("你还记得我是谁吗?"))
# 输出:你好张三!你是做Python后端开发的,主要使用Django框架…
第三轮清空了短期缓存,相当于程序重启。但长期记忆已经持久化到demo.json,Agent重新加载后照样能叫出你的名字、想起你聊过GIL。这就是持久化记忆和history列表的本质区别。
完整版包含forget()遗忘方法、记忆过期策略、三个演示任务和详细的日志输出,放在CSDN下载区。
GLM-4.7-Flash永久免费,200K上下文,注册地址:https://open.bigmodel.cn/ 下一篇Day06会引入向量数据库,用Embedding做语义检索——不再依赖精确关键词匹配。



