ผมสร้าง RAG ขึ้นมาเองตั้งแต่ต้นด้วย Python เพื่อให้เข้าใจมันอย่างแท้จริง
ผมใช้ LangChain ในงานระดับ production มานานถึง 6 เดือน แต่ผมกลับอธิบายไม่ได้ว่ามันทำงานอย่างไร ผมไม่รู้ว่าทำไมถึงเลือกใช้ metric เฉพาะเจาะจงบางตัว หรือข้อความกลายเป็น vector ได้อย่างไร เพราะ library มันซ่อนตรรกะการทำงานเอาไว้
เพื่อแก้ปัญหานี้ ผมจึงลบ framework ทิ้งไป แล้วเขียน RAG pipeline ขึ้นมาใหม่เองตั้งแต่ต้นด้วย Python ล้วนๆ เพียง 500 บรรทัด
และนี่คือสิ่งที่ผมได้เรียนรู้จากการสร้าง stack นี้ขึ้นมาด้วยตัวเอง
ปัญหาของ Black Boxes
เมื่อคุณใช้ library ระดับสูง คุณจะสูญเสียการควบคุม ผมเคยเห็นโมเดลสร้างข้อมูลเท็จ (hallucinate) หรืออ้างอิงแหล่งที่มาผิดพลาด ผมไม่สามารถบอกได้เลยว่าข้อผิดพลาดนั้นมาจาก chunker, embedding model หรือ prompt กันแน่
แต่เมื่อคุณสร้างมันขึ้นมาเอง ทุกเลเยอร์จะสามารถตรวจสอบได้ คุณสามารถสั่ง print ดู chunk ที่ส่งไปยัง LLM ได้อย่างแม่นยำ และเห็นได้ชัดเจนว่าประโยคถูกตัดแบ่งตรงไหน
5 เลเยอร์ของ RAG
RAG ไม่ใช่แค่อัลกอริทึมเดียว แต่มันคือกระบวนการ 5 อย่างที่ทำงานร่วมกัน:
- Chunking: การตัดสินใจว่าจะแบ่งข้อความอย่างไร
- Embedding: การเปลี่ยนข้อความให้เป็นค่าทางคณิตศาสตร์
- Retrieval: การค้นหาชิ้นส่วนข้อมูลที่ถูกต้อง
- Prompt Construction: การกำหนดวิธีการทำงานให้กับโมเดล
- Generation: การสร้างคำตอบสุดท้ายออกมา
บทเรียนจากการสร้าง
1. Chunking คือขั้นตอนที่สำคัญที่สุด บทเรียนส่วนใหญ่มักข้ามขั้นตอนนี้ไป หากคุณไม่ใช้การทำ overlap คุณจะสูญเสียบริบท (context) ตรงรอยต่อของข้อมูล ผมจึงใช้เทคนิค sliding window พร้อมกับ character-level overlap เพื่อให้มั่นใจว่าโมเดลจะเห็นความเชื่อมโยงระหว่างสอง chunk
2. Distance metrics มีความสำคัญ ผมเสียเวลาหลายชั่วโมงในการ debug ผลการค้นหาที่แย่ ปรากฏว่าปัญหาไม่ได้อยู่ที่ข้อมูล แต่อยู่ที่ metric โดยปกติ ChromaDB จะใช้ L2 distance เป็นค่าเริ่มต้น แต่สำหรับการทำ semantic search คุณจำเป็นต้องใช้ Cosine similarity เพียงแค่เปลี่ยนโค้ดบรรทัดเดียว ทุกอย่างก็เปลี่ยนไปทันที
3. Prompt ต้องมีข้อจำกัด LLM คือเครื่องมือเติมเต็มคำพูด ไม่ใช่ผู้วิเศษ หากคุณถามคำถามที่คลุมเครือ มันจะแต่งคำตอบขึ้นมาเอง ผมจึงเรียนรู้ที่จะใช้ strict refusal template โดยบอกโมเดลว่า: "หากใน context ไม่มีคำตอบ ให้ตอบว่าไม่ทราบ" วิธีนี้ช่วยลดการ hallucinate จาก 40% เหลือเพียง 5%
4. ส่ง request แบบ batch การส่งหนึ่ง HTTP request ต่อหนึ่ง chunk นั้นช้ามาก การส่งแบบ batch จะเร็วกว่ามาก เพราะช่วยให้โมเดลในเครื่องสามารถทำ pipeline การทำงานได้อย่างมีประสิทธิภาพ
5. ทดสอบจากล่างขึ้นบน อย่ารอเขียน test ตอนท้ายสุด ให้ทดสอบ chunker ก่อน จากนั้นค่อยทดสอบ embedder แล้วจึงทดสอบ store หากคุณรอทดสอบตอนท้าย คุณจะพบแต่ bug แทนที่จะได้ทดสอบตรรกะการทำงาน
หากคุณรู้สึกว่ายังไม่เข้าใจ AI stack ของคุณอย่างแท้จริง ลองสร้างมันขึ้นมาด้วยตัวเองดูสิ เพราะเป้าหมายไม่ใช่แค่การเขียนโค้ด แต่คือการทำความเข้าใจกระบวนการคิดต่างหาก
Optional learning community: https://t.me/GyaanSetuAi
