從第一原理理解 Agent 記憶:從 Python 列表到 Markdown 檔案、向量搜尋、圖形-向量混合,最終找到一個乾淨的開源解決方案

LLM 本質上是無狀態的。每次 API 呼叫都是從頭開始。你在與 ChatGPT 聊天時感受到的「記憶」,其實是每次請求都重新發送整個對話歷史所創造的幻覺。
這個技巧在閒聊時行得通。但當你試圖建立一個真正的 Agent 時,它就會失效。
以下是忽略記憶時會立刻出現的 7 種失敗模式:
- 上下文失憶: Agent 會問你已經提供過的資訊
- 零個人化: 每次互動都感覺很制式
- 多步驟任務失敗: 中間狀態在任務進行中無聲消失
- 重複犯錯: 沒有情節記憶意味著永遠重複同樣的錯誤
- 無法累積知識: 每次對話都從零開始
- 因資訊缺口而產生幻覺: 當上下文溢出時,模型會自行編造
- 身份崩潰: 沒有連續性,就沒有信任
顯而易見的反應是「塞入更多上下文」。這就是為什麼 128K 和 200K token 視窗感覺應該能解決一切問題。
但它們辦不到。
當相關資訊位於長上下文中間時,準確度會下降超過 30%。 這就是廣為人知的「迷失在中間」效應。
上下文是一個共享預算:系統提示、檢索到的文件、對話歷史和輸出都在爭奪相同的 token。
即使在 100K token 的情況下,缺乏持久性、優先級和顯著性,使得單純的上下文長度仍然不夠。

記憶不是要把更多文字塞進提示詞裡。而是要結構化 Agent 記住的內容,讓它能找到重要的東西。
真正有用的認知科學框架
Lilian Weng 在 2023 年提出的架構已成為預設框架:
Agent = LLM + 記憶 + 規劃 + 工具使用。
這四個同等重要的支柱。
她的分類法借鑒了認知科學,人類記憶分為三個系統:
- 感官記憶 捕捉原始的感知輸入,並將其保留不到一秒鐘。只有你注意到的那部分才會被傳遞下去。
- 工作記憶 是進行主動思考的地方。它一次大約能容納 7±2 個項目(Miller 在 1956 年的發現)。一旦分心,內容就會消失。
- 長期記憶 是持久儲存,沒有實際的容量限制。檢索是瓶頸:你可以儲存數百萬件事,卻仍然想不起你需要的那一件。
每一個都直接對應到現代 Agent 架構中的一個組件:

長期記憶本身又可以進一步拆分:
- 情節記憶: 特定的過去事件(「星期二,PostgreSQL 叢集當機了」)
- 語意記憶: 事實和概念(「PostgreSQL 是一個關聯式資料庫」)
- 程序記憶: 技能和工作流程(「當用戶要求退款時,先檢查購買日期」)
連結情節記憶和語意記憶的是 記憶鞏固:重複的特定事件會濃縮成通用的知識。一個注意到「用戶一致偏好摘要」這個模式數十次的 Agent,應該將此轉化為可重複使用的規則。沒有鞏固,你的 Agent 只是在重播個別事件,而不是從中學習。

