เมื่อโมเดลฉลาดเกินไปก็เป็นปัญหา


ทุกครั้งที่ไล่ตามไอเดียใหม่ๆ ในการพัฒนา มัวแต่จดจ่อกับการ implement จนไม่มีเวลามาแชร์เลย 😅.
ช่วงนี้เรื่องปวดหัว (แบบสนุกๆ) ที่เกี่ยวกับ AI ของผมคือ ดีเลมมาเรื่องโมเดลสุดฉลาดนี่แหละ

Cleaning Roster กับสามวันที่อยู่กับ Opus

ผมทำระบบจัดการที่อิงจาก cleaning roster ขึ้นมา ซึ่งเงื่อนไขมันซับซ้อนมาก โจทย์จริงๆ เลยอยู่ที่ว่าจะ implement ยังไงให้ง่ายที่สุด
เลยเรียกโมเดลที่ดีที่สุดที่ผมเข้าถึงได้ อย่าง Opus 5 มาอธิบายแนวคิดให้ฟัง แล้วมันก็อธิบายทุกประเด็นที่ต้องแก้ทีละอย่างอย่างคล่องแคล่วและมั่นใจสุดๆ แบบนี้จะไม่ให้เชื่อได้ยังไง
ผมใช้เวลาสามวันเต็มๆ แค่ทำความเข้าใจคำอธิบายเชิงวิชาการนี้ 😓 พูดให้ชัดคือ ระหว่างสามวันนั้นผมลงมือทำจริง ชนปัญหาไปเรื่อยๆ จนเข้าใจสถาปัตยกรรมที่มันเสนอ และ implement เสร็จ
แต่พอเข้าใจแล้วก็รู้สึกไม่ชอบเลย ที่ต้อง accommodate สถาปัตยกรรมมากมายขนาดนี้ในระบบของผม แค่เพื่อทำฟีเจอร์ยิง request ไปที่ AI API ตัวเดียว
เลยใช้เวลาอีกสามวันเพื่อ strip ทุกอย่างที่ไม่จำเป็นออก สุดท้ายก็เผา token ไปเยอะมากตอนสร้างโค้ด แล้วก็เผาอีกเยอะตอนลบมันทิ้ง (ที่สำคัญที่สุดคือเวลา ที่มีค่ายิ่งกว่าทองคำ!!)

ถึงอย่างนั้น โมเดลที่ผมเลือกใช้ก็ยังเป็น Opus อยู่ดี มี budget แล้วจะใช้ Sonnet ไปทำไม?

แต่ว่า…

ตลอดหนึ่งอาทิตย์ ผมล้มเหลวในการควบคุมม้าป่าตัวนี้ที่ชื่อ Opus ทุกคำสั่งที่ส่งไป ผมได้แต่ขอร้องให้มันอธิบายง่ายๆ หน่อย
ไม่สิ บางทีก็บอกตรงๆ ไปเลยว่าอย่ามาโชว์ออฟทำเป็นอธิบายแบบรู้ไปหมดทุกเรื่อง แต่ยังไงก็ตาม มันก็ซับซ้อนอยู่ดี มันก็ยังขุดลึกลงไปในปัญหาอยู่ดี

นี่คือสิ่งที่ผมต้องการจริงๆ เหรอ?

ปัญหาซับซ้อนมีอยู่จริงในโลกนี้ แต่มันต้องการสถาปัตยกรรมที่ซับซ้อนด้วยหรือเปล่า เป็นอีกเรื่องหนึ่ง
ผมเป็นคนที่ชอบความเรียบง่าย ถ้าผมเขียนโค้ดยุ่งๆ ไว้ ทีหลังก็ยังกลับมาแก้ให้ง่ายขึ้นได้ แต่สถาปัตยกรรมไม่เหมือนกัน ถ้าดันโครงสร้างที่ซับซ้อนเข้าไปตั้งแต่ระดับสถาปัตยกรรมก่อน ทุกอย่างที่ implement ต่อจากนั้นก็จะซับซ้อนตามไปด้วย

พูดให้ชัดคือ ฟีเจอร์นี้คือการยิง API call ไปหา AI จาก .NET backend แล้วให้ user คอนเฟิร์มผลลัพธ์ แต่ต้อง accommodate กลไกทางเทคนิคสารพัดเข้าไปด้วย
เช่น ถ้า server fail จะ recover ยังไง จะเก็บ state ใน DB ยังไง เสร็จแล้วจะ trigger event อัตโนมัติยังไง.. ฯลฯ พูดง่ายๆ คือพยายามสร้างฟีเจอร์ที่กดปุ่มเดียวแล้วทุกอย่างจัดการให้หมดแบบมหัศจรรย์เกินไป
(อีกแง่หนึ่งคือ พยายามจะทำให้ทุกอย่างอัตโนมัติจนสุดท้ายกลายเป็นระบบที่ user เองก็ไม่รู้ว่าตอนนี้เกิดอะไรขึ้น)

แต่ผมเป็นสาย worse is better ชอบงานที่ internal implementation โปร่งใสมากกว่า เพราะต้องส่งสัญญาณที่ชัดเจนให้ user รู้ว่าตอนนี้เกิดอะไรขึ้น
เลยตัดสินใจว่าจะจัดการ async ผ่าน hook ฝั่ง frontend แทน (ถึงจะรีเฟรชแล้วหายก็ตาม) แต่แบบนี้ backend ยังคงเรียบง่ายแบบเดิม ใช้ REST API แบบ sync ตามปกติ แถมยังอธิบายให้ user เข้าใจได้ชัดเจนว่ามันทำงานยังไง
แล้วก็ตั้งใจว่าจะ implement การจัดการแบบ async นี้ที่ frontend ให้ได้มากที่สุดเท่าที่จะทำได้ เพื่อให้สร้างได้เร็วขึ้น

