- Published on
第一次學 Kotlin Koog AI 就上手 Day 29:結構化資料處理:讓 AI 回覆更精準的秘密武器
在前一篇文章中,我們學習了 AI 應用的測試策略,解決了回應隨機性帶來的挑戰。但你有沒有想過,如果能從源頭就讓 AI 的回應變得可預測,測試會不會變得更簡單?今天我們要探討 Koog 框架的結構化資料處理功能,這是確保 AI 回應一致性的關鍵技術
想像一下,當你的 AI 客服系統回覆客戶時,有時是 JSON 格式,有時是純文字,這不僅讓後續處理變得複雜,更讓測試變得困難重重。透過結構化資料處理,我們能夠確保 AI 總是按照預期的格式回應,大幅提升應用程式的穩定性和可測試性。今天我們將建立一個訂單處理系統和客服助手,實際展示如何運用這項功能
為什麼需要結構化資料處理
傳統 AI 回覆的痛點
在沒有結構化資料處理之前,我們常常會遇到這些問題
// AI 回覆可能是這樣...
"今天台北的氣溫是 25 度,天氣晴朗"
// 或者是這樣...
{
"temperature": 25,
"weather": "sunny",
"city": "台北"
}
// 甚至是這樣...
溫度:25度
天氣:晴天
城市:台北
這種不一致的格式讓程式碼處理變得非常複雜,需要寫很多解析邏輯來處理各種可能的回應格式
結構化資料的優勢
使用 Koog 的結構化資料處理,我們能夠獲得
- 可預測性:AI 總是按照定義的結構回應
- 類型安全:編譯時就能檢查資料結構
- 易於處理:直接得到 Kotlin 物件,無需額外解析
- 錯誤修正:內建重試機制,自動修正格式錯誤
基礎資料模型定義
讓我們從一個簡單的天氣預報範例開始
@Serializable
@SerialName("WeatherForecast")
@LLMDescription("天氣預報資訊")
data class WeatherForecast(
@property:LLMDescription("城市名稱")
val city: String,
@property:LLMDescription("攝氏溫度")
val temperature: Int,
@property:LLMDescription("天氣狀況(例如:晴天、多雲、雨天)")
val conditions: String,
@property:LLMDescription("降雨機率百分比")
val precipitation: Int
)
關鍵註解說明
@Serializable
:讓類別支援序列化@SerialName
:指定序列化時的名稱@LLMDescription
:為 AI 提供清楚的描述,幫助其理解欄位用途
集合與巢狀結構支援
Koog 支援各種複雜的資料結構
@Serializable
@SerialName("ComplexOrderData")
data class ComplexOrderData(
// 清單支援
@property:LLMDescription("商品清單")
val items: List<OrderItem>,
// 對應表支援
@property:LLMDescription("配送資訊對應表")
val shippingInfo: Map<String, String>,
// 巢狀物件支援
@property:LLMDescription("地址資訊")
val address: Address
) {
@Serializable
@SerialName("Address")
data class Address(
@property:LLMDescription("郵遞區號")
val zipCode: String,
@property:LLMDescription("完整地址")
val fullAddress: String
)
}
多型資料結構
使用 sealed class 處理不同類型的通知系統
@Serializable
@SerialName("NotificationRequest")
@LLMDescription("通知請求的基礎類型")
sealed class NotificationRequest {
@Serializable
@SerialName("EmailNotification")
@LLMDescription("電子郵件通知")
data class EmailNotification(
@property:LLMDescription("收件人電子郵件")
val recipient: String,
@property:LLMDescription("郵件主旨")
val subject: String,
@property:LLMDescription("郵件內容")
val content: String,
@property:LLMDescription("是否為重要郵件")
val isUrgent: Boolean = false
) : NotificationRequest()
@Serializable
@SerialName("SMSNotification")
@LLMDescription("簡訊通知")
data class SMSNotification(
@property:LLMDescription("手機號碼")
val phoneNumber: String,
@property:LLMDescription("簡訊內容")
val message: String
) : NotificationRequest()
@Serializable
@SerialName("PushNotification")
@LLMDescription("推播通知")
data class PushNotification(
@property:LLMDescription("使用者 ID")
val userId: String,
@property:LLMDescription("通知標題")
val title: String,
@property:LLMDescription("通知內容")
val body: String,
@property:LLMDescription("附加資料")
val data: Map<String, String> = emptyMap()
) : NotificationRequest()
}
訂單處理系統 - 基本 Agent 請求範例
建立相關訂單資料結構
// 訂單資料模型
@Serializable
@SerialName("OrderInfo")
@LLMDescription("完整的訂單資訊")
data class OrderInfo(
@property:LLMDescription("訂單編號")
val orderId: String,
@property:LLMDescription("客戶資訊")
val customer: CustomerInfo,
@property:LLMDescription("訂單商品清單")
val items: List<OrderItem>,
@property:LLMDescription("訂單狀態")
val status: OrderStatus,
@property:LLMDescription("總金額")
val totalAmount: Double
)
// 客戶資訊
@Serializable
@SerialName("CustomerInfo")
@LLMDescription("客戶基本資料")
data class CustomerInfo(
@property:LLMDescription("客戶姓名")
val name: String,
@property:LLMDescription("電子郵件地址")
val email: String,
@property:LLMDescription("聯絡電話")
val phone: String
)
// 訂單商品
@Serializable
@SerialName("OrderItem")
@LLMDescription("單項商品資訊")
data class OrderItem(
@property:LLMDescription("商品編號")
val productId: String,
@property:LLMDescription("商品名稱")
val productName: String,
@property:LLMDescription("購買數量")
val quantity: Int,
@property:LLMDescription("單價")
val price: Double
)
// 訂單狀態枚舉
@Serializable
@SerialName("OrderStatus")
enum class OrderStatus {
PENDING, // 待處理
CONFIRMED, // 已確認
SHIPPED, // 已出貨
DELIVERED, // 已送達
CANCELLED // 已取消
}
JSON Schema 產生參數說明
在使用 JsonStructuredData.createJsonStructure
建立 JSON Schema 時,需要了解幾個重要參數
val orderStructure = JsonStructuredData.createJsonStructure<OrderInfo>(
schemaFormat = JsonSchemaGenerator.SchemaFormat.JsonSchema, // Schema 格式
examples = exampleOrders, // 範例資料
schemaType = JsonStructuredData.JsonSchemaType.SIMPLE, // Schema 類型
)
關鍵參數解釋
schemaFormat:指定 JSON Schema 格式
JsonSchemaGenerator.SchemaFormat.JsonSchema
:標準 JSON Schema 格式,功能完整JsonSchemaGenerator.SchemaFormat.Simple
:簡化的 Schema 格式,有一些模型和使用上的限制 (例如沒有多型的支援)
schemaType:決定 Schema 的複雜度和相容性
JsonStructuredData.JsonSchemaType.SIMPLE
- 支援標準 JSON 欄位
- 不支援多型(polymorphism)
- 與更多語言模型相容
- 適合簡單的資料結構
JsonStructuredData.JsonSchemaType.FULL
- 進階 JSON Schema 功能
- 支援多型和繼承
- 與較少語言模型相容
- 適合複雜的資料結構
examples:提供範例資料清單,幫助 LLM 更好理解預期的資料結構
基本的結構化範例
suspend fun main() {
val promptExecutor = simpleOpenAIExecutor(ApiKeyManager.openAIApiKey!!)
// 建立範例資料幫助 AI 理解
val exampleOrders = listOf(
OrderInfo(
orderId = "ORD-2025-001",
customer = CustomerInfo(
name = "王小明",
email = "[email protected]",
phone = "0912-345-678"
),
items = listOf(
OrderItem(
productId = "PROD-001",
productName = "智慧手錶",
quantity = 1,
price = 8999.0
)
),
status = OrderStatus.PENDING,
totalAmount = 8999.0
)
)
// 產生結構化資料定義
val orderStructure = JsonStructuredData.createJsonStructure<OrderInfo>(
schemaFormat = JsonSchemaGenerator.SchemaFormat.JsonSchema,
examples = exampleOrders,
schemaType = JsonStructuredData.JsonSchemaType.SIMPLE,
)
val orderContent = "幫我建立一筆購買 iPhone 17 Air 的訂單,客戶是 9527,電話 0912-345-678"
// 執行結構化請求
val structuredResponse = promptExecutor.executeStructured(
prompt = prompt("order-creation") {
system(
"""
您是一個專業的訂單處理助手。
根據使用者的要求建立訂單資訊,確保所有必要欄位都完整填寫。
""".trimIndent()
)
user(orderContent)
},
mainModel = OpenAIModels.CostOptimized.GPT4_1Mini,
structure = orderStructure,
retries = 5,
)
executeStructured 方法參數說明
executeStructured
方法是執行結構化請求的核心方法,包含以下重要參數
- structure:定義預期資料結構的 JSON Schema,由
JsonStructuredData.createJsonStructure
產生 - mainModel:執行主要任務的語言模型,負責理解提示詞並產生內容
- retries:嘗試解析回應的次數(預設:1),當 AI 回應格式不正確時會重試
- fixingModel(選用):用於修正格式錯誤的模型,預設為 GPT-4o
mainModel vs fixingModel
mainModel
:負責理解任務並產生主要內容,可以選擇成本較低的模型fixingModel
:專門處理格式修正,通常使用更強大但成本較高的模型
這種分工可以在保證輸出品質的同時,優化整體的成本和效能
println("訂單描述 - $orderContent")
structuredResponse.getOrNull()?.let { order ->
println("\nAI 回應內容")
println("*".repeat(30))
// 直接取得類型安全的 OrderInfo 物件
println("\n完整內容:${order.structure}")
println("\n訂單編號:${order.structure.orderId}")
println("\n客戶姓名:${order.structure.customer.name}")
println("\n總金額:$${order.structure.totalAmount}")
}
}
執行 AI 回應內容
訂單描述 - 幫我建立一筆購買 iPhone 17 Air 的訂單,客戶是 9527,電話 0912-345-678
AI 回應內容
******************************
完整內容:OrderInfo(orderId=ORD-9527-001, customer=CustomerInfo(name=9527, email=9527@example.com, phone=0912-345-678), items=[OrderItem(productId=IP17AIR-001, productName=iPhone 17 Air, quantity=1, price=0.0)], status=PENDING, totalAmount=0.0)
訂單編號:ORD-9527-001
客戶姓名:9527
總金額:$0.0
AI 客服 - strategy (node) Agent 請求範例
讓我們建立一個客服 AI Agent
資料模型定義
// 客服回應的資料結構
@Serializable
@SerialName("CustomerServiceResponse")
@LLMDescription("客服助手的回應結構")
data class CustomerServiceResponse(
@property:LLMDescription("回應類型")
val responseType: ResponseType,
@property:LLMDescription("回應內容")
val content: String,
@property:LLMDescription("建議的後續動作")
val suggestedActions: List<String>,
@property:LLMDescription("是否需要人工介入")
val requiresHumanIntervention: Boolean = false,
@property:LLMDescription("相關訂單編號(如果適用)")
val relatedOrderId: String? = null
)
@Serializable
enum class ResponseType {
ERROR, // AI 錯誤
INFORMATION, // 資訊查詢
COMPLAINT, // 客訴處理
ORDER_INQUIRY, // 訂單查詢
TECHNICAL_SUPPORT, // 技術支援
GENERAL // 一般詢問
}
系統配置物件
// 客服系統配置物件
object CustomerServiceSystem {
// 建立範例資料幫助 AI 理解
val exampleResponses = listOf(
CustomerServiceResponse(
responseType = ResponseType.ORDER_INQUIRY,
content = "您的訂單 ORD-2025-001 目前正在處理中,預計 3-5 個工作天內出貨",
suggestedActions = listOf("追蹤物流狀態", "聯繫客服確認詳細時程"),
requiresHumanIntervention = false,
relatedOrderId = "ORD-2025-001"
),
CustomerServiceResponse(
responseType = ResponseType.COMPLAINT,
content = "很抱歉造成您的困擾,我們會立即處理您的退貨申請",
suggestedActions = listOf("安排退貨流程", "聯繫品質管理部門", "提供補償方案"),
requiresHumanIntervention = true,
relatedOrderId = null
)
)
// 產生結構化資料定義
val customerServiceStructure = JsonStructuredData.createJsonStructure<CustomerServiceResponse>(
schemaFormat = JsonSchemaGenerator.SchemaFormat.JsonSchema,
examples = exampleResponses,
schemaType = JsonStructuredData.JsonSchemaType.SIMPLE,
)
}
客服 Agent 類別
// 客服助手 Agent 類別
class CustomerServiceAgent() {
// 建立客服策略
private fun createStrategy() = strategy("customer-service") {
val setup by nodeLLMRequest()
val analyzeInquiry by node<Message.Response, CustomerServiceResponse> { _ ->
// 在 Strategy Node 中使用結構化請求
// 這裡使用 requestLLMStructured 而非 executeStructured
// 因為在 Agent 的 writeSession 中已經有了執行上下文
val structureResponse = llm.writeSession {
requestLLMStructured(
structure = CustomerServiceSystem().customerServiceStructure,
fixingModel = OpenAIModels.CostOptimized.GPT4oMini
)
}
structureResponse.fold(
onSuccess = {
it.structure
},
onFailure = {
// 提供預設的客服回應
CustomerServiceResponse(
responseType = ResponseType.ERROR,
content = "處理時發生錯誤,請稍後再試",
suggestedActions = listOf("重新提交請求", "聯繫人工客服"),
requiresHumanIntervention = true,
relatedOrderId = null
)
}
)
}
val processResponse by node<CustomerServiceResponse, String> { response ->
val result = StringBuilder()
result.appendLine("=== 客服助手回應 ===")
result.appendLine("類型:${response.responseType}")
result.appendLine("內容:${response.content}")
if (response.suggestedActions.isNotEmpty()) {
result.appendLine("\n建議後續動作:")
response.suggestedActions.forEach { action ->
result.appendLine("• $action")
}
}
if (response.requiresHumanIntervention) {
result.appendLine("\n⚠️ 此案件需要人工介入處理")
}
response.relatedOrderId?.let { orderId ->
result.appendLine("\n相關訂單:$orderId")
}
result.toString()
}
edge(nodeStart forwardTo setup)
edge(setup forwardTo analyzeInquiry)
edge(analyzeInquiry forwardTo processResponse)
edge(processResponse forwardTo nodeFinish)
}
// 執行客服查詢
suspend fun handleInquiry(inquiry: String): String {
// 初始化 PromptExecutor
val promptExecutor = simpleOpenAIExecutor(ApiKeyManager.openAIApiKey!!)
val agent = AIAgent(
promptExecutor = promptExecutor,
toolRegistry = ToolRegistry.EMPTY,
strategy = createStrategy(),
agentConfig = AIAgentConfig(
prompt = prompt("customer-service-prompt") {
system(
"""
您是一個專業的客服助手,負責處理各種客戶詢問。
請根據客戶的問題分析類型,提供適當的回應,並建議後續處理動作。
處理原則:
1. 保持友善和專業的態度
2. 準確判斷問題類型
3. 提供清楚的解決方案或資訊
4. 必要時建議轉接人工客服
""".trimIndent()
)
},
model = OpenAIModels.CostOptimized.GPT4oMini,
maxAgentIterations = 5
)
)
return agent.run(inquiry)
}
}
AI 客服使用範例
suspend fun main() {
// 建立客服助手 Agent
val customerServiceAgent = CustomerServiceAgent(promptExecutor)
// 模擬客戶詢問
val inquiries = listOf(
"我的訂單 ORD-2025-001 什麼時候會到貨?",
"產品有品質問題,我要退貨!",
"如何更改我的會員資料?",
"APP 一直當機,無法正常使用"
)
// 處理每個客戶詢問
inquiries.forEach { inquiry ->
println("\n客戶詢問:$inquiry")
println("=" * 50)
val response = customerServiceAgent.handleInquiry(inquiry)
println(response)
println()
}
}
執行 AI 回應內容
客戶詢問:我的訂單 ORD-2025-001 什麼時候會到貨?
==================================================
=== 客服助手回應 ===
類型:ORDER_INQUIRY
內容:您的訂單 ORD-2025-001 目前應該在運送途中,建議查看訂單跟蹤資訊以獲得即時狀態更新。
建議後續動作:
• 查看訂單跟蹤資訊
• 聯繫客服以獲取更多詳細資訊
⚠️ 此案件需要人工介入處理
相關訂單:ORD-2025-001
客戶詢問:產品有品質問題,我要退貨!
==================================================
=== 客服助手回應 ===
類型:COMPLAINT
內容:很抱歉聽到您遇到產品品質問題,我們會協助您處理退貨事宜。請您提供訂單號碼和產品資訊,以便我幫助您進行退貨流程。
建議後續動作:
• 提供訂單號碼和產品名稱
• 閱讀退貨政策
• 聯繫人工客服以獲取更直接的協助
客戶詢問:如何更改我的會員資料?
==================================================
=== 客服助手回應 ===
類型:INFORMATION
內容:要更改您的會員資料,請登入您的帳戶,前往會員中心或帳戶設置,然後修改所需資料並保存更改。
建議後續動作:
• 登入帳戶
• 前往會員中心進行資料修改
• 聯絡人工客服以獲得幫助
客戶詢問:APP 一直當機,無法正常使用
==================================================
=== 客服助手回應 ===
類型:TECHNICAL_SUPPORT
內容:您好!感謝您聯繫我們,抱歉聽到您在使用我們的APP時遇到問題。建議您檢查是否有可用的APP更新、重新啟動設備、清除APP緩存以及檢查網絡連接。希望這些步驟能幫助解決問題!
建議後續動作:
• 檢查應用商店中APP的更新
• 重新啟動您的設備
• 清除APP緩存
• 檢查您的網絡連接
• 聯絡人工客服獲取進一步協助
⚠️ 此案件需要人工介入處理
最佳實踐指南
使用清楚的描述
@Serializable
data class Product(
// 好的描述
@property:LLMDescription("產品的唯一識別碼,格式如 PROD-001")
val productId: String,
// 不好的描述
@property:LLMDescription("名稱")
val name: String
)
提供範例資料
val examples = listOf(
Product("PROD-001", "智慧手錶"),
Product("PROD-002", "藍牙耳機")
)
val productStructure = JsonStructuredData.createJsonStructure<Product>(
schemaGenerator = BasicJsonSchemaGenerator.Default,
examples = examples // 範例幫助 AI 更好地理解結構
)
結語
透過本篇文章,我們深入學習了 Koog 框架的結構化資料處理功能,這是建立可靠 AI 應用的核心技術之一。從簡單的資料模型定義到複雜的多型結構,我們掌握了如何讓 AI 產生可預測、類型安全的回應
結構化資料處理不僅解決了 AI 回應不一致的問題,更與我們在 Day 28 學習的測試策略相輔相成。當 AI 的輸出格式固定時,測試變得更加簡單直接,品質保證也更容易實施。這種「從源頭確保品質」的方法,是建立生產級 AI 應用的最佳實踐
下一篇文章,我們將探索 Koog AI 框架的 Embeddings 向量嵌入強大技術。結合結構化資料處理、測試策略和這些進階功能,你將能夠建立真正可靠的企業級 AI 應用
參考文件
支持創作
如果這篇文章對您有幫助,歡迎透過 贊助連結 支持我持續創作優質內容。您的支持是我前進的動力!
圖片來源:AI 產生