- Published on
第一次學 Kotlin Koog AI 就上手 Day 23:進階節點操作:LLM 互動與工具執行
在上一篇文章中,我們學習了策略圖的基礎概念和自訂節點的使用方法。今天我們要進入更進階的領域,學習如何使用 Koog 提供的預定義節點來與 LLM 互動、執行工具,以及實現更複雜的流程控制
LLM 互動節點
nodeLLMRequest:發送請求給 LLM
這是最基本也是最重要的 LLM 互動節點,它將使用者訊息發送給 LLM 並取得回應
val strategy = strategy<String, String>("llm_interaction") {
// 基本 LLM 請求節點 - 允許工具呼叫(預設)
val askLLMNode by nodeLLMRequest("ask_llm")
// 設定不允許工具呼叫的 LLM 節點 - 純文字回應
val simpleResponseNode by nodeLLMRequest("simple_response", allowToolCalls = false)
edge(nodeStart forwardTo askLLMNode)
edge(askLLMNode forwardTo nodeFinish onAssistantMessage { true })
}
nodeUpdatePrompt:更新提示詞
在執行過程中動態修改 Agent 的提示詞
val updateContextNode by nodeUpdatePrompt<String>("update_context") {
user("你是一位有 AI 客服助理")
system("請根據輸入調整回應風格")
}
nodeLLMSendToolResult:回傳工具結果
當工具執行完成後,將結果發送回 LLM。這個節點會將工具執行結果加入對話歷史,並請求 LLM 根據工具結果產生回應
// 將工具執行結果發送給 LLM,並取得基於結果的回應
val sendResultNode by nodeLLMSendToolResult("send_result")
工具執行節點
nodeExecuteTool:執行單一工具
處理 LLM 發出的工具呼叫請求。這個節點會接收工具呼叫訊息,執行對應的工具,並回傳執行結果
// 執行 LLM 要求的單一工具呼叫
val executeToolNode by nodeExecuteTool("execute_tool")
nodeExecuteMultipleTools:平行執行多個工具
當需要同時執行多個工具時使用。這個節點可以平行處理多個工具呼叫,提升效率
// 平行執行多個工具呼叫
val executeMultipleToolsNode by nodeExecuteMultipleTools("execute_multiple")
// 將多個工具結果發送給 LLM 進行處理
val processMultipleResultsNode by nodeLLMSendMultipleToolResults("process_multiple")
edge(executeMultipleToolsNode forwardTo processMultipleResultsNode)
nodeExecuteSingleTool:執行指定工具
直接呼叫特定工具,不需要 LLM 決定。這個節點用於在已知需要執行特定工具時使用
// 注意:此節點用於直接執行特定工具,而非依賴 LLM 的工具呼叫決定
// 需要根據官方 API 文件確認正確的實作方式
val executeSpecificToolNode by nodeExecuteSingleTool<String, String>(
tool = MyCustomTool()
// 具體參數設定請參考官方 API 文件
)
進階流程控制
條件式邊(Conditional Edges)
Koog 提供多種條件類型來控制流程
// 基於輸出內容的條件
edge(sourceNode forwardTo targetNode onCondition { output ->
output.contains("特定關鍵字")
})
// 基於 LLM 回應類型的條件
edge(llmNode forwardTo toolNode onToolCall { true })
edge(llmNode forwardTo finishNode onAssistantMessage { true })
// 基於多個工具呼叫的條件
edge(llmNode forwardTo multiToolNode onMultipleToolCalls { true })
// 當沒有工具被呼叫時
edge(llmNode forwardTo errorNode onToolNotCalled { true })
輸出轉換(Transformed)
在節點間傳遞資料時轉換格式
edge(sourceNode forwardTo targetNode
transformed { output ->
"處理後的資料:${output.uppercase()}"
}
)
// 結合條件和轉換
edge(sourceNode forwardTo targetNode
onCondition { it.isNotEmpty() }
transformed { it.trim() }
)
平行節點執行(Parallel)
同時執行多個節點並選擇結果,這在需要比較不同處理方式或使用不同模型時很有用:
// 定義三個不同的處理節點
val nodeA by node<String, Int>("node_a") { 10 }
val nodeB by node<String, Int>("node_b") { 20 }
val nodeC by node<String, Int>("node_c") { 15 }
// 平行執行並選擇最佳結果
val parallelResult by parallel<String, Int>(
nodeA, nodeB, nodeC
) {
selectByMax { it } // 選擇最大值作為最終結果
}
智慧客服系統範例
讓我們建立一個完整的智慧客服系統,展示各種進階節點的使用
智慧客服系統流程圖
flowchart TD
A[開始] --> B[分析客戶意圖節點]
B --> C{意圖判斷}
C -->|一般詢問| D[設定一般提示詞節點]
C -->|訂單查詢| E[設定訂單提示詞節點]
C -->|投訴處理| F[設定投訴提示詞節點]
D --> G[處理一般詢問節點]
E --> H[處理訂單查詢節點]
F --> I[處理投訴處理節點]
G --> J[結束]
H --> K{LLM 回應類型}
I --> L{LLM 回應類型}
K -->|助手訊息| J
K -->|工具呼叫| M[執行工具節點]
L -->|助手訊息| J
L -->|工具呼叫| M
M --> N[發送工具結果節點]
N --> O{回應類型}
O -->|工具呼叫| M
O -->|助手訊息| P[生成最終回應節點]
P --> J
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#fff3e0
style D fill:#e8f5e8
style E fill:#e8f5e8
style F fill:#e8f5e8
style G fill:#f3e5f5
style H fill:#f3e5f5
style I fill:#f3e5f5
style J fill:#e8f5e8
style K fill:#fff3e0
style L fill:#fff3e0
style M fill:#f3e5f5
style N fill:#f3e5f5
style O fill:#fff3e0
style P fill:#f3e5f5
這個流程圖展示了智慧客服系統的完整處理流程
- 意圖分析階段:根據客戶訊息判斷查詢類型(一般詢問、訂單查詢、投訴處理)
- 提示詞設定階段:為不同類型的查詢設定對應的提示詞和處理策略
- LLM 處理階段:使用不同的節點處理不同類型的查詢
- 一般詢問:直接回應,不需要工具
- 訂單查詢:可能需要查詢工具
- 投訴處理:可能需要轉接或通知工具
- 工具執行階段:當需要執行工具時的循環處理流程
- 結果整合階段:整合工具結果並生成最終回應
新增相關工具
// 自訂工具:查詢訂單狀態
class QueryOrderTool : SimpleTool<QueryOrderTool.Args>() {
@Serializable
class Args(val orderId: String) : ToolArgs
override val argsSerializer = Args.serializer()
override val descriptor = ToolDescriptor("query_order", "查詢訂單狀態")
override suspend fun doExecute(args: Args): String {
// 模擬查詢訂單
val orders = mapOf(
"ORDER001" to "已出貨",
"ORDER002" to "處理中",
"ORDER003" to "已取消"
)
return orders[args.orderId] ?: "訂單不存在"
}
}
// 自訂工具:發送通知
class SendNotificationTool : SimpleTool<SendNotificationTool.Args>() {
@Serializable
class Args(val message: String, val channel: String) : ToolArgs
override val argsSerializer = Args.serializer()
override val descriptor = ToolDescriptor("send_notification", "發送通知給客戶")
override suspend fun doExecute(args: Args): String {
println("📱 通過 ${args.channel} 發送通知:${args.message}")
return "通知已發送"
}
}
// 自訂工具:升級到人工客服
class EscalateToHumanTool : SimpleTool<EscalateToHumanTool.Args>() {
@Serializable
class Args(val reason: String) : ToolArgs
override val argsSerializer = Args.serializer()
override val descriptor = ToolDescriptor("escalate_to_human", "轉接人工客服")
override suspend fun doExecute(args: Args): String {
println("🎧 轉接人工客服,原因:${args.reason}")
return "已轉接至人工客服"
}
}
新增客服使用相關工具的流程
// 封裝客戶查詢和意圖的資料結構
data class CustomerQuery(
val originalMessage: String,
val intent: String
)
class SmartCustomerServiceAgent {
private val toolRegistry = ToolRegistry {
tool(QueryOrderTool())
tool(SendNotificationTool())
tool(EscalateToHumanTool())
}
private val agent = AIAgent(
executor = simpleOpenAIExecutor(ApiKeyManager.openAIApiKey!!),
toolRegistry = toolRegistry,
systemPrompt = """
你是一個專業的客服助手,能夠:
1. 回答一般問題
2. 查詢訂單狀態
3. 處理客戶投訴
4. 必要時轉接人工客服
請使用正體中文回應客戶。
""".trimIndent(),
llmModel = OpenAIModels.CostOptimized.GPT4_1Mini,
strategy = createCustomerServiceStrategy()
)
private fun createCustomerServiceStrategy() = strategy<String, String>("customer_service") {
// 節點:分析客戶意圖並保留原始訊息
val analyzeIntentNode by node<String, CustomerQuery>("analyze_intent") { userMessage ->
println("🔍 分析客戶意圖:$userMessage")
// 簡單的意圖分析
val intent = when {
userMessage.contains("訂單") || userMessage.contains("ORDER") -> "order_query"
userMessage.contains("投訴") || userMessage.contains("問題") || userMessage.contains("不滿") -> "complaint"
userMessage.contains("退貨") || userMessage.contains("退款") -> "return_request"
else -> "general_inquiry"
}
CustomerQuery(userMessage, intent)
}
// 節點:設定一般詢問提示詞
val setupGeneralPromptNode by node<CustomerQuery, CustomerQuery>("setup_general_prompt") { query ->
llm.writeSession {
rewritePrompt {
prompt("setup_general_prompt") {
system("你是一個友善的客服助手,請專業地回答客戶的一般詢問。")
user(query.originalMessage)
}
}
}
query // 傳遞 query 到下一個節點
}
// 節點:設定訂單查詢提示詞
val setupOrderPromptNode by node<CustomerQuery, CustomerQuery>("setup_order_prompt") { query ->
llm.writeSession {
rewritePrompt {
prompt("setup_order_prompt") {
system("""
你是一個客服助手,專門處理訂單查詢。
重要:如果客戶提到具體的訂單號碼(如 ORDER001、ORDER002 等),你必須使用 query_order 工具查詢訂單狀態,不要直接回答。
請先使用工具查詢,然後基於查詢結果回應客戶。
""".trimIndent())
user(query.originalMessage)
}
}
}
query // 傳遞 query 到下一個節點
}
// 節點:設定投訴處理提示詞
val setupComplaintPromptNode by node<CustomerQuery, CustomerQuery>("setup_complaint_prompt") { query ->
llm.writeSession {
rewritePrompt {
prompt("setup_complaint_prompt") {
system("""
客戶有投訴需要處理。請:
1. 表達同理心和歉意
2. 詢問具體問題詳情
3. 提供解決方案
4. 重要:如果問題嚴重或複雜,你必須使用 escalate_to_human 工具轉接人工客服
5. 如果需要發送通知給客戶,使用 send_notification 工具
優先使用適當的工具來處理投訴。
""".trimIndent())
user(query.originalMessage)
}
}
}
query // 傳遞 query 到下一個節點
}
// 節點:處理一般詢問(使用 nodeLLMRequest)
val handleGeneralInquiryNode by nodeLLMRequest("handle_general", allowToolCalls = false)
// 節點:處理訂單查詢(使用 nodeLLMRequest 支援工具)
val handleOrderQueryNode by nodeLLMRequest("handle_order", allowToolCalls = true)
// 節點:處理投訴(使用 nodeLLMRequest 支援工具)
val handleComplaintNode by nodeLLMRequest("handle_complaint", allowToolCalls = true)
// 節點:執行工具
val executeToolNode by nodeExecuteTool("execute_tool")
// 節點:發送工具結果給 LLM
val sendToolResultNode by nodeLLMSendToolResult("send_tool_result")
// 節點:生成最終回應(基於工具結果)
val generateFinalResponseNode by node<String, String>("generate_final_response") { _ ->
llm.writeSession {
updatePrompt {
system("""
現在請根據工具執行的結果,為客戶提供完整、專業且有幫助的回應。
請:
1. 直接回答客戶的問題
2. 基於工具查詢結果提供具體信息
3. 保持友善和專業的語調
4. 如果需要,詢問客戶是否還有其他需要協助的地方
不要只是確認工具已執行,而要基於結果提供實質性的回應。
""".trimIndent())
}
requestLLMWithoutTools().content
}
}
// 定義執行流程
edge(nodeStart forwardTo analyzeIntentNode)
// 根據意圖分流到提示詞設定節點
edge(analyzeIntentNode forwardTo setupGeneralPromptNode onCondition { query ->
query.intent == "general_inquiry"
})
edge(analyzeIntentNode forwardTo setupOrderPromptNode onCondition { query ->
query.intent == "order_query"
})
edge(analyzeIntentNode forwardTo setupComplaintPromptNode onCondition { query ->
query.intent == "complaint" || query.intent == "return_request"
})
// 從提示詞設定節點到 LLM 處理節點
edge(setupGeneralPromptNode forwardTo handleGeneralInquiryNode transformed { _ -> "" })
edge(setupOrderPromptNode forwardTo handleOrderQueryNode transformed { _ -> "" })
edge(setupComplaintPromptNode forwardTo handleComplaintNode transformed { _ -> "" })
// 一般詢問直接結束(不需要工具)
edge(handleGeneralInquiryNode forwardTo nodeFinish onAssistantMessage { true })
// 訂單查詢流程:優先工具執行,但有fallback
edge(handleOrderQueryNode forwardTo executeToolNode onToolCall { true })
edge(handleOrderQueryNode forwardTo nodeFinish onAssistantMessage { true })
// 投訴處理流程:優先工具執行,但有fallback
edge(handleComplaintNode forwardTo executeToolNode onToolCall { true })
edge(handleComplaintNode forwardTo nodeFinish onAssistantMessage { true })
// 工具執行流程
edge(executeToolNode forwardTo sendToolResultNode)
edge(sendToolResultNode forwardTo executeToolNode onToolCall { true })
edge(sendToolResultNode forwardTo generateFinalResponseNode onAssistantMessage { true })
// 最終回應後結束
edge(generateFinalResponseNode forwardTo nodeFinish)
}
suspend fun handleCustomerQuery(query: String): String {
return agent.run(query)
}
}
使用客服工具流程使用範例
suspend fun main() {
val customerService = SmartCustomerServiceAgent()
println("=== 智慧客服系統演示 ===\n")
val testQueries = listOf(
"你好,我想查詢 ORDER001 的訂單狀態",
"我的商品有問題,沒有辦法開機,有沒有辦法換新的,我真的很生氣,可不可以請人跟我聯絡",
"請問你們的營業時間是什麼時候?"
)
testQueries.forEachIndexed { index, query ->
println("📞 客戶諮詢 ${index + 1}:$query")
println("=".repeat(50))
try {
val response = customerService.handleCustomerQuery(query)
println("\n🤖 客服回應:")
println(response)
} catch (e: Exception) {
println("❌ 處理失敗:${e.message}")
}
println("\n" + "=".repeat(60) + "\n")
}
}
執行 AI 回應內容
=== 智慧客服系統演示 ===
📞 客戶諮詢 1:你好,我想查詢 ORDER001 的訂單狀態
==================================================
🔍 分析客戶意圖:你好,我想查詢 ORDER001 的訂單狀態
🔧 執行 query_order:查詢訂單 ORDER001
🤖 客服回應:
您好,您的訂單 ORDER001 已經出貨了,您可以留意配送進度。如果有需要協助追蹤運送或其他訂單相關問題,歡迎隨時告訴我!還有什麼我可以幫助您的嗎?
============================================================
📞 客戶諮詢 2:我的商品有問題,沒有辦法開機,有沒有辦法換新的,我真的很生氣,可不可以請人跟我聯絡
==================================================
🔍 分析客戶意圖:我的商品有問題,沒有辦法開機,有沒有辦法換新的,我真的很生氣,可不可以請人跟我聯絡
🎧 轉接人工客服,原因:客戶商品無法開機,要求換新並請人工聯絡
🤖 客服回應:
您好,非常抱歉聽到您的商品無法開機,讓您感到不便和不快。我已經幫您將問題轉接給我們的人工客服專員,專員將會盡快與您聯繫,協助您辦理換新或其他後續處理。若您還有其他問題或需要額外的協助,隨時告訴我,我會很樂意幫忙!
============================================================
📞 客戶諮詢 3:請問你們的營業時間是什麼時候?
==================================================
🔍 分析客戶意圖:請問你們的營業時間是什麼時候?
🤖 客服回應:
您好!我們的營業時間為週一至週五,上午9點至下午6點。如有其他問題,歡迎隨時向我們詢問!
============================================================
說實在的,這個呼叫工具的流程真的很難寫,除了 API 本身之外,文件其實也沒有寫的很詳細,有點麻煩。就算是寫了相關的流程,AI 也不一定會呼叫使用工具,呼叫了工具產生的結果也不一定是正確的,前後花了很多的時間,在測試寫法和流程
總結
今天我們了解了 Koog 框架的相關工具節點操作
- LLM 互動節點:nodeLLMRequest、nodeUpdatePrompt、nodeLLMSendToolResult
- 工具執行節點:nodeExecuteTool、nodeExecuteMultipleTools
- 進階流程控制:條件邊、輸出轉換、平行處理
- 實際應用:客服系統的相關工具使用實作
這些進階節點讓我們能夠建構出功能豐富、邏輯複雜的 AI 應用系統
下一篇文章我們將學習子圖與模組化,了解如何將複雜的策略圖分解成可重用的模組,進一步提升系統的可維護性和擴展性
參考資源
- Koog AI 官方文件
- Koog Pre-defined nodes and components
- Koog Complex workflow agents
- Koog Custom strategy graphs
支持創作
如果這篇文章對您有幫助,歡迎透過 贊助連結 支持我持續創作優質內容。您的支持是我前進的動力!
圖片來源:AI 產生