【第三周】RAG与Agent实战22:临时会话记忆 —— 让AI拥有“短期记忆”

【第三周】RAG与Agent实战22:临时会话记忆 —— 让AI拥有“短期记忆” 在前面构建的 Chain 都是“无状态”的。也就是说你问它“小明有几只猫”它回答“2只”紧接着你问“那总共有几个宠物”它会一脸茫然因为它根本不记得上一句说了什么。大模型本身是无状态的Stateless每次请求对它来说都是全新的开始。要想实现像微信聊天那样有来有往的多轮对话Multi-turn Conversation我们需要引入**历史记录Message History**机制。今天我们就来学习 LangChain 提供的“记忆外挂”RunnableWithMessageHistory。 核心概念如何给链加上记忆LangChain 并没有修改底层的 Model 或 Prompt而是采用了一种装饰器模式Wrapper Pattern。基础链 (Base Chain)负责处理单次的“提示词 - 模型 - 输出”逻辑。记忆包装器 (History Wrapper)RunnableWithMessageHistory。它在基础链的外面包了一层负责读取在发送请求前从存储中读取历史对话。注入将历史对话插入到 Prompt 的特定位置。保存在收到回复后将新的问答对存入存储。关键组件组件作用类比RunnableWithMessageHistory核心包装类赋予 Chain 记忆能力给手机装上“录音笔”和“回放机”InMemoryChatMessageHistory内存存储器临时存放对话记录手机的 RAM断电/重启后数据丢失MessagesPlaceholderPrompt 中的占位符告诉 AI 历史放哪里剧本里的“[此处插入前情提要]”session_id会话标识区分不同用户的记忆不同的“聊天记录文件夹” 实战代码构建一个有记忆的聊天机器人我们将构建一个简单的场景用户说“小明有2只猫”。用户说“小刚有1只狗”。用户问“共有几个宠物”目标AI 能结合前两句的信息回答出“3个”。完整代码实现fromlangchain_community.chat_models.tongyiimportChatTongyifromlangchain_core.promptsimportChatPromptTemplate,MessagesPlaceholderfromlangchain_core.output_parsersimportStrOutputParserfromlangchain_core.runnables.historyimportRunnableWithMessageHistoryfromlangchain_core.chat_historyimportInMemoryChatMessageHistory# ---------------------------------------------------------# 1. 初始化模型与基础组件# ---------------------------------------------------------modelChatTongyi(modelqwen-plus)str_parserStrOutputParser()# ---------------------------------------------------------# 2. 定义带有“记忆插槽”的提示词# ---------------------------------------------------------# 注意这里使用了 ChatPromptTemplate 而不是普通的 PromptTemplatepromptChatPromptTemplate.from_messages([(system,你需要根据会话历史回应用户问题。对话历史),# 这是一个占位符历史消息会被自动填充到这里MessagesPlaceholder(chat_history),(human,请回答如下问题{input})])# 调试函数打印最终发送给模型的完整 Prompt方便观察历史是否注入defprint_prompt(full_prompt):print(\n*20 当前完整Prompt *20)print(full_prompt.to_string())print(*50\n)returnfull_prompt# 构建基础链此时还没有记忆功能base_chainprompt|print_prompt|model|str_parser# ---------------------------------------------------------# 3. 设置记忆存储 (Memory Store)# ---------------------------------------------------------# 用一个字典模拟数据库key是session_idvalue是历史对象store{}defget_history(session_id): 根据 session_id 获取对应的历史记录对象。 如果不存在则创建一个新的 InMemoryChatMessageHistory。 ifsession_idnotinstore:store[session_id]InMemoryChatMessageHistory()returnstore[session_id]# ---------------------------------------------------------# 4. 创建带记忆的增强链# ---------------------------------------------------------conversation_chainRunnableWithMessageHistory(base_chain,# 被增强的原有 chainget_history,# 获取历史记录的函数input_messages_keyinput,# 用户当前输入在 dict 中的 keyhistory_messages_keychat_history# 历史记录在 Prompt 中的占位符 key)# ---------------------------------------------------------# 5. 执行多轮对话# ---------------------------------------------------------if__name____main__:# 配置 Session ID告诉系统这是哪个用户在说话# 必须通过 config 参数传递这是 LangChain 的标准做法session_config{configurable:{session_id:user_001}}print(️ 第一轮告知事实)resconversation_chain.invoke({input:小明有2只猫},configsession_config)print(fAI:{res}\n)print(️ 第二轮告知更多事实)resconversation_chain.invoke({input:小刚有1只狗},configsession_config)print(fAI:{res}\n)print(️ 第三轮基于历史提问)resconversation_chain.invoke({input:他们一共有几个宠物},configsession_config)print(fAI:{res}\n)⚙️ 运行流程深度解析1. 第一次调用 (input: “小明有2只猫”)读取历史get_history(user_001)发现是新的返回空的历史列表[]。组装 PromptSystem: 你需要根据会话历史... Human: 请回答如下问题小明有2只猫(注意此时 chat_history 是空的)AI 回复AI 确认收到了信息例如“好的我知道了小明有2只猫。”。保存历史系统将Human: 小明有2只猫和AI: ...存入store[user_001]。2. 第二次调用 (input: “小刚有1只狗”)读取历史从store中取出上一轮的对话。组装 PromptSystem: 你需要根据会话历史... Human: 小明有2只猫 AI: 好的我知道了... Human: 请回答如下问题小刚有1只狗(注意历史消息被自动插入了)保存历史将新的一轮对话追加进去。3. 第三次调用 (input: “他们一共有几个宠物”)读取历史取出前两轮的所有对话。组装 Prompt包含了“2只猫”和“1只狗”的完整上下文。AI 回复AI 根据上下文推理出 “2 1 3”回答“他们一共有3个宠物”。 关键点详解1. 为什么需要config参数你会发现invoke时多了一个configsession_config。这是 LangChain 的设计哲学将“运行时配置”与“业务数据”分离。{input: ...}是业务数据。{configurable: {session_id: ...}}是运行环境配置。这样设计的好处是同一个 Chain 对象可以服务于成千上万个不同的用户只需要在调用时传入不同的session_id即可无需为每个用户创建一个新的 Chain 对象。2.InMemoryChatMessageHistory的局限性代码中使用的是内存存储。这意味着✅优点速度快无需配置数据库适合测试和本地演示。❌缺点一旦程序重启或服务器宕机所有聊天记录瞬间消失。生产环境方案在实际项目中我们会使用RedisChatMessageHistory或PostgresChatMessageHistory将记录持久化到数据库中。3.MessagesPlaceholder的作用它不仅仅是一个字符串替换。它是一个智能组件知道如何将BaseMessage列表历史对象正确地格式化为模型能理解的 Message 结构。 总结本节课我们实现了 AI 的“短期记忆”RunnableWithMessageHistory学会了如何使用这个包装器零代码修改基础逻辑直接赋予 Chain 记忆能力。存储管理理解了get_history函数的作用它是连接 Chain 和存储介质的桥梁。会话隔离掌握了通过session_id和config来区分不同用户聊天的技术。现在你的 AI 已经可以进行连续对话了但这还不够它的记忆仅限于当前窗口内存。