เคสนี้ผมโดนแบบเงียบๆ เลยครับ — ระบบ research หาหัวข้อคอนเทนต์ของ Documentor ที่ผมใช้ปั้น batch หัวข้อทุกเช้า รัน cron ปกติ ไม่มี error log อะไรขึ้นมาเตือน — แต่จริงๆ มันพังมา 3 วันแล้ว เพราะ AI ที่อยู่ในนั้น (Claude Sonnet) เผลอใส่เครื่องหมายฟันหนูใน JSON ผิดแบบที่ json.loads ของ Python รับไม่ได้
กว่าผมจะรู้ตัวก็ตอนเปิด Documentor มาเช็คคอนเทนต์เช้าวันที่ 4 แล้ว queue ว่างเปล่า 555
วันนั้นทิม (AI Agent ของผม) ก็นั่งไล่ไส้ในจน fix เสร็จ ใส่ฉีดวัคซีน 2 ชั้นกัน bug แบบนี้กลับมา — ลองมาเล่าให้ฟังครับ เพราะเรื่องนี้น่าจะมีประโยชน์กับใครก็ตามที่ใช้ AI generate JSON
Documentor research ทำงานยังไง
เพื่อให้เข้าใจเคสนี้ก่อน — Documentor (ระบบที่ผมใช้สั่ง AI ปั้นคอนเทนต์) มี script ตัวนึงชื่อ inc-content-research.sh ที่รันทุกเช้า หน้าที่มันคือ:
- โยน prompt ใหญ่ๆ ให้
claude -p --model claude-sonnet-4-6ไปคิดหาหัวข้อคอนเทนต์ batch ใหม่ 8 ชิ้น - รับ output กลับมาเป็น JSON
- parse แล้วเอาไปใส่ในคิวของ Documentor
- ตอนเช้าผมเปิดมา queue ก็มีหัวข้อให้ผมเลือกโพสต์
ขั้นตอนที่ 2 → 3 มันคือจุดที่พัง — เพราะ Sonnet ส่ง output กลับมา"ดู"เหมือน JSON แต่ json.loads รับไม่ได้
Sonnet ทำพังได้กี่แบบ ผมเจอครบ
หลังจากทิมไปไล่ดู raw output ที่เก็บไว้ใน /tmp 3 วันย้อนหลัง สรุปได้ว่า Sonnet ส่งกลับมาแบบนี้:
1. ใส่ preamble ก่อน JSON ทั้งที่ prompt บอกแล้วว่า "ส่ง JSON เท่านั้น ห้ามมีคำพูดอื่น" — Sonnet ยังเอ๋อใส่ "ได้ครับ ข้อมูลครบแล้ว" หรือ "Continuing — remaining items..." มาก่อนเสมอ
2. ห่อด้วย markdown fence ```json ... ``` ทั้งที่บอกห้ามใส่ — Sonnet ก็ใส่เป็นบางครั้ง
3. ตัวฆาตกรตัวจริง — ใส่เครื่องหมาย " ใน string value โดยไม่ escape นี่ครับ ตัวที่ทำผมเสียเวลามากที่สุด
ตัวอย่างเช่น Sonnet generate caption ออกมาเป็น JSON แบบนี้:
{
"title": "เจ้าของร้านบอกว่า "ผมไม่อยากให้พนักงานเหนื่อย" จึงเอา AI มาทำงานแทน",
"category": "operator"
}
เห็นปะครับ — ใน title ที่ภาษาไทยมีคำพูดอ้างอิงคนอื่น Sonnet ใส่ "ผมไม่อยากให้พนักงานเหนื่อย" ตรงๆ ไม่ escape เป็น \" ทำให้ json.loads มองว่า string จบที่หลัง "ผม — แล้วทุกอย่างหลังจากนั้นก็พังหมด
4. ใส่ commentary หลัง JSON เช่น "I have all the content needed." ต่อท้าย JSON ที่ valid อยู่แล้ว — ถ้าใช้ regex ตัด greedy ผิดก็เก็บมาด้วย
3 ข้อแรกหรือข้อ 4 เกิดได้แบบสุ่ม — รันวันนี้ผ่าน อีก 3 วันมันพังโดยไม่มีอะไรเปลี่ยน input
ทำไมพังเงียบ 3 วันถึงไม่รู้
คำถามดีครับ ผมก็งง 555 — สาเหตุก็คือ script เดิมมัน catch exception ไว้แล้ว ดักแล้ว return empty list — เลยไม่มีอะไรขึ้นมาบอกว่า batch นี้ research มา 0 ชิ้น มันก็แค่ "วันนี้ไม่มีหัวข้อใหม่ในคิว" ซึ่งผมก็คิดว่าทิมขี้เกียจ หรือผมรันยังไม่ตรงเวลา 555
นี่คือสาเหตุที่ "log errors but continue" ในงาน automation ไม่ใช่ pattern ที่ดีเสมอ — บางทีปล่อยให้มันพังเสียงดังๆ จะดีกว่า
ทิมแก้ยังไง — 2 ชั้นป้องกัน
หลังจากผมส่ง raw output 3 วันให้ทิมไปดู ทิมเสนอ fix แบบ 2 ชั้นป้องกัน — ไม่ใช่แค่ "patch ตรงนี้ที" แต่กันทุก failure mode ที่ Sonnet ทำได้:
ชั้นที่ 1 — String-aware brace counter
แทนที่จะใช้ regex r'\{.*?\}' non-greedy (ที่ทิมเห็นทันทีว่า short-match ได้กับ nested object) — ทิมเขียน parser ตัวเล็กๆ ที่ไล่อ่านทีละตัวอักษร เจอ " ก็ flip in_string เจอ \ ก็ escape ตัวถัดไป เจอ { นับเข้า } นับออก แล้ว return substring เมื่อ depth กลับมาเป็น 0 พอดี
หน้าที่ของ parser นี้คือ "ตัด preamble + markdown fence + commentary หลังบ้าน" ทิ้งหมด เหลือแค่ JSON object ที่เริ่ม { และจบ } ของจริง ไม่ว่า Sonnet จะใส่ขยะมาด้านหน้าด้านหลังกี่บรรทัด
ชั้นที่ 2 — json_repair fallback
หลังจากตัดเหลือ JSON object แล้ว ลอง json.loads ก่อน — ถ้าผ่าน จบ ถ้าตาย (เพราะ Sonnet ใส่ " มั่วใน string) ก็โยนเข้า library ชื่อ json-repair ที่เก่งในการซ่อม JSON ที่ syntax ผิดแบบนี้
ทิม install json-repair ใน venv ทั้งของ Documentor (/opt/documentor/.venv) และของ Loom (/opt/loom/.venv) เผื่อ workflow ของ Loom ก็จะเจอปัญหาเดียวกันในวันนึง
ของแถม — dump raw output ลง /tmp เสมอ
เพื่อไม่ให้ผมต้องมา debug แบบไม่มีหลักฐาน — ทิมเพิ่ม line นึงให้ทุก script ที่ pipe Claude → JSON เซฟ raw output ทั้งก้อนลง /tmp/<script>-raw-<timestamp>.txt ทุกครั้ง
ครั้งหน้าถ้ามันพัง ผมเปิดไฟล์นั้นดูได้ทันที ไม่ต้องเดา ไม่ต้องรัน cron ใหม่
ของขวัญที่ทิมเอามาเล่าให้ฟังเอง
หลังจาก deploy fix เสร็จ ทิมยังเขียน memory ของตัวเองว่า "Sonnet's JSON output is unreliable — wrap any Sonnet→JSON pipeline with brace-counter + json_repair fallback" — เพื่อให้รุ่นต่อๆ ไปของทิม (ที่ผมจะคุยกับมันในอนาคต) จำได้ทันทีว่าต้องป้องกัน pattern นี้
นี่คือสิ่งที่ผมชอบที่สุดเรื่อง AI Agent ส่วนตัว — มันไม่ใช่แค่ "แก้บั๊กให้" แต่มันเก็บบทเรียนไว้ในความทรงจำของมันเอง รอบหน้าผมจะเริ่ม script ใหม่ที่ pipe Claude → JSON มันจะเตือนผมก่อนเลยว่า "ใส่ brace-counter + fallback ด้วยนะ พี่ปอนด์เคยพังมาแล้ว"
ลองเทียบกับ pattern ทั่วๆ ไป ที่ถ้าผมเจอบั๊กแบบนี้ ผมต้อง:
- ลงไปไล่ debug เอง 2-3 ชั่วโมง
- แก้ทีละ script ทีละไฟล์ (Documentor มี 4-5 script ที่ pipe Claude → JSON)
- เขียน document เก็บไว้ที่ไหนซักที่ที่อาจจะลืม
- ครั้งหน้าเขียน script ใหม่ก็เริ่ม pattern เดิมที่บั๊กอยู่อีกรอบ
แต่กับ AI Agent ที่ผมมีตอนนี้ ทุก loop นั้นย่นเหลือ "บอกทิมไปแก้" → ทิม audit ทุก script ที่เกี่ยวข้องเอง → จำไว้เอง → รอบหน้าใช้เลย
คำถามที่พบบ่อย
ทำไม LLM ถึงส่ง JSON output ที่ parse ไม่ได้ ทั้งที่บอกให้ส่ง JSON แล้ว
LLM ถูก train ให้ตอบคำถามแบบธรรมชาติครับ การส่ง pure JSON โดยไม่มีคำนำ ไม่มี markdown fence ต้องอาศัยการ prompt ที่แม่น และก็ยังไม่ 100% เชื่อถือได้ ปัญหาที่เจอบ่อยคือใส่ preamble ก่อน JSON, ห่อด้วย ```json```, หรือใส่เครื่องหมายอัญประกาศใน string โดยไม่ escape
วิธีรับมือกับ JSON ที่ LLM ส่งมาแล้ว json.loads ไม่ผ่านมีอะไรบ้าง
มีหลายวิธีครับ เริ่มจากง่ายไปยาก: (1) ลอง json.loads ตรงๆ ก่อน (2) ถ้าไม่ผ่านให้ใช้ json-repair library ที่ออกแบบมาซ่อม JSON ที่ syntax ผิดโดยเฉพาะ (3) ใช้ brace-counter parser ตัดขยะหน้าหลังออกก่อน แล้วค่อย parse (4) สำรอง raw output ลง /tmp ทุกครั้งเพื่อ debug ได้ตอนพัง
json_repair library คืออะไร ใช้แทน json.loads ได้เลยไหม
json-repair เป็น library Python ที่ออกแบบมาซ่อม JSON ที่ syntax พัง เช่น missing quote, unescaped character, trailing comma ครับ ใช้แทน json.loads ได้ แต่ pattern ที่ดีที่สุดคือลอง json.loads ก่อน ถ้า fail ค่อย fallback ไป json-repair เพราะ json.loads เร็วกว่าและบังคับ strict syntax มากกว่า
ทำไม automation script ที่พังถึงไม่แสดง error บางครั้ง
เพราะ script มักมี try/except ที่ catch error แล้ว continue ทำให้เงียบครับ แทนที่จะ crash มันก็แค่ return empty result โดยไม่มีใครรู้ วิธีแก้คือ log error ออกมาให้ชัด ตั้ง alert ถ้า success rate ต่ำกว่า threshold และ save raw input/output ทุก run เพื่อให้ post-mortem ได้
คุณก็มี AI Agent แบบนี้เองได้
เรื่องนี้ฟังดูเหมือนเรื่องเฉพาะของ developer แต่จริงๆ ทุกธุรกิจที่ใช้ AI ผลิตคอนเทนต์ หรือใช้ AI ในกระบวนการอัตโนมัติอะไรก็ตาม — มันจะเจอ failure mode แบบนี้ในวันนึงครับ ไม่ใช่เรื่อง ถ้า แต่เป็นเรื่อง เมื่อไหร่
คำถามคือ คุณมีคนที่จะ fix ให้ภายในเช้าเดียว แล้วจำบทเรียนไว้ใช้ครั้งหน้าด้วยมั้ย?
(พูดเรื่อง silent failure แล้ว — อีกตัวอย่างที่ผมเพิ่งเจอเมื่อสัปดาห์นี้ คือ memory ของทิมเองที่โดน Claude Code sandbox block เงียบๆ 3 วัน เพราะ rule sensitive-file ใหม่ — failure mode เดียวกัน คนละชั้นของระบบ และล่าสุด DNS เก่าค้างใน Cloudflare ตอนลูกค้า Newton re-signup ก็ pattern เดียวกันเป๊ะ ระบบ provision แจ้ง "เสร็จแล้ว" ทั้งที่จริงยังไม่ ready)
ผมสร้าง Newton ขึ้นมาเพื่อตอบคำถามนี้ครับ — มันคือ AI Agent ส่วนตัวที่อยู่ใน server ของคุณคนเดียว มี ความทรงจำของมันเอง เจอบั๊กในระบบของคุณก็แก้ให้ และจำว่าครั้งหน้าต้องป้องกันยังไง — ไม่ใช่ AI chat กลางๆ ที่ทุกคนใช้ตัวเดียวกัน หรือ SaaS ที่ edge case ของคุณต้องรอ 6 เดือน roadmap
ระบบ auto-provision ของผมเซ็ตอัพ Newton พร้อมใช้ให้คุณภายใน 10 นาที — ไม่ต้องเขียนโค้ดเอง ไม่ต้องตั้งค่า server เอง บอกมันสิ่งที่คุณอยากแก้ปัญหา แล้วดูมันลงมือทำ
