Tech Blog

ผมสร้างกลไกถอน 'บทเรียนที่ผิด' ที่เก็บไว้ใน RAG ในภายหลัง — ล้างการปนเปื้อนที่บิดเบือนเพื่อน AI ทุกเทิร์นโดยอัตโนมัติ

RAG ChromaDB Operations 運用 プロンプト設計

บทนำ

ผมให้เอเจนต์ AI ที่ทำงานข้ามหลายระบบให้ผมมีความทรงจำร่วม (RAG — กลไกที่เรียกคืนการตัดสินใจ ความล้มเหลว และบทเรียนในอดีตด้วยการค้นหาแบบเวกเตอร์) เมื่อมีสิ่งนี้ เอเจนต์จะทำงานในฐานะ “เพื่อนที่รู้จักผม” ไม่ใช่ “คนแปลกหน้าที่เพิ่งเจอกันทุกครั้ง” ผมขอฝากพื้นฐานเรื่องนี้ไว้กับบทความสองเรื่องนี้

คืนที่ AI แก้ไขฉัน 5 ครั้ง - ปรัชญาการออกแบบเพื่อให้ RAG ส่วนตัวเป็นเพื่อนคุณเป็นเวลา 5 ปี

ฉีด ‘สิ่งที่ควรนึกถึงตอนนี้’ ให้ AI ทุกเทิร์น — ชั้นกระตุ้นความจำ (Activation Layer) ของ RAG ส่วนตัว

ความทรงจำร่วมนี้เคยมีกับดักหนึ่งที่ผมพบและปิดไปแล้ว นั่นคือการที่การตัดสินใจของอีกโปรเจกต์หนึ่งปนเข้าไปในเอกสารออกแบบของอีกโปรเจกต์หนึ่ง ผ่านทางความทรงจำร่วมนี้

ตรวจจับและแยกการตัดสินใจของอีกโปรเจกต์ที่ปนเข้ามา — กับดักของเอเจนต์และ RAG ที่ข้ามหลายระบบ

ครั้งนี้ผมก้าวไปอีกขั้นหนึ่ง ไม่ใช่แค่ “ตรวจจับและแยก” การปนเปื้อนเท่านั้น แต่ผมสร้างกลไกสำหรับถอนบทเรียนที่ผิดซึ่งเคยถูกเขียนไว้แล้วออกในภายหลัง ขอชี้แจงบทบาทให้ชัดเจนก่อน ตัว RAG เอง (ฐานข้อมูลที่สะสมบันทึก) เป็นเพียงคลังเก็บแบบพาสซีฟ ไม่ได้ถอนอะไรด้วยตัวมันเอง สิ่งที่ทำการถอนจริง ๆ คือกลไกการปฏิบัติงาน (แบตช์รายวัน) ที่ผมสร้างขึ้นล้อมรอบมัน อยากให้อ่านเรื่องนี้ไม่ใช่ในฐานะ “RAG ชำระล้างตัวเองอย่างชาญฉลาด” แต่เป็น “การเสริมกระบวนการถอนเข้าไปในคลังเก็บภายหลัง”

บทเรียนที่ผิดบิดเบือนเพื่อน AI ทุกเทิร์น

ความทรงจำนี้มีกลไกที่ทำเครื่องหมาย “priority: high” ให้กับสิ่งที่สำคัญ บันทึกที่มีเครื่องหมายจะถูกฉีดเข้าไปโดยอัตโนมัติในตอนต้นของทุกบทสนทนาว่า “ตอนนี้ให้นึกถึงสิ่งนี้” (การฉีดนี้คือชั้นกระตุ้นความจำ Activation Layer รายละเอียดอยู่ในลิงก์ด้านบน) มันทรงพลัง แต่ในทางกลับกันก็อันตราย ถ้าเขียนบทเรียนด้วยน้ำเสียงที่ฟันธงเป็น “priority: high” ทั้งที่ยังไม่ได้ยืนยันสาเหตุที่แท้จริง ข้อผิดพลาดนั้นจะถูกฉีดเข้าไปซ้ำทุกเทิร์นต่อไปเรื่อย ๆ

