## 課程筆記:第三天 - 情境工程、會話與記憶 (Context Engineering, Sessions & Memory) 歡迎來到課程的第三天。繼第一天定義了代理 (Agent),第二天為其提供了工具(手 和眼)之後,今天我們將專注於為代理建立「記憶」。本筆記涵蓋了白皮書中關於情境工程、短期會話 (Sessions) 和長期記憶 (Memory) 的核心概念,以及來自 Notebook LM 團隊和 ADK 框架專家的深度解析。 --- ### 1. 白皮書總覽:賦予代理記憶 * **核心問題**:大型語言模型 (LLM) 根本上是「**無狀態的**」。智慧代理的目標是在每一次對話時,動態地組合出「**完美的上下文視窗**」。 * **會話 (Sessions)**: * **定義**:會話是「**短期工作台**」,用於儲存當前的即時對話歷史。 * **挑戰**:「**上下文溢位**」。隨著對話變長,上下文視窗會被填滿,導致成本上升或超載。 * **策略**:「**壓縮策略**」,例如遞迴摘要 (recursive summarization) 或基於 token 的截斷 (truncation)。 * **長期記憶 (Long-Term Memory)**: * **定義**:一個用於儲存「**持久性知識**」的「**有組織的檔案櫃**」。 * **記憶 vs. RAG**:標準 RAG 檢索的是「**靜態、全域的事實**」(如研究圖書館員);而記憶是「**動態的、用戶特定的**」,並會隨時間演變(如個人助理)。 * **記憶 ETL 流程**: 1. **提取 (Extract)**:使用 LLM 從嘈雜的對話中提取關鍵事實。 2. **鞏固 (Consolidate)**:解決新舊資訊之間的衝突。 3. **合併 (Merge)**:刪除重複內容,保持知識庫的清新。 * **記憶的種類**: * **陳述性記憶 (Declarative Memory)**:知道「**什麼**」(事實)。 * **程序性記憶 (Procedural Memory)**:知道「**如何**」(如何做某事)。 * **生產注意事項**: * **延遲**:記憶的生成必須是「**非阻塞的背景操作**」,以避免應用程式延遲。 * **隱私**:必須嚴格拒絕 PII(個人可識別資訊)以維護用戶信任。 --- ### 2. 專家 Q&A 重點 * **Steven Johnson (NotebookLM 創辦人)** * **NotebookLM 作為上下文 UI**:NotebookLM 本質上是一個「**上下文管理的消費者介面**」。用戶可以上傳來源,並透過「**聚焦**」(focus) 功能,手動控制模型當下應該關注哪些特定文件。 * **大型上下文與 RAG**:目前 NotebookLM 已使用 10M token 的上下文視窗。如果來源超過此限制,系統會自動在背景執行 RAG,包括生成「**查詢的替代版本**」以檢索最佳的相關段落。 * **代理的未來**:希望 AI 不僅是被動使用工具,而是能「**主動建議並觸發**」工具。例如:「嘿,聽起來你正在做...,你應該建立一個...格式的音訊總覽。需要我幫你做嗎?」。 * **記憶的敘事性**:代理需要理解專案的「**時間敘事**」。如果代理只知道所有事實,但沒有時間感,它可能會提出一年前就解決的老問題。 * **Julia (ADK 框架)** * **ADK 的新上下文結構**:ADK 將上下文乾淨地分離為三個組件: 1. **靜態指令 (Static Instruction)**:核心身份、系統指令、安全策略(可被快取)。 2. **用戶訊息 (End User Message)**:原始、乾淨的用戶輸入,便於審計和評估。 3. **回合指令 (Turn Instruction)**:代表「**應用程式的聲音**」,是後端為**每一回合**動態生成的轉向指令。 * **上下文快取 (Context Caching)**:將上下文儲存起來,未來可直接注入,無需再次呼叫 LLM,既「**更快又更便宜**」。 * **壓縮的權衡**:這是在「**成本與上下文品質**」之間的權衡。 * **截斷 (Truncation)**:成本效益高,但有「**風險**」,可能刪除關鍵資訊。 * **壓縮 (Compaction)**:如遞迴摘要,品質高,但需要「**額外一次 LLM 呼叫**」來生成摘要。 * **Jay Alamar & Kimberley (記憶架構與評估)** * **混合式 RAG**:Jay 指出,僅有向量資料庫是不夠的,一個好的檢索系統最好是「**關鍵字搜尋**」和「**向量搜尋**」的**混合系統**。 * **圖譜資料庫 (Graph DB)**:Kimberley 補充,圖譜在「**鞏固**」記憶時特別有用,因為它能讓記憶之間產生關聯,而不只是孤立的實體。 * **RAG 評估 (Jay)**: 1. **檢索 (Retrieval)**:使用傳統資訊檢索指標(如相關性)。 2. **生成 (Generation)**:使用「**LLM 作為裁判**」。RAGAs 等函式庫提供如「**忠實度**」(Faithfulness) 等指標,以評估答案是否忠於提供的上下文。 * **防止記憶被汙染 (Kimberley)**: 1. **信任來源**:始終優先採用「**高信任度的資料**」(例如,用戶在 CRM 中明確上傳的資料 > 對話中間接推斷的偏好)。 2. **嚴格定義**:明確定義「**什麼該被儲存**」。Agent Memory Bank 支援「客製化」(customization) 功能。 3. **防護**:使用如 **Model Armor** 這樣的工具來檢測「**提示注入**」攻擊,防止惡意資訊被存入記憶。 4. **將記憶作為工具**:Kimberley 與 Julia 都提到,可以讓代理「**決定**」何時檢索或儲存記憶,而不是總是將記憶塞入上下文中。 --- ### 3. 程式實驗 (Code Lab) 演練 * **Notebook 1:會話 (Sessions) - 短期記憶** * **核心問題**:API 呼叫是「**天生無狀態的**」。 * **ADK 方案**: * `InMemorySessionService`:提供會話管理,但如果服務重啟,記憶就會消失。 * `DatabaseSessionService`:將會話歷史寫入持久性儲存(如磁碟或資料庫),使會話得以保留。 * **上下文壓縮 (Context Compaction)**: * ADK 提供了 `EventCompactionConfig`,它能自動將冗長的會話歷史壓縮(例如總結),以節省 token。 * **Notebook 2:記憶 (Memory) - 長期記憶** * **核心差異**:會話 (Session) 用於「**單次對話**」;記憶 (Memory) 用於「**跨多個對話**」。 * **ADK 方案**: * `MemoryService`:管理長期記憶,可對接 `VertexAIMemoryBank` 等後端。 * **儲存**:使用 `AddSessionToMemory` 函式將會話中的關鍵資訊寫入長期記憶。 * **檢索**:提供一個名為 `loadMemory` 的「**工具**」給代理,讓代理能**自行決定**何時去查詢長期記憶。 * **自動化**:使用 ADK 的「**回呼**」(Callbacks) 功能(例如 `after_agent_runs`),可以在對話結束後自動觸發記憶儲存,無需手動呼叫。 --- ### 4. 隨堂測驗 (Pop Quiz) * **Q1**: 哪個比喻最能描述會話 (Session) 和記憶 (Memory) 的區別? * **B**: 會話是「**臨時工作台**」,記憶是「**有組織的檔案櫃**」。 * **Q2**: 什麼是情境工程 (Context Engineering)? * **B**: 在 LLM 上下文視窗中「**動態組合和管理資訊**」的過程。 * **Q3**: 陳述性記憶 (Declarative) 和程序性記憶 (Procedural) 的關鍵功能區別是什麼? * **C**: 陳述性是知道「**什麼**」(knowing what),程序性是知道「**如何**」(knowing how)。 * **Q4**: 記憶管理器中「記憶鞏固」(Memory Consolidation) 的主要目標是什麼? * **A**: 將新資訊與現有記憶「**合併**」,同時**解決衝突並刪除重複資料**。 * **Q5**: RAG 與記憶 (Memory) 在代理系統中的根本區別是什麼? * **C**: RAG 扮演「**研究圖書館員**」(知道全域事實),記憶扮演「**個人助理**」(知道用戶)。 --- # Kaggle 實作 - 從提示到行動 好的,這份筆記是針對 Kaggle 課程第三天的**第一個程式實驗 (Code Lab 3a)**,主題為「**會話管理 (Agent Sessions)**」。 ----- 在今天的課程中,我們將學習如何建立「**有狀態的代理**」(stateful agents)。 ### 1\. 核心問題:LLM 的「無狀態性」 當您與 AI 模型進行 API 呼叫時,這些呼叫天生就是「**無狀態的**」(stateless)。這意味著第二次的呼叫,完全不知道第一次呼叫發生了什麼事。 這會導致糟糕的使用者體驗,因為使用者必須不斷重複自己。 ### 2\. 什麼是「會話 (Session)」? 「會話」就是解決方案。在 ADK (Agent Development Kit) 中: * **會話 = 對話 (Conversation)**。 * **會話是「短期記憶」**。 * 一個會話包含: * **事件 (Events)**:如用戶輸入、代理回應、工具呼叫等。 * **狀態 (State)**:一個「便條紙」(scratch pad),用於儲存鍵值對(例如:`user_name: "Sam"`)。 ----- ### 3\. 實驗 1:建立一個「有狀態」的代理 (In-Memory) 在第一個實驗中,我們使用「記憶體內會話服務」,這非常適合快速原型設計。 #### 程式碼講解 1. **建立代理**:首先,和第一天一樣建立一個標準代理。 2. **實例化服務**:接著,我們實例化 `InMemorySessionService`。 3. **啟動運行器**:將這個服務放進我們的代理運行器 (Runner) 中。 <!-- end list --> ```python # 範例程式碼概念 (基於逐字稿描述) # 1. 建立代理 my_agent = adk.Agent(...) # 2. 實例化「記憶體內」會話服務 session_service = adk.InMemorySessionService() # 3. 將服務注入運行器 runner = adk.SessionRunner( agent=my_agent, session_service=session_service ) # 4. 產生一個會話 ID session_id = runner.create_session() ``` #### 測試狀態 * **第一次互動 (在同一個 session\_id 中)**: * **使用者**:「嗨,我叫 Sam。美國的首都是哪裡?」 * **代理**:(回答問題...) * **第二次互動 (在同一個 session\_id 中)**: * **使用者**:「你好,我叫什麼名字?」 * **代理**:「你告訴過我你叫 Sam。」 **結果**:代理記住了我們的名字! 但這有一個問題: * **限制**:`InMemorySessionService` 是「非持久性的」。如果你重新啟動 Kaggle 核心 (Kernel),所有記憶都會消失。 ----- ### 4\. 實驗 2:持久性會話 (Database) 為了在代理重啟後仍保留對話紀錄,我們需要將會話儲存到「持久性」的地方,例如磁碟或雲端資料庫。 #### 程式碼講解 這次的程式碼幾乎完全相同,我們只需要替換掉「服務」即可: ```python # 範例程式碼概念 (基於逐字稿描述) # 1. 建立代理 (同上) my_agent = adk.Agent(...) # 2. 實例化「資料庫」會話服務 # 這會將會話寫入本地的 SQLite 檔案 session_service = adk.DatabaseSessionService(db_path="my_sessions.db") # 3. 將服務注入運行器 (同上) runner = adk.SessionRunner( agent=my_agent, session_service=session_service ) ``` **結果**:現在,即使你重新啟動筆記本,只要使用相同的 `session_id`,代理仍然會記得你叫 Sam。 ----- ### 5\. 實驗 3:上下文壓縮 (Context Compaction) 隨著對話越來越長,將「整個」對話歷史記錄不斷發送給模型,會變得非常昂貴且緩慢。 **解決方案**:ADK 提供了「**上下文壓縮**」(Context Compaction) 功能,它會像「摘要」一樣,將舊的對話事件壓縮起來。 #### 程式碼講解 我們只需在配置中啟用它: ```python # 範例程式碼概念 (基於逐字稿描述) # 1. 定義壓縮設定 compaction_config = adk.EventCompactionConfig( compaction_fn=adk.compaction.LLMSummarizer(), max_turns=10, # 每 10 輪對話壓縮一次 max_tokens=2048 # 壓縮到 2048 個 token 以下 ) # 2. 將設定應用到服務中 session_service = adk.DatabaseSessionService( db_path="my_sessions.db", compaction_config=compaction_config ) ``` **結果**:ADK 會自動在背景壓縮舊的對話,確保我們不會因為 token 過多而使上下文溢位。 ----- ### 6\. 實驗 4:會話狀態 (Session State) 有時候,我們不需要記住整個對話,只需要記住幾個「**有意義的變數**」(例如:`user_name`, `user_location`, `preferences`)。 這就是「**狀態 (State)**」的作用,它就像是會話專屬的「便條紙」。 #### 程式碼講解 筆記本中的練習會教您如何建立工具或邏輯,以便在對話中提取這些變數,並將它們儲存在 `session.state` 中,供以後的對話使用。 **總結**: * **會話 (Sessions)** 用於管理「對話」或「短期記憶」。 * 使用 `DatabaseSessionService` 來實現**持久性**。 * 使用 `Context Compaction` 來**節省成本和 token**。