- แชร์วิธีการที่เป็นรูปธรรมในการพัฒนาซอฟต์แวร์ด้วย LLM โดยใช้เวิร์กโฟลว์มัลติเอเจนต์แบบ สถาปนิก-นักพัฒนา-ผู้รีวิว เพื่อดูแลโปรเจ็กต์ระดับหลายหมื่นบรรทัดให้มีอัตราบั๊กต่ำ
- เดิมทีสนใจ การสร้างอะไรบางอย่าง มากกว่าการเขียนโปรแกรมเอง และเมื่อ LLM เขียนโค้ดได้ดีขึ้น ก็ทำให้โฟกัสกับการสร้างได้เต็มที่
- ทักษะวิศวกรรมอย่าง การออกแบบสถาปัตยกรรมระบบและการตัดสินใจเลือกสิ่งที่ถูกต้อง สำคัญกว่าความสามารถในการเขียนโค้ดมาก
- ใช้หลายโมเดลผสมกันเพื่อยกระดับคุณภาพการรีวิวโค้ด และแยกใช้ จุดแข็งกับจุดอ่อนของแต่ละโมเดลตามบทบาท
- เปิดเผยเซสชันการเพิ่มฟีเจอร์อีเมลจริงทั้งหมด พร้อมบันทึกอย่างละเอียดถึง กระบวนการร่วมมือระหว่างมนุษย์ที่เป็นผู้นำกับ LLM ตั้งแต่การตัดสินใจด้านสถาปัตยกรรมไปจนถึง QA
ข้อดีของการสร้างด้วย LLM
- เคยคิดว่าตัวเองชอบการเขียนโปรแกรม แต่จริงๆ แล้วชอบ การสร้างสิ่งต่างๆ มากกว่า และเมื่อ LLM เขียนโปรแกรมได้เก่งขึ้น ก็สามารถสร้างของได้อย่างต่อเนื่องไม่หยุด
- ช่วงที่ Codex 5.2 ออกใหม่ๆ และล่าสุดมาถึง Opus 4.6 ก็สามารถเขียนซอฟต์แวร์ได้ด้วย อัตราข้อบกพร่องที่ต่ำมาก ซึ่งอาจต่ำกว่าการเขียนโค้ดด้วยมือเองอย่างมีนัยสำคัญ
- ก่อนหน้านี้พอทำงานไป 2~3 วัน โค้ดมักจะเข้าสู่สภาพที่ดูแลต่อไม่ได้ แต่ตอนนี้สามารถทำงานต่อเนื่องเป็นสัปดาห์และขยาย โค้ดที่ใช้งานได้จริงหลายหมื่นบรรทัด อย่างมั่นคง
- ไม่ใช่ว่าทักษะวิศวกรรมหมดความสำคัญ แต่ ย้ายตำแหน่งไป: จากความสามารถในการเขียนโค้ดให้ถูกต้อง ไปสู่การตัดสินใจที่ทำให้ระบบมีสถาปัตยกรรมที่ถูกต้องและใช้งานได้จริง
- ในโปรเจ็กต์ที่รู้เทคโนโลยีพื้นฐานดีอยู่แล้ว (เช่น แบ็กเอนด์) แม้มี SLoC หลายหมื่นบรรทัดก็ยังไม่มีปัญหา แต่กับเทคโนโลยีที่ยังไม่คุ้นเคย (เช่น แอปมือถือ) โค้ดก็ยังพังเละได้จาก การสะสมของการตัดสินใจที่ผิด
- ในยุคแรกของ LLM (หลัง davinci) จำเป็นต้องตรวจ ทุกบรรทัดของโค้ด ต่อมาจึงลดลงเหลือระดับ ฟังก์ชัน และปัจจุบันมีแนวโน้มว่าแค่ตรวจในระดับ สถาปัตยกรรมทั้งหมด ก็พอ ปีหน้าอาจไม่จำเป็นแม้กระทั่งสิ่งนี้
โปรเจ็กต์ที่สร้างด้วยวิธีนี้
- Stavrobot: ผู้ช่วยส่วนตัว LLM ที่เน้นความปลอดภัย ทำงานอย่างการจัดการปฏิทิน ตรวจสอบเวลาว่าง ค้นคว้า ขยายความสามารถตัวเองด้วยการเขียนโค้ด แจ้งเตือน และจัดการงานบ้านแบบอัตโนมัติ คุณค่าหลักไม่ใช่ฟีเจอร์เดียวที่โดดเด่น แต่คือการ ขจัดความรำคาญเล็กๆ น้อยๆ หลายพันอย่าง
- Middle: อุปกรณ์จี้ขนาดเล็ก สำหรับอัด voice memo แล้วแปลงเป็นข้อความส่งผ่าน webhook จุดสำคัญคือพกติดตัวได้ตลอดและใช้งานได้แทบไม่มีแรงเสียดทาน
- Sleight of Hand: โปรเจ็กต์ศิลปะนาฬิกาแขวนผนังที่เดินติ๊กไม่สม่ำเสมอในระดับวินาที แต่ยังแม่นยำเสมอในระดับนาที มีหลายโหมด เช่น ติ๊กแบบแปรผัน 500ms~1500ms หยุดแบบสุ่มหลังจากเร่งหรือชะลอความเร็วที่สังเกตได้ยาก หรือเร่งเป็นสองเท่าแล้วรอ 30 วินาที
- Pine Town: แคนวาสมัลติเพลเยอร์แบบไร้ขีดจำกัด ที่แต่ละคนได้ที่ดินเล็กๆ ของตัวเองไว้สำหรับวาดภาพ เป็น โปรเจ็กต์ทุ่งหญ้าเชิงโต้ตอบ
- โปรเจ็กต์ทั้งหมดนี้สร้างด้วย LLM และแม้ส่วนใหญ่จะไม่เคยอ่านโค้ดด้วยตัวเอง แต่ก็ เข้าใจสถาปัตยกรรมและการทำงานภายในของแต่ละโปรเจ็กต์เป็นอย่างดี
เครื่องมือ Harness
- ตอนนี้ใช้ OpenCode เป็น harness และก็เคยมีประสบการณ์ที่ดีกับ Pi เช่นกัน
- ข้อกำหนดจำเป็นของ harness มีสองข้อ:
- ต้องใช้โมเดลหลากหลายจากหลายบริษัทได้: harness แบบ first-party ส่วนใหญ่ (Claude Code, Codex CLI, Gemini CLI) รองรับเฉพาะโมเดลของตัวเอง จึงไม่ตรงเงื่อนไขนี้
- ต้องให้ custom agent เรียกหากันเองได้อย่างอิสระ
ทำไมต้องใช้หลายโมเดล
- มองโมเดลแต่ละตัวเหมือนเป็นคนคนหนึ่งได้ และแม้จะรีเซ็ต context ก็ยังมีแนวโน้ม รักษาความเห็น จุดแข็ง และจุดอ่อนเดิมไว้
- หากให้โมเดลรีวิวโค้ดที่ตัวเองเขียน มักมีแนวโน้มเห็นด้วยกับตัวเองจนแทบไม่มีประโยชน์ แต่ถ้าให้ อีกโมเดลหนึ่งมารีวิว คุณภาพจะดีขึ้นมาก
- ณ ตอนนี้ Codex 5.4 ละเอียดและเข้มงวด จึงเหมาะกับการรีวิว, Opus 4.6 มักสอดคล้องกับการตัดสินใจที่ผู้เขียนเองจะเลือก, และ Gemini 3 Flash บางครั้งก็เสนอวิธีแก้ที่โมเดลอื่นพลาดไป
- เพื่อให้ได้ผลลัพธ์ดีที่สุด จำเป็นต้อง ผสมใช้ทุกโมเดล
เวิร์กโฟลว์: สถาปนิก → นักพัฒนา → ผู้รีวิว
- เวิร์กโฟลว์ประกอบด้วย สถาปนิก นักพัฒนา และผู้รีวิว 1~3 คน โดยทั้งหมดถูกตั้งค่าเป็น OpenCode agent (ไฟล์สกิล)
- เหตุผล 3 ข้อที่ใช้หลายเอเจนต์:
- ใช้โมเดลราคาแพง (Opus) กับ การวางแผน และใช้โมเดลราคาถูกกว่า (Sonnet) กับ การเขียนโค้ด เพื่อประหยัดโทเค็น
- การ รีวิวด้วยโมเดลที่ต่างกัน ช่วยจับปัญหาคนละแบบ
- สามารถ แยกสิทธิ์ตามบทบาท ได้ (เช่น อ่านอย่างเดียว vs เขียนได้)
- การใช้สองเอเจนต์ที่เป็นโมเดลเดียวกันและมีสิทธิ์เท่ากัน ก็เหมือนคนคนเดียวสลับใส่หมวกคนละใบ จึงไม่มีความหมายมากนัก
- ไฟล์สกิลนั้น เขียนด้วยมือเองทั้งหมด ถ้าให้ LLM เขียนสกิลให้ ก็เหมือนให้ใครสักคนเขียนว่า “จะเป็นวิศวกรที่ยอดเยี่ยมได้อย่างไร” แล้วส่งคู่มือนั้นกลับมาให้พร้อมบอกว่า “เอาล่ะ ตอนนี้จงเก่งซะ”
บทบาทสถาปนิก
- สถาปนิก (ปัจจุบันคือ Claude Opus 4.6) เป็น เอเจนต์เพียงตัวเดียวที่คุยโดยตรง และควรเป็นโมเดลที่ทรงพลังที่สุด
- จะเริ่มจากการเสนอเป้าหมายของฟีเจอร์หรือบั๊กที่เฉพาะเจาะจงมาก แล้วคุยกันได้นานถึง 30 นาที จนกว่าจะตกลงเป้าหมาย ข้อจำกัด และ trade-off ได้ชัดเจน
- ผลลัพธ์ที่ได้คือ แผนระดับค่อนข้างต่ำ ลงไปถึงระดับไฟล์และฟังก์ชันแต่ละตัว
- นี่ไม่ใช่แค่การ prompt ตรงๆ แต่เป็นกระบวนการ ร่วมกันก่อรูปแผน ด้วยความช่วยเหลือของ LLM เมื่อ LLM ผิดหรือไม่ตรงกับวิธีที่ต้องการ ก็จะมีการแก้ไขอย่างมาก และนี่คือส่วนสำคัญที่ทำให้โปรเจ็กต์ “เป็นของตัวเอง”
- ตั้งค่าไว้ว่า ห้ามเริ่มลงมือ implement จนกว่าจะพูดคำว่า "approved" อย่างชัดเจน เพราะบางโมเดลมีแนวโน้มรีบเริ่มทำทันทีเมื่อคิดว่าตัวเองเข้าใจแล้ว
- หลังอนุมัติแล้ว สถาปนิกจะแบ่งงานเป็น task และ บันทึกอย่างละเอียดไว้ในไฟล์แผน ก่อนเรียกนักพัฒนา
บทบาทนักพัฒนา
- นักพัฒนาสามารถใช้โมเดลที่อ่อนกว่าแต่ คุ้มค่าโทเค็นกว่า ได้ (Sonnet 4.6)
- เพราะแผนถูกกำหนดมาให้เหลือดุลยพินิจน้อยที่สุด บทบาทนี้จึงเป็นการ ลงมือ implement การเปลี่ยนแปลงตามแผนอย่างเคร่งครัด
- เมื่อ implement เสร็จแล้วก็จะเรียกผู้รีวิว
บทบาทผู้รีวิว
- ผู้รีวิวแต่ละคนจะตรวจแผนและ diff พร้อมวิจารณ์ อย่างอิสระจากกัน
- อย่างน้อยจะใช้ Codex เสมอ บางครั้งเพิ่ม Gemini และในโปรเจ็กต์สำคัญก็เพิ่ม Opus ด้วย
- ฟีดแบ็กจะส่งกลับไปยังนักพัฒนา และถ้าเกิด ความเห็นไม่ตรงกันระหว่างผู้รีวิว ก็จะเอสคาเลตกลับไปหาสถาปนิก
- Opus เก่งในการเลือกฟีดแบ็กที่ถูกต้อง และบางครั้งก็ เพิกเฉยต่อฟีดแบ็กที่จุกจิกเกินไป ซึ่งมีโอกาสก่อปัญหาจริงต่ำเมื่อเทียบกับภาระในการแก้
แนวทางโดยรวมและรูปแบบความล้มเหลว
- วิธีนี้ทำให้สามารถ เข้าใจการตัดสินใจทั้งหมดตั้งแต่ระดับสูงกว่าฟังก์ชันขึ้นไป และนำความรู้นั้นไปใช้กับงานต่อเนื่องได้
- เมื่อ LLM มี จุดบอด ที่มองข้ามองค์ประกอบบางอย่างใน codebase การชี้ว่า “ต้องใช้ Y” จะทำให้ LLM รับรู้การมีอยู่ของ Y และเปลี่ยนไปใช้แนวทางที่ดีกว่า
- ถ้าไม่คุ้นเคยกับเทคโนโลยีนั้นมากพอ ก็จะจับการตัดสินใจผิดของ LLM ไม่ได้ และ การตัดสินใจผิดจะซ้อนทับกัน จนสุดท้ายไปถึงจุดที่แก้ไม่ออก
- รูปแบบความล้มเหลวที่พบบ่อยคือเมื่อบอกซ้ำๆ ว่า “โค้ดใช้งานไม่ได้” LLM จะตอบว่า “เข้าใจแล้ว! จะแก้ให้” แล้ว กลับทำให้แย่ลงกว่าเดิม
- ดังนั้นแม้จะยังไม่คุ้นกับเทคโนโลยีนั้น ก็ยังควรพยายาม ทำความเข้าใจให้มากที่สุดตั้งแต่ขั้นวางแผน
เซสชันจริง: เพิ่มการรองรับอีเมลให้ Stavrobot
- มีการเผยแพร่ข้อความเต็มของเซสชันจริงพร้อมคำอธิบายประกอบ โดยตัดการเรียกใช้เครื่องมือและส่วนที่เยิ่นเย้อออก แต่ คงบทสนทนาและกระบวนการตัดสินใจไว้ตามเดิม
- บทสนทนาเริ่มต้น: เสนอเป้าหมายระดับสูง (“อยากเพิ่มการรองรับอีเมลให้บอตนี้”) → LLM อ่านโค้ดและเข้าใจแพตเทิร์นปัจจุบัน (inbound webhook → enqueueMessage → ประมวลผลด้วย LLM → ตอบกลับ) จากนั้นจึงตั้งคำถามด้านการออกแบบ
- วิธี inbound (IMAP polling, webhook, SMTP server), วิธี outbound, จะรองรับสองทางหรือไม่, สถาปัตยกรรม (container แยก vs in-process), การจัดการอีเมล HTML, การติดตามเธรด, ไฟล์แนบ ฯลฯ
- การตัดสินใจด้านดีไซน์: ใช้ Cloudflare Email Worker webhook สำหรับ inbound, ใช้ SMTP client สำหรับ outbound, รองรับบทสนทนาแบบสองทางเต็มรูปแบบ, ประมวลผลแบบ in-process, แปลงเป็น Markdown, มองอีเมลเป็นรายการอิสระ, รองรับไฟล์แนบ
- ข้อเสนอการออกแบบรายละเอียดจาก LLM: เสนอข้อกังวล 7 ข้อและแนวทางที่เจาะจง เช่น MIME parsing (ใช้
mailparser), การยืนยันตัวตนของ webhook (shared secret), ความจำเป็นของ subject สำหรับ outbound, การจัดการอีเมลที่มีแต่ HTML, อัตลักษณ์ของที่อยู่ From, การจัดการอีเมลที่ถูก forward, ไฟล์แนบของ outbound
- เสนอให้ทำ payload ของ Worker ให้เรียบง่ายเป็น
{ from, to, raw } แล้วค่อย parse ฝั่งเซิร์ฟเวอร์
- สรุปโครงสร้าง Config, flow ของ inbound/outbound, รายการไฟล์ที่ต้องแก้ และรายการ explicit non-goal (YAGNI)
- การขัดเกลาแผน: มนุษย์ชี้ว่ามีจุดตกหล่นอย่างการอัปเดต README.md และ
config.example.toml รวมถึงการลบการตรวจสอบ E.164 ออกจากหน้ารายการ allowlist อีเมล → LLM จึงรวมสิ่งเหล่านี้เข้าแผน
- แบ่งเป็น 6 งานย่อย: Config/Dependency, allowlist, การตรวจสอบ allowlist ฝั่ง UI/แบ็กเอนด์, inbound email, outbound email, README/test
- การปรับปรุงเพิ่มเติม: เสนอไอเดียให้ฟิลด์ SMTP เป็น optional เพื่อให้ inbound email ใช้งานได้แม้ไม่มีการตั้งค่า SMTP → แล้วก็ implement ตามนั้น
- บั๊กที่พบใน QA: อีเมลของเจ้าของไม่ได้ถูกลงทะเบียน จึงทำให้ข้อความถูกทิ้ง → สาเหตุคือ
seedOwnerInterlocutor ไม่ได้รวมช่องทางอีเมล → จึงแก้ไข
- ข้อเสนอเพื่อปรับปรุงโค้ด: รีแฟกเตอร์จาก if block แบบฮาร์ดโค้ดแยกตามช่องทาง ไปเป็นการ วนลูปรายการช่องทางที่ใช้ร่วมกัน หลังถกกันเรื่องกรณีพิเศษการแปลงค่าตัวเลขของ Telegram จึงตัดสินใจใช้ลูปเฉพาะใน
seedOwnerInterlocutor และคง getOwnerIdentities ไว้ดังเดิมเพราะความต่างของประเภทข้อมูลเป็นประเด็นสำคัญ
- allowlist อีเมลแบบ wildcard: เพิ่มการรองรับ wildcard ระดับโดเมน ในรูปแบบ
*@example.com เพราะจำเป็นกับ use case จริงที่ใช้อีเมลแบบใช้ครั้งเดียว
- ประเด็นด้านความปลอดภัย: เพื่อป้องกันการโจมตีแบบ
"me@mydomain.com"@evildomain.com จึงแปลง * เป็น [^@]* เพื่อ ไม่ให้ข้ามขอบเขตของ @
- รองรับ partial wildcard อย่าง
myusername+*@gmail.com ด้วย
- มนุษย์ชี้ว่าหากใช้ regular expression ต้อง escape อักขระอื่นทั้งหมด ของที่อยู่อีเมลด้วย
- ยืนยันว่า wildcard ใช้งานได้ทั้งในฟิลด์เจ้าของและ allowlist โดยใช้ helper function
matchesEmailEntry ร่วมกัน
- ใช้เวลาทั้งหมดในการทำฟีเจอร์นี้ ประมาณ 1 ชั่วโมง
บทส่งท้าย
- ไม่ใช่เซ็ตอัปที่หวือหวามาก แต่ใช้งานได้ดีมาก และพอใจกับ ความน่าเชื่อถือ ของกระบวนการ
- รัน Stavrobot แบบ 24/7 มาเกือบหนึ่งเดือนแล้ว และ เสถียรมาก
10 ความคิดเห็น
เหมือนกับเมื่อราว 20 ปีก่อนที่กระแสเว็บเอดิเตอร์หรือบล็อกสำเร็จรูปทำให้มีโฮมเพจหรือโพสต์จำนวนมากที่ไม่มีใครอ่านเกิดขึ้นเป็นจำนวนมาก ในยุค AI ตอนนี้ก็มีลักษณะคล้ายกัน แต่การสร้างแอปแบบคัสตอมและแบ่งปันกระบวนการหรือรูทีนเหล่านั้น ผมคิดว่าเป็นทรัพย์สินที่ยอดเยี่ยมและมีคุณค่าอย่างมาก แน่นอนว่าส่วนตัวผมมองว่ายุคนี้ไม่ใช่การสร้างแอปหรือบริการที่ทำเงินด้วย AI แต่คือการสร้างเครื่องมือคัสตอมที่ตัวเองต้องการได้อย่างง่ายดายเพื่อเพิ่มผลิตภาพ
จริง ๆ แล้วมนุษย์ก็คงเป็นแบบนั้นเหมือนกัน เหตุผลที่มนุษย์ควรแสวงหาความหลากหลายก็คงเพราะแบบนี้ไม่ใช่หรือ...
เพราะเป็นโมเดลภาษา จึงเหมาะที่ให้โมเดลราคาแพงเป็นคนวางแผน
> โมเดลราคาแพง (Opus) ใช้สำหรับการวางแผน ส่วนโมเดลราคาถูก (Sonnet) ใช้สำหรับเขียนโค้ด เพื่อประหยัดโทเค็น
หลายคนก็มักใช้ Sonnet สำหรับวางแผน แล้วใช้ Opus สำหรับลงมือเขียนโค้ดจริง แต่ที่นี่กลับกันนะ
ผมก็ให้
opusกับcodexโต้ตอบกันไปมาสำหรับการวางแผนอยู่เหมือนกันให้
opusเขียนโค้ด แล้วก็ให้opusอีกตัวกับcodexรีวิวโค้ดอีกทีที่รู้สึกระหว่างทำก็คือ ไม่ว่าจะ AI หรือคนก็ตาม ดูเหมือนจะเก่งเรื่องคอยจับผิดคนอื่นกันจริง ๆ..
https://code.claude.com/docs/ko/model-config#opusplan-model-settings
ผมก็ตั้งค่าโมเดลของ Claude เป็น opusplan แล้วใช้อยู่เหมือนกัน
ในการตั้งค่าพื้นฐานของ Oh my opencode นั้น opus จะเป็นคนวางแผน ส่วนการลงมือพัฒนา จะให้โมเดลที่เบากว่าเป็นผู้ทำ
รอบตัวผมเห็นเขาวางแผน/ออกแบบด้วย Sonnet แล้วก็เขียนโค้ดด้วย GLM-5 กันน่ะครับ..
ผมเองก็วางแผนให้ Sonnet ทำหน้าที่วางแผน และให้ Opus ลงมือเขียนโค้ดอยู่เหมือนกัน แต่วิธีของผู้เขียนอาจจะมีประสิทธิภาพมากกว่าก็ได้
ผมก็วางแผนด้วย Opus ให้ Codex high ช่วยรีวิว แล้วให้ sonet หรือ codex medium ทำโค้ดจริงครับ