Agent 智慧體開發框架選型指南

Baihai_IDP發表於2024-11-07

編者按: 本文透過作者的實踐對比發現,框架的選擇應基於專案具體需求和團隊特點,而不是簡單追求某個特定框架。不同框架各有優勢:

  1. 無框架方案實施最為簡單直接,程式碼結構清晰,適合理解智慧體原理,但隨著專案複雜度增加可能變得難以維護。
  2. LangGraph提供完整的智慧體結構規範,特別適合團隊協作和智慧體結構新手,但框架限制較多,如不認同其理念可能面臨較大除錯挑戰。
  3. LlamaIndex Workflows採用事件驅動架構,在框架約束和開發自由度之間取得平衡,對框架依賴較少,但其固有的非同步特性可能增加某些場景的複雜度。

框架選擇需要考慮三個關鍵因素:專案是否已深度整合了特定框架、團隊對智慧體架構的熟悉程度、是否有可供參考的相似專案案例。

作者 | Aparna Dhinakaran

編譯 | 嶽揚

Image by author

智慧體(Agents)正迎來輝煌時刻。伴隨著眾多新框架的湧現和對該領域的持續投資[1],現代 AI 智慧體正在跨越起初的不穩定階段[2],迅速取代 RAG 成為開發首選。那麼,2024 年是否會成為 autonomous AI 系統全面接管撰寫郵件、預訂航班、資料分析等任務的一年呢?

也許吧,但要實現這一點還有很多工作要做。開發人員在構建智慧體時,不僅要決定使用何種模型、應用場景和技術架構,還要挑選合適的開發框架。是堅持較為早期的 LangGraph,還是轉向新興的 LlamaIndex Workflows?或者走傳統路線,自己編寫全部程式碼呢?

這篇文章的目的就是讓您更輕鬆地做出選擇。在過去幾周裡,我使用多個主流框架構建了相同的智慧體,並從技術角度分析了它們各自的優缺點。每個智慧體的所有程式碼都可以在此程式碼倉庫[3]中找到。

本文測試用智慧體的基本概述

本次測試所採用的智慧體整合了多項功能,包括執行函式呼叫(function calling)、使用多種工具或技能、與外部資源建立連線,以及實現狀態或記憶的共享。

該智慧體具備以下幾項核心能力:

  1. 基於知識庫進行問題解答
  2. 資料互動:針對 LLM 應用程式的資料進行問題解答
  3. 資料洞察:對獲取的資料進行更高層次的趨勢和模式分析

為了達成上述目標,智慧體需要掌握三項基本技能:結合產品文件的 RAG、在相關資料庫上生成 SQL 語句的能力,以及資料分析技巧。智慧體的使用者介面使用 gradio 搭建,而智慧體本身則以聊天機器人(chatbot)的形式構建。

01 Code-Based Agent(不使用智慧體框架)

在著手開發智慧體時,您可以選擇不依賴任何框架,而是完全自主構建。在啟動這個專案之初,我首先採用了這種方法。

Image by author

1.1 純程式碼架構

下面是基於純程式碼構建的智慧體,其核心是一個由 OpenAI 提供支援的技能路由器,它透過函式呼叫來確定使用哪項技能。技能執行完畢後,控制權將返回給技能路由器,以便呼叫其他技能或直接向使用者作出回應。

智慧體會持續記錄使用者訊息和智慧體響應,並在每次呼叫時將這一完整列表傳遞給技能路由器,確保在整個互動過程中保留上下文。

各項技能均在獨立的類中進行定義(例如“GenerateSQLQuery”類),這些類都儲存在 SkillMap 中。技能路由器僅與 SkillMap 進行互動,透過它來載入技能的名稱、描述以及可呼叫的函式。這種設計理念使得向智慧體中新增新技能變得非常簡單:只需將該技能編寫為一個獨立的類,並將其加入到 SkillMap 的技能列表即可。這樣做的目的是為了在不影響技能路由器程式碼的前提下,輕鬆實現新技能的新增。

總的來說,這種實現方式雖然簡單易行,但仍然存在一些需要克服的難題。

1.2 使用純程式碼智慧體面臨的挑戰

第一個困難在於如何設計技能路由器的系統提示詞(system prompt)。 在上面的例子中,技能路由器往往傾向於自行生成 SQL 語句,而不是交給相應的技能模組去處理。如果你有過試圖讓大語言模型停止執行某項任務的經歷,那你可能深知這其中的挫敗感;為了找到合適的提示詞,我不得不進行了多次除錯。此外,處理每個步驟產生的不同輸出格式也是一項複雜的工作。 由於我選擇不使用結構化輸出,因此必須為技能路由器和各項技能中大語言模型的呼叫準備多種格式的應對策略。

