[智能体-221]:RunnableWithMessageHistory 全详解,是对langchain LCEL 原始chain的包装,使得不具备记忆功能的原始chain具备了记忆功能。
2026/6/4 22:20:13 网站建设 项目流程

RunnableWithMessageHistory是对langchain LCEL 原始chain的包装,使得不具备记忆功能的原始chain具备了记忆功能,被包装后的chain平等的参与后续chain的组合。

一、核心定位与设计思想

1. 本质定义

RunnableWithMessageHistoryLangChain 基于 LCEL 协议实现的包装类,属于装饰器模式应用:

  • 目标对象:任意标准 LCELRunnable(Prompt + LLM、普通 Chain、自定义 Runnable 等无原生记忆的链路)
  • 核心能力:无感追加对话历史记忆不修改原有链的代码与逻辑
  • 接口一致性:包装后实例依然是标准Runnable,完全遵循 LCEL 规范,和原生链具备同等组合能力,可使用|RunnableParallelRunnableBranch等任意 LCEL 语法拼接、嵌套、复用!!!这正是该消息的强大之处!!!!

2. 解决的痛点

  1. 原生 LCEL 链是无状态的:单次调用独立,无法留存多轮对话上下文;
  2. 传统记忆组件(ConversationBufferMemory)和老版 Chain 强耦合,难以适配 LCEL 流式、组合式的编程范式;
  3. 统一记忆逻辑与业务链逻辑,实现记忆层与业务层解耦

二、工作原理

1. 整体执行流程

  1. 接收入参:调用时传入业务输入 +专属session_id(会话唯一标识);
  2. 加载历史:通过你定义的get_session_history函数,根据session_id读取当前会话的历史消息;
  3. 拼接上下文将「历史对话 + 当前用户提问」自动整合,注入到原始 Chain 的 Prompt 中;
  4. 执行原始链:调用被包装的原生 LCEL 链路,得到模型回复;
  5. 持久化历史把「当前提问 + 模型回答」追加到对应session_id的会话历史中;
  6. 返回结果:输出模型响应,整个过程对上层调用透明。

2. 为什么能平等参与 LCEL 组合

LCEL 的核心是统一 Runnable 协议:所有组件(Prompt、LLM、解析器、包装器)都实现了invoke/stream/batch等标准方法。RunnableWithMessageHistory只是在标准方法内部增加了记忆读写逻辑,对外暴露的接口、入参出参格式和普通链完全一致,因此:

  • 可放在链路任意位置,用管道符|串联;
  • 可并行、分支、嵌套组合;
  • 支持流式输出、批量调用等 LCEL 全部特性。

三、核心参数解析

python

运行

