什么是链
LangChain 中通过 Chain
来链接各个组件和功能,模型之间彼此链接,或模型与其他组件链接
将多个组件相互链接,组合成一个链的想法简单但强大,简化了复杂应用程序的实现,使之更加模块化,能够创建出单一的、连贯的应用程序,从而使调试、维护和改进应用程序变得容易;这也是
LangChain 名称的由来
需要注意,在现版本中 LangChain 已经在主推 LangChain Expression
Language(LCEL),逐渐废弃旧版本中手动创建 Chain 的方式以及旧的 Chain
实现,不过这里也参考早期 Chain 的使用方式,LCEL 留在后面进行学习
链的使用主要是以下两个步骤:
通过设计好的接口,实现一个具体的链的功能;例如 LLM
链(LLMChain)能够接受用户输入,使用 PromptTemplate
对其进行格式化,然后将格式化的响应传递给 LLM;这就相当于把整个Model
I/O的流程封装到链里面
实现了链的具体功能之后,我们可以通过将多个链组合在一起,或者将链与其他组件组合来构建更复杂的链
链可以理解为对一组基础功能的封装,具有一个完整功能的组件,这么看来链其实可以被视为
LangChain 中的一种基本功能单元
LLMChain
LLMChain
围绕着语言模型推理功能又添加了一些功能,整合了PromptTemplate、语言模型(LLM
或聊天模型)和 Output Parser,相当于把 Model I/O
放在一个链中整体操作,它使用提示模板格式化输入,将格式化的字符串传递给
LLM,并返回 LLM 输出
举一个示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from langchain import PromptTemplatetemplate = "{name}的外号是?" prompt_temp = PromptTemplate.from_template(template) prompt = prompt_temp.format (name='霍金' ) print (prompt)import Azuremodel = Azure.chat_model result = model.predict(prompt) print (result)
此时 Model I/O
的实现分为两个部分,提示模板的构建和模型的调用独立处理,如果再加上输出解析器则依然需要独立调用
如果使用 LLMChain 则可以将这几部分结合在一起,使过程更加简洁
1 2 3 4 5 6 7 8 9 10 11 12 13 from langchain import PromptTemplate, OpenAI, LLMChaintemplate = "{name}的外号是?" llm = OpenAI(temperature=0 ) llm_chain = LLMChain( llm=Azure.chat_model, prompt=PromptTemplate.from_template(template)) result = llm_chain("马斯克" ) print (result)
1 {'name' : '马斯克' , 'text' : '马斯克的外号是“火箭人”或“铁人”。' }
Chain 的调用方式
在上面的例子中,直接使用 __call__
方法对 Chain
进行调用,例如 llm_chain("马斯克")
Chain 具有多种调用方式:
call :提示模板中包含多个变量时,可以使用字典一次性输入
run :等价于 __call__
predict :模板变量根据关键字参数赋值而不是字典
apply :针对输入列表运行链,一次处理多个输入
generate :类似于 apply
,只不过返回一个
LLMResult
对象而不是字符串 LLMResult
通常包含模型生成文本过程中的一些相关信息,例如令牌数量、模型名称等
SequentialChain
掌握了 LLMChain 的基本用法,就可以使用 SequentialChain 将几个
LLMChain 串起来,形成一个顺序链
第一个 LLMChain - 推荐电影
1 2 3 4 5 6 7 8 9 llm = Azure.chat_model template = """ 你是一个电影爱好者,请根据用户希望看到的剧情和标签,推荐一部电影。 剧情: {plot} 标签: {tag} 电影爱好者: 这是我推荐的电影:""" prompt_template = PromptTemplate(input_variables=["plot" , "tag" ], template=template) introduction_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="movie_info" )
第二个 LLMChain - 评论电影
1 2 3 4 5 6 7 8 9 llm = Azure.chat_model template = """ 你是一位电影评论家,给你一个电影信息,你需要为这部电影写一篇100字左右的评论。 电影信息: {movie_info} 电影评论人对上述电影的评论:""" prompt_template = PromptTemplate(input_variables=["movie_info" ], template=template) review_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="movie_review" )
第三个 LLMChain -
根据上面的产出写出一篇自媒体的文案
1 2 3 4 5 6 7 8 9 10 11 12 template = """ 你是一个评论电影的自媒体文案撰稿人。给定一部电影的介绍和评论,你需要为这部电影写一篇社交媒体的帖子,200字左右。 电影介绍: {movie_info} 电影评论人对上述花的评论: {movie_review} 社交媒体帖子: """ prompt_template = PromptTemplate(input_variables=["movie_info" , "movie_review" ], template=template) social_post_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="social_post_text" )
使用顺序链链接起来
1 2 3 4 5 6 7 8 9 10 overall_chain = SequentialChain( chains=[introduction_chain, review_chain, social_post_chain], input_variables=["plot" , "tag" ], output_variables=["movie_info" , "movie_review" , "social_post_text" ], verbose=True ) result = overall_chain({"plot" : "激烈的太空战斗" , "tag" : "中国、科幻" }) print (result)
1 2 3 4 > Entering new SequentialChain chain... > Finished chain. {'plot' : '激烈的太空战斗' , 'tag' : '中国、科幻' , 'movie_info' : '《流浪地球》\n\n《流浪地球》是一部中国科幻电影,它将带您进入一个激烈的太空战斗的世界。故事发生在未来,太阳即将灭亡,人类必须寻找新的家园。为了拯救地球,人类发起了一项大胆的计划,将地球变成了一个巨大的太空船,向外太空迁徙。然而,在这个危险的旅程中,他们面临着各种挑战和太空战斗。电影中充满了紧张刺激的场景和惊人的视觉效果,同时也展现了人类的勇气和团结精神。\n\n《流浪地球》是一部非常成功的中国科幻电影,它获得了广泛的赞誉和票房成功。它不仅仅是一部充满动作和太空战斗的电影,还探讨了人类对于未来和生存的思考。如果您喜欢激烈的太空战斗和科幻题材,这部电影将会是您的不错选择。' , 'movie_review' : '《流浪地球》是一部令人惊叹的中国科幻电影。它以壮观的视觉效果和扣人心弦的剧情吸引着观众。故事背景设定在太阳即将灭亡的未来,人类为了生存不得不将地球变成太空船,展开了一场惊险的迁徙之旅。电影中的太空战斗场景令人震撼,同时也展现了人类的勇气和团结精神。这部电影在票房上取得了巨大成功,同时也获得了广泛的赞誉。如果你喜欢科幻和动作片,那么《流浪地球》绝对是你不容错过的选择。' , 'social_post_text' : '大家好!今天我要给大家推荐一部非常精彩的电影,《流浪地球》!这是一部中国科幻电影,带领我们进入一个激烈的太空战斗的世界。\n\n故事发生在未来,太阳即将灭亡,人类为了生存必须寻找新的家园。他们发起了一项大胆的计划,将地球变成了一个巨大的太空船,向外太空迁徙。然而,在这个危险的旅程中,他们面临着各种挑战和太空战斗。\n\n这部电影充满了紧张刺激的场景和惊人的视觉效果,同时也展现了人类的勇气和团结精神。它不仅仅是一部充满动作和太空战斗的电影,还探讨了人类对于未来和生存的思考。\n\n《流浪地球》是一部非常成功的中国科幻电影,它获得了广泛的赞誉和票房成功。如果你喜欢激烈的太空战斗和科幻题材,这部电影将会是你的不错选择。\n\n快来看看这部令人惊叹的中国科幻电影,《流浪地球》,一定会让你大呼过瘾!#流浪地球 #科幻电影' }
使用不同的 variables 再试一次
1 2 result = overall_chain({"plot" : "剑与魔法的世界" , "tag" : "奇幻、冒险" }) print (result)
1 2 3 4 > Entering new SequentialChain chain... > Finished chain. {'plot' : '剑与魔法的世界' , 'tag' : '奇幻、冒险' , 'movie_info' : '《魔戒三部曲:指环王》。这是一部奇幻冒险电影系列,剧情发生在一个充满剑与魔法的世界中。故事讲述了一个拯救中土世界的冒险旅程,包括勇敢的英雄、邪恶的魔法师和各种神秘的生物。这部电影系列融合了精彩的剧情、惊人的特效和精美的场景,一定会让您沉浸在这个奇幻的世界中。' , 'movie_review' : '《魔戒三部曲:指环王》是一部令人沉浸的奇幻冒险电影系列。剧情发生在一个充满剑与魔法的世界中,其中包括了勇敢的英雄、邪恶的魔法师和各种神秘的生物。这部电影系列不仅拥有精彩的剧情,还有惊人的特效和精美的场景。观众们可以完全沉浸在这个奇幻的世界中,感受到其中的魔力。无论是对于魔戒系列的粉丝还是对于奇幻冒险电影的爱好者来说,这部电影都是一次不容错过的视觉盛宴。' , 'social_post_text' : '大家好!今天给大家推荐一部令人沉浸的奇幻冒险电影系列——《魔戒三部曲:指环王》!这部电影发生在一个充满剑与魔法的世界中,故事中有勇敢的英雄、邪恶的魔法师和各种神秘的生物。这部电影系列不仅有精彩的剧情,更有惊人的特效和精美的场景。你一定会完全沉浸在这个奇幻的世界中,感受到其中的魔力。无论你是魔戒系列的粉丝还是奇幻冒险电影的爱好者,这部电影都是一次不容错过的视觉盛宴。快来和我一起探索这个充满剑与魔法的世界吧!#魔戒指环王# #奇幻冒险电影#' }
上述示例通过了三个 LLMChain 和一个 SequentialChain
的结合,由推荐电影到最终写出电影文案的过程
进阶
这里使用极客时间的例子,制作一个简单的鲜花运营智能客服
假设这个鲜花运营智能客服通常会接到两大类问题:
鲜花养护 (保持花的健康、如何浇水、施肥等)
鲜花装饰 (如何搭配花、如何装饰场地等)
这就引入了新的需求, 如果接到的是第一类问题,需要给出 A
指示;如果接到第二类的问题,需要给出 B 指示 。
在实现的过程,可以根据这两个场景来构建两个不同的目标链;遇到不同类型的问题,LangChain
会通过 RouterChain 来自动引导大语言模型选择不同的模板
模板准备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 flower_care_template = """你是一个经验丰富的园丁,擅长解答关于养花育花的问题。 下面是需要你来回答的问题: {input}""" flower_deco_template = """你是一位网红插花大师,擅长解答关于鲜花装饰的问题。 下面是需要你来回答的问题: {input}""" prompt_infos = [ { "key" : "flower_care" , "description" : "适合回答关于鲜花护理的问题" , "template" : flower_care_template, }, { "key" : "flower_decoration" , "description" : "适合回答关于鲜花装饰的问题" , "template" : flower_deco_template, }] from langchain.chains.llm import LLMChainfrom langchain.prompts import PromptTemplatechain_map = {} for info in prompt_infos: prompt = PromptTemplate(template=info['template' ], input_variables=["input" ]) print ("目标提示:\n" , prompt) chain = LLMChain(llm=Azure.chat_model, prompt=prompt, verbose=True ) chain_map[info["key" ]] = chain
LLMRouterChain
RouterChain
路由链,能动态选择用于给定输入的下一个链,我们会根据用户的问题内容,首先使用路由器链确定问题更适合哪个处理模板,然后将问题发送到该处理模板进行回答。如果问题不适合任何已定义的处理模板,会被选择使用默认链
创建一个路由链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParserfrom langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE as RounterTemplatedestinations = [f"{p['key' ]} : {p['description' ]} " for p in prompt_infos] router_template = RounterTemplate.format (destinations="\n" .join(destinations)) print ("路由模板:\n" , router_template)print ("------------------------------------" )router_prompt = PromptTemplate( template=router_template, input_variables=["input" ], output_parser=RouterOutputParser(), ) print ("路由提示:\n" , router_prompt)router_chain = LLMRouterChain.from_llm(llm=Azure.chat_model, prompt=router_prompt, verbose=True )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 路由模板: Given a raw text input to a language model select the model prompt best suited for the input. You will be given the names of the available prompts and a description of what the prompt is best suited for . You may also revise the original input if you think that revising it will ultimately lead to a better response from the language model. << FORMATTING >> Return a markdown code snippet with a JSON object formatted to look like: ```json {{ "destination" : string \ name of the prompt to use or "DEFAULT" "next_inputs" : string \ a potentially modified version of the original input }} ``` REMEMBER: "destination" MUST be one of the candidate prompt names specified below OR it can be "DEFAULT" if the input is not well suited for any of the candidate prompts. REMEMBER: "next_inputs" can just be the original input if you don't think any modifications are needed. << CANDIDATE PROMPTS >> flower_care: 适合回答关于鲜花护理的问题 flower_decoration: 适合回答关于鲜花装饰的问题 << INPUT >> {input} << OUTPUT (must include ```json at the start of the response) >> << OUTPUT (must end with ```) >> 路由提示: input_variables=['input'] output_parser=RouterOutputParser() template='Given a raw text input to a language model select the model prompt best suited for the input. You will be given the names of the available prompts and a description of what the prompt is best suited for . You may also revise the original input if you think that revising it will ultimately lead to a better response from the language model.\n\n<< FORMATTING >>\nReturn a markdown code snippet with a JSON object formatted to look like:\n```json\n{{\n "destination" : string \\ name of the prompt to use or "DEFAULT" \n "next_inputs" : string \\ a potentially modified version of the original input\n}}\n```\n\nREMEMBER: "destination" MUST be one of the candidate prompt names specified below OR it can be "DEFAULT" if the input is not well suited for any of the candidate prompts.\nREMEMBER: "next_inputs" can just be the original input if you don\'t think any modifications are needed.\n\n<< CANDIDATE PROMPTS >>\nflower_care: 适合回答关于鲜花护理的问题\nflower_decoration: 适合回答关于鲜花装饰的问题\n\n<< INPUT >>\n{input}\n\n<< OUTPUT (must include ```json at the start of the response) >>\n<< OUTPUT (must end with ```) >>\n'
通过上面的示例可以看到路由链是如何进行路由的,如何引导 LLM
进行路由判断,以及后续的路由提示:
路由模板
引言 :简单的引导语句,提醒模型它将获得各种模型提示的名称和描述,以及可以更改原始输入以获得更好的响应
格式说明 (<< FORMATTING
>>):指导模型如何格式化其输出,使其以特定的方式返回结果(Markdown
代码片段,其中包含一个特定格式的 JSON 对象) 下面的代码块显示了期望的
JSON 结构,其中 destination
是模型选择的提示名称(或
DEFAULT
),而 next\_inputs
是可能被修订的原始输入
额外的说明和要求 :
提醒模型 destination
字段的值必须是下面列出的提示之一或是 DEFAULT
再次强调,除非模型认为有必要,否则原始输入不需要修改
候选提示 (<< CANDIDATE PROMPTS
>>):列出了两个示例模型提示及其描述
flower_care :适合回答关于鲜花护理的问题”,适合处理与花卉护理相关的问题。
flower_decoration :适合回答关于鲜花装饰的问题”,适合处理与花卉装饰相关的问题。
输入输出 :为模型提供了一个格式化的框架,其中它将接收一个名为
{input}
的输入,并在此后的部分输出结果
路由提示 :根据路由模板,生成了具体传递给 LLM
的路由提示信息
其中 input_variables
指定模板接收的输入变量名,这里只有
input
output_parser
是一个用于解析模型输出的对象,它有一个默认的目的地和一个指向下一输入的键
template
是实际的路由模板,用于给模型提供指示;这就是刚才详细解释的模板内容
template_format
指定模板的格式,这里是
f-string
validate_template
是一个布尔值,如果为
True,则会在使用模板前验证其有效性
概括路由链的逻辑,允许将用户的原始输入送入路由器,然后路由器会决定将该输入发送到哪个具体的模型提示,或者是否需要对输入进行修订以获得最佳的响应
默认链
准备一个默认链,如果路由链没有找到适合的链就以默认链进行处理
1 2 3 4 default_chain = ConversationChain(llm=Azure.chat_model, output_key="text" , verbose=True )
MultiPromptChain
最后我们使用 MultiPromptChain 把前几个链整合在一起实现路由功能
MultiPromptChain
是一个多路选择链,它使用一个路由链在多个提示之间进行选择
关键属性:
router_chain (类型
RouterChain
):这是用于决定目标链和其输入的链;当给定某个输入时,由
router_chain 决定哪一个 destination_chain
应该被选中,以及具体输入是什么
destination_chains (类型
Mapping[str, LLMChain]
):这是一个映射,将名称映射到可以将输入路由到的候选链。例如,你可能有多种处理文本输入的方法(或链),每种方法针对特定类型的问题;destination_chains
可以是这样一个字典:
{'weather': weather_chain, 'news': news_chain}
;weather_chain
可能专门处理与天气相关的问题,而 news_chain 处理与新闻相关的问题
default_chain (类型 LLMChain
):当
router_chain 无法将输入映射到 destination_chains
中的任何一个链时,LLMChain
将使用此默认链;这是一个备选方案,确保即使路由器不能决定正确的链也总有一个链可以处理输入
工作流程如下:
输入首先传递 router_chain
router_chain 根据某些标准或逻辑决定应该使用哪一个
destination_chain
输入随后被路由到选定的
destination_chain,该链进行处理并返回结果
如果 router_chain 不能决定正确的 destination_chain,则输入会被传递给
default_chain
如此 MultiPromptChain
就提供了一个在多个处理链之间动态路由输入的机制,以得到最相关或最优的输出
1 2 3 4 5 multi_prompt_chain = MultiPromptChain( router_chain=router_chain, destination_chains=chain_map, default_chain=default_chain, verbose=True )
执行
准备好了整个链条上的所有组件,概括一下:
模板和目标 LLMChain:不同路径的提示和基础链
LLMRouterChain:进行路由选择的链
默认链 ConversationChain(LLMChain
的实现,处理简单对话):如果没有对应路由,则使用默认链
MultiPromptChain:组合起上述三种类型的组件,成为调用的入口
那么针对创建出的 MultiPromptChain 进行调用
1 print (multi_prompt_chain.run("如何为向日葵浇水?" ))
通过 verboss=True
可以观察到过程日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 > Entering new MultiPromptChain chain... > Entering new LLMRouterChain chain... > Finished chain. flower_care: {'input' : '如何为向日葵浇水?' } > Entering new LLMChain chain... Prompt after formatting: 你是一个经验丰富的园丁,擅长解答关于养花育花的问题。 下面是需要你来回答的问题: 如何为向日葵浇水? > Finished chain. > Finished chain.
可以看到路由链输出了
flower_care: {'input': '如何为向日葵浇水?'}
,多路选择链根据路由返回使用了养花育花的模板
使用默认链测试一下
1 print (multi_prompt_chain.run("可乐鸡翅的做法" ))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 > Entering new MultiPromptChain chain... > Entering new LLMRouterChain chain... > Finished chain. None : {'input' : '可乐鸡翅的做法' }> Entering new ConversationChain chain... Prompt after formatting: The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know. Current conversation: Human: 可乐鸡翅的做法 AI: > Finished chain. > Finished chain.
可以看到路由链输出了
None: {'input': '可乐鸡翅的做法'}
,并且在后续处理的选择中使用了默认提供的
ConversationChain
参考
LangChain
实战课_LangChain_AI_大模型_语言模型_ChatGPT_prompt_接口_提示工程_模型_开发-极客时间
(geekbang.org)