最簡 Agent,以及最先出問題的地方
撇開框架不談,Agent 就是一個循環:感知、思考、行動。
1class Agent:2 """最簡 AI Agent:感知、思考、行動"""3 def __init__(self):4 self.client = anthropic.Anthropic()5 self.model = "claude-sonnet-4-20250514"67 def run(self, user_input: str) -> str:8 response = self.client.messages.create(9 model=self.model,10 max_tokens=1024,11 messages=[{"role": "user", "content": user_input}],12 )13 return response.content[0].text
告訴它「我有 4 顆蘋果」,然後問「我吃了一顆,還剩幾顆?」它完全不知道你在說什麼蘋果。每次呼叫都是獨立存在的。
第 1 層:Python 列表
每個人都會想到的第一個修正方法:
1class Agent:2 def __init__(self):3 self.client = anthropic.Anthropic()4 self.messages = [] # 整個「記憶」就是一個列表56 def chat(self, user_input: str) -> str:7 self.messages.append({"role": "user", "content": user_input})8 response = self.client.messages.create(9 model="claude-sonnet-4-20250514",10 max_tokens=1024,11 messages=self.messages, # 每次都會發送完整歷史12 )13 reply = response.content[0].text14 self.messages.append({"role": "assistant", "content": reply})15 return reply
現在可以進行多輪對話了。蘋果的問題能正確回答,因為每次呼叫都會重新發送完整的對話。
但有兩個問題很快就會出現:
- 列表會無限制地增長。 大約在第 200 輪對話時,你會達到上下文上限,最舊的訊息會無聲消失。第一輪對話中用戶的名字,遠比昨天無關緊要的笑話更早消失。沒有優先級,只有嚴格的時間順序。
- 所有東西都存在 RAM 中。 Python 程序一結束,你的 Agent 就不知道你是誰了。
第 2 層:用 Markdown 檔案實現持久化
下一步是將記憶寫入磁碟。Markdown 很適合:人類可讀、與 Git 相容,而且 Agent 可以將其作為純文字讀取。Claude Code 正是使用這種模式,搭配 CLAUDE.md 和 MEMORY.md 檔案。
1class MarkdownMemoryAgent:2 def __init__(self):3 self.client = anthropic.Anthropic()4 self.history_file = Path("memory/conversation_history.md")5 self.facts_file = Path("memory/known_facts.md")67 def save_to_disk(self, role: str, content: str) -> None:8 with open(self.history_file, "a") as f:9 f.write(f"### {role} at {datetime.now().isoformat()}\n{content}\n\n")1011 def load_history(self) -> str:12 if self.history_file.exists():13 return self.history_file.read_text()14 return ""1516 def chat(self, user_input: str) -> str:17 self.save_to_disk("user", user_input)18 history = self.load_history()19 response = self.client.messages.create(20 model="claude-sonnet-4-20250514",21 max_tokens=1024,22 system=f"Previous conversation:\n{history}",23 messages=[{"role": "user", "content": user_input}],24 )25 reply = response.content[0].text26 self.save_to_disk("assistant", reply)27 return reply
持久化問題解決了。重新啟動腳本後,對話仍然存在於磁碟上。你也可以維護一個單獨的事實檔案,讓 Agent 隨著時間提取:
1- 用戶的名字是 Sarah2- Sarah 管理 Acme Corp 的後端團隊3- Acme Corp 是一家 B2B SaaS 公司4- 目前正在將生產資料庫遷移到新的 AWS 區域
你可以在任何編輯器中打開這個檔案,確切地看到 Agent 知道什麼,並手動修正。對於原型開發來說非常有用。
只有 4 個事實時,這完美運作。將整個檔案載入上下文,LLM 就能處理任何關於 Sarah、她的公司或她所在行業的問題。
現在快轉三個月。你的 Agent 已經提取了 2,000 個事實和 200 個對話記錄。磁碟上有超過 500K token 的 Markdown 內容,而你的上下文視窗只有 128K。
你無法再載入所有內容了。 你需要選擇性地只檢索與當前查詢相關的事實。使用平面檔案時,你唯一的選擇是關鍵字搜尋:
1# 用戶問:「我們的雲端遷移進度如何?」2grep("cloud migration", facts_file)3# 回傳:[]4# 磁碟上的事實是「migrating production database to a new AWS region.」5# 完全沒有出現「cloud migration」這個詞。67# 用戶問:「哪個團隊在負責資料庫工作?」8grep("database team", facts_file)9# 回傳:[]10# 一個事實說 Sarah「manages the backend team」。另一個事實說該團隊11# 正在「migrating production database」。但沒有任何一行同時包含12# 「database」和「team」這兩個詞。
在小規模時,Markdown 檔案可行。但在真實規模下,它們強制使用關鍵字檢索,而關鍵字無法處理同義詞、改寫或跨事實的連結。
資訊就在磁碟上。但你無法載入全部,而且關鍵字搜尋太脆弱,無法找到正確的片段。
如果你用過 OpenClaw,你就會看過這種情況。它將記憶儲存為 Markdown 檢查點檔案,經過數週的日常使用後,隨著上下文累積和壓縮,較早的事實會悄悄流失。儲存空間還在,但檢索功能不見了。
沒有智慧檢索的儲存,就像一個沒有目錄的圖書館。
第 3 層:向量搜尋及其遇到的瓶頸
加上嵌入。將你的 Markdown 分塊,嵌入這些塊,然後透過餘弦相似度進行搜尋。現在「database」可以匹配到「PostgreSQL」,因為它們的向量在嵌入空間中位置相近。同義詞問題解決了。
然後你遇到了新的瓶頸。考慮向量資料庫中的這三個事實:
1- 「Alice 是 Atlas 專案的技術負責人」2- 「Atlas 專案使用 PostgreSQL 作為其主要資料儲存」3- 「PostgreSQL 叢集在星期二發生了一次中斷」
用戶問:「Alice 的專案是否受到星期二中斷的影響?」
查詢提到了 Alice 和星期二的中斷,所以向量搜尋會將第一個和第三個事實排在前面。但關鍵的橋樑——「Atlas 專案使用 PostgreSQL」——既沒有提到 Alice,也沒有提到星期二。它是連結的片段,卻是最不可能浮現出來的那一個。
每個事實都是嵌入空間中的一個孤立點。連結它們的關鍵脈絡,對向量來說是看不見的。

