04|LangChain | 从入门到实战 -六大组件之chain

by:wenwenc9

如果本文如果有错误地方,欢迎指正。

01|LangChain | 从入门到实战-介绍
02|LangChain | 从入门到实战 -六大组件之Models IO
03|LangChain | 从入门到实战 -六大组件之Retrival

一、介绍

执行创建工具,场景等,调用顺序,遵循 LCEL

在许多编程语言和库中,"chain"通常用于描述一系列的操作或函数,这些操作或函数按照特定的顺序依次执行,前一个操作的输出会作为后一个操作的输入。这种模式也被称为管道(Pipeline)或链式调用(Chain Calling)。

在给出的代码中,"chain"是一个处理链,它首先将文档内容作为输入,然后使用ChatPromptTemplate生成一个聊天提示模板,然后调用ChatOpenAI对象进行聊天,并指定了函数和函数调用方式,最后使用JsonKeyOutputFunctionsParser来解析函数的输出结果。

具体来说,"chain"的作用如下:

1.将处理流程串联起来:通过将一系列的操作串联起来,可以使处理流程更清晰,更易于理解和维护。

2.简化代码:通过链式调用,可以避免在每一步操作后都需要单独保存和处理结果,从而简化代码。

3.提高灵活性:通过改变链中的操作或其顺序,可以轻松地修改处理流程,以适应不同的需求。

4.提高效率:在某些情况下,链式调用可以提高代码的执行效率。例如,如果链中的每个操作都是异步执行的,那么整个处理流程可能会比逐个执行这些操作更快。

在langchain库中,"chain"可能是用来串联一系列文本处理和分析操作的工具,以实现从原始文档到最终结果的完整处理流程。

或者这么来说

langchain 中的 chain 是用于表示知识链的类。

知识链(Knowledge Chain)是 langchain 的一个重要概念,它表示一个从一个知识点(concept)延伸到另一个知识点的路径。

一个 chain 包含:

  • 起点(start)
  • 终点(end)
  • 路径(path)

一个 chain 的作用是:

  • 表示两个知识点(concept)之间的关系
  • 提供从一个知识点到另一个知识点的路径
    例如,一个 chain 可以表示:
    起点:苹果
    终点:水果
    路径:
    苹果 -> 红色水果 -> 硬质水果 -> 果实 -> 果类 -> 水果
    通过这个 chain,我们可以理解苹果是水果的一个子类。

langchain 使用 chain 来建立知识点之间的关系,并利用这些关系来推理和理解新知识。

具体来说,chain 在 langchain 中的作用有:

1.表示知识点之间的关系。不同的 chain 表示不同的关系。

2.提供从一个知识点到另一个知识点的路径。这些路径包含多个中间知识点,帮助理解知识点之间的关系。

3.用于推理和理解。langchain 可以根据已有的 chain,推理出新的 chain 或者理解新的知识点。

4.作为知识图谱的基本单位。langchain 中的知识图谱由大量的 chain 构成。

5.用于扩展知识。langchain 可以不断学习新的 chain,从而扩展和完善它的知识。

总的来说,chain 是 langchain 表示和理解知识的重要工具。它通过表示知识点之间的路径关系,帮助 langchain 推理和扩展知识。

类继承关系:

python 复制代码
Chain --> <name>Chain  # Examples: LLMChain, MapReduceChain, RouterChain

代码实现:https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/chains/base.py

定义一个名为Chain的基础类

class Chain(Serializable, Runnable[Dict[str, Any], Dict[str, Any]], ABC):
"""为创建结构化的组件调用序列的抽象基类。

链应该用来编码对组件的一系列调用,如模型、文档检索器、其他链等,并为此序列提供一个简单的接口。

Chain接口使创建应用程序变得容易,这些应用程序是:
- 有状态的:给任何Chain添加Memory可以使它具有状态,
- 可观察的:向Chain传递Callbacks来执行额外的功能,如记录,这在主要的组件调用序列之外,
- 可组合的:Chain API足够灵活,可以轻松地将Chains与其他组件结合起来,包括其他Chains。

链公开的主要方法是:
- `__call__`:链是可以调用的。`__call__`方法是执行Chain的主要方式。它将输入作为一个字典接收,并返回一个字典输出。
- `run`:一个方便的方法,它以args/kwargs的形式接收输入,并将输出作为字符串或对象返回。这种方法只能用于一部分链,不能像`__call__`那样返回丰富的输出。
"""

