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
  1. 先并行运行 chain1 和 chain2
  2. 把结果汇总成一个字典
  3. 再把这个字典交给后面的 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}")

 

posted @ 2026-05-06 17:34  幻影之舞  阅读(20)  评论(0)    收藏  举报