青训营学习收获8
学习实践:用RouterChain确定客户意图(想学“育花”还是“插花”?)
之前的课学习了Chain的基本概念,并使用了LLMChain和SequentialChain。本次学习其他类型的Chain,特别是使用RouterChain来自动引导大语言模型选择不同的处理模板,以确定客户意图。
1、任务设定
假设我们的鲜花运营智能客服ChatBot通常会接到两大类问题:
- 鲜花养护:保持花的健康、如何浇水、施肥等。
- 鲜花装饰:如何搭配花、如何装饰场地等。
我们的任务是:如果接到第一类问题,指示ChatBot A;如果是第二类问题,指示ChatBot B。通过RouterChain,LangChain可以自动选择适合的模板。
可以根据这两个场景来构建两个不同的目标链。当遇到不同类型的问题,LangChain会通过RouterChain来自动引导大语言模型选择不同的模板。
2、整体框架
RouterChain(路由链),能动态选择用于给定输入的下一个链。我们会根据用户的问题内容,首先使用路由器链确定问题更适合哪个处理模板,然后将问题发送到该处理模板进行回答。如果问题不适合任何已定义的处理模板,它会被发送到默认链。本次学习会使用LLMRouterChain和MultiPromptChain(也是一种路由链)组合实现路由功能,该MultiPromptChain会调用LLMRouterChain选择与给定问题最相关的提示,然后使用该提示回答问题。具体步骤如下:
- 构建处理模板:为鲜花护理和鲜花装饰分别定义两个字符串模板。
- 提示信息:组织和存储模板的关键信息。使用列表来组织和存储这两个处理模板的关键信息,如模板的键、描述和实际内容。
- 初始化语言模型:导入并实例化语言模型。
- 构建目标链:为每个模板构建LLMChain。根据提示信息中的每个模板构建对应的LLMChain,并存储在一个字典中。
- 构建LLM路由链:根据提示信息构建路由模板,这是决策的核心部分。首先,它根据提示信息构建了一个路由模板,然后使用这个模板创建了一个LLMRouterChain。
- 构建默认链:处理不适合任何模板的输入。如果输入不适合任何已定义的处理模板,这个默认链会被触发。
- 构建多提示链:将所有链组合成一个完整系统。使用MultiPromptChain将LLM路由链、目标链和默认链组合在一起,形成一个完整的决策系统。
3. 具体实现
3.1 构建提示信息的模板
首先针对两种场景,构建两个提示信息的模板。
# 构建两个场景的模板
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,
}]
代码定义了两个不同场景的模板和相应的提示信息。首先,两个字符串模板分别用于模拟两种角色:一个是经验丰富的园丁,专注于解答养花相关问题;另一个是网红插花大师,专注于鲜花装饰问题。每个模板包含一个占位符 {input},用于插入具体的问题。
接着代码创建了一个列表 prompt_infos,其中包含两个字典。每个字典对应一个场景,包含三个键值对:key 用于标识场景,description 提供场景的简要描述,template 包含对应的字符串模板。这种结构便于根据不同需求选择合适的模板来生成回答。
3.2 初始化语言模型
接下来初始化语言模型。
# 初始化语言模型
from langchain.llms import OpenAI
import os
os.environ["OPENAI_API_KEY"] = 'OpenAI Key'
llm = OpenAI()
初始化一个 OpenAI 语言模型。首先导入了 OpenAI 类,并通过设置环境变量 OPENAI_API_KEY(需填入 API 密钥)来配置访问权限。然后实例化 OpenAI() 创建了一个语言模型对象 llm,用于生成文本或回答问题。
3.3 构建目标链
下面,循环prompt_infos这个列表,构建出两个目标链,分别负责处理不同的问题。
# 构建目标链
from langchain.chains.llm import LLMChain
from langchain.prompts import PromptTemplate
chain_map = {}
for info in prompt_infos:
prompt = PromptTemplate(template=info['template'],
input_variables=["input"])
print("目标提示:\n",prompt)
chain = LLMChain(llm=llm, prompt=prompt,verbose=True)
chain_map[info["key"]] = chain
这段代码用于构建一个目标链,每个链由一个提示模板和语言模型组成。首先,从 langchain 库中导入所需的类。接着,代码遍历 prompt_infos 列表,其中包含多个提示信息。对于每个提示信息,创建一个 PromptTemplate 对象,使用该信息中的模板,并指定输入变量为 "input"。然后,使用该提示模板和语言模型 llm 创建一个 LLMChain 对象,并将其存储在 chain_map 字典中,以 info["key"] 作为键。这个过程为每个提示信息构建了一个链,并将其映射到一个唯一的键。
目标链提示是这样的:
目标提示:
input_variables=['input']
output_parser=None partial_variables={}
template='你是一个经验丰富的园丁,擅长解答关于养花育花的问题。\n 下面是需要你来回答的问题:\n
{input}' template_format='f-string'
validate_template=True
目标提示:
input_variables=['input']
output_parser=None partial_variables={}
template='你是一位网红插花大师,擅长解答关于鲜花装饰的问题。\n 下面是需要你来回答的问题:\n
{input}' template_format='f-string'
validate_template=True
对于每个场景创建一个 LLMChain(语言模型链)。每个链会根据其场景模板生成对应的提示,然后将这个提示送入语言模型获取答案。
3.4 构建路由链
下面,构建路由链,负责查看用户输入的问题,确定问题的类型。
# 构建路由链
from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser
from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE as RounterTemplate
destinations = [f"{p['key']}: {p['description']}" for p in prompt_infos]
router_template = RounterTemplate.format(destinations="\n".join(destinations))
print("路由模板:\n",router_template)
router_prompt = PromptTemplate(
template=router_template,
input_variables=["input"],
output_parser=RouterOutputParser(),)
print("路由提示:\n",router_prompt)
router_chain = LLMRouterChain.from_llm(llm,
router_prompt,
verbose=True)
这段代码用于构建一个路由链,利用语言模型来选择合适的提示链。首先,从 langchain 库中导入相关的类。然后,代码从 prompt_infos 列表中提取每个提示的键和值,并将它们格式化为字符串列表 destinations。接着,使用 MULTI_PROMPT_ROUTER_TEMPLATE 模板,将这些目的地插入模板中,生成 router_template 字符串。
然后创建一个 PromptTemplate 对象 router_prompt,它使用生成的路由模板,并指定输入变量为 "input",同时使用 RouterOutputParser 解析输出。最后,使用 router_prompt 和语言模型 llm 创建一个 LLMRouterChain 对象 router_chain,用于根据输入动态选择合适的提示链,并设置为详细模式以输出调试信息。
输出如下:
进一步说明:路由器链是如何构造提示信息,来引导大模型查看用户输入的问题并确定问题的类型的?
在路由模板部分,这段模板字符串是一个指导性的说明,目的是引导语言模型正确处理用户的输入,并将其定向到适当的模型提示。
(1)路由模板的解释
路由模板,是路由功能得以实现的核心。详细分解一下这个模板的每个部分:
引言
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:
表示模型的输出应该是一个 Markdown 代码片段,其中包含一个特定格式的 JSON 对象。
下面的代码块显示了期望的 JSON 结构,其中 destination 是模型选择的提示名称(或“DEFAULT”),而 next_inputs 是可能被修订的原始输入。
额外的说明和要求
REMEMBER: "destination" MUST be one of the candidate prompt names specified below OR it can be "DEFAULT"...
这是一个重要的指导,提醒模型 "destination" 字段的值必须是下面列出的提示之一或是 “DEFAULT”。
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 >>\n{input}\n\n<< OUTPUT >>\n:
这部分为模型提供了一个格式化的框架,其中它将接收一个名为 {input} 的输入,并在此后的部分输出结果。
总的来说,这个模板的目的是让模型知道如何处理用户的输入,并根据提供的提示列表选择一个最佳的模型提示来回应。
(2)路由提示的解释
路由提示 (router_prompt)则根据路由模板,生成了具体传递给LLM的路由提示信息。
- 其中input_variables 指定模板接收的输入变量名,这里只有
"input"。 - output_parser 是一个用于解析模型输出的对象,它有一个默认的目的地和一个指向下一输入的键。
- template 是实际的路由模板,用于给模型提供指示。就是刚才详细解释的模板内容。
- template_format 指定模板的格式,这里是
"f-string"。 - validate_template 是一个布尔值,如果为 True,则会在使用模板前验证其有效性。
总的来说,这个构造允许将用户的原始输入送入路由器,然后路由器会决定将该输入发送到哪个具体的模型提示,或者是否需要对输入进行修改以获得最佳的响应。
3.5 构建默认链
除了处理目标链和路由链之外,还需要准备一个默认链。如果路由链没有找到适合的链,就以默认链进行处理。
# 构建默认链
from langchain.chains import ConversationChain
default_chain = ConversationChain(llm=llm,
output_key="text",
verbose=True)
构建了一个默认链,旨在处理没有特定路由的输入。首先从 langchain 库中导入 ConversationChain 类。然后使用语言模型 llm 创建一个 ConversationChain 对象 default_chain。该对象的输出键设置为 "text",并启用详细模式以输出调试信息。这个默认链在没有匹配到特定提示链时,提供一个通用的对话处理方式。
3.6 构建多提示链
最后使用MultiPromptChain类把前几个链整合在一起,实现路由功能。这个MultiPromptChain类是一个多路选择链,它使用一个LLM路由器链在多个提示之间进行选择。
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就为我们提供了一个在多个处理链之间动态路由输入的机制,以得到最相关或最优的输出。
# 构建多提示链
from langchain.chains.router import MultiPromptChain
chain = MultiPromptChain(
router_chain=router_chain,
destination_chains=chain_map,
default_chain=default_chain,
verbose=True)
4、运行路由链
设计三个测试,看看是否分别路由到不同的目标链。
测试A:print(chain.run("如何为玫瑰浇水?"))
测试B:print(chain.run("如何为婚礼场地装饰花朵?"))
测试C:print(chain.run("如何考入哈佛大学?"))
从结果来看,这三个测试分别路由到了不同的目标链,其中第三个问题被路由到默认链进行处理。
5、总结思考
在这次学习实践的示例中,使用了LLMRouterChain和MultiPromptChain。其中,LLMRouterChain继承自RouterChain;而MultiPromptChain则继承自MultiRouteChain。整体上,通过MultiPromptChain将其他链组织起来,完成了路由功能:
chain = MultiPromptChain(
router_chain=router_chain,
destination_chains=chain_map,
default_chain=default_chain,
verbose=True)
对于课程中的思考题
- 通过verbose=True这个选项的设定,在输出时显示了链的开始和结束日志,从而得到其相互调用流程。请你尝试把该选项设置为False,看一看输出结果有何不同。
当将
verbose设置为False时,输出结果将不再显示链的开始和结束日志。这意味着在运行链时,你将看不到详细的调试信息,只会看到最终的输出结果。这种设置适合在生产环境中使用,以减少不必要的日志输出,从而简化信息流。不同之处:调试信息:不再显示每个链的调用过程,包括输入、输出和选择的链。输出简洁:只显示最终结果,适合在不需要调试的情况下使用。
- 在这个例子中,我们使用了ConversationChain作为default_chain,这个Chain是LLMChain的子类,你能否把这个Chain替换为LLMChain?
可以将
ConversationChain替换为LLMChain。需要确保为LLMChain提供一个合适的提示模板,以处理未路由的问题,即需要确保LLMChain能够处理所有可能的输入。例如:from langchain.chains.llm import LLMChain from langchain.prompts import PromptTemplate default_prompt = PromptTemplate(template="请问有什么可以帮助您的吗?{input}", input_variables=["input"]) default_chain = LLMChain(llm=llm, prompt=default_prompt, verbose=False)这样替换后,
LLMChain将用于处理默认情况下的问题,但可能缺乏ConversationChain的对话上下文管理功能。
其他想法
- 自动化决策的优势
通过RouterChain,能够实现自动化的决策流程,将不同类型的问题精准地路由到合适的处理模块。这种自动化不仅提高了效率,还减少了人为错误的可能性。在实际应用中,这种机制可以被扩展到更多领域,例如客户服务、在线教育和医疗咨询等场景。
- 模块化设计的灵活性
使用MultiPromptChain的模块化设计,使得系统具备了极大的灵活性和可扩展性。未来可以轻松地添加新的处理链,或调整现有链的逻辑,而无需大规模重构系统。这种设计理念也体现了现代软件工程中的微服务架构思想,能够有效应对复杂业务需求的变化。
- 用户体验的提升
通过更精准的意图识别和问题分类,用户能够获得更及时和相关的响应。这不仅提升了用户体验,也增强了用户对系统的信任和依赖。尤其是在客户服务领域,快速且准确的响应是提升客户满意度的关键。
- 挑战与未来发展
尽管RouterChain和MultiPromptChain提供了强大的功能,但在实际应用中也面临挑战。例如,如何处理模糊或多义性的问题,如何在不确定的情况下选择最优链,都是需要进一步研究和优化的方向。此外,随着模型和数据规模的增长,如何保证系统的性能和响应速度也是一个重要问题。