LangChain Chains基础实操:从组件串联到Runnable接口全解析
在LangChain开发中,单个组件(如提示词模板、模型)的功能有限,而实际应用场景往往需要多个组件协同工作——比如“提示词生成→模型调用→结果解析→二次模型优化”,手动分步调用不仅代码冗余,还容易出现数据流转错误。
Chains(链)作为LangChain的核心组件之一,正是为解决这一问题而生。它的核心思想是“将多个组件串联,上一个组件的输出作为下一个组件的输入”,实现数据的自动化流转与组件协同,让复杂的开发流程变得简洁、可维护。
本文将从基础到原理,手把手拆解Chains的基础使用、|运算符重写的底层逻辑、StrOutputParser解析器的核心作用,以及Runnable接口的核心意义,搭配原创实操代码,帮你快速掌握链的开发技巧,避开常见坑点。
一、Chains基础:组件串联的核心用法
1. 核心定位与价值
LangChain中的链,本质是“组件的有序组合”,它通过特定的规则将提示词模板、模型、解析器等组件串联起来,让数据在组件间自动流转,无需开发者手动传递中间结果。这种方式不仅简化了代码编写,还能保证组件间的协同一致性,大幅提升开发效率。
链的核心前提的是:只有Runnable接口的子类对象,才能加入链中。我们之前学习的PromptTemplate、ChatPromptTemplate、ChatTongyi等组件,均是Runnable接口的子类,因此可以直接通过链进行串联。
2. 基础链实操(原创代码:文案生成链)
最基础的链是“提示词模板+模型”的串联,使用|符号即可快速构建,链的执行可通过invoke(阻塞执行)和stream(流式执行)两种方式触发,适配不同的输出需求。
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.runnables.base import RunnableSerializable
# 1. 构建Chat提示词模板(多轮对话场景,支持历史会话注入)
chat_template = ChatPromptTemplate.from_messages(
[
("system", "你是一名专业的文案策划,擅长撰写短视频标题,简洁有吸引力,无需额外解释"),
MessagesPlaceholder("history"), # 历史会话占位符
("human", "请为{topic}撰写3个短视频标题,每句不超过15字")
]
)
# 2. 准备历史会话数据(模拟用户之前的交互)
history_data = [
("human", "请为美食探店视频写标题"),
("ai", "1. 藏在巷子里的宝藏小店|一口封神 2. 人均50吃到撑!市井烟火气 3. 谁懂啊!这家店承包我的味蕾"),
("human", "再来一组,风格更活泼一点")
]
# 3. 初始化Chat模型
model = ChatTongyi(model="qwen3-max")
# 4. 构建链:提示词模板 | 模型(|是链串联的核心符号)
chain: RunnableSerializable = chat_template | model
# 查看链的类型(RunnableSequence,Runnable接口的子类)
print("链的类型:", type(chain))
# 方式1:invoke阻塞执行(一次性获取完整结果)
print("\n=== invoke阻塞执行输出 ===")
res = chain.invoke({"history": history_data, "topic": "美食探店"})
print(res.content)
# 方式2:stream流式执行(逐段输出结果,适合长文本场景)
print("\n=== stream流式执行输出 ===")
for chunk in chain.stream({"history": history_data, "topic": "美食探店"}):
print(chunk.content, end="", flush=True)
3. 基础链执行流程解析
上述代码中,链的执行流程清晰且自动化,无需手动干预中间步骤:
- 调用chain.invoke/stream时,传入变量(history和topic);
- 提示词模板(chat_template)接收变量,生成完整的提示词(包含历史会话和当前需求);
- 提示词作为输入,传递给模型(model);
- 模型处理后输出结果,返回给开发者(invoke一次性返回,stream逐段返回)。
这种“输入→模板处理→模型调用→输出”的自动化流转,正是链的核心价值所在。
二、底层原理:|运算符的重写机制
1. 运算符重写的核心逻辑
我们在构建链时使用的|符号,并非Python原生的按位或运算符,而是LangChain对|运算符进行了重写。在Python中,所有运算符的行为都由类的魔法方法决定:
- a + b 本质是调用 a.add(b) 方法;
- a | b 本质是调用 a.or(b) 方法。
LangChain通过重写Runnable接口的__or__方法,让|符号拥有了“组件串联”的功能——当我们用|连接两个Runnable子类对象时,会自动生成一个新的链对象(RunnableSequence),而非执行原生的按位或运算。
2. 极简示例:模拟|运算符重写(原创代码)
为了更直观理解运算符重写的原理,我们可以自定义一个简单的类,重写__or__方法,实现类似LangChain链的串联功能:
class Component:
def __init__(self, name):
self.name = name # 组件名称
def __str__(self):
return f"Component({self.name})"
# 重写__or__方法,实现组件串联
def __or__(self, other):
# 返回自定义的链对象,存储串联的组件
return ChainSequence(self, other)
class ChainSequence:
def __init__(self, *args):
self.components = [] # 存储串联的组件列表
for arg in args:
self.components.append(arg)
# 重写__or__方法,支持多组件串联(a | b | c)
def __or__(self, other):
self.components.append(other)
return self
# 模拟链的执行:依次输出每个组件的名称
def run(self):
print("链执行流程:")
for idx, comp in enumerate(self.components, 1):
print(f"步骤{idx}:{comp}")
# 测试:组件串联与链执行
if __name__ == "__main__":
# 创建3个自定义组件
comp1 = Component("提示词模板")
comp2 = Component("Chat模型")
comp3 = Component("结果解析器")
# 用|串联组件,生成链对象
chain = comp1 | comp2 | comp3
# 执行链,查看流程
chain.run()
# 查看链的类型
print("\n链对象类型:", type(chain))
上述代码中,我们通过重写Component类的__or__方法,实现了用|串联组件的功能,生成的ChainSequence对象就类似LangChain中的RunnableSequence链对象。这也正是LangChain链能够通过|符号串联组件的底层逻辑。
三、实战必备:StrOutputParser解析器的核心作用
1. 常见坑点:组件输出类型不匹配
在构建多组件串联的链时,最容易遇到的问题是“上一个组件的输出类型,不符合下一个组件的输入要求”。比如我们尝试用“提示词模板→模型→模型”构建链,会直接报错:
from langchain_core.prompts import PromptTemplate
from langchain_community.chat_models.tongyi import ChatTongyi
# 初始化模型和提示词模板
model = ChatTongyi(model="qwen3-max")
prompt = PromptTemplate.from_template(
"请为{product}撰写1句核心宣传语,仅输出宣传语,无需额外内容"
)
# 错误示例:模型输出无法直接作为第二个模型的输入
chain = prompt | model | model
try:
res = chain.invoke({"product": "智能手表"})
print(res.content)
except Exception as e:
print("报错信息:", e)
报错的核心原因是:第一个模型(model)的输出是AIMessage类型,而第二个模型的invoke方法要求输入类型为PromptValue、str或消息列表,无法直接接收AIMessage类型——这就需要用到StrOutputParser解析器进行类型转换。
2. StrOutputParser核心功能
StrOutputParser是LangChain内置的简单字符串解析器,它的核心作用有两个:
- 将模型输出的AIMessage类型,转换为纯字符串,满足后续组件的输入要求;
- 它本身是Runnable接口的子类,可以直接加入链中,实现组件无缝串联。
3. 正确实操:加入解析器的多模型链(原创代码)
我们将StrOutputParser加入链中,解决类型不匹配问题,实现“文案生成→文案优化”的多模型协同:
from langchain_core.prompts import PromptTemplate
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.output_parsers import StrOutputParser
# 1. 初始化组件
model = ChatTongyi(model="qwen3-max")
# 提示词模板1:生成核心宣传语
prompt1 = PromptTemplate.from_template(
"请为{product}撰写1句核心宣传语,突出{feature}卖点,仅输出宣传语"
)
# 提示词模板2:优化宣传语(更活泼、有网感)
prompt2 = PromptTemplate.from_template(
"请将这句宣传语优化得更活泼、有网感,适配短视频平台,仅输出优化后内容:{slogan}"
)
# 解析器:将AIMessage转换为字符串
parser = StrOutputParser()
# 2. 构建链:prompt1 → model → parser → prompt2 → model
chain = prompt1 | model | parser | prompt2 | model
# 3. 执行链,传入变量
res = chain.invoke({
"product": "无线耳机",
"feature": "降噪+低延迟"
})
print("最终优化后的宣传语:", res.content)
上述链的执行流程的:
prompt1生成提示词 → 模型生成宣传语(AIMessage) → parser将其转为字符串 → prompt2接收字符串生成优化提示词 → 模型输出优化后的宣传语。整个流程自动化流转,完美解决类型不匹配问题。
四、核心基石:Runnable接口的底层作用
1. Runnable接口的核心地位
LangChain中的绝大多数核心组件(提示词模板、模型、解析器、链),都继承了Runnable抽象基类(位于langchain_core.runnables.base)。Runnable接口不仅定义了组件的核心方法(如invoke、stream),还通过重写__or__方法,为链的串联提供了底层支持。
当我们用|连接多个Runnable子类对象时,生成的链对象是RunnableSequence类型(RunnableSerializable的子类),而RunnableSerializable又是Runnable接口的直接子类——这意味着,链本身也是Runnable的子类,因此可以调用invoke、stream方法触发执行。
2. 关键结论(实操必记)
- 只有Runnable子类对象,才能加入链中(组件必须符合Runnable接口规范);
- 用|串联组件生成的链,本质是RunnableSequence对象,支持invoke和stream执行;
- Runnable接口的__or__方法重写,是链能够实现组件串联的核心底层逻辑;
- 解析器(如StrOutputParser)也是Runnable子类,用于解决组件间输出/输入类型不匹配问题。
五、总结:Chains链的实操要点与后续拓展
Chains链的核心价值,是将LangChain的各个组件“串联成线”,实现数据的自动化流转与协同工作,让开发者从繁琐的手动组件调用中解放出来,专注于业务场景的实现。
结合本文的实操内容,总结3个核心实操要点:
- 基础链构建:用|符号串联Runnable子类组件,优先使用invoke(阻塞)和stream(流式)两种执行方式;
- 类型匹配:多模型/多组件串联时,若出现类型不匹配报错,加入StrOutputParser解析器进行转换;
- 底层逻辑:理解|运算符的重写机制和Runnable接口的核心作用,能更好地排查链的执行错误。
后续我们还会基于基础链,学习更复杂的链类型(如SequentialChain、RouterChain),结合向量检索、智能体等组件,解锁更复杂的大模型应用场景——而掌握本文的基础用法,是后续进阶的关键前提。