【三个月 AI Agent 实战学习】Day 31:带记忆的聊天机器人 —— 结合 RAG 与 Memory
Day 31带记忆的聊天机器人 —— 结合 RAG 与 Memory欢迎来到第三十一天今天我们终于将记忆与知识库结合起来构建一个真正实用的带记忆的聊天机器人。这个机器人不仅能记住用户的名字、喜好等个性化信息还能基于本地文档RAG回答问题并且在多轮对话中保持上下文连贯。这个项目将综合运用前几天的知识RAG 管道、向量存储、对话记忆管理以及 LangChain 的链式组合。一、今日学习目标学会将 RAG 检索器和对话记忆组件整合到同一个聊天链中。掌握如何在多轮对话中同时维护用户个性化信息和知识库检索。理解 LangChain 中RunnableWithMessageHistory或手动管理消息历史的方法。通过实战项目“书童机器人”体验带记忆的 RAG 聊天机器人的完整实现。能够根据需求选择合适的记忆策略和 RAG 参数。二、详细实现步骤步骤 1设计“书童机器人”的功能我们的书童机器人应具备以下能力记住用户的名字、喜好例如喜欢读什么类型的书、喜欢的作家等。基于本地知识库例如几篇关于文学、历史的文章回答问题。多轮对话中保持上下文例如用户问“我最喜欢的作家是谁”机器人能根据之前的记忆回答。我们将使用短期记忆用ConversationBufferMemory或ConversationSummaryMemory保存对话历史包括用户个性化信息。RAG使用 Chroma 向量库存储知识文档检索相关片段。LangChain 的 LCEL组合检索器、提示词模板、模型和记忆。步骤 2环境准备确保已安装所有必要库pipinstalllangchain langchain-openai langchain-community chromadb新建memory_rag_chatbot.py导入所需模块importosfromdotenvimportload_dotenvfromlangchain_openaiimportChatOpenAI,OpenAIEmbeddingsfromlangchain_community.vectorstoresimportChromafromlangchain_core.promptsimportChatPromptTemplate,MessagesPlaceholderfromlangchain_core.output_parsersimportStrOutputParserfromlangchain.memoryimportConversationBufferMemoryfromlangchain_core.runnablesimportRunnablePassthrough,RunnableLambdafromlangchain_core.messagesimportHumanMessage,AIMessage加载环境变量并初始化模型和 Embeddingload_dotenv()llmChatOpenAI(modeldeepseek-chat,api_keyos.getenv(DEEPSEEK_API_KEY),base_urlhttps://api.deepseek.com,temperature0.3)embeddingsOpenAIEmbeddings(modelembedding-2,api_keyos.getenv(ZHIPU_API_KEY),base_urlhttps://open.bigmodel.cn/api/paas/v4)步骤 3准备知识库我们准备几个文本文件内容可以是关于文学、历史的知识。为了简单我们直接在代码中定义几个段落或者从docs/目录加载假设之前已创建。这里我们直接创建一些示例文档存入 Chroma。# 定义知识库文档documents[《红楼梦》是中国古典四大名著之一作者曹雪芹。小说以贾宝玉和林黛玉的爱情悲剧为主线展现了封建社会的衰落。,《百年孤独》是哥伦比亚作家加西亚·马尔克斯的代表作属于魔幻现实主义文学流派。,《三体》是刘慈欣创作的科幻小说讲述了地球文明与三体文明的信息交流、生死搏杀及两个文明在宇宙中的兴衰历程。,《活着》是余华的代表作讲述了一个人历经世间沧桑和苦难的一生。,《小王子》是法国作家安托万·德·圣埃克苏佩里的著名童话讲述了一个来自外星球的小王子的旅行故事。]# 创建向量存储使用 ChromavectorstoreChroma.from_texts(textsdocuments,embeddingembeddings,collection_namebook_knowledge)retrievervectorstore.as_retriever(search_kwargs{k:2})# 检索前2个相关片段步骤 4定义提示词模板我们需要一个包含对话历史和检索上下文的提示词。对话历史通过MessagesPlaceholder注入检索上下文通过context变量注入。promptChatPromptTemplate.from_messages([(system,你是一个博学的书童名叫小书。你能记住用户的姓名和喜好并且可以根据知识库回答关于书籍的问题。\n回答问题时请优先使用提供的上下文如果上下文没有相关信息可以结合你自己的知识。\n\n知识库检索结果\n{context}),MessagesPlaceholder(variable_namehistory),(human,{input})])步骤 5组合链并加入记忆我们使用 LCEL 构建链并手动管理记忆因为ConversationChain不适合直接与 RAG 组合。思路每次调用时从记忆中加载历史消息。将用户输入与历史消息合并执行检索仅基于当前输入或基于整个对话进行检索这里简化只用当前输入检索。将检索结果、历史消息和用户输入填入提示词。调用模型生成回答。将用户输入和模型回答存入记忆。我们编写一个自定义函数chat()来完成这些步骤。# 创建记忆对象memoryConversationBufferMemory(return_messagesTrue)defchat(user_input:str)-str:# 1. 检索相关文档只根据当前输入docsretriever.get_relevant_documents(user_input)context\n\n.join(doc.page_contentfordocindocs)# 2. 获取历史消息historymemory.chat_memory.messages# 3. 构造消息列表messages[(system,prompt.messages[0].prompt.template.format(contextcontext)),*[(msg.type,msg.content)formsginhistory],(human,user_input)]# 4. 调用模型直接使用 llm.invoke传入消息列表responsellm.invoke(messages)answerresponse.content# 5. 更新记忆memory.chat_memory.add_user_message(user_input)memory.chat_memory.add_ai_message(answer)returnanswer注意这里我们手动处理了提示词格式化因为 LCEL 与记忆集成的标准方式需要更复杂的设置如使用RunnableWithMessageHistory。为了清晰展示原理我们采用手动方式这也有助于你理解底层流程。步骤 6测试多轮对话现在测试书童机器人print( 书童机器人对话测试 )print(用户我叫小明我喜欢科幻小说。)print(小书,chat(我叫小明我喜欢科幻小说。))print(\n用户你记得我叫什么吗)print(小书,chat(你记得我叫什么吗))print(\n用户推荐一本我可能喜欢的书。)print(小书,chat(推荐一本我可能喜欢的书。))print(\n用户《三体》的作者是谁)print(小书,chat(《三体》的作者是谁))观察输出机器人应该能够记住用户的名字和喜好。在推荐书籍时结合用户喜好科幻推荐《三体》。回答关于《三体》作者的问题从知识库检索或模型自身知识。步骤 7优化与扩展如果知识库文档较多可以将文档从文件中加载参考 Day 18 的TextLoader和DirectoryLoader。还可以改进检索策略例如结合对话历史进行查询扩展比如提取用户喜好用于检索。但今天先保持简单。同时我们可以使用ConversationSummaryMemory替代ConversationBufferMemory来节省 Token但为了保留精确的用户信息如名字、喜好Buffer 更合适因为摘要可能丢失细节。三、常见问题与调试Q1历史消息中包含系统消息吗→ 我们的memory.chat_memory.messages只存储 HumanMessage 和 AIMessage不包含系统消息。系统消息在每次调用时通过提示词模板动态生成这是合理的。Q2为什么使用llm.invoke(messages)而不是构建完整的 LCEL 链→ 因为记忆的加入使得流程稍微复杂手动调用更直观也更容易理解。当项目变复杂时可以使用 LangChain 的RunnableWithMessageHistory来标准化处理但今天手动方式已经足够。Q3检索只根据当前输入如果用户问“我最喜欢的作家是谁”而之前说过“我喜欢曹雪芹”检索不到相关信息怎么办→ 这个问题需要将用户个性化信息记忆也用于检索或者直接从记忆中提取答案。我们的书童机器人可以做到它从历史消息中知道用户喜欢曹雪芹因为之前聊过所以即使检索不到模型也能根据历史回答。这就是为什么我们保留了历史消息——它让模型拥有上下文。Q4知识库中的文档没有覆盖用户的问题模型会怎么回答→ 根据提示词设计如果上下文没有相关信息模型可以结合自身知识回答。如果模型也不确定可能会说不知道取决于提示词约束我们可以在系统提示中强调“如果无法从上下文或自身知识确定请说不知道”。Q5如何将记忆持久化到数据库以便重启后仍然保留→ 可以使用RedisChatMessageHistory或SQLChatMessageHistory将消息保存到外部存储。在创建记忆时指定chat_memory参数即可。例如fromlangchain.memory.chat_message_historiesimportSQLChatMessageHistory historySQLChatMessageHistory(session_iduser_123,connection_stringsqlite:///memory.db)memoryConversationBufferMemory(chat_memoryhistory,return_messagesTrue)这样每个用户有独立的 session实现多用户隔离。Q6为什么记忆中没有自动保存用户个性化信息→ 我们的记忆保存的是完整的对话消息模型可以通过历史消息了解用户信息。如果需要显式提取并存储用户画像可以使用更高级的记忆类型如ConversationEntityMemory它会自动提取实体如名字、喜好并存储。但今天不需要那么复杂。四、今日总结与作业今天你完成了✅ 将 RAG 检索与对话记忆结合构建了一个带记忆的聊天机器人。✅ 手动管理了消息历史理解了多轮对话中记忆与检索的协作方式。✅ 通过“书童机器人”实战体验了个性化与知识库问答的融合。✅ 学会了根据场景选择合适的记忆策略。今日作业必做为书童机器人添加更多知识库文档至少 10 条关于不同类型的书籍并测试它能否根据用户喜好如历史小说、推理小说推荐合适的书。将记忆改为ConversationSummaryMemory运行同样的对话对比 Token 消耗和信息保留情况比如用户名字是否还能记住。实现一个简单的用户偏好提取功能在对话中当用户说“我喜欢 XX”时自动将这条信息存储到一个独立的变量中例如user_profile字典并在后续对话中优先使用该信息进行检索比如用偏好关键词检索。描述你的实现思路可选写代码。思考题在构建客服 Agent 时记忆不仅包括对话历史还可能包括用户订单信息、历史工单等结构化数据。你如何将结构化数据与对话记忆结合请设计一个方案。明日预告我们将学习如何处理超长对话历史实现一个裁剪函数当对话历史超过阈值时自动总结或丢弃最早的消息确保 Agent 永远不会因 Token 超限而崩溃。有任何问题欢迎随时提问

相关新闻

2026张家口整体淋浴房厂家怎么选?避开排名陷阱的实用指南

2026张家口整体淋浴房厂家怎么选?避开排名陷阱的实用指南

每年一开春,就到了装修旺季,最近后台私信里问得最多的问题之一,就是“2026年张家口做整体淋浴房的厂家到底哪家靠谱”。说实话,很多准备装修的业主和正在找供应商的装企朋友,一上来就问“行业TOP排名”,这心…

2026/10/14 16:00:20 阅读更多 →
t3code编码规范体系:从设计到落地的工程实践指南

t3code编码规范体系:从设计到落地的工程实践指南

1. 从“t3code”这个名字说起:它到底是什么第一次看到“t3code”这个词,很多人会下意识地把它当成某个开源库、某个命令行工具,或者某个内部代号。我最初接触它的时候也是这个反应,翻了半天资料才发现,它并不是一个现成…

2026/10/12 10:17:08 阅读更多 →
pstack-claude:用Linux原生栈分析诊断本地Claude服务进程卡死

pstack-claude:用Linux原生栈分析诊断本地Claude服务进程卡死

1. 项目概述:pstack-claude 是什么,它解决的是哪类真实痛点?“pstack-claude”这个名称乍看像一个拼接词,但拆解后立刻能抓住两个关键锚点:pstack和claude。前者是 Linux 系统中一个极其轻量却极为实用的诊断工具&…

2026/10/11 12:33:46 阅读更多 →

最新新闻

Elasticsearch集群运维:索引模板与ILM生命周期策略实战

Elasticsearch集群运维:索引模板与ILM生命周期策略实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 5:22:10 阅读更多 →
MATLAB图像处理工具箱:交互式设计与性能优化

MATLAB图像处理工具箱:交互式设计与性能优化

1. 项目背景与设计思路去年在实验室带本科生做毕业设计时,发现很多同学虽然学了数字图像处理理论,但真正动手实现时却无从下手。市面上现有的MATLAB图像处理工具箱要么过于学术化,要么封装得太"黑箱"。于是花了三个月时间&#xff…

2026/10/15 5:22:10 阅读更多 →
Coze智能体开发实战:工作流逻辑与应用交付深度指南

Coze智能体开发实战:工作流逻辑与应用交付深度指南

1. 这不是又一个“Coze速成课”,而是我用三个月踩出的智能体开发真实路径你点开这个标题,大概率是被“B站讲得最好”“从入门到精通”这类词吸引来的。但我想先说清楚:Coze本身没有魔法,它只是一把结构清晰、手感扎实的螺丝刀——…

2026/10/15 5:22:10 阅读更多 →
字节Seed独立设立大模型安全与对齐部门,对齐到底在做什么

字节Seed独立设立大模型安全与对齐部门,对齐到底在做什么

据报道,10 月 13 日字节跳动 AI 模型部门 Seed 通过内部通知宣布,单独成立一个专门负责大模型安全与对齐的新部门,名叫 “Seed Model - Alignment & Preparedness”,由严林负责,直接向 Seed 负责人吴永辉汇报&…

2026/10/15 5:22:10 阅读更多 →
Nginx 限流实战:防 CC 攻击和爬虫的四个配置

Nginx 限流实战:防 CC 攻击和爬虫的四个配置

背景 服务器上有个公开的 dashboard,虽然不对外宣传,但日志里经常看到: 同一个 IP 一秒钟请求几十次 某个爬虫把整站扫了一遍 偶尔有恶意请求把 CPU 拉满 fail2ban 能封 IP,但它是事后——等攻击发生了才封。 想做到&#xf…

2026/10/15 5:22:10 阅读更多 →
Qt音频EQ曲线绘制重构:滤波器分层、对数采样与性能优化

Qt音频EQ曲线绘制重构:滤波器分层、对数采样与性能优化

做桌面音频工具这行,EQ曲线是个看似简单、实际一碰就头疼的模块。最近我把手头Qt源码里的EQ曲线绘制整体重写了一遍,完成了一次“升级版”重构。这次升级的重点不在样式的花活,而是把所有计算重新分层、把每个公式的来龙去脉整理成详尽注释&a…

2026/10/15 5:21:10 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →