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並行執行多個輸入通過給定的鏈。平行執行可以顯著減少延遲,因為處理可以並行完成而不是循序完成。
- 保證非同步支援:使用 LCEL 建置的任何鏈都可以使用Runnable Async API非同步執行。當您想要在伺服器環境中並行處理大量請求時,這可能很有用。
- 簡化串流:LCEL 鏈可以串流,允許在執行鏈時進行增量輸出。LangChain 可以最佳化輸出串流,以最大程度地減少達到首個 Token 的時間(從聊天模型或llm輸出第一個區塊所經過的時間)。
其他優點包括
- 無縫 LangSmith 追蹤 隨著您的鏈變得越來越複雜,了解每個步驟究竟發生了什麼變得越來越重要。使用 LCEL,所有步驟都會自動記錄到LangSmith,以實現最大的可觀察性和可偵錯性。
- 標準 API:由於所有鏈都是使用 Runnable 介面建置的,因此它們可以像任何其他 Runnable 一樣使用。
- 可使用 LangServe 部署:使用 LCEL 建置的鏈可以使用 LangServe 部署以供生產使用。
我應該使用 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 執行特定任務的指南,請查看相關的操作指南。