Logo
Published on

第一次學 Kotlin Koog AI 就上手 Day 13:省錢又高效:學會使用提示快取

在前幾篇文章中,我們探索了 Koog 的多模態內容處理能力。隨著我們的 AI 應用功能越來越強大,一個新的挑戰浮現:當用戶詢問相同問題時(例如「如何重設密碼」、「支援哪些付款方式」),系統仍會重複調用昂貴的 LLM API,造成不必要的成本和延遲

今天我們將學習 提示快取機制,這是實現效能優化和成本控制的關鍵技術,讓系統能夠瞬間回應常見問題並大幅節省成本

什麼是提示快取?

提示快取是一種將 AI 回應結果暫存起來的機制。當遇到相同的提示時,系統直接返回快取的結果,而不需要重新呼叫 LLM API

主要效益:

  • 回應速度:從秒級降至毫秒級
  • 成本節省:避免重複的 API 付費請求
  • 系統穩定性:減少對外部 API 的依賴

簡單對比:

  • 沒有快取:每次問「如何重設密碼」可能都需要花 1~2 秒 + API 費用
  • 有快取:第一次 1~2 秒,之後可能都是毫秒的回應且免費

記憶體快取:最簡單的開始

記憶體快取將結果直接存在記憶體中,是最快速的快取方案

基本使用

suspend fun main() {

    // 建立記憶體快取,限制最大 100 筆記錄
    val cache = InMemoryPromptCache(maxEntries = 100)

    val executor = simpleOpenAIExecutor(ApiKeyManager.openAIApiKey!!)

    val prompt = prompt("memory") {
        system {
            text("""
                你是一個友善的 AI 助手
                請使用正體中文回答
            """.trimIndent())
        }
        user {
            text("什麼是 Kotlin 協程?請簡單的說明")
        }
    }

    val promptRequest = PromptCache.Request.create(prompt, emptyList())

    println("=== 第一次詢問(會呼叫 API)===")
    var cachedResponse = cache.get(promptRequest)
    if (cachedResponse != null) {
        println("AI (使用快取回應): ${cachedResponse.first().content}")
    } else {
        val response = executor.execute(prompt, OpenAIModels.CostOptimized.GPT4_1Mini)

        // 將回應存入快取
        cache.put(promptRequest, listOf(response))

        println("AI : ${response.content}")
    }

    println("\n=== 第二次詢問相同問題(使用快取)===")
    cachedResponse = cache.get(promptRequest)
    if (cachedResponse != null) {
        println("AI (使用快取回應): ${cachedResponse.first().content}")
    } else {
        val response = executor.execute(prompt, OpenAIModels.CostOptimized.GPT4_1Mini)

        // 將回應存入快取
        cache.put(promptRequest, listOf(response))

        println("AI : ${response.content}")
    }
}
  • 優點: 速度最快、設定簡單
  • 缺點: 應用重啟後會消失

執行 AI 回應內容

