การดู : 123

01/06/2026 04:41น.

วิธีการตั้งค่า JSON Mode ใน Go และการใช้ json.Unmarshal เพื่อจัดการข้อมูลจาก AI

Golang The Series EP.147: Structured Output บังคับ AI ตอบเป็น JSON

#Golang The Series

#Golang

#Go

#JSON Mode

#JSON

#Structured Output

#AI API

#Backend Development

ยินดีต้อนรับสู่ EP.147 ครับ! ในตอนที่แล้วเราได้เรียนรู้ศิลปะการเขียน Prompt เพื่อคุยกับ AI ไปแล้ว แต่ปัญหาที่ยังกวนใจ Gophers มากที่สุดคือความ "ช่างพูด" ของ AI ที่มักจะแถมคำทักทายอย่าง "แน่นอนครับ นี่คือ JSON ที่คุณต้องการ..." หรือปิดท้ายด้วย "หวังว่าข้อมูลนี้จะเป็นประโยชน์นะคะ"

ซึ่งไอ้ประโยคแถมพวกนี้แหละครับที่เป็นตัวการทำให้ฟังก์ชัน json.Unmarshal ใน Go ของเราพ่น Error กระจาย เพราะมันไม่ใช่ JSON ที่บริสุทธิ์ (Pure JSON) วันนี้เราจะมาจบปัญหานี้ด้วยเทคนิคการทำ Structured Output เพื่อบีบให้ AI ตอบกลับมาเป็น Data ตามโครงสร้าง Struct ที่เรากำหนดไว้แบบเป๊ะๆ 100% ครับ

การออกแบบ Struct ใน Go ให้รองรับ AI Output

หัวใจสำคัญไม่ใช่แค่การสร้าง Struct ขึ้นมาเฉยๆ แต่คือการทำให้ Struct นั้นทำหน้าที่เป็น Contract (สัญญา) ระหว่างโค้ด Go ของเรากับ AI ครับ สิ่งที่ต้องโฟกัสมี 3 จุดหลัก:

  • JSON Tags คือกฎเหล็ก: ชื่อที่อยู่ใน json:"..." คือชื่อฟิลด์ที่ AI จะต้องพ่นออกมาให้ตรงกันเป๊ะ (Case-sensitive)

  • Data Type ที่เหมาะสม: หากต้องการคะแนนที่เป็นตัวเลข ต้องใช้ int หรือ float64 เพื่อให้ Go ช่วยตรวจสอบประเภทข้อมูลให้เราตั้งแต่ตอน Unmarshal

  • ความเป็นไปได้ของข้อมูล: หากฟิลด์ไหนมีโอกาสที่ AI จะหาค่าไม่ได้ ให้พิจารณาใช้ Pointer (เช่น *string) เพื่อรองรับค่า null ครับ

ตัวอย่างการวางโครงสร้าง:

Go

type AnalysisResult struct {
    // ใช้ชื่อฟิลด์ที่เป็นมาตรฐานและสื่อความหมายชัดเจน
    Sentiment string   `json:"sentiment"` 
    Score     int      `json:"score"`
    Keywords  []string `json:"keywords"`
    Summary   string   `json:"summary"`
}

💡 Pro-Tip สำหรับ Gopher:

การตั้งชื่อฟิลด์ใน JSON Tags ให้เป็นภาษาอังกฤษที่สื่อความหมายชัดเจน (เช่น sentiment แทนที่จะเป็น s) จะช่วยให้ AI เข้าใจบริบทของข้อมูลที่มันต้องเติมลงไปได้แม่นยำขึ้นโดยแทบไม่ต้องอธิบายเพิ่มใน Prompt เลยครับ เพราะ AI ถูกเทรนมาให้เข้าใจความหมายของชื่อตัวแปรอยู่แล้ว

เทคนิค JSON Mode และ System Prompt

ถ้าเป็นเมื่อก่อน เราต้องภาวนาให้ AI เชื่อฟังคำสั่ง "Please respond in JSON" ของเรา แต่ปัจจุบัน API รุ่นใหม่ๆ อย่าง GPT-4o หรือแม้แต่โมเดลที่รันในเครื่องผ่าน Ollama มีฟีเจอร์ที่เรียกว่า JSON Mode ซึ่งเป็นการบังคับที่ระดับเอนจินเลยว่า "แกห้ามตอบอย่างอื่นนอกจาก JSON ที่ Valid เท่านั้นนะ"

