LangChain:LCEL(表达式语言)与管道符(|)
LCEL(LangChain Expression Language—表达式语言)是 LangChain 框架中构建 Agent 智能体的核心与灵魂。它提供了一套声明式的语法,能用管道符(|)像拼接积木一样,将各种功能组件(模型、提示、解析器、工具等)组合成一个清晰的数据处理流水线。
LCEL概念
LCEL是什么
LCEL(LangChain Expression Language ) 是LangChain的声明式组合语法,使用**管道符(|)**将各种组件(如模型、提示、解析器、工具等)连接成复杂的执行链。
设计理念是:**声明式编排 **和 **一切皆可运行(Everything is a Runnable)**。
- 声明式编排:只需要清晰地描述“我想做什么”(例如:用这个提示词,调用那个模型,然后解析结果),而不是一步步地指示“我该如何做”。这让代码逻辑变得极其直观。
- 一切皆 Runnable:在 LCEL 中,无论是提示词模板、大模型、输出解析器,还是自定义组件,都被抽象为
Runnable对象,拥有invoke、stream、batch等统一接口。它们就像标准化的积木块,可以通过统一的接口被轻松组合和调用。
通过统一接口(Runnable)+ 管道组合(|),将复杂的工作流表示为简单的管道操作。
管道符(|)是什么
管道符 | 的灵感来自 Unix 管道,它将左边组件的输出作为右边组件的输入。
管道符 |是LCEL的连接运算符,它将多个 Runnable 组件按顺序连接起来,形成一个数据流。前一个组件的输出会自动作为下一个组件的输入。整个链条本身也是一个 Runnable,可以被调用。
例如,A | B意味着“将A的输出作为B的输入”。这种语法极其直观,让代码读起来就像描述一个数据处理流水线。
1 | from langchain_core.prompts import ChatPromptTemplate |
执行流程图解
1 | 用户输入: {"topic": "人工智能"} |
组件与管道符(|)
- 组件:任何实现了
Runnable接口的对象(如ChatPromptTemplate、ChatOpenAI、StrOutputParser等)。 - **管道符 (
|)**:将一个Runnable的输出传递给下一个Runnable作为输入,从而构建工作流。 - 工作流:用户输入 → Prompt 模板(格式化)→ LLM 模型(生成回答)→ 输出解析器(提取纯文本)→ 最终输出。
可以把管道符 | 想象成工厂里的传送带,而每个组件则是传送带上的一个工作站。原料(用户输入)从传送带一端进入,依次经过每个工作站(提示模板、语言模型、输出解析器等)的加工,最终在另一端产出成品(最终答案)。
LCEL组件
关键组件详解
| 组件 | 作用 | 示例 |
|---|---|---|
RunnablePassthrough |
原样传递输入 | {"query": RunnablePassthrough()} |
RunnableParallel |
并行执行多个分支 | RunnableParallel(a=chain1, b=chain2) |
RunnableLambda |
包装自定义函数 | RunnableLambda(lambda x: x.upper()) |
RunnableBranch |
条件分支 | RunnableBranch((condition, chain1), default) |
itemgetter |
提取字典字段 | itemgetter("messages") |
常用组件的输入输出类型
PromptTemplate:input: dict→output: PromptValueChatModel:input: PromptValue / str / list[BaseMessage]→output: BaseMessageStrOutputParser:input: BaseMessage→output: strRunnableLambda:任意可调用对象,需要你手动保证类型匹配。
LCEL优势
- 代码更简洁:从嵌套调用变为线性的管道式组合,自动处理组件间的数据传递和格式转换,大幅提升可读性,让代码更优雅。
- 原生支持高级级特性:LCEL 链本身具备流式输出(
stream)、并行处理(batch)、异步调用(ainvoke)和自动重试、回退等机制,也是生产环境所需的能力,可以轻松附加到链上,无需额外代码。 - 强大的组合性与灵活性:通过
RunnableParallel、RunnableBranch等工具,能轻松构建并行、分支、循环等复杂的数据流。 - Agent的本质: 一个复杂的Agent本身就是由多个LCEL链(工具调用链、思考链、响应生成链)组合而成的。
- 统一的接口与生态:
Runnable协议统一了所有组件的行为,所有链都有.invoke(),.ainvoke(),.stream(),.batch()方法,使得复用和集成变得异常简单。
LCEL写法
等价写法
可以用更显式的 RunnableLambda 写出同样的功能(不推荐,但便于理解):
1 | from langchain_core.runnables import RunnableLambda |
或者如果 summarize_chain 已经输出字符串:
1 | full_chain = summarize_chain | (lambda s: {"summary": s}) | translate_chain |
优雅写法
如果需要保留原始输入的同时添加新字段,或者不想写 lambda,可以使用 RunnablePassthrough.assign,如下:
1 | from langchain_core.runnables import RunnablePassthrough |
但 lambda 方式已经足够清晰,是 LCEL 中非常常见的模式。
LCEL示例解读
示例代码
1 | # 用LCEL完成 总结 → 翻译 的两步任务 |
详细拆解这段代码,理解它在 LCEL(LangChain 表达式语言)中的精妙设计。
1 | full_chain = summarize_chain | (lambda x: {"summary": x.content}) | translate_chain |
这段代码构建了一个两步骤的流水线:先总结,再翻译。它体现了 LCEL 用 | 管道符连接不同组件时的类型转换思想。
三个组成部分
| 组件 | 类型 | 输入类型 | 输出类型 |
|---|---|---|---|
summarize_chain |
一个 LCEL 链(通常是 `PromptTemplate | ChatModel | StrOutputParser`) |
lambda x: {"summary": x.content} |
一个 Python 函数,被 RunnableLambda 自动包装 |
取决于上游输出:这里上游是 summarize_chain 输出 str,所以 x 是 str |
dict(例如 {"summary": "这是总结内容"}) |
translate_chain |
另一个 LCEL 链,通常期望输入是一个包含 summary 键的字典 |
dict(必须包含 summary 字段) |
str(翻译后的文本) |
数据流动过程
Step1:执行summarize_chain
假设我们最终调用:
1 | result = full_chain.invoke({"text": "LangChain 是一个用于构建 LLM 应用的框架..."}) |
{"text": "..."}作为输入传给summarize_chain。summarize_chain内部可能是这样的:1
2
3summarize_prompt = PromptTemplate.from_template("用中文总结以下内容:\n{text}")
model = ChatOpenAI()
summarize_chain = summarize_prompt | model | StrOutputParser()- 它输出一个 字符串,例如:这个字符串会作为
1
"LangChain 是一个用于构建大语言模型应用的框架。"
lambda函数的输入x。
Step2:通过lambda函数进行类型转换
1 | lambda x: {"summary": x.content} |
注意,这里有一个隐藏陷阱:x 是字符串,而字符串没有 .content 属性! 实际上,更常见的写法应该是:
1 | lambda x: {"summary": x} # 因为 x 已经是纯文本字符串 |
但原代码写的是 x.content,这暗示 x 可能是一个 BaseMessage 对象(例如 AIMessage)。
这说明 summarize_chain 的输出很可能**没有经过 StrOutputParser**,而是直接输出了 ChatModel 的原始 AIMessage。
修正理解(更符合实际场景),通常我们写两步链时,会这样设计:
1 | summarize_chain = prompt | model # 输出 AIMessage |
summarize_chain输出AIMessage,其content属性是总结文本。- lambda 函数接收
msg(一个AIMessage对象),提取msg.content,并包装成字典{"summary": ...}。 - 这个字典正好是
translate_chain所期望的输入(它的 prompt 模板中有{summary}变量)。
Step3:执行translate_chain
translate_chain收到{"summary": "LangChain 是一个用于构建大语言模型应用的框架。"}- 它内部的 prompt 模板类似:
1
translate_prompt = PromptTemplate.from_template("将以下内容翻译成英文:\n{summary}")
- 经过模型和解析器后,输出一个字符串,例如:
1
"LangChain is a framework for building large language model applications."
最终 full_chain.invoke(...) 返回这个翻译后的字符串。
lambda函数
核心原因:连接两个对数据结构要求不同的组件。
summarize_chain输出的是纯文本(或AIMessage)。translate_chain期望的输入是一个字典,键名为summary。
直接连接 summarize_chain | translate_chain 会失败,因为类型不匹配:
- 如果
translate_chain的第一个组件是PromptTemplate,它要求输入为dict(提供模板变量summary)。 - 而
summarize_chain输出的是str或AIMessage,不能直接塞进PromptTemplate。
lambda 函数充当了一个适配器(Adapter),在 LCEL 中被自动提升为 RunnableLambda,负责将上游的输出转换成下游需要的格式。
示例总结
| 代码片段 | 作用 |
|---|---|
summarize_chain |
第一步:生成总结(输出文本或消息对象) |
| ` | (lambda x: {“summary”: x.content})` |
translate_chain |
第二步:翻译字典中的 summary 字段内容 |
最佳实践总结
- 从简单开始,逐步复杂化:先掌握
prompt | model | parser基础模式,确保跑通。然后逐步加入工具、分支、记忆等组件。不要试图一开始就构建一个完美的复杂Agent。 - **善用
RunnableParallel**:需要多任务并行时优先考虑 - 保持链的可读性:复杂逻辑拆分为多个子链
- **利用
.bind()**:固定参数如model.bind(temperature=0) - 使用LangSmith:生产环境务必开启追踪调试
LangChain:LCEL(表达式语言)与管道符(|)
http://blog.gxitsky.com/2026/04/11/AI-LangChain-034-Chain-LCEL/

