เพื่อให้สถาปัตยกรรม RAG สำหรับระบบ DMS ของคุณใช้งานได้จริงและมีประสิทธิภาพสูงสุด นี่คือ **รายละเอียดการ Implementation พร้อมตัวอย่างการตั้งค่า (Configuration)** ในแต่ละส่วน โดยอิงจากการใช้ Python เป็นหลักในการควบคุม Pipeline ครับ ## 1. Ingestion & Semantic Chunking Pipeline (Typhoon OCR + Typhoon 2.5) ในขั้นตอนนี้ เราจะรับไฟล์เอกสาร นำเข้าสู่ OCR และส่งข้อความดิบให้ Typhoon 2.5 จัดโครงสร้างโดยใส่ tag เพื่อนำมาตัดแบ่งเนื้อหาตามบริบทจริง ### ตัวอย่าง Prompt สำหรับ Typhoon 2.5 (รอบแรก) เพื่อใส่ Tag ```text คุณคือผู้เชี่ยวชาญด้านการจัดการเอกสาร (DMS Editor) หน้าที่ของคุณคือรับข้อความดิบจากระบบ OCR แล้วนำมาจัดโครงสร้างใหม่ให้อยู่ในรูปแบบ Markdown เงื่อนไขสำคัญ: 1. ห้ามแก้ไข ตัดทอน หรือบิดเบือนข้อความสำคัญในเอกสาร 2. ให้วิเคราะห์เนื้อหา และแบ่งเนื้อหาออกเป็นส่วนๆ (Semantic Chunks) โดยใช้ Tag พิเศษ ครอบเนื้อหาที่เป็นเรื่องเดียวกันไว้ ดังนี้: ...ข้อความ... 3. หากมีข้อมูลสำคัญ เช่น เลขที่เอกสาร, วันที่, ชื่อบุคคล หรือเรื่อง ให้สกัดออกมาในรูปแบบของ Metadata ไว้ที่ส่วนบนสุดของเอกสารด้วยโครงสร้าง JSON ใน Tag ... ``` ### Python Implementation (การตัด Chunk จาก Tag) ```python import re import json # สมมติผลลัพธ์ที่ได้มาจาก Typhoon 2.5 รอบแรก typhoon_output = """ { "doc_number": "REQ-009", "date": "2026-06-05", "subject": "ขออนุมัติจัดซื้ออุปกรณ์เครือข่ายสำหรับโครงการ" } เนื่องด้วยระบบเครือข่ายเดิมในสำนักงานสนามมีความเร็วไม่เพียงพอต่อการใช้งาน... 1. Managed Switch 24-Port 2.5GbE จำนวน 1 ตัว 2. Core Router ER7206 จำนวน 1 ตัว """ def parse_typhoon_chunks(output): # สกัด Metadata metadata_match = re.search(r'(.*?)', output, re.DOTALL) metadata = json.loads(metadata_match.group(1).strip()) if metadata_match else {} # สกัด Chunks chunk_pattern = r'(.*?)' chunks = re.findall(chunk_pattern, output, re.DOTALL) processed_chunks = [] for topic, content in chunks: processed_chunks.append({ "text": content.strip(), "metadata": { **metadata, "chunk_topic": topic } }) return processed_chunks chunks_to_embed = parse_typhoon_chunks(typhoon_output) ``` ## 2. Vector Storage & Search Setup (BGE-M3 + Qdrant) BGE-M3 สามารถทำ **Hybrid Search** ได้ดีมาก (Dense Vector แทนความหมายเชิงลึก + Sparse Vector แทนคำสำคัญ/Keyword) เราจะตั้งค่า Qdrant Collection ให้รองรับทั้งสองแบบเพื่อป้องกันปัญหาคำค้นหาเฉพาะ (เช่น เลขที่เอกสาร หรือรหัสพัสดุ) หลุดหาย ### ตัวอย่างการตั้งค่า Qdrant Collection (Python Client) ```python from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams, SparseVectorParams, OptimizersConfigDiff client = QdrantClient(url="http://localhost:6333") # สร้าง Collection ที่รองรับทั้ง Dense และ Sparse (Hybrid) client.create_collection( collection_name="dms_documents", vectors_config={ # Dense Vector สำหรับ BGE-M3 (ขนาด 1024 มิติ) "bge_dense": VectorParams( size=1024, distance=Distance.COSINE ) }, sparse_vectors_config={ # Sparse Vector สำหรับทำ Keyword Matching จาก BGE-M3 "bge_sparse": SparseVectorParams() }, # เปิดใช้งาน Payload Index สำหรับ Metadata Filtering (เช่น ค้นหาเฉพาะเลขที่เอกสาร) optimizers_config=OptimizersConfigDiff(memmap_threshold=20000) ) ``` ## 3. Retrieval & Re-ranking Pipeline (Qdrant Search + BGE-Reranker) เมื่อผู้ใช้ส่งคำถามเข้ามา เราจะดึงข้อมูลแบบ Hybrid จาก Qdrant ออกมาจำนวนหนึ่ง (เช่น 15 Chunks) แล้วใช้ **BGE-Reranker** สแกนซ้ำเพื่อเลือกตัวที่ใช่ที่สุด 3-5 อันดับแรก ### Python Implementation สำหรับ Hybrid Search และ Rerank ```python from sentence_transformers import SentenceTransformer from transformers import AutoModelForSequenceClassification, AutoTokenizer import torch # 1. โหลดโมเดลสำหรับ Embedding และ Reranking # (ในงานจริงแนะนำให้ Host เป็น API แยก เช่น Tei หรือใช้ Local Inference) embedding_model = SentenceTransformer('BAAI/bge-m3') rerank_tokenizer = AutoTokenizer.from_pretrained('BAAI/bge-reranker-large') rerank_model = AutoModelForSequenceClassification.from_pretrained('BAAI/bge-reranker-large') rerank_model.eval() def hybrid_search_and_rerank(query, top_k_qdrant=15, top_k_final=3): # ก้าวที่ 1: แปลง Query เป็น Vector query_dense_vector = embedding_model.encode(query).tolist() # ก้าวที่ 2: ดึงข้อมูลจาก Qdrant (สมมติเรียกใช้ client.search) # *ในจุดนี้สามารถใส่ Filter สำหรับเจาะจงเลขเอกสารหรือวันที่ได้จาก Payload* qdrant_results = client.search( collection_name="dms_documents", query_vector=("bge_dense", query_dense_vector), limit=top_k_qdrant ) # ก้าวที่ 3: เตรียมข้อมูลเข้า Reranker pairs = [[query, res.payload['text']] for res in qdrant_results] with torch.no_grad(): inputs = rerank_tokenizer(pairs, padding=True, truncation=True, return_tensors='pt', max_length=512) scores = rerank_model(**inputs).logits.view(-1).tolist() # ก้าวที่ 4: ประกบคะแนนใหม่และจัดเรียงลำดับ reranked_results = [] for i, res in enumerate(qdrant_results): reranked_results.append({ "text": res.payload['text'], "metadata": res.payload['metadata'], "rerank_score": scores[i] }) # เรียงจากคะแนนมากไปน้อย reranked_results.sort(key=lambda x: x['rerank_score'], reverse=True) # ส่งคืนเฉพาะ Top K ที่ดีที่สุดไปให้ LLM return reranked_results[:top_k_final] ``` ## 4. Generation Pipeline (Typhoon 2.5 รอบสุดท้าย) นำ Chunks ที่ผ่านการ Rerank มาเรียงต่อกันเป็น Context เพื่อให้ Typhoon 2.5 ตอบคำถาม โดยเน้นย้ำให้โมเดลตอบตามข้อเท็จจริงในเอกสารเท่านั้น ### ตัวอย่างการประกอบ System Prompt และ Prompt template ```text System Prompt: คุณคือผู้ช่วยอัจฉริยะประจำระบบจัดการเอกสาร (DMS AI Assistant) หน้าที่ของคุณคือตอบคำถามของผู้ใช้โดยใช้ข้อมูลจาก "Context เอกสารที่กำหนดให้" เท่านั้น ห้ามใช้ความรู้ภายนอกที่ไม่มีในเอกสารตอบโดยเด็ดขาด หากใน Context ไม่มีข้อมูลที่ตอบคำถามได้ ให้แจ้งผู้ใช้ตรงๆ ว่า "ไม่พบข้อมูลดังกล่าวในเอกสาร" และโปรดอ้างอิง เลขที่เอกสาร (doc_number) ทุกครั้งที่ตอบคำถามเพื่อความน่าเชื่อถือ ---------------- Context เอกสารที่ค้นพบ: [ข้อความจาก Chunk ที่ 1] (อ้างอิง: REQ-009) [ข้อความจาก Chunk ที่ 2] (อ้างอิง: REQ-009) ---------------- คำถามของผู้ใช้: อุปกรณ์เครือข่ายที่ขออนุมัติจัดซื้อในเอกสาร REQ-009 มีอะไรบ้างและใช้ที่ไหน? ``` ### 🛠️ คำแนะนำเพิ่มเติมสำหรับการดูแลระบบ (Ops & Infrastructure) 1. **การลด Latency ของ Reranker:** เนื่องจาก Reranker ต้องคำนวณ Cross-Attention ทุกครั้งที่ค้นหา หากรันบน CPU อาจจะช้า (ประมาณ 1-2 วินาทีต่อการ Query) แนะนำให้รันบน **GPU** หรือเลือกใช้โมเดลย่อส่วนอย่าง bge-reranker-base เพื่อทำความเร็วให้ตอบรับกับระบบ DMS ที่มีผู้ใช้งานพร้อมกันจำนวนมาก 2. **การทำ Document Version Control (Void & Replace):** ในระบบ DMS เมื่อเอกสารมีการแก้ไข (เช่น ออกเวอร์ชันใหม่ของ REQ-009) อย่าลืมส่ง Metadata version หรือรันคำสั่ง **Delete Points** ใน Qdrant โดยกรองจาก doc_number เก่าออกไปก่อน เพื่อไม่ให้ระบบดึงเอาข้อความจากเอกสารเวอร์ชันเก่าขึ้นมาตอบปนกับเวอร์ชันปัจจุบันครับ 3.