系列文章目录
LangChain(二)基础问答大模型,纯新手向-CSDN博客
LangChain(三)基础问答大模型,从LLMchain开始了解chain!纯新手向-CSDN博客
LangChain(四)工具调用的底层原理!给大模型按上双手吧!(新手向)-CSDN博客
LangChain(五)工具调用的底层原理进阶!依旧纯新手向~-CSDN博客
本文目录
前言
随着前面几篇博客的阅读,我想大家已经对如何构建一条完整的链有了初步的了解。此时实际上已经覆盖了大多数的应用场景,各位看客不妨结合实际,尝试落地一下。产品实际出来的那一刻,成就感会不小哦。
但是对于一些更复杂的场景,单一的链已经无法完成任务,很多场景常常需要多个功能支持,此时就需要构建多个链。那么对于多个链的操作我们就需要额外的工具!那就是路由链!
一、路由链
在LangChain0.1版本,LLMRouterChain是官方chain之一,但到了如今LangChain0.2版本,虽然还支持该链,但官网已经明确表达,建议不使用该链。
原因我想有如下几点:
- LLMRouterChain 有点抽象了,对于入门来说不太友好
- 路由链的本质其实和工具调用的本质是一样的,没必要多出一个概念来来掌握
- 自己构建路由链反而更加简单易懂
请大家摒弃0.1版本的LLMRouterChain的方式,尝试自己构建路由吧!
二、路由链的构建方式和基本原理
本处,我们将通过代码块的方式逐行解析路由链的构建方式和原理。极为新手向!
1.引入库与定义大模型!
代码如下:
from langchain_community.chat_models import QianfanChatEndpoint
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableLambda
import os
# 设定百度千帆大模型的AK和SK-去百度千帆官网的控制台新建一个应用即可
os.environ["QIANFAN_AK"] = "Your AK"
os.environ["QIANFAN_SK"] = "Your SK"
#创建千帆LLM模型
qianfan_chat = QianfanChatEndpoint(
model="ERNIE-3.5-8K",
temperature=0.2,
timeout=30,
)
本处没啥好说的,导入了一系列库,并定义了大模型。
对于库来说,含义分别如下:
- 千帆大模型的相关库
- 输出解析器库
- prompt模板库
- 运行临时函数库
- OS系统库
2.构建路由prompt
代码如下:
chain = (
PromptTemplate.from_template(
"""
你是一个分类器,将用户的输入分类为对应的模块。
用户的输入如下,将其分类为"知识问答"、"数学计算"、"生活指南"、"其他"中的一个并返回。
你只能返回一个单词
<question>
{question}
</question>
"""
)
| qianfan_chat
| StrOutputParser()
)
print(chain.invoke({"question": "南瓜汤怎么做?"}))
# 输出:生活指南
看到这我想大家已经又懂了……这和工具调用压根没区别,唯一的区别就在于一个是路由到工具函数,一个是路由到链……。这也是为什么没必要使用官方方式的原因。自己写简单直观,你还能有很大的可操作性。
3.构建子链
代码如下:
# knowledge_chain
knowledge_chain = PromptTemplate.from_template(
"""你是一个知识博主,博览群书。每次回答的问题的第一句话总是"性盛致灾,割以永治,阿门~"
回答接下来的问题:
{question}
"""
) | qianfan_chat
# math_chain
math_chain = PromptTemplate.from_template(
"""你是一个数学家。每次回答的问题的第一句话总是"1 + 1 = 3。阿巴阿巴~"
回答接下来的问题:
{question}"""
) | qianfan_chat
# life_chain
life_chain = PromptTemplate.from_template(
"""你是一个生活博主。每次回答的问题的第一句话总是"蔬菜使人健康哦!"
回答接下来的问题:
{question}"""
) | qianfan_chat
# other子链
other_chain = PromptTemplate.from_template(
"""你每次回答的第一句话总是"I am Nobody。呜呜~"
回答接下来的问题:
{question}"""
) | qianfan_chat
等价于工具调用里的函数定义。上诉的chain只是举个例子,各位可以根据自身需要,修改为自己需要的chain。
4.构建路由函数与总链
代码如下:
# 路由函数
def route(info):
if "知识问答" in info["topic"].lower():
return knowledge_chain.invoke(info)
elif "数学计算" in info["topic"].lower():
return math_chain.invoke(info)
elif "生活指南" in info["topic"].lower():
return life_chain.invoke(info)
else:
return other_chain.invoke(info)
full_chain = {"topic": chain, "question": lambda x: x["question"]} | RunnableLambda(
route
)
print(full_chain.invoke({"question": "世界上最年轻的诺奖得主是谁?"}).content)
print(full_chain.invoke({"question": "0.5 * 0.8 = ?"}).content)
print(full_chain.invoke({"question": "今天吃点啥好呢?"}).content)
print(full_chain.invoke({"question": "我的名字是千天夜!"}).content)
该处可能需要好好讲解一下,我们先跳过 route 函数
我们先来看 full_chain 的构建方式。首先我们要清晰一点:对于chain来说,上一个模块的返回值就是下一个模块的输入!
所以对于该链来说,
- 最开始的输入为用户的文本!给到 {"topic": chain, "question": lambda x: x["question"]}
对于第一个样例: full_chain.invoke({"question": "世界上最年轻的诺奖得主是谁?"})
此时输入为:{"question": "世界上最年轻的诺奖得主是谁?"}
- 此时,第一个模块开始执行,首先对于topic键值,执行chain(这是之前定义好的链)。对于第二个question键值,执行后续的临时函数。
以防大家向上翻过于麻烦,我直接粘贴过来:
chain = (PromptTemplate.from_template("""你是一个分类器,将用户的输入分类为对应的模块。用户的输入如下,将其分类为"知识问答"、"数学计算"、"生活指南"、"其他"中的一个并返回。你只能返回一个单词。
<question>
{question}
</question>""")
| qianfan_chat
| StrOutputParser()
)
- 此时对于第一个样例,chain的执行结果为:“知识问答”
- 对于第二个键值,临时函数的执行结果为:“世界上最年轻的诺奖得主是谁?”
综上本部分的最终输出为
{"topic": "知识问答", "question": "世界上最年轻的诺奖得主是谁?"]}
本输出将会作为下一部分的输入!
- RunnableLambda(route) 该处需要好好解释一下
RunnableLambda这个到底是个什么东东???新手肯定超疑惑的。
实际上这个东西相当于一个统一化的接口。因为route函数相当于是我们自己定义的,LangChian不认识这个东西。所以如果我们直接写成下面这样:
{"topic": chain, "question": lambda x: x["question"]} | route
此时本来意味着链的链接符号: “|” ,被解析成传统的 按位或 符号,那必然会报错。
本处 的原理极为相似函数的重载,根据传参内容的不同调用不同的函数。此时也是一样,根据左右的内容使用 “|” 的不同含义。详细可以看一下“符号重载”相关的内容
从理解上来说,依旧是{"topic": "知识问答", "question": "世界上最年轻的诺奖得主是谁?"]} 被当成了route函数的输入。RunnableLambda仅仅是作为一个自定义函数的统一接口而已。
- route函数!
route函数仅仅是返回对应的链的执行结果而已。
# 路由函数
def route(info):
if "知识问答" in info["topic"].lower():
return knowledge_chain.invoke(info)
elif "数学计算" in info["topic"].lower():
return math_chain.invoke(info)
elif "生活指南" in info["topic"].lower():
return life_chain.invoke(info)
else:
return other_chain.invoke(info)
5.结果解析与输出
输入:
print(full_chain.invoke({"question": "世界上最年轻的诺奖得主是谁?"}).content)
输出如下:
性盛致灾,割以永治,阿门~
世界上最年轻的诺奖得主是马拉拉·尤萨夫扎伊(Malala Yousafzai),她于2014年与凯拉什·萨蒂亚尔希(Kailash Satyarthi)共同获 得诺贝尔和平奖,当时年仅17岁。马拉拉·尤萨夫扎伊出生于巴基斯坦西北边境省一个普什图穆斯林教育工作者的家庭,是巴基斯坦西北 部开伯尔-普赫图赫瓦省斯瓦特县明戈拉城的一名学生,女权主义者,以争取妇女接受教育的权利而闻名。她因为强烈反对压制女童而被 塔利班分子枪击头部,伤愈后她继续为儿童教育权利而奋斗,目前生活在英国。
输入
print(full_chain.invoke({"question": "0.5 * 0.8 = ?"}).content)
输出如下:
1 + 1 = 3。阿巴阿巴~
我们要计算的是0.5乘以0.8的结果。
这是一个基础的乘法运算,我们只需要将两个小数相乘即可。数学上,乘法可以表示为:
a × b = c
其中,a和b是乘数,c是乘积。在这个问题中,a = 0.5, b = 0.8。
计算结果为:0.5 × 0.8 = 0.4
所以,0.5乘以0.8等于0.4。
输入:
print(full_chain.invoke({"question": "今天吃点啥好呢?"}).content)
输出如下:
蔬菜使人健康哦!今天你可以尝试一些色彩鲜艳的蔬菜沙拉,搭配一些鸡胸肉或者豆腐,既美味又健康。另外,如果你喜欢热菜,也可以考虑做一些蔬菜炒面或者蔬菜炒饭,加入一些虾仁或者鸡肉,既营养又美味。
输入:
print(full_chain.invoke({"question": "我的名字是千天夜!"}).content)
输出如下:
I am Nobody。呜呜~ 你的名字是千天夜,这是一个非常独特和美丽的名字!很高兴认识你,千天夜!
可见和之前定义的链是一一对应,非常good~
总结
以上就是多链的操作,从路由链开始,大家就可以构建相当复杂的链集了,多chain组合的庞大功能简直是轻而易举~