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 分钟前
innovus Flip chip 产品设计方法(3)
数据库·windows·microsoft
vibag15 分钟前
使用底层API构建图
人工智能·语言模型·langchain·大模型·langgraph
m0_7269659825 分钟前
【闲聊】用coze和阿里云搭建agent区别
ai·agent
GalenZhang88830 分钟前
使用 Python SDK 将数据写入飞书多维表格
数据库·python·飞书·多维表格
云和数据.ChenGuang33 分钟前
GaussDB 期末考试题与面试题
数据库·opengauss·gaussdb·数据库期末试题
不屈的铝合金36 分钟前
SQL 语言概述与数据库核心前置配置了解
数据库·sql·mysql·约束·sql 语句分类·字符集配置·校对规则
萧曵 丶36 分钟前
可重复读(Repeatable Read)隔离级别下幻读产生的原因
数据库·sql·mysql
陈鋆1 小时前
LightRAG应用一:[LightRAG WebUI]
ai
Antoine-zxt1 小时前
MySQL宕机日志迷局破解指南:从前台启动到精准排错
数据库·mysql·adb
松涛和鸣1 小时前
DAY47 FrameBuffer
c语言·数据库·单片机·sqlite·html