「一個真正具備靈魂的 AI Agent,必須能在長對話中維持精準的上下文記憶,並在每一次對話結束後,自動從互動中提煉精華(Active Learning),實現無需人工干預的自我進化閉環。」
先前,我們解剖了 FastAPI 主應用程式的 API 控制端點。在 /chat 的處理管線中,有兩個不可或缺的 Services Layer:
1. ConversationMemory:基於 aiosqlite 託管的對話歷程持久化資料庫,負責提供高效率的 20 輪 Sliding Window 檢索與歷史記憶壓縮。
2. LearningModule:負責在對話生成後,透過背景非阻斷任務從對話中萃取金融領域知識點,並以 0.85 相似度門檻進行去重後寫回 ChromaDB 向量庫。
今天我們將深入原始碼,解析這兩大服務模組的底層實作與防禦設計!
ConversationMemory 使用 aiosqlite 實現全非阻斷式的資料庫操作,資料庫檔案預設存放在 data/conversations.db。
1. DDL 與效能索引設計
為了讓資料庫即使在累積超過 10,000 筆對話時依然能在 500 ms 內完成上下文查詢,系統建立了一個關鍵複合索引:
-- conversations 表格與索引建立
CREATE TABLE IF NOT EXISTS conversations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'summary')),
content TEXT NOT NULL CHECK(length(content) <= 10000),
created_at TEXT NOT NULL DEFAULT (datetime('now', 'utc')),
is_summary INTEGER NOT NULL DEFAULT 0
);
-- 關鍵效能優化:針對 session_id 與 created_at 建立降冪複合索引
CREATE INDEX IF NOT EXISTS idx_session_created
ON conversations(session_id, created_at DESC);
2. 20 輪滑動視窗載入
當 /chat 呼叫 load_context(session_id, limit=20) 時,模組會利用複合索引以 created_at DESC, id DESC 撈出最新的 20 筆紀錄,再透過 reversed(rows) 將其反轉為標準的時間軸順序(Oldest first),精準控管 Prompt Token 消耗。
3. 記憶自動壓縮機制
當同一個 Session 的對話累積達到 101 輪 時,系統會自動觸發壓縮邏輯:
(1) 保留最新 20 輪:將前 81 輪舊對話提取出來,拼接為 summary_text。
(2) 資料處理:開啟 SQLite Transaction,刪除舊的 81 筆記錄,並寫入一筆 role='summary'、is_summary=1 的壓縮 Turn。
(3) 成果:將對話歷史重置為精簡的 21 輪(1 筆歷史摘要 + 20 筆近期對話),徹底解決超長對話 Token 溢位問題。
LearningModule 是 Angelina 實現「越用越聰明」的核心。它接受 RAGEngine 與 GeminiGateway 的注入,完全在背景執行。
1. 異步背景任務與領域過濾
系統在回覆使用者後以 asyncio.create_task 觸發,不阻斷 Response 發送。
模組會呼叫 Gemini API(帶入 _EXTRACTION_SYSTEM_PROMPT),專注將對話內容分類並萃取至 5 大金融領域:
(1) 投資策略(股票、基金、資產配置)
(2) 稅務規則(稅務規劃、扣除額、資本利額)
(3) 預算原則(儲蓄率、緊急預備金)
(4) 風險管理(分散風險、停損機制)
(5) 市場數據事實(利率、指數點位、經濟指標)
2. 餘弦相似度向量去重
為避免重複的知識點充斥向量庫,模組在寫入前會呼叫 _rag_engine.search(query=text, top_k=1):
(1) 去重門檻 (DEDUP_SIMILARITY_THRESHOLD = 0.85):若查詢到的最高相似度 $\ge 0.85$,認定為重複知識,直接剔除。
(2) Fail-open 降級保護:若去重查詢過程中發生 Exception,會 Warning 並回傳 False(允許寫入),優先確保知識不會因瞬間網路異常而遺失。
3. 門檻自動重構機制
當新寫入的知識點累積達到 REBUILD_THRESHOLD = 50 筆時,模組會自動搜尋 data/notebooklm 下最新的導出檔案,並觸發 _rag_engine.rebuild_index() 重新進行全量索引優化,確保檢索品質保持在頂峰。
透過 ConversationMemory 與 LearningModule 的代碼解剖,我們確認了:
明日預告:敬請期待~!