Logo
Published on

第一次學 Kotlin Koog AI 就上手 Day 15:洞察 AI 內心:掌握 Agent 的事件與生命週期

在前一篇文章中,我們學習了 Koog 的記憶體系統,讓 Agent 具備持久化記憶能力。今天我們將探討 Koog 的事件處理機制,這是了解和監控 Agent 執行過程的重要工具

想像一下,如果我們能夠觀察 AI Agent 的「內心世界」,就像醫生透過心電圖監控病人的心跳一樣。事件處理系統就是我們的「AI 心電圖」,讓我們能夠即時掌握 Agent 在做什麼、進展如何,以及是否遇到問題

為什麼需要事件處理?

在開發 AI 應用時,我們常常會遇到這些問題

  • 黑盒子問題:Agent 在執行時發生了什麼?
  • 除錯困難:出錯時很難找到問題所在
  • 效能監控:不知道 Agent 的回應速度如何
  • 用戶體驗:無法了解用戶等待的原因

事件處理系統就是解決這些問題的關鍵。它讓我們能夠「看見」Agent 的執行過程,就像在程式中加入 println 一樣簡單,但功能更強大

三個關鍵事件

在 Koog 框架中,有三個最重要的事件需要我們掌握。注意:事件處理需要使用 install(EventHandler) 功能

onBeforeAgentStarted - Agent 啟動事件

當 Agent 開始執行任務時觸發,就像是打開燈泡的那一刻

install(EventHandler) {
    onBeforeAgentStarted { eventContext ->
        println("Agent 開始工作了!")
        println("Agent ID:${eventContext.agent.id}")
        println("使用策略:${eventContext.strategy.name}")
        println("開始時間:${java.time.LocalDateTime.now()}")
    }
}

onToolCall - 工具調用事件

當 Agent 開始使用工具(如搜尋、計算、API 調用)時觸發

install(EventHandler) {
    onToolCall { eventContext ->
        println("工具 ${eventContext.tool.name} 開始執行")
        println("輸入參數:${eventContext.toolArgs}")
        println("執行 ID:${eventContext.runId}")
    }
}

onAgentRunError - 錯誤處理事件

當執行過程中發生錯誤時觸發,是除錯的重要線索

install(EventHandler) {
    onAgentRunError { eventContext ->
        println("發生錯誤:${eventContext.throwable.message}")
        println("錯誤類型:${eventContext.throwable.javaClass.simpleName}")
        println("Agent ID:${eventContext.agentId}")
        // 可以選擇性地記錄詳細的堆疊追蹤
        eventContext.throwable.printStackTrace()
    }
}

客服機器人範例

讓我們透過一個簡單的客服機器人來看看如何使用這些事件

class EventCustomerServiceBot {

    fun createBot(): AIAgent<String, String> {
        return AIAgent(
            executor = simpleOpenAIExecutor(ApiKeyManager.openAIApiKey!!),
            systemPrompt = """
                你是一個友善的客服助手,專門協助客戶解決問題
                請用親切的語氣回應,並盡量提供有用的資訊
                使用正體中文回應
            """.trimIndent(),
            llmModel = OpenAIModels.CostOptimized.GPT4_1Mini
        ) {
            // 安裝事件處理功能
            install(EventHandler) {

                // 當客服機器人開始工作時
                onBeforeAgentStarted { eventContext ->
                    val currentTime = LocalDateTime.now()
                        .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))

                    println("=".repeat(50))
                    println("客服機器人已啟動")
                    println("開始時間:$currentTime")
                    println("Agent ID:${eventContext.agent.id}")
                    println("使用策略:${eventContext.strategy.name}")
                    println("執行 ID:${eventContext.runId}")
                    println("=".repeat(50))
                }

                // 當開始使用工具時(比如查詢資料庫、搜尋資訊等)
                onToolCall { eventContext ->
                    println("工具開始執行:")
                    println("   工具名稱:${eventContext.tool.name}")
                    println("   輸入參數:${eventContext.toolArgs}")
                    println("   執行 ID:${eventContext.runId}")
                    println("-".repeat(30))
                }

                // 當工具執行完成並有結果時
                onToolCallResult { eventContext ->
                    println("工具執行完成:")
                    println("   工具名稱:${eventContext.tool.name}")

                    // 顯示工具執行的簡要結果
                    val resultPreview = when {
                        eventContext.result.toString().length <= 100 -> eventContext.result.toString()
                        else -> "${eventContext.result.toString().take(97)}..."
                    }
                    println("   執行結果:$resultPreview")
                    println("-".repeat(30))
                }

                // 當 Agent 執行完成時
                onAgentFinished { eventContext ->
                    println("客服機器人執行完成")
                    println("   Agent ID:${eventContext.agentId}")
                    println("   執行 ID:${eventContext.runId}")
                    println("=".repeat(50))
                }

                // 當發生錯誤時
                onAgentRunError { eventContext ->
                    println("客服系統發生問題:")
                    println("   錯誤訊息:${eventContext.throwable.message}")
                    println("   Agent ID:${eventContext.agentId}")
                    println("   發生時間:${LocalDateTime.now()}")

                    // 根據錯誤類型提供不同的處理建議
                    when {
                        eventContext.throwable.message?.contains("timeout") == true ->
                            println("   建議:網路連線可能不穩定,請稍後再試")
                        eventContext.throwable.message?.contains("quota") == true ->
                            println("   建議:API 配額可能已用完,請檢查帳戶狀態")
                        else ->
                            println("   建議:請檢查設定或聯繫技術支援")
                    }
                    println("=".repeat(50))
                }
            }
        }
    }
}

