最近上了 deeplearning.ai 上面的一個短期課程,是由 Letta 的創辦人開的課。標題也滿吸引人的,叫 LLMs as Operating Systems: Agent Memory ,主要就是介紹透過 Letta 這個 framework 去讓你的 LLM 有記憶能力,隨著使用者的使用,慢慢學習到使用者的偏好與資訊,而且透過這樣多個 agent 的彼此交互作用,可以完成複雜的功能。
Letta 這個 framework 其實是 MemGPT 這篇 paper 的實作,核心概念就是去實現一個 stateful 的 LLM,在不涉及像 fine tune 這些重新訓練 LLM 的方式,去增強 LLM 的表現。我自己看完這個短期課程之後,除了 Letta 本身的 prompt engineering 有很多我覺得值得學習的地方外,對於 “記憶” 方面的抽象化也學習到了很多。
做為介紹 Letta 的第一篇,會先從 Letta 這個 framework 裡面的一些基本概念講起。我自己也把它的 repo 抓了下來研究了一下,其實也有很多精彩的 prompt engineering 設計,不過內容會有一點多,打算留到 Part 2 再說 😛
LLMs as OS
MemGPT 的 paper 裡就有提到,它的靈感是起源于作業系統。在作業系統裡,每個 process 自行運作並管理自己的 memory (heap、stack、hard disk 等), 透過 IPC 去共享資料,而作業系統協調多個 process 去完成使用者所要求的複雜工作;同樣的類比,當我們要實現一個 LLM Agent ,也可以把 LLM Agent 看作一個 process,其 context window 就是 memory 的一部分:
那就像每個 process 都會有狀態,在 Letta 裡也定義了一個 AgentState
:
上面圖片裡的藍色框框就是所謂的 AgentState
,由 memory、tools 與 messages 組成。
熟悉 LLM 的讀者應該對 tools 不陌生,也就是這個 agent 可以有哪些工具可以使用,來協助它生產 prompt 跟底層的 LLM 進行 completion。譬如說可以有個 tool 叫 search_db
,讓 agent 在需要時去搜尋 database 來豐富 prompt 裡含有的資訊,而且在 Letta 裡這些 tools 都是可以根據自己需求進行擴充,也有跟 LangChain 裡的 tool (LangChainBaseTool
) 整合。
Messages 包含了目前 agent 與外界互動的所有歷史訊息,而 Memories 則包含了這個 agent 的各種 memory (後面會解釋)。
Agent 在接收到新的使用者 message 時,會經過兩個步驟:
- Context Compilation:概念上就是以
AgentState
為基礎,讓 agent 可以使用 tools 、進行反思 (Internal Monologue)、更新 Memories 等等作業,最後生成需要給 LLM 的 prompt。 - 將上一步生成的 prompt 交給底層 LLM (
gpt-4o
,llama3
等) 去生成內容。 - 重複上面兩個步驟。
看到這裡,或許會有個很自然的疑問:那 Agent 怎麼跟使用者互動?
這邊就說到了一個我蠻欣賞的設計,Letta 把跟使用者或其他 agent 的互動也定義成一個 tool,譬如像下圖中的 send_message
:
這樣的設計聰明的地方在於,這樣的互動一旦變成了可以使用的 tool 之一,而 agent 又可以自行選擇要用什麼 tool 的情況下,Letta 中的 agent 就可以自行決定它什麼時候要與外界進行互動。譬如說上圖中的 agent 可以在經過第一次的思考時決定先更新它自己的 memory 而使用了 edit_memory
這個 tool,完成更新後在第二次思考時決定使用 send_message
再與外界進行互動,而互動的對象除了使用者外,也可以是另外一個 agent。整個流程大概可以用下面這張圖表示:
其中 Looping via Heartbeats
是指 agent 會進行 Multi-Step Reasoning,這樣說或許有一點而抽象,但從實作上來說,就是 Agent 決定了使用一個工具之後,是不是還要繼續使用另一個工具這件事。以上面的例子來說,假如今天實現了一個餐廳的客服 agent ,在接受到使用者說:“我的名字是 Dboy” 後,第一步使用了 update_memory
在它記憶中記錄下使用者名稱,然後下一步是決定使用 search_db
找看看是否有之前的點餐紀錄,最後使用 send_message
對使用者說:“嗨 Dboy,很高興您再次光臨。上次點的海南雞飯覺得如何啊?目前有在特價要不要考慮一下?”
而這樣多步驟的使用工具就是 Letta 裡所謂的 heartbeats requests 。
那在上圖中 Letta 的 Inner Thoughts 也是一個我看它 source code 覺得學到最多的地方,不過這邊就不展開,等到 Part 2 再說。
所以說整個 Agent 的運作流程就可以像是這樣:
Memory Model
最後來說說 Agent State 裡的 Memories 的一些概念,包含了 Core Memory 、Chat History/Memory 與 External Memory 。
Core Memory 指的是在這次 Agent 運行的過程中,與任務最相關的 memory 。如果用作業系統做類比的話,就是 process 的 memory map。概念上你可以說 Core Memory 就是近似於 LLM 中的 system prompt,但由於 Letta 被設計成 agent 也可以因應任務需要針對 Core Memory 進行編輯,所以大概就是 system prompt 加上可以被編輯的部分:
相關官方文件可以看這裡。
Chat Memory/History 顧名思義,就是這個 agent 與使用者之間過去對話的紀錄。
External Memory 就是一些無法放進 agent 的 context window 中所以必須在外部進行儲存的記憶。其中在被細分成 Archival Memory 與 Recall Memory:
- Archival Memory:指的是被存在 vector database 中可以被搜尋的過去記憶內容。其實就是 Letta 用來支援 RAG 相關功能的物件,只是說與傳統的 RAG 不同,Letta 中把對 archival memory 的修改與搜尋也定義成 tool,所以 Letta 中的 agent 可以自行在 inner thoughts 的階段決定要不要調用 RAG 與要調用哪一種類的 RAG。
- Recall Memory:考慮到 LLM 有限的 context window,當 agent 與使用者長時間互動後,彼此的聊天記錄可能會放不下,這時 Letta 會自動把過去聊天記錄經過彙整後,存入 Recall Memory 中,讓這些紀錄可以在需要的時候以更精練的形式進入 LLM 的 prompt 裡,實現對聊天記錄的長期記憶。
我自己在修課的過程中是看到這張圖很有啟發性:
也就是說,Letta 在不同種類的記憶間定義了一些層級關係,這跟計算機組織架構裡會提到的像是各種層級的 cache 機制很像。譬如 core memory 就像是 CPU 的 cache,讀取最快運算上最有效率可惜非常有限;external memory 可以是一般的 RAM 或是 Disk storage,雖然比 CPU cache 運用起來慢了一些,但是容量相對較大。如果是要實現自己的 agent ,無論是否用 Letta 來實作,這都是一個很好的設計範本。
最後,Letta 中的 memory 以 Block
為單位,每個 Block 可以儲存任意內容但有字數上限,也有 label 標記方便搜尋。
更有趣的是,agent 間可以透過共享 Block 做到比 send_message
更有效率的記憶共享,以作業系統來說就像是 IPC 與 threads 直接 shared memory 的不同吧:
結語
目前使用 deeplearning.ai 上的 notebook 玩了一下 Letta,除了直接從創辦人那邊學到第一手的概念外,也因為中間手殘寫錯一些 tool 的 code 發現了其實 Letta 對於這類錯誤也實作了自我修復機制,雖然無法修復到一樣生成高品質的回覆,不過至少不會因為這樣的錯誤造成程式崩潰。另外,出於好奇心,看了一些 Letta 原始碼中的 prompt engineering,也學到了不少技巧,也看到了一些很不錯的設計,就留待後續的 Part 2 (?) 或 Part 3 (??) 再慢慢說吧。