這不是邊緣案例。這是真實世界問題的正常樣貌。商業知識本質上是關聯性的:人屬於團隊,團隊擁有專案,專案依賴系統,系統會發生事件。任何跨越兩個或更多跳躍的查詢,都超出了平面向量檢索所能回答的範圍。
能力矩陣
每一層都解決了前一層的痛苦,但卻揭示了更深層的問題:

你需要在單一記憶層中同時擁有持久性、語意理解和關聯推理能力。
自己建構這個系統意味著要將向量資料庫、圖形資料庫、關聯式儲存、實體提取器、去重複化管線和邊緣權重系統全部黏合在一起。在你寫任何一行 Agent 邏輯之前,這就需要數週的基礎設施工作。
我一直在使用一個能乾淨填補這個空白的解決方案。它完全開源,在同一個架構下處理所有三種儲存模式,而且你可以在幾分鐘內讓它運作起來。讓我們來談談 Cognee。
Cognee:三種儲存、一個引擎、四個呼叫
Cognee 是一個專為 Agent 記憶打造的開源知識引擎。它將向量搜尋與知識圖譜以及關聯式溯源層結合成一個單一系統。
整個 API 表面只有四個非同步呼叫:
1import cognee23await cognee.add("你的文件內容") # 攝取任何內容4await cognee.cognify() # 建立知識圖譜 + 嵌入5await cognee.memify() # 自我改善記憶6await cognee.search("你的查詢") # 透過推理進行檢索
在這四個呼叫背後,是一個三層儲存架構。

為什麼是三種儲存而不是一種?
每種儲存都捕捉了其他儲存無法捕捉的知識維度:
- 關聯式儲存 → 溯源:資料來自哪裡、何時被攝取、誰有權限存取
- 向量儲存 → 語意:內容的意義是什麼、它與什麼相似
- 圖形儲存 → 關係:實體如何連結、什麼導致什麼、誰向誰報告
扁平化任何一個,你就會失去對檢索準確性很重要的資訊。
預設的技術棧是 SQLite + LanceDB + Kuzu,完全嵌入式且基於檔案。pip install cognee 加上一個 LLM API 金鑰,你就可以開始運行了。
不需要 Docker,不需要外部服務。
對於生產環境,可以將 SQLite 換成 Postgres,LanceDB 換成 Qdrant/Pinecone/pgvector,Kuzu 換成 Neo4j/FalkorDB/Neptune。
無論哪種方式,都是相同的四個呼叫 API。
cognify 實際上做了什麼?
cognee.cognify() 執行一個多階段管線,將原始文字轉換為結構化、相互連結的知識:
- 文件分類:按類型和領域分類
- 權限檢查:用於多租戶存取控制
- 區塊提取:尊重段落結構(不是固定大小的切割)
- 實體和關係提取:透過 LLM 進行,並透過內容雜湊自動去重複化
- 摘要生成:用於高效檢索
- 雙重索引:進入向量儲存(嵌入)和圖形儲存(邊緣)
去重複化步驟比聽起來更重要。如果同一個實體出現在 50 份文件中,Cognee 會將其合併成一個帶有 50 條入邊的單一圖形節點。你的 Agent 不再將「Alice」視為 50 個不同的陌生人。而且這個管線預設是增量式的:只有新的或更新的檔案才會被重新處理。

