AI大模型从入门到精通系列教程(六):LangChain 框架之链与 LCEL 详解

79 阅读12分钟

一、LangChain 的链和 LCEL

1.1 链的基本使用

在 LangChain 的世界里,链(Chain)就像是一个精心设计的生产流水线。对于简单的语言模型应用,我们或许可以直接调用大型语言模型(LLM)来完成任务,比如简单的文本生成。但当面对复杂的应用场景时,就如同工厂要生产复杂的产品,仅靠单一的 “机器”(LLM)远远不够。这时候,链就派上用场了,它将 LangChain 的各个组件和功能像流水线一样连接起来 。

从定义上来说,链是把多个组件相互串联,组合成一个有序的链条结构。这样做能极大地简化复杂应用程序的实现过程,并且让整个应用变得模块化。模块化的好处不言而喻,后续的调试、维护和改进工作都会变得更加轻松。例如,在一个智能文档问答系统中,链可以把文档读取组件、问题分析组件、模型调用组件以及答案整理组件有序地连接起来,形成一个高效的工作流程。

LangChain 通过设计良好的接口来实现具体链的功能。以 LLMChain 为例,它在整个链的体系中承担着重要角色。LLMChain 的主要职责是接受用户输入,然后按照特定的规则将输入格式化,最后把格式化后的内容传递给 LLM 进行处理。假设我们有一个任务是让语言模型根据给定的主题创作一首诗歌。首先,我们准备好包含主题信息的用户输入,LLMChain 会把这个输入按照预先设定好的诗歌创作提示模板进行格式化,比如添加一些引导性的语句,像 “以 {主题} 为主题,创作一首优美的诗歌,要求押韵且富有意境”,然后将格式化后的提示传递给 LLM,LLM 根据这个提示生成诗歌。

当我们实现了单个链的具体功能后,还可以进一步发挥创造力。通过组合多个链,或者将链与其他组件进行组合,能够构建出更为复杂且功能强大的链。比如,我们可以先构建一个链,专门负责从大量文档中提取关键信息,再构建另一个链,根据提取的关键信息生成总结报告。然后将这两个链组合起来,就形成了一个能从文档集合中自动生成总结报告的复杂链结构。

此外,LangChain 贴心地为开发者提供了多种类型的预置链。这些预置链就像是工厂里已经调试好的标准化生产线,能够方便快捷地实现各种常见任务。比如,有专门用于文本摘要的链,当我们输入一篇较长的文章时,它能迅速生成简洁明了的摘要;还有用于情感分析的链,输入一段文本,它可以判断出文本所表达的情感是积极、消极还是中性。

在实际调用 LLMChain 时,有多种方式可供选择。我们可以直接调用链对象,这其实是在调用内部实现的__call__方法。例如llm_chain("苹果") ,这里的 “苹果” 就是传递给链的输入,链会按照内部逻辑进行处理。run方法与直接调用等价,如llm_chain.run("苹果") ,效果与前面的直接调用是一样的。predict方法则稍有不同,它的输入键被指定为关键字参数,像result = llm_chain.predict(topic="苹果") ,通过明确指定参数名 “topic”,使代码的可读性更强,在处理多个输入参数时更加清晰。apply方法允许针对输入列表运行链,一次处理多个输入。假设我们有一个水果列表["苹果", "香蕉", "橙子"] ,使用llm_chain.apply([{"topic": "苹果"}, {"topic": "香蕉"}, {"topic": "橙子"}]) ,链会依次对列表中的每个输入进行处理。generate方法类似于apply ,不过它返回的是 LLMResult 对象,这个对象包含了模型生成文本过程中的丰富信息,例如生成文本所消耗的令牌数量、所使用的模型名称等,方便开发者对模型的运行情况进行深入分析。

1.2 LCEL 高级特性与组件

LCEL,即 LangChain Expression Language,是 LangChain 定义的一种极为强大的表达式语言。它为调用一系列组件提供了一种高效且简洁的方式,是 LangChain 框架中的一大亮点。

LCEL 的使用方式别具一格,它通过一系列的管道符(|)将所有实现了 Runnable 接口的组件串联起来。可以把它想象成一条数据的高速公路,各个组件就像是高速公路上的不同站点,数据在这些站点间有序地流动和处理。为了实现这种高效的串联调用,LangChain 计划让所有组件都实现 Runnable 接口,我们常用的 PromptTemplate 、LLMChain 、StructuredOutputParser 等组件都在其列。

