LangChain LCEL 链式调用详解
目录
-
[一、LCEL 概述](#一、LCEL 概述)
-
[二、顺序链 Sequence(
|管道符)](#二、顺序链 Sequence(| 管道符)) -
[三、并行链 RunnableParallel](#三、并行链 RunnableParallel)
-
[四、分支链 RunnableBranch](#四、分支链 RunnableBranch)
-
[五、函数链 RunnableLambda](#五、函数链 RunnableLambda)
-
[六、串行链(Lambda 中间转换)](#六、串行链(Lambda 中间转换))
一、LCEL 概述
LCEL(LangChain Expression Language)是 LangChain v0.1.x 引入的全新表达式语言,用于以一种声明式、函数式的方式组合 LangChain 组件(Prompt、Model、OutputParser、自定义函数等)。
LCEL 的核心理念:一切皆 Runnable。每个组件都实现了 Runnable 接口,支持 .invoke()、.stream()、.batch() 等调用方式,通过 | 管道符可以把多个 Runnable 按顺序拼接起来,形成一条处理链。
LCEL 等价于 Linux 的管道符
|------前一个组件的输出自动成为下一个组件的输入。
LCEL 的核心优势
| 特性 | 说明 |
|---|---|
| 声明式组合 | 用 ` |
| 统一接口 | 所有组件都实现了 Runnable 接口,.invoke() / .stream() / .batch() |
| 流式输出 | 天然支持 stream(),从第一个 token 开始就能推送 |
| 异步支持 | 自动适配 async / sync |
| 并行执行 | RunnableParallel 可同时执行多条链 |
| 调试方便 | 中间结果可注入 RunnableLambda 打印观察 |
| 可序列化 | 链对象可序列化为 JSON/YAML,方便存储和部署 |
基础三件套
prompt = ChatPromptTemplate.from_messages([...]) # 提示词
model = init_chat_model(...) # 大模型
parser = StrOutputParser() # 输出解析器
# 三件套顺序组合,核心写法
chain = prompt | model | parser
result = chain.invoke({"question": "..."})
二、顺序链 Sequence(| 管道符)
文件: LCEL_RunnableSequenceDemo.py
2.1 什么是顺序链
顺序链是 LCEL 最基础的形式------将 Prompt → Model → OutputParser 三个组件用 | 串联起来,让数据像流水线一样依次经过每个环节。
输入 → Prompt.format() → Model.invoke() → OutputParser.invoke() → 最终输出
2.2 手动逐步调用 vs 链式调用
方式一:手动逐步调用(显式、调试友好)
from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
import os
from dotenv import load_dotenv
load_dotenv(encoding='utf-8')
chat_prompt = ChatPromptTemplate.from_messages([
("system", "你是一个{role},请简短回答我提出的问题"),
("human", "请回答:{question}")
])
# 第一步:格式化提示词
prompt = chat_prompt.invoke({"role": "AI助手", "question": "什么是LangChain,简洁回答100字以内"})
# 第二步:调用模型
model = init_chat_model(
model="qwen-plus",
model_provider="openai",
api_key=os.getenv("aliQwen-api"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
result = model.invoke(prompt)
# 第三步:解析输出
parser = StrOutputParser()
response = parser.invoke(result)
# response 类型:str(纯文本)
方式二:LCEL 链式调用(一行搞定)
# 三件套直接用管道符串联
chain = chat_prompt | model | parser
# 一行调用,等价于上面三步
result_chain = chain.invoke({
"role": "AI助手",
"question": "什么是LangChain,简洁回答100字以内"
})
# result_chain 类型:str(直接是字符串,不需要再手动解析)
2.3 对比总结
| 对比项 | 手动逐步调用 | LCEL 链式 | |
|---|---|---|
| 代码行数 | 多步显式调用 | 一行串联 |
| 中间结果 | 可单独获取 prompt/result | 内部透明处理 |
| 可维护性 | 步骤多时繁琐 | 声明式,清晰直观 |
| 最终输出 | AIMessage 对象需手动解析 | 自动经过 OutputParser,直接拿字符串 |
| 流式支持 | 需手动处理 | chain.stream() 天然支持 |
2.4 链的类型
chain = chat_prompt | model | parser
print(type(chain)) # <class 'langchain_core.runnables.chain.RunnableSequence'>
| 操作符创建的是一个 RunnableSequence 对象,它本身也是 Runnable,可以继续用 | 接到其他组件后面。
三、并行链 RunnableParallel
文件: LCEL_RunnableParallelDemo.py
3.1 什么是并行链
并行链使用 RunnableParallel 同时启动多个子链,所有子链并行执行(非串行等待),待全部完成后以字典形式合并结果。
→ chain1(中文) → {"chinese": "...", "english": "..."}
输入 → RunnableParallel
→ chain2(英文) →
3.2 代码示例
from langchain_core.runnables import RunnableParallel
# 子链1:中文介绍 langchain
prompt1 = ChatPromptTemplate.from_messages([
("system", "你是一个知识渊博的计算机专家,请用中文简短回答"),
("human", "请简短介绍什么是{topic}")
])
parser1 = StrOutputParser()
chain1 = prompt1 | model | parser1
# 子链2:英文介绍 langchain
prompt2 = ChatPromptTemplate.from_messages([
("system", "你是一个知识渊博的计算机专家,请用英文简短回答"),
("human", "请简短介绍什么是{topic}")
])
parser2 = StrOutputParser()
chain2 = prompt2 | model | parser2
# 用 RunnableParallel 并行组装两条链
parallel_chain = RunnableParallel({
"chinese": chain1,
"english": chain2
})
# 一次调用,同时获取中英文结果
result = parallel_chain.invoke({"topic": "langchain"})
# 返回值结构:
# {
# "chinese": "LangChain 是一个用于构建 LLM 应用的框架...",
# "english": "LangChain is a framework for building LLM applications..."
# }
3.3 核心特点
-
并行执行 :两条子链同时运行,不是一条跑完再跑另一条
-
字典合并结果 :返回键为传入字典的 key(
chinese/english) -
独立参数 :
RunnableParallel内部自动将输入{"topic": "langchain"}传递给每个子链 -
运行效率高:适合多条独立搜索/分析任务同时进行
四、分支链 RunnableBranch
文件: LCEL_RunnableBranchDemo.py
4.1 什么是分支链
RunnableBranch 根据条件判断 动态选择执行路径。每个分支是一个 (condition, runnable) 元组,按顺序逐一匹配条件,命中则执行对应路径。
输入 → 判断语言类型
├─ 包含"日语" → japanese_chain
├─ 包含"韩语" → korean_chain
└─ 其他 → english_chain(默认分支)
4.2 代码示例
from langchain_core.runnables import RunnableBranch
# 判断语言函数
def determine_language(inputs):
query = inputs["query"]
if "日语" in query:
return "japanese"
elif "韩语" in query:
return "korean"
else:
return "english"
# 三套提示词
english_prompt = ChatPromptTemplate.from_messages([
("system", "你是一个英语翻译专家,你叫小英"),
("human", "{query}")
])
japanese_prompt = ChatPromptTemplate.from_messages([
("system", "你是一个日语翻译专家,你叫小日"),
("human", "{query}")
])
korean_prompt = ChatPromptTemplate.from_messages([
("system", "你是一个韩语翻译专家,你叫小韩"),
("human", "{query}")
])
parser = StrOutputParser()
# RunnableBranch 构建分支链
# 格式:(条件函数, 对应链),最后一个为默认分支(无条件)
chain = RunnableBranch(
(lambda x: determine_language(x) == "japanese", japanese_prompt | model | parser),
(lambda x: determine_language(x) == "korean", korean_prompt | model | parser),
(english_prompt | model | parser) # 默认分支,无条件
)
# 测试
test_queries = [
{'query': '请你用韩语翻译这句话:"见到你很高兴"'},
{'query': '请你用日语翻译这句话:"见到你很高兴"'},
{'query': '请你用泰语翻译这句话:"见到你很高兴"'} # 无关键词,走默认分支
]
for query_input in test_queries:
lang = determine_language(query_input)
result = chain.invoke(query_input)
# 日语查询 → 小日回答,韩语查询 → 小韩回答,泰语 → 小英回答
4.3 关键点
-
条件为 lambda 函数 :返回
True/False,第一个返回True的分支被执行 -
最后一个元组无条件:作为默认分支,所有条件都不满足时执行
-
每次
invoke只走一条路径:不会同时执行多个分支 -
可用 RunnableBranch 嵌套:实现更复杂的条件逻辑
五、函数链 RunnableLambda
文件: LCEL_RunnableLambdaDemo.py
5.1 什么是函数链
RunnableLambda 将普通 Python 函数包装为 Runnable,插入到链中执行。用于中间数据转换、日志打印、条件计算等。
5.2 代码示例:链中间插入调试打印
from langchain_core.runnables import RunnableLambda
# 调试打印函数(作为中间节点)
def debug_print(x):
logger.info(f"中间结果: {x}")
return {"input": x} # 必须返回下一个节点需要的输入格式
debug_node = RunnableLambda(debug_print)
# 子链1:介绍 langchain
chain1 = prompt1 | model | parser1
# 子链2:翻译为英文
chain2 = prompt2 | model | parser2
# 方式一:直接用函数名(自动转 RunnableLambda)
full_chain = chain1 | debug_print | chain2
# 方式二:显式用 RunnableLambda 包装
full_chain = chain1 | debug_node | chain2
result = full_chain.invoke({"topic": "langchain"})
# 执行流程: chain1输出 → debug_print打印 → {"input: 前一步结果} → chain2翻译 → 最终结果
5.3 RunnableLambda vs 普通 Lambda
| 对比项 | RunnableLambda(func) |
直接用函数名 func |
|---|---|---|
| 显式程度 | 明确表示要纳入 LCEL 链 | 隐式自动包装 |
| 可读性 | 更清晰,适合复杂链 | 简洁,适合简单场景 |
| 功能 | 完全等价 | 自动等价为 RunnableLambda(func) |
两种写法在 LCEL 链中等价,LangChain 内部会自动将函数名转换为 RunnableLambda。
5.4 注意事项
-
函数的返回值会成为下游节点的输入,必须与下游的输入格式匹配
-
RunnableLambda不支持.stream()流式输出(会退化为同步返回) -
适合做数据转换、字段映射、日志记录,不要在这里做 I/O 密集操作
六、串行链(Lambda 中间转换)
文件: LCEL_RunnableSerializableDemo.py
6.1 什么是串行链
当链中需要多次调用大模型,且后一步依赖前一步的输出时,用普通 | 无法直接串联(因为输出格式不匹配)。此时需要在中间用 Lambda 函数做数据转换,将前一步的输出调整为后一步期望的输入格式。
chain1输出(str) → lambda转换 → {"input": chain1_output} → chain2 → 最终结果
6.2 代码示例
# chain1:生成中文介绍
chain1 = prompt1 | model | parser1 # 输出: str
# chain2:将内容翻译为英文
chain2 = prompt2 | model | parser2 # 期望输入: {"input": str}
# 关键:lambda 将 chain1 的 str 输出转换为 chain2 需要的 dict 输入
full_chain = chain1 | (lambda content: {"input": content}) | chain2
result = full_chain.invoke({"topic": "langchain"})
# 执行流程:
# 1. chain1 执行 → "LangChain 是一个..."
# 2. lambda 转换 → {"input": "LangChain 是一个..."}
# 3. chain2 执行(翻译为英文)→ "LangChain is a framework for..."
6.3 对比:串行链 vs 顺序链 vs 并行链
| 维度 | 顺序链(|) |
串行链(|+Lambda) |
并行链(RunnableParallel) |
|---|---|---|---|
| 执行方式 | 串行,一个接一个 | 串行,中间做转换 | 并行,同时执行多条链 |
| 节点关系 | 上下游直接对接 | 中间通过 Lambda 转换格式 | 独立子链,互不依赖 |
| 典型场景 | Prompt→Model→Parser | 多次调用LLM,第二步依赖第一步 | 同一输入,多角度分析 |
| Lambda 用途 | 无需(格式天然匹配) | 转换数据格式/字段映射 | 无需 |
七、各链式调用对比总结
7.1 一图总览
输入
│
├─ Prompt ── Model ── Parser 顺序链(最基础)
│ (三件套管道串联)
│
├─ chain1 ──┐
│ ├─→ RunnableParallel ──→ dict结果 并行链(同时执行多条)
│ chain2 ──┘
│
├─ condition1 ──→ branch1 分支链(条件路由)
│ condition2 ──→ branch2
│ default ──→ branch3
│
└─ chain1 ──→ lambda转换 ──→ chain2 串行链(多次LLM调用)
7.2 核心 API 对照表
| 类/函数 | 作用 | 典型用法 |
|---|---|---|
prompt | model | parser |
顺序链,三件套串联 | 最基础的链 |
RunnableParallel({"a": chain1, "b": chain2}) |
并行执行多条链 | 同时中英文回答 |
RunnableBranch((cond1, chain1), (cond2, chain2), default) |
条件分支选择 | 语言路由、意图识别 |
RunnableLambda(func) |
将 Python 函数纳入链 | 数据转换、调试打印 |
chain1 | (lambda x: {...}) | chain2 |
中间数据格式转换 | 多次调用 LLM |
7.3 选用指南
| 需求场景 | 推荐方案 |
|---|---|
| Prompt → Model → Parser 标准流程 | 顺序链 | |
| 同一问题并行获取多语言/多角度结果 | RunnableParallel |
| 根据条件走不同处理逻辑 | RunnableBranch |
| 中间插入日志、数据转换 | RunnableLambda |
| 多次调用 LLM,后一步依赖前一步输出 | 串行链 | + Lambda 转换 |
| 需要流式输出 | 以上所有链均支持 .stream() |
7.4 统一调用方式
所有 Runnable(链也是 Runnable)都支持以下调用方法:
# 同步调用
result = chain.invoke({"input": "..."})
# 流式调用(token 级)
for token in chain.stream({"input": "..."}):
print(token, end="", flush=True)
# 批量调用
results = chain.batch([{"input": "a"}, {"input": "b"}])
# 异步调用
result = await chain.ainvoke({"input": "..."})