• 5分钟理透LangChain的Chain


    LangChain几乎是LLM应用开发的第一选择,它的野心也比较大,它致力于将自己打造成LLM应用开发的最大社区。而LangChain最核心的部分非 Chain 莫属。

    Chain到底是个啥,概念比较模糊,像雾像雨又像风,这篇文章将带你快速理透 LangChain 中的 Chain 概念。

    1. Chain是核心

    LangChain的Chain到底是什么?一句话总结:Chain是指对 LangChain 多个组件的一系列调用。

    再看看官网的解释:Chain是指调用的序列 - 无论是调用 LLM、工具还是数据预处理步骤,主要支持的方法是使用 LCEL。

    官网里还提到了LCEL,LCEL是LangChain 表达式语言,是一种更加高效简介的链接 LangChain 组件的方式,也是官网推荐的方式。

    从下图官网的描述,也可以看到,Chain可以是从最简单的“prompt + LLM”链 到 最复杂的链(运行了包含 100 多个步骤的链)。

    2. 为什么需要Chain

    我们所期待的LLM是能处理许多复杂任务,而非简单的一问一答,也不是简单的处理单一任务。

    所以,最终我期待的LLM处理任务的流程应该是这样,它中间的复杂过程对用户来说是一个黑盒:

    既然定位是完成复杂任务,那自然就需要通过某个机制将多个单一任务串起来,形成一个大的链条,多个步骤共同完成某个复杂任务。

    ***Chain可以将多个步骤连接到一起,最终完成各种复杂繁琐的任务。***这就是Chain存在的必要性了。我很喜欢LangChain的Logo,很形象地表达了这一思想。

    Chain需要对多个组件一系列的调用或者一系列的串联,这样才能完成复杂任务。当然,我们也可以把 Chain 看作是流水线。通过使用 Chain,你可以将各个步骤定义为独立的模块,然后按顺序串联起来。这样不仅大大简化了代码逻辑,也使得整个流程更加直观和易于管理。

    而LCEL的存在,也只是为了让构建链的过程更简单,让链的表达力更清晰更简单。

    接下来,我将通过一个示例展示没有 Chain有Chain的2种实现方式,以便更清晰地理解 Chain 的价值。

    3. 如果没有Chain

    这里举个例子,比如:我们给LLM输入一段项目描述,让LLM给这个项目起一个名称Slogan

    如果不使用Chain的话,我们可以这样实现。

    # 本次需求:我们给LLM输入一段项目描述,让LLM给这个项目起一个名称和Slogan
    # 以下是实现:
    
    from langchain.prompts import PromptTemplate, ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    
    
    proj_desc = """
        我们本次的项目是去森林里探险救援,我们有一个10人小队,
        我们要到达一个叫做“蝴蝶谷”的目的地,去那里解救一位被困的科学家。
        这期间我们可能会遇到许多危险,我们需要共同合作,互相帮助,历经磨难,才能到达目的地。
        我们的任务是要在5天内到达目的地并且救出探险家,才算完成这次探险,否则任务失败,我们将受到惩罚。
        出发前我们要各自准备好自己的装备和干粮,加油!
    """
    
    
    def name_slogan_by_desc(project_desc):
        """
        根据项目描述,生成项目名称和slogan
        """
        str_parser = StrOutputParser()
    
        promt_template_project_name = "请你根据标签里的关于某个项目的描述,生成一个项目名称,只需要返回项目名称。{project_desc}"
        promt_project_name = PromptTemplate.from_template(promt_template_project_name)
        final_promt_project_name = promt_project_name.invoke({"project_desc": project_desc})
        res_project_name = model.invoke(final_promt_project_name)
        parsed_res_project_name = str_parser.invoke(res_project_name)
    
    
        promt_template_slogan = "请你根据标签里的关于某个项目的描述,和这个项目的名称{project_name},给这个项目起一个slogan,slogan要求干脆简洁积极向上,只返回slogan。{project_desc}"
        promt_slogan = PromptTemplate.from_template(promt_template_slogan)
        final_promt_slogan = promt_slogan.invoke(
            {"project_desc": project_desc, "project_name": parsed_res_project_name}
        )
        response_slogan = model.invoke(final_promt_slogan)
        parsed_response_slogan = str_parser.invoke(response_slogan)
    
    
        final_result = {
            "project_name": parsed_res_project_name,
            "slogan": parsed_response_slogan,
        }
        return final_result
    
    # 输入项目描述,输出项目名称和slogan
    result = name_slogan_by_desc(proj_desc)
    print(result)
    
    

    执行结果如下:

    {'project_name': '蝴蝶谷救援行动', 'slogan': '拯救科学家,共同合作,蝴蝶谷等你来!'}
    

    可以看到,实现过程比较繁琐,变量和代码也多,不够直观,很容易出错。这还只是简单场景,如果碰到复杂场景就更麻烦了。

    4. 因为有了Chain

    接下来,我们使用 LangChain 的 Chain 功能,来实现相同的功能。代码如下:

    # 本次需求:我们给LLM输入一段项目描述,让LLM给这个项目起一个名称和Slogan
    # 以下是实现:
    
    from operator import itemgetter
    from langchain.prompts import PromptTemplate, ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    from langchain.chains import LLMChain, SequentialChain
    
    proj_desc = """
        我们本次的项目是去森林里探险救援,我们有一个10人小队,
        我们要到达一个叫做“蝴蝶谷”的目的地,去那里解救一位被困的科学家。
        这期间我们可能会遇到许多危险,我们需要共同合作,互相帮助,历经磨难,才能到达目的地。
        我们的任务是要在5天内到达目的地并且救出探险家,才算完成这次探险,否则任务失败,我们将受到惩罚。
        出发前我们要各自准备好自己的装备和干粮,加油!
    """
    
    def name_slogan_by_desc(project_desc):
        """
        根据项目描述,生成项目名称和slogan
        """
    
        # 第1条链
        promt_template_project_name = "请你根据标签里的关于某个项目的描述,生成一个项目名称,只需要返回项目名称。{project_desc}"
        chain_one = LLMChain(
            llm=model,
            prompt=PromptTemplate.from_template(promt_template_project_name),
            output_parser=StrOutputParser(),
            output_key="project_name",
        )
    
        # 第2条链
        promt_template_slogan = "请你根据标签里的关于某个项目的描述,和这个项目的名称{project_name},给这个项目起一个slogan,slogan要求干脆简洁积极向上,只返回slogan。{project_desc}"
        chain_two = LLMChain(
            llm=model,
            prompt=PromptTemplate.from_template(promt_template_slogan),
            output_parser=StrOutputParser(),
            output_key="slogan",
        )
    
        # 串联两条链
        sequential_chain = SequentialChain(
            chains=[chain_one, chain_two],
            input_variables=["project_desc"],
            output_variables=["project_name", "slogan"],
        )
        final_res = sequential_chain(project_desc)
    
        final_result = {
            "project_name": final_res["project_name"],
            "slogan": final_res["slogan"],
        }
        return final_result
    
    # 输入项目描述,输出项目名称和slogan
    result = name_slogan_by_desc(proj_desc)
    print(result)
    

    执行结果如下:

    {'project_name': '蝴蝶谷救援行动', 'slogan': '团结合作,共赴蝴蝶谷'}
    

    可以看到代码更简洁,也很直观,当然,也可以使用LCEL让整个链条更加简洁清晰。

    5. LCEL表达式

    LCEL方式的代码如下:

    # 本次需求:我们给LLM输入一段项目描述,让LLM给这个项目起一个名称和Slogan
    # 以下是实现:
    
    from langchain.prompts import PromptTemplate, ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
    
    proj_desc = """
        我们本次的项目是去森林里探险救援,我们有一个10人小队,
        我们要到达一个叫做“蝴蝶谷”的目的地,去那里解救一位被困的科学家。
        这期间我们可能会遇到许多危险,我们需要共同合作,互相帮助,历经磨难,才能到达目的地。
        我们的任务是要在5天内到达目的地并且救出探险家,才算完成这次探险,否则任务失败,我们将受到惩罚。
        出发前我们要各自准备好自己的装备和干粮,加油!
    """
    
    def name_slogan_by_desc(project_desc):
        """
        根据项目描述,生成项目名称和slogan
        """
    
        # 第1条链
        promt_template_project_name = "请你根据标签里的关于某个项目的描述,生成一个项目名称,只需要返回项目名称。{project_desc}"
        chain_one = (
            PromptTemplate.from_template(promt_template_project_name)
            | model
            | {"project_name": StrOutputParser(), "project_desc": lambda x: project_desc}
        )
    
        # 第2条链
        promt_template_slogan = "请你根据标签里的关于某个项目的描述,和这个项目的名称{project_name},给这个项目起一个slogan,slogan要求干脆简洁积极向上,只返回slogan。{project_desc}"
        chain_two = (
            PromptTemplate.from_template(promt_template_slogan)
            | model
            | {"slogan": StrOutputParser(), "project_info": lambda x: chain_one}
        )
    
        # 串联两条链
        final_chain = chain_one | chain_two
        final_res = final_chain.invoke({"project_desc": project_desc})
    
        final_result = {
            "project_name": final_res["project_info"]["project_name"],
            "slogan": final_res["slogan"],
        }
    
        return final_result
    
    # 输入项目描述,输出项目名称和slogan
    result = name_slogan_by_desc(proj_desc)
    print(result)
    

    普通方式和LCEL方式的核心代码对比:

    • 普通方式

    • LCEL方式

    6. 总结

    本文主要聊了 LangChain 中的 Chain 概念。Chain 是 LangChain 中的核心组件,我们对多个组件的一系列调用就是Chain。

    使用Chain可以让构建复杂的任务,更加清晰简洁。

    =====>>>>>> 关于我 <<<<<<=====

    本篇完结!欢迎点赞 关注 收藏!!!

    原文链接:https://mp.weixin.qq.com/s/IdaO8CeS1TKoQDCcjMqWsg

  • 相关阅读:
    儿童生意的副作用
    Java项目(三)-- SSM开发社交网站(3)--整合MyBatis-Plus及书评网数据库表设计
    leetCode 337. 打家劫舍 III 动态规划 房子都连成树了,偷不偷呢? “树形dp“ (递归三部曲 + 动规五部曲)
    机器学习(三):多项式回归
    CV复习:上/下采样
    not_the_same_3dsctf_2016【简单解法和mprotect解法】
    vue3+ts+vite数据大屏自适应总结(两种方法)
    Quartz手动修改数据库cron表达式(无须重启服务器即可更改定时时间)
    Java写一套漂亮的代码,哪些设计模式比较常用?
    查找算法-斐波那契查找法(Fibonacci Search)
  • 原文地址:https://www.cnblogs.com/mangod/p/18251121