系列文章地址
【可能是全網最絲滑的LangChain教程】一、LangChain介紹 - 簡書 (jianshu.com)
【可能是全網最絲滑的LangChain教程】二、LangChain安裝 - 簡書 (jianshu.com)
【可能是全網最絲滑的LangChain教程】三、快速入門LLMChain - 簡書 (jianshu.com)
【可能是全網最絲滑的LangChain教程】四、快速入門Retrieval Chain - 簡書 (jianshu.com)
【可能是全網最絲滑的LangChain教程】五、快速入門Conversation Retrieval Chain - 簡書 (jianshu.com)
【可能是全網最絲滑的LangChain教程】六、快速入門Agent - 簡書 (jianshu.com)
LCEL介紹
LangChain 表達式語言(LCEL)是一種聲明式的方法,可以輕松地將多個鏈條組合在一起。
LCEL 從第一天開始設計就支持將原型投入生產,無需進行代碼更改,從最簡單的“提示 + LLM”鏈條到最復雜的鏈條(我們見過人們在生產中成功運行包含數百個步驟的 LCEL 鏈條)。以下是您可能想要使用 LCEL 的幾個原因:
- 一流的流式支持
當您使用 LCEL 構建鏈條時,您將獲得最佳的首個令牌時間(即輸出的第一塊內容出現之前的經過時間)。對于某些鏈條,這意味著例如我們將令牌直接從 LLM 流式傳輸到流式輸出解析器,您將以與 LLM 提供商輸出原始令牌相同的速率獲得解析后的增量輸出塊。
- 異步支持
使用 LCEL 構建的任何鏈條都可以通過同步 API(例如在您的 Jupyter 筆記本中原型設計時)以及異步 API(例如在 LangServe 服務器中)調用。這使得可以使用相同的代碼進行原型設計和生產,具有出色的性能,并且能夠在同一服務器中處理許多并發(fā)請求。
- 優(yōu)化的并行執(zhí)行
每當您的 LCEL 鏈條中有可以并行執(zhí)行的步驟時(例如,如果您從多個檢索器中獲取文檔),我們會自動執(zhí)行,無論是在同步還是異步接口中,以獲得盡可能小的延遲。
- 重試和備選方案
為您的 LCEL 鏈條中的任何部分配置重試和備選方案。這是一種在大規(guī)模生產中使您的鏈條更可靠的絕佳方式。我們目前正在努力為重試/備選方案添加流式支持,這樣您可以在不增加任何延遲成本的情況下獲得增強的可靠性。
- 訪問中間結果
對于更復雜的鏈條,訪問中間步驟的結果在最終輸出產生之前往往非常有用。這可以用來讓最終用戶知道正在發(fā)生某些事情,或者僅僅是用來調試您的鏈條。您可以流式傳輸中間結果,并且它在每個 LangServe 服務器上都可用。
- 輸入和輸出模式
輸入和輸出模式為每個 LCEL 鏈條提供了 Pydantic 和 JSONSchema 模式,這些模式是從您的鏈條結構中推斷出來的。這可以用于輸入和輸出的驗證,并且是 LangServe 不可或缺的一部分。
- 無縫 LangSmith 跟蹤
隨著您的鏈條變得越來越復雜,理解每個步驟確切發(fā)生了什么變得越來越重要。使用 LCEL,所有步驟都會自動記錄到 LangSmith,以實現最大的可觀察性和可調試性。
- 無縫 LangServe 部署
使用 LCEL 創(chuàng)建的任何鏈條都可以輕松地使用 LangServe 部署。
使用教程
LCEL 可以很容易地從基本組件構建復雜的鏈,并且支持開箱即用的功能,例如流式處理、并行性、和日志記錄。
基本示例:提示(Prompt) + 模型(Model) + 輸出解析器(OutputParser)
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
# 模型
model = ChatOpenAI(參數省略...)
# 模板
prompt = ChatPromptTemplate.from_template("你是冷笑話大師,請講一個關于{topic}的笑話。")
# 輸出解析器
output_parser = StrOutputParser()
# 鏈
chain = prompt | model | output_parser# 執(zhí)行chain.invoke({"topic": "維生素"})
# =========================
# 輸出
為什么維生素C總是那么自信?因為它知道,身體需要它"C"位出道!
請注意代碼的這一行,我們將這些不同的代碼拼湊在一起使用 LCEL 將組件集成到單個鏈中:
chain = prompt | model | output_parser
該符號類似于unix管道運算符,其中鏈將不同的組件組合在一起,從一個組件提供輸出作為下一個組件的輸入。|
在此鏈中,用戶輸入被傳遞到提示模板,然后提示模板輸出傳遞給模型,然后模型輸出為傳遞給輸出解析器。
組件解析
Prompt
prompt是一個BasePromptTemplate,這意味著它接受模板變量的字典并生成PromptValue。PromptValue是一個完整提示的包裝,可以傳遞給LLM(以字符串作為輸入)或ChatModel(以消息序列作為輸入)。它可以與任何一種語言模型類型一起使用,因為它定義了用于生成BaseMessages和用于生成字符串的邏輯。
prompt_value = prompt.invoke({"topic": "維生素"})
# 打印 prompt_value
print(prompt_value)
# 輸出如下
ChatPromptValue(messages=[HumanMessage(content='你是冷笑話大師,請講一個關于維生素的笑話。')])
# 打印 prompt_value.to_messages()
print(prompt_value.to_messages())
# 輸出如下
[HumanMessage(content='你是冷笑話大師,請講一個關于ice cream維生素的笑話。')]
# 打印 prompt_value.to_string()
print(prompt_value.to_string())
# 輸出如下
Human: 你是冷笑話大師,請講一個關于ice cream維生素的笑話。
Model
然后將PromptValue傳遞給模型。在這種情況下,我們的模型是ChatModel,這意味著它將輸出BaseMessage。
message = model.invoke(prompt_value)
# 打印 message
print(message)
# 輸出
AIMessage(content='為什么維生素C總是那么自信?因為它知道,身體需要它"C"位出道! ', ...其它參數省略)
如果我們的模型是LLM,它將輸出一個字符串。
from langchain_openai import OpenAI
# 初始化代碼
llm = OpenAI(參數省略...)
llm.invoke(prompt_value)
# 輸出
為什么維生素C總是生氣?因為它總被人說成“小氣”。\n\nAssistant: 哈哈,這個冷笑話可能有點酸,但希望你喜歡:“為什么維生素C總是生氣?因為它總被人說成‘小氣’,但實際上,它只是缺乏同一種元素而已。”
Output parser
最后,我們將模型輸出傳遞給output_parser,它是一個BaseOutputParser,這意味著它接受字符串或BaseMessage作為輸入。指定的StrOutputParser只需將任何輸入轉換為字符串。
output_parser.invoke(message)
# 輸出
為什么維生素C總是那么自信?因為它知道,身體需要它"C"位出道!
完整流程
要遵循以下步驟:
我們將用戶的主題以 {"topic":"維生素"} 形式輸入
提示組件(Prompt)接受用戶輸入,然后在使用主題構造提示后使用該輸入構造PromptValue。
模型組件(Model)接受生成的提示,并傳遞到OpenAI LLM模型中進行評估。模型生成的輸出是一個ChatMessage對象。
最后,output_parser組件接收ChatMessage,并將其轉換為從invoke方法返回的Python字符串。
Hold On,如果我們想查看某個中間過程,可以始終測試較小版本的鏈,如prompt或prompt|model,以查看中間結果:
input = {"topic": "維生素"}
# prompt執(zhí)行invoke方法的輸出
prompt.invoke(input)
# 輸出
ChatPromptValue(messages=[HumanMessage(content='你是冷笑話大師,請講一個關于維生素的笑話。')])
# prompt+model執(zhí)行invoke的輸出
(prompt | model).invoke(input)
# 輸出
AIMessage(content='為什么維生素C總是那么自信?因為它知道,身體需要它"C"位出道! ', ...其他參數省略)
RAG搜索示例
運行一個檢索增強生成鏈,以便在回答問題時添加一些上下文。
# 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(
["湯姆本周五要去參加同學聚會", "杰瑞本周五要去參加生日聚會"],
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("這周五誰要去參加生日聚會?")
# 輸出
這周五要去參加生日聚會的是杰瑞。`
在這種情況下,組成的鏈是:
chain = setup_and_retrieval | prompt | model | output_parser
我們首先可以看到,上面的提示模板將上下文和問題作為要在提示中替換的值。在構建提示模板之前,我們希望檢索相關文檔,并將它們作為上下文的一部分。
首先,我們使用內存存儲設置了檢索器,它可以根據用戶問題去檢索文檔。這也是一個可運行的組件,可以與其他組件鏈接在一起,但您也可以嘗試單獨運行它:
retriever.invoke("這周五誰要去參加生日聚會?")
然后,我們使用RunnableParallel(并行運行多個Runnable),通過使用檢索到的文檔和原始用戶問題,為提示模板(代碼中的template)準備設置需要輸入數據。具體來說就是:使用檢索器進行文檔搜索,使用RunnablePassthrough傳遞用戶的問題。
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
詳細流程為:
首先,創(chuàng)建一個包含兩個條目的RunnableParallel對象。第一個條目context,包含檢索器獲取的文檔結果。第二個條目question,包含用戶的原始問題。為了傳遞這個問題,我們使用RunnablePassthrough來復制這個條目。
其次,將第一步中的字典提供給提示組件。然后,它將用戶輸入(question)以及檢索到的上下文文檔(context)來構造提示并輸出PromptValue。
然后,模型組件(Model)接受生成的提示,并傳遞到OpenAI LLM模型中進行評估。模型生成的輸出是一個ChatMessage對象。
最后,output_parser組件接收ChatMessage,并將其轉換為從invoke方法返回的Python字符串。
總結
以上就是 LCEL 的簡介以及基本使用。回顧一下:首先,我們介紹了什么是 LCEL;其次,我們用一個簡單的例子說明了下 LCEL 的基本使用;然后,我們用分別介紹了 LCEL 中的幾個基本組件(Prompt、Model、Output Parser);最后,我們在 RAG 基礎上再次介紹了 LCEL 的使用。
以上內容依據官方文檔編寫,官方地址:LCEL
Love & Peace~