LCEL与链式调用
1. 抽象基类
就是先规定“这一类对象应该具备哪些共同能力”,但不急着规定“每个对象内部具体怎么做”。
抽象基类最有价值的地方:先把共同规则定下来,再让不同对象去实现自己的具体行为。
2. Runnable
把这些“能接收输入并产生输出”的对象,尽量抽象成统一接口。这个统一接口,就是 Runnable。
Runnable 是 LangChain 中最核心的抽象之一,本质上就是一个“可执行的数据处理节点”接口
Runnable 是什么:它表示“这一类对象是可执行组件”,是一种统一抽象标准
1. prompt模板
- 从业务角度看,Prompt 模板最常见的方法是
format(...) - 但从“统一接口、便于后续组合”的角度看,它也可以用
invoke(...)
2. 模型
- 从业务角度看,模型最常见的是
invoke(...) - 它接收上一步 Prompt 的输出,再返回
AIMessage
ai_message = model.invoke(prompt_value)
3.解析器
- 从业务角度看,解析器常见有
parse(...) - 但在 LCEL 里也可以统一通过
invoke(...)接收上一步结果
result = parser.invoke(ai_message)
价值: 组件之间更容易替换;流程更容易串联;链本身也可以继续被当作一个组件使用;中间结果传递方式更统一,不需要每一步都手写很多适配代码
Runnable 接口的对象常用方法
| 方法 | 作用 | 适合场景 |
invoke(input) |
同步处理单个输入 | 最常用,单次调用 |
| batch(inputs) | 同步批量处理多个输入 | 批量任务、离线任务 |
| stream(input) | 同步流式处理 | 打字机输出、长文本生成 |
| ainvoke(input) | 异步处理单个输入 | 异步服务、高并发 |
| abatch(inputs) | 异步批量处理 | 异步批量任务 |
astream(input) |
异步流式处理 | 异步聊天 UI、流式返回 |
astream_log(...) |
流式输出并可选带上中间步骤 | 调试链、观察多节点执行过程 |
LCEL简介
LangChain Expression Language 的缩写,中文通常叫 LangChain 表达式语言
作用很直接:用一种声明式、可组合的方式,把多个 Runnable 连接起来
经典写法:
chain = prompt | model | parser
好处:
1. 表达更清晰
2. 更容易组合
-
用 LCEL 连接出来的结果,本身还可以继续参与组合:
-
一条顺序链可以放进分支链
-
一条顺序链可以放进并行链
-
并行链的输出还可以继续交给下一步处理
LCEL 还会帮我们自动处理“前一步结果如何传给下一步”这类重复性工作。只有当前后节点的输入输出结构不匹配时,我们才需要自己插入 lambda 或 RunnableLambda 做一次映射。
3. 统一支持同步、异步、批量、流式
根据 LangChain 官方参考,RunnableSequence 和 RunnableParallel 这类组合结构,会自动继承 Runnable 的同步、异步、批量、流式能力。这意味着你不只是得到了一条“可读的链”,还得到了一条“可统一执行的链”。
LCEL的核心思想
- 顺序组合:前一步输出作为下一步输入
- 条件路由:根据输入选择不同链
- 并行组合:同一输入同时喂给多条链
- RunnableSequence 和 RunnableParallel 是两种最核心的组合原语
- 很多其他 Runnable 结构,本质上都可以看作在这两种基础组合能力上的扩展或变体
RunnableLambda 则相当于在组合过程中插入一段你自己的 Python 逻辑,让你可以做:
- 中间结果调试
- 输入结构映射
- 输出结果整理
- 简单业务判断
LCEL和Chain的关系
- LCEL 是什么:它是构建流程的一种表达方式,重点在“怎么写”。
- Chain 是什么:它是通过 LCEL 或其他组合方式构建出来的可执行流程,重点在“最后得到了什么”。
一句最容易记的话来理解:LCEL 是构建链的方法,Chain 是构建出来的流程
Chain结构
| 类型 | 典型写法 / 类名 | 执行方式 | 输入 → 输出 | 典型场景 |
| 顺序链 | prompt | model | parser/RunnableSequence |
一步接一步执行 | 单输入 → 单输出 | 最基础的问答、抽取、摘要 |
| 分支链 | RunnableBranch(...) | 按条件只走其中一条子链 | 单输入 → 单输出 | 意图路由、多语言路由 |
| 多步串行链 | 多条子链继续用 | 串联 |
前一步结果给后一步 | 单输入 → 单输出 | 先总结再翻译、先整理再生成 |
| 并行链 | RunnableParallel({...}) | 多条子链同时执行 | 单输入 → 多输出 | 中英文同时生成、多模型并跑 |
| 函数链 | RunnableLambda(func) | 在链中插入 Python 函数 | 取决于函数 | 字段映射、调试、轻量业务逻辑 |
简洁一点:
- 只有一条直线流程:先用顺序链
- 要按条件切换不同流程:用分支链
- 要把多个模型调用前后串起来:用多步串行链
- 要同一输入同时跑多条链:用并行链
- 要插入自定义 Python 逻辑:用函数链
1. RunnableSequence(顺序敛)
chain = prompt | model | parser
import os
from dotenv import load_dotenv
load_dotenv(encoding="utf-8")
from langchain.chat_model import init_chat_model
from langchain_core.output_parser import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from loguru import logger
# 创建聊天提示模板(Runnable 子类):包含系统角色与用户问题占位符
chat_prompt = ChatPromptTemplate.from_messages(
[
("system","你是一个{role},请简短回答我提出的问题"),
("human", "请回答:{question}"),
]
)
# 初始化聊天模型(同样实现 Runnable,支持 invoke/stream/batch)
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",
)
# 字符串输出解析器(Runnable):从 AIMessage 中取出文本,得到更适合业务继续处理的文本结果
parser = StrOutputParser()
# 用管道符 | 构建顺序链;LCEL 是写法,组合后的 chain 才是最终得到的 RunnableSequence 对象
chain = chat_prompt | model | parser
# 链整体也是 Runnable:一次 invoke 完成「渲染 → 模型 → 解析」,入参为提示词变量
result_chain = chain.invoke(
{"role": "AI助手", "question": "什么是LangChain,简洁回答100字以内"}
)
logger.info(f"Chain执行结果:\n{result_chain}")
logger.info(f"Chain执行结果类型: {type(result_chain)}")
print()
2.RunnableBranch(分支链)
RunnableBranch(
(条件1, 链1),
(条件2, 链2),
默认链,
)
在项目里非常常见,比如:
- 根据语言选择不同翻译链
- 根据用户意图选择不同客服流程
- 根据问题类型选择不同 Prompt 或模型
import os
from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableBranch
from loguru import logger
from dotenv import load_dotenv
load_dotenv(encoding="utf-8")
# 英语分支:提示词模板 + 占位符 query
english_prompt = ChatPromptTemplate.from_messages(
[("system", "你是一个英语翻译专家,你叫小英"), ("human", "{query}")]
)
japanese_prompt = ChatPromptTemplate.from_messages(
[("system", "你是一个日语翻译专家,你叫小日"), ("human", "{query}")]
)
korean_prompt = ChatPromptTemplate.from_messages(
[("system", "你是一个韩语翻译专家,你叫小韩"), ("human", "{query}")]
)
def determine_language(inputs):
"""根据 query 中的关键词判断语言类型,供分支条件使用。"""
query = inputs["query"]
# # 判断字符是否在字符串中
if "日语" in query:
return "japanese"
elif "韩语" in query:
return "korean"
else:
return "english"
model = init_chata_model(
model="qwen-plus",
model_provider="openai",
api_key=os.getenv("aliQwen-api"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
parser = StrOutputParser()
# RunnableBranch
# 条件为可调用对象,接收输入 dict,返回 bool;第一个命中的分支会执行,最后一个参数是默认分支
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)
logger.info(f"检测到语言类型: {lang}")
if lang == "japanese":
chatPromptTemplate = japanese_prompt
elif lang == "korean":
chatPromptTemplate = korean_prompt
else:
chatPromptTemplate = english_prompt
result = chain.invoke(query_input)
logger.info(f"输出结果: {result}\n")
3.Multi-Step Chain(多步船型链)
重点是:如何把多条子链首尾串起来,形成多步串行流程
import os
from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from loguru import logger
from dotenv import load_dotenv
load_dotenv(encoding="utf-8")
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",
)
# 子链 1:用中文介绍某主题,输出为 str
prompt1 = ChatPromptTemplate.from_messages(
[
("system", "你是一个知识渊博的计算机专家,请用中文简短回答"),
("human", "请简短介绍什么是{topic}"),
]
)
parser1 = StrOutputParser()
chain1 = prompt1 | model | parser1
result1 = chain1.invoke({"topic": "langchain"})
logger.info(result1)
# 子链 2:将用户输入翻译成英文,期望入参为 {"input": 文本}
prompt2 = ChatPromptTemplate.from_messages(
[("system", "你是一个翻译助手,将用户输入内容翻译成英文"), ("human", "{input}")]
)
parser2 = StrOutputParser()
chain2 = prompt2 | model | parser2
# 串行组合:chain1 输出文本,用 lambda 转为 {"input": content},以匹配 chain2 需要的输入结构
full_chain = chain1 | (lambda content: {"input": content}) | chain2
# 一次 invoke:先执行 chain1,再把结果作为 chain2 的 input
result = full_chain.invoke({"topic": "langchain"})
logger.info(result)
最值得你注意的点,不是文件名,而是中间这个映射动作:
(lambda content: {"input": content})
4.RunnableParallel(并行链)
RunnableParallel 解决的是另一个常见问题:同一份输入,我想同时交给多条链处理。
并行链不是“写在一起”,而是真的在“同一输入下多路执行”。
写法:
1.显式
parallel_chain = RunnableParallel({
"chinese": chain1,
"english": chain2,
})
2.直接用字典表达并行结构:
parallel_then_summary = {
"paragraph_1": chain1,
"paragraph_2": chain2,
} | summary_chain
- 先并行运行
chain1和chain2 - 把结果汇总成一个字典
- 再把这个字典交给后面的
summary_chain
import os
from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel
from loguru import logger
from dotenv import load_dotenv
load_dotenv(encoding="utf-8")
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",
)
# 子链 1:中文简短介绍
prompt1 = ChatPromptTemplate.from_messages(
[
("system", "你是一个知识渊博的计算机专家,请用中文简短回答"),
("human", "请简短介绍什么是{topic}"),
]
)
parser1 = StrOutputParser()
chain1 = prompt1 | model | parser1
# 子链 2:英文简短介绍(与 chain1 同结构,仅提示词语言不同)
prompt2 = ChatPromptTemplate.from_messages(
[
("system", "你是一个知识渊博的计算机专家,请用英文简短回答"),
("human", "请简短介绍什么是{topic}"),
]
)
parser2 = StrOutputParser()
chain2 = prompt2 | model | parser2
# RunnableParallel:同一输入会同时喂给多个子链,结果按键汇总为 dict
parallel_chain = RunnableParallel({"chinese": chain1, "english": chain2})
# 一次 invoke,返回 {"chinese": "...", "english": "..."}
result = parallel_chain.invoke({"topic": "langchain"})
logger.info(result)
5.函数链
RunnableLambda 的价值在于:把普通 Python 函数也变成 Runnable。
在开发中经常遇到这些情况
- 上一步输出结构不符合下一步输入要求
- 想打印中间结果做调试
- 想做一次字段重命名
- 想插入一点简单业务逻辑
如果没有函数节点,你就得把链拆开,在外面手写很多中间处理代码;有了 RunnableLambda,这些逻辑就能被放回链内部
写法:
1. 直接写方法名
chain = chain1 | debug_print | chain2
2. 用RunnableLambda包装
chain = chain1 | RunnableLambda(debug_print) | chain2
import os
from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda
from loguru import logger
from dotenv import load_dotenv
load_dotenv(encoding="utf-8")
model = init_chat_model(
model="qwen-plus",
model_provider="openai",
api_key=os.getenv("aliQwen-api"),
temperature=0.0,
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
def debug_print(x):
"""打印中间结果,并把文本包装成 chain2 所需的 {"input": 文本} 结构。"""
logger.info(f"中间结果:{x}")
return {"input": x}
# 子链 1:中文介绍某主题,输出 str
prompt1 = ChatPromptTemplate.from_messages(
[
("system", "你是一个知识渊博的计算机专家,请用中文简短回答"),
("human", "请简短介绍什么是{topic}"),
]
)
parser1 = StrOutputParser()
chain1 = prompt1 | model | parser1
# 子链 2:将 input 翻译成英文
prompt2 = ChatPromptTemplate.from_messages(
[("system", "你是一个翻译助手,将用户输入内容翻译成英文"), ("human", "{input}")]
)
parser2 = StrOutputParser()
chain2 = prompt2 | model | parser2
# 方式一:直接把函数放在 | 之间,LCEL 会自动包装成 Runnable
full_chain = chain1 | debug_print | chain2
result1 = full_chain.invoke({"topic": "langchain"})
logger.info(f"最终结果111:{result1}")
# 方式二:显式使用 RunnableLambda(函数),效果相同
debug_node = RunnableLambda(debug_print)
full_chain = chain1 | debug_node | chain2
result2 = full_chain.invoke({"topic": "langchain"})
logger.info(f"最终结果222:{result2}")

浙公网安备 33010602011771号