news 2026/5/16 6:30:21

LangChain 实战:RunnableWithMessageHistory 深度详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LangChain 实战:RunnableWithMessageHistory 深度详解

在构建聊天机器人(Chatbot)时,**“记忆”(Memory)**是核心能力之一。早期的 LangChain 使用ConversationChain+Memory对象来管理历史,但在 LCEL(LangChain Expression Language)时代,官方推荐使用更灵活、更解耦的RunnableWithMessageHistory

本文将以一个完整的 Python 示例为基础,深入剖析RunnableWithMessageHistory的工作原理、核心参数及最佳实践。

1. 为什么需要它?

在没有RunnableWithMessageHistory之前,手动管理对话历史通常需要以下繁琐步骤:

  1. 查询:根据 User ID 从数据库查出历史记录。
  2. 拼接:手动把历史记录塞进 Prompt 中。
  3. 调用:执行 LLM。
  4. 保存:手动把 User Input 和 AI Output 追加保存回数据库。

RunnableWithMessageHistory就像一个自动化的切面(Aspect),它包装了你的 Chain,自动在后台完成了上述“查询-注入-保存”的所有工作,让你只需要关注当前轮次的交互。

2. 实战代码演示

以下是一个可运行的完整示例 (src/examples/memory/demo_runnable_with_history.py)。

2.1 准备工作

首先,我们需要定义基础组件:LLM、Prompt 以及历史记录的存储位置。

fromlangchain_core.promptsimportChatPromptTemplate,MessagesPlaceholderfromlangchain_core.runnables.historyimportRunnableWithMessageHistoryfromlangchain_community.chat_message_historiesimportChatMessageHistoryfromsrc.llm.gemini_chat_modelimportget_gemini_llm# 1. 定义存储:这里使用内存字典模拟数据库store={}# 2. 定义工厂函数:告诉系统如何根据 session_id 获取历史对象defget_session_history(session_id:str):ifsession_idnotinstore:store[session_id]=ChatMessageHistory()returnstore[session_id]# 3. 初始化 LLMllm=get_gemini_llm()# 4. 定义 Prompt:必须包含历史记录的占位符prompt=ChatPromptTemplate.from_messages([("system","You are a helpful assistant."),MessagesPlaceholder(variable_name="history"),# <--- 关键点:预留位置("human","{input}"),])# 5. 创建基础 Chainchain=prompt|llm

2.2 核心包装

这是最关键的一步。我们将无状态的chain包装成有状态的with_message_history

with_message_history=RunnableWithMessageHistory(chain,get_session_history,input_messages_key="input",history_messages_key="history",)

2.3 调用执行

调用时,我们需要通过config传递session_id

# 第一轮:告诉我是 Bobresponse1=with_message_history.invoke({"input":"Hi! My name is Bob."},config={"configurable":{"session_id":"session_1"}})print(f"AI:{response1.content}")# 第二轮:问我的名字response2=with_message_history.invoke({"input":"What is my name?"},config={"configurable":{"session_id":"session_1"}})print(f"AI:{response2.content}")# 输出: AI: Your name is Bob. (成功记住了!)

3. 参数深度剖析 (非常重要)

RunnableWithMessageHistory的构造函数接收 4 个核心参数,理解它们是掌握 LCEL Memory 的关键。

1.runnable(位置参数 1)

  • 含义:被包装的基础对象(通常是Prompt | LLM组成的 Chain)。
  • 要求:这个 Chain 本身必须是无状态的。它不知道“历史”的存在,它只知道接收一个包含消息列表的 Prompt 并输出结果。历史的注入是在它运行之前由包装器完成的。

2.get_session_history(位置参数 2)

  • 含义:一个工厂函数(Factory Function)。
  • 签名(session_id: str) -> BaseChatMessageHistory
  • 作用:这是 LangChain 与外部存储(Redis, Postgres, Memory)交互的接口。
    • invoke开始时,系统调用此函数加载旧记录。
    • invoke结束时,系统调用此函数保存新记录。
  • 实战:生产环境中,这里通常返回RedisChatMessageHistoryPostgresChatMessageHistory的实例。

3.input_messages_key(关键字参数)

  • 含义“哪个 Key 代表用户的新消息?”
  • 背景:Chain 的输入通常是一个字典(例如{"input": "你好", "style": "幽默"})。系统需要知道要把哪一个值作为HumanMessage保存到历史记录中。
  • 设定:在上面的例子中,我们调用 invoke 时用了{"input": "..."},所以这里填"input"