# 调用链
def invoke(
    self, input: Dict[str, Any], config: Optional[runnableConfig] = None
) -> Dict[str, Any]:
    """传统调用方法。"""
    return self(input, **(config or {}))

# 链的记忆,保存状态和变量
memory: Optional[BaseMemory] = None
"""可选的内存对象,默认为None。
内存是一个在每个链的开始和结束时被调用的类。在开始时,内存加载变量并在链中传递它们。在结束时,它保存任何返回的变量。
有许多不同类型的内存,请查看内存文档以获取完整的目录。"""

# 回调,可能用于链的某些操作或事件。
callbacks: Callbacks = Field(default=None, exclude=True)
"""可选的回调处理程序列表(或回调管理器)。默认为None。
在对链的调用的生命周期中,从on_chain_start开始,到on_chain_end或on_chain_error结束,都会调用回调处理程序。
每个自定义链可以选择调用额外的回调方法,详细信息请参见Callback文档。"""

# 是否详细输出模式
verbose: bool = Field(default_factory=_get_verbosity)
"""是否以详细模式运行。在详细模式下,一些中间日志将打印到控制台。默认值为`langchain.verbose`。"""

# 与链关联的标签
tags: Optional[List[str]] = None
"""与链关联的可选标签列表,默认为None。
这些标签将与对这个链的每次调用关联起来,并作为参数传递给在`callbacks`中定义的处理程序。
你可以使用这些来例如识别链的特定实例与其用例。"""

# 与链关联的元数据
metadata: Optional[Dict[str, Any]] = None
"""与链关联的可选元数据,默认为None。
这些元数据将与对这个链的每次调用关联起来,并作为参数传递给在`callbacks`中定义的处理程序。
你可以使用这些来例如识别链的特定实例与其用例。"""

二、基础案例

1、LLMchain 案例一

python 复制代码
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain

# 调用模型
llm = OpenAI()
# 提示词
prompt = PromptTemplate(
    input_variables=["content1", 'content2'],
    template="关于{content1}{content2}是什么?请中文回复我"
)
# 构建一个链路
chain = LLMChain(llm=llm, prompt=prompt)
# 提问
res = chain.run({'content1': '苹果', 'content2': '笑话'})

上面的这个,创建了一个调用模型,以及构建了prompt

通过LLMchain进行组装使用

2、LLMchain 案例二

通过链创建聊天模型

python 复制代码
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import (
    ChatPromptTemplate,
    HumanMessagePromptTemplate,
)

# 创建用户提示词
human_message_prompt = HumanMessagePromptTemplate(
    prompt=PromptTemplate(
        input_variables=["content1", 'content2'],
        template="关于{content1}{content2}是什么?请中文回复我"
    ),
)
# 组装提示模板
chat_prompt_template = ChatPromptTemplate.from_messages([human_message_prompt])
chat = ChatOpenAI(temperature=0.9)
chain = LLMChain(llm=chat, prompt=chat_prompt_template)
# 提问
res = chain.run({'content1': '苹果', 'content2': '笑话'})
print(res)

3、LCEL 构成链 案例三

python 复制代码
# 导入llm 模型
from langchain_openai import ChatOpenAI

from langchain.prompts import ChatPromptTemplate

model = ChatOpenAI()
prompt = ChatPromptTemplate.from_template("告诉我一个笑话关于{topic}")


# 构建链 LCEL 模式
chain = prompt | model 

chain.invoke({"topic": "小狗"})

三、常用chain

如果要了解更多链,可以看源码去
https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/chains/init.py

1、LLMChain

