นักพัฒนาสามารถควบคุมค่าใช้จ่ายในการแก้ไข JSON ที่รูปแบบผิดพลาด (malformed JSON) จากโมเดลภาษาขนาดใหญ่ (LLMs) ได้แล้ว โดยการจำกัดจำนวนครั้งในการพยายามแก้ไขไว้ที่สองครั้ง ดังที่แสดงในลูป Python ง่าย ๆ รูปแบบนี้—สร้าง (generate), แยกส่วน (parse), ตรวจสอบ (validate), ลองใหม่ (retry), แล้วจึงยอมรับหรือล้มเหลว—จะเปลี่ยน "JSON ที่อาจจะถูกต้อง" ให้กลายเป็นอัตราความสำเร็จที่วัดผลได้ และช่วยป้องกันการใช้โทเคน (token) ที่พุ่งสูงเกินควบคุม
ทำไมลูปการแก้ไขจึงสำคัญ
LLMs มักจะได้รับคำสั่งให้ "ส่งคืน JSON" แต่ผลลัพธ์ที่ได้มักจะมีข้อผิดพลาดทางไวยากรณ์ (syntax errors), คีย์ที่หายไป หรือค่าที่ขัดกับกฎทางธุรกิจ ระบบปลายน้ำ (downstream system) ที่คาดหวังข้อมูล (payload) ที่มีรูปแบบเข้มงวดจะเกิดการล่มหรือให้ผลลัพธ์ที่ผิดพลาดหากได้รับข้อมูลดังกล่าว หากไม่มีวิธีการที่เป็นระบบในการตรวจจับและแก้ไขปัญหาเหล่านี้ ทีมงานอาจต้องยอมรับอัตราความล้มเหลวที่สูง หรือต้องเสียเงินไปกับการส่ง prompt ให้โมเดลซ้ำแล้วซ้ำเล่าจนกว่าจะได้ผลลัพธ์ที่ถูกต้อง
การตรวจสอบสามชั้น
ลูปที่เชื่อถือได้จะแยกการตรวจสอบออกเป็น:
- Syntax – สตริงนั้นสามารถแยกส่วน (parse) เป็น JSON ได้หรือไม่?
- Shape – ออบเจกต์ระดับบนสุดมีคีย์ตรงตามที่คาดหวังไว้ทุกประการหรือไม่?
- Semantic – ค่าต่าง ๆ เป็นไปตามข้อจำกัดของโดเมน (domain constraints) หรือไม่ (ตัวอย่างเช่น "category" ต้องอยู่ในชุดข้อมูลที่กำหนดไว้ล่วงหน้า)?
การหยุดที่ชั้นที่ล้มเหลวเป็นชั้นแรกจะช่วยให้ลูปสามารถส่งข้อความแจ้งข้อผิดพลาดที่แม่นยำไปยังโมเดล ซึ่งจะช่วยเพิ่มโอกาสในการแก้ไขให้ถูกต้องในการลองครั้งถัดไปได้อย่างมหาศาล
ตัวอย่าง Python แบบมินิมอล
โค้ดต่อไปนี้ใช้เพียงไลบรารีมาตรฐาน (standard library) เท่านั้น โดยมีการกำหนดสกีมา (schema) ขนาดเล็ก (ดิกชันนารีที่มีคีย์ category และ summary) และรายการหมวดหมู่ที่อนุญาตแบบคงที่
import json
CATEGORIES = {"bug", "feature", "question"}
def validate(raw: str):
try:
value = json.loads(raw)
except json.JSONDecodeError as exc:
return None, f"syntax: {exc.msg}"
if not isinstance(value, dict):
return None, "shape: root must be an object"
if set(value) != {"category", "summary"}:
return None, "shape: expected category and summary"
if value["category"] not in CATEGORIES:
return None, "semantic: invalid category"
if not isinstance(value["summary"], str) or not value["summary"].strip():
return None, "semantic: summary must be text"
return value, None
ตัวลูปการแก้ไขเองจะกำหนดเพดานสูงสุดในการพยายามแก้ไขไว้:
MAX_ATTEMPTS = 2
raw = model_response # initial LLM output
for attempt in range(MAX_ATTEMPTS + 1):
value, error = validate(raw)
if error is None:
break
if attempt == MAX_ATTEMPTS:
raise RuntimeError(f"failed after {MAX_ATTEMPTS} retries: {error}")
# Send the error and schema back to the model for a fix
raw = ask_model_to_repair(raw, error)
หาก JSON ผ่านการตรวจสอบในการผ่านครั้งแรก ลูปจะจบการทำงานทันที มิฉะนั้นจะมีการลองใหม่ โดยการส่งข้อมูล (payload) ที่กระชับไปยังโมเดล ได้แก่: ผลลัพธ์เดิม, ข้อความข้อผิดพลาดที่ชัดเจน และสกีมาที่คาดหวัง หลังจากพยายามล้มเหลวครบสองครั้ง กระบวนการจะหยุดทำงานพร้อมแจ้งข้อผิดพลาด (exception) ที่ชัดเจน
กฎสองข้อสำหรับการแก้ไขที่คุ้มค่า
รักษา Prompt สำหรับการแก้ไขให้กระชับ – ใส่เฉพาะ JSON ดิบ, ข้อความข้อผิดพลาด และสกีมาเท่านั้น การเพิ่มประวัติการแชท (chat history) ที่ยาวเกินไปจะทำให้การใช้โทเคนเพิ่มขึ้นและอาจทำให้โมเดลสับสน ซึ่งจะทำให้ค่าใช้จ่ายต่อการลองใหม่สูงขึ้นโดยไม่ช่วยเพิ่มความแม่นยำ
แยกการตรวจสอบออกจากตรวจสอบข้อเท็จจริง – ตัวแยกส่วน (parser) สามารถตรวจจับปัญหาด้านโครงสร้างได้ แต่ไม่สามารถตรวจสอบได้ว่าข้อความ "summary" นั้นเป็นความจริงหรือไม่ การตรวจสอบข้อเท็จจริงควรอยู่ในขั้นตอนถัดไป การพยายาม "แก้ไข" ข้อกล่าวอ้างที่เป็นเท็จมีแต่จะทำให้คำโกหกนั้นดูเรียบร้อยขึ้นเท่านั้น
การวัดความสำเร็จ
การบันทึก (logging) ประเภทข้อผิดพลาดในการพยายามแต่ละครั้ง (syntax, shape, semantic) และการดูว่าลูปประสบความสำเร็จหรือไม่ จะให้ตัวชี้วัดที่เป็นรูปธรรม นั่นคือ สัดส่วนของคำตอบจาก LLM ที่สามารถนำไปใช้งานได้ภายในขอบเขตการลองใหม่ที่กำหนด ทีมงานสามารถติดตามสิ่งนี้ได้เมื่อเวลาผ่านไป เปรียบเทียบรูปแบบของ prompt หรือทดลองใช้การกำหนดสกีมาที่แตกต่างกัน
ข้อเสียที่อาจเกิดขึ้น
การจำกัดขอบเขตของลูปหมายความว่าผลลัพธ์ที่ผิดพลาดบางส่วนจะถูกทิ้งไปหลังจากถึงขีดจำกัด ซึ่งอาจเพิ่มอัตราความล้มเหลวโดยรวมหากคุณภาพพื้นฐานของโมเดลต่ำ ในสภาพแวดล้อมที่ทุกคำตอบมีความสำคัญยิ่ง นักพัฒนาอาจเลือกที่จะตั้งเพดานการลองใหม่ให้สูงขึ้นโดยยอมแลกกับโทเคนส่วนเกิน การแลกเปลี่ยน (trade-off) นี้ชัดเจนมาก: คือการจ่ายมากขึ้นเพื่อให้ครอบคลุมมากขึ้น หรือการใช้งบประมาณที่จำกัดภายใต้เพดานที่คาดการณ์ได้
สิ่งที่ควรจับตามองต่อไป
- Prompt engineering เพื่อการแก้ไข – การปรับปรุงรูปแบบข้อความแจ้งข้อผิดพลาดสามารถลดจำนวนครั้งที่ต้องลองใหม่ได้
- ตัวแยกส่วน (parsers) ทางเลือกอื่น – ไลบรารีบางตัวมีการแยกส่วนแบบยืดหยุ่น (tolerant parsing) ที่สามารถแก้ไขปัญหาเล็กน้อยได้โดยอัตโนมัติ การเปรียบเทียบต้นทุนของไลบรารีเหล่านี้กับลูปที่มีการจำกัดขอบเขตจึงเป็นสิ่งที่คุ้มค่าที่จะทำ
- การอัปเดตโมเดล – LLM เวอร์ชันใหม่ ๆ อาจสร้าง JSON ที่สะอาดขึ้นได้ทันที ซึ่งอาจช่วยให้สามารถลดขีดจำกัดการลองใหม่ลงได้อีก
ลูปการแก้ไข JSON แบบจำกัดขอบเขตช่วยให้นักพัฒนามีเครื่องมือที่ใช้งานได้จริงในการเปลี่ยนผลลัพธ์จาก LLM ที่ไม่แน่นอนให้กลายเป็นข้อมูลที่เชื่อถือได้โดยไม่ทำให้ค่าใช้จ่ายพุ่งสูงขึ้น การปฏิบัติกับการตรวจสอบ (validation) ให้เป็นขั้นตอนสำคัญและจำกัดจำนวนครั้งในการลองใหม่ จะช่วยให้ทีมงานสามารถควบคุมได้ทั้งงบประมาณและระบบปลายน้ำ
