任务设定
假设一个鲜花运营智能客服ChatBot通常会接到两大类问题。
- 鲜花养护(保持花的健康、如何浇水、施肥等)
- 鲜花装饰(如何搭配花、如何装饰场地等)
需求是,如果接到的是第一类问题,你要给ChatBot A指示;如果接到第二类的问题,你要给ChatBot B指示。
整体框架
RouterChain,也叫路由链,能动态选择用于给定输入的下一个链。我们会根据用户的问题内容,首先使用路由器链确定问题更适合哪个处理模板,然后将问题发送到该处理模板进行回答。如果问题不适合任何已定义的处理模板,它会被发送到默认链。
在这里,我们会用LLMRouterChain和MultiPromptChain(也是一种路由链)组合实现路由功能,该MultiPromptChain会调用LLMRouterChain选择与给定问题最相关的提示,然后使用该提示回答问题。
具体步骤如下:
- 构建处理模板:为鲜花护理和鲜花装饰分别定义两个字符串模板。
- 提示信息:使用一个列表来组织和存储这两个处理模板的关键信息,如模板的键、描述和实际内容。
- 初始化语言模型:导入并实例化语言模型。
- 构建目标链:根据提示信息中的每个模板构建了对应的LLMChain,并存储在一个字典中。
- 构建LLM路由链:这是决策的核心部分。首先,它根据提示信息构建了一个路由模板,然后使用这个模板创建了一个LLMRouterChain。
- 构建默认链:如果输入不适合任何已定义的处理模板,这个默认链会被触发。
- 构建多提示链:使用MultiPromptChain将LLM路由链、目标链和默认链组合在一起,形成一个完整的决策系统。
具体实现
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,
}]
2. 初始化语言模型
# 初始化语言模型
from langchain.llms import OpenAI
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI Key'
llm = OpenAI()
3. 构建目标链
# 构建目标链
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
输出
目标提示:
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
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)
输出
路由模板:
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 >>
路由提示: input_variables=['input'] output_parser=RouterOutputParser(default_destination='DEFAULT', next_inputs_type=<class 'str'>, next_inputs_inner_key='input') partial_variables={} 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 >>\n' template_format='f-string' validate_template=True
路由模版部分
一个指导性的说明,目的是引导语言模型正确处理用户的输入,并将其定向到适当的模型提示
路由提示的解释
路由提示 (router_prompt)则根据路由模板,生成了具体传递给LLM的路由提示信息。
- 其中input_variables 指定模板接收的输入变量名,这里只有
"input"。 - output_parser 是一个用于解析模型输出的对象,它有一个默认的目的地和一个指向下一输入的键。
- template 是实际的路由模板,用于给模型提供指示。这就是刚才详细解释的模板内容。
- template_format 指定模板的格式,这里是
"f-string"。 - validate_template 是一个布尔值,如果为 True,则会在使用模板前验证其有效性。
5. 构建默认链
# 构建默认链
from langchain.chains import ConversationChain
default_chain = ConversationChain(llm=llm,
output_key="text",
verbose=True)
如果路由链没有找到适合的链,那么,就以默认链进行处理。
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)
运行路由链
思考题
-
通过verbose=True这个选项的设定,在输出时显示了链的开始和结束日志,从而得到其相互调用流程。请你尝试把该选项设置为False,看一看输出结果有何不同。
如果把所有的verbose设定为False,则终端显示信息中所有以>开头的系统信息都不显示了。
-
在这个例子中,我们使用了ConversationChain作为default_chain,这个Chain是LLMChain的子类,你能否把这个Chain替换为LLMChain?
可以,但需要提供prompt,在本文中,最好是只提供一个template为’{input}’的prompt,这样才能够不影响模型回复文本时的角度,角色。
prompt=PromptTemplate.from_template('{input}') default_chain = LLMChain( prompt=prompt, llm=llm, output_key="text", verbose=verbose )