LLMChain是一个简单的链,它围绕语言模型添加了一些功能。它在整个LangChain中广泛使用,包括在其他链和代理中。

LLMChain由PromptTemplate和 语言模型(LLM 或聊天模型)组成。它使用提供的输入键值(以及内存键值,如果可用)格式化提示模板,将格式化的字符串传递给 LLM 并返回 LLM 输出。

介绍部分chain启动方法,如果不想了解原理的,可以看代码就好了

python 复制代码
from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI
from langchain.chains import LLMChain

prompt_template = "关于 {content}的笑话?"

llm = OpenAI(temperature=0)
llm_chain = LLMChain(
    llm=llm,
    prompt=PromptTemplate.from_template(prompt_template)
)

1.1 利用 chain.run 传入一个参数

源码

传入参数

python 复制代码
a = llm_chain('太阳')
b = llm_chain.run('太阳')
c = llm_chain.run({'content':'太阳'})
d = llm_chain.run({'abcdefg':'太阳'})

当执行的时候,a,b,c,能够成功运行,而d错误,

为什么prompt有一个 key 参数,可以在提问的的时候,不用传入参数指定(下面流程演示)

为什么d错误(代码有检测,从prompt进行判别)

为什么a是obj,b、c是文本(也可以按照下面流程调试)

跟着堆栈调试,进入,可以看到从prompt 获取了输入参数

并且,如果用户没有指定参数,那么就从prompt获取key,作为输入的key

1.2 chain.apply 传入多个问题

传入多个问题

python 复制代码
input_list = [
    {"content": "小鸟"},
    {"content": "大象"},
    {"content": "老虎"}
]
llm_chain.apply(input_list)
python 复制代码
[{'text': '\n\nQ: 为什么小鸟笑得很开心?\nA: 因为它发现自己可以飞!'},
 {'text': '\n\nQ: What did the elephant say to the naked man?\nA: "How do you breathe through that tiny thing?"'},
 {'text': '\n\nQ: What did the tiger say when he saw the zebra?\nA: Stripes!'}]

有没有一种办法,传入多个问题,可不可以不要一次行返回结果,调用一次返回一个当然有(可以使用 generate)

python 复制代码
llm_chain.generate(input_list)

1.3 chain.predict

predict与方法类似run,只是输入键被指定为关键字参数而不是 Python 字典 而前面的 run

python 复制代码
llm_chain.predict(content="大象")

多个参数

python 复制代码
template = """Tell me a {adjective} joke about {subject}."""prompt = PromptTemplate(template=template, input_variables=["adjective", "subject"])
llm_chain = LLMChain(prompt=prompt, llm=OpenAI(temperature=0))
llm_chain.predict(adjective="sad", subject="ducks")

1.4 chain.invoke

跟run大体一样


1.5 扩展

python 复制代码
from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI
from langchain.chains import LLMChain

from langchain.output_parsers import CommaSeparatedListOutputParser

# 创建输出解析器(逗号分割列表解析器
output_parser = CommaSeparatedListOutputParser()
template = """列举彩虹的所有颜色,不要有其他多余回复"""
prompt_template = PromptTemplate(template=template, input_variables=[], output_parser=output_parser)
llm = OpenAI(temperature=0)
llm_chain = LLMChain(
    llm=llm,
    prompt=prompt_template
)
res = llm_chain.predict()
res2 = llm_chain.predict_and_parse()
  1. ConversationChain 对话链,存上下文

    默认情况下,ConversationChain有一种简单类型的内存,可以记住所有先前的输入/输出,并将它们添加到传递给 LLM 的上下文中

  2. RouterChain 路由链,主要用于动态选择下一个要调用的链

  3. MultiPromptChain 多提示链

    • 有多个不同类型的路由链,每个路由链对应一个提示。
    • RouterChain根据问题的类型,选择对应的路由链。
    • 路由链选择对应的提示,然后交给MultiPromptChain使用该提示回答问题。

2 、路由链 RouterChain

我们现在有一个目标:

创建2个应用chain角色,根据用户问题,指定那个chain进行回复,如果问题不在这2个角色范围,则使用默认链对话