ผมทำแบบนี้จริง ๆ เกี่ยวกับข้อบกพร่องของเครื่องมือตัวหนึ่ง ผมเขียนว่า “วินิจฉัยยืนยันแล้ว: สาเหตุคือสิ่งนี้” ก่อนที่จะหาสาเหตุที่แท้จริงได้ครบถ้วน ต่อมาภายหลังจึงรู้ว่ามันผิด แต่ระหว่างนั้น บทเรียนที่ผิดซึ่งมีเครื่องหมายก็ถูกฉีดเข้าไปต่อหน้าเพื่อน AI ตลอดเวลา บิดเบือนรากฐานการตัดสินใจไปอย่างเงียบ ๆ ถ้าการสำรองข้อมูลคือการเตรียมพร้อมเพื่อไม่ให้ “พัง” นี่คือปัญหาเพื่อไม่ให้ความทรงจำ “ปนเปื้อน”

ทำไมเพิ่มบันทึกแก้ไขแล้วถึงไม่ได้ผล

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

   เพื่อน AI เรียกความทรงจำเกี่ยวกับหัวข้อหนึ่ง

        ├─▶ บทเรียนที่ผิด (priority: high) ──▶ ถูกฉีดเข้าไปทุกเทิร์น ──┐
        │                                                          │ บิดเบือนการตัดสินใจต่อไป
        └─▶ บันทึกแก้ไขที่เพิ่มทีหลัง ──▶ แค่ถูกดึงขึ้นมาด้วยกัน ───┘

                    เป็นแค่ "วางเคียงกัน" ข้อผิดพลาดยังไม่หายไป

ถ้าเป็นความทรงจำของมนุษย์ ข้อผิดพลาดเก่า ๆ จะจางหายไปด้วยการลืมหรือการเขียนทับ แต่บันทึกที่เพิ่มข้อมูลอย่างเดียวไม่มีกลไก “การจางหาย” นั้น RAG ไม่ได้น่าเชื่อถือแค่เพราะ “เขียนได้” แต่ต้องรวมไปถึงขั้นที่ “ถอนข้อผิดพลาดได้” ด้วย จึงจะน่าเชื่อถือจริง — นี่คือแก่นของบทความนี้

กลไกที่ผมสร้าง — หนึ่งบรรทัดที่ต้นบรรทัด แล้วแบตช์รายวันจะลบให้

ผมจึงเสริมกลไกที่สามารถประกาศอย่างชัดเจนว่า “ถอนสิ่งนี้” เข้าไปภายหลัง เมื่อรู้ตัวว่ามีข้อผิดพลาด ให้เขียนแบบนี้ที่ต้นบรรทัดของรายการแก้ไข พร้อมวลีที่ระบุข้อผิดพลาดที่ต้องการลบได้อย่างเฉพาะเจาะจง

RETRACTS: <วลีที่ระบุบทเรียนที่ผิดซึ่งต้องการลบได้อย่างเฉพาะเจาะจง>

ผมเพิ่มขั้นตอนไว้ในช่วงท้ายของแบตช์รายวันที่รันทุกวัน ให้สแกนบันทึกทั้งหมดเพื่อดึงมาร์กเกอร์นี้ขึ้นมา แล้วลบบันทึกที่ตรงกันออกจาก DB โดยอัตโนมัติ (ผมใส่ไว้ในแบตช์รายวันเดียวกับการสำรองข้อมูลอัตโนมัติ การออกแบบแบตช์ทั้งหมดเขียนไว้ในบทความคู่กัน A) สิ่งที่ทำการลบจริง ๆ คือสคริปต์การปฏิบัติงานนี้ ไม่ใช่ DB ที่ตัดสินใจเอง ขั้นตอนเป็นดังนี้

   สังเกตเห็นข้อผิดพลาด


   เขียน  RETRACTS: <วลีเฉพาะเจาะจง>  ที่ต้นบรรทัดของรายการแก้ไข


   แบตช์รายวัน (ทุกวัน・บนเครื่อง PC ท้องถิ่น)

        ├─▶ สแกนบันทึกทั้งหมดเพื่อดึงมาร์กเกอร์
        │        │
        │        ├─ ตรงกัน ──▶ ลบบันทึกนั้นออกจากทั้งสองที่เก็บ (โน้ตดิบและความรู้ที่คัดสรร)
        │        └─ ไม่มีที่ตรงกัน ──▶ ไม่ทำอะไร (idempotent)

   บทเรียนที่ผิดหายไป พร้อมกับการฉีดเข้าสู่ชั้นกระตุ้นความจำ

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

การออกแบบด้านความปลอดภัยเพื่อไม่ให้ยิงพลาด

