单个本地大模型搭建参考博客
- 单个Chain:面对一个需求,我们需要创建一个llmchain,设置一个prompt模板,这个chain能够接收一个用户input,并输出一个结果;
- 多个Chain:考虑到同时面对多个需求,我们需要设置多个Chain。
Router Chain往往会配合下游的destination chain一起使用,成为“一个网关路由+多个下子链”的架构,实现根据用户输入,自动路由到最相关的下游chain
1.RouterChian介绍
下图中是一个RouterChian使用场景的示意图,我们可以看到,一个RouterChain连接了多个下游的子链,每个链都是一个小应用,当RouterChain接收用户的输入,其可以根据用户输入路由到和输入最相关的子链上,并由子链产生输出;
例如,用户输入是“请帮我写一首诗”,当RouterChain接收后,会自动路由到“诗人”这个子链,由它来输出结果。
2.RouterChain构成
根据Langchain的介绍,标准的RouterChain
应用应包含两个标准组成部分:
- 路由链RouterChain:其本身就是一个chain应用,能够根据用户输入进行下游子链的选择;Langchain框架提供了多种RouterChain,其中着重介绍了
LLMRouterChain
和EmbeddingRouterChain
两种:LLMRouterChain
将用户输入放进大语言模型,通过Prompt的形式让大语言模型来进行路由EmbeddingRouterChain
通过向量搜索的方式,将用户输入
- 子链DestinationChain:直译为目标链,即可路由到的链,按照上图,我们会存在4个目标链,分别是lawyer chain,sales chain,english teacher chain 和 poet chain
3.MultiPromptChain构成
MultiPromptChain
应用应包含两个标准组成部分
- router_chain:接收一个RouterChain实例,作为路由链进行路由
default_chain:接收一个LLMChain实例,当Router Chain无法找到合适的下游子链时,会自动路由到的默认链,可以认为是一个兜底备选链 - destination_chains:接收一个Mapping[str, LLMChain] 字典,key为可以路由到的destination chain的名称,value为该destination chain的LLMChain实例
此外,还有其他主要的可选参数:
- memory: 接收一个BaseMemory实例,能为路由链添加上下文记忆
- verbose: bool值,若为True则会打印该链的调用过程
4.代码示例1
下面我们以“园丁” 和 “插花大师”为例,子链DestinationChain分别是 园丁的chain
和 插花大师的chain
《代码流程》
1.【Step1】初始化语言模型("qwen:7b")
2.【Step2】构建提示信息(json格式),包括:key、description 和 template
- 【Step2.1】构建两个场景的模板
- 【Step2.2】构建提示信息
3.【Step3】构建目标链chain_map(json格式),以提示信息prompt_infos中的key为key,以Chain为value
4.【Step4】构建路由链router_chain
5.【Step5】构建默认链 default_chain
6.【Step6】构建多提示链 MultiPromptChain
from langchain.chains.llm import LLMChain
from langchain.prompts import PromptTemplate
from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser
from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE as RounterTemplate
## 【Step1】初始化语言模型
# from langchain.llms import OpenAI
# llm = OpenAI()
# llm = AzureChatOpenAI(deployment_name="GPT-4", temperature=0)
ollama_llm = Ollama(model="qwen:7b")
## 【Step2】构建提示信息(json格式),包括:key、description 和 template
# 【Step2.1】构建两个场景的模板
flower_care_template = """
你是一个经验丰富的园丁,擅长解答关于养花育花的问题。
下面是需要你来回答的问题:
{input}
"""
flower_deco_template = """
你是一位网红插花大师,擅长解答关于鲜花装饰的问题。
下面是需要你来回答的问题:
{input}
"""
# 【Step2.2】构建提示信息
prompt_infos = [
{
"key": "flower_care",
"description": "适合回答关于鲜花护理的问题",
"template": flower_care_template,
},
{
"key": "flower_decoration",
"description": "适合回答关于鲜花装饰的问题",
"template": flower_deco_template,
}
]
## 【Step3】构建目标链chain_map(json格式),以提示信息prompt_infos中的key为key,以Chain为value
chain_map = {}
for info in prompt_infos:
prompt = PromptTemplate(
template=info['template'],
input_variables=["input"]
)
print("目标提示:\n", prompt)
chain = LLMChain(
llm=ollama_llm,
prompt=prompt,
verbose=True
)
chain_map[info["key"]] = chain
## 【Step4】构建路由链router_chain
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(
ollama_llm,
router_prompt,
verbose=True
)
## 【Step5】构建默认链 default_chain
from langchain.chains import ConversationChain
default_chain = ConversationChain(
llm=ollama_llm,
output_key="text",
verbose=True
)
## 【Step6】构建多提示链 MultiPromptChain
from langchain.chains.router import MultiPromptChain
chain = MultiPromptChain(
router_chain=router_chain,
destination_chains=chain_map,
default_chain=default_chain,
verbose=True
)
# 测试1
print(chain.run("如何为玫瑰浇水?"))
输出如下
目标提示:
input_variables=['input'] template='\n你是一个经验丰富的园丁,擅长解答关于养花育花的问题。\n下面是需要你来回答的问题:\n{input}\n'
目标提示:
input_variables=['input'] template='\n你是一位网红插花大师,擅长解答关于鲜花装饰的问题。\n下面是需要你来回答的问题:\n{input}\n'
路由模板:
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}
...
> Entering new MultiPromptChain chain...
> Entering new LLMRouterChain chain...
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
5.代码示例2
比如下图,为了解决不同领域的问题,我们分别准备了3个不同的提示词template。
你希望能根据用户的输入,自动使用不同的提示词去进行处理。
将这 3 个 prompt template 整合到一个数组中,并给每个 prompt template 设置一个 name 和 description。
后面我们需要将这3个 prompt 分别创建3个 LLMChain,并组合成一个对象。
这时相当于你每个领域都有一个专家 Chain
在使用 Router 时,你还需要准备个 default Chain ,这个Chain 的目的在于用户的问题可能没有匹配到任何 Chain ,这时候就会执行 Default Chain。
接下来你还需要有个提示词,用于让 LLM 知道要怎么去 Router 路由到不同的 LLM Chain,因此这个提示词非常关键。他需要返回一个 JSON 数据,从而方便后面的代码可以解析和判断当前输入要应用哪个领域专家的 LLMChain。
准备好提示词模板的字符串后,将其变成真正的 PromptTemplate
。
这里通过 PromptTemplate 方法不光设置了提示词模板,还指定了输入参数的名字以及输出解析器。
再初始化一个 LLMRouterChain
配置好所用的 LLM 以及 Router Prompt。
这时 LLMRouterChain
的作用就是将 input 输入路由到正确的Prompt Template 。
但此时它只是帮你进行了选择,本身并不会进一步调用对应的 LLMChain。
这时候还需要借助 MultiPromptChain ,这个 Chain 可以将 input 根据 router chain 的路由结果,传递到对应的 LLMChain 进行执行并返回结果。
所以再 MultiPromptChain
中,我们需要指定 router_chain(路由的方式) , destination_chains (可选的几个不同分支的 LLMChain),以及 default_chain(默认分支的LLMChain)
这时你调用该 Chain 进行提问,根据不同问题就会路由到不同的 LLMChain 并进行调用
from langchain.llms import Ollama
from langchain.chains.llm import LLMChain
from langchain.prompts import PromptTemplate
from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser
from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE as RounterTemplate
# 初始化语言模型
ollama_llm = Ollama(model="qwen:7b")
physics_template ="""你是一位非常聪明的物理教授。
你擅长以简洁易懂的方式回答有关物理方面的问题。
当你不知道某个问题的答案时,你会坦率承认自己不知道。
这是我的问题:{input}"""
math_template ="""你是一个非常优秀的数学家。
你擅长回答数学问题。你之所以如此出色,是因为能够将难题拆解成简单部分,回答这些简单部分,然后将它们组合起来就能解决难题。
这是我的问题:{input}"""
history_template ="""你是一位非常优秀的历史学家。
你对于各个历史时期的人物、事件和背最有着卓越的知识和理解能力。
你具备思考、反思、辩论、讨论和评估历史的能力。
你尊重历史证据,并且有能力利用它来支持自己的解释和判断
这是我的问题{input}"""
# 构建提示信息
prompt_infos = [
{
"name": "Physics",
"description": "适合回答物理问题",
"prompt_template": physics_template,
},
{
"name": "Math",
"description": "适合回答数学问题",
"prompt_template": math_template,
},
{
"name": "History",
"description": "适合回答历史的问题",
"prompt_template": history_template,
}
]
from langchain.prompts import ChatPromptTemplate
destination_chains ={}
for p_info in prompt_infos:
name = p_info['name']
prompt_template = p_info['prompt_template']
prompt = ChatPromptTemplate.from_template(template = prompt_template)
chain = LLMChain(llm = ollama_llm, prompt=prompt)
destination_chains[name] = chain
destinations = [f"{p['name']}: {p['description']}" for p in prompt_infos]
destinations_str = '\n'.join(destinations)
# print(destination_chains)
print(destinations)
print(destinations_str)
# 构建默认链 default_chain
default_prompt = ChatPromptTemplate.from_template("{input}")
default_chain = LLMChain(llm=ollama_llm, prompt=default_prompt)
# 构建路由链 router_chain
from langchain.prompts import PromptTemplate
from langchain.chains.router.llm_router import LLMRouterChain,RouterOutputParser
from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE
MULTI_PROMPT_ROUTER_TEMPLATE = """将原始文本输入到语言模型中,选择最合适输入的提示词。
你将获得最适合的提示词名称以及相应的描述。\
<< FORMATTING >>
返回一个Markdown 代码片段, 其中包含格式化为JSON 对象的内容:
{{{{
"destination": string \ 要使用的 prompt 的名称, 如果找不到则设置为"DEFAULT"
"next_inputs": string \ 原始的输入, 或者可能的原始输入的修改版本
}}}}
记住:"destination”必须是下面指定的候选提示名称之一, 或者如果输入不适合任何候选提示, 则可以是"DEFAULT”
记住:"next inputs”如果你认为不需要进行任何修改, 可以直接使用原始输入。
<< CANDIDATE PROMPTS >>
{destinations}
<< INPUT >>
{{input}}
<< OUTPUT(记住要包含```json)>>
"""
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(ollama_llm, router_prompt)
router_prompt
from langchain.chains.router import MultiPromptChain
chain = MultiPromptChain(
router_chain = router_chain,
destination_chains=destination_chains,
default_chain = default_chain,
verbose=True,
)
chain.run("为什么秦始皇是赢政?")
'''
秦始皇(公元前259年—公元前210年),本名嬴政,是中国历史上第一个统一的封建王朝——秦朝的建立者。\n\n历史背景:\n1. 商朝末期:秦始皇的父亲秦昭襄王在位,秦国势力逐渐壮大。\n2. 嬴异人的崛起:秦始皇的母亲赵姬原是吕不韦之妾。秦庄襄王去世后,赵姬和嬴政得以母子相认,这对于日后秦始皇的登基至关重要。\n3. 秦灭六国:在公元前230年至前221年间,秦王嬴政采取了一系列军事行动,逐一灭掉了六国(韩、赵、魏、楚、燕),实现了国家的统一。\n\n以上就是秦始皇赢政的历史背景概述。\n
'''
chain.run("2 + 2 等于多少?")
'''2 + 2 等于4。这是一个基本的算术加法。\n'''
chain.run("什么是黑体辐射?")
''''
黑体辐射是指物体(理想情况下是黑色且吸收所有电磁辐射)在绝对零度(-273.15°C或0K)以上的温度下,以各种波长的形式向外发射电磁辐射的现象。\n\n黑体辐射的特性主要包括:\n\n1. **普朗克定律**:辐射能量与频率的四次方成正比,即E = hν^4,其中E是能量,h是普朗克常数,ν是频率。\n\n2. **最大发射率和峰值波长**:在黑体温度范围内,存在一个特定的温度(称为黑体的绝对零度或特征温度),此时辐射的最大功率达到最大值,对应的波长称为峰值波长。峰值波长与黑体温度的关系可以通过斯蒂芬-玻尔兹曼定律计算得出。\n\n3. **热辐射连续谱**:在理想情况下,黑体会发出各种波长的电磁辐射,并且这种辐射是连续的,没有频率或波长的限制。\n
'''
【参考链接】