เงื่อนไขที่ห้ามลืม!

ถึงแม้จะเปิดโหมดนี้แล้ว แต่ OpenAI มีกฎเหล็กว่า คุณต้องระบุคำว่า "JSON" ลงใน Message (แนะนำให้ใส่ใน System Prompt) ไว้ด้วยเสมอ หากลืมใส่ API จะตีกลับเป็น Error ทันทีครับ

ตัวอย่างการตั้งค่าใน Go:

Go

req := openai.ChatCompletionRequest{
    Model: openai.GPT4o,
    // จุดสำคัญ: บังคับระดับ Structure ให้เป็น JSON Object
    ResponseFormat: &openai.ChatCompletionResponseFormat{
        Type: openai.ChatCompletionResponseFormatTypeJSONObject,
    },
    Messages: []openai.ChatCompletionMessage{
        {
            Role:    openai.ChatMessageRoleSystem,
            // ต้องมีคำว่า JSON อยู่ในนี้เสมอ!
            Content: "You are a helpful assistant designed to output JSON.",
        },
        {
            Role:    openai.ChatMessageRoleUser,
            Content: "Analyze this feedback: 'I love this gopher tool!'",
        },
    },
}

🛡️ ทำไมต้องใช้ JSON Mode?

การใช้โหมดนี้จะการันตีว่า Output ที่ได้จะไม่มีประโยคทักทายอย่าง "Here is your data:" โผล่มาแน่นอน และ JSON ที่ได้จะเป็นรูปแบบที่ Well-formed (ปีกกาเปิด-ปิดครบถ้วน) เสมอ ช่วยลดภาระในการทำ Error Handling ในโค้ด Go ของเราไปได้มหาศาลครับ

การ Parsing และ Handling Error

เมื่อ AI ตอบกลับมาในรูปแบบ JSON String หน้าที่ของนักพัฒนา Go คือการแปลงข้อมูลนั้นให้กลับมาเป็น Typed Struct เพื่อให้เราเรียกใช้งาน Field ต่างๆ ได้อย่างปลอดภัย (Type Safety) โดยใช้ฟังก์ชัน json.Unmarshal

ตัวอย่างการเขียนโค้ด:

Go

var result AnalysisResult

// ดึงเนื้อหาจาก AI Response
content := resp.Choices[0].Message.Content

// แปลงจาก String/Byte Slice เข้าสู่ Struct
err := json.Unmarshal([]byte(content), &result)
if err != nil {
    // ในโปรดักชันจริง แนะนำให้ Log และแจ้งเตือน หรือทำ Retry Logic
    log.Printf("Error: AI returned invalid JSON: %v", err)
    return
}

// ใช้งานข้อมูลได้ทันทีแบบ Strong Type!
fmt.Printf("Sentiment: %s (Score: %d)\n", result.Sentiment, result.Score)

⚠️ ข้อควรระวัง: อย่าเพิ่งไว้ใจ AI 100%

แม้ว่าเราจะเปิด JSON Mode แล้ว แต่ในฐานะ Developer เราควรทำระบบให้ Robust (อึด) ต่อความผิดพลาดด้วยเทคนิคเหล่านี้ครับ:

  1. Validation: หลังจาก Unmarshal เสร็จ ควรตรวจสอบค่าที่ได้ด้วย (เช่น Score ควรอยู่ระหว่าง 0-100 หรือไม่?) เพราะ AI อาจจะตอบ JSON ที่มีโครงสร้างถูก แต่ "เนื้อหา" ยังผิดเพี้ยนได้

  2. Graceful Recovery: แทนที่จะใช้ log.Fatalf ซึ่งจะทำให้โปรแกรมหยุดทำงานทันที เราควรจัดการ Error ให้เหมาะสม เช่น การส่งค่า Default กลับไป หรือการส่ง Prompt กลับไปให้ AI แก้ไขใหม่อีกรอบ (Self-Correction)

  3. The Markdown Trap: สำหรับโมเดลบางตัว (เช่น Local LLM) ต่อให้สั่งเป็น JSON มันอาจจะแถม json ... มาให้ แนะนำให้เขียนฟังก์ชันสั้นๆ เพื่อ Trim หรือลบเครื่องหมายเหล่านี้ออกก่อนจะส่งให้ json.Unmarshal ครับ

ทำไมต้องทำ Structured Output?

