Tool: rag_search — ค้นไฟล์ในเครื่องแบบไม่ต้องใช้ RAM ของ embedding model

หลังจากทำความเข้าใจว่า tool คืออะไร agent เรียกใช้อย่างไร บทความนี้จะลงรายละเอียด rag_search ซึ่งเป็น tool หลักที่ AGENT LITE ใช้เมื่อจำเป็นต้องค้นหาไฟล์ในเครื่อง
Tool นี้คืออะไร
rag_search ค้นหาไฟล์ในเครื่องด้วยชื่อไฟล์หรือคำในเนื้อหาไฟล์ คืนผลเป็น path พร้อม snippet สั้นๆ ให้เห็นบริบท — schema ที่ส่งให้โมเดลเห็นระบุไว้ตรงๆ ว่า “Use this FIRST when the user asks to find a file, before trying bash/find”
จุดที่น่าแปลกใจที่สุดสำหรับคนคุ้นกับคำว่า “RAG” (Retrieval-Augmented Generation) คือ tool นี้ไม่มี embedding model เลย มันเป็นการค้นแบบ lexical (ตรงคำ) ล้วนๆ ผ่าน SQLite FTS5 (full-text search ในตัว SQLite) — เหตุผลอยู่ในคอมเมนต์ต้นไฟล์ตรงๆ: เวอร์ชันเดิมของโปรเจกต์ต้นทาง (MAX_VLM) ใช้ MiniLM + ChromaDB ซึ่งกิน RAM ราว 0.5-1GB สำหรับโมเดล embedding อย่างเดียว — บนเครื่องเป้าหมาย RAM 8GB นั่นคือเกือบทั้งงบ RAM ของทั้ง agent ทีมพัฒนาจึงตัดสินใจแลกความสามารถค้นเชิงความหมาย (semantic) ทิ้งไป เพื่อแลกกับการรันได้บนเครื่องเล็กจริงๆ — trade-off นี้บันทึกไว้ชัดเจนว่า “query ที่ไม่มีคำตรงกับไฟล์เป้าหมายเลยจะหาไม่เจอ” เป็นข้อจำกัดที่ทีมยอมรับไว้ตั้งแต่ต้น
Agent เรียก tool นี้เมื่อไร
จาก docstring ที่ผูกกับ tool (ส่วนที่โมเดลเห็นตอนตัดสินใจ):
Use this FIRST for every on-device file discovery/search request. The only exception is when the user explicitly says to use/run bash.
พูดง่ายๆ: คำขอหาไฟล์ = เรียก rag_search ก่อนเสมอ ยกเว้นผู้ใช้สั่ง bash ตรงๆ และถ้าเจอไฟล์ที่ต้องการแล้ว ให้เรียก read_file ต่อด้วย path ที่ได้ ไม่ใช่เรียก bash ซ้ำ — มีตัวอย่าง ✅/❌ กำกับไว้ใน docstring ตรงๆ เพื่อกันโมเดลเลือกผิด:
❌ "ไฟล์ rental_contract.docx อยู่ไหน สรุปให้หน่อย" -> ห้ามเรียก bash ตรงๆ
✅ rag_search(query="rental_contract.docx") ก่อน แล้วค่อย read_file path ที่ได้
ข้างในทำงานอย่างไร: ขยาย query ด้วย synonym ก่อนค้น
ก่อนยิง query เข้า FTS5 ระบบทำความสะอาด query ก่อน (_query_terms): normalize Unicode (NFC), casefold, ตัดคำฟุ่มเฟือย (หา, ให้, หน่อย, the, a, an ฯลฯ) ทิ้ง แล้วเก็บคำจริงสูงสุด 12 คำ
จุดที่น่าสนใจคือตาราง synonym ที่ตรวจทานไว้ล่วงหน้า — เช่นค้น "รูป" ระบบจะ OR เข้ากับ ภาพ, image, photo, picture ให้อัตโนมัติ เพราะพบจริงว่าผู้ใช้พิมพ์คำหนึ่ง แต่ไฟล์ที่มีเนื้อหาเกี่ยวข้องใช้อีกคำ (เช่นไฟล์เขียนว่า “ภาพ” แต่คนค้น “รูป”) — ระบบล่างระดับ trigram tokenizer ของ SQLite เองก็ case-insensitive อยู่แล้วโดยไม่ต้องทำอะไรเพิ่ม (ทดสอบแล้วจริง ก่อนตัดสินใจไม่ต้องแก้ตรงนั้น)
ยังมีการ split คำอังกฤษที่ PDF/OCR มักตัดกลางคำ เช่น information ที่ PDF extract ออกมาเป็น infor mation — ระบบสร้าง query variant เผื่อเจอคำที่ถูกตัดแบบนี้ด้วย (จำกัดจำนวน variant ไม่ให้ query บวมเกินไป)
Ranking: ทำไมไม่เชื่อ BM25 ล้วนๆ กับชื่อไฟล์
คอมเมนต์ในซอร์สเล่าบั๊กจริงที่เจอตอน smoke test สุดท้ายก่อน merge: ค้นหาไฟล์ชื่อ “PLAN.md” ตรงๆ แต่ BM25 ranking (แม้ลองปรับ column weight หลายแบบแล้ว) กลับจัดอันดับไฟล์ PLAN.md ตัวจริงต่ำกว่าไฟล์ที่แค่ “พูดถึง” [PLAN.md] และต่ำกว่าไฟล์ชื่อไม่เกี่ยวอย่าง ENCRYPTION_PLAN.md ด้วยซ้ำ — สรุปว่า BM25 บน field ชื่อไฟล์แบบสั้นๆ ที่ผ่าน trigram tokenizer ไม่ได้ทำงานตามสัญชาตญาณคนทั่วไปคาดหวัง
ทางแก้: ไม่เชื่อคะแนน BM25 สำหรับชื่อไฟล์เลย — ใช้ FTS5 แค่หา candidate (เพื่อความเร็ว/recall) แล้วจัดอันดับเองด้วยกฎง่ายๆ ที่ตรวจสอบได้ตรงไปตรงมา: ชื่อตรงเป๊ะ > query เป็น substring ของชื่อ > ชื่อสั้นกว่า (เจาะจงกว่า)
Fallback หลายชั้นเมื่อ FTS หาไม่เจอ
- ยังไม่มี index เลย → ค้นชื่อไฟล์ตรงๆ ในโฟลเดอร์ผู้ใช้ (Desktop, Documents, Downloads) แบบจำกัดเวลา 4 วินาที/50 ผลลัพธ์
- FTS ค้นแล้วได้ 0 ผล กับ query แบบคำเดียว → ลองค้นชื่อไฟล์แบบ filesystem walk เหมือนกัน (คำเดียวมักหมายถึง “หาไฟล์ชื่อนี้” ไม่ใช่ค้นเนื้อหา)
- แม้แต่นั้นก็ไม่เจอ แต่คำถามพูดถึงโปรเจกต์ปัจจุบัน → สแกนเนื้อหาไฟล์ source/config จริงในโปรเจกต์ (
.py,.md,.jsonฯลฯ) ตรงๆ เพราะ index หลักตั้งใจครอบคลุมแค่เอกสารผู้ใช้ ไม่รวมไฟล์ source
ความปลอดภัยและความสะอาดของผลลัพธ์
ทุก path ที่จะคืนกลับต้องผ่าน _allowed_result_path: ไฟล์ต้องมีอยู่จริง (index อาจเก่ากว่าการเปลี่ยนแปลงจริงบน disk เช่นไฟล์ที่ iCloud ย้ายไปแล้ว), ไม่ใช่ sensitive path, อยู่ในขอบเขตโปรเจกต์ (ถ้า query จำกัดขอบเขต), และไม่ใช่ path ภายในของตัวเอง (.git, __pycache__, logs/ ฯลฯ) — ข้อสุดท้ายนี้กันไม่ให้ log เก่าของ agent เองมาแซงไฟล์จริงในผลการค้น
ทำไมเรื่องนี้ถึงสำคัญ
rag_search เป็นตัวอย่างที่ดีของการตัดสินใจแลก feature เพื่อความเป็นไปได้จริง — โปรเจกต์ยอมทิ้ง semantic search (แม่นกว่าในหลายกรณี) เพื่อให้รันได้บนเครื่อง RAM 8GB จริงๆ แล้วชดเชยด้วยกลไก lexical ที่ฉลาดขึ้น (synonym, split, fallback หลายชั้น) แทนที่จะพึ่งพา embedding model อย่างเดียว
ความคิดเห็น
กำลังโหลดความคิดเห็น...