
Building Effective Agents
這一篇是參考 Anthropic 的 Building Effective Agents 文章,剛好最近有在用 LangGraph 實作 agent ,有一些心得,但是沒有系統的整理好,順著這篇文章的架構,我也試著整理一下。
所以這篇文章有點像是翻譯、心得、筆記的混合體。
Agent 是指在執行過程中,LLM 有參與決策或執行
首先來區分什麼是 Workflows 與 Agents:
- Workflows 指的是 LLMs 跟 tools 是由 predefined code paths 來執行。
- Agents 執行過程中 LLMs 能動態的決策執行流程與 tools 的調用,維持完成任務的控制權。
以應用場景來說,Agent 通常適合用在 open-ended problems, 你很難去製定出確切的執行步驟的問題,例如:
- 一個 coding agent 根據不同任務可能會需要編輯多個檔案。
- Claude 的 Computer Use,使用電腦完成任務。
必要時才使用 Agent 機制,否則能用 prompt 解就儘量從簡
- 在一開始做 LLM application 的時候,建議使用最簡單能夠達成目的的方法,所以系統裡可能沒有任何的 agent,因為 agent 會增加 cost 以及 performance消耗,必須在衡量整體效益之後再決定是否需要。
- 當系統越來越大的時候,使用 workflow 可以讓系統執行結果更如我們的預期,而 agent 則是在需要大規模靈活性,和模型驅動決策時(model-driven decision-making)的更佳選擇。
- 不要急著使用 agent,可以先從簡單的 prompt 開始,設計一個評估機制來評估 prompt 好壞,並持續優化 prompt,真的無法再開始考慮導入 agent。
💡這裡我覺得很重要而且很容易被忘記,開發 LLM Application 時要先準備能夠評估用的資料,定出好的回覆的評判標準,以及好的回覆的範例。
Agent Frameworks 看似好用,但要確保你瞭解底層原理,以及它幫你解決了哪些事情之後再使用
Frameworks 幫忙封裝了很多實作在裡面,你可能會在一開始覺得很好用,但是到後來很難 debug,建議一開始從直接的 LLM API呼叫,一般的 pattern 可以直接以程式碼做掉 (可以參考 anthropic cookbook),要使用 framework 的話,請確保你瞭解它的底層機制,它幫你做了什麼,不要去假設它執行了什麼,而是要深刻的瞭解。
這一點我是蠻認同的,一開始使用 LangChain
,雖然用起來很方便,code 少少的就能完成任務,但是其實會不太知道自己做了什麼,所以寫完 code 覺得虛虛的,遇到問題時也不太知道該往什麼方向去追,後來轉用 litellm
變得要實作很多東西,但就比較清楚自己在做什麼,瞭解了之後其實再回頭看 LangChain
才比較懂得它做了什麼事 (確實是封裝的很漂亮),但也因為事情都做掉了,就還是維持使用 litellm
,到後來使用 LangGraph
的時候,也是能不用 LangChain
就不用。
以下列出一些文章內有提到的 Frameworks:
- LangGraph from LangChain;
- Amazon Bedrock’s AI Agent framework;
- Rivet, a drag and drop GUI LLM workflow builder; and
- Vellum, another GUI tool for building and testing complex workflows.
使用 agent 時的一些 pattern
這裡有提供一些 pattern 可以參考,這些 pattern 並不是強制規定,你可以依情境搭配使用,但注意只有在能夠明確證明可以改善成果時,才考慮增加複雜性。
The augmented LLM (賦能 LLM )
賦能LLM,比如可以提供 LLM 更多的資訊,或是讓它能做更多的事情,像是:
- retrieval:可能是從資料庫中撈資料,或是從網路上撈資料
- tools:可能是呼叫外部工具,像是寫程式、訂房…
- memory:可能是儲存、讀取記憶的內容
LangGraph
在 Memory 章節 有提到,可以分為 short-term 和 long-term 的記憶,short-term 通常是記憶這次的對話內容,long-term 則是可能記憶 user profile之類的內容。
可以從兩個層面來 implement
- 根據使用者所需要的 use-case 來進行客製化
- 確保它們提供簡單以及提供 well-documented interface給 LLM
也就是說,不要一股腦的想要去打造一個萬能的 agent,依據使用者目前所需要的情境,跟目前進行的步驟,賦予在該步驟中所需要的工具即可,確保 LLM 知道什麼時候該呼叫什麼工具,多的都是會讓人花時間去調 prompt 或 debug,還不一定調的好。
Prompt Chaining (把大任務切成很多的 steps)
Prompt Chaining 是指把一個大任務切成很多的 steps,前一個 LLM 的輸出會變成下一個 LLM的輸入,如果有需要,有可以在中間加一些步驟 (圖片中的
Gate
) 來確保中間的執行 on track.
當任務可以很明確、清楚的被切分成不同步驟來執行時,這是個好方法,比如:
- 產生 marketing 文件,並翻譯成不同的語言。
- 先撰寫文件大綱,確認大綱符合預期後,再撰寫文件內容
我們實務中也有發現,prompt 寫的太長,會導致 LLM 的輸出不穩定 (因為你要交付它的任務太多了),所以有時候會需要拆成多個 prompt 來執行 (給它很明確的小任務,LLM 能夠執行的很好)。
Routing (LLM 負責引導到對應的 followup task)
Routing 是指收到輸入後,引導到對應的 followup task。當複雜任務中存在明確的類別需要被分別處理,而且分類可以由 LLM 或更傳統的分類模型/演算法精確處理時,Routing 效果較好,比如:
- 將不同類型的客戶服務查詢(一般問題、退款請求、技術支援)導向不同的處理流程、提示和工具。
- LangGraph 有提供一個 Customer support bot (訂飯店、機票、車)的範例,還蠻有趣的
- 這裡有提供一個 trick,簡單的任務可以交由 haiku 來做,複雜的決策,可以交由 sonnet 來做 (還蠻實用的)
Parallelization (平行處理多個任務)
LLM 有時候會需要平行處理執行一些任務,然後再將結果匯集起來。有兩種變型:
- Sectioning: 把任務拆分成可平行處理的子任務
- 比如做一個 research,生出三組不同的關鍵字,然後分別去搜尋,最後再將結果匯集起來。
- Voting: 執行同一個任務多次,以取得多樣性的結果
- 比如做一個 research,有 interviewer 與 expert 模擬多輪訪談,取得不同的觀點,最後再將結果匯集起來。可以參考 LangGraph 實作 Research Assistant (STORM):平行多輪與專家進行深度訪談
當任務可以被拆分成小任務進行平行處理,而且多樣化的觀點對於結果的產生很有幫助,Parallelization 會很有效。
Sectioning 的範例:
- 實作 guardrails,一個模型負責使用者的 query產出,由另一個模型來負責檢查回應裡面是否有不當的言論或產出。這會比一個模型同時負責 response 與 guardrails 效果要好。
- 自動評估 LLM response 的好壞,可以給 不同 LLM 特定的 prompt 進行不同面向的評估
Voting 的範例:
- 審查程式碼中的漏洞,多個不同的 prompt 檢查程式碼,若發現問題就進行標記。
- 評估特定內容是否不當,使用多個提示評估不同面向,或需要不同的投票門檻來平衡誤報(false positives)和漏報(false negatives)。
Orchestrator-workers (LLM 指揮其它的 LLM workers)
由一個統合的 LLM來指揮不同的 worker LLMs 做事,再將結果綜合起來;這個很適合任務的類型是你無法預期去如何拆分子任務的。雖然看起來跟 Parallelization (平行處理多個任務) 很像,不同的是會有哪些子任務並非 pre-defined 的,而是由 orchestrator 根據輸入來決定的:
- 比如在 coding 時,要決定修改哪些檔案,以及檔案裡的哪些內容。
- 從多個來源收集和分析可能相關信息的搜尋任務。
這個部份目前比較沒有做到,日後有機會實作再來補充。
Evaluator-optimizer (經由 evaluate and feedback 不斷優化產出)
一個 LLM 會產生 response,另一個 LLM 負責評估它的結果,形成一個 evaluation and feedback loop. 當有很明確的評估準則,而且 iteration refinement 可以提供可量測的數值時 (可能夠知道不斷在改善) 會是一個很好的 workflow。
有兩個指標適合用來做判斷適不適合使用這個 pattern:
- 當人類表達他們的 feedback 時,LLM 的回應能夠得到明顯改善
- LLM 能夠提供這樣的 feedback
可能使用場景
- 文學翻譯,其中有些細微差別翻譯 LLM 可能一開始沒有 catch 到,但評估的 LLM 可以提供有用的批評。
- 複雜的搜尋任務,需要多輪搜尋和分析才能收集全面資訊,由評估者決定是否需要進行更多搜尋
這類似於人類作者在製作精緻文件時可能經歷的 iterative 寫作過程。
在 Agents 運作中也可以去確認運作的狀況,而不用等到執行完
- 在執行中,很重要的一件事情是要讓 agent 有 一些依據 (“ground truth“)來讓 LLM 評估進度,比如:
- code execution:來判斷產出的 code 有沒有錯
- tool calls result: 能不能成功呼叫 tool calls,呼叫完的回傳結果
- 執行是否有如預期進行,設置一些檢查機制也很重要,可以使用比如:
- Human in the loop:在執行的過程中可以加入使用者的回饋
- max number of iterations:在自動的 feedback loop 中,設定一個最大 iteration 次數,如果超過次數,則停止執行
Agents 實作三原則:架構簡單、流程透明、介面可讀
- 架構簡單:agent 設計儘可能維持「簡單」,這篇文章也一直有強調,能用 prompt 解決的問題,就儘量不要使用 agent。非必要時,不要增加系統的複雜度。
- 流程透明:應該要讓使用者能夠看到 agent 是如何一步一步的達成目標,而不是讓它像一個黑盒子一樣,只給你最終結果,卻不知道它是怎麼來的。
- 介面可讀:在設計 agent 與 computer 互動介面時,需要非常仔細地考慮如何讓 agent 能夠有效地使用各種工具,並且要做好完善的 documentation 和 testing,以確保介面的可靠性和易用性。
- 工具的可靠性做好,在開發的時候,才能聚焦在 LLM 或是流程的問題上。
要讓 agent 運作的好,有清楚而且深思熟慮過的 documentation 是必要的 (Prompt Engineering your Tools)
最後文章提到給 LLM 使用工具時所提供的資訊的重要性,概念是,人類需要花多大的力氣去理解這些 tool 該如何使用,LLM也就需要花多大的力氣去理解,所以要去思考:
- 如果你自己都不太知道該怎麼使用你的 tool,LLM也應該不太知道該怎麼使用,好的文件應該會包含,example usage, edge cases, input format requirements, and clear boundaries from other tools. 以下是一些範例:
- 檔案讀取工具
工具名稱:read_file
描述:從文件系統讀取指定檔案的內容
輸入格式:
- path: 字串類型,必須是合法的檔案路徑
- encoding: 字串類型,可選參數,預設為 'utf-8'
使用示例:
read_file("data/users.csv") // 讀取 CSV 檔案
read_file("config.json", encoding="utf-8") // 指定編碼讀取
邊界情況:
- 檔案不存在時返回錯誤
- 檔案過大(>100MB)時發出警告
- 遇到編碼問題時提供詳細錯誤信息
與其他工具的界限:
- 不同於 read_binary,此工具專門處理文本檔案
- 不負責檔案的寫入操作,寫入請使用 write_file 工具
- 網頁爬蟲工具
工具名稱:web_scraper
描述:抓取指定網頁的內容並提取結構化數據
輸入格式:
- url: 字串類型,必須是有效的 HTTP/HTTPS URL
- selector: 字串類型,CSS 選擇器
- timeout: 整數類型,可選,預設 30 秒
使用示例:
web_scraper("https://example.com", ".article-title") // 抓取文章標題
web_scraper("https://example.com", "#price", timeout=60) // 較長超時時間
邊界情況:
- 網站無法訪問時的錯誤處理
- 反爬蟲機制的應對
- 動態加載內容的處理方式
與其他工具的界限:
- 不處理需要登入的網頁,請使用 authenticated_scraper
- 不進行資料分析,僅負責資料獲取
這些我自己都沒有做很好,待改進。🫣
此外還有
- 在設計 function name, parameters, docs的時候,要思考當你把這些 tools 都給它的時候,它是否能夠區別在什麼時機點要使用哪個 tool?
- 設計多一點案例,讓 model 去執行,看看它能否如你預期的去執行 tool
- 不要去教育 LLM 應該如何使用你的 tool,而是要去調整你的 tool 讓 LLM 更加容易理解並使用
- 文章中有提到他們曾經設計了一個 tool,要要傳 relative path,但是 LLM 一直生出 absolute path,與其一直教育 LLM 什麼是 relative path,不如調整 tool 的傳入,讓 LLM 使用起來更正確。