第10课:LangChain|LLMChain|ConversationChain对话链底层源码与实战
摘要 本文系统讲解了LangChain中即将被废弃的LLMChain和ConversationChain组件,帮助开发者理解其历史作用、当前局限及迁移路径。主要内容包括: 组件解析: LLMChain作为最早的"模型+模板"封装,已在0.1.17版本标记废弃 ConversationChain作为带记忆的对话链,在0.2.7版本废弃 迁移方案: 推荐使用LCEL的prompt

文章目录
课程导读 & 学习目标
在前面的课程中,我们已经系统学习了LangChain的核心组件。第5课我们用PromptTemplate构建了动态提示模板,第6课用Few-Shot示例引导模型输出格式,第7课用ChatPromptTemplate编排了系统消息、用户消息和历史消息,第8课用OutputParser将模型输出转为结构化数据,第9课我们用LCEL(LangChain Expression Language)的|操作符构建了基础的RunnableSequence链。可以说,你已经掌握了LangChain 0.3.x体系下的现代开发范式。
然而,在实际工作中,你很可能会遇到大量使用旧版API编写的代码,尤其是LLMChain和ConversationChain这两个类。它们曾是LangChain 1.0版本到来之前的核心抽象,封装了提示词模板与模型调用的组合(LLMChain)以及对话记忆管理(ConversationChain)。如果你去看LangChain的官方参考文档,会发现ConversationChain的页面顶部被标记为**「Since v1.0 Deprecated」**。没错,这些曾经的核心抽象已经在v0.1.17和v0.2.7中先后被标记为废弃,并将在未来的1.0版本中被彻底移除。
这节课要回答的核心问题是:如果我们想要真正理解LangChain的演进脉络、读懂存量代码、并为将来的大版本升级做准备,就必须搞清楚LLMChain和ConversationChain是什么、怎么用、为什么要被废弃,以及如何优雅地迁移到现代LCEL+Runnable体系中去。
理解这两个类的重要性,并不在于继续使用它们,而在于它们代表了LangChain在不同时期的两次关键架构决策。LLMChain标志着LangChain从「单一模型调用工具」走向「组件化抽象」的第一步,而ConversationChain则试图在LLMChain之上通过内置内存组件解决多轮对话中的上下文记忆问题。但是,随着LangChain Expression Language(LCEL)和Runnable统一接口的全面推行,旧版Chain架构的局限性暴露无遗,淘汰是必然的。
学完本节课,你将达到以下目标:
- 全面掌握
LLMChain的废弃状态与迁移路径:理解它的核心参数(prompt、llm)、调用方法(invoke、run、apply)和底层执行流程,并熟练迁移到prompt | llm的LCEL模式。 - 深入理解
ConversationChain的内部机制:掌握其内存集成原理、默认提示模板、与LLMChain的继承关系,以及为什么它会被RunnableWithMessageHistory取代。 - 透彻认识
ChatMessageHistory的最低层原理:理解它与ConversationBufferMemory的本质区别,以及为什么新架构用前者替代后者。 - 完成三个完整的项目实战:
- 实战一:用
LLMChain实现一次基础调用,然后用LCEL方式重写,体验迁移过程。 - 实战二:对比学习
ConversationChain与RunnableWithMessageHistory+ChatMessageHistory,看新架构如何以更模块化的方式实现对话记忆。 - 实战三:构建一个「带多轮记忆的智能客服机器人」,将
RunnableWithMessageHistory与ChatMessageHistory组合使用。 - 高级挑战:以
ConversationSummaryBufferMemory为基准,探索用RunnableWithMessageHistory实现记忆总结的复杂能力。
- 实战一:用
前置知识与环境准备
1.1 环境沿用
继续使用前几课创建的langchain_course虚拟环境(Python 3.10+)。如果尚未创建,请参考第1课的依赖安装命令。
1.2 安装依赖包
# 激活虚拟环境
source venv/bin/activate # Mac/Linux
# venv\Scripts\activate # Windows
# 基础依赖(确保使用0.3.x版本)
pip install langchain==0.3.7 langchain-core==0.3.21 langchain-openai==0.2.8 python-dotenv==1.0.1
# 验证安装
python -c "from langchain.chains import LLMChain; print('LLMChain可以导入,但会触发废弃警告')"
python -c "from langchain_core.runnables.history import RunnableWithMessageHistory; print('✓ 新组件导入成功')"
1.3 API密钥与模型选择
沿用第3课或第4课的.env配置。本节课的LLMChain和ConversationChain示例,建议使用本地Ollama模型以快速运行。从它们迁移到LCEL和Runnable体系后,模型的切换方式没有任何改变。
核心概念深度拆解
2.1 LLMChain的定义——最早被废弃的「模型+模板」封装
LLMChain是LangChain中最简单的Chain类型。它的核心职责就是「组合PromptTemplate和LLM」,让你的提示以标准的Recipe(配方)流转。它的典型代码结构如下:
from langchain.chains import LLMChain
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
prompt = PromptTemplate.from_template("写一首关于{topic}的短诗")
llm = ChatOpenAI(model="glm-4")
chain = LLMChain(prompt=prompt, llm=llm)
result = chain.invoke({"topic": "春天"})
print(result["text"])
这段代码的核心思想很简单:将提示词模板和模型实例组合成一个可调用的链对象,然后在invoke时传入变量字典,自动完成格式化→调用模型→返回输出的串行流程。
核心参数:
prompt:PromptTemplate或ChatPromptTemplate实例,定义输入变量和输出格式。llm:BaseLLM或BaseChatModel子类的实例,负责执行真正的推理。output_key:输出结果的键名,默认为"text",可在多个Chain串联时用来标识中间产物的字段名。memory:可选的Memory对象,用来加载和保存对话历史。callbacks/verbose:用于日志打印、追踪等可观测性上的需求。
核心方法:
invoke(input_dict):接收字典(例如{"topic":"春天"}),经过链处理后返回一个包含输入变量和输出结果的字典。返回结果是字典而非纯字符串,目的是为了方便不同Chain之间传递数据。- **`run(args, **kwargs)***:更简洁的调用方式,可以传多个关键词参数,返回纯字符串(而不是字典)。
apply(input_list):对批量的输入(一个列表)做批处理请求,适用于model.batch模式的更底层的同步并发优化。
LLMChain的核心局限(为什么被废弃):它的灵活性不足,链式逻辑较为固定,难以支持复杂的工作流或动态输入。此外,它未完全适配LangChain 0.3.x的核心——LangChain Expression Language(LCEL)。RunnableSequence通过|运算符实现相同的功能,且更简洁、通用,长期支持也更稳定。
LLMChain的废弃时间线:LangChain官方从v0.1.17开始,就在LLMChain的代码中引入DeprecationWarning警告。使用LLMChain的代码会在控制台打印如下告警:
LangChainDeprecationWarning: The class LLMChain was deprecated in LangChain 0.1.17 and will be removed in 1.0. Use :meth:~RunnableSequence, e.g., prompt | llm`` instead.
这意味着,虽然在1.0到来之前的过渡期,LLMChain仍能暂时工作,但任何基于它的代码都应该尽快迁移到RunnableSequence。
2.2 ConversationChain的定义——「对话链」的演进与终结
ConversationChain继承自LLMChain,但多了一个memory参数。它的核心职责是「在LLMChain的基础上,内建一个Memory组件来管理多轮对话的上下文」。ConversationChain官方的副标题是:「Chain to have a conversation and load context from memory」,它使用了默认的对话模板和默认的ConversationBufferMemory,让你能够快速构建最简单的记忆对话机器人。
典型示例代码:
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="glm-4")
memory = ConversationBufferMemory()
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)
print(conversation.invoke("我的名字是李明")["response"])
print(conversation.invoke("你还记得我叫什么吗?")["response"])
输出示例:
我的名字是李明 -> AI回复...
你还记得我叫什么吗? -> AI回复:你的名字是李明。
为什么它会在v0.2.7被标记废弃? ConversationChain有几个致命缺陷:它的内部提示模板和内存管理逻辑较为固定,难以支持复杂对话流程,尤其是与支持Tool-calling的Agent系统结合时力不从心。官方在v0.2.7开始了对该类的废止,后续版本开始强烈推荐使用RunnableWithMessageHistory。
2.3 内存组件(基类)的架构变迁
在LangChain旧的内存体系中,核心类是ConversationBufferMemory——它继承自BaseMemory,利用缓冲区把所有对话存储在一个列表里,作为提示的一部分注入。
随着LangChain的演进,LangChain官方开始在新的Runnable体系中统一使用RunnableWithMessageHistory,并以BaseChatMessageHistory作为内存存储的基础接口。
BaseChatMessageHistory 是一个专门的接口,负责存储messages的存储与加载。它的最小实现ChatMessageHistory极其轻量,只是将HumanMessage和AIMessage保存在内存列表中。这些旧版的ConversationBufferMemory们从v0.3.1开始被逐步标记废弃,官方建议迁移到基于BaseChatMessageHistory的方案,或者与LangGraph的持久化系统深度集成。
用RunnableWithMessageHistory切换存储后端(从单纯的内存列表ChatMessageHistory到SQLite、Redis或云端的持久化存储)时,业务代码——除了get_session_history的函数实现——几乎不需要更改,真正做到了「替换存储后端不改业务代码」。
2.4 新旧架构的演进脉络——一张图遮住时间线
| 维度 | 旧链时代 (v0.1.x - v0.2.x) | 新Runnable时代 (v0.3.x) |
|---|---|---|
| 模型+模板封装 | LLMChain |
prompt | llm (RunnableSequence) |
| 对话记忆管理 | ConversationChain + ConversationBufferMemory |
RunnableWithMessageHistory + ChatMessageHistory |
| 记忆存储接口 | BaseMemory (in-memory) |
BaseChatMessageHistory (可扩展到文件/数据库/Redis) |
| 与LCEL的接口 | 不兼容或非常别扭 | 完美兼容 |
| 批量处理和流式支持 | 有限 | 原生支持(.stream(), .batch(), .astream()) |
底层运行原理剖析
3.1 LLMChain的「输入到输出」的执行细节
当调用llm_chain.invoke({"topic":"春天"})时,LLMChain底层执行了以下几个步骤:
- 拦截输入:
Chain的基类Chain会先调用prep_inputs做输入预处理:验证input_keys中的必需字段。 - 格式化提示模板:调用
prompt.format_prompt将输入的变量字典转换为PromptValue对象,再转换成StringPromptValue(纯文本)或ChatPromptValue(消息列表)。 - 调用模型的generate方法:把
PromptValue转换成模型API能接受的字符串格式,再通过LLM实例的属性做网络请求(对应旧模型基类的_generate)。 - 格式化输出:模型返回
LLMResult之后,提取generations[0].text,存进{"text": value}形式的字典中。 - 调用回调和内存更新:如果配置了Memory,
Chain基类会在执行完成后调用memory.save_context输入与输出的内容到内存缓冲区;如果开启verbose=True,打印每个时间环节的Debug日志。
流程图:
用户输入 {"topic":"春天"}
↓
Chain.prep_inputs() 验证输入键
↓
PromptTemplate.format() 拼凑模板字符串
↓
LLM.invoke(提示字符串)
↓
提取 generation.text
↓
返回 {"text": "生成的短诗"}
↓
(如果配置了Memory)memory.save_context(...)
3.2 ConversationChain如何集成ConversationBufferMemory
ConversationChain在LLMChain的基础上重写了_call方法,在调用模型之前先执行memory.load_memory_variables({}),将历史会话以字符串形式加载进inputs变量里,最后将格式化后的提示整体交给模型。
查看旧版源代码,ConversationChain默认使用的Prompt是:
DEFAULT_TEMPLATE = """以下是人类和AI助手之间的一次友好对话。AI助手非常健谈,并从其上下文中提供具体回答。
当前对话记录:
{history}
人类:{input}
AI助手:"""
其中{history}就是由ConversationBufferMemory从内存中加载的原始对话内容(拼接Human和AI角色的完整输出)。这也揭示了一个本质问题——它生成的上下文包含大量的重复前缀,很容易撑爆Token上下文窗口。更高级的记忆类型如ConversationSummaryBufferMemory试图用摘要来缓解这一问题,但在旧架构上只能靠这种黏糊糊的方式实现。
3.3 RunnableWithMessageHistory与ChatMessageHistory的新流程
在新架构中,RunnableWithMessageHistory作为一个Runnable的包装器嵌在链的外部,不会侵入核心的业务逻辑。它的执行流程如下:
- 用户调用
chain_with_history.invoke({"input": "你好"}, config={"configurable": {"session_id": "abc123"}})。 RunnableWithMessageHistory从config参数中取出session_id并调用get_session_history(session_id),获取最新的BaseChatMessageHistory实例。- 从
BaseChatMessageHistory.messages中获取所有历史消息,通过input_messages_key和history_messages_key的配置,合并成新的提示消息列表。 - 内层Core Chain(
prompt | model)接受formatted的messages并调用模型。 - 模型返回输出的AIMessage后,外层包装器自动把模型输出作为新的消息添加到
BaseChatMessageHistory.messages列表中,并返回输出结果。
这里最关键的设计是:messages的加载和保存完全由RunnableWithMessageHistory接管,用户核心链的逻辑里完全看不到任何与记忆有关的代码,实现了记忆的彻底解耦。用户可以通过更换get_session_history的实现来任意切换存储的后端(例如用Redis、SQLite或FileChatMessageHistory),而核心链的提示词定义无需动一行代码。
核心API/组件源码解读
4.1 LLMChain的核心源码结构(历史版本)
虽然LLMChain的源码在最新版已经从LangChain主仓库移除,但通过阅读其__call__方法可以理解整个旧设计的缺陷。旧版的结构大致是:
class LLMChain(Chain):
def __call__(self, inputs, return_only_outputs=False, ...):
# 输入映射
inputs = self.prep_inputs(inputs)
# 调用核心逻辑
outputs = self._call(inputs)
# 可选保存memory
if self.memory is not None:
self.memory.save_context(inputs, outputs)
# 返回
return outputs if return_only_outputs else {**inputs, **outputs}
def _call(self, inputs):
# 提示模板格式化
prompt_value = self.prompt.format_prompt(**inputs)
# 调用大模型
response = self.llm.generate_prompt([prompt_value], ...)
return {self.output_key: response.generations[0][0].text}
这段代码的问题在于:Chain基类将memory管理、输入输出映射和核心逻辑强行捆绑在一个类里,无法自由插拔,也与更通用的Runnable接口格格不入。
4.2 RunnableWithMessageHistory的核心参数详解
通过解读官方文档,RunnableWithMessageHistory的接口设计如下:
RunnableWithMessageHistory(
runnable: Runnable,
get_session_history: Callable[[str], BaseChatMessageHistory],
input_messages_key: str = "input",
history_messages_key: str = "history",
history_factory_config: Optional[Sequence[ConfigurableFieldSpec]] = None
)
关键参数解读:
runnable:任何支持Runnable接口的对象。通常是一个LCEL链,例如prompt | llm | parser。get_session_history:用户传入的函数,根据session_id返回对应的BaseChatMessageHistory实例。调用时必须在config中显式传入{"configurable": {"session_id": "..."}}。input_messages_key:定义在每次runnable的输入字典中,代表用户当前提问的key。history_messages_key:定义在runnable输入字典中接受历史消息的key。这个key必须与ChatPromptTemplate中的MessagesPlaceholder(variable_name="...")的变量名匹配。history_factory_config:高级功能,用于动态传递额外输入字段到get_session_history。
4.3 ChatMessageHistory——最轻量的内存历史存储
ChatMessageHistory是BaseChatMessageHistory最简单的实现,只存储消息列表,并提供add_message()增加消息。
# 伪代码实现
class ChatMessageHistory(BaseChatMessageHistory):
messages: List[BaseMessage] = []
def add_message(self, message: BaseMessage) -> None:
self.messages.append(message)
def clear(self) -> None:
self.messages = []
你可以通过ChatMessageHistory.messages查看本轮对话已经加载的全部HumanMessage和AIMessage。高级持久化则可以通过FileChatMessageHistory和RedisChatMessageHistory等实现,替换get_session_history中返回的存储类即可。
4.4 @chain_decorator——把函数转为「可重用链」的精巧语法糖
在LangChain 0.3.x中,如果你想把一个Python函数快速转换成可参与LCEL流水线的Runnable,可以在函数定义上附加@chain装饰器。例如:
from langchain_core.runnables import chain
@chain
def split_words(text: str):
return text.split()
它由RunnableLambda包装成一个具备invoke能力的对象,与prompt | llm | parser的链式集合完全兼容。在实战三的高级挑战中,我们会用到这个装饰器实现自定义的记忆压缩逻辑。
手把手项目实战教学
本节课的四个实战项目,会分别覆盖以下内容:
- 实战一:
LLMChain基础与迁移实践。 - 实战二:
ConversationChain与RunnableWithMessageHistory的对比。 - 实战三:构建带多轮记忆的智能客服机器人。
- 高级挑战:
ConversationSummaryBufferMemory的迁移等价实现。
实战一:从LLMChain迁移到RunnableSequence
目标:体验从LLMChain代码触发废弃警告到使用现代RunnableSequence完全迁移的全过程。
步骤1(旧代码) :创建llmchain_old.py
import os
from dotenv import load_dotenv
from langchain.chains import LLMChain
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
load_dotenv()
llm = ChatOpenAI(model="glm-4", temperature=0.7)
prompt = PromptTemplate.from_template("用一句话解释这个概念:{concept}")
# 创建LLMChain(会触发LangChainDeprecationWarning)
chain = LLMChain(prompt=prompt, llm=llm, verbose=True)
result = chain.invoke({"concept": "RAG(检索增强生成)"})
print(result["text"])
运行后看控制台,会输出一个DeprecationWarning,提示LLMChain将在1.0被移除,推荐用prompt | llm替代。
步骤2(新代码) :重写为llmchain_new.py
import os
from dotenv import load_dotenv
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
load_dotenv()
llm = ChatOpenAI(model="glm-4", temperature=0.7)
prompt = PromptTemplate.from_template("用一句话解释这个概念:{concept}")
# 旧LLMChain等价于prompt | llm | StrOutputParser()
chain = prompt | llm | StrOutputParser()
result = chain.invoke({"concept": "RAG(检索增强生成)"})
print(result) # 直接打印字符串
这种写法避免了废弃警告,并且可以免费获得.stream()、.batch()等现代Runnable特性。值得注意的是,这里必须连接一个StrOutputParser(),才能将模型返回的AIMessage转换为纯字符串结果,与LLMChain的run()默认输出保持一致。
实战二:ConversationChain与RunnableWithMessageHistory对比
目标:面对面体验ConversationChain和RunnableWithMessageHistory的异同。
步骤1(ConversationChain) :创建conversation_chain_old.py
import os
from dotenv import load_dotenv
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
from langchain_openai import ChatOpenAI
load_dotenv()
llm = ChatOpenAI(model="glm-4", temperature=0.7)
memory = ConversationBufferMemory()
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)
print(conversation.invoke("我叫李明,是一名软件工程师")["response"])
print(conversation.invoke("我做什么工作?")["response"])
步骤2(RunnableWithMessageHistory) :创建conversation_chain_new.py
import os
from dotenv import load_dotenv
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
load_dotenv()
model = ChatOpenAI(model="glm-4", temperature=0.7)
# 定义提示模板:带MessagesPlaceholder占位符,用于接收历史消息
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个友好的AI助手"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
# 基础链
base_chain = prompt | model | StrOutputParser()
# 在外部管理会话历史
store = {}
def get_session_history(session_id: str):
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# 用RunnableWithMessageHistory包装
chain_with_history = RunnableWithMessageHistory(
base_chain,
get_session_history,
input_messages_key="input",
history_messages_key="history"
)
config = {"configurable": {"session_id": "user001"}}
print(chain_with_history.invoke({"input": "我叫李明,是一名软件工程师"}, config))
print(chain_with_history.invoke({"input": "我记得我告诉你我做什么工作了吗?"}, config))
对比总结:两种模式都能正确记住第一轮的对话内容。但新架构将提示模板MessagesPlaceholder、存储手段ChatMessageHistory和外部会话管理store完全剥离,模块各自独立,比ConversationChain的黑盒封装更灵活、更易于扩展。
实战三:构建智能客服机器人(带Redis持久化)
目标:构建一个智能客服机器人,用RunnableWithMessageHistory管理每用户的独立会话记忆,并将聊天记录自动持久化到Redis或SQLite,确保服务器重启或会话迁移后,对话上下文仍未丢失。这标志着为真实生产环境设计的记忆能力。
步骤:
- 创建提示模板,包括系统角色设定、历史消息占位符和用户输入。
- 初始化模型和
RunnableWithMessageHistory。 - 实现自定义
get_session_history函数,可将ChatMessageHistory替换为SQLChatMessageHistory(存储到SQLite)或RedisChatMessageHistory(存储到Redis数据库)。 - 测试跨会话记忆效果。
完整代码见下方「完整可运行代码」部分。
高级挑战:模拟ConversationSummaryBufferMemory的效果
LLMChain和ConversationChain都面临长对话Token过长的瓶颈,官方的ConversationSummaryBufferMemory曾经尝试用「摘要+窗口片段」来解决。现在,因为BaseChatMessageHistory直接存储原始的Message对象,没有内置摘要或窗口修剪。手动实现这种行为的方法是在get_session_history之上定期裁剪对话长度,再用模型压缩成摘要,并保存为某个特殊类型的系统消息。
具体思路:在每次加载历史消息前,先检查store[session_id].messages的长度。如果超过某个阈值(例如tokens超过3000),就用单独的LLM对这些消息生成总结,再将原来的messages替换成一个SystemMessage(content=summary_text),从而保留对话的核心信息。
这一挑战在大规模Agent的长语境对话中越来越重要。由于篇幅限制,实现代码可参考开源社区中langchain_memory_skill的过滤器典型思路。
完整可运行Python代码(带逐行详细注释)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
LangChain 第10课:LLMChain与ConversationChain完整演示
涵盖:LLMChain基础与迁移、ConversationChain对比、RunnableWithMessageHistory持久记忆
"""
import os
from dotenv import load_dotenv
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, PromptTemplate
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory, SQLChatMessageHistory
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
load_dotenv()
# ========== 演示一:LLMChain及其迁移 ==========
def demo_llmchain():
print("\n【演示一】LLMChain -> RunnableSequence 迁移")
llm = ChatOpenAI(model="glm-4", temperature=0.7)
# 旧方式:LLMChain
from langchain.chains import LLMChain
prompt_old = PromptTemplate.from_template("用一句话解释:{concept}")
chain_old = LLMChain(prompt=prompt_old, llm=llm, verbose=False)
result_old = chain_old.invoke({"concept": "RAG"})
# LLMChain在0.3.x中会触发DeprecationWarning,但暂时仍可运行
print(f"【LLMChain输出】{result_old['text']}")
# 新方式:RunnableSequence + StrOutputParser
prompt_new = PromptTemplate.from_template("用一句话解释:{concept}")
chain_new = prompt_new | llm | StrOutputParser()
result_new = chain_new.invoke({"concept": "RAG"})
print(f"【RunnableSequence输出】{result_new}")
# ========== 演示二:ConversationChain vs RunnableWithMessageHistory ==========
def demo_conversation_chains():
print("\n【演示二】ConversationChain 与 RunnableWithMessageHistory 对比")
llm = ChatOpenAI(model="glm-4", temperature=0.7)
# ---- 1. ConversationChain (旧) ----
# 以下代码会触发废弃警告,但为了教学展示其原始行为,仍然给出
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
memory_old = ConversationBufferMemory()
conv_old = ConversationChain(llm=llm, memory=memory_old, verbose=False)
conv_old.invoke("我叫王小明")["response"]
result_old = conv_old.invoke("我叫什么名字?")["response"]
print(f"【ConversationChain】{result_old}")
# ---- 2. RunnableWithMessageHistory (新) ----
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个友善的AI助手"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
chain = prompt | llm | StrOutputParser()
store = {}
def get_session_history(session_id: str):
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
conv_new = RunnableWithMessageHistory(
chain, get_session_history,
input_messages_key="input",
history_messages_key="history"
)
config = {"configurable": {"session_id": "test_user"}}
conv_new.invoke({"input": "我叫王小明"}, config)
result_new = conv_new.invoke({"input": "我叫什么名字?"}, config)
print(f"【RunnableWithMessageHistory】{result_new}")
# ========== 演示三:持久化到SQLite(真正的生产级记忆) ==========
def demo_persistent_memory():
print("\n【演示三】持久化记忆:SQLite 存储")
llm = ChatOpenAI(model="glm-4", temperature=0.7)
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业的客服助手"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
chain = prompt | llm | StrOutputParser()
# 使用SQLite存储每个session的完整对话记录
def get_session_history(session_id: str):
return SQLChatMessageHistory(
session_id=session_id,
connection_string="sqlite:///chat_history.db"
)
conv = RunnableWithMessageHistory(
chain, get_session_history,
input_messages_key="input",
history_messages_key="history"
)
config = {"configurable": {"session_id": "customer_123"}}
print(conv.invoke({"input": "我需要退款"}, config))
print(conv.invoke({"input": "我刚才的请求是什么?"}, config))
# ========== 主入口 ==========
def main():
print("LangChain 第10课:LLMChain与ConversationChain完整演示")
demo_llmchain()
demo_conversation_chains()
demo_persistent_memory()
if __name__ == "__main__":
main()
环境依赖安装命令
# 激活虚拟环境
source venv/bin/activate # Mac/Linux
# venv\Scripts\activate # Windows
# 基础依赖
pip install langchain==0.3.7 langchain-core==0.3.21 langchain-openai==0.2.8 python-dotenv==1.0.1
# 持久化依赖(用于SQLite)
pip install sqlalchemy
# 验证安装
python -c "from langchain_community.chat_message_histories import SQLChatMessageHistory; print('✓ SQLChatMessageHistory可用')"
常见报错坑点与避坑方案
坑1:LangChainDeprecationWarning突然爆出(升级LangChain后)
现象:以前跑得好好的LLMChain代码报出废弃警告。
原因:LangChain从v0.1.x升级到v0.2.x或v0.3.x后,LLMChain被标记为即将废弃,会随机打印警告。虽然代码暂时还能运行,但未来的1.0版本会彻底删除它,导致业务爆炸。
解决方案:采用本文介绍的prompt | llm | StrOutputParser完全重写对应代码,彻底摆脱LLMChain依赖。
坑2:RunnableWithMessageHistory无法加载历史消息(会话管理遗漏)
现象:RunnableWithMessageHistory明明写对了代码,却完全没记忆功能。
原因:提示模板中没有添加MessagesPlaceholder占位符,或者input_messages_key/history_messages_key参数名拼写错误。
解决方案:检查是否在ChatPromptTemplate中显式包含MessagesPlaceholder(variable_name="history");同时检查RunnableWithMessageHistory的参数设置,确保history_messages_key与MessagesPlaceholder中的变量名完全一致,且input_messages_key指向用户输入的键名。
坑3:ConversationBufferMemory与ChatMessageHistory混用导致类型错误
现象:想用ConversationBufferMemory加载历史消息,但传给了RunnableWithMessageHistory方案,报TypeError。
原因:RunnableWithMessageHistory只能接受实现了BaseChatMessageHistory接口的类,而ConversationBufferMemory实现的是旧的BaseMemory接口,两者不兼容。
解决方案:改用langchain_community.chat_message_histories.ChatMessageHistory替换旧的Memory类。
坑4:LLMChain批量处理.apply()不知道用谁替换
原因:LLMChain.apply(list_of_inputs)是一个批量调用的语法糖。
解决方案:新LCEL架构中推荐用chain.batch(list_of_dicts)代替,它们都是利用线程级并发,语义上完全等价。
本节核心知识点总结
📌 LLMChain的废弃与迁移路径:LLMChain在v0.1.17被标记为废弃,将很快在1.0中移除。最优的迁移方案是替换为prompt | llm | StrOutputParser的RunnableSequence模式,并利用StrOutputParser完成纯文本提取功能。
📌 ConversationChain的局限与退出:ConversationChain在v0.2.7被废弃。它的内在架构把模板、Memory、模型强耦合在一起,无法满足现代LCEL和Agent系统对模块化、可观测性和高度可扩展的要求。
📌 RunnableWithMessageHistory是新一代对话记忆的标准:它与ChatMessageHistory或SQLChatMessageHistory协同实现多会话隔离和自动历史注入,与MessagesPlaceholder配合使用。任何LangChain开发者都应该全面转向此方案。
📌 底层内存的迁移导向:旧版ConversationBufferMemory已渐行渐远。新的ChatMessageHistory只负责消息读写,不涉及业务逻辑,在未来的LangGraph持久化方案中会长期稳定。
📌 最后,请毫不犹豫地朝LangChain Expression Language(LCEL)和Runnable的世界迁移,因为这是LangChain官方强制推行的进化方向。
课后练习题
选择题
1. 以下关于LLMChain的说法,正确的是?
A. LLMChain是LangChain未来版本会长期保留的核心类。
B. 官方推荐用prompt | llm(即RunnableSequence)替代LLMChain。
C. LLMChain可以使用chat_message_history参数。
D. LLMChain必须结合ConversationSummaryMemory才能生效。
答案及解析:B。LLMChain已经在v0.1.17中被标记为废弃,官网推荐使用prompt | llm的方式进行迁移。
2. 以下哪个新组件是ConversationChain的官方推荐替代方案?
A. ConversationBufferWindowMemory
B. RunnableWithMessageHistory
C. BaseMessageHistory
D. Chain
答案及解析:B。LangChain官方参考文档明确指出,ConversationChain已被废弃,推荐使用RunnableWithMessageHistory替代。
3. 在ChatPromptTemplate中,用来加载历史消息的占位符是?
A. {history_var}
B. {RunnableHistory}
C. MessagesPlaceholder
D. {conversation_history}
答案及解析:C。MessagesPlaceholder是最标准的占位模式,它的variable_name参数值需与RunnableWithMessageHistory的history_messages_key一致。
4. 在新记忆中,哪个接口是历史的通用基类,可被替换为SQLite、Redis等不同存储后端?
A. BaseMemory
B. BaseChatMessageHistory
C. BaseConversationMemory
D. BaseMessageBuffer
答案及解析:B。BaseChatMessageHistory是LangChain新体系中处理聊天历史的根接口,其具体实现如SQLChatMessageHistory可以对接多种存储后端。
简答题
5. 请描述LangChain从LLMChain迁移到RunnableSequence的核心原因。
参考答案:模块化不足——LLMChain将模板、模型、输出处理强行捆绑在一个类中;生态不兼容——无法接入LangChain 0.3.x的透明流式、批处理和异步接口;后期维护风险——官方已声明将从代码库中彻底移除,后续不再更新。
6. 解释ChatMessageHistory如何存储消息,以及它与ConversationBufferMemory的差异。
参考答案:ChatMessageHistory只存储HumanMessage和AIMessage的列表,本身无业务逻辑,完全尊重模型的消息角色划分。ConversationBufferMemory把历史格式化为纯文本字符串并嵌入提示中,不仅浪费Token,还会破坏消息的角色信息。因此新架构选用前者。
实践题
7. 请编写一个LangChain程序,实现对每个user_id独立管理会话记忆,要求:
- 提示模板:系统角色为「心理咨询师」+ 历史消息占位符 + 用户输入。
- 必须采用
RunnableWithMessageHistory+ChatMessageHistory实现多session隔离。 - 测试两个用户会话,确保记忆彼此独立互不影响。
参考答案:
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="glm-4", temperature=0.7)
prompt = ChatPromptTemplate.from_messages([
("system", "你是一名专业的心理咨询师,请温和地帮助用户。"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
chain = prompt | llm | StrOutputParser()
store = {}
def get_history(session_id):
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
runner = RunnableWithMessageHistory(
chain, get_history,
input_messages_key="input",
history_messages_key="history"
)
config_a = {"configurable": {"session_id": "user_alice"}}
config_b = {"configurable": {"session_id": "user_bob"}}
runner.invoke({"input": "我叫Alice,我最近总是失眠"}, config_a)
runner.invoke({"input": "我叫Bob,我害怕考试"}, config_b)
# 两个会话互相独立
print(runner.invoke({"input": "我叫什么名字,我有什么烦恼?"}, config_a))
print(runner.invoke({"input": "我叫什么名字,我有什么烦恼?"}, config_b))
🔗《30节课 LangChain 从入门到精通》系列课程导航
🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~
更多推荐


所有评论(0)