11/08/2026 04:45น.

Golang The Series EP.168: Error Handling in AI วิธีรับมือเมื่อ AI ตอบกลับสิ่งที่ไม่คาดคิด (JSON พัง)
#Golang
#Go Backend
#AI Error Handling
#จัดการ Error AI
#JSON พัง
#LLM
#สอน Golang
#Retry Loop
ยินดีต้อนรับเข้าสู่ EP.168 ครับ! ในตอนที่แล้ว (EP.167: Monitoring AI Performance) เราได้วางระบบ Monitoring ด้วย Prometheus เพื่อคอยตรวจวัด Latency ของระบบกันไปแล้ว วันนี้เราจะมาจัดการกับหนึ่งใน Pain point ที่คนทำ AI Backend น่าจะเคยเจอกันทุกคน นั่นคือปัญหาเรื่อง JSON พัง หรือ AI ตอบกลับมาเป็นคำอธิบายแทนที่จะเป็นข้อมูลที่เราต้องการ
เวลาเราเขียน Web API ปกติ ถ้ามีอะไรผิดพลาด เซิร์ฟเวอร์มักจะพ่น HTTP Status Code ออกมาให้เรารู้ทันที เช่น 400 Bad Request หรือ 500 Internal Server Error แต่โลกของ Large Language Models (LLMs) ไม่ได้ตรงไปตรงมาแบบนั้นครับ หลายครั้งที่เราเจอเคสชวนปวดหัว เช่น:
HTTP Status 200 OK แต่ข้างในคือ Refusal: โมเดลตอบกลับมาว่า "ขออภัย ฉันไม่สามารถตอบคำถามนี้ได้เนื่องจากนโยบายความปลอดภัย..."
Invalid Structured Output (JSON Malformed): เราสั่งให้ตอบเป็น JSON เพื่อนำไป
json.Unmarshalลง Struct ใน Go แต่โมเดลดันแถม Markdown Code Block (json ...) มาด้วย หรือบางทีก็ลืมปิดปีกกา}จนระบบพังHallucination & Out-of-Bounds Values: ตอบกลับข้อมูลที่ถูกฟอร์แมต แต่ส่งค่าที่ไม่ตรงตามที่ระบบต้องการ (เช่น Score ควรเป็น 0.0 - 1.0 แต่โมเดลส่ง 99.0 มาให้)
ถ้าเราปล่อยให้ข้อมูลที่ไม่คาดคิดเหล่านี้ทะลุเข้าไปในระบบ จะเกิด Panic หรือทำให้ Business Logic ฝั่ง Go พังลงทันที ในตอนนี้เราจะมาดูเทคนิคการตั้งรับ Error เหล่านี้ใน Go กันครับ
โครงสร้าง 3 ชั้นในการดักจับ AI Error
เพื่อความปลอดภัย เราจะวางโครงสร้างการรับมือเป็น 3 ชั้น ดังนี้:
Plaintext
[User Input]
│
▼
┌─────────────────────────────────┐
│ 1. Pre-validation & Guardrails │ ───> ดักจับ Prompt Injection / คำถามนอกขอบเขตก่อนส่ง
└────────────────┬────────────────┘
│
▼
[Call LLM API]
│
▼
┌─────────────────────────────────┐
│ 2. Structural Parsing & Retry │ ───> ทำความสะอาดข้อความ & ดักจับ JSON Malformed
└────────────────┬────────────────┘
│
▼
┌─────────────────────────────────┐
│ 3. Semantic Validation │ ───> ตรวจสอบความถูกต้องของ Business Rules และตัวเลข
└────────────────┬────────────────┘
│
▼
[Safe Business Logic]
โครงสร้างและการเขียน Go ดักจับ Error พร้อม Retry Loop
เพื่อรับมือกับปัญหา LLM คาย JSON ฟอร์แมตเบี้ยว เราจะสร้างฟังก์ชันสำหรับ Clean Up ข้อความ รวบรวม Error และทำ Retry Loop โดยอัตโนมัติหากปาร์สข้อมูลไม่ผ่าน
ส่วนที่ 1: โครงสร้างข้อมูล ฟังก์ชัน Clean Up และ Semantic Validator
Go
package main
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
)
// UserAnalysisResult คือ Struct เป้าหมายที่เราต้องการรับจาก AI
type UserAnalysisResult struct {
Sentiment string `json:"sentiment"`
Keywords []string `json:"keywords"`
Score float64 `json:"score"`
}
// cleanJSONResponse ทำการลบ Markdown Formatting ที่ LLM มักจะแถมมาให้ออก
func cleanJSONResponse(raw string) string {
cleaned := strings.TrimSpace(raw)
// ลบ Markdown Code Block ทิ้ง
if strings.HasPrefix(cleaned, "```") {
lines := strings.Split(cleaned, "\n")
if len(lines) >= 2 {
// ตัดแถวแรก (```json) และแถวสุดท้าย (```) ออก
cleaned = strings.Join(lines[1:len(lines)-1], "\n")
}
}
return strings.TrimSpace(cleaned)
}
// validateSemantic ตรวจสอบเงื่อนไขทางธุรกิจเพิ่มเติมหลังจาก Unmarshal ได้แล้ว
func (r *UserAnalysisResult) validateSemantic() error {
// 1. เช็กว่า Sentiment อยู่ในขอบเขตที่ยอมรับได้หรือไม่
validSentiments := map[string]bool{"positive": true, "negative": true, "neutral": true}
if !validSentiments[strings.ToLower(r.Sentiment)] {
return fmt.Errorf("invalid sentiment value: '%s'", r.Sentiment)
}
// 2. เช็กว่า Score อยู่ในช่วง 0.0 - 1.0 หรือไม่
if r.Score < 0.0 || r.Score > 1.0 {
return fmt.Errorf("score out of bounds (0.0 - 1.0): %f", r.Score)
}
return nil
}
// simulateLLMCall จำลองการยิง AI โดยตอบกลับฟอร์แมตที่อาจมีปัญหาในรอบแรกๆ
func simulateLLMCall(attempt int) string {
switch attempt {
case 1:
// Attempt 1: แถม Markdown code block และ JSON พัง (พังตรง Unmarshal)
return "```json\n{\"sentiment\": \"positive\", \"keywords\": [\"go\", \"ai\"], \"score\": 0.9"
case 2:
// Attempt 2: JSON ถูกต้อง แต่ Semantic ผิด (Score ทะลุเกณฑ์)
return "{\"sentiment\": \"super_happy\", \"keywords\": [\"go\"], \"score\": 99.0}"
default:
// Attempt 3: ตอบกลับถูกต้องสมบูรณ์
return "{\"sentiment\": \"positive\", \"keywords\": [\"golang\", \"ai\"], \"score\": 0.95}"
}
}
ส่วนที่ 2: ส่วนควบคุมความปลอดภัยหลักและการประมวลผล (main.go)
Go
// SafeAIParseRunner ฟังก์ชันสำหรับยิง LLM + Parse + Validation พร้อมระบบ Retry
func SafeAIParseRunner(ctx context.Context, maxRetries int) (*UserAnalysisResult, error) {
var lastErr error
for attempt := 1; attempt <= maxRetries; attempt++ {
fmt.Printf("🔄 [Attempt %d/%d] กำลังประมวลผลคำสั่ง AI...\n", attempt, maxRetries)
// 1. เรียกใช้งาน LLM
rawOutput := simulateLLMCall(attempt)
// 2. Clean Up Strings
cleanedOutput := cleanJSONResponse(rawOutput)
// 3. Structural Parsing Check
var result UserAnalysisResult
if err := json.Unmarshal([]byte(cleanedOutput), &result); err != nil {
lastErr = fmt.Errorf("JSON Structural Error: %w", err)
fmt.Printf("❌ ล้มเหลว: %v\n\n", lastErr)
time.Sleep(200 * time.Millisecond)
continue // ข้ามไป Retry รอบถัดไป
}
// 4. Semantic Validation Check
if err := result.validateSemantic(); err != nil {
lastErr = fmt.Errorf("Semantic Validation Error: %w", err)
fmt.Printf("❌ ล้มเหลว: %v\n\n", lastErr)
time.Sleep(200 * time.Millisecond)
continue // ข้ามไป Retry รอบถัดไป
}
// หากผ่านการตรวจสอบทุกขั้นตอน
fmt.Printf("✅ สำเร็จในการพยายามรอบที่ %d!\n", attempt)
return &result, nil
}
return nil, fmt.Errorf("พยายามเกินจำนวนสูงสุด (%d ครั้ง) Error ล่าสุด: %w", maxRetries, lastErr)
}
func main() {
ctx := context.Background()
result, err := SafeAIParseRunner(ctx, 3)
if err != nil {
fmt.Printf("🔴 System Error: %v\n", err)
// ณ จุดนี้เราสามารถทำ Fallback Behavior ได้ เช่น ส่ง Default Struct ออกไปแทน
return
}
fmt.Println("\n--- ผลลัพธ์ที่ปลอดภัยพร้อมนำไปใช้งานต่อ ---")
fmt.Printf("Sentiment : %s\n", result.Sentiment)
fmt.Printf("Keywords : %v\n", result.Keywords)
fmt.Printf("Score : %.2f\n", result.Score)
}
เทคนิคขั้นสูง: Structured Outputs & Self-Correction Loop
นอกจากการทำ Clean Up และ Retry ในฝั่ง Go แล้ว ในปัจจุบันเราควรดึงความสามารถของ API มาช่วยลดข้อผิดพลาดตั้งแต่ต้นทางด้วย:
JSON Schema Enforcement (Structured Outputs): กำหนด
ResponseFormatให้เป็น JSON Schema ไปยังโมเดล (เช่น OpenAI Structured Outputs หรือ Gemini Response Schema) การทำแบบนี้จะบังคับให้โมเดลตอบกลับมาตามโครงสร้างเป๊ะๆ ลดปัญหา JSON พังได้เกือบ 100%Self-Correction Retry (Feedback Loop): เมื่อเกิด Error ในรอบแรก แทนที่เราจะยิง Prompt เดิมซ้ำโง่ๆ ให้เราเอา Error Message ใน Go (เช่น
invalid sentiment value: super_happy) แนบต่อท้ายไปใน Prompt รอบใหม่ เพื่อบอก AI ว่า "คุณตอบผิดตรงนี้นะ แก้มาใหม่ด้วย" วิธีนี้จะเพิ่มโอกาสสำเร็จในรอบถัดไปได้สูงมาก
🎯 ท้าให้ลอง (Daily Mission)
ลองนำโค้ดด้านบนไปรันในเครื่องของคุณ แล้วสังเกตการทำงานและการตัด Error ในแต่ละ Attempt บน Console
การบ้านชวนคิด: หากคุณเจอกรณีที่ AI ตอบกลับเป็นข้อความปฏิเสธ (Refusal Text) เช่น "ฉันไม่สามารถประมวลผลคำถามเกี่ยวกับนโยบายนี้ได้" ซึ่งส่งผลให้ json.Unmarshal พังแน่นอน คุณจะมีวิธีเขียน Custom Error Handler ใน Go อย่างไรเพื่อแยกแยะให้ได้ว่านี่คือ "JSON พังปกติ" หรือ "AI ปฏิเสธการตอบ" เพื่อให้ระบบสามารถจัดการได้อย่างถูกต้อง? ลองออกแบบกันดูนะครับ!
🙋♂️ FAQ คำถามที่พบบ่อย
ทำไมไม่ใช้ Regex ดึงเฉพาะ JSON ออกมา แทนการเขียนเช็ก Markdown Code Block?
การใช้ Regex ดึง JSON ค่อนข้างเสี่ยงและกินรีซอร์สมากกว่าครับ ถ้าโมเดลตอบ JSON ที่มีโครงสร้างซ้อนกันลึกๆ หรือมีอักขระพิเศษ Regex อาจจะจับคู่พลาดได้ การใช้ฟังก์ชันตัด String ธรรมดาเพื่อเอา Markdown ออกจะทำงานได้รวดเร็วและปลอดภัยกว่ามากใน Go
ควรตั้ง maxRetries ไว้ที่เท่าไหร่ดี?
โดยทั่วไป 2 ถึง 3 ครั้งก็เพียงพอแล้วครับ การเรียก LLM ใช้เวลานาน (High Latency) ถ้าเรา Retry มากเกินไป Request นั้นอาจจะบล็อกระบบจนเกิด Timeout ฝั่ง Client ได้ แนะนำว่าถ้า Retry ครบโควตาแล้ว ให้ทำระบบ Fallback โดยการส่งค่า Default กลับไปให้ผู้ใช้งานแทนครับ
เราจำเป็นต้องเขียนเช็ก Semantic Validation ทุกฟิลด์เลยไหม?
ไม่จำเป็นครับ ให้เน้นเช็กเฉพาะฟิลด์ที่มีผลต่อ Business Logic หลักๆ เช่น สถานะ (Status), ตัวเลขคะแนน (Score) หรือฟิลด์ที่ต้องนำไปประมวลผลทางคณิตศาสตร์ต่อ ส่วนที่เป็นแค่ข้อความทั่วไป (เช่น คำอธิบายประกอบ) ปล่อยผ่านได้เลยเพื่อประหยัดเวลาประมวลผลครับ
📝 สรุป
ในการทำงานกับ AI เราไม่สามารถเชื่อใจผลลัพธ์ที่ได้มาแบบ 100% ครับ เทคนิคที่เราเรียนรู้ในวันนี้คือการทำ Pre-validation, การสร้างฟังก์ชัน Clean Up เพื่อจัดการ JSON ที่แถม Markdown, การทำ Semantic Validation เพื่อกรองข้อมูลขยะ และการใช้ Retry Loop เพื่อแก้ปัญหาเฉพาะหน้าโดยอัตโนมัติ การวางโครงสร้าง 3 ชั้นแบบนี้จะช่วยให้ Go Backend ของเราทนทานต่อความไม่แน่นอนของ LLM และปล่อยขึ้น Production ได้อย่างมั่นใจครับ
ในตอนต่อไป (EP.169): เราสร้างระบบ AI Microservices บน Go มาอย่างเข้มข้นตลอดทั้งซีรีส์ แต่หลายคนอาจจะสงสัยว่า "ทำไมถึงต้องใช้ Go ในงาน AI ทั้งที่ Python คือลูกรักของวงการนี้?" ตอนหน้าเราจะมาทดสอบเปรียบเทียบหมัดต่อหมัดในหัวข้อ "Benchmarking Go vs. Python for AI Pipelines — ดูความแตกต่างด้าน Latency, Concurrency และ Memory Usage" ห้ามพลาดครับ Gophers!
ฝากกดติดตามพวกเราได้ที่ Superdev Academy ในทุกช่องทางนะครับ!
🔵 Facebook: Superdev Academy Thailand (อัปเดตข่าวสารและบทความใหม่)
🎬 YouTube: Superdev Academy Channel (ติวเข้มแบบวิดีโอ)
📸 Instagram: @superdevacademy (เกร็ดความรู้สั้นๆ และเบื้องหลังการทำงาน)
🎬 TikTok: @superdevacademy (Tips & Tricks ฉบับย่อยง่าย)
🌐 Website: superdevacademy.com (คลังบทความและคอร์สเรียนฉบับเต็ม)