在前面构建的 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个”。 完整代码实现from langchain_community.chat_models.tongyi import ChatTongyi from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_core.chat_history import InMemoryChatMessageHistory # --------------------------------------------------------- # 1. 初始化模型与基础组件 # --------------------------------------------------------- model = ChatTongyi(model="qwen-plus") str_parser = StrOutputParser() # --------------------------------------------------------- # 2. 定义带有“记忆插槽”的提示词 # --------------------------------------------------------- # 注意:这里使用了 ChatPromptTemplate 而不是普通的 PromptTemplate prompt = ChatPromptTemplate.from_messages([ ("system", "你需要根据会话历史回应用户问题。对话历史:"), # 这是一个占位符,历史消息会被自动填充到这里 MessagesPlaceholder("chat_history"), ("human", "请回答如下问题:{input}") ]) # 调试函数:打印最终发送给模型的完整 Prompt,方便观察历史是否注入 def print_prompt(full_prompt): print("n" + "="*20 + " 当前完整Prompt " + "="*20) print(full_prompt.to_string()) print("="*50 + "n") return full_prompt # 构建基础链(此时还没有记忆功能) base_chain = prompt | print_prompt | model | str_parser # --------------------------------------------------------- # 3. 设置记忆存储 (Memory Store) # --------------------------------------------------------- # 用一个字典模拟数据库,key是session_id,value是历史对象 store = {} def get_history(session_id): """ 根据 session_id 获取对应的历史记录对象。 如果不存在,则创建一个新的 InMemoryChatMessageHistory。 """ if session_id not in store: store[session_id] = InMemoryChatMessageHistory() return store[session_id] # --------------------------------------------------------- # 4. 创建带记忆的增强链 # --------------------------------------------------------- conversation_chain = RunnableWithMessageHistory( base_chain, # 被增强的原有 chain get_history, # 获取历史记录的函数 input_messages_key="input", # 用户当前输入在 dict 中的 key history_messages_key="chat_history" # 历史记录在 Prompt 中的占位符 key ) # --------------------------------------------------------- # 5. 执行多轮对话 # --------------------------------------------------------- if __name__ == "__main__": # 配置 Session ID,告诉系统这是哪个用户在说话 # 必须通过 config 参数传递,这是 LangChain 的标准做法 session_config = { "configurable": { "session_id": "user_001" } } print("️ 第一轮:告知事实") res = conversation_chain.invoke({"input": "小明有2只猫"}, config=session_config) print(f"AI: {res}n") print("️ 第二轮:告知更多事实") res = conversation_chain.invoke({"input": "小刚有1只狗"}, config=session_config) print(f"AI: {res}n") print("️ 第三轮:基于历史提问") res = conversation_chain.invoke({"input": "他们一共有几个宠物?"}, config=session_config) print(f"AI: {res}n")
python
运行
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
System: 你需要根据会话历史... Human: 请回答如下问题:小明有2只猫
text
12 (注意:此时 chat_history 是空的)AI 回复:AI 确认收到了信息(例如:“好的,我知道了,小明有2只猫。”)。保存历史:系统将 Human: 小明有2只猫 和 AI: ... 存入 store["user_001"]。 2. 第二次调用 (input: “小刚有1只狗”) 读取历史:从 store 中取出上一轮的对话。组装 Prompt:System: 你需要根据会话历史... Human: 小明有2只猫 AI: 好的,我知道了... Human: 请回答如下问题:小刚有1只狗
text
1234 (注意:历史消息被自动插入了!)保存历史:将新的一轮对话追加进去。 3. 第三次调用 (input: “他们一共有几个宠物?”) 读取历史:取出前两轮的所有对话。组装 Prompt:包含了“2只猫”和“1只狗”的完整上下文。AI 回复:AI 根据上下文推理出 “2 + 1 = 3”,回答 “他们一共有3个宠物”。你会发现 invoke 时多了一个 config=session_config。
这是 LangChain 的设计哲学:将“运行时配置”与“业务数据”分离。
代码中使用的是内存存储。这意味着:
✅ 优点:速度快,无需配置数据库,适合测试和本地演示。❌ 缺点:一旦程序重启或服务器宕机,所有聊天记录瞬间消失。 生产环境方案:在实际项目中,我们会使用 RedisChatMessageHistory 或 PostgresChatMessageHistory,将记录持久化到数据库中。 3. MessagesPlaceholder 的作用它不仅仅是一个字符串替换。它是一个智能组件,知道如何将 BaseMessage 列表(历史对象)正确地格式化为模型能理解的 Message 结构。
本节课我们实现了 AI 的“短期记忆”:
RunnableWithMessageHistory:学会了如何使用这个包装器,零代码修改基础逻辑,直接赋予 Chain 记忆能力。存储管理:理解了 get_history 函数的作用,它是连接 Chain 和存储介质的桥梁。会话隔离:掌握了通过 session_id 和 config 来区分不同用户聊天的技术。现在,你的 AI 已经可以进行连续对话了!但这还不够,它的记忆仅限于当前窗口(内存)。
相关知识
【第三周】RAG与Agent实战22:临时会话记忆 —— 让AI拥有“短期记忆”
洪水过后狗狗带着鹅回家:长期记忆与短期记忆的差异
智能宠物玩具:AI Agent的宠物智力开发
如何提高英语单词学习的短期记忆效率
狗狗的记忆:解析它们的记忆机制
智能宠物屋:AI Agent的宠物行为分析与训练
记忆、认知与记忆本体论
智能宠物玩具:AI Agent的互动模式调整
记忆
小猫有记忆吗
网址: 【第三周】RAG与Agent实战22:临时会话记忆 —— 让AI拥有“短期记忆” https://m.mcbbbk.com/newsview1358202.html
| 上一篇: 澳洲和加拿大宠物类目流量是不是很 |
下一篇: 阿拉斯加犬吃什么 |