=== 第一次詢問(會呼叫 API===
AI : Kotlin 協程(Coroutine)是一種輕量級的非同步程式設計工具,可以幫助你用更簡單、直覺的方式寫出非同步、並行的程式碼。它讓你可以暫停函式的執行(不會阻塞線程),等候耗時操作完成後再繼續,寫起來像同步程式碼一樣清晰易懂。簡單來說,協程讓非同步程式設計變得更簡單、更有效率。

=== 第二次詢問相同問題(使用快取)===
AI (使用快取回應): Kotlin 協程(Coroutine)是一種輕量級的非同步程式設計工具,可以幫助你用更簡單、直覺的方式寫出非同步、並行的程式碼。它讓你可以暫停函式的執行(不會阻塞線程),等候耗時操作完成後再繼續,寫起來像同步程式碼一樣清晰易懂。簡單來說,協程讓非同步程式設計變得更簡單、更有效率。

檔案快取:持久化的選擇

檔案快取將結果存在磁碟上,重啟應用後仍然保留

基本使用

suspend fun main() {

    // 建立檔案快取,指定快取目錄和最大檔案數量
    val cache = FilePromptCache(
        Path.of("cache/prompts"),
        50
    )

    val executor = simpleOpenAIExecutor(ApiKeyManager.openAIApiKey!!)

    val prompt = prompt("file") {
        system {
            text("""
                你是一個友善的 AI 助手
                請使用正體中文回答
            """.trimIndent())
        }
        user {
            text("什麼是 Kotlin 協程?請簡單的說明")
        }
    }

    val promptRequest = PromptCache.Request.create(prompt, emptyList())

    println("=== 第一次詢問(會呼叫 API)===")
    var cachedResponse = cache.get(promptRequest)
    if (cachedResponse != null) {
        println("AI (從檔案快取載入): ${cachedResponse.first().content}")
    } else {
        val response = executor.execute(prompt, OpenAIModels.CostOptimized.GPT4_1Mini)

        // 將回應存入檔案快取
        cache.put(promptRequest, listOf(response))

        println("AI : ${response.content}")
        println("(回應已存入檔案快取)")
    }

    println("\n=== 第二次詢問相同問題(使用檔案快取)===")
    cachedResponse = cache.get(promptRequest)
    if (cachedResponse != null) {
        println("AI (從檔案快取載入): ${cachedResponse.first().content}")
    } else {
        val response = executor.execute(prompt, OpenAIModels.CostOptimized.GPT4_1Mini)

        // 將回應存入檔案快取
        cache.put(promptRequest, listOf(response))

        println("AI : ${response.content}")
        println("(回應已存入檔案快取)")
    }
}
  • 優點: 持久化存儲、重啟後仍可用
  • 缺點: 相對較慢、需要磁碟空間

執行 AI 回應內容

=== 第一次詢問(會呼叫 API===
AI : Kotlin 協程是一種用來寫非同步程式的輕量級工具。它可以讓你用同步的寫法來寫非同步程式,讓程式碼更簡潔且容易理解。協程可以暫停和恢復執行,幫助你有效率地處理耗時的操作,比如網路請求或資料庫讀取,而不會阻塞主執行緒。簡單來說,Kotlin 協程讓非同步程式寫起來像同步程式一樣方便。
(回應已存入檔案快取)

=== 第二次詢問相同問題(使用檔案快取)===
AI (使用快取回應): Kotlin 協程是一種用來寫非同步程式的輕量級工具。它可以讓你用同步的寫法來寫非同步程式,讓程式碼更簡潔且容易理解。協程可以暫停和恢復執行,幫助你有效率地處理耗時的操作,比如網路請求或資料庫讀取,而不會阻塞主執行緒。簡單來說,Kotlin 協程讓非同步程式寫起來像同步程式一樣方便。

檔案快取內容

這是儲存在檔案裡的快取內容

{
  "response": [
    {
      "type": "ai.koog.prompt.message.Message.Assistant",
      "content": "Kotlin 協程是一種用來寫非同步程式的輕量級工具。它可以讓你用同步的寫法來寫非同步程式,讓程式碼更簡潔且容易理解。協程可以暫停和恢復執行,幫助你有效率地處理耗時的操作,比如網路請求或資料庫讀取,而不會阻塞主執行緒。簡單來說,Kotlin 協程讓非同步程式寫起來像同步程式一樣方便。",
      "metaInfo": {
        "timestamp": "2025-08-14T07:09:06.782301Z",
        "totalTokensCount": 163,
        "inputTokensCount": 42,
        "outputTokensCount": 121
      },
      "finishReason": "stop"
    }
  ],
  "request": {
    "prompt": {
      "messages": [
        {
          "type": "ai.koog.prompt.message.Message.System",
          "content": "你是一個友善的 AI 助手\n請使用正體中文回答",
          "metaInfo": {
            "timestamp": "2025-08-14T07:09:03.958772Z"
          }
        },
        {
          "type": "ai.koog.prompt.message.Message.User",
          "content": "什麼是 Kotlin 協程?請簡單的說明",
          "metaInfo": {
            "timestamp": "2025-08-14T07:09:03.959661Z"
          }
        }
      ],
      "id": "memory"
    }
  }
}

Redis 快取:企業級選擇

Redis 快取適合生產環境,支援分散式部署

基本配置

suspend fun main() {

    // 建立 Redis 客戶端連線
    val client = RedisClient.create("redis://localhost:6379")

    // 建立 Redis 快取,設定鍵前綴和 TTL
    val cache = RedisPromptCache(
        client = client,
        keyPrefix = "ai-app-cache:",
        ttl = 7.days  // 快取保存 7 天
    )

    val executor = simpleOpenAIExecutor(ApiKeyManager.openAIApiKey!!)

    val prompt = prompt("redis") {
        system {
            text("""
                你是一個友善的 AI 助手
                請使用正體中文回答
            """.trimIndent())
        }
        user {
            text("什麼是 Kotlin 協程?請簡單的說明")
        }
    }

    val promptRequest = PromptCache.Request.create(prompt, emptyList())

    try {
        println("=== 第一次詢問(會呼叫 API)===")
        var cachedResponse = cache.get(promptRequest)
        if (cachedResponse != null) {
            println("AI (從 Redis 快取載入): ${cachedResponse.first().content}")
        } else {
            val response = executor.execute(prompt, OpenAIModels.CostOptimized.GPT4_1Mini)

            // 將回應存入 Redis 快取
            cache.put(promptRequest, listOf(response))

            println("AI : ${response.content}")
            println("(回應已存入 Redis 快取)")
        }

        println("\n=== 第二次詢問相同問題(使用 Redis 快取)===")
        cachedResponse = cache.get(promptRequest)
        if (cachedResponse != null) {
            println("AI (從 Redis 快取載入): ${cachedResponse.first().content}")
        } else {
            val response = executor.execute(prompt, OpenAIModels.CostOptimized.GPT4_1Mini)

            // 將回應存入 Redis 快取
            cache.put(promptRequest, listOf(response))

            println("AI : ${response.content}")
            println("(回應已存入 Redis 快取)")
        }
    } finally {
        cache.close()
    }
}

執行 AI 回應內容

=== 第一次詢問(會呼叫 API===
AI : Kotlin 協程(Coroutine)是一種輕量級的非同步程式設計工具,可以讓你用順序的寫法來處理非同步任務。它能夠暫停和恢復程式執行,達到高效管理多個任務的效果,比傳統的線程使用更少資源,讓程式碼更簡潔易讀。簡單來說,協程就是幫助你寫出「看起來像同步,實際上是非同步」的程式碼。
(回應已存入 Redis 快取)

=== 第二次詢問相同問題(使用 Redis 快取)===
AI (Redis 快取載入): Kotlin 協程(Coroutine)是一種輕量級的非同步程式設計工具,可以讓你用順序的寫法來處理非同步任務。它能夠暫停和恢復程式執行,達到高效管理多個任務的效果,比傳統的線程使用更少資源,讓程式碼更簡潔易讀。簡單來說,協程就是幫助你寫出「看起來像同步,實際上是非同步」的程式碼。

快取策略選擇

比較一下三種不同快取之間,相關的優點和缺點

快取類型適用場景優勢限制
記憶體快取開發測試、小型應用速度最快、設定簡單重啟後遺失、記憶體限制
檔案快取單機部署、中型應用持久化、成本低相對較慢、磁碟空間限制
Redis 快取生產環境、分散式系統高可用、支援集群需要維護 Redis、網路開銷

使用 CachedPromptExecutor

Koog 提供了 CachedPromptExecutor 來簡化快取整合

suspend fun main() {

    // 建立快取
    val cache = InMemoryPromptCache(maxEntries = 100)

    // 執行器
    val executor = simpleOpenAIExecutor(ApiKeyManager.openAIApiKey!!)

    // 包裝成快取執行器
    val cachedExecutor = CachedPromptExecutor(
        cache,
        executor
    )

    val prompt = prompt("memory") {
        system {
            text(
                """
                你是一個友善的 AI 助手
                請使用正體中文回答
            """.trimIndent()
            )
        }
        user {
            text("什麼是 Kotlin 協程?請簡單的說明")
        }
    }

    println("=== 第一次詢問(會呼叫 API)===")
    var response = cachedExecutor.execute(prompt, OpenAIModels.CostOptimized.GPT4_1Mini)
    println("$response")
    println("\nAI : ${response.content}")

    println("\n=== 第二次詢問相同問題(使用 memory 快取)===")
    response = cachedExecutor.execute(prompt, OpenAIModels.CostOptimized.GPT4_1Mini)
    println("\n$response")
    println("\nAI : ${response.content}")
}

這個設計的好處是

  • 簡單方便:不需要大量的修改/增加現有程式碼
  • 自動處理:快取的存取完全自動
  • 易於切換:可以輕鬆開關快取功能

執行 AI 回應內容

如果我們把 response 完整的印出來看的話,因為是從 cache 中讀取,所以第二次的所有 token count 都會是 null

=== 第一次詢問(會呼叫 API===
Assistant(content=Kotlin 協程(Coroutine)是一種用來處理非同步編程的輕量級工具。它讓你可以寫出看起來像同步的程式碼,但實際上可以非同步執行,避免阻塞主執行緒。協程能簡化異步任務的寫法,提高程式的可讀性和效率。簡單來說,協程就是用來方便管理並發和異步操作的機制。, metaInfo=ResponseMetaInfo(timestamp=2025-08-14T07:32:19.366384Z, totalTokensCount=146, inputTokensCount=42, outputTokensCount=104, additionalInfo={}), attachments=[], finishReason=stop)

AI : Kotlin 協程(Coroutine)是一種用來處理非同步編程的輕量級工具。它讓你可以寫出看起來像同步的程式碼,但實際上可以非同步執行,避免阻塞主執行緒。協程能簡化異步任務的寫法,提高程式的可讀性和效率。簡單來說,協程就是用來方便管理並發和異步操作的機制。

=== 第二次詢問相同問題(使用 memory 快取)===

Assistant(content=Kotlin 協程(Coroutine)是一種用來處理非同步編程的輕量級工具。它讓你可以寫出看起來像同步的程式碼,但實際上可以非同步執行,避免阻塞主執行緒。協程能簡化異步任務的寫法,提高程式的可讀性和效率。簡單來說,協程就是用來方便管理並發和異步操作的機制。, metaInfo=ResponseMetaInfo(timestamp=2025-08-14T07:32:19.372485Z, totalTokensCount=null, inputTokensCount=null, outputTokensCount=null, additionalInfo={}), attachments=[], finishReason=stop)

AI : Kotlin 協程(Coroutine)是一種用來處理非同步編程的輕量級工具。它讓你可以寫出看起來像同步的程式碼,但實際上可以非同步執行,避免阻塞主執行緒。協程能簡化異步任務的寫法,提高程式的可讀性和效率。簡單來說,協程就是用來方便管理並發和異步操作的機制。

總結

在本篇文章中,我們學會了

  • 提示快取概念:透過暫存回應避免重複 API 呼叫
  • 記憶體快取:最快速的快取方案,適合開發和測試
  • 檔案快取:持久化選擇,適合單機部署
  • Redis 快取:企業級方案,支援分散式部署
  • CachedPromptExecutor:簡化快取整合的透明化工具

快取機制是 AI 應用優化的重要基石。透過適當的快取策略,我們可以

  • 大幅提升回應速度:從秒級降至毫秒級
  • 顯著降低 API 成本:避免重複的付費請求
  • 改善用戶體驗:更快的回應讓用戶更滿意

在使用快取時,最麻煩的是官方的文件寫的設定方式全部都是錯的方式,所以在寫這篇時,研究了很久..

下一篇文章(Day 14),我們將學習 Agent 記憶體系統,這與快取不同,記憶體系統能讓 AI 記住用戶的個人資訊和偏好,實現真正的個人化服務

參考資料


支持創作

如果這篇文章對您有幫助,歡迎透過 贊助連結 支持我持續創作優質內容。您的支持是我前進的動力!


圖片來源:AI 產生