资讯动态

AI Agent记忆系统架构:短期与长期记忆的工程实现与避坑指南

发布时间:2026/10/20 5:48:38 来源:尧图企业网站定制
做Agent这件事越做到后面越会撞上一个尴尬事实模型能力不够你可以换更强的底座工具调用不行你可以把工具描述写得更好但Agent转头就忘这个问题不是换个模型就能解决的。AI Agent记忆系统本质上是给一次性的对话接上一个能跨时间、跨会话存取的状态层。它分短期和长期两条线短期负责当前任务的工作记忆长期负责把对话沉淀成可检索、可复用、可更新的知识。这篇文章把短期记忆和长期记忆的架构设计与工程实现拆开揉碎从设计思路到具体代码给正在做或者准备做Agent的开发者一份能直接抄作业的参考。适合看这篇的人有三类刚用大模型API写聊天机器人、但每次都靠用户重复背景信息才能干活的已经用Agent框架搭了多轮对话、但对话一长就上下文爆炸的以及想给Agent做个性化记忆能力的产品和算法工程人员。下面我会按架构分层→短期记忆实现→长期记忆存储检索→协同调度→实战避坑的顺序讲尽量让新手能照做也让有经验的同行能挑到可复用的设计细节。1. 记忆系统整体架构与记忆分层设计1.1 为什么Agent必须有一套记忆系统先把根因说清楚。大模型对话的本质是无状态函数你给它一段文本它回你一段文本上下文一关状态清零。Agent不一样Agent要完成任务就得能在多轮里保持目标、积累信息、引用之前的结论。没有记忆的Agent每次开口都在失忆重启用户得把背景资料讲三遍。我见过很多团队的第一版Agent都是暴力塞上下文把历史对话全量拼进system prompt对话轮次一多上下文窗口直接被撑爆更别提成本了。这是治标不治本。真正的解法是给Agent设计一套独立的记忆系统把模型无状态的短板用外部状态补齐。这套系统的目标就一句话在正确的时机把正确的那一小部分过去放回模型面前。这句话听起来简单落地却难。难在正确两个字短期记忆要留多少长期记忆调哪些两者的优先级怎么排这些都得在架构层面定好规则而不是靠拼字符串。1.2 短期记忆和长期记忆的边界怎么划用人类认知做类比最好懂。短期记忆像桌面上的便签正在处理的任务、上一步的结论、用户刚说的关键偏好都是随手要用的速度要快长期记忆像档案柜用户画像、领域知识、历史项目、以往决策理由平时不用全摊开需要时按索引去查。工程上两者的边界会体现在存储介质和生命周期上维度短期记忆工作记忆长期记忆长期知识内容最近对话、任务中间态、上下文变量用户画像、历史事件、领域事实、经验规律生命周期单次会话会话结束即销毁或压缩跨会话持久保存按策略更新与遗忘存储形态内存对象、键值缓存、对话窗口向量数据库、结构化表、JSON文件访问方式全量注入或摘要注入检索召回按相关性动态写入更新方式每次对话追加、截断、压缩抽取写入、去重合并、冲突覆盖划边界时最容易犯的错是把长期记忆当成短期记忆的备份。短期记忆里的历史对话是一份时间线长期记忆是语义层存的是从时间线里提炼出来的结论。打个比方昨天你帮用户配置了服务器SSH短期记忆记录的是昨天下午捣鼓了SSH的详细步骤长期记忆记录的是该用户使用的是某家的云主机偏好密钥登录对安全要求高。前者是流水账后者才是能复用的资产。1.3 读写两条主链路架构设计的核心骨架记忆系统的架构不用搞得很复杂核心是两条链路写路径用户输入和Agent输出进入短期记忆 → 短期记忆做抽摘要/抽取 → 关键信息结构化后写入长期记忆。读路径新用户输入触发查询 → 先从长期记忆召回相关记忆 → 与短期记忆按配额组装 → 拼进上下文给模型。这两条链路本身不复杂复杂的是让它们协同。写路径不能每轮都写否则长期记忆里全是重复碎片读路径不能什么记忆都召回否则上下文噪声会淹掉真正重要的信息。一个比较稳妥的做法是把记忆调度做成独立的中间层不要和业务逻辑耦合在一起。业务层只负责说当前用户是U当前会话是S请给我组装上下文记忆层负责去长期库里捞什么、短期库留多少、怎么拼。这样后续换存储、调策略都不用动业务代码。2. 短期记忆工作记忆区的工程实现短期记忆在工程上最直接的载体是上下文窗口但上下文窗口是稀缺资源所以短期记忆的核心不是存多少而是怎么聪明地丢。2.1 上下文配额管理给记忆定预算第一步要做的是给上下文的各个部分定配额否则长期记忆越塞越多最后又把窗口撑爆。我习惯把上下文分成三个区系统指令区、长期记忆摘要区、短期对话区。系统指令区放Agent人设、任务说明、工具定义这部分基本不变。长期记忆摘要区放从长期库召回并压缩过的记忆这里要做严格的条数限制。短期对话区放最近的对话窗口超出的部分进入截断或摘要。一个可参考的配额模型按4K上下文举例系统指令1200 token长期记忆摘要800 token短期对话区1800 token缓冲余量200 token这里的核心思路是预算是写死的内容是动态的。对话区超了就压缩或截断对话区长期摘要超了就压缩摘要而不是反过来把系统指令挤掉。系统指令一旦被挤掉Agent会连自己是谁、要干什么都忘掉这种错误非常低级但很容易犯。2.2 滑动窗口截断守住最近对话时间线在实现短期对话区时最常用的是滑动窗口。它的逻辑很简单无论历史多长只保留最近的N轮。# 一个简单的滑动窗口实现 class SlidingWindowBuffer: def __init__(self, max_turns10): self.max_turns max_turns self.messages [] def add_turn(self, user_msg, assistant_msg): self.messages.append({role: user, content: user_msg}) self.messages.append({role: assistant, content: assistant_msg}) self._trim() def _trim(self): if len(self.messages) self.max_turns * 2: overflow len(self.messages) - self.max_turns * 2 self.messages self.messages[overflow:]实际操作中单纯截断有个问题早期对话里的关键信息会直接丢。比如用户第三轮说记住我用的是月度订阅第五十轮再问我这个月账单到期日是什么时候滑动窗口早把那条信息挪出去了。所以窗口截断只适合低要求场景。更稳的做法是配合摘要被截断的部分不是直接删除而是在截断前生成摘要挂到长期记忆里。等用户再提到相关话题时靠检索把它召回来。2.3 滚动摘要压缩让上下文持续瘦身滚动摘要的思路是每次对话进行到一定量就把之前的历史交给模型总结成一段短摘要之后把原文替换成摘要继续跑。典型实现长这样def get_running_summary(summary_so_far, new_messages, llm): if summary_so_far is None: content \n.join([m[content] for m in new_messages]) else: content f已有摘要:\n{summary_so_far}\n\n追加对话:\n \n.join( [m[content] for m in new_messages] ) prompt [ { role: system, content: ( 你是记忆整理助手。把已有摘要和新对话合并成最新摘要。 保留用户明确说过的偏好、事实和未完成任务。 不要加入你的推测。控制在200字以内。 ), }, {role: user, content: content}, ] summary llm.chat(messagesprompt) return summary滚动摘要有几个细节需要注意。第一触发时机不能只看轮次还要看token量。我建议按token数触发比如累计超过1500 token就做一次摘要。第二摘要要从未来使用的角度写不是复述发生了什么而是提炼出哪些信息后续还可能会用。比如用户提到了服务器在某某地域比用户和我们聊了服务器的价格有用得多。第三摘要会叠加误差所以每次都不应该完全抛弃原文而是在摘要后保留最近一两轮原文作为缓冲。2.4 结构化工作状态别把所有记忆都压进自然语言短期记忆里还有一类信息不适合塞自然语言任务中间状态。比如Agent在做一个多步骤任务现在已经完成了哪几步、下一步是什么、有哪些可选参数这些用自然语言描述又长又容易乱但用结构化的JSON挂到会话状态里非常清晰。{ session_id: s_1024, task: { name: 服务器迁移, current_step: 备份数据, done_steps: [确认源机器, 创建快照], params: { src_host: 10.x.x.x, region: ap-east } }, user_intent: 迁移后检查服务可用性, pending_input: null }每次调用模型前把这个JSON压缩成一行文本注入上下文每轮结束后根据模型输出更新它。这其实就是老生常谈的结构化状态管理但放到Agent语境下特别容易被忽略。很多人把所有东西都写成对话文本上下文又长又乱还不好解析得不偿失。3. 长期记忆存储设计与检索优化长期记忆是Agent真正的增量资产也是记忆系统里最需要花心思设计的一层。短期记忆的坑主要在配额管理长期记忆的坑则在数据质量和检索准确性。3.1 存什么事实、事件、语义三种记忆长期记忆我习惯拆成三类分类不同写法和用法也不同记忆类型内容示例检索方式事实记忆用户属性、偏好、配置用户使用月付套餐用户偏好邮件通知结构化KV按实体查询事件记忆发生过的事、任务结果8月3日完成过一轮服务器巡检时间线向量检索语义记忆Agent学习到的规律、概念用户常用方案A而非方案B脚本部署前必须备份向量检索抽象关联事实记忆和事件记忆的分工要特别说明一下。事实记忆是当前为真的信息可以直接覆盖事件记忆是过去发生的记录只能追加不能覆盖。这个区分很重要用户改了地址事实记忆里地址要更新但事件记忆里2025年初用户曾在A地的记录不应该被删掉因为它是历史证据。3.2 写入流程从对话到可检索的记忆条目长期记忆不是把对话原文复制进去就完事而是要经历抽取→去重→向量化→入库四个步骤。抽取是其中最关键的环节。我通常的做法是在对话到达某个边界点比如完成了一个子任务或者用户明确表达了偏好时调用一次抽取模型把记忆点转成结构化JSON{ memory_type: fact, entity: user, attribute: plan_type, value: monthly, confidence: 0.95, source_message_id: m_233, timestamp: 2026-01-17T10:32:00Z }抽取提示词要注意两点。第一禁止推测。模型很容易在抽取时脑补用户没说过的话必须在提示词里明确只抽取用户明确表达的不要总结用户没有给出的信息。第二要有去重要求。抽取结果里如果已经存在相同实体属性的旧记忆要返回update而不是新建。去重之后做向量化。这里我建议把实体属性值上下文拼成一句话再向量化而不是只向量化值。比如用户 plan_type 等于 monthly和用户目前选择按月订阅方案语义相近的靠向量才能召回。3.3 检索排序不只是向量TopN很多Agent记忆的第一版都直接做向量Top5召回塞进上下文但效果往往很差因为向量检索只看语义相似度不看时间的紧迫性和事实的确定性。长期记忆的检索排序至少要叠加三个因素语义相关性、时间衰减、置信度。一个参考公式score similarity × decay_factor × confidencesimilarity向量相似度范围0~1。decay_factor时间衰减系数比如当天为1一周后衰减为0.6一个月后0.2。confidence抽取阶段模型给的置信度0~1。举例说明。用户两年前说过喜欢喝美式昨天说现在改喝拿铁了。用户不提咖啡的前提下两条记忆的语义相似度可能都在阈值内但旧记忆被覆盖后就不该再返回如果没做覆盖就必须靠时间衰减把旧记忆的分数拉下来。检索还有个工程细节尽量做混合检索关键词命中 向量召回再用上面的公式统一排序。有些事实记忆是精确值套餐类型月度向量召回容易漂但关键词倒排索引一查就中。3.4 记忆更新与冲突处理覆盖、合并和版本化长期记忆最复杂的部分不是让它记住而是让它接受新信息、放下旧信息。冲突处理我总结成三种策略冲突场景策略示例同一属性的值被更新直接覆盖旧值保留历史版本地址从A地改为B地当前值用BA进历史不同记忆条目含义相近合并去重保留置信度高的核心词喜欢简约风格和不爱复杂设计合并事件记忆新增追加不覆盖旧事件历史巡检记录持续追加不删旧记录实现上给每条记忆加version字段和status字段active/superseded/deleted会好办很多。查询只取active更新时把旧条目置为superseded再新建一条active。这样既能保证当前记忆干净也能保留完整的演变轨迹后续做调试和审计非常有用。3.5 存储选型按数据量分阶段存储永远是跟着规模走的。我给一个选型参考不一定适用所有人但能给选择困难的人一个方向阶段数据规模推荐方案备注小项目/原型百级用户JSON文件或单机内存KV开发迭代快不用上重型组件中规模单机万级用户嵌入式向量库关系型数据库注意备份和持久化大规模生产百万级以上独立向量检索服务分布式存储需要独立运维考虑容量规划这里要强调一点不要在原型阶段就上一套复杂的分布式存储没必要。记忆系统的难点在逻辑设计不在存储组件。用文件把逻辑先跑通再迁移到正式存储这个路径最省力。4. 短期与长期协同一条完整的记忆调度链路两边分开设计好了接下来就是组合拳。这一节用一条完整的带记忆会话串起来。4.1 一次完整的带记忆会话过程场景用户第二次打开Agent说上次那个迁移任务帮我看看卡在哪一步了。def handle_new_session(user_id, session_id, user_input, memory_store, llm): # 1. 读长期记忆用户画像 最近的活跃任务 long_term_memories memory_store.query(user_id, query_textuser_input, top_k5) # 2. 建短期工作区 short_term ShortTermBuffer() short_term.add_turn(user_input, None) # 3. 组装系统提示词 system_prompt build_system_prompt( user_profilelong_term_memories[facts], task_statelong_term_memories[active_task], conversation_summarylong_term_memories[recent_events], ) # 4. 调模型 response llm.chat(messages[system_prompt] short_term.messages) # 5. 写回长期记忆本轮的新状态 memory_store.write(user_id, extract_memories(user_input, response)) return response这个流程看起来不复杂但每一步都有讲究。第一步的检索query用的是当前用户输入而不只是该用户的所有记忆因为上下文空间有限必须按需求召回。第三步组装的system prompt不是列出用户所有信息而是只放和当前任务相关的画像和事件否则就像进了档案室把所有柜子都打开反而是灾难。4.2 注入剂量与时机控制记忆注入最经典的翻车现场就是召回了一堆记忆每条都对但模型被这堆记忆干扰得不知道怎么回答。原因在于剂量过大。我做经验值长期记忆摘要的总量建议控制在上下文窗口的20%~30%召回条数控制在3~8条之间。召回条数多了模型会试图雨露均沾地引用每条记忆输出变得又长又碎。召回条数少了关键信息漏掉回答就没针对性。具体调优时可以先从top_k5起步观察回答质量再增减。时机上记住一个原则不是每轮都要做长期检索。如果当前用户输入可以靠短期窗口就解决就不必多一次向量查询。只有两类情况值得触发长期检索一是新会话开始上下文是空的二是当前输入里出现了之前提到过我上次说的这类回溯指代。4.3 遗忘机制记忆需要定期清理和刷新长期记忆不等于永远记住。没有遗忘机制的记忆系统最终会变成一个越存越脏的垃圾场。我在线上系统里会做三件事定期衰减扫描每周跑一次对超过一定时间且没有访问的条目降低置信度或归档。冲突清理对superseded状态的条目定期物理清理或者归档减少查询量。用户主动删除提供清除我的记忆之类的能力这是对用户数据和隐私的尊重也是很多场景合规的底线。遗忘机制的工程实现不复杂难的是判断标准。我的经验是不要按固定天数一刀切而是结合最后访问时间和是否与活跃任务相关。一个用户三个月前用的项目管理偏好可能已经过期但同一时间设定的自动备份策略如果还在运转就仍然是有效记忆。4.4 可观测性设计记忆系统怎么调试记忆系统是隐性的故障多发区。模型输出错了你可以追prompt但记忆坏了往往很难察觉。所以从第一天起就把可观测性做好能省后面大量排查时间。我自己的做法是三个全记录一是记忆写入全记录谁在什么时间写入了一条什么记忆原始来源是哪条消息二是召回过程全记录每次查询召回的前十名是哪些、分数分别多少、有没有被过滤掉三是最终注入结果记录模型实际看到的记忆摘要长什么样。这三个记录单独存一份调试日志不进生产上下文。等出问题时把用户输入→召回结果→注入内容→模型输出四段拼在一起问题出在哪个环节基本一眼就能看出来不像黑盒只能靠猜。5. 实战避坑我踩过的五个典型问题这一节整理我在实际项目里遇到过的问题每个都配了症状和解决思路。5.1 上下文爆炸越跑越慢症状运行时token消耗逐渐走高响应延迟明显变长偶尔直接报上下文超限。原因短期记忆和长期摘要的配额没有写死有的工程师把摘要越拼越长把长期记忆固定拼进system从几百token涨到几千token。解法把配额收敛到一个配置中心统一管理系统指令/长期摘要/短期窗口/缓冲的token上限并在每次拼装后做token长度校验超了就强制走压缩分支。这个校验不是可选项是必须项。5.2 召回了一堆记忆模型反而答非所问症状长短期记忆都正常工作但Agent回答明显跑偏。原因召回结果里的相关性阈值太低塞了太多弱相关记忆或者是召回条数太多模型被噪声干扰。解法把相关性阈值从0.6左右往上调召回条数限制在5条以内同时做一次重排。重排的意思是对召回的候选记忆先用轻量规则筛掉明显不相关的再进排序公式最后注入时只保留高分段。宁可少给不要乱给。5.3 用户改了偏好旧记忆还在干扰症状用户说了我现在不用邮件通知了但Agent还在提醒您偏好邮件通知。原因事实记忆没有做覆盖更新。新偏好写进去了但旧偏好的条目还是active状态。解法写路径必须带覆盖逻辑。当抽取到user.notification_preference的新值时先查库里有没有同实体同属性的active条目有的话把它置为superseded再写入新条目。这个过程一步都不能省省了后面全是脏数据。5.4 记忆系统成为成本刺客症状线上账单里essages token消耗比预期高一截。原因每轮都触发摘要、每轮都做长期检索并召回、抽取模型用在了所有对话轮次上。这些操作单独看都不大量一大就是实打实的成本。解法做异步化。对话线上只做必要的短期窗口更新摘要和抽取放进异步队列批量处理。比如固定每10轮或者每满一定token才跑一次摘要长期检索只在识别到回溯指代或新会话启动时才触发。记忆系统的延迟可以容忍没必要和在线对话抢时间。5.5 隐私与数据边界做记忆系统一定要明确一个问题用户记忆不是免费的资源它携带敏感信息。工程上至少要保证三件事长期记忆存储加密尤其是画像里可能存在的联系方式、地址之类敏感字段。支持用户查看和删除自己的记忆条目并且删除应该是物理删除或彻底置灰而不是留在后台。写入时做内容过滤对明显敏感的信息打标默认不进入长期记忆如果业务确实需要必须有明确授权。合规不是把锅甩给法务而是架构师在系统设计时就得把数据边界画出来。早期不管后面改起来是大工程。最后分享一点我个人做成这件事后的最大体会。记忆系统真正让用户觉得这Agent懂我的时刻不是某条记忆检索得有多准而是中断之后接得上。我之前在做个人知识助手类Agent时把记忆回写改成了独立的优先级队列不让它和在线对话抢资源用户在前一轮中断后隔了一天才回来说继续上次那个方案它能快速接上上下文那一刻我才觉得记忆系统的价值真正兑现了。再送一个小技巧给每条记忆都加上source_message_id记录它是从哪一条消息抽取出来的。这个字段平时看着没用一旦做错需要回溯时它能帮你少排查几个小时。别偷懒加上它。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