为了实现这个目标,下面拆成3个链进行了解,最后组装成一个完整代码

1、ConversationChain

默认情况下,ConversationChain有一种简单类型的内存,可以记住所有先前的输入/输出,并将它们添加到传递给 LLM 的上下文中

python 复制代码
from langchain.llms import OpenAI
from langchain.chains import ConversationChain
from langchain.chains.llm import LLMChain
from langchain.prompts import PromptTemplate
default_chain = ConversationChain(llm=llm, output_key="text") 

可以看到前面对话的内容

用LLmchain进行比对,看不到

2、RouterChain

源码位置
https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/chains/router/init.py

路由链,主要用于动态选择下一个要调用的链

  • LLMRouterChain 将用户输入放进大语言模型,通过Prompt的形式让大语言模型来进行路由

  • EmbeddingRouterChain 通过向量搜索的方式,将用户输入

下面代码,创建2个应用角色,用于实现,用户输入问题自动选择走哪个chain

python 复制代码
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
physics_template = """你是一位非常聪明的物理学教授。 \
您擅长以简洁易懂的方式回答有关物理的问题。 \
当你不知道某个问题的答案时,你就承认你不知道。

这是一个问题:
{input}"""

math_template = """你是一位非常优秀的数学家。 你很擅长回答数学问题。 \
你太棒了,因为你能够将难题分解成各个组成部分,\
回答各个组成部分,然后将它们组合起来回答更广泛的问题。

这是一个问题:
{input}"""

prompt_infos = [
    {
        "name": "physics",
        "description": "适合回答物理问题",
        "prompt_template": physics_template,
    },
    {
        "name": "math",
        "description": "适合回答数学问题",
        "prompt_template": math_template,
    },
]
destination_chains = {}
for p_info in prompt_infos:
    name = p_info["name"]
    prompt_template = p_info["prompt_template"]
    prompt = PromptTemplate(template=prompt_template, input_variables=["input"])
    chain = LLMChain(llm=llm, prompt=prompt)
    destination_chains[name] = chain

到这里,在 destination_chains 构建了2个链,现在还不能直接使用

现在,我们将其组装成routerchain

python 复制代码
from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser
from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE
# 提取2个链的参数描述
destinations = [f"{p['name']}: {p['description']}" for p in prompt_infos] # 列表字典
destinations_str = "\n".join(destinations)  # 字符串

# 构建多模板
router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format(destinations=destinations_str)
router_prompt = PromptTemplate(
    template=router_template,
    input_variables=["input"],
    output_parser=RouterOutputParser(),
)
router_chain = LLMRouterChain.from_llm(llm, router_prompt)

可以看到,虽然进行提问,但是并不会返回答案

3、MultiPromptChain

多提示链

  • 有多个不同类型的路由链,每个路由链对应一个提示。

  • RouterChain根据问题的类型,选择对应的路由链。

  • 路由链选择对应的提示,然后交给MultiPromptChain使用该提示回答问题。

使用这个链,需要3个必须要素:

  1. router_chain 路由链
  2. destination_chains 目标链dict对象
  3. default_chain 默认链
python 复制代码
from langchain.chains.router import MultiPromptChain
chain = MultiPromptChain(
    router_chain=router_chain, # 路由链
    destination_chains=destination_chains, 
    default_chain=default_chain,# 
    verbose=True,
)

4、完整代码

python 复制代码
from langchain.llms import OpenAI
from langchain.chains import ConversationChain
from langchain.chains.llm import LLMChain
from langchain.prompts import PromptTemplate

physics_template = """你是一位非常聪明的物理学教授。 \
您擅长以简洁易懂的方式回答有关物理的问题。 \
当你不知道某个问题的答案时,你就承认你不知道。

这是一个问题:
{input}"""

math_template = """你是一位非常优秀的数学家。 你很擅长回答数学问题。 \
你太棒了,因为你能够将难题分解成各个组成部分,\
回答各个组成部分,然后将它们组合起来回答更广泛的问题。

这是一个问题:
{input}"""