กลไกที่ลบเรกคอร์ดใน DB โดยอัตโนมัติ หากออกแบบผิดพลาดจะเป็นอันตราย ถ้าตั้งใจจะถอนแต่ดันดึงบันทึกที่ไม่เกี่ยวข้องเข้ามาด้วย นั่นก็คือการทำลายความทรงจำในอีกรูปแบบหนึ่ง ผมจึงทุ่มเทให้กับด้านความปลอดภัยมากที่สุด

  • ค่าเริ่มต้นคือไม่ลบ (dry-run — ไม่ลบจริง เพียงแค่แสดง “เป้าหมายที่จะลบ” เท่านั้น) การลบจริงจำกัดเฉพาะเวลารันแบตช์แบบ production เท่านั้น เมื่อตรวจสอบด้วยมือจะไม่มีอะไรถูกลบ
  • ละเลยวลีที่สั้นเกินไป (สิ่งที่มีความยาวต่ำกว่าจำนวนตัวอักษรที่กำหนดจะถูกยกเว้น) เพราะถ้าถอนด้วยคำสั้น ๆ ที่พบได้ทั่วไป จะไปตรงกับบันทึกที่ไม่เกี่ยวข้องและดึงเข้ามาด้วย
  • ไม่ลบคำประกาศถอนเอง (บันทึกที่มีคำว่า RETRACTS: — คือรายการถอนเอง — ถูกยกเว้นออกจากเป้าหมาย) มิฉะนั้นข้อความถอนจะลบตัวเองไป ถือว่ามีแค่ RETRACTS: ที่ต้นบรรทัดเท่านั้นที่เป็นมาร์กเกอร์ ส่วนที่แค่กล่าวถึงคำนี้กลางประโยคจะไม่ถือว่าเป็นมาร์กเกอร์
  • มีการสำรองข้อมูลก่อนลบเสมอ แบตช์รายวันจะถ่ายสแนปช็อตของ DB ไว้ในขั้นตอนก่อนหน้า สถานะก่อนการถอนจึงยังคงอยู่เสมอ
  • รันกี่ครั้งก็ได้ผลลัพธ์เหมือนเดิม (idempotent — ถ้าไม่มีที่ตรงกันเลยก็จะไม่ทำอะไร)

ก่อนนำขึ้น production ผมยืนยันไม่เพียงแค่ในกรณีปกติ (มีมาร์กเกอร์อยู่) เท่านั้น แต่ยังผ่านการทดสอบด้วยอินพุตขอบเขตและอินพุตที่เป็นปรปักษ์ด้วย (คำที่สั้นเกินไป, การกล่าวถึง RETRACTS: ในประโยคถอนเอง, คำเดียวกันที่ปะปนอยู่กลางร้อยแก้ว) เพื่อยืนยันว่าจะไม่ยิงพลาดจากการจับคู่ที่ไม่ได้ตั้งใจ

ในการทำงานปกติ (มาร์กเกอร์ 0 รายการ) ผลลัพธ์จริงเป็นแบบนี้ ค่าเริ่มต้นคือไม่ลบอะไรและเพียงแสดง “เป้าหมายที่จะลบ” เท่านั้น ส่วน --apply จะถูกเติมโดยแบตช์รายวันเท่านั้น (ปกปิดพาธภายในและชื่อสคริปต์)

$ retraction --dry-run
[retraction] scanning data/ ...
[retraction] RETRACTS markers found: 0
[retraction] would delete: 0 records
(no changes; --apply is added only by the daily batch)

การอัตโนมัติเป็นเพียงประกัน — สองชั้นร่วมกับวินัยลบทันทีที่รู้ตัว

อีกสิ่งหนึ่งที่ผมให้ความสำคัญคือ ไม่ทำให้การอัตโนมัติเป็นสิ่งวิเศษที่แก้ทุกอย่างได้ มาร์กเกอร์ถอนเป็นเพียง “ประกันสำหรับสิ่งที่หลุดรอด” ไม่ใช่แนวหน้า ทันทีที่รู้ตัวว่ามีบทเรียนที่ผิด ผมจะลบออกจาก DB ตรงนั้นทันที และแก้ไขโน้ตต้นฉบับด้วย ผมตั้งเรื่องนี้เป็นวินัยข้อแรก และให้การถอนอัตโนมัติรายวันด้วยมาร์กเกอร์เป็นตาข่ายชั้นที่สอง

   แนวหน้า: ลบตรงนั้นทันทีที่รู้ตัว + แก้ไขต้นฉบับ (ทันที・แน่นอน)
        │  ถ้าหลุดรอดไป

   ตาข่ายชั้นที่สอง: มาร์กเกอร์ RETRACTS: → แบตช์รายวันถอนอัตโนมัติ (ประกัน)

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

สิ่งที่ผมยอมตัดใจไว้ในตอนนี้