1.3 純程式碼智慧體的優點

基於程式碼的方法提供了一個紮實的基礎和出發點,是一種絕佳的學習途徑,讓我們可以在不依賴現成框架提供的智慧體教程的情況下,瞭解智慧體的運作原理。雖然引導大語言模型按既定行為模式運作確實存在難度,但程式碼結構本身簡潔明瞭,易於操作,對於某些使用場景而言,這種做法是完全合理的(具體分析將在下文展開)。

02 LangGraph

LangGraph 是眾多智慧體框架中歷史最為悠久的之一,它於 2024 年 1 月首次釋出。該框架的設計初衷是為了解決現有流程和鏈條的非迴圈性問題,它透過採用 Pregel 圖結構來解決這一問題。 LangGraph 透過引入節點(nodes)、邊(edges)以及條件邊(conditional edges)的概念,簡化了在智慧體中建立迴圈流程的過程,使得圖的遍歷變得更加直觀。LangGraph 是基於 LangChain 構建的,它繼承了後者的物件(objects)和型別(types)。

Image by author

2.1 LangGraph 架構

從表面上看,LangGraph 智慧體與基於程式碼的智慧體有相似之處,但它們的底層程式碼卻有大不相同。雖然 LangGraph 在技術上也使用了“路由器(router)”這一概念,即透過程式碼函式呼叫 OpenAI 並利用其響應來推進到下一個步驟,但程式在不同技能之間的切換控制機制卻完全不同。

在此定義的圖(graph)中,包含了一個用於初始化 OpenAI 呼叫的節點,即上文中提到的“agent”,以及一個用於工具處理步驟節點,即“tools”。LangGraph 內建了一個名為 ToolNode 的物件,它能夠接收一系列可呼叫的工具,並根據 ChatMessage 的響應來觸發這些工具,完成操作後再次回到“agent”節點。

每當“agent”節點(也可以理解為基於程式碼的智慧體中的技能路由器(router))被呼叫之後,should_continue 這條邊將判斷是將響應直接返回給使用者,還是轉給 ToolNode 來處理工具呼叫。

在每個節點中,“state” 負責儲存與 OpenAI 的互動訊息和響應列表,這一點與基於程式碼的智慧體保持上下文的方式相似。

2.2 使用 LangGraph 面臨的挑戰

在處理 LangGraph 構建的智慧體示例時,遇到的主要難題在於必須藉助 Langchain 物件才能確保流程的順暢。

挑戰 1:函式呼叫的 validation 錯誤

為了能夠使用 ToolNode 物件,我不得不對 Skill 程式碼進行大規模的重構。ToolNode 需要一組可呼叫的函式列表,我本以為可以直接使用現成的函式,但是函式引數配置出了問題,導致流程受阻。

這些技能(skills)是以類形式定義的,每個類都有一個可呼叫的成員函式,其中“self”是首個引數。GPT-4o 足夠智慧,能夠在生成函式呼叫(function call)時自動排除“self”引數,但 LangGraph 卻因此認為缺少了必要引數,從而丟擲了 validation 錯誤。

這個問題讓我摸索了好幾小時才搞清楚,因為錯誤資訊把函式里的第三個引數(資料分析技能中的“args”)錯誤地標記為缺失引數(missing parameter):

需要指出的是,這個誤導性的錯誤資訊其實來自 Pydantic,而非 LangGraph。

最後,我下定決心,改用 Langchain 的 @tool 裝飾器將我的技能(skills)重新編寫為基本方法,這樣程式就能正常執行了。

挑戰 2:Debugging

正如前文所述,在框架中除錯非常困難。主要是因為錯誤資訊混亂不清,以及框架中的抽象概念,它們使得追蹤和檢視變數變得非常複雜。

抽象概念主要體現在嘗試跟蹤智慧體間傳遞的訊息時。LangGraph 會將訊息儲存在 state[“messages”] 裡。Graph 中的一些節點會自動從這些訊息(messages)中提取資訊,這樣的自動化過程可能會讓節點在訪問訊息(messages)時,我們難以把握訊息(messages)的具體內容。

智慧體行動的順序檢視(圖片由作者提供)

2.3 LangGraph 的優點