prompt_infos = [
    {
        "name": "physics",
        "description": "适合回答物理问题",
        "prompt_template": physics_template,
    },
    {
        "name": "math",
        "description": "适合回答数学问题",
        "prompt_template": math_template,
    },
]
llm = OpenAI()

####################### 链dict对象  #######################   
destination_chains = {}
# 遍历提示模板,组装成一个chain,然后根据模板名存储到dict中
for p_info in prompt_infos:
    name = p_info["name"]
    prompt_template = p_info["prompt_template"]
    prompt = PromptTemplate(template=prompt_template, input_variables=["input"])
    chain = LLMChain(llm=llm, prompt=prompt)
    destination_chains[name] = chain
    
####################### 基础链  #######################   
default_chain = ConversationChain(llm=llm, output_key="text") # 起始链路


from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser
from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE
# 提取2个链的参数描述
destinations = [f"{p['name']}: {p['description']}" for p in prompt_infos] # 列表字典
destinations_str = "\n".join(destinations)  # 字符串

# 构建多模板
router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format(destinations=destinations_str)

router_prompt = PromptTemplate(
    template=router_template,
    input_variables=["input"],
    output_parser=RouterOutputParser(),
)

####################### 路由链  #######################   
router_chain = LLMRouterChain.from_llm(llm, router_prompt)

# 使用
from langchain.chains.router import MultiPromptChain
chain = MultiPromptChain(
    router_chain=router_chain, # 路由链
    destination_chains=destination_chains, 
    default_chain=default_chain,# 
    verbose=True,
)

因为,前面创建了2个角色,一个是数学家,一个是物理学家,所以可看到调用日志上面写的 physics

当输入问题在多链对象查找不到,时候调用default_chain,可以看到历史对话也出来了

3、顺序链

调用语言模型后的下一步是对语言模型进行一系列调用。当您想要获取一个调用的输出并将其用作另一个调用的输入时,这特别有用。

通过一些示例来说明如何使用顺序链来执行此操作。

顺序链允许您连接多个链并将它们组成执行某些特定场景的管道。顺序链有两种类型:

  1. SimpleSequentialChain:顺序链的最简单形式,其中每个步骤都有**一个单一的输入/输出**,并且一个步骤的输出是下一步的输入。
  2. SequentialChain:更通用的顺序链形式,允许多个输入/输出

白话文:我可以创建一个专家团,A是分析用户问题,A得出的问题作为下次输入,交给B解决

6.1 SimpleSequentialChain

单个输入输出

如下代码,构建2个角色链

python 复制代码
from langchain.llms import OpenAI
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate

# 创建一个,作家角色
llm = OpenAI(temperature=.7)
synopsis_template = """你是一名写作家. 给定游戏的标题,你的工作就是为该标题写一个概要.

标题: {title}
写作家: 这是以上游戏的简介:"""
synopsis_prompt_template = PromptTemplate(input_variables=["title"], template=synopsis_template)
synopsis_chain = LLMChain(llm=llm, prompt=synopsis_prompt_template)

# 创建一个,评论员角色
llm = OpenAI(temperature=.7)
commentator_template = """你是一名评论员. 给定游戏的简介,你的工作就是为该游戏,给出评分(1-10).

游戏简介概要: {content}
评论员: 这是对于该游戏的评分:"""
commentator_prompt_template = PromptTemplate(input_variables=["content"], template=commentator_template)
commentator_chain = LLMChain(llm=llm, prompt=commentator_prompt_template)

组装使用

python 复制代码
from langchain.chains import SimpleSequentialChain

overall_chain = SimpleSequentialChain(chains=[synopsis_chain, commentator_chain], verbose=True)

overall_chain.run('奥特曼大战怪兽')

6.2 SequentialChain

面多复杂的chain,进行多输入输出的情况

python 复制代码
from langchain.llms import OpenAI
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate

