DeepSeek 官网能记住我叫大煊、刚才聊啥同样的问题搬到本地invoke调模型第二轮它直接回「我不知道你刚才提到的名字」。模型没坏是我没给记忆。对 Javaer 来说LangChain 的 Memory记忆就像HttpSession 里塞对话记录或者 ThreadLocal 里挂的上下文——不是大模型自带记忆是你在每次请求前后维护一份「聊天记录副本」再塞给模型看。我从对照实验到RunnableWithMessageHistory带历史消息的可运行链跑通的最短路径内存版 → 多 session 隔离 → 可选 Redis 落盘。真相失忆发生在应用层不在模型里模型本身不会自动记住上一轮。是应用层在读历史、写历史、拼 Prompt提示词。实现记忆就三件事读从记忆组件取出历史对话拼历史 当前用户输入塞进 Prompt传给模型写本轮问答写回记忆组件可以记一句Memory 不是让模型变聪明是让每次请求变「完整」。用户输入Prompt: history questionMemoryModel回答RunnableWithMessageHistoryLangChain 早期有个ConversationChain几行代码就能聊但提示模板和内存逻辑绑死跟 LCEL 链式调用的Runnable体系不太合拍。LangChain 0.3.x 起推荐RunnableWithMessageHistory你的链还是prompt | llm | parser外面套一层壳通过get_session_history回调决定历史存哪儿。跟 Spring 从「一个大而全的 Facade」往「小接口 组合」演进的路数有点像——换存储后端不用动链本身。自己往history.messages里塞消息再llm.invoke也能拼但 Runnable替你自动化了读 → 拼 → 写那一圈。存储后端怎么选实现特性InMemoryChatMessageHistory内存进程退出即丢FileChatMessageHistory文件单机持久化RedisChatMessageHistoryRedis多实例共享、可持久化ElasticsearchChatMessageHistoryES适合检索型场景BaseChatMessageHistory是用来保存聊天消息历史的抽象基类,其中最重要就是清空所有消息和添加消息点开抽象基类可以看到Langchain可选的记忆存储组件还是有很多选型对照实验没 Memory 的「我不知道」跑下面这段两轮invoke彼此独立——上一轮的内容根本没传进去importosfromdotenvimportload_dotenvfromlangchain_core.output_parsersimportStrOutputParserfromlangchain_core.promptsimportPromptTemplatefromlangchain.chat_modelsimportinit_chat_model load_dotenv()llminit_chat_model(modelos.getenv(DEEPSEEK_MODEL,deepseek-chat),model_provideropenai,api_keyos.getenv(DEEPSEEK_API_KEY),temperature0.0,base_urlos.getenv(DEEPSEEK_BASE_URL,https://api.deepseek.com),)promptPromptTemplate.from_template(请根据用户问题作答{question})chainprompt|llm|StrOutputParser()print(chain.invoke({question:你好我是大煊你怎么称呼}))print(chain.invoke({question:你还记得我刚才自我介绍的名字吗}))自动挡多 session RunnableWithMessageHistory真实项目里不同用户得隔离历史。用一个dict按session_id会话标识存各自的InMemoryChatMessageHistory——跟 Java 里MapString, HttpSession按 userId 取 Session 是一个路数。importosfromdotenvimportload_dotenvfromlangchain.chat_modelsimportinit_chat_modelfromlangchain_core.chat_historyimportInMemoryChatMessageHistoryfromlangchain_core.runnables.historyimportRunnableWithMessageHistoryfromlangchain_core.promptsimportChatPromptTemplate,MessagesPlaceholderfromlangchain_core.output_parsersimportStrOutputParser load_dotenv()llminit_chat_model(modelos.getenv(DEEPSEEK_MODEL,deepseek-chat),model_provideropenai,api_keyos.getenv(DEEPSEEK_API_KEY),base_urlos.getenv(DEEPSEEK_BASE_URL,https://api.deepseek.com),)store{}defget_session_history(session_id:str):ifsession_idnotinstore:store[session_id]InMemoryChatMessageHistory()returnstore[session_id]promptChatPromptTemplate.from_messages([(system,你是一个耐心的中文助理会结合上文连贯作答。),MessagesPlaceholder(history),(human,{question}),])memory_chainprompt|llm|StrOutputParser()with_historyRunnableWithMessageHistory(memory_chain,get_session_history,input_messages_keyquestion,history_messages_keyhistory,)cfg{configurable:{session_id:user-001}}print(AI,with_history.invoke({question:我是大煊Javaer 一枚。},cfg))print(AI,with_history.invoke({question:你记得我叫啥不},cfg))三个名字必须对齐模板里MessagesPlaceholder(history)、history_messages_keyhistory、input_messages_keyquestion与 Prompt 里 human 的{question}一致——我对过一次少一个历史就是空的模型每轮都像第一次见面。可选进阶Redis Stack内存版进程一挂历史就没了。要上生产或本地重启还想接着聊把get_session_history里的实现换成RedisChatMessageHistory就行——链和 Prompt 不用动只改回调。开发阶段注意REDIS_URL端口和 reids端口映射对齐有ttl的话历史可能被自动清掉。Redis Stack是redis的扩展版也是近年来redis的主推版本功能维度原生 RedisRedis Stack 增强功能数据结构字符串、列表、集合、哈希等增加 JSON、图、时间序列、概率结构等高级类型查询能力仅限键值查询支持全文搜索、向量搜索、图查询、JSON 查询使用场景缓存、消息队列、计数器等实时推荐、时序分析、知识图谱、文档数据库、AI 向量检索开发体验命令行操作需手动拼装逻辑提供 RedisInsight 和对象映射库开发效率更高复杂多 Agent 编排以后可以换 LangGraph 的 Checkpointer这是「AI Agent 实战踩坑」系列的第 5 篇。欢迎大家关注后续还会继续分享知识片段参考LangChain Short-term memory
模型没失忆,是你没给它记忆——速通 LangChain Memory最短路径
发布时间:2026/6/15 5:11:07
DeepSeek 官网能记住我叫大煊、刚才聊啥同样的问题搬到本地invoke调模型第二轮它直接回「我不知道你刚才提到的名字」。模型没坏是我没给记忆。对 Javaer 来说LangChain 的 Memory记忆就像HttpSession 里塞对话记录或者 ThreadLocal 里挂的上下文——不是大模型自带记忆是你在每次请求前后维护一份「聊天记录副本」再塞给模型看。我从对照实验到RunnableWithMessageHistory带历史消息的可运行链跑通的最短路径内存版 → 多 session 隔离 → 可选 Redis 落盘。真相失忆发生在应用层不在模型里模型本身不会自动记住上一轮。是应用层在读历史、写历史、拼 Prompt提示词。实现记忆就三件事读从记忆组件取出历史对话拼历史 当前用户输入塞进 Prompt传给模型写本轮问答写回记忆组件可以记一句Memory 不是让模型变聪明是让每次请求变「完整」。用户输入Prompt: history questionMemoryModel回答RunnableWithMessageHistoryLangChain 早期有个ConversationChain几行代码就能聊但提示模板和内存逻辑绑死跟 LCEL 链式调用的Runnable体系不太合拍。LangChain 0.3.x 起推荐RunnableWithMessageHistory你的链还是prompt | llm | parser外面套一层壳通过get_session_history回调决定历史存哪儿。跟 Spring 从「一个大而全的 Facade」往「小接口 组合」演进的路数有点像——换存储后端不用动链本身。自己往history.messages里塞消息再llm.invoke也能拼但 Runnable替你自动化了读 → 拼 → 写那一圈。存储后端怎么选实现特性InMemoryChatMessageHistory内存进程退出即丢FileChatMessageHistory文件单机持久化RedisChatMessageHistoryRedis多实例共享、可持久化ElasticsearchChatMessageHistoryES适合检索型场景BaseChatMessageHistory是用来保存聊天消息历史的抽象基类,其中最重要就是清空所有消息和添加消息点开抽象基类可以看到Langchain可选的记忆存储组件还是有很多选型对照实验没 Memory 的「我不知道」跑下面这段两轮invoke彼此独立——上一轮的内容根本没传进去importosfromdotenvimportload_dotenvfromlangchain_core.output_parsersimportStrOutputParserfromlangchain_core.promptsimportPromptTemplatefromlangchain.chat_modelsimportinit_chat_model load_dotenv()llminit_chat_model(modelos.getenv(DEEPSEEK_MODEL,deepseek-chat),model_provideropenai,api_keyos.getenv(DEEPSEEK_API_KEY),temperature0.0,base_urlos.getenv(DEEPSEEK_BASE_URL,https://api.deepseek.com),)promptPromptTemplate.from_template(请根据用户问题作答{question})chainprompt|llm|StrOutputParser()print(chain.invoke({question:你好我是大煊你怎么称呼}))print(chain.invoke({question:你还记得我刚才自我介绍的名字吗}))自动挡多 session RunnableWithMessageHistory真实项目里不同用户得隔离历史。用一个dict按session_id会话标识存各自的InMemoryChatMessageHistory——跟 Java 里MapString, HttpSession按 userId 取 Session 是一个路数。importosfromdotenvimportload_dotenvfromlangchain.chat_modelsimportinit_chat_modelfromlangchain_core.chat_historyimportInMemoryChatMessageHistoryfromlangchain_core.runnables.historyimportRunnableWithMessageHistoryfromlangchain_core.promptsimportChatPromptTemplate,MessagesPlaceholderfromlangchain_core.output_parsersimportStrOutputParser load_dotenv()llminit_chat_model(modelos.getenv(DEEPSEEK_MODEL,deepseek-chat),model_provideropenai,api_keyos.getenv(DEEPSEEK_API_KEY),base_urlos.getenv(DEEPSEEK_BASE_URL,https://api.deepseek.com),)store{}defget_session_history(session_id:str):ifsession_idnotinstore:store[session_id]InMemoryChatMessageHistory()returnstore[session_id]promptChatPromptTemplate.from_messages([(system,你是一个耐心的中文助理会结合上文连贯作答。),MessagesPlaceholder(history),(human,{question}),])memory_chainprompt|llm|StrOutputParser()with_historyRunnableWithMessageHistory(memory_chain,get_session_history,input_messages_keyquestion,history_messages_keyhistory,)cfg{configurable:{session_id:user-001}}print(AI,with_history.invoke({question:我是大煊Javaer 一枚。},cfg))print(AI,with_history.invoke({question:你记得我叫啥不},cfg))三个名字必须对齐模板里MessagesPlaceholder(history)、history_messages_keyhistory、input_messages_keyquestion与 Prompt 里 human 的{question}一致——我对过一次少一个历史就是空的模型每轮都像第一次见面。可选进阶Redis Stack内存版进程一挂历史就没了。要上生产或本地重启还想接着聊把get_session_history里的实现换成RedisChatMessageHistory就行——链和 Prompt 不用动只改回调。开发阶段注意REDIS_URL端口和 reids端口映射对齐有ttl的话历史可能被自动清掉。Redis Stack是redis的扩展版也是近年来redis的主推版本功能维度原生 RedisRedis Stack 增强功能数据结构字符串、列表、集合、哈希等增加 JSON、图、时间序列、概率结构等高级类型查询能力仅限键值查询支持全文搜索、向量搜索、图查询、JSON 查询使用场景缓存、消息队列、计数器等实时推荐、时序分析、知识图谱、文档数据库、AI 向量检索开发体验命令行操作需手动拼装逻辑提供 RedisInsight 和对象映射库开发效率更高复杂多 Agent 编排以后可以换 LangGraph 的 Checkpointer这是「AI Agent 实战踩坑」系列的第 5 篇。欢迎大家关注后续还会继续分享知识片段参考LangChain Short-term memory