ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI Agent 开发工程师(四):给 Agent 装上长期记忆——从“记住上下文“到“真正记得“

AI Agent 开发工程师(四):给 Agent 装上长期记忆——从“记住上下文“到“真正记得“ 上一篇给了我们什么能干活但健忘上一篇我们做出了第一台能干活的工具型 Agent它接上了真实模型、会调真实行情工具你说一句它就帮你查出来。但如果你多跟它聊几次会撞见一个尴尬的事实——它转身就忘。你上午让它记住我最近在调研海康威视和 AI 摄像头下午换个会话重新打开问我最近关注什么股票它一脸茫然。不是它傻是它从来没有记过它的记忆只有messages里那几轮对话会话一关烟消云散。这一篇我们帮它装上长期记忆——不只是当前这轮记得而是过了一天、换个会话它还记得你以前交代过的事。做完这一篇你的 Agent 就从能干活升级为记得住能给你一个跨会话陪伴感。1. 短期记忆 vs 长期记忆差在哪一层先把话说清楚避免混淆。我们用记忆这个词指代两种完全不同的东西短期记忆长期记忆存什么当前对话的每句来回messages跨会话沉淀下来的事实/偏好存哪模型请求里的 messages 数组程序自己数据库/文件生命周期请求结束即丢持久跨会话谁来管模型调用方 append你自己的记忆层第2篇里我们说的记忆 messages 数组是指短期。这一篇要做的是长期那半把重要的东西抽出来、存进一个数据库下次要用时再检索回来、喂回给对话。这样 Agent 即使面对一次全新的会话也能哦原来上回你交代过这个。记忆文件名SQLite 归档一行一行记不需要高大上就用 Python 自带的sqlite3当记忆仓库。每个记忆条目就是一行带内容的记录importsqlite3defconnect():connsqlite3.connect(agent_memory.db)conn.execute(CREATE TABLE IF NOT EXISTS memories( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, created_at TEXT DEFAULT (datetime(now)) ))returnconn DBconnect()defadd_memory(content):DB.execute(INSERT INTO memories(content) VALUES(?),(content,))DB.commit()defall_memories():return[r[0]forrinDB.execute(SELECT content FROM memories)]add_memory(用户最近关注海康威视和 AI 摄像头)就把这条记进去了。all_memories()能把它读回来。这跟你第3篇把工具函数存成一个普通函数是同一个思路记忆就是数据数据就在本地库。agent_memory.db落在你项目目录里重启程序也在——这就实现了持久。关键一步让 Agent想起该记得的检索看一眼记忆库乱成一团可能有几百条用户上周关注 A“用户下周出差到 B”。不能全塞给模型——上下文会爆且绝大多数和眼前问题无关。所以要用你的问题去检索把最相关的那几条捞出来。这里用到的是一个朴素但有效的办法把每条记忆和你的提问都转成向量算它们有多像余弦相似度取最高分。中文文本我们用字符 n-gram 做向量对中文友好不用额外分词:fromsklearn.feature_extraction.textimportTfidfVectorizerdefrecall(query,top_k3):memall_memories()ifnotmem:returnvecTfidfVectorizer(analyzerchar,ngram_range(1,3))Xvec.fit_transform(mem)# 每条记忆 - 一个向量qvec.transform([query])# 用户提问 - 一个向量scores(X q.T).toarray().ravel()# 余弦相似度已L2归一化idxsorted(range(len(scores)),keylambdai:scores[i],reverseTrue)[:top_k]hits[mem[i]foriinidxifscores[i]0.05]# 过阈值才算命中return.join(hits)真实验证我往库里存了三条然后分别用不同问法检索问「查一下贵州茅台什么价格」-- 用户上次问过贵州茅台行情…(相似度0.47) 问「宝宝多大了」 -- 用户的新生宝宝叫阳阳刚满月 (0.42) 问「腾讯腾讯收盘多少」 -- 用户对腾讯控股的港股行情感兴趣(0.33)三问三中。你没提股票“宝宝这些字但 Agent 靠像不像把最相关的那条捞了出来——这正是检索在记忆系统里的主要作用理解上要留一分清醒这是按相似度召回”不是语义理解全貌真实场景里做精确还得靠更稳的索引与阈值。把这套存→取→注入的记忆子系统画成一张架构图它是后面许多会回忆的 Agent的地基注入层检索层存储层SQLite 记忆库一条一行·带时间戳把用户问题编码成向量特征TF-IDF char n-gram与每条记忆算相似度命中 Top-K 注入会话上下文用户新提问按相关度排序取 top-KLLM 回答带记忆用户问题 ──▶ 编码成特征 ──▶ 检索层(算相似度) ◀── 记忆库 │ ▼ 注入 top-K 记忆 ──▶ LLM 在上下文中看到历史偏好让记住落地把记忆注入会话端到端实录有了存储 检索还差在用的时候真的想起来。做法很朴素每次用户发问题时先recall(query)把相关记忆捞出来塞进 system 提示词里让模型参考然后才把对话交给模型。我把它写成一个带记忆的 Agent跑了一遍完整流程存一次记忆 → 新会话自动想起fromopenaiimportOpenAI clientOpenAI(api_key你的KEY,base_urlhttps://open.bigmodel.cn/api/paas/v4/)MODELglm-4-flash# ———— 场景第一会话用户交代了个人偏好 ————add_memory(用户的新生宝宝叫阳阳刚满月)add_memory(用户关注贵州茅台行情现价约 1252)add_memory(用户对腾讯控股的港股行情感兴趣)# ——— 新会话用户只问别的不主动提宝宝 ———q记得我宝宝叫什么、多大了吗mvrecall(q)# 检索自动捞回宝宝叫阳阳那条system你是私人助理。以下是检索到的历史记忆回答时请参考\nmv respclient.chat.completions.create(modelMODEL,messages[{role:system,content:system},{role:user,content:帮我回忆一下我的宝宝叫什么、多大了}],max_tokens80)print(resp.choices[0].message.content)线上真实输出用户新问题: 记得我宝宝叫什么、多大了吗 检索记忆: 用户的新生宝宝叫阳阳刚满月 Agent 回答: 您的宝宝名叫阳阳目前刚满月。关键点: 用户新会话的问题短到只有帮我回忆没再提宝宝阳阳这些词但 Agent 是靠recall注入的那条记忆答对的。这就是换了会话、它还记得的实现方式。踩坑想用它干好这几点要想清楚记忆去重与更新用户的偏好会变。别让用户关注个股 A用户其实关注个股 B两条矛盾记忆并存——设计上要允许覆盖/撤销旧记忆别越堆越乱。隐私别踩服务器久久保存用户的个人敏感信息要有这条能不能记的判断。教程里做到内容中性可选删除即合格删接口本质就是 SQLiteDELETE。别把全部回忆当上下文宁可top_k小一点3~5 行也不要一股脑全塞进去。检索质量 数量否则模型更晕、token 更贵。检索不是万能的早期中文小库char n-gram 够用库大了换真实语义 embedding如开源向量模型精度更高。思路不变换算器而已。小结你的 Agent 现在记得住了这一篇我们把第 2 篇的记忆是 messages推进成了跨会话的持久记忆存储SQLite 一行一行落盘进程重启不丢检索TF-IDF char n-gram 余弦让像的浮出来注入会话开始前 recall → 塞进 system → 模型想起历史。至此你的 Agent 从会调工具到跨会话记得是单独用和长期陪你干的分水岭。高能工具 长效牵挂——它离能办事又近了一大步。你可能会问那记忆这件事会不会让 Agent 上下文爆炸控制好阈值、做去重就不会。下一篇我们往更好更稳健的流程控制走——《规划器让 Agent 面对复杂问题不慌不乱》把这四要素里的规划落地成真正能拆任务、分步执行的代码补上工具型 Agent 的大脑中枢。敬请期待。
返回列表