LangChain 表达式语言 (LCEL)
LangChain Expression Language (LCEL) 采用声明式方法,从现有的 Runnables 构建新的 Runnables。
这意味着你描述你想要发生什么,而不是你想要它如何发生,从而允许 LangChain 优化链的运行时执行。
我们通常将使用 LCEL 创建的 Runnable
称为“链”。重要的是要记住,“链”是 Runnable
,并且它实现了完整的 Runnable 接口。
- LCEL 速查表 展示了涉及 Runnable 接口和 LCEL 表达式的常见模式。
- 请参阅以下 操作指南 列表,其中涵盖了 LCEL 的常见任务。
- 内置
Runnables
的列表可以在 LangChain Core API 参考 中找到。当使用 LCEL 在 LangChain 中组合自定义“链”时,许多这些 Runnables 都很有用。
LCEL 的优势
LangChain 以多种方式优化使用 LCEL 构建的链的运行时执行
- 优化并行执行:使用 RunnableParallel 并行运行 Runnables,或使用 Runnable Batch API 并行运行多个输入通过给定的链。并行执行可以显著减少延迟,因为可以并行而不是顺序地完成处理。
- 简化流式传输:LCEL 链可以流式传输,从而允许在链执行时进行增量输出。LangChain 可以优化输出的流式传输,以最大限度地减少首个令牌时间(直到来自聊天模型或llm的第一个输出块出现的时间)。
其他优势包括
- 无缝 LangSmith 追踪 随着你的链变得越来越复杂,了解每个步骤究竟发生了什么是越来越重要的。使用 LCEL,所有步骤都会自动记录到 LangSmith,以实现最大的可观察性和可调试性。
- 标准 API:由于所有链都是使用 Runnable 接口构建的,因此它们可以像任何其他 Runnable 一样使用。
- 可使用 LangServe 部署:使用 LCEL 构建的链可以使用 LangServe 部署以供生产使用。
我应该使用 LCEL 吗?
LCEL 是一种 编排解决方案 —— 它允许 LangChain 以优化的方式处理链的运行时执行。
虽然我们已经看到用户在生产环境中运行包含数百个步骤的链,但我们通常建议将 LCEL 用于更简单的编排任务。当应用程序需要复杂的状态管理、分支、循环或多个代理时,我们建议用户利用 LangGraph。
在 LangGraph 中,用户定义指定应用程序流程的图。这允许用户在需要 LCEL 时在单个节点内继续使用 LCEL,同时可以轻松定义更易读和可维护的复杂编排逻辑。
以下是一些指南
- 如果你要进行单个 LLM 调用,则不需要 LCEL;而是直接调用底层的聊天模型。
- 如果你有一个简单的链(例如,提示 + llm + 解析器,简单的检索设置等),如果你正在利用 LCEL 的优势,则 LCEL 是一个合理的选择。
- 如果你正在构建一个复杂的链(例如,带有分支、循环、多个代理等),请改用 LangGraph。请记住,你始终可以在 LangGraph 中的单个节点内使用 LCEL。
组合原语
LCEL
链是通过将现有的 Runnables
组合在一起来构建的。两个主要的组合原语是 RunnableSequence 和 RunnableParallel。
许多其他组合原语(例如,RunnableAssign)可以被认为是这两个原语的变体。
你可以在 LangChain Core API 参考 中找到所有组合原语的列表。
RunnableSequence
RunnableSequence
是一种组合原语,允许你按顺序“链接”多个 runnables,其中一个 runnable 的输出充当下一个 runnable 的输入。
import { RunnableSequence } from "@langchain/core/runnables";
const chain = new RunnableSequence({
first: runnable1,
// Optional, use if you have more than two runnables
// middle: [...],
last: runnable2,
});
使用某些输入调用 chain
const finalOutput = await chain.invoke(someInput);
对应于以下内容
const output1 = await runnable1.invoke(someInput);
const finalOutput = await runnable2.invoke(output1);
runnable1
和 runnable2
是你想要链接在一起的任何 Runnable
的占位符。
RunnableParallel
RunnableParallel
是一种组合原语,允许你并发运行多个 runnables,每个 runnable 都提供相同的输入。
import { RunnableParallel } from "@langchain/core/runnables";
const chain = new RunnableParallel({
key1: runnable1,
key2: runnable2,
});
使用某些输入调用 chain
const finalOutput = await chain.invoke(someInput);
将产生一个 finalOutput
对象,该对象具有与输入对象相同的键,但值被相应 runnable 的输出替换。
{
key1: await runnable1.invoke(someInput),
key2: await runnable2.invoke(someInput),
}
回想一下,runnables 是并行执行的,因此虽然结果与上面显示的对象理解相同,但执行时间要快得多。
组合语法
RunnableSequence
和 RunnableParallel
的使用非常普遍,因此我们创建了一个使用它们的简写语法。这有助于使代码更具可读性和简洁性。
pipe
方法。
你可以使用 .pipe(runnable)
方法将 runnables 管道连接在一起。
const chain = runnable1.pipe(runnable2);
等效于
const chain = new RunnableSequence({
first: runnable1,
last: runnable2,
});
RunnableLambda 函数
你可以通过 RunnableLambda
类将通用 TypeScript 函数定义为 runnables。
const someFunc = RunnableLambda.from((input) => {
return input;
});
const chain = someFunc.pipe(runnable1);
旧版链
LCEL 旨在围绕行为和自定义提供一致性,而不是旧版子类化的链,例如 LLMChain
和 ConversationalRetrievalChain
。许多这些旧版链隐藏了重要的细节,例如提示,并且随着更多种类的可行模型的出现,自定义变得越来越重要。
有关如何使用 LCEL 完成特定任务的指南,请查看相关的操作指南。