- Published on
第一次學 Kotlin Koog AI 就上手 Day 14:賦予 AI 記憶:實現個人化服務的關鍵
在前一篇文章中,我們學習了提示快取機制,大幅提升了系統回應速度並節省了 API 成本。快取幫助我們解決了重複查詢的效能問題,而回應時間可能從秒級降至毫秒級
今天我們要探索一個完全不同的概念:Agent 記憶體系統。如果說快取是為了短期效能優化,那記憶體系統就是為了長期學習和個人化。透過記憶體功能,AI 代理能夠記住重要資訊、學習使用者偏好,並在未來的互動中提供更智慧、更個人化的服務
記憶體 vs 快取:關鍵差異一覽表
在深入實作之前,讓我們用一個簡單的對比表格來理解這兩個系統的差異
比較項目 | 快取系統(Day 13) | 記憶體系統(Day 14) |
---|---|---|
主要目的 | 短期效能優化 | 長期學習和個人化 |
儲存內容 | 完整的提示和回應 | 提取的重要資訊和偏好 |
生命週期 | 短期(分鐘到小時) | 長期(天到年) |
使用時機 | 重複查詢的快速回應 | 個人化服務和智慧推薦 |
範例應用 | "API 金鑰設定步驟" 的完整回答 | "使用者偏好詳細說明" |
價值體現 | 節省成本,提升速度 | 增強體驗,建立關係 |
簡單來說
- 快取:讓 AI 回答得更快 ⚡
- 記憶體:讓 AI 回答得更貼心 ❤️
PersonalizedGreeter:個人化問候範例
讓我們透過一個簡單而實用的範例來學習記憶體系統。我們將建立一個 PersonalizedGreeter
,它能
- 檢查記憶體中是否有使用者姓名
- 如果有,提供個人化問候
- 如果沒有,詢問姓名並儲存到記憶體中
在這裡是把對話中的
姓名
截取出來,如果要比較簡單的話,可以把整個句子塞到記憶體裡面,拿出來丟給 AI,它應該有辦法處理 XD
基礎記憶體配置
class PersonalizedGreeter {
// 配置 memory provider
private val memoryProvider = LocalFileMemoryProvider(
config = LocalMemoryConfig("personalized-greeter"),
storage = SimpleStorage(JVMFileSystemProvider.ReadWrite),
fs = JVMFileSystemProvider.ReadWrite,
root = Path("memory/user-data"),
)
// 定義使用者資訊概念
private val userInfoConcept = Concept(
"user-info",
"使用者的基本資訊,包含姓名和偏好",
FactType.SINGLE
)
// 使用者記憶體主題
private val userSubject = object : MemorySubject() {
override val name: String = "user"
override val promptDescription: String = "使用者的個人資訊和偏好設定"
override val priorityLevel: Int = 1
}
// 創建具備記憶體功能的 Agent
private val agent = AIAgent(
executor = simpleOpenAIExecutor(ApiKeyManager.openAIApiKey!!),
systemPrompt = createSystemPrompt(),
llmModel = OpenAIModels.CostOptimized.GPT4_1Mini
) {
// 安裝記憶體功能
install(AgentMemory) {
memoryProvider = this@PersonalizedGreeter.memoryProvider
agentName = "personalized-greeter" // Agent 識別名稱
featureName = "personalized-greeter" // 功能名稱
organizationName = "demo-app" // 組織名稱
productName = "greeting-service" // 產品名稱
}
}
// 💡 參數與記憶體作用域的關係
//
// AgentMemory 安裝時的參數會影響記憶體的組織層級
// • agentName → 對應 MemoryScope.Agent
// • featureName → 對應 MemoryScope.Feature
// • productName → 對應 MemoryScope.Product
// • organizationName → 提供額外的組織上下文
//
// 這些參數幫助建立記憶體的層級結構,讓您可以根據不同的業務需求來組織和檢索記憶體
private fun createSystemPrompt() = """
你是一個友善的個人化助手。
核心能力:
- 記住使用者的姓名和偏好
- 提供個人化的問候和服務
- 在初次見面時主動詢問並記住使用者資訊
行為準則:
- 如果知道使用者姓名,要親切地稱呼他們
- 如果是新使用者,要禮貌地詢問姓名並記住
- 始終保持友善和專業的態度
- 使用正體中文回應
""".trimIndent()
/**
* 處理使用者互動的主要方法
*/
suspend fun greetUser(userInput: String): PersonalizedResponse {
try {
// 嘗試從記憶體載入使用者資訊
val userName = loadUserName()
// 根據是否有記憶決定回應方式
val enhancedInput = if (userName != null) {
// 有記憶:提供個人化上下文
"使用者姓名:$userName\n使用者說:$userInput"
} else {
// 無記憶:正常處理
userInput
}
// 處理請求
val response = agent.run(enhancedInput)
// 嘗試從回應中學習新資訊
learnFromInteraction(userInput, response)
return PersonalizedResponse(
response = response,
hasMemory = userName != null,
userName = userName
)
} catch (e: Exception) {
return PersonalizedResponse(
response = "很抱歉,系統暫時無法處理您的請求。",
hasMemory = false,
error = e.message
)
}
}
/**
* 從記憶體載入使用者姓名
*/
private suspend fun loadUserName(): String? {
return try {
val userMemories = memoryProvider.load(
concept = userInfoConcept,
subject = userSubject,
scope = MemoryScope.Product("personalized-service")
)
userMemories.firstOrNull()?.let { memory ->
when (memory) {
is SingleFact -> memory.value
else -> null
}
}
} catch (e: Exception) {
println("⚠️ 載入使用者記憶時發生錯誤: ${e.message}")
null
}
}
/**
* 從互動中學習新資訊
*/
private suspend fun learnFromInteraction(
userInput: String,
response: String
) {
try {
// 簡單的姓名識別邏輯
when {
userInput.contains("我是") || userInput.contains("我叫") -> {
val possibleName = extractNameFromInput(userInput)
if (possibleName != null) {
saveUserName(possibleName)
}
}
response.contains("請問您的姓名") || response.contains("可以告訴我您的名字") -> {
// AI 正在詢問姓名,暫不學習
}
}
} catch (e: Exception) {
println("⚠️ 學習過程中發生錯誤: ${e.message}")
}
}
/**
* 從使用者輸入中提取姓名
*/
private fun extractNameFromInput(input: String): String? {
val patterns = listOf(
Regex("我是\\s*([^\\s,,]{2,4})"),
Regex("我叫\\s*([^\\s,,]{2,4})"),
Regex("叫我\\s*([^\\s,,]{2,4})")
)
for (pattern in patterns) {
pattern.find(input)?.let { matchResult ->
return matchResult.groupValues[1]
}
}
return null
}
/**
* 將使用者姓名儲存到記憶體
*/
private suspend fun saveUserName(userName: String) {
try {
memoryProvider.save(
fact = SingleFact(
concept = userInfoConcept,
value = userName,
timestamp = System.currentTimeMillis()
),
subject = userSubject,
scope = MemoryScope.Product("personalized-service")
)
println("🧠 已記住使用者姓名:$userName")
} catch (e: Exception) {
println("⚠️ 儲存使用者資訊時發生錯誤: ${e.message}")
}
}
}
/**
* 個人化回應資料類別
*/
data class PersonalizedResponse(
val response: String,
val hasMemory: Boolean,
val userName: String? = null,
val error: String? = null
)
實際使用範例
讓我們透過一個完整的示範來看看記憶體系統如何運作
suspend fun main() {
val greeter = PersonalizedGreeter()
println("🤖 個人化問候助手啟動")
println("=".repeat(50))
// === 第一次互動:新使用者 ===
println("\n👋 第一次見面")
println("=".repeat(20))
val firstResponse = greeter.greetUser(
userInput = "你好"
)
println("使用者:你好")
println("助手:${firstResponse.response}")
println("📊 記憶體狀態:${if (firstResponse.hasMemory) "有記憶" else "無記憶"}")
delay(1000)
// === 自我介紹:儲存姓名 ===
println("\n📝 自我介紹")
println("=".repeat(20))
val introResponse = greeter.greetUser(
userInput = "我是 Cash"
)
println("使用者:我是 Cash")
println("助手:${introResponse.response}")
println("📊 記憶體狀態:${if (introResponse.hasMemory) "有記憶" else "無記憶"}")
println("👤 記住的姓名:${introResponse.userName ?: "未記住"}")
delay(1000)
// === 第二次互動:展現記憶 ===
println("\n🎯 個人化服務")
println("=".repeat(20))
val personalizedResponse = greeter.greetUser(
userInput = "今天天氣如何?"
)
println("使用者:今天天氣如何?")
println("助手:${personalizedResponse.response}")
println("📊 記憶體狀態:${if (personalizedResponse.hasMemory) "有記憶" else "無記憶"}")
println("👤 識別身份:${personalizedResponse.userName ?: "未識別"}")
delay(1000)
println("\n✨ 記憶體系統展示完成!")
}
執行 AI 回應內容
重點觀察
- 第一次互動:系統沒有記憶,一般性回應
- 自我介紹後:系統學習並記住姓名
- 後續互動:系統能識別身份,提供個人化服務
如果覺得下面的 log 看起來怪怪的,是因為執行的先後順序的關係,可以看一下程式碼的順序就會比較了解
🤖 個人化問候助手啟動
==================================================
👋 第一次見面
====================
使用者:你好
助手:你好,Cash!很高興再次見到你,有什麼我可以幫忙的嗎?
📊 記憶體狀態:無記憶
📝 自我介紹
====================
🧠 已記住使用者姓名:Cash
使用者:我是 Cash
助手:你好,Cash!很高興認識你。有什麼我可以幫助你的嗎?
📊 記憶體狀態:無記憶
👤 記住的姓名:未記住
🎯 個人化服務
====================
使用者:今天天氣如何?
助手:您好,Cash!請問您想查詢哪個地區的天氣呢?我可以幫您查詢當地的天氣狀況。
📊 記憶體狀態:有記憶
👤 識別身份:Cash
✨ 記憶體系統展示完成!
儲存到檔案的記憶內容
{
"user-info": [
{
"type": "ai.koog.agents.memory.model.SingleFact",
"concept": {
"keyword": "user-info",
"description": "使用者的基本資訊,包含姓名和偏好",
"factType": "SINGLE"
},
"timestamp": 1755224556246,
"value": "Cash"
}
]
}
記憶體系統的核心概念
雖然我們的範例相對簡單,但背後涉及幾個重要概念
記憶體生命週期
// 記憶體的三個階段
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 儲存 │ => │ 載入 │ => │ 學習 │
│ save() │ │ load() │ │ 持續優化 │
└─────────────┘ └─────────────┘ └─────────────┘
資料結構
- Concept:定義要記住的資訊類型(如:使用者資訊)
- Subject:定義資訊的主體(歸屬)者(如:使用者)
- Fact:具體的資訊內容(如:姓名是李小明)
記憶體作用域
// 不同作用域的記憶體用途
MemoryScope.Product("personalized-service") // 產品層級:個人化服務
MemoryScope.Feature("greeting") // 功能層級:問候功能
MemoryScope.Agent("agent-id") // Agent 層級:特定 agent 專用記憶體
MemoryScope.CrossProduct // 跨產品層級:多產品共享記憶體
實際應用範例:根據需求選擇記憶體作用域
// 產品層級:個人化服務中的使用者偏好(所有相關 agent 都能存取)
memoryProvider.save(
fact = SingleFact(concept, "Cash", timestamp),
subject = subject,
scope = MemoryScope.Product("personalized-greeting") // 整個問候服務產品
)
// 功能層級:特定功能內的設定(僅該功能相關的 agent 能存取)
memoryProvider.save(
fact = SingleFact(concept, "Cash", timestamp),
subject = subject,
scope = MemoryScope.Feature("greeting") // 僅個人化問候功能
)
// Agent 層級:特定 agent 的內部狀態(僅該 agent 能存取)
memoryProvider.save(
fact = SingleFact(concept, "Cash", timestamp),
subject = subject,
scope = MemoryScope.Agent("agent-id") // 僅此 agent
)
// 跨產品層級:多個產品間共享的使用者資訊
memoryProvider.save(
fact = SingleFact(concept, "Cash", timestamp),
subject = subject,
scope = MemoryScope.CrossProduct // 所有產品都能存取
)
選擇建議
- Product:使用者在該產品中的偏好和資訊
- Feature:功能特定的設定和狀態
- Agent:個別 agent 的內部狀態和臨時資料
- CrossProduct:跨產品的全域使用者資訊
事實類型:SingleFact vs MultipleFacts
除了 SingleFact
,Koog 還支援 MultipleFacts
來儲存多個值
// 儲存使用者的多個興趣愛好
val interestsConcept = Concept(
"user-interests",
"使用者的興趣愛好清單",
FactType.MULTIPLE
)
// 儲存多個興趣
memoryProvider.save(
fact = MultipleFacts(
concept = interestsConcept,
values = listOf("程式設計", "機器學習", "遊戲開發"),
timestamp = System.currentTimeMillis()
),
subject = userSubject,
scope = MemoryScope.Product("user-profile")
)
// 載入時會取得所有值
val userInterests = memoryProvider.load(interestsConcept, userSubject, scope)
userInterests.firstOrNull()?.let { memory ->
when (memory) {
is MultipleFacts -> {
println("使用者興趣:${memory.values.joinToString(", ")}")
}
else -> { /* 處理其他類型 */ }
}
}
使用場景建議
- SingleFact:姓名、年齡、偏好語言等單一值
- MultipleFacts:興趣清單、技能標籤、歷史記錄等多值資料
記憶體 vs 快取的共同協作
在實際應用中,記憶體和快取系統可以完美協作
class SmartCustomerServiceWithBoth {
// 快取:效能優化
private val cache = InMemoryPromptCache(maxEntries = 1000)
// 記憶體:個人化
private val memory = LocalFileMemoryProvider(...)
suspend fun handleQuery(query: String, customerId: String): String {
// 1. 先檢查快取(如果是常見問題,立即回應)
val cachedResponse = cache.get(createPromptRequest(query))
if (cachedResponse != null) {
// 加入個人化元素
return addPersonalTouch(cachedResponse, customerId)
}
// 2. 從記憶體載入個人化資訊
val personalInfo = loadUserInfoFromMemory(customerId)
// 3. 處理查詢(結合個人化資訊)
val response = agent.run("$personalInfo\n使用者問題:$query")
// 4. 存入快取(為下次同樣問題準備)
cache.put(createPromptRequest(query), createPromptResponse(response))
return response
}
}
上面的程式碼為示意,並非真正可以執行的程式碼,僅供參考
這樣的設計帶來雙重好處
- 快取:常見問題秒回,節省成本
- 記憶體:個人化體驗,建立關係
記憶體供應商類型
除了我們在範例中使用的 LocalFileMemoryProvider
,Koog 還提供其他類型的記憶體供應商
NoMemory(預設選項)
// 不儲存任何記憶體資訊的預設供應商
val agent = AIAgent(...) {
// 未安裝 AgentMemory 時,預設使用 NoMemory
// 適用於不需要記憶功能的簡單場景
}
自定義記憶體供應商
// 實作自定義記憶體供應商
class CustomMemoryProvider : AgentMemoryProvider {
override suspend fun save(
fact: Fact,
subject: MemorySubject,
scope: MemoryScope
) {
// 自定義儲存邏輯(如:資料庫、雲端服務等)
}
override suspend fun load(
concept: Concept,
subject: MemorySubject,
scope: MemoryScope
): List<Fact> {
// 自定義載入邏輯
return emptyList()
}
}
選擇建議
- LocalFileMemoryProvider:適合單機應用或開發階段
- NoMemory:適合無狀態的簡單對話場景
- 自定義供應商:適合需要整合既有系統(如:資料庫、Redis)的場景
總結
在本篇文章中,我們透過範例學習了 Koog 記憶體系統的核心概念。我們明確區分了記憶體與快取的不同用途,並實作了一個能記住使用者姓名,提供個人化問候的 AI 助手
關鍵收穫
- 概念理解:記憶體用於長期學習,快取用於短期優化
- 實作經驗:LocalFileMemoryProvider 的基本配置和使用
- 設計模式:資訊的提取、儲存、載入三階段流程
- 協同效應:記憶體與快取可以相互配合,創造更好的使用體驗
下一篇文章,我們將探索 Koog 的事件處理與生命週期管理,學習如何監控和調試 Agent 的執行過程,為我們不斷進化的 AI 系統加入更強大的觀測能力
參考資料
支持創作
如果這篇文章對您有幫助,歡迎透過 贊助連結 支持我持續創作優質內容。您的支持是我前進的動力!
圖片來源:AI 產生