
在前一篇文章中,我們學習了 Koog 的記憶體系統,讓 Agent 具備持久化記憶能力。今天我們將探討 Koog 的事件處理機制,這是了解和監控 Agent 執行過程的重要工具
想像一下,如果我們能夠觀察 AI Agent 的「內心世界」,就像醫生透過心電圖監控病人的心跳一樣。事件處理系統就是我們的「AI 心電圖」,讓我們能夠即時掌握 Agent 在做什麼、進展如何,以及是否遇到問題
在開發 AI 應用時,我們常常會遇到這些問題
事件處理系統就是解決這些問題的關鍵。它讓我們能夠「看見」Agent 的執行過程,就像在程式中加入 println 一樣簡單,但功能更強大
在 Koog 框架中,有三個最重要的事件需要我們掌握。注意:事件處理需要使用 install(EventHandler) 功能
當 Agent 開始執行任務時觸發,就像是打開燈泡的那一刻
install(EventHandler) {
    onBeforeAgentStarted { eventContext ->
        println("Agent 開始工作了!")
        println("Agent ID:${eventContext.agent.id}")
        println("使用策略:${eventContext.strategy.name}")
        println("開始時間:${java.time.LocalDateTime.now()}")
    }
}
當 Agent 開始使用工具(如搜尋、計算、API 調用)時觸發
install(EventHandler) {
    onToolCall { eventContext ->
        println("工具 ${eventContext.tool.name} 開始執行")
        println("輸入參數:${eventContext.toolArgs}")
        println("執行 ID:${eventContext.runId}")
    }
}
當執行過程中發生錯誤時觸發,是除錯的重要線索
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")
    }
}
客戶問題:你好,請問你們的營業時間是什麼時候?
==================================================
客服機器人已啟動
開始時間:2025-08-15 16:07:45
gent 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 -> ... }
    }
}
可能原因:使用了被標記為 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 框架中的事件處理機制
install(EventHandler) 安裝事件處理功能onBeforeAgentStarted、onToolCall、onAgentRunError
除了上述三個關鍵事件,Koog 還提供了更多事件類型供進階使用
onBeforeAgentStarted - Agent 啟動前onAgentFinished - Agent 執行完成onAgentRunError - Agent 執行發生錯誤onStrategyStarted - 策略開始執行onStrategyFinished - 策略執行完成onBeforeNode - 節點執行前onAfterNode - 節點執行後onBeforeLLMCall - LLM 呼叫前onAfterLLMCall - LLM 呼叫後onToolCall - 工具開始執行onToolCallResult - 工具執行完成並有結果onToolCallFailure - 工具執行失敗onToolValidationError - 工具參數驗證錯誤這些事件提供了完整的監控能力,讓你能夠追蹤 Agent 執行的每個階段
事件處理就像是給 Agent 裝上了「儀表板」,讓我們能夠清楚看到它的運作狀況。透過這些豐富的事件類型,你可以建立一個全面監控、可除錯的 AI 應用
在下一篇文章中,我們將學習 Koog 的策略模式與工作流程設計,了解如何建立更複雜的 Agent 執行邏輯
圖片來源:AI 產生
同步刊登於 Blog 第一次學 Kotlin Koog AI 就上手 Day 15:洞察 AI 內心:掌握 Agent 的事件與生命週期