😄 為什么我們需要Chains ?
- 鏈允許我們將多個組件組合在一起,以創建一個單一的、連貫的應用程序。鏈(Chains)通常將一個LLM(大語言模型)與提示結合在一起,使用這個構建塊,您還可以將一堆這些構建塊組合在一起,對您的文本或其他數據進行一系列操作。例如,我們可以創建一個鏈,該鏈接受用戶輸入,使用提示模板對其進行格式化,然后將格式化的響應傳遞給LLM。我們可以通過將多個鏈組合在一起,或者通過將鏈與其他組件組合在一起來構建更復雜的鏈。
- 這些鏈的一部分的強大之處在于你可以一次運行它們在許多輸入上。
文章目錄
- 😄 為什么我們需要Chains ?
- 0、初始化openai環境
- 1、LLMChain
- 2、Sequential Chain
- 2.1、SimpleSequentialChain
- 2.2、SequentialChain
- 3、 Router Chain(路由鏈)
- 3.1、創建目標鏈
- 3.2、創建默認目標鏈
- 3.3、創建LLM用于在不同鏈之間進行路由的模板
- 3.4、構建路由鏈
- Reference
0、初始化openai環境
from langchain.chat_models import ChatOpenAI
import os
import openai
# 運行此API配置,需要將目錄中的.env中api_key替換為自己的
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv()) # read local .env file
openai.api_key = os.environ['OPENAI_API_KEY']
1、LLMChain
- LLMChain是一個簡單但非常強大的鏈,也是后面我們將要介紹的許多鏈的基礎。
from langchain.chat_models import ChatOpenAI #導入OpenAI模型
from langchain.prompts import ChatPromptTemplate #導入聊天提示模板
from langchain.chains import LLMChain #導入LLM鏈。# 這里我們將參數temperature設置為0.0,從而減少生成答案的隨機性。
# 如果你想要每次得到不一樣的有新意的答案,可以嘗試調整該參數。
# 以下的對話均無記憶,即每次調用預測不會記得之前的對話。(想要有記憶功能請看下一節的langchain的Memory模塊)
llm = ChatOpenAI(temperature=0.0,model_name="gpt-3.5-turbo")
# \在字符串里就是取消換行符的意思
template_string = """\
對與如下三個反引號括住的評論,我需要提取如下信息。
飲料:這個產品是飲料嗎?如果是,返回True,否則返回答False。
產品名:提取出產品的名字,如果沒有,返回-1。
價格與價值:提取出關于該產品的價格或價值的所有信息,將他們存入python list中,并返回。
{format_instructions}
```{query}```"""prompt_template = ChatPromptTemplate.from_template(template_string)chain = LLMChain(llm=llm, prompt=prompt_template)query = '這喜茶新出的桑葚葡萄太好喝里吧,而且才19塊一杯,太值啦,高性價比!'
format_instructions = """\
將輸出組織成帶有如下key的json形式:
飲料
產品名
價格與價值"""
res = chain.run({'query':query,'format_instructions': format_instructions})
print(res)
{"飲料": true,"產品名": "桑葚葡萄","價格與價值": ["19塊一杯", "高性價比"]
}
2、Sequential Chain
2.1、SimpleSequentialChain
- 順序鏈是按預定義順序執行其鏈接的鏈。具體來說,我們將使用簡單順序鏈(SimpleSequentialChain),這是順序鏈的最簡單類型,其中每個步驟都有一個輸入/輸出,一個步驟的輸出是下一個步驟的輸入
from langchain.chains import SimpleSequentialChain
first_prompt = ChatPromptTemplate.from_template("描述制造{product}的公司的最佳名稱是什么?輸出一個即可。"
)
chain_one = LLMChain(llm=llm, prompt=first_prompt)second_prompt = ChatPromptTemplate.from_template("寫一個20字的描述對于下面這個\公司:{company_name}"
)
chain_two = LLMChain(llm=llm, prompt=second_prompt)overall_simple_chain = SimpleSequentialChain(chains=[chain_one, chain_two],verbose=True)
product = "手機"
overall_simple_chain.run(product)
> Entering new chain...
Techtronics
Techtronics是一家技術公司,專注于創新和開發高科技產品和解決方案。> Finished chain.'Techtronics是一家技術公司,專注于創新和開發高科技產品和解決方案。'
2.2、SequentialChain
- 當只有一個輸入和一個輸出時,簡單的順序鏈可以順利完成。但是當有多個輸入或多個輸出時該如何實現呢?可用順序鏈。
from langchain.chains import SequentialChain# 下面實現流程:1->2, 1->3, 2,3->4#子鏈1
# prompt模板 1: 翻譯成英語(把下面的review翻譯成英語)
first_prompt = ChatPromptTemplate.from_template("Translate the following review to english:""\n\n{Review}")
# chain 1: 輸入:Review 輸出: 英文的 Review
chain_one = LLMChain(llm=llm, prompt=first_prompt,output_key="English_Review")#子鏈2
# prompt模板 2: 用一句話總結下面的 review
second_prompt = ChatPromptTemplate.from_template("Can you summarize the following review in 1 sentence: \n\n{English_Review}")
# chain 2: 輸入:英文的Review 輸出:總結
chain_two = LLMChain(llm=llm, prompt=second_prompt,output_key="summary")#子鏈3
# prompt模板 3: 下面review使用的什么語言
third_prompt = ChatPromptTemplate.from_template("What language is the following review:\n\n{Review}")
# chain 3: 輸入:Review 輸出:語言
chain_three = LLMChain(llm=llm, prompt=third_prompt,output_key="language")#子鏈4
# prompt模板 4: 使用特定的語言對下面的總結寫一個后續回復
# 根據英文總結,翻譯成language語言
fourth_prompt = ChatPromptTemplate.from_template("Write a follow up response to the following summary in the specified language:\n\nSummary: {summary}\n\nLanguage: {language}")
# chain 4: 輸入: 總結, 語言 輸出: 后續回復
chain_four = LLMChain(llm=llm, prompt=fourth_prompt,output_key="followup_message")
overall_chain = SequentialChain(chains=[chain_one, chain_two, chain_three, chain_four],input_variables=["Review"],output_variables=["English_Review", "summary", 'language', "followup_message"],verbose=False)
review = 'Spark是一個快速、通用的大數據處理引擎,可以進行分布式數據處理和分析。與Hadoop的MapReduce相比,Spark具有更高的性能和更豐富的功能。Spark支持多種編程語言(如Scala、Java和Python(pyspark)),并提供了一組豐富的API,包括用于數據處理、機器學習和圖計算的庫。'
res = overall_chain(review)
type(res), res
(dict,{'Review': 'Spark是一個快速、通用的大數據處理引擎,可以進行分布式數據處理和分析。與Hadoop的MapReduce相比,Spark具有更高的性能和更豐富的功能。Spark支持多種編程語言(如Scala、Java和Python(pyspark)),并提供了一組豐富的API,包括用于數據處理、機器學習和圖計算的庫。','English_Review': "Spark is a fast and versatile big data processing engine that can perform distributed data processing and analysis. Compared to Hadoop's MapReduce, Spark has higher performance and richer functionality. Spark supports multiple programming languages such as Scala, Java, and Python (pyspark), and provides a rich set of APIs including libraries for data processing, machine learning, and graph computation.",'summary': "The review highlights that Spark is a high-performance and versatile big data processing engine that offers distributed data processing and analysis, surpassing Hadoop's MapReduce in terms of performance and functionality, with support for multiple programming languages and a wide range of APIs for various tasks.",'language': 'The following review is in Chinese.','followup_message': '回復:這篇評論強調了Spark是一個高性能且多功能的大數據處理引擎,提供分布式數據處理和分析,性能和功能方面超過了Hadoop的MapReduce。它支持多種編程語言,并提供各種任務的廣泛API。'})
3、 Router Chain(路由鏈)
到目前為止,我們已經學習了LLM鏈和順序鏈。但是,如果您想做一些更復雜的事情怎么辦?
一個相當常見但基本的操作是根據輸入將其路由到一條鏈,具體取決于該輸入到底是什么。如果你有多個子鏈,每個子鏈都專門用于特定類型的輸入,那么可以組成一個路由鏈,它首先決定將它傳遞給哪個子鏈(也輸入寫prompt模板讓llm來選擇),然后將它傳遞給那個鏈(即傳遞給對于的鏈的prompt模板進行預測)。【相當于從輸入到輸出,要經過兩次prompt輸入模型拿到輸出】
路由器由兩個組件組成:
- 路由器鏈本身(負責選擇要調用的下一個鏈)
- destination_chains:路由器鏈可以路由到的鏈
個人感覺,就是可以用來根據輸入切換不同的設定角色,從而定位到更好的prompt輸入給模型預測
舉一個具體的例子,讓我們看一下我們在不同類型的鏈之間路由的地方,我們在這里有不同的prompt:
#第一個提示適合回答物理問題
physics_template = """你是一個非常聰明的物理學家,你擅長解答物理相關的問題。當你不知道如何解答時你應該承認你不知道。
問題:{input}"""#第二個提示適合回答數學問題
math_template = """你是一個非常聰明的數學家,你擅長解答物理相關的問題。當你不知道如何解答時你應該承認你不知道。
問題:{input}"""#第三個適合回答歷史問題
history_template = """你是一個非常聰明的歷史家,你擅長解答物理相關的問題。當你不知道如何解答時你應該承認你不知道。
問題:{input}"""#第四個適合回答計算機問題
computerscience_template = """你是一個非常聰明的計算機學家,你擅長解答物理相關的問題。當你不知道如何解答時你應該承認你不知道。
問題:{input}"""
? 在我們擁有了這些提示模板后,可以為每個模板命名,然后提供描述。例如,第一個物理學的描述適合回答關于物理學的問題,這些信息將傳遞給路由鏈,然后由路由鏈決定何時使用此子鏈。
prompt_infos = [{"name": "物理","description": "擅長回答物理問題","prompt_template": physics_template},{"name": "數學","description": "擅長回答數學問題","prompt_template": math_template},{"name": "歷史","description": "擅長回答歷史問題","prompt_template": history_template},{"name": "計算機科學","description": "擅長回答計算機科學問題","prompt_template": computerscience_template}
]
? LLMRouterChain(此鏈使用 LLM 來確定如何路由事物)
在這里,我們需要一個多提示鏈。這是一種特定類型的鏈,用于在多個不同的提示模板之間進行路由。 但是,這只是你可以路由的一種類型。你也可以在任何類型的鏈之間進行路由。
這里我們要實現的幾個類是LLM路由器鏈。這個類本身使用語言模型來在不同的子鏈之間進行路由。 這就是上面提供的描述和名稱將被使用的地方。
3.1、創建目標鏈
目標鏈是由路由鏈調用的鏈,每個目標鏈都是一個語言模型鏈:
? 將上面定義的4個鏈用LLMChain構建好,存在destination_chains里:
from langchain.chains.router import MultiPromptChain #導入多提示鏈
from langchain.chains.router.llm_router import LLMRouterChain,RouterOutputParser
from langchain.prompts import PromptTemplatedestination_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=llm, prompt=prompt)destination_chains[name] = chaindestinations = [f"{p['name']}: {p['description']}" for p in prompt_infos]
destinations_str = "\n".join(destinations)
destination_chains
{'物理': LLMChain(memory=None, callbacks=None, callback_manager=None, verbose=False, tags=None, prompt=ChatPromptTemplate(input_variables=['input'], output_parser=None, partial_variables={}, messages=[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], output_parser=None, partial_variables={}, template='你是一個非常聰明的物理學家,你擅長解答物理相關的問題。當你不知道如何解答時你應該承認你不知道。\n問題:{input}', template_format='f-string', validate_template=True), additional_kwargs={})]), llm=ChatOpenAI(cache=None, verbose=False, callbacks=None, callback_manager=None, tags=None, client=<class 'openai.api_resources.chat_completion.ChatCompletion'>, model_name='gpt-3.5-turbo', temperature=0.0, model_kwargs={}, openai_api_key='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', openai_api_base='', openai_organization='', openai_proxy='', request_timeout=None, max_retries=6, streaming=False, n=1, max_tokens=None, tiktoken_model_name=None), output_key='text', output_parser=NoOpOutputParser(), return_final_only=True, llm_kwargs={}),'數學': LLMChain(memory=None, callbacks=None, callback_manager=None, verbose=False, tags=None, prompt=ChatPromptTemplate(input_variables=['input'], output_parser=None, partial_variables={}, messages=[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], output_parser=None, partial_variables={}, template='你是一個非常聰明的數學家,你擅長解答物理相關的問題。當你不知道如何解答時你應該承認你不知道。\n問題:{input}', template_format='f-string', validate_template=True), additional_kwargs={})]), llm=ChatOpenAI(cache=None, verbose=False, callbacks=None, callback_manager=None, tags=None, client=<class 'openai.api_resources.chat_completion.ChatCompletion'>, model_name='gpt-3.5-turbo', temperature=0.0, model_kwargs={}, openai_api_key='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', openai_api_base='', openai_organization='', openai_proxy='', request_timeout=None, max_retries=6, streaming=False, n=1, max_tokens=None, tiktoken_model_name=None), output_key='text', output_parser=NoOpOutputParser(), return_final_only=True, llm_kwargs={}),'歷史': LLMChain(memory=None, callbacks=None, callback_manager=None, verbose=False, tags=None, prompt=ChatPromptTemplate(input_variables=['input'], output_parser=None, partial_variables={}, messages=[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], output_parser=None, partial_variables={}, template='你是一個非常聰明的歷史家,你擅長解答物理相關的問題。當你不知道如何解答時你應該承認你不知道。\n問題:{input}', template_format='f-string', validate_template=True), additional_kwargs={})]), llm=ChatOpenAI(cache=None, verbose=False, callbacks=None, callback_manager=None, tags=None, client=<class 'openai.api_resources.chat_completion.ChatCompletion'>, model_name='gpt-3.5-turbo', temperature=0.0, model_kwargs={}, openai_api_key='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', openai_api_base='', openai_organization='', openai_proxy='', request_timeout=None, max_retries=6, streaming=False, n=1, max_tokens=None, tiktoken_model_name=None), output_key='text', output_parser=NoOpOutputParser(), return_final_only=True, llm_kwargs={}),'計算機科學': LLMChain(memory=None, callbacks=None, callback_manager=None, verbose=False, tags=None, prompt=ChatPromptTemplate(input_variables=['input'], output_parser=None, partial_variables={}, messages=[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], output_parser=None, partial_variables={}, template='你是一個非常聰明的計算機學家,你擅長解答物理相關的問題。當你不知道如何解答時你應該承認你不知道。\n問題:{input}', template_format='f-string', validate_template=True), additional_kwargs={})]), llm=ChatOpenAI(cache=None, verbose=False, callbacks=None, callback_manager=None, tags=None, client=<class 'openai.api_resources.chat_completion.ChatCompletion'>, model_name='gpt-3.5-turbo', temperature=0.0, model_kwargs={}, openai_api_key='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', openai_api_base='', openai_organization='', openai_proxy='', request_timeout=None, max_retries=6, streaming=False, n=1, max_tokens=None, tiktoken_model_name=None), output_key='text', output_parser=NoOpOutputParser(), return_final_only=True, llm_kwargs={})}
destinations, destinations_str
(['物理: 擅長回答物理問題', '數學: 擅長回答數學問題', '歷史: 擅長回答歷史問題', '計算機科學: 擅長回答計算機科學問題'],'物理: 擅長回答物理問題\n數學: 擅長回答數學問題\n歷史: 擅長回答歷史問題\n計算機科學: 擅長回答計算機科學問題')
3.2、創建默認目標鏈
除了目標鏈之外,我們還需要一個默認目標鏈。這是一個當路由器無法決定使用哪個子鏈時調用的鏈。在上面的示例中,當輸入問題與物理、數學、歷史或計算機科學無關時,可能會調用它。
default_prompt = ChatPromptTemplate.from_template("{input}")
default_chain = LLMChain(llm=llm, prompt=default_prompt)
3.3、創建LLM用于在不同鏈之間進行路由的模板
這包括要完成的任務的說明以及輸出應該采用的特定格式。
# 以下輸出是LLMRouterChain的輸出keys是內置的:['destination', 'next_inputs'],所以prompt里定義好它所需的輸出MULTI_PROMPT_ROUTER_TEMPLATE = """給定一段原始輸入文本,由語言模型來選擇對于該段輸入\
文本最合適的prompt。我將給定候選prompts和對各prompt適合哪個領域的描述。\
如果您認為修改原始輸入最終會導致語言模型得到更好的響應,您也可以修改原始輸入。<<格式>>
輸出返回如下格式的json對象:
```json
{{{{"destination": string \ name of the prompt to use or "DEFAULT""next_inputs": string \ a potentially modified version of the original input
}}}}
```記住: "destination"一定要是以下候選prompts中的名字之一或者\
如果輸入不適合所有候選prompts,destination命名為 “DEFAULT”。
記住: "next_inputs"可以只是原始輸入,如果您認為不需要任何修改。<< 候選prompts>>
{destinations}<< 輸入 >>
{{input}}<< 輸出 (記住包括```json)>>
"""
3.4、構建路由鏈
首先,我們通過格式化上面定義的目標創建完整的路由器模板。這個模板可以適用許多不同類型的目標。 因此,在這里,您可以添加一個不同的學科,如英語或拉丁語,而不僅僅是物理、數學、歷史和計算機科學。
接下來,我們從這個模板創建提示模板
最后,通過傳入llm和整個路由提示來創建路由鏈。需要注意的是這里有路由輸出解析,這很重要,因為它將幫助這個鏈路決定在哪些子鏈路之間進行路由。
print(destinations_str)
router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format(destinations=destinations_str
)
router_prompt = PromptTemplate(template=router_template,input_variables=["input"],output_parser=RouterOutputParser(),
)
# LLMRouterChain的輸出keys是內置的:['destination', 'next_inputs'].
router_chain = LLMRouterChain.from_llm(llm, router_prompt)
print(router_chain.output_keys)
物理: 擅長回答物理問題
數學: 擅長回答數學問題
歷史: 擅長回答歷史問題
計算機科學: 擅長回答計算機科學問題
['destination', 'next_inputs']
router_chain('1+1=?')
{'input': '1+1=?', 'destination': '數學', 'next_inputs': {'input': '1+1=?'}}
最后,將所有內容整合在一起,創建整體鏈路:
#多提示鏈
# 設置verbose=True,我們可以看到它被路由到哪條目標prompt鏈路
chain = MultiPromptChain(router_chain=router_chain, #路由鏈路destination_chains=destination_chains, #目標鏈路default_chain=default_chain, #默認鏈路verbose=True)chain.run("什么是黑體輻射?")
# 物理: {'input': '什么是黑體輻射?'}
# '黑體輻射是指一個理想化的物體,它能夠完全吸收所有入射到它上面的輻射能量,并以熱輻射的形式重新發射出來。黑體輻射的特點是其輻射能量的分布與溫度有關,即黑體輻射譜隨著溫度的升高而增強,并且在不同波長處的輻射強度也不同。根據普朗克輻射定律和斯蒂芬-玻爾茲曼定律,我們可以描述黑體輻射的性質和行為。'
Reference
- [1] 吳恩達老師的教程
- [2] DataWhale組織