4.history_messages_key(关键字参数)

  • 含义“历史记录应该填到 Prompt 的哪个坑里?”
  • 背景:在 Prompt 中,我们预留了一个占位符MessagesPlaceholder(variable_name="history")
  • 设定:系统加载出历史记录(List[Message])后,会自动将其注入到这个 key 中。所以这里必须填"history",以匹配 Prompt 中的变量名。

4. 运行流程图解

当你执行with_message_history.invoke({...}, config={...})时,内部发生了什么?

  1. 提取 ID:从config中读取session_id
  2. 加载历史:调用get_session_history(session_id)获取当前的历史消息列表。
  3. 注入 Prompt
    • 创建一个新的输入字典。
    • 将用户输入 (input) 放入。
    • 将历史列表 (history) 放入。
  4. 执行 Chain:运行基础 Chain (prompt | llm)。
  5. 保存历史
    • 将用户的输入 (HumanMessage) 追加到历史对象。
    • 将 AI 的输出 (AIMessage) 追加到历史对象。
  6. 返回结果:将 AI 的输出返回给用户。

5. 总结

RunnableWithMessageHistory是 LangChain 中优雅管理状态的瑞士军刀。它通过配置化的方式,将繁琐的历史记录读写逻辑从业务逻辑中剥离出来,极大地简化了代码结构。

记忆口诀

  • Config定身份 (session_id)。
  • Factory找仓库 (get_session_history)。
  • Keys做映射 (input/history keys)。

6. 架构关系图 (Mermaid)

下图直观地展示了RunnableWithMessageHistoryget_session_historyMessagesPlaceholderChatPromptTemplate之间的协作关系。

LLMMessagesPlaceholderChatPromptTemplateChatMessageHistory (Storage)get_session_historyRunnableWithMessageHistoryUserLLMMessagesPlaceholderChatPromptTemplateChatMessageHistory (Storage)get_session_historyRunnableWithMessageHistoryUser1. 用户发起调用2. 加载历史记录3. 准备 Prompt 输入4. 渲染 Prompt5. 执行推理6. 保存新对话7. 返回结果invoke(input="Hi", config={session_id: "1"})1调用工厂函数(session_id="1")2获取/创建历史对象3返回 List[BaseMessage] (旧历史)4传入 {input: "Hi", history: [旧历史]}5遇到 variable_name="history"6展开 [旧历史] 列表7填充 {input} 到 HumanMessage8生成最终完整消息列表9发送完整消息列表10返回 AI 回复 ("Hello!")11add_user_message("Hi")12add_ai_message("Hello!")13返回 "Hello!"14

图解说明

  1. RunnableWithMessageHistory是总指挥,负责协调所有组件。
  2. get_session_history是仓库管理员,负责根据 ID 找到对应的历史记录本。
  3. MessagesPlaceholder是 Prompt 里的“占位符”,负责把从仓库拿出来的历史记录“平铺”到对话中。
  4. ChatPromptTemplate是最终的拼装车间,输出给 LLM 的是包含历史和新输入的完整列表。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/14 1:00:37

基于协同过滤算法的运动场馆服务平台设计与实现_93kv0nhb

一、项目技术介绍 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/…

作者头像 李华
网站建设 2026/5/12 11:08:18

基于微信小程序的智慧社区娱乐服务管理平台_jm78648u

一、项目技术介绍 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/…

作者头像 李华
网站建设 2026/5/10 3:01:04

基于微信小程序的粤语文化传播平台的设计与开发_4b942thb

一、项目技术介绍 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/…

作者头像 李华
网站建设 2026/5/8 16:21:06

HarmonyOS 上,App、游戏、PC 能共用架构吗?

子玥酱 &#xff08;掘金 / 知乎 / CSDN / 简书 同名&#xff09; 大家好&#xff0c;我是 子玥酱&#xff0c;一名长期深耕在一线的前端程序媛 &#x1f469;‍&#x1f4bb;。曾就职于多家知名互联网大厂&#xff0c;目前在某国企负责前端软件研发相关工作&#xff0c;主要聚…

作者头像 李华
网站建设 2026/5/12 11:16:48

YOLO26改进12:SPPF-LSKA:注意力机制改进SPPF,增强多尺度特征提取能力

论文介绍 摘要 视觉注意力网络(VAN)中的大核注意力(LKA)模块已被证明在一系列视觉任务中表现出卓越性能,甚至超越视觉变换器(ViTs)。然而,LKA模块中的深度卷积层在卷积核尺寸增大时,计算量和内存占用呈二次方增长。为缓解这一问题,并支持在VAN的注意力模块中使用极大…

作者头像 李华