RunnableWithMessageHistory( runnable: Runnable, # 必选:被包装的原始LCEL链 get_session_history: Callable[[str], BaseChatMessageHistory], # 必选:会话历史获取函数 input_messages_key: str = "input", # 用户输入字段名 history_messages_key: Optional[str] = None, # 历史消息注入字段名 output_messages_key: Optional[str] = None # 输出消息字段名 )
  1. runnable待增强的原始 LCEL 链,纯业务逻辑,本身不处理记忆
  2. get_session_history回调函数,入参为session_id,返回一个消息历史存储实例。
    • 内置实现:内存存储、文件、Redis、数据库等;
    • 作用:按会话隔离历史,支持多用户、多对话并发。
  3. input_messages_keyPrompt 模板中,接收当前用户输入的变量名。
  4. history_messages_keyPrompt 模板中,接收拼接后的历史对话的变量名。

四、完整实战示例(分两种常用写法)

场景 1:标准对话模板(显式指定历史变量)

最规范写法,手动在 Prompt 中预留历史消息位。

python

运行

from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_openai import ChatOpenAI from langchain_community.chat_message_histories import ChatMessageHistory # 1. 构建无记忆的原始 LCEL 链 # MessagesPlaceholder 专门用来承载动态对话历史 prompt = ChatPromptTemplate.from_messages([ ("system", "你是专业助手,根据上下文回答问题"), MessagesPlaceholder(variable_name="history"), # 历史对话占位 ("human", "{question}") # 当前用户问题 ]) llm = ChatOpenAI(model="gpt-3.5-turbo") # 原始链:纯业务逻辑,无记忆 raw_chain = prompt | llm # 2. 定义会话历史获取函数(内存版) def get_history(session_id: str) -> ChatMessageHistory: return ChatMessageHistory() # 3. 包装为带记忆的链 history_chain = RunnableWithMessageHistory( runnable=raw_chain, get_session_history=get_history, input_messages_key="question", # 当前输入变量名 history_messages_key="history" # 历史消息变量名 ) # 4. 调用测试(同一个session_id 共享历史) session_cfg = {"configurable": {"session_id": "user_001"}} # 第一轮对话 res1 = history_chain.invoke({"question": "我叫小王"}, config=session_cfg) print(res1.content) # 第二轮对话,自动带上历史 res2 = history_chain.invoke({"question": "我叫什么名字?"}, config=session_cfg) print(res2.content)

场景 2:极简写法(自动推导字段)

适合简单对话,框架自动处理消息拼接。

python

运行

# 简化 Prompt prompt = ChatPromptTemplate.from_messages([ ("system", "友好聊天"), ("human", "{input}") ]) raw_chain = prompt | llm # 包装记忆链 history_chain = RunnableWithMessageHistory( raw_chain, get_history, input_messages_key="input" ) # 继续 LCEL 组合:包装后的链正常拼接解析器 from langchain_core.output_parsers import StrOutputParser final_chain = history_chain | StrOutputParser() # 链式组合,完全兼容 # 调用 print(final_chain.invoke({"input": "今天天气如何"}, config={"configurable": {"session_id":"user_002"}}))

五、关键特性与进阶说明

1. 会话隔离(session_id)

  • 不同session_id对应独立的对话历史,天然支持多用户、多会话
  • 内存存储重启后历史清空,生产环境建议替换为 Redis / 数据库持久化。

2. LCEL 组合能力验证

包装后的链 = 标准Runnable,支持所有 LCEL 组合语法:

python

运行

# 并行组合示例 from langchain_core.runnables import RunnableParallel # 带记忆的链 和 普通链 并行执行 parallel_chain = RunnableParallel({ "chat": history_chain, "summary": raw_chain }) # 正常调用 parallel_chain.invoke({"question": "你好"}, config={"configurable":{"session_id":"s1"}})

3. 流式、批量支持

原生stream/batch方法完全保留,包装不影响流式输出等高级特性。

4. 优势总结

  1. 解耦:记忆逻辑和业务链完全分离,原始链可单独测试、复用;
  2. 通用:任意 LCEL 链都可一键加装记忆,无需改造原有代码;
  3. 兼容:100% 适配 LCEL 生态,组合、嵌套、分支不受限制;
  4. 灵活:历史存储可自由切换(内存、Redis、MySQL 等)。

六、和传统 Memory 的核心区别

表格

维度传统 Memory + 老式 ChainRunnableWithMessageHistory + LCEL
耦合度强耦合,代码侵入高装饰器包装,零侵入
组合性难以自由拼接、嵌套原生支持 LCEL 全组合能力
状态管理链内部维护状态独立会话存储,多会话隔离清晰
适用范式旧版命令式编程LCEL 声明式、流式编程

七、常见使用注意点

  1. 必须通过config={"configurable": {"session_id": "xxx"}}传入会话 ID,否则无法区分历史;
  2. Prompt 中使用MessagesPlaceholder承载历史消息是最佳实践,格式更标准;
  3. 内存版ChatMessageHistory仅适用于测试,线上务必使用持久化存储;
  4. 包装顺序:先构建业务 LCEL 链,最后统一包装记忆,逻辑更清晰。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询