每個圖形節點都有一個對應的嵌入。 這種雙重表示是核心技巧:透過向量進入(找到語意相似的內容),然後透過圖形離開(沿著關係找到連結的實體),或者反過來。這就是為什麼多跳躍查詢可以在不犧牲語意搜尋的情況下運作。
Memify:會學習的記憶
memify() 是 Cognee 與所有「攝取並搜尋」工具的不同之處。它對圖形執行一個受強化學習啟發的最佳化過程:
- 強化:那些導致良好檢索結果的有用路徑
- 修剪:那些許久未被觸及的過時節點
- 自動調整:基於實際使用情況的邊緣權重
- 添加衍生事實:透過識別隱含關係
一個客戶支援 Agent 的圖形會自然地強化產品文件和退款政策的路徑,同時讓很少被查詢的 HR 邊緣衰減。圖形會隨著時間發展出自己的相關性判斷。

十四種檢索模式
Cognee 提供了 14 種搜尋模式。你會實際用到的有這些:

使用 Cognee 記憶建立一個真實的 Agent
以下是將 Cognee 接入感知-思考-行動循環的完整模式:
1import cognee2from cognee import SearchType34class CogneeMemoryAgent:5 """具有圖形-向量混合持久記憶的 Agent。"""67 def __init__(self, session_id: str = "default"):8 self.llm_client = OpenAI()9 self.session_id = session_id1011 async def ingest(self, text: str, dataset: str = "main"):12 await cognee.add(text, dataset)13 await cognee.cognify([dataset])1415 async def recall(self, query: str) -> str:16 results = await cognee.search(17 query_text=query,18 query_type=SearchType.GRAPH_COMPLETION,19 session_id=self.session_id,20 )21 return results[0] if results else ""2223 async def chat(self, user_input: str) -> str:24 context = await self.recall(user_input)25 messages = [26 {"role": "system", "content": "你很有幫助。請使用記憶上下文。"},27 {"role": "system", "content": f"記憶上下文:\n{context}"},28 {"role": "user", "content": user_input},29 ]30 response = self.llm_client.chat.completions.create(31 model="gpt-4o-mini", messages=messages32 )33 reply = response.choices[0].message.content34 await cognee.add(35 f"用戶:{user_input}\n助理:{reply}",36 "conversations"37 )38 await cognee.cognify(["conversations"])39 return reply
記憶循環:攝取、提取、儲存、檢索、回應、再次儲存。每一輪對話都會豐富知識圖譜,而增量處理意味著你只需要為索引新內容付費。
會話記憶會自動處理代詞解析:
1await cognee.search(query_text="Alice 住在哪裡?", session_id="conv_1")2await cognee.search(query_text="她做什麼工作?", session_id="conv_1")3# 「她」會從會話上下文中解析為 Alice
多租戶功能內建於圖形層級,具有每個資料集的權限(讀取、寫入、刪除、分享)。不是命名空間隔離,而是實際的圖形層級隔離。
實務上的前進方向
如果你今天正在建立一個 Agent,真正的起點問題是:「我的 Agent 需要記住什麼,以及它將回答什麼樣的問題?」
如果你的查詢只需要相似度搜尋(「找像這樣的對話」),那麼純向量記憶就夠了。一旦查詢跨越了實體邊界(「Alice 的專案是否受到星期二中斷的影響?」),你就需要圖形遍歷。
你可以自己將獨立的向量、圖形和關聯式儲存串接起來。走這條路的團隊通常會花費數週時間在基礎設施上,來建立一個仍然無法從自身使用中學習的記憶層。
Cognee 將這一切濃縮成四個 API 呼叫。嵌入式預設值讓你在幾分鐘內就能開始運行。可替換的後端(Postgres、Qdrant、Neo4j)則能帶你進入生產環境,而無需更改你的 Agent 程式碼。
智慧需要結構,而不僅僅是儲存。 這三種儲存模式(關聯式、向量、圖形)不是相互競爭的選項。它們是同一個記憶系統中互補的層。將它們視為如此,才能把一個無狀態的 LLM 包裝器,變成一個真正會學習的東西。
你希望你的 Agent 明天能記住,但今天卻忘記了的下一件事是什麼?從那裡開始。
👉 [在 GitHub 上查看 Cognee →](https://github.com/topoteretes/cognee),給它一個星星,然後試著將它接入你的下一個 Agent。
四個非同步呼叫,一個 pip install,你就可以開始運行了。
以上就是今天的內容!
如果你喜歡這篇文章:
找我 → @akshay_pachaar ✔️
我每天都會分享關於 AI、機器學習和 Vibe Coding 最佳實踐的教學和見解。





