引言
LangChain的核心概念是Chain,它是一种由多个流程构件组成的有向图,可以对输入的文本进行各种转换和处理,输出你想要的结果。LangChain提供了一种专门的表达式语言,叫做LCEL(LangChain Expression Language),它可以让你用简洁和灵活的语法来定义和操作Chain,无需编写复杂的代码。
今天我将带领大家使用LCEL语法来构建和组合Chain,实现强大的LLM应用。
本文为一个系列,之前内容没有看过的小伙伴可以点击链接查看:AI课程合集
LCEL语法基础
LCEL是一个用于构建复杂链式组件的语言,它支持流式处理、并行化、日志记录等功能。LCEL的基本语法规则是使用|
符号将不同的组件连接起来,形成一个链式结构。|
符号类似于Unix的管道操作符,它将一个组件的输出作为下一个组件的输入,从而实现数据的传递和处理。
LCEL的语法非常简洁和灵活,它可以用于各种场景和任务。例如,我们可以使用LCEL来实现以下功能:
- 生成一个关于某个主题的笑话:我们可以将一个提示模板和一个语言模型组合起来,形成一个链式结构,如下所示:
prompt = BasePromptTemplate("tell me a short joke about {topic}")
model = ChatModel()
output_parser = StrOutputParser()
joke = ({"topic": RunnablePassthrough()} | prompt | model | output_parser)
这个链式结构的作用是,首先根据用户输入的主题,生成一个提示,然后将提示传递给语言模型,让它生成一个笑话,最后将笑话转换为字符串,返回给用户。我们可以用以下代码来测试这个链式结构:
joke.invoke("ice cream")
# > "Why did the ice cream go to therapy? nnBecause it had too many toppings and couldn't find its cone-fidence!"
通过以上案例,我们能够了解如何使用LCEL语言构建一个生成笑话的链式结构。我将为您解释其中的每一步:
- 首先,我们传入用户想要的主题,例如 "ice cream",作为输入。
- 通过{"topic": RunnablePassthrough()},将输入转化为字典类型{"topic": "ice cream"}
- 然后,我们使用提示模板组件,根据用户输入的主题,生成一个提示,例如"tell me a short joke about ice cream",并将其封装为一个PromptValue类型的对象。这个对象可以适用于不同类型的语言模型,因为它可以生成字符串或消息序列。
- 接着,我们使用大语言模型,会根据提示模板生成的提示,生成一段文本,例如"Why did the ice cream go to therapy?nBecause it had too many toppings and couldn't cone-trol itself!",并将其封装为一个ChatMessage类型的对象。这个对象包含了生成者、内容和时间等信息。
- 最后,我们使用输出解析器组件,根据用户的需求,将语言模型生成的文本转换为不同的格式或类型,例如字符串。这样,用户就可以方便地获取和使用生成的内容。
为什么要用LCEL?
LCEL语法的核心思想是:一切皆为对象,一切皆为链。这意味着,LCEL语法中的每一个对象都实现了一个统一的接口:Runnable,它定义了一系列的调用方法(invoke, batch, stream, ainvoke, …)。这样,你可以用同样的方式调用不同类型的对象,无论它们是模型、函数、数据、配置、条件、逻辑等等。而且,你可以将多个对象链接起来,形成一个链式结构,这个结构本身也是一个对象,也可以被调用。这样,你可以将复杂的功能分解成简单的组件,然后用LCEL语法将它们组合起来,形成一个完整的应用。
LCEL语法还提供了一些组合原语,让你可以更灵活地控制链式结构的行为,例如:
- 并行化:你可以使用
parallel
原语将多个对象并行执行,提高效率和性能。 - 回退:你可以使用
fallback
原语为某个对象指定一个备选对象,当主对象执行失败时,自动切换到备选对象,保证应用的可用性和稳定性。 - 动态配置:你可以使用
config
原语为某个对象指定一个配置对象,根据运行时的输入或条件,动态地修改对象的参数或属性,增加应用的灵活性和适应性。
LCEL语法的优势
为了更好地理解LCEL语法的优势,我们可以将它与传统的编程语言进行对比,看看如果不使用LCEL语法,我们需要做哪些额外的工作。我们仍以上述笑话的生成链为例。
这段代码非常简洁和清晰,只需要几行就可以实现我们想要的功能。而且,这段代码还具有很高的可扩展性和灵活性,例如:
- 如果我们想要以流式的方式获取笑话,我们只需要改变调用方法,使用
stream
代替invoke
:
# 调用笑话对象,传入一个主题字符串,得到一个笑话字符串的流
joke.stream("dog")
- 如果我们想要同时处理多个主题,我们只需要改变调用方法,使用
batch
代替invoke
:
# 调用笑话对象,传入一个主题字符串的列表,得到一个笑话字符串的列表
joke.batch(["dog", "cat", "banana"])
- 如果我们想让请求异步执行只需要
joke.ainvoke("dog")
- 模型的变更也十分简单,只需要变更modal变量的定义即可
prompt = BasePromptTemplate("tell me a short joke about {topic}")
# 改用gpt-3.5-turbo的llm
model = OpenAI(model="gpt-3.5-turbo")
output_parser = StrOutputParser()
joke = ({"topic": RunnablePassthrough()} | prompt | model | output_parser)
- 同时LCEL标准模型中的对象都可以直接增加同类型对象作为fallbacks,操作上只需要执行with_fallbacks方法即可。由于整条链亦是LCEL标准模型,因而链亦可配置fallbacks
# 增加OpenAI的llm作为ChatModel的fallbacks
prompt = BasePromptTemplate("tell me a short joke about {topic}")
model = ChatModel()
fallback_llm = OpenAI(model="gpt-3.5-turbo")
modal_with_fallback = model.with_fallbacks([fallback_llm])
output_parser = StrOutputParser()
joke = ({"topic": RunnablePassthrough()} | prompt | modal_with_fallback | output_parser)
以上只是一些简单的例子,你可以根据自己的需求,使用LCEL语法提供的更多的组合原语,实现更复杂的功能和效果。
那么,如果我们不使用LCEL语法,而是使用传统的编程语言,我们需要做哪些额外的工作呢?我们以Python为例,看看我们需要写多少代码,才能实现与LCEL语法相同的功能。
代码对比
从上面的代码可以看出,如果我们不使用LCEL语法,而是使用传统的编程语言,我们需要写很多的代码,才能实现与LCEL语法相同的功能。而且,这些代码还存在很多的问题,例如:
- 代码的可读性和可维护性很差,需要花费很多的时间和精力去理解和修改。
- 代码的可扩展性和灵活性很低,需要对代码进行大量的修改,才能实现不同的功能和效果。
- 代码的可复用性和可移植性很差,需要对代码进行大量的修改,才能适应不同的场景和平台。
因此,我们可以看出,LCEL语法相比传统的编程语言,具有很多的优势,它可以让我们更高效、更简单、更灵活地构建复杂的AI应用。
总结
在本文中,我们介绍了如何使用LangChain的LECL语法。我们介绍了LECL的基本语法以及基于LECL的流、异步等多种用法,并对比了不适用LECL语法开发的情况。
我们希望本文能够帮助你了解LangChain中特色的LECL语法,鼓励你尝试使用LangChain开发自己的应用。
参考资料:
- [1] Why use LCEL.https://python.langchain.com/docs