LLM 是無狀態的,但用戶是有狀態的。
Memory 系統要解決的問題只有一個:
讓無狀態的 LLM 表現得像是「記得你」。
怎麼設計這個橋樑,以及這個橋樑的代價——是這篇的核心。
一、核心問題:為什麼需要不同類型的 Memory
沒有 Memory 的 Agent 每次對話從零開始:
Session 1:
User: "我主要用 Python,偏好簡短的回答"
Agent: "好的!" (記不住)
Session 2(三天後):
User: "幫我寫一個排序函數"
Agent: "您好!請問您用哪種程式語言?"
↑
明明說過了,還在問
但「把所有對話都記住」也不可行:
問題 1:儲存量
10K 用戶 × 每天 10 輪 × 365 天 = 3,650 萬條對話記錄
問題 2:Context 限制
把所有歷史塞進 LLM context → 超過 context window
問題 3:相關性
3 年前討論的內容,現在可能完全不相關
結論:需要多種記憶類型,各自解決不同的問題。
二、四種記憶類型:各解決什麼問題
問題 解決方案
─────────────────────────────────────────────────────
當前對話的臨時狀態? → Working Memory(工作記憶)
LLM context window
生命週期:當次對話
記得過去發生過什麼? → Episodic Memory(情節記憶)
向量化的對話歷史
生命週期:跨 session,可衰減
記得這個人是什麼樣的人?→ Semantic Memory(語意記憶)
結構化的 user profile
生命週期:持久化,主動更新
知道怎麼做某件事? → Procedural Memory(程序記憶)
Few-shot examples / Fine-tuning
生命週期:模型層,最持久
三、完整 Memory 架構圖
用戶請求
│
▼
┌──────────────────────────────────────────────┐
│ Memory Retrieval Layer │
│ │
│ ┌──────────────────────┐ │
│ │ Semantic Memory │ ← 用戶偏好、profile │
│ │ (Structured DB) │ 每次對話都載入 │
│ └──────────────────────┘ │
│ + │
│ ┌──────────────────────┐ │
│ │ Episodic Memory │ ← 相關歷史片段 │
│ │ (Vector DB) │ 按語意相似度召回 │
│ └──────────────────────┘ │
└──────────────────┬───────────────────────────┘
│ 組合成 Working Memory
▼
┌──────────────────────────────────────────────┐
│ Working Memory │
│ (LLM Context Window) │
│ │
│ [System Prompt] │
│ [User Profile from Semantic Memory] │
│ [Relevant History from Episodic Memory] │
│ [Current Conversation] │
│ [Current Query] │
└──────────────────┬───────────────────────────┘
│
▼
LLM
│
▼
回應
│
(對話結束後,非同步)
▼
┌──────────────────────────────────────────────┐
│ Memory Update Layer │
│ │
│ ┌────────────────────────────┐ │
│ │ 提取重要資訊 │ │
│ │ → 更新 Semantic Memory │ ← 偏好、事實 │
│ └────────────────────────────┘ │
│ ┌────────────────────────────┐ │
│ │ 壓縮對話摘要 │ │
│ │ → 存入 Episodic Memory │ ← 做了什麼 │
│ └────────────────────────────┘ │
└──────────────────────────────────────────────┘
關鍵設計決策:Memory Update 是非同步的
不在請求路徑上——避免增加用戶感知延遲。
四、各類型記憶的設計細節
Working Memory:Context 組裝策略
Context 組裝優先順序(有限空間的分配):
總預算:128K tokens(以 GPT-4o 為例)
┌──────────────────────┬───────────────┐
│ 區塊 │ Token 預算 │
├──────────────────────┼───────────────┤
│ System Prompt │ ~500 (固定) │
│ User Profile │ ~300 (固定) │
│ Procedural Examples │ ~1,000 (固定) │
│ Episodic Recall │ ~2,000 (彈性) │
│ Current Conversation │ ~剩餘 │
│ Output Reserve │ ~4,000 (保留) │
└──────────────────────┴───────────────┘
當 Conversation 過長時 → 壓縮或截斷,但優先保留固定區塊
Episodic Memory:向量化存取
對話發生
│
▼
[對話摘要提取] ← LLM 非同步提取關鍵資訊
│
▼
[Embedding 生成] ← 轉換為向量表示
│
▼
┌────────────────────────────────┐
│ Vector Database │
│ │
│ memory_id: m_001 │
│ user_id: u_123 │
│ vector: [0.12, -0.34, ...] │
│ content: "用戶詢問 Q4 銷售 │
│ 數字,發現資料庫 │
│ 有延遲問題" │
│ importance: 0.72 │
│ timestamp: 2026-03-15 │
└────────────────────────────────┘
│
│ 下次對話時
▼
[Query Embedding] ← 當前問題轉向量
│
▼
[Similarity Search] → 召回最相關的 top-k 記憶
重要設計:Importance Score
不是所有記憶都值得保留,用 LLM 判斷重要性:
高重要性(0.8+):
├── 用戶明確表達的偏好("我不喜歡...")
├── 未解決的問題("下次要繼續處理...")
└── 重要的決定("確認採用方案 B")
低重要性(< 0.3):
├── 閒聊("謝謝"、"好的")
├── 可以從公開資訊取得的問題(不需要記住回答了什麼)
└── 重複性的例行查詢
Semantic Memory:結構化 Profile
User Profile 結構:
┌─────────────────────────────────────────────────────┐
│ user_id: u_123 │
│ │
│ Identity │
│ ├── name: "Alice" │
│ ├── role: "Data Engineer" │
│ └── department: "Analytics" │
│ │
│ Preferences(從對話中學習) │
│ ├── language: "繁體中文" │
│ ├── response_style: "簡潔,要有範例" │
│ └── technical_level: "intermediate" │
│ │
│ Context │
│ ├── tools: ["Python", "dbt", "BigQuery"] │
│ ├── active_projects: ["data-pipeline-v2"] │
│ └── open_issues: [ │
│ {issue: "dbt 模型跑太慢", since: "2026-05"} │
│ ] │
│ │
│ Meta │
│ └── last_updated: "2026-06-01" │
└─────────────────────────────────────────────────────┘
五、四大工程挑戰與對應設計
挑戰一:記憶衝突
問題情境:
2026-01:User profile 記載 "技術等級:初學者"
2026-06:用戶已經成長為中級工程師
如果不更新 → Agent 一直給太基礎的解釋 → 用戶沮喪
解決策略:
├── 新記憶優先(衝突時以最新記錄為準)
├── Confidence score(多次確認才更新 profile)
└── 主動重確認(6 個月未更新的欄位,主動詢問)
挑戰二:記憶過時(Staleness)
不同記憶欄位的「半衰期」不同:
欄位 建議更新週期 觸發條件
─────────────────────────────────────────────
技術偏好 90 天 對話中有新工具提及
活躍專案 30 天 專案名稱長時間不出現
聯絡資訊 365 天 用戶主動更新
open_issues 自動關閉 問題被標記為解決
設計:為每個欄位設 decay rule,過期後主動重確認
而不是等到用戶抱怨
挑戰三:隱私權
用戶的記憶控制權:
┌─────────────────────────────────────────────┐
│ Memory Privacy API │
│ │
│ GET /memory/{user_id} │
│ → 讓用戶看到 Agent 記了什麼 │
│ │
│ DELETE /memory/{user_id} │
│ → 全部刪除(GDPR right to erasure) │
│ │
│ DELETE /memory/{user_id}/episodic │
│ → 只刪對話歷史,保留 profile │
│ │
│ PUT /memory/{user_id}/profile │
│ → 用戶直接修改自己的 profile │
└─────────────────────────────────────────────┘
挑戰四:記憶召回的精確性 vs 完整性
Precision vs Recall 的 trade-off:
相似度 threshold = 0.95(高精確):
+ 召回的記憶高度相關
- 可能漏掉有用但相似度稍低的記憶
→ 適合:用戶不想看到不相關的「舊事」
相似度 threshold = 0.80(高召回):
+ 相關的記憶幾乎都能找到
- 召回太多不相關記憶,污染 context
→ 適合:確保重要記憶不會被遺漏
實務建議:
重要記憶(importance > 0.8)→ threshold 0.80,寧可多召回
一般記憶(importance < 0.5)→ threshold 0.92,只召回高相關
六、選型決策:什麼場景用什麼記憶
你的 Agent 需要什麼?
│
├── 記住當前對話的狀態?
│ → Working Memory only(默認已有)
│
├── 記住用戶是誰、偏好什麼?
│ → + Semantic Memory
│ 用 PostgreSQL / Firestore 存 profile
│
├── 記住「幾個月前說過什麼」?
│ → + Episodic Memory
│ 用 Pinecone / Weaviate / pgvector 存向量
│
├── 需要跨用戶共享的知識(最佳實踐、FAQ)?
│ → + Procedural Memory(Few-shot in prompt)
│ 或 Fine-tuning(更持久)
│
└── 全部都要?
→ 混合架構
注意:複雜度線性上升,從最小可行的開始
七、快速複習卡
四種記憶類型:
Working Memory → Context window,當次對話,無需額外設計
Episodic Memory → 向量 DB,跨 session 歷史,按相似度召回
Semantic Memory → 結構化 DB,user profile,每次對話載入
Procedural Memory → Few-shot / Fine-tuning,模型層,最持久
四大工程挑戰:
衝突 → 新記憶優先 + confidence score
過時 → decay rule + 主動重確認
隱私 → Memory API(查看、刪除、修改)
精確性 → 依 importance 調整 recall threshold
架構流程:
請求 → 載入 Semantic + 召回 Episodic → Working Memory → LLM
→ 對話結束後非同步更新 Semantic + Episodic
系列導覽:
← (十三)RKK 實戰:Prompt Injection 攻防與 Agent 安全
→ (十五)RKK 實戰:Agent 規模化與 Cache 策略