LangGraph 的最大優勢在於其易用性。它的圖結構程式碼簡潔且易於理解。對於那些擁有複雜節點邏輯的場景,LangGraph 能夠提供一個清晰的圖檢視,讓我們更輕鬆地把握智慧體的連線方式。此外,LangGraph 還可以直接轉換以 LangChain 構建的現有應用程式。

2.4 經驗之談

當我們只使用 LangGraph 框架的相關功能時,一切都會執行得非常流暢;但一旦我們嘗試跳出框架,就要準備好進行一些令人頭疼的除錯了。

03 LlamaIndex Workflows

Workflows 是智慧體框架領域的新晉成員,它於今年夏初首次亮相。與 LangGraph 類似,它的設計宗旨是簡化可迴圈智慧體的構建過程。此外,Workflows 特別強調其非同步執行的能力。

在 Workflows 中,某些設計元素似乎是為了直接對標 LangGraph,尤其是它採用事件(events)而非邊(edges)或條件邊(conditional edges)作為連線邏輯的方式。在 Workflows 中,智慧體邏輯被封裝在“步驟(steps)”中(與 LangGraph 中的“節點(nodes)”相對應),而事件(events)的發出和接收則負責在不同的步驟(steps)間傳遞資訊。

Image by author

上述框架與 LangGraph 的結構頗為相似,但有一點不同:我給 Workflow 增加了一個初始化步驟,用於準備智慧體的環境上下文,稍後我會詳細介紹這一點。儘管兩者的結構相似,但它們所依賴的程式碼實現卻截然不同。

3.1 Workflows 架構

以下程式碼段描繪了 Workflow 的架構。與 LangGraph 相仿,在這一部分,我配置了狀態資訊(state),並將各項技能(skills)繫結到了 LLM 物件上。

在這裡,我還定義了一個額外的步驟——“prepare_agent”。該步驟負責將使用者輸入轉換成 ChatMessage,並將其儲存到工作流的記憶儲存中。將這一過程作為一個獨立的步驟分離出來,意味著智慧體在遍歷工作步驟(steps)時可以重複回到這一步,從而避免反覆將使用者資訊加入到記憶儲存中。

在 LangGraph 的實現案例中,我透過一個位於圖(graph)之外的 run_agent 方法實現了相同的功能。這一改變主要是出於風格上的考慮,但我認為,將這一邏輯整合到 Workflow 和圖(graph)中,會更加整潔和高效。

在 Workflow 配置完成後,我繼續編寫了路由程式碼:

以及工具呼叫處理程式碼:

它們的實現方式似乎更接近於純程式碼的智慧體,而非 LangGraph 智慧體。這主要是因為 Workflows 選擇在各步驟(steps)中維護條件路由(conditional routing)邏輯,而不是像 LangGraph 那樣使用條件邊(conditional edge)(第 18-24 行在 LangGraph 中是條件邊,而現在它們只是路由步驟的一部分)。另外,LangGraph 中的 ToolNode 物件能夠在 tool_call_handler 方法中自動處理大部分任務。

在路由步驟之後,我們能夠將 SkillMap 以及基於純程式碼的智慧體中已有的技能(skills)直接應用於 Workflows。這些技能(skills)無需任何修改即可與 Workflows 配合使用,這大大簡化了我的工作。

3.2 使用 Workflows 面臨的挑戰

挑戰 1:Sync vs Async

儘管對於線上執行的智慧體來說,非同步執行是更優的選擇,但除錯同步執行的智慧體通常更為簡便。Workflows 本身是為了非同步操作而設計的,因此嘗試將其改為同步執行非常困難。

起初,我以為只需去掉“async”方法標識,並將函式名“achat_with_tools”改為“chat_with_tools”即可。但是,由於 Workflow 類內部的方法同樣採用了非同步標記,為了實現同步執行,我不得不重新定義這些方法。儘管如此,我最終還是選擇了非同步處理方式,幸運的是,這並沒有增加除錯的難度。

智慧體行動的順序檢視(圖片由作者提供)

挑戰 2:Pydantic Validation Errors

與 LangGraph 的問題類似,在智慧體的技能(skills)處也出現了令人困惑的 Pydantic Validation Errors。幸運的是,由於 Workflows 能夠很好地處理成員函式,這些問題這次比較容易解決。最終,我不得不更加規範地為智慧體技能(skills)建立 LlamaIndex FunctionTool 物件:

從構建 FunctionTools 的 AgentFlow.__init__ 檔案中摘錄

3.3 Workflows 的優點

與 LangGraph 相比,我在使用 Workflows 構建智慧體時要輕鬆得多,主要原因是 Workflows 並未提供內建功能,而是需要我自己編寫路由邏輯和工具操作程式碼。 這也使得我的 Workflow 智慧體與基於純程式碼的智慧體看起來極為相似。

