LangChain 運算式語言 (LCEL)
LangChain Expression Language (LCEL) 採用宣告式方法,從現有的可執行物件建立新的可執行物件。
這表示您描述應該發生什麼,而不是如何發生,讓 LangChain 可以最佳化鏈的執行階段執行。
我們通常將使用 LCEL 建立的 Runnable
稱為「鏈」。請務必記住,「鏈」是 Runnable
,並且實作完整的可執行介面。
- LCEL 速查表顯示常見的模式,其中包含可執行介面和 LCEL 運算式。
- 請參閱下列操作指南清單,其中涵蓋 LCEL 的常見任務。
- 內建
Runnables
的清單可以在LangChain Core API 參考中找到。當在 LangChain 中使用 LCEL 組成自訂「鏈」時,其中許多可執行物件都很有用。
LCEL 的優點
LangChain 以多種方式最佳化使用 LCEL 建置的鏈的執行階段執行
- 最佳化平行執行:使用RunnableParallel 平行執行可執行物件,或使用Runnable Batch API 平行執行多個輸入通過給定的鏈。平行執行可以顯著減少延遲,因為可以平行而不是循序完成處理。
- 保證非同步支援:使用Runnable Async API 可以非同步執行使用 LCEL 建置的任何鏈。當在伺服器環境中執行鏈時,這可能很有用,在伺服器環境中,您想要同時處理大量請求。
- 簡化串流:可以串流 LCEL 鏈,允許在執行鏈時進行增量輸出。LangChain 可以最佳化輸出的串流,以最大限度地減少首次 Token 時間(從聊天模型或llm 輸出第一個區塊所經過的時間)。
其他優點包括
- 無縫 LangSmith 追蹤 隨著您的鏈變得越來越複雜,了解每個步驟究竟發生什麼變得越來越重要。使用 LCEL,所有步驟都會自動記錄到 LangSmith,以實現最大的可觀察性和可偵錯性。
- 標準 API:由於所有鏈都是使用可執行介面建置的,因此它們可以像任何其他可執行物件一樣使用。
- 可使用 LangServe 部署:使用 LCEL 建置的鏈可以使用部署以供生產使用。
我應該使用 LCEL 嗎?
LCEL 是一個協調解決方案——它允許 LangChain 以最佳化的方式處理鏈的執行階段執行。
雖然我們已經看到使用者在生產環境中執行具有數百個步驟的鏈,但我們通常建議將 LCEL 用於更簡單的協調任務。當應用程式需要複雜的狀態管理、分支、循環或多個代理程式時,我們建議使用者利用 LangGraph。
在 LangGraph 中,使用者定義指定應用程式流程的圖形。這允許使用者在需要 LCEL 時繼續在個別節點內使用 LCEL,同時輕鬆定義更易讀且可維護的複雜協調邏輯。
以下是一些指南
- 如果您要進行單次 LLM 呼叫,則不需要 LCEL;而是直接呼叫底層聊天模型。
- 如果您有一個簡單的鏈(例如,提示 + llm + 解析器、簡單的檢索設定等),如果您正在利用 LCEL 的優點,則 LCEL 是合理的選擇。
- 如果您正在建置複雜的鏈(例如,具有分支、循環、多個代理程式等),請改用 LangGraph。請記住,您始終可以在 LangGraph 中的個別節點內使用 LCEL。
組合基本元件
LCEL
鏈是透過將現有的 Runnables
組合在一起而建置的。兩個主要的組合基本元件是 RunnableSequence 和 RunnableParallel。
許多其他組合基本元件(例如,RunnableAssign)可以視為這兩個基本元件的變體。
您可以在 LangChain Core API 參考中找到所有組合基本元件的清單。
RunnableSequence
RunnableSequence
是一個組合基本元件,可讓您循序「鏈接」多個可執行物件,其中一個可執行物件的輸出作為下一個可執行物件的輸入。
from langchain_core.runnables import RunnableSequence
chain = RunnableSequence([runnable1, runnable2])
使用某些輸入調用 chain
final_output = chain.invoke(some_input)
對應於以下內容
output1 = runnable1.invoke(some_input)
final_output = runnable2.invoke(output1)
runnable1
和 runnable2
是您想要鏈接在一起的任何 Runnable
的預留位置。
RunnableParallel
RunnableParallel
是一個組合基本元件,可讓您同時執行多個可執行物件,並為每個可執行物件提供相同的輸入。
from langchain_core.runnables import RunnableParallel
chain = RunnableParallel({
"key1": runnable1,
"key2": runnable2,
})
使用某些輸入調用 chain
final_output = chain.invoke(some_input)
將產生一個 final_output
字典,其索引鍵與輸入字典相同,但值已替換為相應可執行物件的輸出。
{
"key1": runnable1.invoke(some_input),
"key2": runnable2.invoke(some_input),
}
回想一下,可執行物件是平行執行的,因此雖然結果與上面顯示的字典理解相同,但執行時間要快得多。
RunnableParallel
支援同步和非同步執行(所有 Runnables
都是如此)。
- 對於同步執行,
RunnableParallel
使用 ThreadPoolExecutor 同時執行可執行物件。 - 對於非同步執行,
RunnableParallel
使用 asyncio.gather 同時執行可執行物件。
組合語法
RunnableSequence
和 RunnableParallel
的使用非常普遍,因此我們為使用它們建立了一個簡寫語法。這有助於使程式碼更易讀且更簡潔。
|
運算子
我們多載了 |
運算子,以從兩個 Runnables
建立 RunnableSequence
。
chain = runnable1 | runnable2
等效於
chain = RunnableSequence([runnable1, runnable2])
.pipe
方法
如果您對運算子多載有道德上的顧慮,可以使用 .pipe
方法來代替。這與 |
運算子等效。
chain = runnable1.pipe(runnable2)
強制型轉
LCEL 應用自動型別強制型轉,以簡化鏈的組合。
如果您不了解型別強制型轉,您可以隨時直接使用 RunnableSequence
和 RunnableParallel
類別。
這會使程式碼更冗長,但也會使其更明確。
字典到 RunnableParallel
在 LCEL 運算式中,字典會自動轉換為 RunnableParallel
。
例如,以下程式碼
mapping = {
"key1": runnable1,
"key2": runnable2,
}
chain = mapping | runnable3
它會自動轉換為以下內容
chain = RunnableSequence([RunnableParallel(mapping), runnable3])
您必須小心,因為 mapping
字典不是 RunnableParallel
物件,它只是一個字典。這表示以下程式碼會引發 AttributeError
mapping.invoke(some_input)
函數到 RunnableLambda
在 LCEL 運算式中,函數會自動轉換為 RunnableLambda
。
def some_func(x):
return x
chain = some_func | runnable1
它會自動轉換為以下內容
chain = RunnableSequence([RunnableLambda(some_func), runnable1])
您必須小心,因為 lambda 函數不是 RunnableLambda
物件,它只是一個函數。這表示以下程式碼會引發 AttributeError
lambda x: x + 1.invoke(some_input)
舊版鏈
LCEL 旨在圍繞行為和自訂提供與舊版子類別鏈(例如 LLMChain
和 ConversationalRetrievalChain
)的一致性。許多這些舊版鏈隱藏了重要的詳細資訊,例如提示,並且隨著越來越多可行的模型出現,自訂變得越來越重要。
如果您目前正在使用其中一個舊版鏈,請參閱本指南以取得有關如何遷移的指引。
如需有關如何使用 LCEL 執行特定任務的指南,請查看相關的操作指南。