当前位置: 首页 > news >正文

第 5 篇:Agent 记不住事?补上 Memory + RAG 检索

系列简介:从零搭建一个多 Agent AI 助手,覆盖原理、实现、部署全链路。不讲空话,每篇都有可运行的代码。
项目地址:https://github.com/CodeMomentYY/LangGraph-Agent
本篇目标:给 Agent 加上记忆能力——短期对话历史 + 长期 RAG 知识检索,让它不再“每次见面都是陌生人”。

前言

大家好,我是一名前端工程师。都说前端“已死”,那与其担心被 AI 替代,不如打入敌人内部,于是我开始折腾 Agent 开发。

折腾下来发现,Agent 的核心不是算法,而是“工程能力”(怎么设计架构、怎么串联服务、怎么把 LLM 的能力落地成产品)。这些恰好是我们擅长的事。

这个系列记录我从零搭建多 Agent 系统的完整过程。只聊技术知识和设计思路,代码交给 AI 写。如果你也想从应用层切入 AI,希望这个系列对你有帮助。

读完本篇你将学到:

  • Agent 为什么“没记性”,记忆该怎么分层设计;
  • 短期记忆:用 JSON 文件持久化每个 session 的对话历史;
  • 长期记忆:用 ChromaDB 做向量存储 + 关键词检索,让 Agent 能“回忆”跨会话的内容;

背景与动机

上一篇我们把 Agent 搬进了浏览器,有了对话界面和流式输出。但你试几轮就会发现一个问题:换个会话,Agent 就把你忘了

比如:你跟它说“我叫小明,喜欢吃火锅”,下一轮问“我喜欢吃什么”,它一脸茫然。

这不是 Bug,而是 LLM 的本质特性——无状态。每次调用 LLM 都是一次全新的对话,它不会自动记住上一轮说了什么。所谓的“记忆”,全靠我们把历史消息塞进 Prompt 里。

但塞多少合适?全塞进去会爆 Token 限制(大部分模型 4K-128K),而且历史越长,LLM 越容易“走神”——注意力被无关信息分散。

所以我们需要分层记忆:近期的完整保留,久远的按相关性检索。

核心概念

两种记忆的分工

短期记忆长期记忆
存什么当前 session 的完整对话历史所有 session 的对话摘要 + 知识库
怎么存JSON 文件(按 session_id 隔离)ChromaDB 向量库
怎么取全量加载,拼到 messages 里按当前问题做相似度检索,取 top_k
生命周期会话内有效永久保留
类比工作记忆(你正在聊的话题)长期记忆(你记得去年聊过的事)

RAG 是什么?

RAG =Retrieval Augmented Generation,拆开来看:

  • Retrieval(检索):从知识库里找到和当前问题相关的内容;
  • Augmented(增强):把检索到的内容塞进 Prompt,作为上下文;
  • Generation(生成):LLM 基于增强后的 Prompt 生成回答(一句话:先查资料,再回答问题);

整体数据流

把短期记忆和长期记忆结合起来,每次请求的完整流程是:

动手实现

Step 1:短期记忆(JSON 文件存 session)

最简单的方案:每个 session_id 对应一个 JSON 文件,里面存序列化后的 LangChain 消息列表。

""" 对话历史管理 每个 session_id 对应一个 JSON 文件 生产级会用 Redis / PostgreSQL,但学习阶段 JSON 文件最直观 """importos,jsonfromlangchain_core.messagesimportHumanMessage,AIMessage,ToolMessage MEMORY_DIR="data/memory"defload_history(session_id:str)->list:"""加载某个 session 的对话历史"""file_path=_get_session_file(session_id)ifnotos.path.exists(file_path):return[]withopen(file_path,"r",encoding="utf-8")asf:data_list=json.load(f)return[_deserialize_message(d)fordindata_list]defsave_history(session_id:str,messages:list):"""保存对话历史(覆盖写入)"""file_path=_get_session_file(session_id)data_list=[_serialize_message(msg)formsginmessages]withopen(file_path,"w",encoding="utf-8")asf:json.dump(data_list,f,ensure_ascii=False,indent=2)

