Skip to main content
Open on GitHub

LangChain 表达式语言(LCEL)

先决条件

LangChain Expression Language (LCEL) 采用一种 声明式 方法,从现有的 Runnables 构建新的 Runnables

这意味着您描述的是应该发生什么,而不是如何发生,从而使 LangChain 能够优化链的运行时执行。

我们通常将使用 LCEL 创建的 Runnable 称为“链”。需要记住的是,“链”是一个 Runnable,并且实现了完整的 Runnable 接口

笔记
  • LCEL 速查表 展示了涉及 Runnable 接口和 LCEL 表达式的常见模式。
  • 请参阅以下涵盖 LCEL 常用任务的操操作指南列表。
  • LangChain 核心 API 参考 中可以找到内置的 Runnables 列表。这些可运行对象中的许多在使用 LCEL 于 LangChain 中构建自定义“链”时非常有用。

LCEL的优势

LangChain 通过多种方式优化了使用 LCEL 构建的链在运行时的执行效率:

  • 优化的并行执行: 使用 RunnableParallel 并行运行 Runnables,或使用 Runnable 批处理 API 并行处理多个输入。并行执行可显著降低延迟,因为处理过程可以并行而非串行进行。
  • 保证异步支持: 使用 LCEL 构建的任何链都可以通过 Runnable 异步 API 异步运行。这在服务器环境中非常有用,当您需要并发处理大量请求时。
  • 简化流式传输: LCEL 链可以进行流式传输,允许在链执行过程中逐步输出结果。LangChain 可以优化输出的流式传输,以最小化首个标记的时间(即从开始到聊天模型或大语言模型输出第一个输出块所经过的时间)。

其他优势包括:

  • 无缝集成 LangSmith 跟踪 随着您的链式结构变得越来越复杂,了解每一步的具体操作变得愈发重要。 通过 LCEL,所有步骤都会自动记录到 LangSmith,以实现最大程度的可观测性和可调试性。
  • 标准 API: 由于所有链都是使用 Runnable 接口构建的,因此它们可以以与任何其他 Runnable 相同的方式使用。
  • 可通过 LangServe 部署: 使用 LCEL 构建的链可以使用 LangServe 进行部署,适用于生产环境。

我应该使用 LCEL 吗?

LCEL 是一种 编排解决方案 —— 它使 LangChain 能够以优化的方式处理链在运行时的执行。

尽管我们已经看到用户在生产环境中运行包含数百个步骤的链条,但我们通常建议使用LCEL来处理更简单的编排任务。当应用程序需要复杂的状态管理、分支、循环或多个代理时,我们建议用户利用LangGraph

在 LangGraph 中,用户定义图来指定应用程序的流程。这使得用户在需要时可以在各个节点中继续使用 LCEL,同时轻松定义更复杂、更易读且更易维护的编排逻辑。

以下是几点指南:

  • 如果您只进行一次大型语言模型调用,就不需要使用LCEL;而是可以直接调用底层的 聊天模型
  • 如果你有一个简单的链(例如,提示 + 大型语言模型 + 解析器,或简单的检索设置等),并且你正在利用 LCEL 的优势,那么 LCEL 是一个合理的选项。
  • 如果您正在构建一个复杂的链(例如,具有分支、循环、多个代理等),请改用 LangGraph。请记住,您可以在 LangGraph 的各个节点中始终使用 LCEL。

组合原语

LCEL 通过组合现有的 Runnables 来构建。两种主要的组合原语是 RunnableSequenceRunnableParallel

许多其他组合原语(例如 RunnableAssign)可以被视为这两种原语的变体。

笔记

您可以在 LangChain Core API 参考 中找到所有组合原语的列表。

RunnableSequence

RunnableSequence 是一个组合原语,允许您“串联”多个可运行对象,前一个可运行对象的输出作为下一个可运行对象的输入。

from langchain_core.runnables import RunnableSequence
chain = RunnableSequence([runnable1, runnable2])
API 参考:RunnableSequence

使用一些输入调用 chain

final_output = chain.invoke(some_input)

对应如下:

output1 = runnable1.invoke(some_input)
final_output = runnable2.invoke(output1)
笔记

runnable1runnable2 是您想要串联的任意 Runnable 的占位符。

RunnableParallel

RunnableParallel 是一个组合原语,允许你并行运行多个可运行对象,并为每个对象提供相同的输入。

from langchain_core.runnables import RunnableParallel
chain = RunnableParallel({
"key1": runnable1,
"key2": runnable2,
})
API 参考:RunnableParallel

使用一些输入调用 chain

final_output = chain.invoke(some_input)

将生成一个 final_output 字典,其键与输入字典相同,但值被相应可运行对象的输出所替换。

{
"key1": runnable1.invoke(some_input),
"key2": runnable2.invoke(some_input),
}

回想一下,可运行对象是并行执行的,因此虽然结果与上面所示的字典推导式相同,但执行时间要快得多。

笔记

RunnableParallel同时支持同步和异步执行(所有 Runnables 均如此)。

  • 对于同步执行,RunnableParallel 使用 ThreadPoolExecutor 来并发运行可运行对象。
  • 对于异步执行,RunnableParallel 使用 asyncio.gather 来并发运行可运行对象。

组合语法

RunnableSequenceRunnableParallel 的使用非常普遍,因此我们创建了一种简写语法来使用它们。这有助于使代码更易读且更简洁。

| 运算符

我们已重载 | 运算符,通过两个 Runnables 创建一个 RunnableSequence

chain = runnable1 | runnable2

相当于:

chain = RunnableSequence([runnable1, runnable2])

.pipe 方法

如果你对操作符重载有道德上的顾虑,可以改用 .pipe 方法。这与 | 操作符等价。

chain = runnable1.pipe(runnable2)

强制

LCEL 会自动进行类型转换,以简化链的组合。

如果您不理解类型强制转换,可以始终直接使用 RunnableSequenceRunnableParallel 类。

这会使代码更加冗长,但也会使其更加明确。

字典转 RunnableParallel

在LCEL表达式中,字典会自动转换为 RunnableParallel

例如,以下代码:

mapping = {
"key1": runnable1,
"key2": runnable2,
}

chain = mapping | runnable3

它会自动转换为以下内容:

chain = RunnableSequence([RunnableParallel(mapping), runnable3])
注意

你必须小心,因为 mapping 字典不是一个 RunnableParallel 对象,它只是一个字典。这意味着以下代码将引发一个 AttributeError

mapping.invoke(some_input)

转换为 RunnableLambda 函数

在 LCEL 表达式中,函数会自动转换为 RunnableLambda

def some_func(x):
return x

chain = some_func | runnable1

它会自动转换为以下内容:

chain = RunnableSequence([RunnableLambda(some_func), runnable1])
注意

你必须小心,因为lambda函数不是一个RunnableLambda对象,它只是一个函数。这意味着以下代码会引发一个AttributeError

lambda x: x + 1.invoke(some_input)

旧版链

LCEL 旨在为行为的一致性以及对传统继承链(如 LLMChainConversationalRetrievalChain)的自定义提供支持。许多传统链隐藏了提示词等重要细节,随着越来越多可行模型的出现,自定义的重要性也日益凸显。

如果您当前正在使用这些旧版链,请参阅 此指南以获取迁移指导

有关如何使用LCEL执行特定任务的指南,请参阅 相关操操作指南