實際使用範例

suspend fun main() {

    // 建立客服機器人
    val bot = CustomerServiceBot().createBot(apiKey)

    // 模擬客戶對話
    val customerQuestions = listOf(
        "你好,請問你們的營業時間是什麼時候?",
        "運費是怎麼計算的?"
    )

    customerQuestions.forEach { question ->
        println("客戶問題:$question")

        try {
            val response = bot.run(question)
            println("客服回應:$response")
        } catch (e: Exception) {
            println("處理失敗:${e.message}")
        }

        println("\n" + "=".repeat(60) + "\n")
    }
}

執行 AI 回應內容

客戶問題:你好,請問你們的營業時間是什麼時候?
==================================================
客服機器人已啟動
開始時間:2025-08-15 16:07:45
Agent ID:e503114c-9d6f-4a04-b5f2-e6f40f3ff380
使用策略:single_run
執行 ID:85c5d609-f781-421f-b795-9242b608b5d3
==================================================
客服機器人執行完成
   Agent ID:e503114c-9d6f-4a04-b5f2-e6f40f3ff380
   執行 ID:85c5d609-f781-421f-b795-9242b608b5d3
==================================================
客服回應:您好!感謝您的詢問。我們的營業時間是週一到週五,上午9點到下午6點,週末及國定假日休息。如果您有任何其他問題,隨時歡迎告訴我喔!祝您有美好的一天!

============================================================

客戶問題:運費是怎麼計算的?
==================================================
客服機器人已啟動
開始時間:2025-08-15 16:07:47
Agent ID:e503114c-9d6f-4a04-b5f2-e6f40f3ff380
使用策略:single_run
執行 ID:f0a73e3f-d11e-409c-9c83-581e33b6c753
==================================================
客服機器人執行完成
   Agent ID:e503114c-9d6f-4a04-b5f2-e6f40f3ff380
   執行 ID:f0a73e3f-d11e-409c-9c83-581e33b6c753
==================================================
客服回應:您好!關於運費的計算方式,通常會根據以下幾個因素來決定:

1. **配送地點**:寄送的地址距離您的所在地越遠,運費通常會越高。
2. **包裹重量與尺寸**:重量較重或體積較大的包裹,運費會比較貴。
3. **運送方式**:例如標準快遞、加急快遞、國際運送等,不同方式費用也會不同。
4. **促銷活動或免運門檻**:有時候店家會提供滿額免運或優惠折扣。

如果您方便的話,可以提供您所在的地區和想購買的商品資訊,我可以幫您查詢更詳細的運費標準喔!祝您購物愉快!😊

============================================================

進階使用技巧

計算處理時間

install(EventHandler) {
    // 記錄開始時間
    var startTime = 0L

    onBeforeAgentStarted { eventContext ->
        startTime = System.currentTimeMillis()
        println("開始處理客戶問題...")
        println("執行 ID:${eventContext.runId}")
    }

    onAgentFinished { eventContext ->
        val duration = System.currentTimeMillis() - startTime
        println("處理完成,耗時:${duration}ms")
        println("執行 ID:${eventContext.runId}")
    }
}

