
Structured Output คือเทคนิคที่บังคับให้ผลลัพธ์ของ LLM มีโครงสร้างตาม Schema ที่เข้มงวด เช่น JSON Schema
หากนำการตอบกลับของ LLM ที่เป็นข้อความอิสระไปใช้ในระบบธุรกิจโดยตรง มักจะเกิดปัญหาการ Parse ล้มเหลวหรือข้อมูลในฟิลด์ขาดหายอยู่บ่อยครั้ง การใช้ Structured Output จะช่วยให้คุณได้รับผลลัพธ์ในรูปแบบที่ระบบธุรกิจสามารถอ่านได้โดยตรงอย่างเสถียร เพียงแค่ระบุ JSON Schema ไว้ใน response_format ของ OpenAI API เท่านั้น
บทความนี้จัดทำขึ้นสำหรับวิศวกรและสถาปนิกที่ต้องการเชื่อมต่อ LLM เข้ากับระบบขององค์กรโดยตรง โดยจะอธิบายขั้นตอนที่จำเป็นสำหรับการใช้งานจริงอย่างละเอียด ตั้งแต่แนวทางการออกแบบ JSON Schema, การเขียนโปรแกรมด้วย TypeScript, การทำ Validation, การจัดการ Error ไปจนถึงรูปแบบการเชื่อมต่อกับ ERP และคิวแบบอะซิงโครนัส (Asynchronous Queue)
คุณเคยประสบปัญหาแบบนี้ไหม? เมื่อสั่ง LLM ว่า "ส่งข้อมูลคำสั่งซื้อกลับมาในรูปแบบ JSON" บางวันได้รับ {"item": "coffee"} แต่บางวันกลับได้รับข้อความธรรมดาว่า "รายการสั่งซื้อคือกาแฟ"
หากนำ output ที่เป็น free text ส่งเข้าสู่ระบบธุรกิจโดยตรง จะเกิดความล้มเหลวในการ parse และการสูญหายของข้อมูลโดยไม่คาดคิด แม้จะระบุรูปแบบไว้ใน prompt แล้ว แต่เนื่องจาก LLM ทำงานแบบ probabilistic จึงไม่สามารถรับประกันความสม่ำเสมอของ output ได้ ผลที่ตามมาคือระบบฝั่งผู้รับต้องสะสม pre-processing และ exception handling ที่ซับซ้อนมากขึ้นเรื่อยๆ ทำให้ต้นทุนการเชื่อมต่อระบบพองตัวขึ้น
Structured Output คือกลไกที่แก้ปัญหานี้ได้อย่างถึงรากถึงโคน ด้วยการบังคับให้ output ของโมเดลเป็นไปตาม schema อย่างเคร่งครัด ฝั่ง application จึงไม่ต้องตั้งสมมติฐานว่า "รูปแบบอาจผิดพลาด" อีกต่อไป เมื่อความเสถียรของรูปแบบ output ได้รับการรับประกันแล้วเท่านั้น การออกแบบที่นำ LLM ไปฝังโดยตรงในระบบธุรกิจจึงจะเป็นไปได้จริงในทางปฏิบัติ
ในช่วงเริ่มต้นของการนำ LLM มาประยุกต์ใช้ในระบบงาน มักจะมีความคิดที่ว่า "เพียงแค่สั่งใน Prompt ว่า 'ให้ตอบกลับเป็น JSON' ก็เพียงพอแล้ว" แต่ในความเป็นจริง การส่งออกข้อมูลเป็นข้อความอิสระ (free text) จะกลายเป็นปัจจัยที่สร้างความไม่เสถียรอย่างร้ายแรงในการเชื่อมต่อกับระบบงานครับ
ในการส่งออกข้อความอิสระ (free-text) แบบเดิม การประมวลผลการแยกวิเคราะห์ (parsing) มักจะเป็นการเดิมพันที่ไม่แน่นอนว่า "ถ้าใช้ Regular Expression จับได้ก็สำเร็จ ถ้าจับไม่ได้ก็ล้มเหลว" แต่เมื่อใช้ Structured Output โมเดลจะสร้างผลลัพธ์ตามข้อจำกัดของประเภทข้อมูลใน JSON Schema ทำให้การประมวลผลการแยกวิเคราะห์ฝั่งแอปพลิเคชันมีความเสถียรขึ้นอย่างมาก
เหตุผลที่ความเสถียรเพิ่มขึ้นคือ รูปแบบของผลลัพธ์ได้รับการรับประกันไว้ล่วงหน้า เนื่องจากประเภทข้อมูลอย่าง string / number / boolean ถูกบังคับในระดับ Schema จึงทำให้เกิดข้อผิดพลาดในการแปลงประเภทข้อมูล (type casting error) ได้ยากขึ้น และคีย์ที่ระบุไว้ในฟิลด์ required จะต้องปรากฏอยู่ในผลลัพธ์เสมอ นอกจากนี้ ความลึกของอาร์เรย์และออบเจกต์ยังถูกกำหนดให้คงที่ตาม Schema จึงไม่เกิดการเปลี่ยนแปลงโครงสร้างที่ไม่คาดคิด
ในแง่ของการแบ่งเงื่อนไข (conditional branching) หากผลลัพธ์อยู่ใน Schema เดียวที่ตายตัว การตั้งค่า strict: true จะช่วยให้การประมวลผลการแยกวิเคราะห์เรียบง่ายและมีประสิทธิภาพ ในทางกลับกัน หากคาดการณ์ว่าจะมีรูปแบบผลลัพธ์หลายแบบ การใช้ oneOf หรือ anyOf เพื่อกำหนดเงื่อนไขแยกสาขาที่ฝั่ง Schema และตรวจสอบฟิลด์ type ในโค้ดฝั่งรับข้อมูลจะเป็นการออกแบบที่ปลอดภัยกว่า
เมื่อการประมวลผลการแยกวิเคราะห์มีความเสถียร ผลกระทบต่อตรรกะทางธุรกิจ (business logic) ในขั้นตอนถัดไปก็จะเปลี่ยนไปในทางที่ดีขึ้นด้วย
เมื่อคุณพยายามเชื่อมต่อ ERP หรือระบบธุรกิจเข้ากับ LLM คุณเคยรู้สึกไหมว่า "ทำไมต้องเขียนโค้ดจัดการการ Parse ข้อมูลมากมายขนาดนี้ เพียงเพื่อรับผลลัพธ์จาก LLM?"
Structured Output ช่วยลดต้นทุนในการเชื่อมต่อนี้ได้อย่างเป็นระบบ โดยมีจุดสำคัญของกลไก 3 ประการดังนี้:
JSON.parse เพียงครั้งเดียวตัวอย่างเช่น หากต้องการดึงข้อมูลคำสั่งซื้อด้วย LLM ในอดีตคุณจำเป็นต้องมีตรรกะเฉพาะตัวเพื่อสกัด "รหัสสินค้า", "จำนวน" และ "กำหนดส่ง" ออกจากข้อความ แต่หากใช้ Structured Output เพียงแค่กำหนดฟิลด์เหล่านี้ไว้ใน Schema ตัว LLM ก็จะส่งคืนข้อมูลในรูปแบบ JSON ที่จัดระเบียบเรียบร้อยมาให้ ฝั่ง ERP จึงสามารถนำ JSON ที่ได้รับไป POST เข้าสู่ API Endpoint ได้ทันที ซึ่งช่วยลดต้นทุนการพัฒนาเลเยอร์แปลงข้อมูลขั้นกลางลงได้อย่างมาก
บทสรุป: ความสำเร็จหรือความล้มเหลวของการนำไปใช้งาน ขึ้นอยู่กับ 3 ปัจจัยหลัก ได้แก่ การเลือกโมเดล (Model Selection), การออกแบบสกีมา (Schema Design) และสภาพแวดล้อมในการพัฒนา (Development Environment) การเตรียมตัวที่ไม่เพียงพอจะนำไปสู่การแก้ไขงานในขั้นตอนถัดไป
ก่อนเริ่มการใช้งาน Structured Output จำเป็นต้องมีการเตรียมความพร้อม 3 ประการ ได้แก่ การตรวจสอบโมเดลที่รองรับ, แนวทางการออกแบบ JSON Schema และการจัดเตรียมไลบรารีที่จำเป็น โดยจะอธิบายรายละเอียดทีละขั้นตอนดังนี้
ในตอนแรกเราอาจคิดว่า "ถ้าเป็นโมเดลรุ่นล่าสุดก็จะใช้ Structured Outputs ได้ทั้งหมด" แต่ในความเป็นจริง สถานะการรองรับจะแตกต่างกันไปตามการจับคู่ระหว่างโมเดลและเวอร์ชัน ดังนั้นการตรวจสอบล่วงหน้าจึงเป็นสิ่งที่ขาดไม่ได้
OpenAI ระบุไว้อย่างชัดเจนว่า Structured Outputs (รูปแบบ json_schema) สามารถใช้งานได้ใน GPT-4o, GPT-4 Turbo และโมเดลตั้งแต่ gpt-3.5-turbo-1106 เป็นต้นไป สำหรับรูปแบบ json_object แบบเดิมนั้นถือว่าไม่แนะนำให้ใช้แล้ว และแนวทางปฏิบัติพื้นฐานในการพัฒนาใหม่คือการเลือกใช้รูปแบบ json_schema
คุณภาพของการออกแบบ Schema มีผลอย่างมากต่ออัตราความแม่นยำในการปฏิบัติตามคำสั่งของโมเดล ยิ่ง Schema มีความเรียบง่ายและชัดเจนเท่าใด LLM ก็จะมีแนวโน้มที่จะทำตามคำสั่งได้อย่างถูกต้องแม่นยำมากขึ้นเท่านั้น
หลักการพื้นฐานในการออกแบบ
string แล้ว ควรใช้ enum หรือ format (เช่น "format": "date") เพื่อระบุขอบเขตของค่าที่ยอมรับได้ให้ชัดเจนdescription: การใส่ description ในแต่ละฟิลด์จะช่วยให้โมเดลตีความเจตนาได้อย่างถูกต้องมากขึ้นการตัดสินใจเลือกประเภทข้อมูลตามกรณีการใช้งาน
การจัดการฟิลด์ที่จำเป็น (Required) และฟิลด์ทางเลือก (Optional) จำเป็นต้องปรับใช้ตามวัตถุประสงค์ โดยมีหลักการพื้นฐานคือ ฟิลด์ที่ใช้สำหรับการเขียนข้อมูลลงในระบบงานควรระบุไว้ใน required ส่วนข้อมูลเสริมหรือฟิลด์สำหรับการขยายในอนาคตควรนิยามเป็น Optional โดยใช้ nullable ในทางกลับกัน หากต้องการให้ LLM เป็นผู้ตัดสินใจว่าควรมีค่าหรือไม่ ให้กำหนดเป็น null ได้ โดยตั้งสมมติฐานว่าจะมีการประมวลผลภายหลังในชั้นการตรวจสอบข้อมูล (Validation layer)
"ควรใช้ไลบรารีตัวไหนเพื่อให้ทำงานได้เร็วที่สุด" — การหาคำตอบสำหรับคำถามนี้ก่อนเริ่มลงมือเขียนโค้ด จะช่วยป้องกันการต้องย้อนกลับมาแก้ไขงานในภายหลัง
สำหรับการใช้ TypeScript โครงสร้างขั้นต่ำที่จำเป็นมีดังนี้:
response_format โดยตรงzodResponseFormat ที่มาพร้อมกับ openai SDK: สำหรับแปลง Zod Schema ให้เป็น JSON Schema โดยอัตโนมัติตัวอย่างคำสั่งติดตั้ง:
1npm install openai zod zod-to-json-schemaแนะนำให้ใช้ Node.js เวอร์ชัน LTS (18 ขึ้นไป) เนื่องจากมีการรองรับ fetch ในตัว ซึ่งช่วยให้การจัดการ Dependency ของ SDK มีความเสถียร
สำหรับการจัดการ Environment Variables ควรใช้ dotenv ควบคู่ไปด้วย และเป็นหลักการพื้นฐานที่จะไม่เขียน OPENAI_API_KEY ลงในโค้ดโดยตรง สำหรับ CI/CD Pipeline แนะนำให้ใช้บริการจัดการความลับ (Secret Management) เช่น GitHub Actions Secrets หรือ Azure Key Vault แทน
บทสรุป: สามารถทำได้โดยผ่าน 3 ขั้นตอน ได้แก่ การออกแบบ Schema, การตั้งค่า API Parameter และการทำ Validation
การใช้งาน Structured Output จะดำเนินการตามลำดับขั้นตอน คือ การกำหนด Schema, การนำไปรวมเข้ากับ API และการตรวจสอบ Response หากทำตามขั้นตอนเหล่านี้อย่างถูกต้อง คุณจะสามารถสร้าง Pipeline ที่มีความเสถียร ซึ่งสามารถส่งผลลัพธ์จาก LLM ไปยังระบบงานได้โดยตรง
ก้าวแรกของการออกแบบ Schema ที่ผู้ใช้งานจำนวนมากมักจะพลาดคือความคิดที่ว่า "ค่อยมาขยายเพิ่มทีหลังก็ได้" ในความเป็นจริงแล้ว หากไม่กำหนดความเข้มงวดของประเภทข้อมูล (Type) และโครงสร้างฟิลด์ให้ชัดเจนตั้งแต่ขั้นตอนการออกแบบเริ่มต้น จะทำให้การเชื่อมต่อกับระบบปลายทางเกิดความเสียหายได้ง่าย
หลักการพื้นฐานในการกำหนด Schema
ขั้นแรกให้ระบุฟิลด์ที่จำเป็นสำหรับการแสดงผล แล้วกำหนดประเภทข้อมูลที่เหมาะสมให้กับแต่ละฟิลด์
"type": "string" สำหรับข้อความ และ "type": "number" หรือ "integer" สำหรับตัวเลข"enum" สำหรับค่าที่มีตัวเลือกจำกัด เพื่อป้องกันไม่ให้โมเดลส่งค่าข้อความอื่นที่ไม่ได้กำหนดไว้required และแยกฟิลด์ที่เป็นตัวเลือก (Optional) ออกมาอย่างชัดเจนตัวอย่างเช่น หากต้องการดึงข้อมูลการสั่งซื้อ การเริ่มต้นด้วยโครงสร้างขั้นต่ำอย่าง order_id (string), amount (number) และ status (enum: "pending" / "confirmed" / "cancelled") จะช่วยเพิ่มความแม่นยำในการปฏิบัติตามคำสั่งของโมเดลได้
การทำงานร่วมกับการออกแบบ Prompt
นอกเหนือจากการกำหนด Schema แล้ว การระบุเจตนาของโครงสร้างลงใน System Prompt ก็เป็นสิ่งสำคัญ การสั่งเพียงว่า "ให้ตอบกลับเป็น JSON เท่านั้น" นั้นไม่เพียงพอ แต่ควรเพิ่มข้อความที่ระบุความคาดหวังที่ชัดเจน เช่น "โปรดส่งออกข้อมูลตามฟิลด์และประเภทข้อมูลที่กำหนดไว้ดังต่อไปนี้" ซึ่งจะช่วยให้โมเดลปฏิบัติตาม Schema ได้ดียิ่งขึ้น
เมื่อกำหนด Schema เสร็จเรียบร้อยแล้ว ขั้นตอนต่อไปคือการตั้งค่าพารามิเตอร์เมื่อเรียกใช้งาน API สำหรับ OpenAI API คุณสามารถเปิดใช้งาน Structured Outputs ได้โดยการส่ง {"type": "json_schema", "json_schema": {...}} ไปที่ response_format เนื่องจากรูปแบบ "json_object" แบบเดิมนั้นถูกเลิกใช้งานแล้ว (deprecated) จึงขอแนะนำให้เลือกใช้รูปแบบ "json_schema" สำหรับการพัฒนาใหม่
การตั้งค่าพื้นฐานใน TypeScript มีดังนี้:
1const response = await openai.chat.completions.create({
2 model: "gpt-4o",
3 response_format: {
4 type: "json_schema",
5 json_schema: {
6 name: "invoice_data",
7 strict: true,
8 schema: invoiceSchema, // Schema ที่กำหนดไว้ในขั้นตอนก่อนหน้า
9 },
10 },
11 messages: [{ role: "user", content: prompt }],
12});โดยปกติแล้วค่าเริ่มต้นของแฟล็ก strict จะเป็น false แต่สำหรับการเชื่อมต่อกับระบบงาน (Business System) ขอแนะนำให้ตั้งค่าเป็น true
เมื่อ API ส่งค่า JSON กลับมา ปัญหาที่มักพบได้บ่อยในหน้างานคือ "ทั้งที่ควรจะเป็นไปตาม Schema แล้ว แต่กลับเกิดข้อผิดพลาดในการ Parse" แม้ว่าจะเปิดใช้งาน Structured Outputs แล้วก็ตาม แต่ในบางกรณีอาจเกิด JSON ที่ไม่สมบูรณ์ได้เนื่องจากปัญหาเครือข่ายหลุดหรือบริบทของโมเดลเกินขีดจำกัด ดังนั้นชั้นการตรวจสอบ (Validation layer) จึงเป็นสิ่งที่ขาดไม่ได้
การประมวลผล Response ที่รัดกุมควรแบ่งออกเป็น 3 ขั้นตอนดังนี้:
JSON.parse() ครอบด้วย try-catch เพื่อดักจับข้อผิดพลาดทางไวยากรณ์เป็นอันดับแรกตัวอย่างการใช้งานด้วย TypeScript + Zod:
1import { z } from "zod";
2
3const OrderSchema = z.object({
4 orderId: z.บทสรุป: หลังจากได้รับผลลัพธ์ที่มีโครงสร้าง (Structured Output) แล้ว คุณสามารถเชื่อมต่อเข้ากับระบบโดยตรงได้ผ่าน 3 ช่องทาง ได้แก่ การป้อนข้อมูลเข้า ERP โดยอัตโนมัติ, คิวแบบอะซิงโครนัส (Asynchronous Queue) และการสร้างโค้ดที่มีความปลอดภัยทางประเภทข้อมูล (Type-safe Code Generation)
เมื่อได้รับ JSON ที่เป็นไปตามสคีมาแล้ว โจทย์ถัดไปคือการนำข้อมูลเหล่านั้นเข้าสู่ระบบธุรกิจจริง เราจะอธิบายรูปแบบการใช้งาน (Implementation Patterns) ของแต่ละส่วน ได้แก่ การเชื่อมต่อผ่าน REST API, การประมวลผลแบบอะซิงโครนัส และการแชร์ประเภทข้อมูล (Type) ร่วมกับส่วนหน้า (Frontend) ตามลำดับ
การส่งข้อมูลเข้า ERP โดยอัตโนมัติมักทำให้เราคิดว่า "ลองยิง POST ไปก่อนก็น่าจะได้" แต่ในความเป็นจริง การตรวจสอบสคีมา (Schema Validation) ด้วย Structured Output ก่อนส่งข้อมูล จะช่วยลดภาระงาน Rollback ในระบบปลายทาง (Downstream) ให้เหลือเกือบศูนย์
หากต้องการส่ง JSON ที่ LLM ตอบกลับไปยัง REST API endpoint ของ ERP โดยตรง สิ่งสำคัญคือต้องทำให้ขั้นตอนต่อไปนี้เป็นมาตรฐาน:
Idempotency-Key) ไว้ใน Request Headerตัวอย่างการใช้งานด้วย TypeScript แบบย่อมีดังนี้:
เมื่อต้องส่งเอกสารจำนวนมากไปยัง LLM ในคราวเดียว การเรียกใช้ Synchronous API แบบขนานโดยตรงมักจะทำให้เกิดข้อผิดพลาด Rate Limit บ่อยครั้ง ซึ่งส่งผลให้การประมวลผลโดยรวมขาดความเสถียร การใช้ Asynchronous Queue เข้ามาคั่นกลางจะช่วยให้สามารถรักษาสมดุลระหว่าง Throughput และความน่าเชื่อถือได้
หากจำนวนรายการที่ประมวลผลมีน้อย (ประมาณหลักสิบ) การประมวลผลแบบขนานด้วย Promise.allSettled ก็เพียงพอแล้ว แต่สำหรับการประมวลผลแบบ Batch ที่มีจำนวนหลายร้อยรายการขึ้นไป การกระจายงานผ่าน Message Queue เช่น BullMQ หรือ AWS SQS จะเหมาะสมกว่า
คุณเคยประสบปัญหาการใช้งาน Structured Output ในฝั่งแบ็กเอนด์แล้วเกิด Runtime error เนื่องจากข้อมูลไม่ตรงกับ Type definition ในฝั่งฟรอนต์เอนด์หรือไม่? ปัญหานี้สามารถแก้ไขได้ด้วยการรวม "Single Source of Truth ของ Type" ไว้ที่ JSON Schema เพียงที่เดียว
ขั้นตอนพื้นฐานในการสร้าง Type มีดังนี้:
json-schema-to-typescript (json2ts) เพื่อสร้าง TypeScript Type definition (.d.ts) โดยอัตโนมัติด้วยวิธีนี้ เมื่อมีการแก้ไข Schema ตัว Type definition จะถูกอัปเดตโดยอัตโนมัติ ทำให้สามารถตรวจพบความไม่สอดคล้องกันได้ตั้งแต่ขั้นตอน Compile
ประเด็นสำคัญของตัวอย่างการใช้งาน มีดังนี้:
1// นำเข้า Type ที่สร้างขึ้นโดยอัตโนมัติ
2import type { InvoiceOutput } from "@shared/types/invoice";
3
4// สามารถ Cast ข้อมูล API Response ได้โดยตรง
5const data = response.data as InvoiceOutput;เนื่องจาก Response จาก LLM ถูกดึงมาด้วยค่า strict: true จึงไม่มีฟิลด์ที่นอกเหนือจาก Schema ปรากฏอยู่ ทำให้ฝั่งฟรอนต์เอนด์ไม่ต้องเขียนโค้ด Validation เพิ่มเติมให้ยุ่งยาก
บทสรุป: ความผิดพลาดที่มักเกิดขึ้นหลังจากการนำไปใช้งานจริงจะกระจุกตัวอยู่ที่ 3 ประเด็นหลัก ได้แก่ การออกแบบ Schema, การจัดการ null และการจัดการเวอร์ชัน (Version Control)
แม้จะมีการนำ Structured Output มาใช้แล้ว แต่ปัญหาที่เกิดจาก Schema ก็ยังคงเกิดขึ้นได้ง่าย ในหัวข้อ H3 ต่อไปนี้ จะสรุปรูปแบบความผิดพลาดที่พบบ่อยและแนวทางการป้องกันครับ
หลายคนมักคิดว่าการออกแบบ Schema อย่างละเอียดจะช่วยเพิ่มความแม่นยำ แต่ในความเป็นจริง ยิ่ง Schema มีความซับซ้อนมากเท่าไร โมเดลก็ยิ่งมีแนวโน้มที่จะทำตามคำสั่งไม่ได้มากขึ้นเท่านั้น
ตัวอย่างความผิดพลาดที่พบบ่อย ได้แก่:
oneOf / anyOf เพื่อสร้างเงื่อนไขแบบผสมมากเกินไปเมื่อให้ Schema ที่ซับซ้อนเช่นนี้ โมเดลอาจส่งผลลัพธ์ที่ละเว้นฟิลด์บังคับ หรือตีความประเภทข้อมูล (Type) ผิดพลาดได้ แม้ว่า Structured Outputs ของ OpenAI จะรองรับ JSON Schema ในรูปแบบย่อย แต่ยิ่ง Schema มีขนาดใหญ่เท่าไร ก็ยิ่งใช้โทเค็นมากขึ้น และไปแย่งทรัพยากรการประมวลผลที่ควรจะใช้สำหรับ "การทำความเข้าใจ Schema" ภายในบริบท (Context)
วิธีแก้ไขคือ "การแบ่งส่วนและทำเป็นขั้นตอน" (Split and Stage):
$defs เพื่อกำหนดนิยามของ Sub-schema ที่นำกลับมาใช้ใหม่ได้ เพื่อให้โครงสร้างอ่านง่ายขึ้นนอกจากนี้ หากตั้งค่า strict: true คุณจำเป็นต้องระบุฟิลด์ทั้งหมดที่มีอยู่ใน Schema ไว้ใน required ด้วย โปรดระวังว่าการปล่อยให้ฟิลด์ที่เป็นทางเลือก (Optional) ไม่อยู่ใน required อาจเป็นสาเหตุให้เกิดข้อผิดพลาดในการตรวจสอบความถูกต้อง (Validation Error) ได้ (รายละเอียดจะอธิบายในหัวข้อถัดไป)
การจัดการ Optional field ดูเหมือนจะเป็นเรื่องง่ายในตอนแรก แต่กลับเป็นจุดที่มักเกิดข้อผิดพลาดในการใช้งานจริงได้ง่าย
ข้อผิดพลาดที่พบบ่อยคือ กรณีที่กำหนดฟิลด์ใน JSON Schema โดยไม่ใส่ไว้ในอาร์เรย์ required แต่ไม่ได้คาดคิดว่าโมเดลอาจละเว้นฟิลด์นั้นไปโดยสิ้นเชิง หากโค้ดฝั่งรับเข้าถึง response.discount_rate โดยตรง จะทำให้เกิดข้อผิดพลาด undefined หรือ null reference exception เนื่องจากตัวฟิลด์นั้นไม่มีอยู่จริง
สิ่งที่ต้องระวังคือ พฤติกรรมระหว่างกรณีที่ฟิลด์ถูกละเว้น กับกรณีที่ส่งค่ากลับมาเป็น null นั้นมีความแตกต่างกัน ในกรณีที่ถูกละเว้น คีย์นั้นจะไม่มีอยู่ในออบเจกต์ ในขณะที่กรณีส่งค่าเป็น null คีย์นั้นจะมีอยู่แต่มีค่าเป็นว่าง ดังนั้นจึงจำเป็นต้องแยกเขียนตรรกะการตรวจสอบ (Validation logic) ออกจากกัน
รูปแบบความผิดพลาดที่พบได้บ่อยคือ การกำหนด Optional field ในสคีมาเพียงอย่างเดียวโดยไม่ได้กำหนดประเภทให้รองรับค่าว่าง (null-nullable) เช่น ["string", "null"] ซึ่งจะทำให้เกิด Validation error ทันทีที่โมเดลส่งค่า null กลับมา นอกจากนี้ โค้ดฝั่งรับที่อ้างอิงโดยตรงโดยไม่ใช้ ?? หรือ ?. เพื่อจัดการค่าเริ่มต้น ก็จะทำให้เกิด Runtime error เมื่อคีย์ขาดหายไป วิธีแก้ไขนั้นง่ายมาก คือการตั้งค่า strict: true แล้วใส่ทุกฟิลด์ไว้ใน required พร้อมระบุ "type": ["string", "null"] ให้ชัดเจน วิธีนี้จะช่วยให้คุณสามารถควบคุมทั้งกรณีการละเว้นฟิลด์และการส่งค่า null ได้อย่างตั้งใจ
「เมื่อสัปดาห์ที่แล้วยังทำงานได้อยู่เลย แต่พอมาวันนี้กลับเกิด Parse Error ขึ้นมาเฉยๆ」——คำถามในลักษณะนี้มักเกิดขึ้นบ่อยครั้งในหน้างานที่ไม่มีการจัดการเวอร์ชันของ Schema อย่างเป็นระบบ
เมื่อมีการอัปเดต JSON Schema การเพิ่มฟิลด์มักจะรักษาความเข้ากันได้ย้อนหลัง (Backward Compatibility) ได้ง่าย ในขณะที่การลบฟิลด์ การเปลี่ยนประเภทข้อมูล (Type) หรือการเปลี่ยนสถานะ Required นั้นถือเป็นการเปลี่ยนแปลงที่ทำลายระบบ (Breaking Change) ซึ่งมีความเสี่ยงที่โค้ดฝั่งรับข้อมูลเดิมจะพังทันทีที่เปลี่ยน Prompt หรือคำนิยาม Schema ฝั่ง LLM
รูปแบบการเปลี่ยนแปลงที่ทำลายระบบ (Breaking Change) ที่พบบ่อย
string เป็น numberrequiredprice → unit_price)แนวทางการจัดการที่แนะนำ
version ไว้ใน Schema เพื่อให้ฝั่งรับข้อมูลสามารถตรวจสอบเวอร์ชันก่อนดำเนินการแยกส่วนการประมวลผลได้Q1. Structured Output สามารถใช้กับโมเดลใดก็ได้โดยไม่ต้องทำ Fine-tuning ใช่หรือไม่?
ไม่สามารถใช้ได้กับทุกโมเดล ในกรณีของ OpenAI ฟีเจอร์ Structured Outputs ที่ระบุ json_schema ใน response_format จะใช้งานได้เฉพาะกับโมเดลที่รองรับ เช่น GPT-4o, GPT (ตระกูล GPT-4 Turbo) และ gpt-3.5-turbo-1106 ขึ้นไป แม้ว่าจะไม่จำเป็นต้องทำ Fine-tuning แต่เนื่องจากสถานะการรองรับจะแตกต่างกันไปตามเวอร์ชันของโมเดล จึงแนะนำให้ตรวจสอบรายชื่อโมเดลที่รองรับในเอกสารอย่างเป็นทางการก่อนใช้งาน สำหรับโมเดลแบบ Open Source นั้น การรองรับฟังก์ชันที่เทียบเท่ากันจะขึ้นอยู่กับแต่ละโมเดล
Q2. ควรเลือกใช้ strict: true และ strict: false อย่างไร?
เมื่อตั้งค่า strict: true โมเดลจะสร้างผลลัพธ์ที่ปฏิบัติตาม Schema ที่กำหนดไว้อย่างเคร่งครัด สำหรับการใช้งานที่ไม่อนุญาตให้เกิดความผิดพลาดในการ Parse เช่น การป้อนข้อมูลอัตโนมัติเข้าสู่ระบบธุรกิจ การใช้ strict: true จะมีความเหมาะสมมากกว่า ในทางกลับกัน strict: false (ค่าเริ่มต้น) จะมีความยืดหยุ่นในการปฏิบัติตาม Schema มากกว่า จึงเหมาะสำหรับการสำรวจข้อมูลอย่างอิสระในช่วงการทำ Prototype หรือขั้นตอนการทดสอบ โดยหลักการแล้วในสภาพแวดล้อมการใช้งานจริง (Production) ควรเลือกใช้ strict: true และดำเนินการหลังจากตรวจสอบการออกแบบ Schema อย่างละเอียดแล้ว
Q3. แนวคิดของ Structured Output สามารถนำไปประยุกต์ใช้กับรูปแบบอื่นที่ไม่ใช่ JSON (เช่น YAML, XML) ได้หรือไม่?
เนื่องจากฟีเจอร์ Structured Outputs ของ OpenAI ถูกออกแบบมาโดยมี JSON Schema เป็นพื้นฐาน จึงไม่มีกลไกที่บังคับให้ส่งออกเป็น YAML หรือ XML โดยตรง อย่างไรก็ตาม สามารถใช้วิธีนำเสนอ Template ของ YAML หรือ XML ใน Prompt ให้กับ LLM แล้วทำการตรวจสอบด้วย Parser หลังจากได้รับผลลัพธ์ สำหรับวิธีที่แน่นอนกว่าคือการออกแบบ Pipeline โดยรับผลลัพธ์แบบโครงสร้างเป็น JSON ก่อน แล้วจึงแปลงเป็น YAML หรือ XML ในระดับ Application Layer ซึ่งจะมีความเสถียรมากกว่า
Q4. เมื่อมีการเปลี่ยนแปลง Schema จะลดผลกระทบต่อระบบที่เชื่อมต่ออยู่เดิมให้เหลือน้อยที่สุดได้อย่างไร?
การเพิ่ม Field มักจะรักษาความเข้ากันได้แบบย้อนหลัง (Backward Compatibility) ได้ง่าย แต่การลบ Field หรือการเปลี่ยนประเภทข้อมูล (Type) อาจส่งผลกระทบที่ทำให้ระบบพังได้ การระบุฟิลด์เวอร์ชัน $schema ไว้ใน Schema และเพิ่มเลขเวอร์ชันเมื่อมีการเปลี่ยนแปลงเป็นวิธีที่มีประสิทธิภาพ นอกจากนี้ แนะนำให้ใช้วิธีการย้ายระบบแบบค่อยเป็นค่อยไป (Gradual Migration) โดยการเปิดใช้งาน Schema ใหม่และเก่าควบคู่กันไปในช่วงเปลี่ยนผ่าน และยกเลิกเวอร์ชันเก่าหลังจากยืนยันแล้วว่าระบบปลายทางรองรับเวอร์ชันใหม่เรียบร้อยแล้ว เพื่อรักษาเสถียรภาพของการเชื่อมต่อระบบ
Q5. สามารถใช้วิธีการ Implement เดียวกันกับ Azure OpenAI ได้หรือไม่?
Structured Outputs ของ Azure OpenAI รองรับ JSON Schema subset เดียวกันกับ OpenAI และวิธีการระบุ response_format ก็เหมือนกันโดยพื้นฐาน แม้ว่า Endpoint และวิธีการยืนยันตัวตนจะเป็นรูปแบบเฉพาะของ Azure แต่การออกแบบ Schema และตรรกะการตรวจสอบ (Validation Logic) สามารถนำมาใช้ต่อได้เลย อย่างไรก็ตาม เนื่องจากสถานะการ Deploy โมเดลที่รองรับและช่วงเวลาการเปิดให้บริการฟีเจอร์ในแต่ละภูมิภาค (Region) อาจแตกต่างกัน จึงแนะนำให้ตรวจสอบสถานะการรองรับล่าสุดในเอกสารของ Azure
อาจจะเผลอคิดไปว่า "น่าจะใช้ได้กับทุกโมเดล" แต่ในความเป็นจริง การตรวจสอบโมเดลที่รองรับเป็นสิ่งสำคัญอันดับแรก เนื่องจาก Structured Outputs (การบังคับเอาต์พุตเป็น JSON Schema) ขึ้นอยู่กับสถาปัตยกรรมและการฝึกฝนของโมเดล จึงไม่สามารถนำไปใช้กับโมเดลใดก็ได้ตามใจชอบ
สรุปสถานะการรองรับ
response_format: { type: "json_schema" } ของ OpenAI ระบุว่าสามารถใช้งานได้กับโมเดล GPT-4o, GPT-4 Turbo และ gpt-3.5-turbo-1106 ขึ้นไป"json_object" แบบเดิมถือว่าไม่แนะนำให้ใช้แล้ว และขอแนะนำให้เปลี่ยนไปใช้วิธี "json_schema" แทนความจำเป็นในการทำ Fine-tuning
โดยหลักการแล้ว ไม่จำเป็นต้องทำ Fine-tuning เนื่องจาก Structured Outputs สามารถทำงานได้เพียงแค่การออกแบบ Prompt และการตั้งค่าพารามิเตอร์ response_format เท่านั้น อย่างไรก็ตาม ควรระมัดระวังในประเด็นต่อไปนี้
สรุปคือ OpenAI Structured Outputs เป็นฟีเจอร์ที่ออกแบบมาโดยอิงจาก JSON เป็นหลัก จึงไม่สามารถบังคับใช้ Schema สำหรับ YAML หรือ XML โดยตรงได้
อย่างไรก็ตาม แนวทางแก้ไขที่ใช้งานได้จริงจะแตกต่างกันไปตามวัตถุประสงค์:
js-yaml ในฝั่งแอปพลิเคชันเพื่อแปลงเป็น YAML เนื่องจากขั้นตอนการตรวจสอบ Schema (Schema validation) จะเสร็จสิ้นตั้งแต่ในระดับ JSON ทำให้ไม่สูญเสียความปลอดภัยของประเภทข้อมูล (Type safety)fast-xml-parser) เข้าไปในไปป์ไลน์เป็นวิธีที่ใช้งานได้จริง แต่หากมีองค์ประกอบที่แสดงผลใน JSON ได้ยาก เช่น โครงสร้าง Attribute ของ XML จำเป็นต้องคำนึงถึงโครงสร้างหลังการแปลงตั้งแต่ขั้นตอนการออกแบบ Schema เพื่อกำหนด Type ให้เหมาะสมการสั่งผ่าน Prompt ว่า "โปรดส่งออกในรูปแบบ YAML" สามารถทำได้ในทางเทคนิค แต่เนื่องจากวิธีนี้ไม่มีการบังคับด้วย JSON Schema ความเสถียรของผลลัพธ์จะลดลงอย่างมาก จึงไม่แนะนำสำหรับการเชื่อมต่อกับระบบงานจริง
ควรยึดหลักการตัดสินใจว่า หากระบบปลายทางต้องการ YAML หรือ XML ให้รับข้อมูลเป็น JSON แล้วค่อยแปลง แต่หากต้องการให้ความสำคัญกับความน่าเชื่อถือของผลลัพธ์จาก LLM เป็นอันดับแรก ให้คงการบังคับใช้ JSON Schema ไว้จะดีที่สุด
Chi
ศึกษาเอกวิทยาการสารสนเทศที่มหาวิทยาลัยแห่งชาติลาว และระหว่างศึกษาได้มีส่วนร่วมในการพัฒนาซอฟต์แวร์ทางสถิติ สั่งสมพื้นฐานด้านการวิเคราะห์ข้อมูลและการเขียนโปรแกรมอย่างเป็นรูปธรรม ตั้งแต่ปี 2021 ได้ก้าวเข้าสู่เส้นทางการพัฒนา Web และแอปพลิเคชัน และตั้งแต่ปี 2023 เริ่มสั่งสมประสบการณ์การพัฒนาอย่างจริงจังทั้งในด้าน Frontend และ Backend ในบริษัทปัจจุบันรับผิดชอบการออกแบบและพัฒนาบริการ Web ที่ใช้ AI โดยมีส่วนร่วมในโครงการที่นำการประมวลผลภาษาธรรมชาติ (NLP) การเรียนรู้ของเครื่อง (Machine Learning) และ Generative AI รวมถึงโมเดลภาษาขนาดใหญ่ (LLM) มาผสานรวมกับระบบงานจริง มีความกระตือรือร้นในการติดตามเทคโนโลยีล่าสุดอยู่เสมอ และให้ความสำคัญกับความรวดเร็วในการดำเนินงานตั้งแต่การพิสูจน์แนวคิดทางเทคนิคไปจนถึงการนำไปใช้งานจริง