LangChain 表达式语言 (LCEL)
LangChain Expression Language (LCEL) 采用声明性方法从现有 Runnable 构建新的 Runnable。
这意味着你描述了应该发生的事情,而不是它应该如何发生,从而允许 LangChain 优化链的运行时执行。
我们通常指的是Runnable使用 LCEL 作为 “链” 创建。重要的是要记住,“链”是Runnable它实现了完整的 Runnable Interface。
- LCEL 备忘单显示了涉及 Runnable 接口和 LCEL 表达式的常见模式。
- 请参阅以下操作指南列表,其中涵盖了 LCEL 的常见任务。
- 内置列表
Runnables可以在 LangChain Core API 参考中找到。当使用 LCEL 在 LangChain 中组合自定义 “链” 时,其中许多 Runnable 都很有用。
LCEL 的优势
LangChain 以多种方式优化了使用 LCEL 构建的链的运行时执行:
- 优化的并行执行:使用 RunnableParallel 并行运行 Runnable,或使用 Runnable Batch API 通过给定链并行运行多个输入。并行执行可以显著降低延迟,因为处理可以并行完成,而不是按顺序完成。
- 保证异步支持:使用 LCEL 构建的任何链都可以使用 Runnable Async API 异步运行。在您想要同时处理大量请求的服务器环境中运行链时,这可能很有用。
- 简化流式处理:可以流式处理 LCEL 链,从而允许在执行链时进行增量输出。LangChain 可以优化输出的流式,以最大限度地减少获得第一个令牌的时间(从聊天模型或 llm 的第一块输出出来之前经过的时间)。
其他好处包括:
- 无缝 LangSmith 跟踪随着链条变得越来越复杂,了解每一步到底发生了什么变得越来越重要。 使用 LCEL,所有步骤都会自动记录到 LangSmith 中,以实现最大的可观察性和可调试性。
- 标准 API:因为所有链都是使用 Runnable 接口构建的,所以它们可以像任何其他 Runnable 一样使用。
- 可与 LangServe 一起部署:使用 LCEL 构建的链可以部署用于生产用途。
我应该使用 LCEL 吗?
LCEL 是一种编排解决方案,它允许 LangChain 以优化的方式处理链的运行时执行。
虽然我们已经看到用户在生产中运行具有数百个步骤的链,但我们通常建议使用 LCEL 来执行更简单的编排任务。当应用程序需要复杂的状态管理、分支、周期或多个代理时,我们建议用户利用 LangGraph。
在 LangGraph 中,用户定义指定应用程序流程的图形。这允许用户在需要 LCEL 时在单个节点中继续使用 LCEL,同时可以轻松定义更具可读性和可维护性的复杂编排逻辑。
以下是一些准则:
- 如果您进行单个 LLM 调用,则不需要 LCEL;而是直接调用基础聊天模型。
- 如果您有一个简单的链(例如,提示 + llm + 解析器、简单的检索设置等),如果您正在利用 LCEL 的优势,那么 LCEL 是一个合理的选择。
- 如果您正在构建一个复杂的链(例如,具有分支、循环、多个代理等),请改用 LangGraph。请记住,您始终可以在 LangGraph 中的各个节点中使用 LCEL。
合成基元
LCEL链是通过组合现有的Runnables一起。两个主要的组合基元是 RunnableSequence 和 RunnableParallel。
许多其他组合基元(例如 RunnableAssign)可以被认为是这两个基元的变体。
您可以在 LangChain Core API Reference 中找到所有组合基元的列表。
RunnableSequence
RunnableSequence是一个组合基元,允许您按顺序 “链接” 多个 Runnable,其中一个 Runnable 的输出用作下一个 Runnable 的输入。
from langchain_core.runnables import RunnableSequence
chain = RunnableSequence([runnable1, runnable2])
调用chain有一些输入:
final_output = chain.invoke(some_input)
对应于以下内容:
output1 = runnable1.invoke(some_input)
final_output = runnable2.invoke(output1)
runnable1和runnable2是任何Runnable你想要链接在一起。
RunnableParallel
RunnableParallel是一个组合基元,允许您同时运行多个可运行对象,并为每个可运行对象提供相同的输入。
from langchain_core.runnables import RunnableParallel
chain = RunnableParallel({
"key1": runnable1,
"key2": runnable2,
})
调用chain有一些输入:
final_output = chain.invoke(some_input)
将产生一个final_outputdictionary 替换为与输入字典相同的键,但其值替换为相应 runnable 的输出。
{
"key1": runnable1.invoke(some_input),
"key2": runnable2.invoke(some_input),
}
回想一下,runnables 是并行执行的,所以虽然结果与 字典理解,执行时间要快得多。
RunnableParallel支持同步和异步执行(作为Runnablesdo) 的
- 对于同步执行,
RunnableParallel使用 ThreadPoolExecutor 并发运行 runnables。 - 对于异步执行,
RunnableParallel使用 asyncio.gather 并发运行 Runnable。
组合语法
的用法RunnableSequence和RunnableParallel是如此常见,以至于我们创建了一个使用它们的简写语法。这有助于
使代码更具可读性和简洁性。
这|算子
我们已经超载了|运算符创建RunnableSequence从 2Runnables.
chain = runnable1 | runnable2
等效于:
chain = RunnableSequence([runnable1, runnable2])
这.pipe方法
如果您对运算符重载有道德上的疑虑,可以使用.pipe方法。这相当于|算子。
chain = runnable1.pipe(runnable2)
强迫
LCEL 应用自动类型强制转换,以便更轻松地组合链。
如果您不理解类型强制转换,您始终可以使用RunnableSequence和RunnableParallel类。
这将使代码更加冗长,但也将使代码更加明确。
Dictionary 到 RunnableParallel
在 LCEL 表达式中,字典会自动转换为RunnableParallel.
例如,以下代码:
mapping = {
"key1": runnable1,
"key2": runnable2,
}
chain = mapping | runnable3
它会自动转换为以下内容:
chain = RunnableSequence([RunnableParallel(mapping), runnable3])
您必须小心,因为mappingdictionary 不是RunnableParallelobject,它只是一个字典。这意味着以下代码将引发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 函数不是RunnableLambdaobject,它只是一个函数。这意味着以下代码将引发AttributeError:
lambda x: x + 1.invoke(some_input)
传统链
LCEL 旨在提供对传统子类链的行为和定制的一致性,例如LLMChain和ConversationalRetrievalChain.其中许多遗留链隐藏了重要的细节,如提示,以及更广泛的种类
的可行模型出现后,定制变得越来越重要。
如果您当前正在使用这些传统链之一,请参阅本指南以获取有关如何迁移的指导。
有关如何使用 LCEL 执行特定任务的指南,请查看相关的操作指南。