【LCEL 链式调用详解】调用篇-2

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": "..."})

相关推荐
BU摆烂会噶1 小时前
【LangGraph】运行时上下文(Runtime Context)
人工智能·python·langchain
一个处女座的程序猿O(∩_∩)O1 小时前
大模型决战2026:从百模大战到空间智能,AI Agent与推理架构的深度实战
人工智能·架构
swipe1 小时前
别把语音 Agent 当成“接两个 API”——用 NestJS 搭一套 ASR + LLM + 流式 TTS 的实时语音助手
前端·后端·llm
第七种黄昏2 小时前
用AI一天做出一个完整App:VibeCoding全流程实战记录(小白也能复现)
人工智能
skilllite作者2 小时前
SkillLite 原生系统级沙箱功能代码导览
人工智能·chrome·后端·架构·rust
GISer_Jing2 小时前
AI Agent中游产业链全景拆解:智能体开发的核心生态与技术版图
前端·人工智能·后端
Zzzzmo_2 小时前
【JavaEE】文件操作和IO
java·java-ee·io·文件操作·file·流对象
冬奇Lab2 小时前
RAG 系列(七):检索策略——如何找到最相关的内容
人工智能·llm·源码