【LangChain学习笔记】链式调用

85 阅读9分钟

一、核心定义:Runnable 是什么?

Runnable 是 LangChain 生态中组件交互的标准化接口,核心作用是统一所有核心组件(提示模板、大模型、解析器、工具等)的调用方式与数据流转规则,是组件实现“即插即用”和链式调用的基础。

关键特性:

  1. 内置组件默认实现:LangChain 核心组件(PromptTemplateChatOpenAIJsonOutputParserStrOutputParser 等)均已默认实现 Runnable 接口,可直接用于串联调用;
  2. 统一调用能力:任何实现 Runnable 接口的组件,都具备 invokestreambatch 等标准调用方法,无需额外适配;
  3. 无缝串联:支持通过特定方式组合为复杂流程,组件间数据自动流转,无需手动传递参数。
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import Runnable
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

# 初始化内置核心组件
prompt_template = PromptTemplate.from_template("你好呀")
str_output_parser = StrOutputParser()
llm = ChatOpenAI(
    model_name="qwen-plus",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key=""  # 替换为个人有效 API Key
)

# 验证组件是否为 Runnable 实例(均返回 True)
print(isinstance(prompt_template, Runnable))  # True
print(isinstance(llm, Runnable))              # True
print(isinstance(str_output_parser, Runnable))# True
    

二、核心方法:Runnable 的标准调用方式

Runnable 接口定义了 3 种通用调用方法,适配不同业务场景,所有实现类(内置组件、组合序列)均必须支持,调用语法统一。

invoke:同步调用(最常用)

功能:

一次性执行组件/链式对象,等待完整计算完成后返回结果(阻塞式调用)。

适用场景:

需要完整结果的场景(如批量数据处理、后台任务、简单问答、数据解析等)。

基础语法:

component.invoke(input_data)

其中 input_data 为组件所需的输入参数,通常是字典格式(需匹配组件的参数要求)。

stream:流式调用

功能:

非阻塞式调用,逐块返回执行结果,无需等待完整计算完成。

适用场景:

需要实时展示结果的场景(如聊天机器人界面、在线问答、长文本生成、实时日志输出等)。

基础语法:

for chunk in component.stream(input_data): print(chunk)

通过循环迭代器逐块获取结果,需注意过滤空片段以保证输出整洁。

batch:批量调用

功能:

一次性传入多个输入参数组成的列表,批量执行组件/链式对象,最终返回与输入顺序对应的结果列表。

适用场景:

高效处理批量任务(如批量文本生成、批量格式解析、多问题批量问答、批量数据清洗等),提升处理效率。

基础语法:

component.batch([input1, input2, input3, ...])

三、核心使用:Runnable 的组合与流转方式

Runnable 的核心价值在于通过灵活组合,实现复杂的自动化流程。常见组合方式包括:串行、并行、自定义、透传、分支,所有组合后的对象均为 Runnable 实例,支持标准调用方法。

串行组合:有序执行流水线

多个 Runnable 组件按顺序执行,前一个组件的输出作为后一个组件的输入,形成线性流水线。LangChain 支持 3 种串行实现方式,核心逻辑一致。

方式 1:通过 RunnableSequence 显式定义

from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableSequence
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

# 初始化组件
prompt_template = PromptTemplate.from_template("你好呀,我是{name}")
str_output_parser = StrOutputParser()
llm = ChatOpenAI(
    model_name="qwen-plus",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key=""  # 替换为个人有效 API Key
)

# 显式定义串行顺序(first→middle→last)
chain = RunnableSequence(
    last=str_output_parser,
    middle=[llm],
    first=prompt_template,
)

# 调用串行链(传入 first 组件所需参数)
response = chain.invoke(input={"name": "LangChain 学习者"})
print(response)  # 输出示例:你好呀,我是 LangChain 学习者

方式 2:通过 pipe 方法链式调用

# 沿用上述代码的组件初始化逻辑
# 通过 pipe 方法按顺序串联
chain = prompt_template.pipe(llm).pipe(str_output_parser)

# 调用方式一致
response = chain.invoke(input={"name": "LangChain 学习者"})
print(response)
    

方式 3:通过 | 运算符串联(新版 LangChain 推荐)

# 沿用上述代码的组件初始化逻辑
# 通过 | 运算符按顺序串联(最简洁)
chain = prompt_template | llm | str_output_parser

# 调用方式一致
response = chain.invoke(input={"name": "LangChain 学习者"})
print(response)
    

关键说明:

无论哪种串行方式,组合后的 chain 都是 Runnable 实例,可直接使用 invokestreambatch 等标准方法。

并行组合:多流程同步执行

通过 RunnableParallel 类实现多个 Runnable 流程同步执行,最终聚合所有流程的结果(按定义的键名返回字典)。适用于多任务并行处理场景。

from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableParallel
from langchain_openai import ChatOpenAI

# 初始化组件
str_output_parser = StrOutputParser()
llm = ChatOpenAI(
    model_name="qwen-plus",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key=""  # 替换为个人有效 API Key
)

# 定义两个独立的串行链(均为 Runnable 实例)
chain1 = PromptTemplate.from_template("帮我计算1+1=?") | llm | str_output_parser
chain2 = PromptTemplate.from_template("帮我计算2+1=?") | llm | str_output_parser

# 并行组合两个链
parallel_chain = RunnableParallel(
    calc1=chain1,  # 键名 calc1 对应 chain1 的结果
    calc2=chain2   # 键名 calc2 对应 chain2 的结果
)

# 调用并行链(无需传入额外参数,因子链无占位符)
response = parallel_chain.invoke(input={})
print(response)
# 输出示例:{'calc1': '1+1=2', 'calc2': '2+1=3'}
    

