上個月LangChain剛剛發布了正式的0.1穩定版本(沒錯,是0.1而不是1.0),在版本公告里面首當其沖宣布的最重要更新,是在這個版本里面引入了一個最新庫 - LangGraph。這是一個面向當前LLM開發領域最火熱的AI Agent開發與控制的開發庫,也是LangChain試圖用來彌補其在Agent開發、特別是復雜的多Agent系統定制方面的不足的重大嘗試,相信也會成為LangChain在2024升級更新的重點領域! 我們會用一系列文章深入LangGraph,結合官方例子介紹與剖析其在幾個重點Agent方向的應用。
由于官方文檔較為晦澀,加上LangChain一貫的“重量級”風格。為了更好地幫助深入淺出的理解LangGraph,并照顧到沒有LangChain基礎的朋友,我們首先來了解一些“預備知識”。 PART 01 ![]() ![]() 預備知識 【LangChain中的鏈與LCEL】 Chain(鏈)是LangChain中最核心的概念之一(看名字就知道)。簡單的說,就是把自然語言輸入、關聯知識檢索、Prompt組裝、可用Tools信息、大模型調用、輸出格式化等這些LLM 應用中的常見動作,組裝成一個可以運行的“鏈”式過程。鏈可以直接調用,也可以用來進一步構建更強大的Agent。 LCEL即LangChain Express Language,即LangChain表達語言。這是LangChain提供的一種簡潔的、用于組裝上述“鏈”的聲明性方式。 我們看一個官方使用LCEL“組裝”Chain的例子就明白: prompt = ChatPromptTemplate.from_template('講一個關于 {topic} 的笑話') #調用chain 這個官方的例子中,把提示(prompt)、大模型(model)、輸出解析(output_parser)幾個組件使用管道符號“|”鏈接在一起,上個組件的輸出作為下一個組件的輸入,一起形成了一個鏈。 對于最常見的RAG應用來說,使用LCEL也無非是在此之上增加一個檢索相關文檔的動作,類似: chain = setup_and_retrieval | prompt | model | output_parser 這里很清晰地看到一個簡單的RAG應用處理過程:檢索關聯文檔 => 組裝Prompt => 調用大模型 => 輸出處理。 最后總結一下:LCEL就是LangChain提供用來組裝Chain的一種簡單表示方式。用這種方式組裝鏈,可以自動獲得諸如批量、流輸出、并行、異步等一系列能力;而且鏈可以進一步通過LCEL組裝成更復雜的鏈與Agent。 【LCEL構建與調度Agent】 那么如何用LCEL來創建一個AI Agent并調度運行呢?以最常見的React(推理&行動)范式的Agent來說,相對于Chain需要擴展的能力有:
以LCEL來組裝并創建運行一個Agent的簡單過程如下: ''' 注意到,相對于Chain.invoke()直接運行,這里的Agent_executor的作用就是為了能夠實現多次循環ReAct的動作,以最終完成任務。 【什么是圖(Graph)】 圖是計算機科學中的一種數據結構。大部分人可能都接觸過一些基本的數據結構,比如隊列(Queue)、堆棧(Stack)、鏈表(List)或者樹(Tree)等,圖(Graph)也是其中的一種相對復雜的數據結構。我們無意在此普及圖的數據結構知識,你只需要了解的圖的幾個基本知識:
PART 02 ![]() ![]() LangGraph的驅動力 即然上文介紹的LCEL已經很強大,但是為什么還需要LangGraph呢?基于LCEL構建的Chain與Agent又存在哪些不足呢? * 鏈(Chain):無法滿足在循環中調用LLM以完成任務。 上文中,我們可以輕易地使用LCEL來快速創建一個鏈,但是很顯然的一個問題是:如果我們把鏈中的組件想象成Graph中的節點,組件之間的聯系想象成Graph中的邊,那么這個鏈就是一個有向無環圖(DAG)。即在一次Chain運行中,一個調用節點無法重復/循環進入。 那么為什么需要將循環引入運行時呢?考慮一個增強的RAG應用: 在這個RAG應用設計中,我們可以對語義檢索出來的關聯文檔(上下文)進行評估:如果評估的文檔質量很差,可以對檢索的問題進行重寫(Rewrite,比如把輸入的問題結合對話歷史用更精確的方式來表達),并把重寫結果重新交給檢索器,檢索出新的關聯文檔,這樣有助于獲得更精確的結果。 這里把Rewrite的問題重新交給檢索器,就是一個典型的“循環”動作。而在目前LangChain的簡單鏈中是無法支持的。 其他一些典型的依賴“循環”的場景包括:
* AgentExecutor:盡管支持“循環”,但缺乏精確控制能力。 那么,如果我們需要在循環中調用LLM能力,就需要借助于AgentExecutor。其調用的過程主要就是兩個步驟:
這里的AgentExecute存在的問題是:過于黑盒,所有的決策過程隱藏在AgentExecutor背后,缺乏更精細的控制能力,在構建復雜Agent的時候受限。這些精細化的控制要求比如:
所以,讓我們簡單總結LangGraph誕生的動力:LangChain簡單的鏈(Chain)不具備“循環”能力;而AgentExecutor調度的Agent運行又過于“黑盒”。因此需要一個具備更精細控制能力的框架來支持更復雜場景的LLM應用。 PART 03 ![]() ![]() LangGraph的設計思想 LangGraph并非一個獨立于Langchain的新框架,它是基于Langchain之上構建的一個擴展庫,可以與Langchain現有的鏈、LCEL等無縫協作。LangGraph能夠協調多個Chain、Agent、Tool等共同協作來完成輸入任務,支持LLM調用“循環”以及Agent過程的更精細化的控制。 LangGraph的實現方式是把之前基于AgentExecutor的黑盒調用過程用一種新的形式來構建:狀態圖(StateGraph)。把基于LLM的任務(比如RAG、代碼生成等)細節用Graph進行精確的定義(定義圖的節點與邊),最后基于這個圖來編譯生成應用;在任務運行過程中,維持一個中央狀態對象(state),會根據節點的跳轉不斷更新,狀態包含的屬性可自行定義。 我們用官方的一個增強的RAG應用的Graph來幫助理解: 這個Graph中體現了LangGraph的幾個基本概念:
在上圖中,推理函數調用、調用檢索器、生成響應內容、問題重寫等都是其中的任務節點。
在上圖中,Check Relevance就是一個條件邊,它的上游節點是檢索相關文檔,條件函數是判斷文檔是否相關,如果相關,則進入下游節點【產生回答】;如果不相關,則進入下游節點【重寫輸入問題】。 在構建好StateGraph,并增加Node和Edge后,可以通過compile編譯成可運行的應用: app = graph.compile() 接下來你就可以調用這個app來完成你的任務。 PART 04 ![]() ![]() LangGraph構建基礎Agent 我們可以粗暴的認為LangGraph就是把現在黑盒的AgentExecutor揉碎掰開,允許你定義內部的細節結構(用圖的方式),從而實現更強大的功能。那么我們當然可以用LangGraph來重新實現原來的AgentExecutor,即實現一個最基礎的ReAct范式的Agent應用。 對應的Graph如下: 簡單的實現代碼如下(省略了部分細節):
代碼中的注釋對graph構建的細節做了解釋。顯然,這要比簡單的使用agentExecutor要復雜的多,但同時也展示了LangGraph在構建LLM應用時強大的控制能力:通過Graph的定義,可以對一個LLM應用的處理過程進行非常細節的編排設計,從而滿足大量復雜場景的AI Agent應用。 由于LangGraph剛推出不久,一些細節與易用性在后期也會不斷完善。比如未來是否會提供更直觀的定義界面等,也值得期待。在后續的文章中,我們將逐漸實踐幾個代表性場景下的LangGraph的應用,比如代碼助手,自省式RAG,多Agent應用等,敬請期待。 ![]() END |
|