跳到主要内容
Open on GitHub

LangChain 表达式语言 (LCEL)

先决条件

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

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

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

注意
  • LCEL 速查表展示了涉及 Runnable 接口和 LCEL 表达式的常见模式。
  • 请参阅以下 操作指南列表,其中涵盖了使用 LCEL 的常见任务。
  • 内置 Runnables 的列表可以在 LangChain Core API 参考中找到。当在 LangChain 中使用 LCEL 组合自定义“链”时,其中许多 Runnables 非常有用。

LCEL 的优势

LangChain 以多种方式优化使用 LCEL 构建的链的运行时执行

  • 优化的并行执行:使用 RunnableParallel 并行运行 Runnables,或使用 Runnable Batch API 并行运行多个输入通过给定的链。并行执行可以显着减少延迟,因为可以并行而不是顺序地完成处理。
  • 保证异步支持:使用 Runnable Async API 可以异步运行使用 LCEL 构建的任何链。当在服务器环境中运行链,并且您希望并发处理大量请求时,这可能很有用。
  • 简化流式传输:LCEL 链可以流式传输,从而允许在链执行时进行增量输出。LangChain 可以优化输出的流式传输,以最大限度地减少首个令牌的时间(直到来自聊天模型llm的第一个输出块出现的时间)。

其他优势包括

  • 无缝 LangSmith 追踪 随着您的链变得越来越复杂,了解每个步骤究竟发生了什么变得越来越重要。使用 LCEL,所有步骤都会自动记录到 LangSmith 中,以实现最大的可观察性和可调试性。
  • 标准 API:由于所有链都是使用 Runnable 接口构建的,因此它们可以像任何其他 Runnable 一样使用。
  • 可使用 LangServe 部署:使用 LCEL 构建的链可以部署以供生产使用。

我应该使用 LCEL 吗?

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

虽然我们已经看到用户在生产环境中运行具有数百个步骤的链,但我们通常建议将 LCEL 用于更简单的编排任务。当应用程序需要复杂的状态管理、分支、循环或多个 agent 时,我们建议用户利用 LangGraph

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

以下是一些指南

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

组合原语

LCEL 链通过将现有的 Runnables 组合在一起构建。两个主要的组合原语是 RunnableSequenceRunnableParallel

许多其他组合原语(例如,RunnableAssign)可以被认为是这两个原语的变体。

注意

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

RunnableSequence

RunnableSequence 是一种组合原语,允许您“链式”顺序地运行多个 runnables,其中一个 runnable 的输出充当下一个 runnable 的输入。

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 是一种组合原语,允许您并发运行多个 runnables,每个 runnables 都提供相同的输入。

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

使用某些输入调用 chain

final_output = chain.invoke(some_input)

将产生一个 final_output 字典,该字典具有与输入字典相同的键,但值被相应 runnable 的输出替换。

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

回想一下,runnables 是并行执行的,因此虽然结果与上面显示的字典理解相同,但执行时间要快得多。

注意

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

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

组合语法

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 执行特定任务的指南,请查看相关的操作指南


此页是否对您有帮助?