在 Python 中,管道符(|)类似于 or 运算(或运算),例如 A|B,在 LCEL 的语境下,就相当于 A.or (B)。具体到 LangChain 的 Runnable 接口实现中,通过or方法将所有的 Runnable 组件串联起来,然后通过invoke方法去依次执行这些组件。在执行过程中,上一个组件的输出会自动作为下一个组件的输入,形成一个流畅的数据处理流程。这就如同在一个自动化生产线上,前一个工序完成的半成品会直接进入下一个工序进行进一步加工。

接下来,我们深入了解一些 LCEL 中的重要组件:

  1. RunnablePassthrough:这个组件主要用于在链中传递数据,就像是数据的搬运工。它常常被放置在链的起始位置,负责接收用户的输入。比如在一个简单的文本处理链中,RunnablePassthrough | some_other_component ,用户输入的数据首先会被RunnablePassthrough接收,然后原封不动地传递给some_other_component进行后续处理。当然,如果它处在链的中间位置,那就负责接收上一步的输出,并将其传递给下一步的组件。例如component1 | RunnablePassthrough | component2 ,RunnablePassthrough接收component1的输出,再传递给component2 。
  1. RunnableLambda:这是一个非常灵活且强大的组件,它能够将 Python 函数转换为 Runnable 对象。这一转换意义重大,使得任何 Python 函数都能无缝融入 LCEL 链中,成为其中的一部分。通过自定义函数与RunnableLambda的组合,我们可以轻松地将各种外部系统与 LCEL 链打通,实现更丰富的功能。比如,在一个复杂的文本处理链中,我们想要在某个中间步骤插入一段自定义功能,例如打印日志信息,记录处理过程中的关键数据。我们可以先定义一个 Python 函数,如def log_message(message): print(f"Log: {message}") ,然后使用RunnableLambda(log_message)将这个函数转换为 Runnable 对象,插入到 LCEL 链中合适的位置,如component1 | RunnableLambda(log_message) | component2 。这样,当数据从component1输出后,会先进入RunnableLambda(log_message) ,触发日志打印功能,然后再传递给component2继续处理。
  1. 并行化(parallel 原语) :在 LCEL 中,通过parallel原语可以将多个对象并行执行。这在处理一些耗时较长且相互独立的任务时非常有用,能够显著提高效率和性能。例如,我们有一个任务是同时对多篇文章进行情感分析和关键词提取。可以将情感分析组件和关键词提取组件通过parallel原语并行化处理,parallel([sentiment_analysis_component, keyword_extraction_component]) | result_processing_component 。这样,多篇文章可以同时分别进入情感分析组件和关键词提取组件进行处理,而不是依次串行处理,大大缩短了整体的处理时间。
  1. 回退(fallback 原语) :fallback原语为某个对象指定一个备选对象。当主对象在执行过程中出现失败的情况时,系统会自动切换到备选对象继续执行,这为应用的可用性和稳定性提供了有力保障。比如,我们在调用一个语言模型进行文本生成时,主模型可能由于网络问题或者负载过高而无法正常工作。此时,可以使用fallback原语为其指定一个备用模型,main_language_model.with_fallbacks(backup_language_model) | text_processing_component 。如果main_language_model执行失败,系统会自动切换到backup_language_model继续进行文本生成,确保整个文本处理流程不会中断。
  1. 动态配置(config 原语) :借助config原语,我们可以为某个对象指定一个配置对象。这样,在运行时,根据输入或者特定条件的变化,能够动态地修改对象的参数或属性,极大地增加了应用的灵活性和适应性。例如,在一个图像识别应用中,图像识别模型的参数可能需要根据不同的图像类型或者识别任务的复杂程度进行调整。我们可以通过config原语,在运行时根据输入图像的特征信息,动态地修改图像识别模型的参数,如调整识别精度、检测阈值等,image_input | config(image_recognition_model, {"precision": "high", "threshold": 0.8}) | image_recognition_model | result_output 。

1.3 应用实战:多用户聊天机器人实战