序列化的关键是把 LangChain 的消息对象转成 dict,存type(HumanMessage / AIMessage / ToolMessage)和content,加载时再还原回来。

在 API 层的使用方式:

# 每次请求前:加载历史 → 拼到 state 里history=load_history(session_id)all_messages=history+[HumanMessage(content=request.message)]# Agent 执行完后:追加新消息 → 保存history.append(HumanMessage(content=request.message))history.append(AIMessage(content=reply))save_history(session_id,history)

这样同一个 session 内的对话就能连贯了——Agent 知道你上一句说了什么,结合上下文信息来回答你。

Step 2:长期记忆(ChromaDB 向量库)

短期记忆解决了“本次会话内的连贯性”,但跨会话就失效了。长期记忆用向量库来解决:把每轮对话存进去,下次提问时按相似度检索。

""" 向量记忆存储(RAG 长期记忆) 使用 ChromaDB 存储对话历史的向量表示 """importtimeimportchromadbfrompathlibimportPath CHROMA_PATH=Path("data/chroma")_client=chromadb.PersistentClient(path=str(CHROMA_PATH))_collection=_client.get_or_create_collection(name="conversations",metadata={"hnsw:space":"cosine"},# 余弦相似度)defsave_conversation(session_id:str,user_message:str,ai_reply:str):"""保存一轮对话到向量库"""doc=f"用户:{user_message}\n助手:{ai_reply}"doc_id=f"{session_id}_{int(time.time()*1000)}"_collection.add(documents=[doc],metadatas=[{"session_id":session_id,"timestamp":str(int(time.time()))}],ids=[doc_id],)

ChromaDB 会自动用内置的 embedding 模型把文本转成向量存储。查询时也是先把 query 转向量,再做余弦相似度匹配。

Step 3:检索——中文场景的坑

理论上,检索应该用向量相似度来匹配。但实际跑下来发现一个问题:ChromaDB 默认的 Embedding 模型(all-MiniLM-L6-v2)对中文支持很差

比如你存了“北京今天晴天 25°C”,查“北京天气”可能检索不到——因为英文模型对中文语义的理解很有限。

解决方案有两条路:

  • 换中文 Embedding 模型(如 text2vec-chinese、bge-base-zh),但需要额外下载模型,部署成本高;
  • 关键词检索兜底:用滑动窗口提取 2-4 字的 n-gram,做字面匹配;

这里我们关键词检索兜底,学习阶段够用,而且不依赖额外模型:

defsearch_relevant(query:str,top_k:int=3)->list[str]:"""根据当前问题检索相关内容(关键词匹配兜底)"""if_collection.count()==0:return[]results_with_score=[]all_data=_collection.get(include=["documents","metadatas"])query_terms=_extract_terms(query)# 提取检索词fordoc,metainzip(all_data["documents"],all_data["metadatas"]):score=0forterminquery_terms:iftermindoc.lower():score+=len(term)ifscore>0:# 知识库内容加权(优先级更高)ifmetaandmeta.get("session_id")=="knowledge":score*=3results_with_score.append((score,doc))results_with_score.sort(key=lambdax:x[0],reverse=True)return[docfor_,docinresults_with_score[:top_k]]def_extract_terms(text:str)->list[str]:"""从文本中提取检索词(2-4字滑动窗口 + 英文单词)"""clean=''.join(cforcintextifc.isalnum()orcin'的了吗呢是')terms=set()forsizein[2,3,4]:foriinrange(len(clean)-size+1):term=clean[i:i+size]iftermnotin('的了','了吗','吗呢','是的'):terms.add(term)# 英文单词也加入importreforwinre.findall(r'[a-zA-Z]+',text):iflen(w)>=2:terms.add(w.lower())returnlist(terms)

这个方案不完美,但在中文场景下比默认的向量检索靠谱得多。后续如果要提升效果,换一个中文 Embedding 模型就行,检索接口不用改。

Step 4:把 RAG 接入 chat_agent

chat_agent 在回答之前,先用当前问题去向量库检索相关内容,找到了就拼进 System Prompt:

defchat_agent_node(state):"""聊天 Agent:检索相关历史 + 调 LLM 回答"""# 取最后一条用户消息last_user_msg=""formsginreversed(state["messages"]):ifhasattr(msg,"type")andmsg.type=="human":last_user_msg=msg.contentbreak# RAG 检索prompt=CHAT_PROMPTiflast_user_msg:relevant=search_relevant(last_user_msg,top_k=3)ifrelevant:memory_context="\n\n".join(relevant)prompt+=f"\n\n【重要】以下是相关的参考资料和历史对话,请优先基于这些内容回答:\n\n{memory_context}"messages=[SystemMessage(content=prompt)]+list(state["messages"])response=invoke_llm(messages)return{"messages":[response]}

逻辑很简单:有相关内容就塞进去,没有就正常回答。LLM 会自动判断检索到的内容是否有用。

Step 5:知识库导入

除了对话历史,我们还可以主动往向量库里灌知识。比如把公司文档、产品说明喂进去,Agent 就能回答相关问题。

导入时用session_id="knowledge"作为标识,检索时对知识库内容加权(×3),优先返回:

# 导入知识save_conversation(session_id="knowledge",user_message="项目用了哪些技术栈?",ai_reply="项目前端用 Vue3 + TypeScript,后端用 FastAPI + LangGraph,向量库用 ChromaDB...")# 检索时知识库内容权重更高ifmeta.get("session_id")=="knowledge":score*=3

当我们提问内容中有关键词,就会触发 RAG 检索,匹配到的信息用来丰富上下文,然后再塞给 LLM 分析总结——这就是 RAG 的魅力。

Step 6:验证效果

用 curl 快速验证:

# 第一轮:告诉 Agent 信息curl-XPOST /api/chat-d'{"message": "我叫小明,最喜欢吃火锅", "session_id": "test-1"}'# → "好的小明,记住了!火锅确实好吃。"# 第二轮:同一个 session,Agent 记得curl-XPOST /api/chat-d'{"message": "我喜欢吃什么?", "session_id": "test-1"}'# → "你之前说最喜欢吃火锅!"# 第三轮:换一个 session,短期记忆失效,但长期记忆能检索到curl-XPOST /api/chat-d'{"message": "小明喜欢吃什么?", "session_id": "test-2"}'# → "根据之前的对话记录,小明最喜欢吃火锅。"

在 Web 界面上的效果:

刨根问底

序号问题
1️⃣Q:为什么不直接把所有历史都塞进 Prompt?
A:Token 限制 + 噪声问题。假设每轮对话 200 token,聊 50 轮就是 10K token,再加上 System Prompt 和工具描述,很容易超限。而且无关的历史会分散 LLM 的注意力,回答质量反而下降。
2️⃣Q:ChromaDB 默认 embedding 对中文效果怎么样?
A:不太行。默认用的 all-MiniLM-L6-v2 是英文模型,中文语义理解很弱。生产环境建议换 bge-base-zh 或 text2vec-chinese。我们用关键词检索兜底,学习阶段够用。
3️⃣Q:短期记忆为什么用 JSON 不用 Redis?
A:学习阶段追求直观可调试——打开文件就能看到完整对话历史,方便排查问题。生产环境肯定要换 Redis 或数据库,支持过期、并发、持久化。

本篇小结

  • LLM 本身无状态,“记忆”全靠工程手段实现;
  • 短期记忆(JSON 文件)解决会话内连贯性,长期记忆(ChromaDB)解决跨会话检索;
  • RAG 的本质是“先查资料再回答”,把检索结果塞进 Prompt 就行;
  • 中文 embedding 是个坑,关键词检索是务实的兜底方案;

写在最后

记忆是 Agent 从“工具”变成“助手”的关键一步。一个能记住你偏好的 Agent,和一个每次都要重新自我介绍的 Agent,体验差距是巨大的。

但记忆也带来了新问题:存什么、存多久、什么时候该忘记?这些都是工程上需要持续优化的点。目前我们的方案够用,但离“好用”还有距离——比如对话摘要压缩、记忆淘汰策略、多用户隔离等,后续有机会再聊,感兴趣的小伙伴也可以慢慢摸索。

我们这个系列最关键还是了解 Agent 核心构建过程和设计思路,真正企业级产品针对各个技术点会有更加完善的解决方案。

下一篇预告:Agent 现在有了记忆,但它的能力还是被我们手写的几个工具限制住了。下一篇是系列完结篇,最后聊聊 MCP(Model Context Protocol),让 Agent 能调用整个开放工具生态,顺便回顾整个系列的全景架构。

http://www.jsqmd.com/news/900177/

相关文章:

  • 企业级集成怎么选:n8n、Zapier还是RestCloud iPaaS?
  • 2026年 东莞遮光膜厂家推荐排行榜:mini遮光膜/PET遮光膜/点阵遮光膜/黑色遮光膜/LED遮光膜/防漏光遮光膜优质品牌深度解析 - 品牌企业推荐师(官方)
  • 论文ai痕迹去不掉怎么办?2026年5月4款降AI工具深度推荐
  • 基于监督学习的工业物联网无线干扰识别:从原理到嵌入式实现
  • 2026年5月比较好的家电清洗公司哪家权威厂家推荐榜,油烟机深度清洗、空调全拆清洗、洗衣机夹层除菌清洗、冰箱及地暖清洗厂家选择指南 - 海棠依旧大
  • macOS Sequoia上如何安装gcc/g++环境?
  • 一站式搞定Invar 36现货:多规格棒材带材的优质供应网络汇总 - 品牌2025
  • Arm编译器v5到v6预定义宏迁移实战指南
  • 别再死记硬背L1、L2范数了!用Python可视化带你直观理解Lp范数家族
  • 2026年|论文去AI痕迹指南:DeepSeek降AI指令+3款工具测评(降至10%) - 降AI实验室
  • 2026年Q2专业的宁波公职面试培训公司:深度解析宁波彤心教育科技有限公司 - 2026年企业资讯
  • CSE-CIC-IDS2018数据集实战:如何用Python预处理CSV文件并快速开始你的入侵检测模型训练
  • 2026年 木屋厂家推荐排行榜:实木/防腐/原木/轻型/重型/景区/民宿/度假/网红/别墅/移动木屋及文旅木屋定制品牌与优质厂家推荐 - 品牌企业推荐师(官方)
  • [仅仅两步]的电信IPTV单线复用
  • 2026年论文降重指南:DeepSeek降AI指令与3款工具亲测解析(90%降至10%) - 降AI实验室
  • Board Scout:基于数据挖掘的棋牌游戏威胁预警系统设计与实现
  • 别再死记硬背公式了!用Python模拟一个天气预测的马尔可夫链(附完整代码)
  • 别再被‘鬼影’迷惑了!用Python模拟雷达多重频解距离模糊(附代码)
  • 2026年 吉帕钢HC1000/1470DP厂家推荐榜:宝钢超高强度钢,轻量化工艺与抗疲劳性能深度解析 - 品牌企业推荐师(官方)
  • 从想法到上线:我用AI在一天内“摸”出了一个面试文档系统
  • 车载以太网之要火系列 - 第53篇:郭大侠学DDS(数据帧):数据入帧君需知,序列化后力道施
  • 2026年 宝钢镀锌HC420/780DHD+Z吉帕钢厂家推荐榜单:超高强度/轻量化/汽车用先进高强钢品牌深度解析 - 品牌企业推荐师(官方)
  • 2026年当前本地花洒哪家强?长治科勒卫浴旗舰店深度测评与专业解析 - 2026年企业资讯
  • Scanpy实战:从10x Genomics原始数据到发表级图表,一篇就够了
  • 2026年5月,昆山市知名的空调维修服务商如何选?这份专业推荐指南给你答案 - 2026年企业资讯
  • 2026年5月新消息:广东财富传承律师咨询推荐深度解析 - 2026年企业资讯
  • 图神经网络在接触力学中的高效应用与优化
  • 一个开发工程师每天怎么用 Git + Gerrit 协作开发代码。
  • 创业团队如何建立招聘流程
  • 我用了几个月向量引擎 API 中转站后,整理出这份普通人也能看懂的实测笔记