# 创建一个,作家角色
llm = OpenAI(temperature=.7)
synopsis_template = """你是一名写作家. 给定游戏的标题,以及类型,然后你的工作就是为该标题结合类型写一个概要.

标题: {title}
类型: {type}
写作家: 这是以上游戏的简介:"""
synopsis_prompt_template = PromptTemplate(input_variables=["title", "type"], template=synopsis_template)
synopsis_chain = LLMChain(llm=llm, prompt=synopsis_prompt_template, output_key='content')

# 创建一个,评论员角色
llm = OpenAI(temperature=.7)
commentator_template = """你是一名评论员. 给定游戏的简介,你的工作就是为该游戏,给出评分(1-10).

游戏简介概要: {content}
评论员: 这是对于该游戏的评论和评分:"""
commentator_prompt_template = PromptTemplate(input_variables=["content"], template=commentator_template)
commentator_chain = LLMChain(llm=llm, prompt=commentator_prompt_template, output_key='comment')

# 组装
from langchain.chains import SequentialChain

overall_chain = SequentialChain(chains=[synopsis_chain, commentator_chain],
                                input_variables=['title', 'type'],
                                output_variables=["content", "comment"],
                                verbose=True)

res = overall_chain({'title': '奥特曼大战怪兽', 'type': '恐怖'})
print(res)

输入输出都为2个,这个由你决定

4、转换链

TransformChain,允许在链之间添加自定义的转换函数

python 复制代码
from langchain.chains import TransformChain
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate

def transform_func(inputs: dict) -> dict:
    text = inputs["text"]
    shortened_text = "\n\n".join(text.split("\n\n")[:3])
    return {"output_text": shortened_text}

template = """总结这些文本,告诉我深层次意义,翻译成英文:
{output_text}
总结:"""
prompt = PromptTemplate(input_variables=["output_text"], template=template)

transform_chain = TransformChain(
    input_variables=["text"], output_variables=["output_text"], transform=transform_func,prompt=prompt
)

直接用这个对话看看会发生什么,可以看到,有6行通过\N\N换行,截取了前3行。

此时这个链没有到达openai,只是调用了转换链,不具备直接跟模型交互。

加上llmchain,和顺序链,进行使用

python 复制代码
from langchain.chains import TransformChain, LLMChain, SimpleSequentialChain
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate

def transform_func(inputs: dict) -> dict:
    text = inputs["text"]
    shortened_text = "\n\n".join(text.split("\n\n")[:3])
    return {"output_text": shortened_text}


transform_chain = TransformChain(
    input_variables=["text"], output_variables=["output_text"], transform=transform_func
)

template = """总结这些文本,告诉我深层次意义,翻译成英文:
{output_text}
总结:"""
prompt = PromptTemplate(input_variables=["output_text"], template=template)
llm_chain = LLMChain(llm=OpenAI(), prompt=prompt)

sequential_chain = SimpleSequentialChain(chains=[transform_chain, llm_chain])
相关推荐
上山的月6 分钟前
MySQL -函数和约束
数据库·mysql
zhcf9 分钟前
【MySQL】十三,关于MySQL的全文索引
数据库·mysql
丁总学Java18 分钟前
要查询 `user` 表中 `we_chat_open_id` 列不为空的用户数量
数据库·mysql
抓哇能手18 分钟前
数据库系统概论
数据库·人工智能·sql·mysql·计算机
littlegirll19 分钟前
一个从oracle使用spool导出数据到kadb的脚本
数据库·oracle
geovindu21 分钟前
CSharp: Oracle Stored Procedure query table
数据库·oracle·c#·.net
油丶酸萝卜别吃34 分钟前
MyBatis中XML文件的模板
xml·数据库·mybatis
三天不学习36 分钟前
【Select 语法全解密】.NET开源ORM框架 SqlSugar 系列
数据库·.net·orm·微软技术·sqlsugar
CC呢1 小时前
基于单片机的智能婴儿床监护系统多功能婴儿床摇篮系统
数据库·mongodb
林的快手2 小时前
209.长度最小的子数组
java·数据结构·数据库·python·算法·leetcode