嗨,大家今天過得好嗎?歡迎來到鐵人賽 Day 18。
昨天我們成功用動態變數注入 Prompt,完成了精準的硬體菜單推薦。但是,目前的系統存在一個很大的致命傷:我們的 AI 是一個嚴重的失憶患者。
在真實的商業場景中,使用者看完菜單後,有高達 80% 的機率會接著問:「那如果我預算再加 5000 塊,顯卡可以升級到哪一張?」或者是「幫我把機殼換成白色的。」
如果你現在把這句話發給我們的 Webhook,AI 會一頭霧水,因為它根本不記得上一秒才剛幫你配過電腦。在傳統程式碼開發中,要實作「記憶」,你得把歷史對話存進資料庫(如 Redis),然後每次打 API 時,再把一大串的 [{"role": "user"...}, {"role": "assistant"...}] 陣列重新包裝丟給 LLM。
今天,我們要用 n8n 裡的神奇魔法,不寫半行 Code 解決這個痛點!
回到我們 n8n 的畫布上。還記得 Day 16 我們在 Basic LLM Chain 下方接了一個 Model 子節點嗎?現在,我們要為它裝上第二個子節點:Memory。
Basic LLM Chain 節點下方的另一個缺口(Memory 旁邊的 + 號)。Window Buffer Memory。很多新手會直覺選 Buffer Memory(全域記憶)。這是一個燒錢陷阱!
全域記憶會把使用者說過的「每一句話」都存起來,隨著對話越來越長,你每次呼叫 API 送出的 Token 數就會呈指數型暴增。不僅回應速度變慢,月底收到 OpenAI 的帳單更會讓你痛哭流涕。
Window Buffer Memory (滑動視窗記憶) 允許你設定一個 Window Size(例如設為 4)。這代表 AI 只會「記住」最近 4 輪的對話。對於硬體推薦這種短暫的任務來說,這已經非常足夠了,還能幫你完美控管 API 成本。
把 Memory 節點接上去後,請點開它。你會看到一個非常重要、全端工程師絕對不能漏掉的欄位:Session ID。
如果你把這個欄位留空,或是寫死一個字串(例如 my-session),災難就發生了:全台灣使用你 App 的人,都會共用同一個大腦記憶!
想像一下:A 先生正在配 3 萬塊的遊戲機,B 小姐正在配 1 萬塊的文書機,結果 B 小姐說「預算加 5000」,AI 會把 A 先生的遊戲顯卡塞給 B 小姐... 這絕對會客訴接不完。
為了區分「現在是誰在跟我講話」,我們必須更新前端的資料結構。
請回到 Xcode,在你的 HardwareRequest Model 中新增一個 sessionId:
import Foundation
struct HardwareRequest {
// 每次開啟 App 或發起新對話時,產生一組唯一的 UUID
var sessionId: String = UUID().uuidString
var budget: String = ""
var purpose: String = "3A 遊戲"
}
接著,在 n8n 的 Memory 節點設定中,將 Session ID 欄位改為吃前端傳來的變數:{{ $json.sessionId }}
這樣一來,n8n 底層就會自動幫你管理:當收到 UUID 是 A 的請求,就去記憶庫翻出 A 的對話紀錄;收到 B 就翻出 B 的,兩者互不干擾,完美實現多用戶並發 (Concurrency) 的對話狀態!
進階技巧:記憶持久化
n8n 預設的 Memory 節點是存在記憶體 (RAM) 裡的,只要你重啟 n8n 容器,記憶就會清空。這在開發階段很方便。
等專案要上線時,你可以把Window Buffer Memory替換成Postgres Chat Memory節點,並連上我們 Day 13 建好的資料庫,這樣就能達到永久保存對話歷史的企業級架構!
今天我們只加了一個小小的子節點,就讓整個 AI 引擎完成了一次「從無狀態 (Stateless) 到有狀態 (Stateful)」的超進化。現在,你可以用 App 對 AI 進行連續追問,它都會記得你剛剛的預算和菜單。
但這時候,你可能會遇到一個很嚴重的問題:
當我們在進行連續對話時,AI 為了跟你「聊天」,回傳的文字可能會變成:「沒問題!我幫您把預算加了 5000,以下是新的菜單...」
完蛋了!我們在 Day 8 可是用 Swift 定義了嚴格的 JSON 格式 (HardwareMenu)。一旦 AI 參雜了人類語言,Swift 端的 JSONDecoder 就會當場 Crash,App 直接罷工。
遇到這種情況,我們該如何「馴服」AI,逼迫它不管怎麼聊天,都只能回傳完美且標準的 JSON 格式呢?明天,我們要祭出 n8n 裡對付 LLM 最暴力的殺手鐧:JSON Schema 強制解析!
我們 Day 19 見!