by:wenwenc9
如果本文如果有错误地方,欢迎指正。
01|LangChain | 从入门到实战-介绍
02|LangChain | 从入门到实战 -六大组件之Models IO
03|LangChain | 从入门到实战 -六大组件之Retrival
04|LangChain | 从入门到实战 -六大组件之Chain
一、介绍
执行创建工具,场景等,调用顺序,遵循 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 推理和扩展知识。
类继承关系:
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 案例一
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 案例二
通过链创建聊天模型
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 构成链 案例三
# 导入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启动方法,如果不想了解原理的,可以看代码就好了
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 传入一个参数
传入参数
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 传入多个问题
传入多个问题
input_list = [
{"content": "小鸟"},
{"content": "大象"},
{"content": "老虎"}
]
llm_chain.apply(input_list)
[{'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)
llm_chain.generate(input_list)
1.3 chain.predict
predict与方法类似run,只是输入键被指定为关键字参数而不是 Python 字典 而前面的 run
。
llm_chain.predict(content="大象")
多个参数
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 扩展
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()
-
ConversationChain
对话链,存上下文
默认情况下,ConversationChain有一种简单类型的内存,可以记住所有先前的输入/输出,并将它们添加到传递给 LLM 的上下文中 -
RouterChain
路由链,主要用于动态选择下一个要调用的链 -
MultiPromptChain
多提示链- 有多个不同类型的路由链,每个路由链对应一个提示。
- RouterChain根据问题的类型,选择对应的路由链。
- 路由链选择对应的提示,然后交给MultiPromptChain使用该提示回答问题。
2 、路由链 RouterChain
我们现在有一个目标:
创建2个应用chain角色,根据用户问题,指定那个chain进行回复,如果问题不在这2个角色范围,则使用默认链对话
为了实现这个目标,下面拆成3个链进行了解,最后组装成一个完整代码
1、ConversationChain
默认情况下,ConversationChain有一种简单类型的内存,可以记住所有先前的输入/输出,并将它们添加到传递给 LLM 的上下文中
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
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
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个必须要素:
- router_chain 路由链
- destination_chains 目标链dict对象
- default_chain 默认链
from langchain.chains.router import MultiPromptChain
chain = MultiPromptChain(
router_chain=router_chain, # 路由链
destination_chains=destination_chains,
default_chain=default_chain,#
verbose=True,
)
4、完整代码
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、顺序链
调用语言模型后的下一步是对语言模型进行一系列调用。当您想要获取一个调用的输出并将其用作另一个调用的输入时,这特别有用。
通过一些示例来说明如何使用顺序链来执行此操作。
顺序链允许您连接多个链并将它们组成执行某些特定场景的管道。顺序链有两种类型:
- SimpleSequentialChain:顺序链的最简单形式,其中每个步骤都有**
一个单一的输入/输出
**,并且一个步骤的输出是下一步的输入。 - SequentialChain:更通用的顺序链形式,
允许多个输入/输出
。
白话文:我可以创建一个专家团,A是分析用户问题,A得出的问题作为下次输入,交给B解决
6.1 SimpleSequentialChain
单个输入输出
如下代码,构建2个角色链
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)
组装使用
from langchain.chains import SimpleSequentialChain
overall_chain = SimpleSequentialChain(chains=[synopsis_chain, commentator_chain], verbose=True)
overall_chain.run('奥特曼大战怪兽')
6.2 SequentialChain
面多复杂的chain,进行多输入输出的情况
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,允许在链之间添加自定义的转换函数
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,和顺序链,进行使用
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])