Memory Architecture
記憶架構設計,包含長期記憶、情境記憶、短期記憶的三層設計,以及記憶的自動提取、對話反思和四層上下文整合。
設計問題
你上週告訴 AI 助理:
- 搜尋 email 時先給我摘要,再決定要不要看全文
- John 是我們正在談合約的外部律師
今天你再問它「幫我找 John 那封提到 deadline 的信」,它卻像第一次認識你一樣,重新把十幾封全文都丟出來。
這就是沒有記憶系統的 AI。每次對話都是一個全新的陌生人;一個 production-ready 的 AI 助理必須能「記得」用戶。
核心問題:如何設計一個記憶系統,讓 AI 能跨對話累積知識、自動學習用戶偏好,同時控制 token 成本?
記憶和檢索在解不同的問題
Retrieval Pipeline 負責找出「哪幾封 email 相關」; 這篇則負責讓系統記得「這位使用者偏好怎麼搜尋、怎麼呈現結果,以及過去哪些互動方式有效」。
記憶的三個維度
人類的記憶有不同的類型,AI 助理也需要:
Semantic Memory
跨所有對話的持久知識:用戶偏好、背景資訊、重要事實。
- 「用戶搜尋 email 時偏好先看摘要,再決定是否讀全文」
- 「John 是正在往來合約的外部律師」
- 「用戶只想看過去 7 天內的投資人 email」
特性:少量、高價值、需要更新和刪除
Episodic Memory
對過去對話的「回憶」,不是逐字記錄,而是經驗摘要。
- 「上次找合約 deadline 時,先用寄件人 + 日期篩選,再用語意搜尋補漏,效果很好」
- 「上次搜尋 email 時,先給摘要再讓用戶選要讀哪幾封全文,互動最順」
特性:與特定對話綁定、幫助改進未來回應策略
Working Memory
當前對話的上下文。受限於 LLM 的 context window。
- 最近 10 則訊息
- 從舊訊息中語意搜尋出的相關內容,例如「剛才提到的 John、deadline、合約」
特性:每次對話臨時組裝、用完即棄
長期記憶的設計
資料模型
Memory {
id: string
title: string ← 簡短描述,用於搜尋
content: string ← 詳細內容
createdAt: datetime
updatedAt: datetime
}刻意保持簡單,只用一個標題和一段文字。不做更複雜的結構(如 key-value、知識圖譜),因為 LLM 擅長處理自然語言,不需要結構化資料。
記憶搜尋
每次用戶發送訊息時,系統搜尋最相關的記憶:
用戶訊息 → 組成語意查詢 → embedding 搜尋所有記憶 → 取 top 3為什麼限制 3 個?
- 每則記憶佔用 token
- 太多記憶可能干擾 LLM(互相矛盾的資訊)
- 語意搜尋已經保證了最相關的排在前面
記憶注入
搜尋到的記憶以結構化方式注入 system prompt:
<memories>
<memory id="mem-1">
用戶偏好:不吃辣,喜歡日料和義大利菜
</memory>
<memory id="mem-2">
用戶的專案:正在開發一個電商平台,使用 Next.js
</memory>
</memories>用 XML tag 包裝,讓 LLM 清楚區分「這是記憶」和「這是對話內容」。
記憶自動提取
觸發時機:每次對話的 onFinish 回呼(不阻塞串流)
提取邏輯:把對話內容和現有記憶一起餵給 LLM,讓它判斷需要新增、更新或刪除什麼:
輸入:
- 本次對話內容
- 現有的所有記憶
輸出(結構化):
- additions: [{ title, content }] ← 新記憶
- updates: [{ id, title, content }] ← 更新的記憶
- deletions: [id] ← 要刪除的記憶 ID為什麼要看現有記憶?
如果不看,LLM 可能:
- 重複新增已經存在的記憶
- 新增與現有記憶矛盾的內容
- 不知道該更新而非新增
保守策略
Prompt 明確指示「Be conservative — only add memories that will genuinely help personalize future conversations.」
不保守會怎樣?
記憶會快速膨脹,裡面充滿低價值的資訊(「用戶今天問了天氣」),搜尋的信噪比下降。
使用輕量模型
記憶提取用最便宜的模型。它不需要複雜推理,只需要判斷「這段對話有沒有值得長期記住的東西」。
衝突解決
如果同一個 ID 同時出現在 updates 和 deletions 中(LLM 偶爾會犯這種錯),更新優先:
filteredDeletions = deletions.filter(id =>
!updates.some(update => update.id === id)
)情境記憶的設計
Chat Reflection(對話反思)
每次對話結束後,自動生成一份結構化的摘要:
ChatLLMSummary {
tags: string[] ← 2-4 個主題標籤
summary: string ← 一句話摘要
whatWorkedWell: string ← 有效的策略
whatToAvoid: string ← 應避免的做法
}為什麼是這四個欄位?
- tags:用於搜尋索引。「transformer_architecture」比「machine_learning」更有區分力
- summary:快速了解對話做了什麼
- whatWorkedWell:Agent 可以在類似場景中重複成功策略
- whatToAvoid:Agent 可以避免重蹈覆轍
Prompt 設計的關鍵
好的反思是具體且可操作的:
好:"Showing 3 email snippets first helped the user quickly identify the right contract thread"
壞:"Presented the search results well"
好:"Fetching full email bodies too early increased token usage before the user chose which thread mattered"
壞:"Used too many tokens"壞的反思太抽象,對未來的對話毫無幫助。
Related Chats(關聯對話搜尋)
反思摘要會被嵌入到對話的搜尋文本中:
chatToText = Title + Summary + WhatWorkedWell + WhatToAvoid + Tags + Messages每次新對話時,搜尋前 3 個最相關的過去對話,注入 Agent 上下文:
用戶問題 → 語意搜尋過去的對話 → 取 top 3 → 注入 system prompt這樣 Agent 不僅知道「上次聊過什麼」,還知道「上次什麼策略有效」。
短期記憶的設計
問題
對話可能長達 50 輪。但 LLM 有 token 限制,而且越長的 context 回答品質越差(lost in the middle 問題)。
滑動視窗 + 語意檢索
最近 10 則完整保留(維持對話的連貫性),較舊的 40 則透過語意搜尋找出與當前問題相關的 10 則。
結果:LLM 看到 20 則訊息(10 最近 + 10 語意相關),而非 50 則全量。
為什麼不只保留最近 N 則?
只保留最近的會丟失重要的上下文。例如:
- 對話一開始設定的背景(「我在做一個電商專案」)
- 30 分鐘前討論的結論(「我們決定用 PostgreSQL」)
- 中間提到的約束(「預算是 5 萬美元」)
這些可能不在最近 10 則中,但語意搜尋會把它們找回來。
四層上下文的整合
每一層的 token 預算:
| 層級 | 數量上限 | 預估 token |
|---|---|---|
| 長期記憶 | 3 則 | ~300 |
| 情境記憶 | 3 段 | ~1,500 |
| 舊訊息 | 10 則 | ~2,000 |
| 最近訊息 | 10 則 | ~2,000 |
| 合計 | ~5,800 |
加上 system prompt 本身約 1,000 token,總共約 7,000 token 的輸入。這在大部分模型的 context window 中綽綽有餘,同時提供了豐富的上下文。
生產考量
記憶的隱私
記憶包含用戶的個人資訊。在多用戶環境中:
- 記憶必須嚴格隔離(per-user)
- 加密儲存
- 支持用戶刪除(GDPR right to be forgotten)
記憶的品質控制
自動提取的記憶可能有問題(重複、矛盾、錯誤)。需要:
- 用戶可以查看和編輯記憶的 UI
- 定期的記憶清理機制
- 記憶數量上限(避免無限膨脹)
記憶的評估
如何知道記憶系統有沒有用?
- A/B 測試:有記憶 vs 沒記憶的回答品質
- 用戶滿意度:記憶是否提升了個人化程度
- Precision:注入的記憶有多少真的被用到了
後處理的容錯
記憶提取和對話反思都在後處理中執行。它們的失敗不應該影響用戶體驗:
Promise.allSettled([
extractAndUpdateMemories(...), ← 失敗了?log 一下,繼續
reflectOnChat(...) ← 失敗了?log 一下,繼續
])用 allSettled 而非 all,確保互不影響。
重點總結
| 記憶類型 | 來源 | 生命週期 | 搜尋方式 |
|---|---|---|---|
| 長期記憶 | 對話自動提取 + 手動新增 | 永久(可更新/刪除) | 語意搜尋 top 3 |
| 情境記憶 | 對話結束自動反思 | 與對話綁定 | 語意搜尋 top 3 |
| 短期記憶 | 當前對話訊息 | 對話期間 | 最近 10 + 語意 10 |
| 設計決策 | 選擇 | 原因 |
|---|---|---|
| 記憶格式 | 自然語言 title + content | LLM 原生理解,不需要結構化 |
| 提取策略 | LLM 判斷 + 保守原則 | 避免記憶膨脹 |
| 提取時機 | onFinish 異步 | 不阻塞串流 |
| 反思結構 | tags + summary + 策略 | 兼顧搜尋索引和經驗傳承 |
| 視窗策略 | 滑動 + 語意搜尋 | 保留連貫性 + 找回重要上下文 |
Last updated on