我们将通过构建一个多用户聊天机器人来深入理解链和 LCEL 的实际应用。在这个项目中,我们要打造的聊天机器人需要具备同时与多个用户进行交互的能力,并且能够根据用户的历史对话记录提供个性化的回复。

首先,我们需要确定聊天机器人的整体架构。它主要包含以下几个关键部分:

  1. 聊天模型选择:我们选用一款合适的聊天模型,比如基于 OpenAI 的 GPT 系列模型或者其他开源的优秀聊天模型。这个模型将负责处理用户输入的消息,并生成相应的回复。例如使用 OpenAI 的 ChatGPT 模型,通过 LangChain 提供的接口进行集成,from langchain.chat_models import ChatOpenAI ,chat_model = ChatOpenAI(temperature = 0.7) ,这里的temperature参数控制着生成回复的随机性,值越高回复越具创造性,但也可能更偏离常规;值越低回复越保守、确定性越高。
  1. 记忆组件实现:为了让聊天机器人能够记住用户的历史对话,我们引入记忆组件。这里使用ConversationBufferMemory ,它是一种简单且常用的记忆机制,能够存储会话记忆。通过以下代码实现:from langchain.memory import ConversationBufferMemory ,memory = ConversationBufferMemory() 。在实际对话过程中,每一轮用户的输入和机器人的回复都会被记录在memory中,当用户后续询问相关内容时,机器人可以结合历史记录给出更连贯、准确的回答。例如,用户先询问 “明天天气如何”,机器人回复后,当用户接着问 “那适合出门吗”,机器人可以根据之前关于天气的对话记录,给出合理的关于是否适合出门的回答。
  1. 链的构建:构建一个 LLMChain,将聊天模型和记忆组件整合起来。from langchain.chains import LLMChain ,from langchain.prompts import PromptTemplate 。首先定义一个提示模板,这个模板决定了如何将用户输入和历史对话组合成一个完整的提示传递给聊天模型。例如prompt_template = PromptTemplate(input_variables=["history", "user_input"], template="根据以下历史对话和用户当前输入,生成回复:\n历史对话:{history}\n用户输入:{user_input}") 。然后创建 LLMChain 对象,chain = LLMChain(llm = chat_model, prompt = prompt_template, memory = memory) 。这样,当用户输入消息时,LLMChain 会根据提示模板,将历史对话(存储在memory中)和用户当前输入组合成一个完整的提示,传递给聊天模型生成回复。
  1. 多用户管理:为了实现多用户聊天,我们需要一个用户管理系统。可以使用一个字典来存储每个用户的聊天状态和相关信息,例如users = {} 。当一个新用户加入聊天时,为其创建一个新的记忆对象和聊天链对象,并将其信息存储在users字典中。假设用户的唯一标识为user_id ,可以通过以下代码实现:if user_id not in users: users[user_id] = {"memory": ConversationBufferMemory(), "chain": LLMChain(llm = chat_model, prompt = prompt_template, memory = users[user_id]["memory"])} 。这样,每个用户都有自己独立的记忆和聊天链,保证了对话的独立性和个性化。
  1. 对话处理逻辑:在接收到用户的消息后,根据用户的user_id从users字典中获取对应的记忆和聊天链对象,然后使用聊天链处理用户输入并生成回复。例如user_id = get_user_id_from_request(request) ,response = users[user_id]["chain"]({"user_input": get_user_message_from_request(request)})["text"] 。这里的get_user_id_from_request和get_user_message_from_request是自定义函数,用于从请求中提取用户 ID 和用户消息。最后,将生成的回复返回给用户。

在这个多用户聊天机器人的实现过程中,链和 LCEL 发挥了重要作用。链将聊天模型、记忆组件以及提示模板等组件有机地组合在一起,形成了一个完整的对话处理流程。而 LCEL 的特性,虽然在这个简单示例中没有直接体现复杂的用法,但在更复杂的场景下,比如并行处理多个用户的请求以提高响应速度,或者为不同用户设置不同的备用聊天模型以应对主模型故障等方面,都有着巨大的应用潜力。通过这个实战项目,我们可以看到 LangChain 的链和 LCEL 如何帮助我们快速、高效地构建出功能强大的多用户聊天机器人应用。