ข้อเสนอของ Opus (3 วัน)อัตโนมัติทั้งหมด: เรียก AI → ยืนยันตัวรับ WebhookEvent Busตัวจัดการ RetryState Machine (DB)เรียก AI APIทริกเกอร์เมื่อเสร็จOrchestratorมุมมองผู้ใช้: ไม่รู้ว่าเกิดอะไรขึ้นใช้เวลา 3 วัน strip ออกเผา token ทั้งเวลาก็เสียFrontend Hook + REST แบบ Syncผู้ใช้รู้สถานะตลอดเวลาFrontend HookREST API (Sync)คำตอบจาก AI→ ขึ้นจอทันทีรีเฟรชแล้วหาย แต่แบบนี้ดีกว่า

ฝั่งซ้ายคือข้อเสนอของ Opus ที่จัดการทุกอย่างให้อัตโนมัติแบบผู้ใช้ไม่รู้ตัว ส่วนฝั่งขวาคือสิ่งที่เลือกใช้จริง — การประมวลผลแบบ sync ที่โปร่งใสผ่าน frontend hook — worse is better

ดังนั้น ไม่ว่าจะรีวิวโค้ดตอนไหน ผมอยากให้ตัวเองวินิจฉัยจุดที่มีปัญหาได้เร็ว และเข้าใจมันได้เร็วเมื่อจำเป็น
พูดง่ายๆ คือใช้ Sonnet ในการออกแบบสถาปัตยกรรม แต่ debug ยังใช้ Opus อยู่ (ม้าป่าตัวนี้พอมาเจอ edge case มันมีพลังในการสืบสวนที่บ้าคลั่งมาก น่ากลัวจริงๆ 😬)

พูดง่ายๆ คือ อย่างน้อยในเรื่อง system design ผมเริ่มจงใจเลือกใช้โมเดลที่โง่ลงมานิดหน่อย และถ้าทฤษฎีนี้ฟังดูน่าสนใจ ผมแนะนำคลิปด้านล่างนี้เลย ที่อธิบายว่าการตัดฟีเจอร์ “ฉลาด” ทิ้งไปโดยตรงยังไง ถึงสร้างระบบเครื่องบินเจ็ตที่ไม่มีความล้มเหลวได้ :)



แล้วสรุปทำอะไรไปบ้าง?

มาแนะนำฟีเจอร์ที่ทำมาช่วงนี้กันครับ :)

1. ฟีเจอร์ Cleaning Roster

blog placeholder แบ่งเป็นสองเลเยอร์ คือคนที่มีคิวเวรทำความสะอาดของอาทิตย์นั้น กับคนที่ทำความสะอาดจริงๆ จะถูกแสดงไว้ทุกอาทิตย์ ทุกบ้าน ถึงเทนแนนต์จะสลับคิวกันเอง ตารางก็ยังแสดงผลถูกต้องแบบธรรมชาติ :)

blog placeholder นี่คือหน้าจอที่ฝั่งเทนแนนต์เห็น มีทั้งคิวเวรของอาทิตย์นั้น คู่มือทำความสะอาด และอัปโหลดรูปอยู่ในหน้าเดียวกันหมด

2. ฟีเจอร์อ่าน Cleaning Roster อัตโนมัติ

ตอนนี้ cleaning roster เป็นแบบรูปภาพ ต้นฉบับหน้าตาเป็นตารางแบบนี้

blog placeholderblog placeholder

แค่กดปุ่มนี้ปุ่มเดียว มันก็จะอ่านค่าใส่ในเซลล์ให้อัตโนมัติเลย เป็นกลยุทธ์แบบ image parsing -> เก็บข้อมูลแบบมีโครงสร้าง ซึ่ง LLM ทำได้ดีมากๆ blog placeholder

3. ใครยังไม่ได้ทำความสะอาด?

blog placeholder ฝั่งแอดมินก็จะเห็นคนที่ยังไม่ได้ทำความสะอาดถูกแสดงรวมเป็นรายอาทิตย์ในที่เดียวเลย :) ดึงเป็นอีเมลออกมาได้ด้วย

4. Inline Notes (Control + .)

blog placeholder แล้วก็ทำระบบโน้ตภายในขึ้นมา ให้แอดมินกด Control + . แล้วเช็กคอมเมนต์ที่เกี่ยวกับข้อมูลในพื้นที่ทำงานตอนนั้นได้เลย เป็นการ implement ที่ flexible มาก สามารถดึงโน้ตที่เกี่ยวข้องกันมาได้เร็วผ่านการใช้ node (ในฐานะนักพัฒนา ผมสนุกกับการทำให้ระบบเก็บความเชื่อมโยงแบบไดนามิกนี้ได้มาก) แต่จากมุมมองของ user การต้องจัดการโน้ตเองดูจะเป็นภาระเพิ่มขึ้นมา เลยพักการพัฒนาฟีเจอร์เพิ่มเติมไว้ก่อน
คิดว่าทีหลังอาจจะลองทำให้ AI มารับบทบาท assistant ในส่วนนี้ แต่ตอนนี้ขอข้ามไปก่อน คงเพราะมันไม่ใช่ฟีเจอร์ที่ทำงานให้เองแบบมหัศจรรย์

แล้วก็จะทยอยอัปฟีเจอร์เล็กๆ น้อยๆ ที่ทำเสร็จไปเรื่อยๆ ครับ :)