LCEL(Lang Chain Expression Language)是将一些有趣的 Python 概念抽象成一种格式,使得可以构建 LangChain 组件链的 “极简主义” 代码层。
LCEL 具有以下强大的支持:
在本章节中,我们将介绍 LCEL 是什么,它是如何工作的,以及 LCEL 链、管道(pipe
)和可运行项(Runnable
)的基本要点。
# 导入所需的模块和类
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema.output_parser import StrOutputParser
from langchain.chains import LLMChain
# 创建聊天提示模板,指定要获取关于的主题
prompt = ChatPromptTemplate.from_template(
"给我一个关于{topic}的一句话介绍"
)
# 创建ChatOpenAI模型实例
model = ChatOpenAI(temperature=0)
# 创建输出解析器实例
output_parser = StrOutputParser()
# 创建LLMChain链,将聊天提示、模型和输出解析器组合在一起
chain = LLMChain(
prompt=prompt,
llm=model,
output_parser=output_parser
)
# 运行链,并指定主题为"大语言模型"
out = chain.run(topic="大语言模型")
print(out)
# -> 大语言模型是一种基于深度学习的人工智能技术,能够自动学习和生成自然语言文本,具有广泛的应用领域,如机器翻译、文本生成和对话系统等
这个链的目标是使用 ChatOpenAI 模型生成一个简短的关于指定主题的介绍。我们通过设置温度参数为 0,确保模型生成的输出更加确定性,使得结果更加精确和可控。
|
)而不是 LLMChain
来创建我们的链。# 使用 LangChain Expression Language(LCEL)创建链
lcel_chain = prompt | model | output_parser
# 运行链,并通过字典传递主题为"大语言模型"
out = lcel_chain.invoke({"topic": "大语言模型"})
print(out)
# -> 大语言模型是一种基于深度学习的人工智能技术,能够自动学习和生成自然语言文本,具有广泛的应用领域,如机器翻译、文本生成和对话系统等
这里的语法并不典型于Python,但只使用了原生Python。|
操作符简单地将左侧的输出传递给右侧的函数。
为了理解 LCEL 和管道运算符的工作原理,我们创建自己的管道兼容函数。
当 Python 解释器在两个对象之间看到 |
运算符(如 a
|
b
)时,它会尝试将对象 a
传递给对象 b
的 __or__
方法。这意味着这些模式是等价的:
# 对象方法
chain = a.__or__(b)
chain("一些输入")
# 管道方法
chain = a | b
chain("一些输入")
考虑到这一点,我们可以构建一个 Runnable
类,它接受一个函数并将其转换为可以使用管道运算符 |
与其他函数链接的函数。
class Runnable:
def __init__(self, func):
self.func = func
def __or__(self, other):
def chained_func(*args, **kwargs):
# 其他函数使用这个函数的结果
return other(self.func(*args, **kwargs))
return Runnable(chained_func)
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
让我们实现这个,取值 3,加上 5(得到 8),然后乘以 2,最后期望得到 16。
def add_five(x):
return x + 5
def multiply_by_two(x):
return x * 2
# 使用 Runnable 包装这些函数
add_five = Runnable(add_five)
multiply_by_two = Runnable(multiply_by_two)
# 使用对象方法运行它们
chain = add_five.__or__(multiply_by_two)
print(chain(3)) # (3 + 5) * 2 = 16
# -> 16
直接使用 __or__
我们会得到正确答案,让我们尝试使用管道操作符 |
将它们链接在一起:
# 将可运行的函数链接在一起
chain = add_five | multiply_by_two
# 调用链
print(chain(3)) # (3 + 5) * 2 = 16
# -> 16
无论使用哪种方法,我们都会得到相同的响应,这就是 LCEL 在链接组件时使用的管道逻辑。
RunnableLambda
是一个 LangChain 抽象,它允许我们将 Python 函数转换为与管道兼容的函数,类似于我们在之前介绍的 Runnable
类。
让我们尝试一下我们之前的 add_five
和 multiply_by_two
函数。
from langchain_core.runnables import RunnableLambda
# 使用 RunnableLambda 包装这些函数
add_five = RunnableLambda(add_five)
multiply_by_two = RunnableLambda(multiply_by_two)
与之前的 Runnable
抽象类似,我们可以使用 |
运算符将 RunnableLambda
抽象连接在一起:
# 将可运行的函数链接在一起
chain = add_five | multiply_by_two
与我们的 Runnable
抽象不同,我们不能通过直接调用它来运行 RunnableLambda
链,而是必须调用 chain.invoke
:
# 调用链
print(chain.invoke(3))
# -> 16
可以看到使用 RunnableLambda
获得了和 Runnable
类似的结果。
以上内容概述了 LangChain 表达语言(LCEL)的基础知识,通过 LCEL 我们可以轻松地构建链式结构。
LCEL 的优劣势多种多样。喜欢 LCEL 的人通常注重其极简的代码风格,以及对流式、并行操作和异步的支持,同时也看好 LCEL 与 LangChain 在组件链式连接方面的良好集成。
然而,有些人对 LCEL 持有不太喜欢的态度。这些人通常指出 LCEL 是对已经非常抽象的库再加一层抽象,语法令人困扰,违背了 Python 之禅,并且需要花费较多的时间来学习新的(或不太常见的)语法。
这两种观点都是有道理的,因为 LCEL 是一种极为不同的方法。然而,由于 LCEL 具有快速开发的特性,目前在 LangChain 开源社区中被广泛使用。对 LCEL 原理的简单了解将有助于读者在今后使用各种 LangChain 代码时更加得心应手。