最大的區別在於事件(events)的使用上。我使用兩個自定義事件在智慧體中的各個步驟之間移動:

這種基於事件的發射器-接收器架構(emitter-receiver),取代了直接呼叫智慧體中某些方法的做法,例如工具呼叫處理(tool call handler)。

對於那些步驟(steps)更為複雜、非同步觸發且可能產生多個事件(events)的系統來說,這種架構就非常有助於乾淨利落地管理這些步驟。

Workflows 的其他優點還包括其輕量級特性,不會施加過多的結構限制(除了必須使用特定的 LlamaIndex 物件外),並且其基於事件(event-based)的架構為直接函式呼叫提供了一種有效的替代方案,這對於處理複雜、非同步的應用場景尤為有益。

04 對這些方法進行比較

對比這三種方法,各有其獨到之處。

無框架方法實施起來最簡單。由於所有抽象層都是由開發者自行定義(如前例中的 SkillMap 物件),因此管理不同型別(types)和物件(objects)相對簡單。但是,程式碼的可讀性和易用性完全取決於開發者個人,可以預見,如果沒有一定的智慧體結構約束,智慧體的複雜性增加後可能會變得難以駕馭。

LangGraph 提供了豐富的智慧體結構支援,使得智慧體的定義非常清晰。對於多人協作開發的智慧體來說,這種智慧體結構設定有助於統一架構規範。LangGraph 也為那些對智慧體結構不太熟悉的開發者提供了幫助。不過,這樣做也有代價 —— 由於 LangGraph 為你做了許多工作,如果你不完全認同這個框架,它可能會讓你頭疼不已;程式碼可能會非常簡潔,但你可能要為此進行更多的除錯工作。

Workflows 則處於兩者之間。基於事件(event-based)的架構在某些專案中可能極具價值,而且因為它對 LlamaIndex 型別的使用要求不高,對於那些沒有在應用程式中完全使用該框架的開發者來說,提供了更大的自由度。

Image created by author

歸根結底,關鍵問題可能在於“你是否已經在使用 LlamaIndex 或 LangChain 來組織應用程式?” LangGraph 和 Workflows 都與它們所依賴的框架緊密整合,因此每個特定智慧體框架的額外優勢可能不足以成為轉換使用的理由。

純程式碼方法可能永遠是一個有吸引力的選擇。如果你能夠嚴格地記錄並執行所建立的任何抽象概念,那麼確保外部框架不會成為你的阻礙就很容易了。

05 在選擇智慧體框架時需要考慮的關鍵問題

當然,單純一句“具體情況具體分析”這樣的回答總是讓人不太滿意。以下三個問題或許能幫你選擇下一個智慧體專案應該採用哪個框架。

你的專案是否已經深度整合了 LlamaIndex 或 LangChain?

如果是的話,不妨優先考慮這兩個選項。

你對智慧體的常見架構是否熟悉,還是更希望有人告訴你應該如何構建智慧體結構?

如果你傾向於後者,那麼 Workflows 可能是個不錯的選擇。如果你非常傾向於後者,那麼 LangGraph 或許更適合你。

你要構建的智慧體是否有參考樣例?

框架的一個優勢在於,每個框架都有大量的教程和例項供你參考。而純程式碼構建智慧體的參考例項相對較少。

Image created by author

06 Conclusion

選擇一個智慧體框架只是影響生成式人工智慧系統在生產環境中表現眾多決策中的一項,建立強大的安全保障和對大語言模型(LLM)的監控[4]是必要的 —— 同時,面對新智慧體框架、研究成果和模型對傳統技術的顛覆,我們還需保持靈活應對的態度。

Thanks for reading!

Hope you have enjoyed and learned new things from this blog!

About the authors

Aparna Dhinakaran

Co-Founder and CPO of Arize AI. Formerly Computer Vision PhD at Cornell, Uber Machine Learning, UC Berkeley AI Research.

END

本期互動內容 🍻

❓請分享一下你最常使用的智慧體開發方式?為什麼?

🔗文中連結🔗

[1]https://foundationcapital.com/goodbye-aiops-welcome-agentsres...

[2]https://arxiv.org/html/2405.13966v1

[3]https://github.com/Arize-ai/phoenix/tree/main/examples/agent_framework_comparison

[4]https://docs.arize.com/phoenix/tracing/llm-traces

原文連結:

https://towardsdatascience.com/choosing-between-llm-agent-fra...

相關文章