Agent Memory:大多數記憶系統不需要向量檢索
來源: https://blog.aihao.tw/2026/04/28/agent-memory-no-vector/ 發布: 2026-04-28 擷取: 2026-04-30 標籤: AI、Agent、Memory、RAG
一句話記憶
業界成熟產品(ChatGPT、Claude、OpenAI、Mastra)做 agent memory 都沒用向量檢索;它們用「結構化小檔 + 固定區塊注入 + 背景重寫」就解決了。向量只在「通用框架/多租戶/萬條以上」三種特殊情況才需要。
為什麼這篇容易混亂?先建骨架
文章一次塞了三層東西,讀起來像三本書混在一起:
| 層級 | 內容 | 記法 |
|---|---|---|
| A. 框架 | 記憶分類、操作、時機、挑戰 | 「上下文內外 × ADD/UPDATE/DELETE/NOOP × Hot/Background」 |
| B. 五個產品案例 | ChatGPT / Claude Code / Anthropic API / OpenAI / Mastra | 看下方對照表 |
| C. 觀點 | Plastic Labs:Memory as Reasoning | 「不是儲存事實,是推理使用者模型」 |
讀的時候先確認自己在哪一層,就不會被淹沒。
A. 框架(4 個關鍵字鎖住)
- 存哪裡:context-in(短期)vs context-out(長期)
- 動作:ADD / UPDATE / DELETE / NOOP(不寫也是決策)
- 時機:Hot Path(即時、顯式)vs Background(延遲、隱式)
- 挑戰 4:相膨刪替——相關性、記憶膨脹、刪除機制、新舊替換
記憶不是 LLM 內建能力,是工程設計的結果。
B. 五個產品並排對照(這張表是本文精華)
| 產品 | 儲存形式 | 結構特色 | 寫入時機 | 用向量? |
|---|---|---|---|---|
| ChatGPT | Prompt 內 4 層注入 | ① 環境資訊 ② 長期事實 ③ 最近對話摘要 ④ 當前對話 | 顯式累積 | ❌ |
| Claude Code | 檔案系統 + 索引 | 三層帶寬:索引常駐/主題檔按需/原始紀錄搜尋不讀 | autoDream 背景重寫(合併/去重/解衝突) | ❌ |
| Anthropic API (Memory Tool / Store) |
純檔案系統 | Agent 自決何時讀;建議「多個小聚焦檔」優於「少數大檔」;每筆 ≤100KB | Agent 主動 | ❌ |
| OpenAI (旅遊管家範例) |
JSON 結構化狀態 | ① 個人檔案 ② 全域筆記 ③ session 暫存 ④ 旅行歷史 | 4 觸發點:開始/進行中/被裁時/結束 | ❌ |
| Mastra (LongMemEval 94.87%) |
system prompt 注入 | 觀察者 + 反思者:輕量模型壓縮對話為「優先級標記觀察條目」,達閾值合併 | 背景 | ❌ |
共通模式(記三點):
- 結構化小知識塊
- 直接注入 prompt(搭配提示快取)
- 背景流程做整理(壓縮、合併、淘汰)
真正難點:4 個治理問題
不是「找不到」,而是「該不該記、怎麼整理、什麼時候忘」:
- 寫入篩選——瑣碎/瞬時/低價值的不要進
- 整理機制——合併去重 + 衝突調解 + 版本替換 + 衰減歸檔
- 讀取規則——優先序:當前訊息 > session 記憶 > 全域記憶
- 遺忘策略——主動淘汰,避免被舊理解困住
LangChain Agent Builder 有全職員工做記憶 prompt engineering——可見這是 prompt 問題,不是檢索演算法問題。
C. Memory as Reasoning(Plastic Labs 觀點)
把記憶從「資料庫問題」重新定義成「推理問題」:
- ❌ 舊:抽事實 → 向量化 → 儲存 → 檢索
- ✅ 新:推論出可操作的使用者模型,重組成有用上下文
要點:
- 寫入:只記「未來 3 個月以上仍會影響回答品質」的資訊
- 召回:兩階段——候選召回 → 「哪些記憶會改變措辭/判斷/優先序/風險提醒」
- 衝突:新舊不一致 → 觸發調解,不是覆蓋
- 分層:明確事實 / 推論觀察 / 工作偏好 / 暫時脈絡 / 待驗證假設
五層架構:原始事件 → 原子記憶 → 推理觀察 → 任務當下上下文 → 更新迴圈
何時才需要向量檢索(3 個例外)
- 通用框架(mem0、Letta 等)——不知用戶會放什麼,要模糊匹配
- 企業多租戶——百萬級記憶 + 跨用戶知識共享
- 記憶累積到萬條——需要候選召回階段
反過來的判準:你的 agent 場景如果記憶 < 數千條、用戶可預期、結構可設計 → 不需要向量。
一張流程圖(自己畫的腦圖)
新訊息進來
│
├─ Hot Path:要不要立刻 ADD/UPDATE/DELETE/NOOP?
│ └─ 是 → 寫入結構化檔案
│
└─ Background(延遲)
├─ 觀察者:壓縮成優先級標記
├─ 反思者:合併去重、解衝突、淘汰過時
└─ 重寫索引
回應時
│
└─ 注入 prompt:環境 + 長期事實 + 最近摘要 + 當前對話
└─ (超大規模才加:向量候選召回)
金句
「記憶不是把過去保存下來,而是判斷:留下什麼、刪掉什麼、相信什麼。」
記憶系統的核心挑戰在於「治理過去如何進入現在」,而非技術容量。
如果只記三件事
- 業界都用「結構化小檔 + 注入 + 背景重寫」,不用向量。
- 真正的難點是治理(寫/整/讀/忘),不是檢索。
- 向量只在「通用框架/多租戶/萬條以上」三種例外才需要。
附錄 A:Memory 為什麼不適合向量檢索?
向量搜尋是針對「大規模相似度」設計的,剛好打不到 memory 的核心需求。
| Memory 的特性 | 向量搜尋的假設 | 衝突 |
|---|---|---|
| 量小(單用戶通常 < 數千條) | 為百萬級設計才划算 | 殺雞用牛刀,索引 overhead 比暴力還貴 |
| 要精準(記得「我女兒叫小美」就是要叫對) | 近似最近鄰(ANN),recall < 100% | 記憶錯了比沒記憶還糟 |
| 要可解釋(為什麼提這件事?) | 黑盒 cosine similarity | 你不知道為什麼某條被召回 |
| 常更新(人會改變、會矛盾) | 索引偏好 batch 寫入 | 新資料分布漂移,老 index 品質下降 |
| 要可刪可改(GDPR / 用戶要求忘記) | 多數 ANN index 真刪很麻煩 | 只能 mark-as-deleted 再 rebuild |
| 語意關聯複雜(「她最近壓力大」連到「她下週要 demo」) | 只看 embedding 餘弦距離 | 推理鏈不是相似度能抓的 |
向量搜尋實際會出包的方式
- 語意相近 ≠ 該被想起
- 「我討厭香菜」跟「我喜歡香菜」embedding 很近,餘弦距離抓不出反義
- 「上週去日本」跟「明年想去日本」也很近,但時態完全顛倒
- 長尾關鍵字打不中
- 用戶名字、產品代號、特殊術語在 embedding 空間沒區隔
- 「叫我 Doro 不要叫 Fan-Cheng」這種偏好用向量超容易漏
- 時間維度消失
- Embedding 沒有「最近」概念,三年前說的話跟昨天說的等權
- 要靠額外 metadata 欄位來補,反正都要欄位了不如直接用 SQL
- 無法表達否定 / 條件
- 「除了週一以外都可以開會」——向量搜尋無法精準召回這條約束
- Filter 才是對的工具,不是相似度
附錄 B:分層組合(1+2+3+4)的邊際成本
實務上 production 系統不是「選一個」,而是分層疊加。但每多一層都要付稅。
四層架構
選項 1:常駐 prompt
┌──────────────────────────────┐
│ 高確信度、體積小 │
│ 索引 + 核心事實 │
└──────────────────────────────┘
│ LLM 看了覺得不夠 →
▼
選項 2:按需讀檔
┌──────────────────────────────┐
│ 結構化、命名清楚 │
│ Read(memory/foo.md) │
└──────────────────────────────┘
│ 不知道在哪 →
▼
選項 3:SQL / 全文檢索
┌──────────────────────────────┐
│ 精準關鍵字、可 filter │
│ SELECT ... WHERE ... LIKE ... │
└──────────────────────────────┘
│ 字面找不到,但語意像 →
▼
選項 4:向量檢索
┌──────────────────────────────┐
│ 模糊匹配、語意接近 │
│ 候選多但有雜訊 │
└──────────────────────────────┘
確信度由上往下遞減,召回率由上往下遞增。每層處理它最擅長的事。
「直接讓 LLM 從候選裡挑」的四個代價
直覺上:「就把 100 條候選都丟給 LLM,反正它會挑」——理論上對,實務上付這四筆稅:
代價 1:Token 成本
- 向量召回常一次回 50–100 個候選
- 100 條 × 平均 200 token = 20,000 token
- Opus 每次查詢 $0.30;一天一萬次 = $3,000/天
- → 通常需要 rerank 階段(小模型先過濾到 top 5–10)
代價 2:Attention dilution(”lost in the middle”)
LLM 不是真的能完美過濾雜訊:
- 即使模型有 1M context,雜訊會壓低相關內容的注意力
- 研究顯示:context 中間的內容召回率明顯比頭尾低
- 100 條候選裡只有 3 條相關 → 模型也可能挑錯
→ 「LLM 能不能挑」跟「LLM 會不會挑對」是兩件事。
代價 3:錯誤記憶會被當真(最關鍵)
LLM 沒有判斷「這條記憶本身是不是真的」的能力。
舉例:
- 向量召回一條舊記憶:「Doro 偏好 Vue.js」
- 半年前已改用 React,新記憶寫了「Doro 主要用 React」
- 兩條都被召回,LLM 看到「偏好 Vue」就直接用了
LLM 看到的是「上下文裡有這條」,不是「這條的可信度」。 → 必須在 retrieval 前就治理好記憶:衝突調解(新蓋舊)、時間戳、標記過時/待驗證。 → 這就是 Plastic Labs 講的「Memory as Reasoning,不是儲存事實」的真正意思。
代價 4:延遲
| 層 | 延遲 |
|---|---|
| Prompt 注入 | 0 ms(已經在) |
| 檔案讀取 | ~10 ms × N 個檔 |
| SQL | ~50 ms |
| 向量檢索 | ~100–500 ms |
| 加 rerank | +500 ms |
互動式 agent 需要 < 2s 回應,疊太多層會超時。
該疊到哪層的判準
| 記憶量 | 建議架構 |
|---|---|
| < 100 條 | 只用 1(全塞 prompt) |
| 100–數千條 | 1+2(Claude Code 模式) |
| 數千–數萬條 + 需要條件查詢 | 1+2+3(加 SQL filter) |
| 數萬條以上 + 模糊查詢 | 1+2+3+4(向量候選 → rerank → 注入) |
判準不是「可不可以加」,而是「這層帶來的召回值不值它的成本」。
該誰決定?三層分工
| 該誰決定 | 該決定什麼 |
|---|---|
| 資料治理層(寫入時 + 背景重寫) | 這條記憶還算不算數?有沒有過期?有沒有衝突? |
| 檢索層(SQL / 向量) | 這條看起來相關不相關? |
| LLM(在 prompt 裡) | 這次回應該用哪幾條?怎麼用? |
LLM 是最後一道過濾,不是唯一一道。記憶系統的工程量主要花在前兩層。
一句話
1+2+3+4 可以組合,但每多一層要付 token、延遲、雜訊三筆稅。LLM 能在用的時候挑,但挑不出「這條記憶本身是不是真的、是不是過期」——這要在寫入跟整理階段就治理好。