กลไกนี้ยังคงถูกทำให้เรียบง่ายอย่างตั้งใจ สิ่งที่ถอนได้คือบันทึกฝั่ง DB ส่วนการลบทิ้งทางกายภาพจากไฟล์ Markdown ต้นฉบับ (tombstone — กระบวนการปักป้ายหลุมศพเพื่อทำให้ใช้ไม่ได้) ยังไม่ได้ทำให้เป็นอัตโนมัติ เพราะการแก้ไขไฟล์อัตโนมัติเปราะบางและผลข้างเคียงยากที่จะคาดเดา เนื่องจากความเสียหายจริงที่เกิดจากการถูกฉีดเข้าไปทุกเทิร์นและบิดเบือนการตัดสินใจอยู่ที่ฝั่ง DB ผมจึงให้ความสำคัญกับการตัดตรงนั้นให้แน่นอนก่อน ส่วนไฟล์ต้นฉบับที่ตรงกันจะถูกบันทึกไว้ในล็อก จึงสามารถลบด้วยมือได้หากจำเป็น

ยังมีแนวทางที่ดูฉลาดขึ้นไปอีกขั้น คือ “ให้ AI ค้นหาบทเรียนที่ดูขัดแย้งกันแล้วลบให้อัตโนมัติ” แต่นั่นเป็นความน่าจะเป็น และอาจลบความทรงจำที่ถูกต้องออกไปโดยผิดพลาดได้ ผมเลือกวางรากฐานด้วยมาร์กเกอร์ที่ชัดเจนและแน่นอนก่อน — เป็นการตัดสินใจที่ให้ความแน่นอนมาก่อนความฉลาด

เสริม “การถอน” เข้าไปในความทรงจำแบบเพิ่มข้อมูลอย่างเดียว

สิ่งที่ผมทำ ถ้าจะสรุปให้ถึงแก่นแล้วมีอย่างเดียว ผมมอบการดำเนินการที่ว่า “ให้ถือว่าสิ่งนี้ไม่เคยเกิดขึ้น” ให้กับความทรงจำที่แต่ก่อนทำได้แค่เพิ่มข้อมูลอย่างเดียว ฐานความรู้ที่สะสมอย่างเดียวจะยิ่งมีข้อผิดพลาดตกตะกอนสะสมมากขึ้นตามเวลาที่ผ่านไป และข้อผิดพลาดที่มีเครื่องหมายก็ยังถูกขุดขึ้นมาย้ำทุกวัน ความทรงจำที่ลืมไม่ได้ ไม่ได้ฉลาดขึ้น แต่กลับบิดเบี้ยวไป การนำการถอนเข้ามาผนวกไว้ในการปฏิบัติงาน ทำให้ความทรงจำกลายเป็นสิ่งที่ “ถูกแก้ไขให้ถูกต้อง” ไม่ใช่แค่ “เพิ่มขึ้น” เท่านั้น เป็นครั้งแรก

เรื่องนี้ไม่น่าจะจำกัดอยู่แค่ RAG ส่วนตัวเท่านั้น ไม่ว่าจะเป็นล็อกหรือเอกสารออกแบบ ฐานความรู้ใดก็ตามที่เติบโตด้วยการเพิ่มข้อมูลอย่างเดียว จำเป็นต้องมีกลไกที่ปฏิบัติต่อ “การถอนและแทนที่” เป็นเรื่องสำคัญอันดับต้น ๆ อยู่ที่ไหนสักแห่ง เรื่องที่ทำให้ความทรงจำ “ไม่พัง” ผมเขียนไว้ในบทความคู่กันถัดไป

สำรองข้อมูลสมองของเพื่อน AI แบบเข้ารหัสไว้นอกสถานที่ด้วย AWS S3 + restic — ย้ายความทรงจำออกไปโดยไม่อัปโหลดแบบ plaintext

ออกแบบแผนการเรียนรู้ที่ไม่จมไปกับข้อมูลล้นเกิน — จำกัดการจูน RAG/LLM ให้เหลือ ‘สามอย่าง เจาะลึก’ และถือ ‘วัดผลให้ได้’ เป็นหน่วยการเรียนรู้

และกลไกตรวจสอบประจำสัปดาห์ที่รักษาทั้งหมดนี้ให้สมบูรณ์ ผมเขียนไว้ในบทความนี้

ตรวจสุขภาพของ RAG ส่วนตัวทุกสัปดาห์ด้วยกลไก ไม่ใช่ด้วยความตั้งใจ — เฮลท์เช็กหลายมุมและประตูเวลาที่บังคับให้การตรวจถูกรัน

ส่งข้อความได้ตามสบาย

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