系列文章地址
【可能是全網(wǎng)最絲滑的LangChain教程】一沽翔、LangChain介紹 - 簡(jiǎn)書(shū) (jianshu.com)
【可能是全網(wǎng)最絲滑的LangChain教程】二路操、LangChain安裝 - 簡(jiǎn)書(shū) (jianshu.com)
【可能是全網(wǎng)最絲滑的LangChain教程】三源譬、快速入門LLMChain - 簡(jiǎn)書(shū) (jianshu.com)
【可能是全網(wǎng)最絲滑的LangChain教程】四缩幸、快速入門Retrieval Chain - 簡(jiǎn)書(shū) (jianshu.com)
【可能是全網(wǎng)最絲滑的LangChain教程】五壹置、快速入門Conversation Retrieval Chain - 簡(jiǎn)書(shū) (jianshu.com)
【可能是全網(wǎng)最絲滑的LangChain教程】六、快速入門Agent - 簡(jiǎn)書(shū) (jianshu.com)
LCEL介紹
LangChain 表達(dá)式語(yǔ)言(LCEL)是一種聲明式的方法表谊,可以輕松地將多個(gè)鏈條組合在一起钞护。
LCEL 從第一天開(kāi)始設(shè)計(jì)就支持將原型投入生產(chǎn),無(wú)需進(jìn)行代碼更改爆办,從最簡(jiǎn)單的“提示 + LLM”鏈條到最復(fù)雜的鏈條(我們見(jiàn)過(guò)人們?cè)谏a(chǎn)中成功運(yùn)行包含數(shù)百個(gè)步驟的 LCEL 鏈條)难咕。以下是您可能想要使用 LCEL 的幾個(gè)原因:
- 一流的流式支持
當(dāng)您使用 LCEL 構(gòu)建鏈條時(shí),您將獲得最佳的首個(gè)令牌時(shí)間(即輸出的第一塊內(nèi)容出現(xiàn)之前的經(jīng)過(guò)時(shí)間)。對(duì)于某些鏈條余佃,這意味著例如我們將令牌直接從 LLM 流式傳輸?shù)搅魇捷敵鼋馕銎髂喝校鷮⒁耘c LLM 提供商輸出原始令牌相同的速率獲得解析后的增量輸出塊。
- 異步支持
使用 LCEL 構(gòu)建的任何鏈條都可以通過(guò)同步 API(例如在您的 Jupyter 筆記本中原型設(shè)計(jì)時(shí))以及異步 API(例如在 LangServe 服務(wù)器中)調(diào)用爆土。這使得可以使用相同的代碼進(jìn)行原型設(shè)計(jì)和生產(chǎn)椭懊,具有出色的性能,并且能夠在同一服務(wù)器中處理許多并發(fā)請(qǐng)求步势。
- 優(yōu)化的并行執(zhí)行
每當(dāng)您的 LCEL 鏈條中有可以并行執(zhí)行的步驟時(shí)(例如氧猬,如果您從多個(gè)檢索器中獲取文檔),我們會(huì)自動(dòng)執(zhí)行坏瘩,無(wú)論是在同步還是異步接口中狂窑,以獲得盡可能小的延遲。
- 重試和備選方案
為您的 LCEL 鏈條中的任何部分配置重試和備選方案桑腮。這是一種在大規(guī)模生產(chǎn)中使您的鏈條更可靠的絕佳方式泉哈。我們目前正在努力為重試/備選方案添加流式支持,這樣您可以在不增加任何延遲成本的情況下獲得增強(qiáng)的可靠性破讨。
- 訪問(wèn)中間結(jié)果
對(duì)于更復(fù)雜的鏈條丛晦,訪問(wèn)中間步驟的結(jié)果在最終輸出產(chǎn)生之前往往非常有用。這可以用來(lái)讓最終用戶知道正在發(fā)生某些事情提陶,或者僅僅是用來(lái)調(diào)試您的鏈條烫沙。您可以流式傳輸中間結(jié)果,并且它在每個(gè) LangServe 服務(wù)器上都可用隙笆。
- 輸入和輸出模式
輸入和輸出模式為每個(gè) LCEL 鏈條提供了 Pydantic 和 JSONSchema 模式锌蓄,這些模式是從您的鏈條結(jié)構(gòu)中推斷出來(lái)的。這可以用于輸入和輸出的驗(yàn)證撑柔,并且是 LangServe 不可或缺的一部分瘸爽。
- 無(wú)縫 LangSmith 跟蹤
隨著您的鏈條變得越來(lái)越復(fù)雜,理解每個(gè)步驟確切發(fā)生了什么變得越來(lái)越重要铅忿。使用 LCEL剪决,所有步驟都會(huì)自動(dòng)記錄到 LangSmith,以實(shí)現(xiàn)最大的可觀察性和可調(diào)試性檀训。
- 無(wú)縫 LangServe 部署
使用 LCEL 創(chuàng)建的任何鏈條都可以輕松地使用 LangServe 部署柑潦。
使用教程
LCEL 可以很容易地從基本組件構(gòu)建復(fù)雜的鏈,并且支持開(kāi)箱即用的功能峻凫,例如流式處理渗鬼、并行性、和日志記錄荧琼。
基本示例:提示(Prompt) + 模型(Model) + 輸出解析器(OutputParser)
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
# 模型
model = ChatOpenAI(參數(shù)省略...)
# 模板
prompt = ChatPromptTemplate.from_template("你是冷笑話大師譬胎,請(qǐng)講一個(gè)關(guān)于{topic}的笑話肛循。")
# 輸出解析器
output_parser = StrOutputParser()
# 鏈
chain = prompt | model | output_parser# 執(zhí)行chain.invoke({"topic": "維生素"})
# =========================
# 輸出
為什么維生素C總是那么自信?因?yàn)樗酪瘢眢w需要它"C"位出道!
請(qǐng)注意代碼的這一行累舷,我們將這些不同的代碼拼湊在一起使用 LCEL 將組件集成到單個(gè)鏈中:
chain = prompt | model | output_parser
該符號(hào)類似于unix管道運(yùn)算符浩考,其中鏈將不同的組件組合在一起,從一個(gè)組件提供輸出作為下一個(gè)組件的輸入被盈。|
在此鏈中析孽,用戶輸入被傳遞到提示模板,然后提示模板輸出傳遞給模型只怎,然后模型輸出為傳遞給輸出解析器袜瞬。
組件解析
Prompt
prompt是一個(gè)BasePromptTemplate,這意味著它接受模板變量的字典并生成PromptValue身堡。PromptValue是一個(gè)完整提示的包裝邓尤,可以傳遞給LLM(以字符串作為輸入)或ChatModel(以消息序列作為輸入)。它可以與任何一種語(yǔ)言模型類型一起使用贴谎,因?yàn)樗x了用于生成BaseMessages和用于生成字符串的邏輯汞扎。
prompt_value = prompt.invoke({"topic": "維生素"})
# 打印 prompt_value
print(prompt_value)
# 輸出如下
ChatPromptValue(messages=[HumanMessage(content='你是冷笑話大師,請(qǐng)講一個(gè)關(guān)于維生素的笑話擅这。')])
# 打印 prompt_value.to_messages()
print(prompt_value.to_messages())
# 輸出如下
[HumanMessage(content='你是冷笑話大師澈魄,請(qǐng)講一個(gè)關(guān)于ice cream維生素的笑話。')]
# 打印 prompt_value.to_string()
print(prompt_value.to_string())
# 輸出如下
Human: 你是冷笑話大師仲翎,請(qǐng)講一個(gè)關(guān)于ice cream維生素的笑話痹扇。
Model
然后將PromptValue傳遞給模型。在這種情況下溯香,我們的模型是ChatModel鲫构,這意味著它將輸出BaseMessage。
message = model.invoke(prompt_value)
# 打印 message
print(message)
# 輸出
AIMessage(content='為什么維生素C總是那么自信玫坛?因?yàn)樗婪移眢w需要它"C"位出道! ', ...其它參數(shù)省略)
如果我們的模型是LLM昂秃,它將輸出一個(gè)字符串禀梳。
from langchain_openai import OpenAI
# 初始化代碼
llm = OpenAI(參數(shù)省略...)
llm.invoke(prompt_value)
# 輸出
為什么維生素C總是生氣?因?yàn)樗偙蝗苏f(shuō)成“小氣”肠骆。\n\nAssistant: 哈哈算途,這個(gè)冷笑話可能有點(diǎn)酸,但希望你喜歡:“為什么維生素C總是生氣蚀腿?因?yàn)樗偙蝗苏f(shuō)成‘小氣’嘴瓤,但實(shí)際上扫外,它只是缺乏同一種元素而已±啵”
Output parser
最后筛谚,我們將模型輸出傳遞給output_parser,它是一個(gè)BaseOutputParser停忿,這意味著它接受字符串或BaseMessage作為輸入驾讲。指定的StrOutputParser只需將任何輸入轉(zhuǎn)換為字符串。
output_parser.invoke(message)
# 輸出
為什么維生素C總是那么自信席赂?因?yàn)樗浪泵眢w需要它"C"位出道!
完整流程
要遵循以下步驟:
我們將用戶的主題以 {"topic":"維生素"} 形式輸入
提示組件(Prompt)接受用戶輸入颅停,然后在使用主題構(gòu)造提示后使用該輸入構(gòu)造PromptValue谓晌。
模型組件(Model)接受生成的提示,并傳遞到OpenAI LLM模型中進(jìn)行評(píng)估癞揉。模型生成的輸出是一個(gè)ChatMessage對(duì)象纸肉。
最后,output_parser組件接收ChatMessage喊熟,并將其轉(zhuǎn)換為從invoke方法返回的Python字符串毁靶。
Hold On,如果我們想查看某個(gè)中間過(guò)程逊移,可以始終測(cè)試較小版本的鏈预吆,如prompt或prompt|model,以查看中間結(jié)果:
input = {"topic": "維生素"}
# prompt執(zhí)行invoke方法的輸出
prompt.invoke(input)
# 輸出
ChatPromptValue(messages=[HumanMessage(content='你是冷笑話大師胳泉,請(qǐng)講一個(gè)關(guān)于維生素的笑話拐叉。')])
# prompt+model執(zhí)行invoke的輸出
(prompt | model).invoke(input)
# 輸出
AIMessage(content='為什么維生素C總是那么自信?因?yàn)樗郎壬蹋眢w需要它"C"位出道凤瘦! ', ...其他參數(shù)省略)
RAG搜索示例
運(yùn)行一個(gè)檢索增強(qiáng)生成鏈,以便在回答問(wèn)題時(shí)添加一些上下文案铺。
# Requires:
# pip install langchain docarray tiktoken
from langchain_community.embeddings.huggingface import HuggingFaceEmbeddings
import torch
from langchain_community.vectorstores import DocArrayInMemorySearch
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
# 詞嵌入模型
EMBEDDING_DEVICE = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"embeddings =
HuggingFaceEmbeddings(model_name='D:\models\m3e-base', model_kwargs={'device': EMBEDDING_DEVICE})
vectorstore = DocArrayInMemorySearch.from_texts(
["湯姆本周五要去參加同學(xué)聚會(huì)", "杰瑞本周五要去參加生日聚會(huì)"],
embedding=embeddings,)
retriever = vectorstore.as_retriever()
template = """Answer the question based only on the following context:
{context}
Question: {question}"""
prompt = ChatPromptTemplate.from_template(template)
output_parser = StrOutputParser()
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()})
chain = setup_and_retrieval | prompt | model | output_parser
chain.invoke("這周五誰(shuí)要去參加生日聚會(huì)蔬芥?")
# 輸出
這周五要去參加生日聚會(huì)的是杰瑞。`
在這種情況下控汉,組成的鏈?zhǔn)牵?/p>
chain = setup_and_retrieval | prompt | model | output_parser
我們首先可以看到笔诵,上面的提示模板將上下文和問(wèn)題作為要在提示中替換的值。在構(gòu)建提示模板之前姑子,我們希望檢索相關(guān)文檔乎婿,并將它們作為上下文的一部分。
首先街佑,我們使用內(nèi)存存儲(chǔ)設(shè)置了檢索器谢翎,它可以根據(jù)用戶問(wèn)題去檢索文檔捍靠。這也是一個(gè)可運(yùn)行的組件,可以與其他組件鏈接在一起森逮,但您也可以嘗試單獨(dú)運(yùn)行它:
retriever.invoke("這周五誰(shuí)要去參加生日聚會(huì)榨婆?")
然后,我們使用RunnableParallel(并行運(yùn)行多個(gè)Runnable)褒侧,通過(guò)使用檢索到的文檔和原始用戶問(wèn)題良风,為提示模板(代碼中的template)準(zhǔn)備設(shè)置需要輸入數(shù)據(jù)。具體來(lái)說(shuō)就是:使用檢索器進(jìn)行文檔搜索璃搜,使用RunnablePassthrough傳遞用戶的問(wèn)題。
setup_and_retrieval = RunnableParallel( {"context": retriever, "question": RunnablePassthrough()})
最終的完整執(zhí)行鏈如下:
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()})
chain = setup_and_retrieval | prompt | model | output_parser
詳細(xì)流程為:
首先鳞上,創(chuàng)建一個(gè)包含兩個(gè)條目的RunnableParallel對(duì)象这吻。第一個(gè)條目context,包含檢索器獲取的文檔結(jié)果篙议。第二個(gè)條目question唾糯,包含用戶的原始問(wèn)題。為了傳遞這個(gè)問(wèn)題鬼贱,我們使用RunnablePassthrough來(lái)復(fù)制這個(gè)條目移怯。
其次,將第一步中的字典提供給提示組件这难。然后舟误,它將用戶輸入(question)以及檢索到的上下文文檔(context)來(lái)構(gòu)造提示并輸出PromptValue。
然后姻乓,模型組件(Model)接受生成的提示嵌溢,并傳遞到OpenAI LLM模型中進(jìn)行評(píng)估。模型生成的輸出是一個(gè)ChatMessage對(duì)象蹋岩。
最后赖草,output_parser組件接收ChatMessage,并將其轉(zhuǎn)換為從invoke方法返回的Python字符串剪个。
總結(jié)
以上就是 LCEL 的簡(jiǎn)介以及基本使用秧骑。回顧一下:首先扣囊,我們介紹了什么是 LCEL乎折;其次,我們用一個(gè)簡(jiǎn)單的例子說(shuō)明了下 LCEL 的基本使用侵歇;然后笆檀,我們用分別介紹了 LCEL 中的幾個(gè)基本組件(Prompt、Model盒至、Output Parser)酗洒;最后士修,我們?cè)?RAG 基礎(chǔ)上再次介紹了 LCEL 的使用。
以上內(nèi)容依據(jù)官方文檔編寫(xiě)樱衷,官方地址:LCEL
Love & Peace~