关键说明:

并行链的返回结果是字典,键为 RunnableParallel 中定义的名称,值为对应子链的执行结果;若子链需要输入参数,可通过 invoke 传入统一参数,子链会自动获取所需字段。

自定义 Runnable:通过 RunnableLambda 实现

若内置组件无法满足个性化需求,可通过 RunnableLambda 封装自定义逻辑(如数据清洗、格式转换、结果加工等),快速生成自定义 Runnable 组件,无缝融入现有流程。

from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda
from langchain_openai import ChatOpenAI

# 初始化组件
str_output_parser = StrOutputParser()
llm = ChatOpenAI(
    model_name="qwen-plus",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key=""  # 替换为个人有效 API Key
)

# 自定义 Runnable:将结果转为大写(通过 Lambda 表达式)
upper_lambda = RunnableLambda(lambda x: x.upper())

# 组合流程:模板 → 大模型 → 解析 → 自定义加工
chain = (
    PromptTemplate.from_template("将苹果翻译为英文")
    | llm
    | str_output_parser
    | upper_lambda
)

# 调用链
response = chain.invoke(input={})
print(response)  # 输出示例:APPLE

透传:保留原始结果(RunnablePassthrough)

通过 RunnablePassthrough 可实现“结果透传”,即保留某个环节的原始输出,同时结合其他组件加工结果,最终聚合返回。适用于需要同时获取原始数据和加工数据的场景。

from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import (
    RunnableLambda,
    RunnablePassthrough,
    RunnableParallel,
)
from langchain_openai import ChatOpenAI

# 初始化组件
str_output_parser = StrOutputParser()
llm = ChatOpenAI(
    model_name="qwen-plus",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key="sk-94207ea3eb4b44eaae46683ed8ac55e5"  # 替换为个人有效 API Key
)

# 自定义加工逻辑:转为大写
upper_lambda = RunnableLambda(lambda x: x.upper())

# 组合流程:翻译 → 解析 → 同时返回加工结果和原始结果
chain = (
    PromptTemplate.from_template("将苹果翻译为英文")
    | llm
    | str_output_parser
    | RunnableParallel(
        processed=upper_lambda,  # 加工后的结果(大写)
        original=RunnablePassthrough()  # 透传原始翻译结果
    )
)

# 调用链
response = chain.invoke(input={})
print(response)
# 输出示例:{'processed': 'APPLE', 'original': 'apple'}
    

分支:条件流转(RunnableBranch)

通过 RunnableBranch 可实现“条件分支流转”,即根据前一个组件的输出结果,匹配不同的条件逻辑,执行对应的后续组件。适用于需要动态选择处理流程的场景。

from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableBranch, RunnableLambda
from langchain_openai import ChatOpenAI

# 初始化组件
str_output_parser = StrOutputParser()
llm = ChatOpenAI(
    model_name="qwen-plus",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key="sk-94207ea3eb4b44eaae46683ed8ac55e5"  # 替换为个人有效 API Key
)

# 分支流程:根据大模型返回结果中的关键词,执行不同替换逻辑
chain = (
    PromptTemplate.from_template("你是谁?")  # 询问模型身份
    | llm
    | str_output_parser
    | RunnableBranch(
        # 条件 1:若结果包含“通义千问”,替换为“通义千问 Plus”
        (lambda x: "通义千问" in x, RunnableLambda(lambda x: x.replace("通义千问", "通义千问 Plus"))),
        # 条件 2:若结果包含“豆包”,替换为“豆包 Plus”
        (lambda x: "豆包" in x, RunnableLambda(lambda x: x.replace("豆包", "豆包 Plus"))),
        # 默认条件:无匹配关键词时,直接返回原始结果
        RunnableLambda(lambda x: x)
    )
)

# 调用链
response = chain.invoke(input={})
print(response)
# 输出示例(若模型回答“我是通义千问”):我是通义千问 Plus
    

四、底层原理:Runnable 组合与执行逻辑

所有 Runnable 组合(串行、并行等)的执行逻辑,均基于“统一接口 + 数据流转”的核心原理,具体可拆解为:

  1. 接口统一:所有组件/组合对象均实现 Runnable 接口,确保调用方法(invoke/stream/batch)和数据格式的一致性;
  2. 触发机制:调用组合对象的某个方法时,该方法会被自动传递给所有子 Runnable 组件;
  3. 参数传递:原始输入参数仅需传入组合对象,首个 Runnable 组件接收参数并执行对应方法,执行结果作为下一个组件的输入参数,依次流转(并行场景下各子链同步接收参数);
  4. 结果聚合:所有组件执行完成后,组合对象按定义的规则(串行取最后一个组件结果、并行聚合为字典)返回最终结果。

简化理解:

Runnable 就像“标准化的管道接口”,组件是“管道片段”,组合方式是“管道连接方式”。调用组合对象就像打开水龙头,水(参数)从第一个管道片段流入,按连接方式依次流过所有片段,最终从出口(最终结果)流出,整个过程无需手动干预中间流转。

五、关键注意事项

  1. 参数匹配:输入参数需与组合对象中“首个接收参数的组件”的要求匹配(如模板的占位符、自定义组件的输入类型),否则会报错;
  2. 格式兼容:组件间的输出/输入格式需匹配(如前一个组件输出字符串,后一个组件需支持接收字符串),建议通过单独调用组件的 invoke 方法测试格式;
  3. 组合灵活度:可嵌套组合(如串行链作为并行链的子链、分支链中包含透传逻辑),核心是保证所有嵌套组件均为 Runnable 实例。