統計工具使用次數

install(EventHandler) {
    val toolUsageCount = mutableMapOf<String, Int>()

    onToolCall { eventContext ->
        // 統計工具使用次數
        toolUsageCount[eventContext.tool.name] =
            toolUsageCount.getOrDefault(eventContext.tool.name, 0) + 1

        println("${eventContext.tool.name} 已使用 ${toolUsageCount[eventContext.tool.name]} 次")
    }
}

支援多個事件處理器

install(EventHandler) {
    // 可以為同一個事件註冊多個處理器
    onBeforeAgentStarted { eventContext ->
        println("第一個處理器:Agent 開始執行")
    }

    onBeforeAgentStarted { eventContext ->
        println("第二個處理器:記錄開始時間")
    }
}

簡單的錯誤提示機制

install(EventHandler) {
    var errorCount = 0

    onAgentRunError { eventContext ->
        errorCount++
        println("第 $errorCount 次錯誤:")

        if (errorCount >= 3) {
            println("連續錯誤過多,建議檢查系統狀態")
        }
    }
}

疑難排解

事件沒有觸發

解決方法:在 Agent 建立時安裝 EventHandler 功能

val agent = AIAgent(...) {
    install(EventHandler) {  // 確保有安裝 EventHandler
        onBeforeAgentStarted { eventContext -> ... }
    }
}

使用了過時的 API

可能原因:使用了被標記為 Deprecated 的屬性賦值方式

解決方法:改用新的方法呼叫方式

// 舊的方式(已棄用)
install(EventHandler) {
    onToolCall = { stage, tool, toolArgs -> ... }
}

// 新的方式(推薦)
install(EventHandler) {
    onToolCall { eventContext -> ... }
}

輸出太多資訊

解決方法:加入條件判斷,只在需要時輸出

install(EventHandler) {
    val isDebugMode = System.getenv("DEBUG") == "true"

    onToolCall { eventContext ->
        if (isDebugMode) {
            println("工具:${eventContext.tool.name}")
        }
    }
}

效能影響

解決方法:保持事件處理邏輯簡單

install(EventHandler) {
    // 避免在事件中做複雜的運算
    onBeforeAgentStarted { eventContext ->
        heavyDatabaseOperation() // 不建議
    }

    // 保持簡單快速
    onBeforeAgentStarted { eventContext ->
        println("Agent started: ${eventContext.agent.id}")
    }
}

總結

今天我們學習了 Koog 框架中的事件處理機制

  • 新的事件 API:使用 install(EventHandler) 安裝事件處理功能
  • 三個核心事件onBeforeAgentStartedonToolCallonAgentRunError
  • Context 物件:透過 eventContext 存取豐富的事件資訊
  • 實際應用:透過智能客服機器人了解事件的實際用途
  • 進階功能:支援多個事件處理器、完整的事件生命週期
  • 除錯技巧:使用事件來找出問題和優化效能

完整的事件類型清單

除了上述三個關鍵事件,Koog 還提供了更多事件類型供進階使用

Agent 生命週期事件

  • onBeforeAgentStarted - Agent 啟動前
  • onAgentFinished - Agent 執行完成
  • onAgentRunError - Agent 執行發生錯誤

策略執行事件

  • onStrategyStarted - 策略開始執行
  • onStrategyFinished - 策略執行完成

節點執行事件

  • onBeforeNode - 節點執行前
  • onAfterNode - 節點執行後

LLM 呼叫事件

  • onBeforeLLMCall - LLM 呼叫前
  • onAfterLLMCall - LLM 呼叫後

工具相關事件

  • onToolCall - 工具開始執行
  • onToolCallResult - 工具執行完成並有結果
  • onToolCallFailure - 工具執行失敗
  • onToolValidationError - 工具參數驗證錯誤

這些事件提供了完整的監控能力,讓你能夠追蹤 Agent 執行的每個階段

事件處理就像是給 Agent 裝上了「儀表板」,讓我們能夠清楚看到它的運作狀況。透過這些豐富的事件類型,你可以建立一個全面監控、可除錯的 AI 應用

在下一篇文章中,我們將學習 Koog 的策略模式與工作流程設計,了解如何建立更複雜的 Agent 執行邏輯

參考資料


支持創作

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


圖片來源:AI 產生