LangChain 核心链式组件对比:从 SequentialChain 到 LCEL
本文档旨在详细解析 LangChain 中用于构建顺序工作流的三种核心方式:
SimpleSequentialChain
(简单顺序链)SequentialChain
(通用顺序链)- LCEL 管道符
|
(现代链式表达式)
我们将首先分别介绍它们的概念,然后通过一个统一的编程示例,展示它们各自的实现方式和差异,并深入讲解其中用到的关键组件。
1. 组件介绍
1.1 SimpleSequentialChain
(简单顺序链)
概念:
这是最基础的顺序链,如同一个简单的“接力赛”。第一个链执行完毕后,将其单个字符串输出直接作为第二个链的单个输入,以此类推。
核心特点:
- 单入单出: 链条中的每一个子链都必须只有一个输入和一个输出。
- 最终输出: 整个
SimpleSequentialChain
只返回最后一个链的输出结果。 - 无中间状态: 你无法获取中间步骤产生的任何结果。
- 适用场景: 用于非常简单、线性的任务,例如:
主题 -> 标题 -> 关于标题的推文
。
1.2 SequentialChain
(通用顺序链)
概念:
这是一个更强大、更灵活的顺序链,可以看作一个拥有共享上下文的“工厂流水线”。它能处理包含多个输入和输出的子链。
核心特点:
- 多入多出: 子链可以拥有多个输入和输出。
- 共享上下文 (Memory):
SequentialChain
维护一个包含所有步骤输入和输出的字典。后续的链可以从这个字典中获取任何它所需要的变量。 - 访问中间结果: 你可以访问并返回流程中任何一步的输出结果。
- 显式定义: 需要通过
input_variables
,output_variables
, 和output_key
等参数来精确控制数据流。 - 适用场景: 绝大多数需要分解步骤、并且后一步需要前一步结果的复杂任务。
1.3 LCEL 管道符 |
(LangChain Expression Language)
概念:
这是 LangChain 当前及未来推荐的链构建方式,其核心是 Runnable
协议和管道符 |
。它允许你像拼接“乐高积木”一样,将各种组件(提示、模型、解析器、函数等)自由地组合在一起。
核心特点:
- 简洁直观: 语法非常简洁,
a | b
的意思就是将a
的输出作为b
的输入。 - 高度灵活: 任何实现了
Runnable
接口的对象都可以被链接,包括普通的 Python 函数,这提供了极大的灵活性。 - 强大的数据流控制: 通过字典和
RunnablePassthrough
等工具,可以轻松实现比SequentialChain
更复杂的数据流管理。 - 原生高级功能: 天然支持**流式(Streaming)、异步(Async)和批量(Batch)**调用,无需额外配置。
- 透明可调试: 链的内部结构清晰,易于调试和可视化。
- 适用场景: 所有新项目。它是构建任何类型链(无论是简单还是复杂)的黄金标准。
2. 统一任务场景
我们将用以下这个任务来对比三种实现方式:
- 初始输入: 一个技术主题(例如:“人工智能”)。
- 第一步: 根据该主题,生成一个吸引人的中文博客标题。
- 第二步: 根据生成的标题,为这篇博客写一段简短(2-3句话)的摘要。
- 最终输出: 同时得到生成的标题和摘要。
注意: SimpleSequentialChain
由于其局限性,无法直接完成“同时输出两项结果”的任务。为了演示,我们将让它完成一个类似的、但只返回最终结果的线性任务。
3. 完整代码示例与组件讲解
以下是完整的 Python 代码,演示了如何用三种方式解决上述问题。
# 安装必要的库
# pip install langchain langchain-openai python-dotenvimport os
from dotenv import load_dotenv# --- 1. 环境与基础组件设置 ---
# 加载 .env 文件中的环境变量 (OPENAI_API_KEY)
load_dotenv()# 导入 LangChain 相关模块
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain, SimpleSequentialChain, SequentialChain
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnable import RunnablePassthroughprint("--- 基础组件初始化 ---")
# 初始化大语言模型 (LLM)
# 这是所有链的“大脑”,负责根据提示生成文本。
# 我们选择 gpt-4o 模型,并设置 temperature=0.7 以增加一些创造性。
llm = ChatOpenAI(model="gpt-4o", temperature=0.7)# 初始化输出解析器 (Output Parser)
# 它的作用是将LLM返回的原始输出(通常是一个 AIMessage 对象)转换成更易于处理的格式,比如纯字符串。
# 在 LCEL 中非常常用。
output_parser = StrOutputParser()# --- 2. SimpleSequentialChain 示例 ---
# 由于它只能返回最终结果,我们调整任务为:主题 -> 标题 -> 关于标题的推文
print("\n--- 示例1: SimpleSequentialChain ---")# a. 第一个链:生成标题
prompt_title = PromptTemplate.from_template("给我一个关于技术主题 '{topic}' 的吸引人的中文博客标题。"
)
chain_title = LLMChain(llm=llm, prompt=prompt_title)# b. 第二个链:根据标题写推文
prompt_tweet = PromptTemplate.from_template("为这篇博客文章写一条推文,标题是:'{title}'" # 注意这里的输入变量名'title'是随意的,因为SimpleSequentialChain会自动传递
)
chain_tweet = LLMChain(llm=llm, prompt=prompt_tweet)# c. 组装简单顺序链
# 它会自动将 chain_title 的输出作为 chain_tweet 的输入
simple_chain = SimpleSequentialChain(chains=[chain_title, chain_tweet], verbose=True)# 运行并打印结果
print("运行 SimpleSequentialChain...")
simple_result = simple_chain.invoke("人工智能在医疗领域的应用")
print("\n[SimpleSequentialChain 最终输出]:")
print(simple_result)# --- 3. SequentialChain 示例 ---
# 这个可以完美解决我们的原始任务:同时返回标题和摘要
print("\n--- 示例2: SequentialChain ---")# a. 第一个链:生成标题 (带 output_key)
# output_key 是关键,它定义了此链输出结果在共享上下文中的键名。
chain_title_seq = LLMChain(llm=llm,prompt=prompt_title,output_key="blog_title" # 指定输出的键为 'blog_title'
)# b. 第二个链:生成摘要
# 它的 input_variables 必须是上一个链的 output_key。
prompt_summary = PromptTemplate.from_template("根据这篇博客的标题 '{blog_title}',写一段2-3句话的简短摘要。"
)
chain_summary_seq = LLMChain(llm=llm,prompt=prompt_summary,output_key="blog_summary" # 指定输出的键为 'blog_summary'
)# c. 组装通用顺序链
# 在这里,我们精确地控制输入和输出
full_sequential_chain = SequentialChain(chains=[chain_title_seq, chain_summary_seq],input_variables=["topic"], # 定义整个流程的初始输入变量output_variables=["blog_title", "blog_summary"], # 定义整个流程最终要返回的变量verbose=True
)# 运行并打印结果
print("运行 SequentialChain...")
seq_result = full_sequential_chain.invoke({"topic": "人工智能在医疗领域的应用"})
print("\n[SequentialChain 最终输出]:")
print(seq_result)# --- 4. LCEL 管道符 | 示例 ---
# 这是解决该任务的现代、推荐方式
print("\n--- 示例3: LCEL 管道符 | ---")# a. 定义第一个处理步骤 (LCEL 风格)
# 这里的 chain_title_lcel 是一个 Runnable 对象
chain_title_lcel = prompt_title | llm | output_parser# b. 定义第二个处理步骤 (LCEL 风格)
# 这里的 chain_summary_lcel 也是一个 Runnable 对象
chain_summary_lcel = prompt_summary | llm | output_parser# c. 使用 RunnablePassthrough.assign 组装流水线
# 这是 LCEL 中管理上下文、构建复杂数据流的核心技巧。
# .assign() 的作用是:并行或串行地运行一些 Runnable,然后将它们的结果添加到(或覆盖)输入的字典中。
lcel_chain = RunnablePassthrough.assign(# 第一步:运行标题链,并将结果存入 'blog_title' 键。# 输入的字典(包含'topic')会自动传递给 chain_title_lcel。blog_title=chain_title_lcel
).assign(# 第二步:运行摘要链,并将结果存入 'blog_summary' 键。# 此时输入的字典已经包含了 'topic' 和 'blog_title',chain_summary_lcel 会自动找到它需要的 'blog_title'。blog_summary=chain_summary_lcel
)# 运行并打印结果
print("运行 LCEL 链...")
lcel_result = lcel_chain.invoke({"topic": "人工智能在医疗领域的应用"})
print("\n[LCEL 链最终输出]:")
print(lcel_result)
4. 关键组件详细讲解
在上面的代码中,我们用到了以下几个核心组件:
-
ChatOpenAI
:- 作用: LangChain 对 OpenAI 聊天模型(如 GPT-3.5, GPT-4, GPT-4o)的封装。它是执行语言理解和生成任务的“大脑”。
- 关键参数:
model
用于指定模型版本,temperature
用于控制生成文本的随机性(0最确定,2最随机)。
-
PromptTemplate
:- 作用: 提示词模板。它允许你创建一个包含动态变量的、可复用的提示词结构。
- 关键参数:
template
是包含占位符(如{topic}
)的字符串,input_variables
是一个列表,定义了所有占位符的名称。
-
LLMChain
:- 作用: 这是 LangChain 经典的链实现,也是最基础的链之一。它将一个
PromptTemplate
和一个LLM
绑定在一起。 - 工作流程: 接收输入变量 -> 使用
PromptTemplate
格式化成最终提示词 -> 将提示词发送给LLM
-> 返回LLM
的输出。 output_key
: 在SequentialChain
中至关重要,它为该链的输出命名,以便后续的链可以引用它。
- 作用: 这是 LangChain 经典的链实现,也是最基础的链之一。它将一个
-
SimpleSequentialChain
/SequentialChain
:- 作用: 如前所述,它们是“容器”链,用于将多个子链按顺序组合起来。
- 关键参数:
chains
是一个按执行顺序排列的子链列表。input_variables
和output_variables
(仅SequentialChain
) 用于定义整个工作流的公共接口。
-
StrOutputParser
(String Output Parser):- 作用: 一个简单的输出解析器,是 LCEL 中非常常用的组件。它的功能是将模型输出(可能是复杂对象)转换成一个简单的字符串。
- 为何需要: 在 LCEL 中,
prompt | llm
的直接输出是一个AIMessage
对象。通过| output_parser
,我们可以直接得到干净的文本,方便后续处理或直接使用。
-
RunnablePassthrough.assign
:- 作用: 这是 LCEL 中实现类似
SequentialChain
共享上下文功能的核心工具。 - 工作原理: 它接收一个字典作为输入,然后并行或串行地运行你提供给它的其他
Runnable
(比如我们例子中的chain_title_lcel
)。最后,它将这些Runnable
的结果作为新的键值对,添加到原始的输入字典中,并返回这个更新后的字典。 - 为何强大: 这种机制让你能够像搭积木一样,一步步地丰富你的数据上下文,非常灵活和强大。
- 作用: 这是 LCEL 中实现类似
总结: 从 SequentialChain
到 LCEL 的演进,体现了 LangChain 从声明式、类封装的设计哲学,向量更简洁、更灵活、功能更强大的函数式组合范式的转变。对于所有新项目,强烈建议直接学习和使用 LCEL。