สำหรับ Gopher อย่างเรา การที่ AI คุยเป็น JSON ไม่ใช่แค่เรื่องของความสะดวก แต่มันคือการทำให้ AI ทำงานได้เหมือนกับ Microservice ตัวหนึ่งในระบบเลยครับ

  • Reliability (ความน่าเชื่อถือ): ระบบมีความเสถียรสูงขึ้น เพราะเรารู้ล่วงหน้าว่า Data Type ที่ได้จะเป็นอย่างไร ไม่ต้องมาลุ้นว่าวันนี้ AI จะตอบเป็นร้อยแก้วหรือร้อยกรอง ทำให้โอกาสที่โปรแกรมจะ Panic หรือ Error ลดลงอย่างมาก

  • Automation (ระบบอัตโนมัติ): เมื่อข้อมูลเป็น JSON เราสามารถเชื่อมต่อ Workflow ได้ทันที เช่น นำผลลัพธ์ไป Insert ลง Database, ส่ง Webhook ไปหาทีม Support หรือส่งต่อให้ Service อื่นๆ ในระบบประมวลผลต่อได้ทันทีโดยไม่ต้องใช้มนุษย์มานั่งคัดกรอง

  • Type Safety (ความปลอดภัยของข้อมูล): เราได้ดึงจุดแข็งของภาษา Go มาใช้อย่างเต็มที่ ทั้งการทำ Validation, การตรวจสอบข้อมูลที่ขาดหาย (Missing fields) และการจัดการโครงสร้างข้อมูลที่ซับซ้อน (Nested Structs) ได้อย่างมั่นใจ

🎯 ท้าให้ลอง (Daily Mission)

เพื่อให้เห็นภาพการใช้งานจริง ผมอยากให้ทุกคนลองสร้างโปรแกรม Go สั้นๆ ที่รับประโยครีวิวสินค้าจาก User แล้วสั่งให้ AI สกัดข้อมูล (Extract) ออกมาเป็น JSON โดยใช้ Struct ดังนี้ครับ:

Go

type ReviewAnalysis struct {
    IsPositive  bool   `json:"is_positive"`
    ProductName string `json:"product_name"`
    DefectFound string `json:"defect_found"`
}

โจทย์การบ้าน:

ลองส่งประโยคที่ "ไม่มีชื่อสินค้า" หรือ "ไม่มีการแจ้งตำหนิ" เข้าไปดูครับ แล้วสังเกตว่า AI จะจัดการอย่างไร? มันจะใส่ค่าว่าง "", ใส่ null หรือข้ามฟิลด์นั้นไปเลย?

Hint: ลองปรับ Struct ของคุณให้ใช้ Pointer เช่น *string แล้วดูความแตกต่างตอน Unmarshal ว่ามันช่วยให้คุณจัดการกับข้อมูลที่ "ไม่ได้ส่งมา" ได้ดีขึ้นขนาดไหน!


บทสรุป

การทำ Structured Output คือการเปลี่ยน AI จากแชทบอทช่างคุยให้กลายเป็น Data Processor ที่คุยภาษาเดียวกับ Backend ของเราครับ เมื่อคุณคุม Output ได้ 100% ขีดจำกัดในการสร้างแอปพลิเคชันของคุณก็จะขยายไปได้กว้างกว่าเดิมมาก

ในตอนต่อไป (EP.148):

เมื่อเราจัดการข้อมูลได้แม่นยำแล้ว ต่อไปคือการทำให้ User ประทับใจด้วยความเร็ว! เราจะมาทำระบบ "Handling Streams: การทำระบบ Chat แบบ Real-time Stream ด้วย Go Channels" เพื่อให้ข้อความค่อยๆ ไหลออกมาเหมือน ChatGPT ของจริง... เตรียมตัวรับมือกับ Concurrency ใน Go ให้ดี แล้วเจอกันครับ!

ฝากกดติดตามพวกเราได้ที่ Superdev Academy ในทุกช่องทางนะครับ!

  • 🔵 Facebook: Superdev Academy Thailand (อัปเดตข่าวสารและบทความใหม่)

  • 🎬 YouTube: Superdev Academy Channel (ติวเข้มแบบวิดีโอ)

  • 📸 Instagram: @superdevacademy (เกร็ดความรู้สั้นๆ และเบื้องหลังการทำงาน)

  • 🎬 TikTok: @superdevacademy (Tips & Tricks ฉบับย่อยง่าย)

  • 🌐 Website: superdevacademy.com (คลังบทความและคอร์สเรียนฉบับเต็ม)