ผมเคยเจอกรณีที่ RAG pipeline สร้างคำตอบที่ดูน่าเชื่อถือมาก แต่พอตรวจสอบดูแล้ว คำตอบนั้นไม่ได้อ้างอิงจาก document ที่ retrieve มาเลย — นี่คือสิ่งที่เรียกว่า hallucination ในระบบ RAG วันนี้ผมจะแชร์วิธีการตรวจจับและลด hallucination ด้วยวิธีที่ใช้งานได้จริงใน production

RAG Hallucination คืออะไร และทำไมต้องสนใจ

RAG (Retrieval-Augmented Generation) แม้จะช่วยให้ LLM ตอบได้แม่นยำขึ้นด้วยข้อมูลจาก document แต่ hallucination ก็ยังเกิดขึ้นได้ โดยเฉพาะเมื่อ:

เหมาะกับใคร / ไม่เหมาะกับใคร

เกณฑ์ HolySheep AI Official API Relay Service อื่น
ราคา (GPT-4.1) $8/MTok $2-$15/MTok $10-$20/MTok
ความเร็ว Latency <50ms 100-300ms 80-200ms
การจ่ายเงิน ¥1=$1, WeChat/Alipay บัตรเครดิตเท่านั้น บัตรเครดิต/Bank Transfer
เครดิตฟรี มีเมื่อลงทะเบียน $5 ฟรี น้อย/ไม่มี
Hallucination Detection Built-in monitoring ต้อง implement เอง แตกต่างกันไป
เหมาะกับ Startup, SME, นักพัฒนาไทย/จีน องค์กรใหญ่ในต่างประเทศ ผู้ใช้ระดับกลาง
ไม่เหมาะกับ ต้องการ SLA 99.99%+ งบจำกัด, ผู้เริ่มต้น ต้องการราคาถูก

วิธีการตรวจจับ Hallucination ใน RAG

1. Source Attribution Scoring

วิธีแรกที่ผมใช้บ่อยคือ ตรวจสอบว่าคำตอบของ LLM มีกี่เปอร์เซ็นต์ที่มาจาก source document จริงๆ

# HolySheep AI - Source Attribution Detection
import requests
import json

BASE_URL = "https://api.holysheep.ai/v1"

def calculate_attribution_score(question: str, answer: str, retrieved_docs: list) -> dict:
    """
    ตรวจสอบว่าคำตอบมาจาก source จริงหรือไม่
    """
    
    prompt = f"""คุณคือผู้เชี่ยวชาญด้านการตรวจสอบความจริง
    
Question: {question}

Retrieved Documents:
{chr(10).join([f"[Doc {i+1}]: {doc}" for i, doc in enumerate(retrieved_docs)])}

Generated Answer: {answer}

วิเคราะห์และให้คะแนน:
1. ข้อความใดในคำตอบที่สนับสนุนโดย documents (ระบุ doc number)
2. ข้อความใดที่ไม่มีหลักฐานใน documents
3. คะแนน attribution 0-100%

ตอบกลับเป็น JSON format:
{{
  "attributed_claims": [...],
  "unattributed_claims": [...],
  "attribution_score": 0-100,
  "confidence": "high/medium/low",
  "hallucination_risk": "low/medium/high"
}}"""

    response = requests.post(
        f"{BASE_URL}/chat/completions",
        headers={
            "Authorization": f"Bearer YOUR_HOLYSHEEP_API_KEY",
            "Content-Type": "application/json"
        },
        json={
            "model": "gpt-4.1",
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0.1,
            "response_format": {"type": "json_object"}
        }
    )
    
    result = json.loads(response.json()["choices"][0]["message"]["content"])
    
    # ถ้า attribution score ต่ำกว่า 70% แสดง warning
    if result["attribution_score"] < 70:
        print(f"⚠️ WARNING: High hallucination risk! Score: {result['attribution_score']}%")
        print(f"Unattributed claims: {result['unattributed_claims']}")
    
    return result

ตัวอย่างการใช้งาน

question = "นโยบายการคืนเงินของบริษัทเป็นอย่างไร?" retrieved_docs = [ "นโยบายการคืนสินค้าภายใน 7 วัน โดยสินค้าต้องไม่ผ่านการใช้งาน", "การคืนเงินจะดำเนินการภายใน 14 วันทำการ" ] answer = "นโยบายการคืนเงินภายใน 30 วัน และสามารถคืนสินค้าที่ใช้แล้วได้" result = calculate_attribution_score(question, answer, retrieved_docs) print(f"Attribution Score: {result['attribution_score']}%")

2. Semantic Similarity Check

วิธีที่สองคือ ตรวจสอบ semantic similarity ระหว่างคำตอบกับ documents ที่ retrieve มา

# Semantic Similarity based Hallucination Detection
from sentence_transformers import SentenceTransformer
import numpy as np

class HallucinationDetector:
    def __init__(self):
        # ใช้โมเดล embedding ที่รองรับภาษาไทย
        self.model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
        self.threshold = 0.65  # ถ้าต่ำกว่านี้ถือว่า hallucination
    
    def detect(self, question: str, answer: str, retrieved_docs: list) -> dict:
        """
        ตรวจจับ hallucination ด้วย semantic similarity
        """
        
        # สร้าง embedding ของคำตอบ
        answer_embedding = self.model.encode([answer])[0]
        
        # สร้าง embedding ของ documents
        doc_embeddings = self.model.encode(retrieved_docs)
        
        # คำนวณ similarity สูงสุดกับ documents
        similarities = [
            np.dot(answer_embedding, doc_emb) / 
            (np.linalg.norm(answer_embedding) * np.linalg.norm(doc_emb))
            for doc_emb in doc_embeddings
        ]
        
        max_similarity = max(similarities)
        avg_similarity = np.mean(similarities)
        
        # แยกวิเคราะห์แต่ละประโยคในคำตอบ
        sentences = answer.split('. ')
        sentence_scores = []
        
        for sent in sentences:
            if sent.strip():
                sent_emb = self.model.encode([sent])[0]
                sent_sim = max([
                    np.dot(sent_emb, doc_emb) / 
                    (np.linalg.norm(sent_emb) * np.linalg.norm(doc_emb))
                    for doc_emb in doc_embeddings
                ])
                sentence_scores.append({
                    "sentence": sent,
                    "similarity": sent_sim,
                    "is_hallucination": sent_sim < self.threshold
                })
        
        # คำนวณ hallucination ratio
        hallucination_count = sum(1 for s in sentence_scores if s["is_hallucination"])
        hallucination_ratio = hallucination_count / len(sentence_scores) if sentence_scores else 0
        
        return {
            "max_similarity": float(max_similarity),
            "avg_similarity": float(avg_similarity),
            "sentence_analysis": sentence_scores,
            "hallucination_ratio": float(hallucination_ratio),
            "overall_risk": "high" if hallucination_ratio > 0.3 else 
                          "medium" if hallucination_ratio > 0.1 else "low",
            "recommendation": "ใช้คำตอบได้" if hallucination_ratio < 0.2 
                             else "ควรตรวจสอบเพิ่มเติม" if hallucination_ratio < 0.5 
                             else "ไม่แนะนำใช้คำตอบนี้"
        }

การใช้งาน

detector = HallucinationDetector() question = "วิธีการติดตั้งระบบ?" retrieved_docs = [ "ขั้นตอนที่ 1: ดาวน์โหลด installer จากเว็บไซต์", "ขั้นตอนที่ 2: รันไฟล์ setup.exe และทำตามขั้นตอน", "ขั้นตอนที่ 3: Restart เครื่องหลังติดตั้งเสร็จ" ] answer = "ติดตั้งโดยการคลิกขวาที่ไฟล์แล้วเลือก Run as Administrator" result = detector.detect(question, answer, retrieved_docs) print(f"Hallucination Risk: {result['overall_risk']}") print(f"Recommendation: {result['recommendation']}")

3. RAG Pipeline พร้อม Built-in Hallucination Detection

# Complete RAG Pipeline พร้อม Hallucination Detection
import requests
import json
from typing import List, Dict, Optional

class HolySheepRAGWithGuardrails:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.holysheep.ai/v1"
        self.hallucination_threshold = 0.7  # 70% minimum
    
    def _call_llm(self, messages: list, temperature: float = 0.3) -> str:
        """เรียก HolySheep API"""
        response = requests.post(
            f"{self.base_url}/chat/completions",
            headers={
                "Authorization": f"Bearer {self.api_key}",
                "Content-Type": "application/json"
            },
            json={
                "model": "gpt-4.1",
                "messages": messages,
                "temperature": temperature
            }
        )
        return response.json()["choices"][0]["message"]["content"]
    
    def _verify_answer(self, question: str, answer: str, context: str) -> dict:
        """ตรวจสอบว่าคำตอบ match กับ context หรือไม่"""
        
        verification_prompt = [
            {"role": "system", "content": """คุณคือผู้ตรวจสอบความถูกต้อง
        
ตรวจสอบว่าคำตอบสรุปข้อเท็จจริงจาก context ที่ให้มาหรือไม่

กฎ:
- คำตอบต้องสนับสนุนด้วยข้อมูลใน context เท่านั้น
- ห้ามเพิ่มข้อมูลที่ไม่มีใน context
- ห้ามขัดแย้งกับข้อมูลใน context

ให้คะแนน 0-100 และอธิบายเหตุผล"""},
            {"role": "user", "content": f"""Context:\n{context}\n\nQuestion:\n{question}\n\nAnswer:\n{answer}\n\nตรวจสอบและให้คะแนน:"""}
        ]
        
        result = self._call_llm(verification_prompt, temperature=0.1)
        
        # Parse score (สมมติว่า LLM ตอบเป็น "Score: 85/100")
        try:
            score = int(''.join(filter(str.isdigit, result.split('/')[0].split()[-1])))
        except:
            score = 50  # Default fallback
        
        return {
            "score": score,
            "is_grounded": score >= 70,
            "needs_human_review": score < 90,
            "verification_detail": result
        }
    
    def query(self, question: str, retrieved_context: str, 
              use_fallback: bool = True) -> Dict:
        """
        Query RAG system พร้อม hallucination guardrails
        """
        
        # Step 1: Generate answer
        messages = [
            {"role": "system", "content": """คุณคือผู้ช่วยที่ตอบคำถามโดยอ้างอิงจาก context ที่ให้มาเท่านั้น

กฎสำคัญ:
1. ตอบจากข้อมูลใน context เท่านั้น
2. ถ้า context ไม่มีข้อมูลที่ต้องการ ให้ตอบว่า "ไม่พบข้อมูลในเอกสารที่ให้มา"
3. อ้างอิงแหล่งที่มาในคำตอบ"""},
            {"role": "user", "content": f"""Context:\n{retrieved_context}\n\nQuestion:\n{question}"""}
        ]
        
        answer = self._call_llm(messages)
        
        # Step 2: Verify answer
        verification = self._verify_answer(question, answer, retrieved_context)
        
        result = {
            "answer": answer,
            "verification": verification,
            "source_used": True
        }
        
        # Step 3: Fallback if hallucination detected
        if not verification["is_grounded"] and use_fallback:
            result["fallback_triggered"] = True
            result["fallback_answer"] = self._generate_safe_fallback(question)
        
        return result
    
    def _generate_safe_fallback(self, question: str) -> str:
        """คำตอบสำรองเมื่อตรวจพบ hallucination"""
        
        fallback_prompt = [
            {"role": "system", "content": "คุณคือผู้ช่วยที่ตอบอย่างระมัดระวัง ถ้าไม่แน่ใจให้บอกว่าไม่รู้"},
            {"role": "user", "content": f"""คำถาม: {question}

เนื่องจากไม่สามารถยืนยันความถูกต้องของคำตอบได้ กรุณาตอบอย่างปลอดภัย:
1. บอกว่าต้องตรวจสอบข้อมูลเพิ่มเติม
2. แนะนำให้ติดต่อ support
3. ไม่ควรสร้างข้อมูลที่ไม่มั่นใจ"""}
        ]
        
        return self._call_llm(fallback_prompt, temperature=0.2)

การใช้งาน

rag = HolySheepRAGWithGuardrails(api_key="YOUR_HOLYSHEEP_API_KEY") question = "ระยะเวลารับประกันของสินค้าคือเท่าไร?" context = """ เงื่อนไขการรับประกัน: - อุปกรณ์อิเล็กทรอนิกส์: รับประกัน 1 ปี - อะไหล่: รับประกัน 6 เดือน - การติดตั้ง: รับประกัน 30 วัน """ result = rag.query(question, context) print(f"Answer: {result['answer']}") print(f"Verification Score: {result['verification']['score']}") print(f"Grounded: {result['verification']['is_grounded']}")

เทคนิคการลด Hallucination

1. Query Expansion & Decomposition

แยกคำถามซับซ้อนเป็นคำถามย่อย เพื่อ retrieve context ที่แม่นยำขึ้น

2. Hybrid Retrieval

ใช้ทั้ง vector search และ keyword search ร่วมกัน เพื่อให้ได้ context ที่ครบถ้วน

3. Re-ranking

เรียงลำดับ documents ที่ retrieve มาแล้วด้วย cross-encoder เพื่อให้ได้ document ที่เกี่ยวข้องที่สุด

4. Self-Correction Prompting

ให้ LLM ตรวจสอบคำตอบตัวเองก่อนส่งกลับ

ราคาและ ROI

บริการ GPT-4.1 Claude Sonnet 4.5 Gemini 2.5 Flash DeepSeek V3.2
HolySheep AI $8/MTok $15/MTok $2.50/MTok $0.42/MTok
Official API $15/MTok $15/MTok $1.25/MTok $0.27/MTok
ประหยัดได้ 47% 0% -100% 35%
* อัตราแลกเปลี่ยน ¥1=$1 ทำให้ผู้ใช้ไทย/จีนประหยัดได้มาก

ทำไมต้องเลือก HolySheep

ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข

กรณีที่ 1: "Low Attribution Score แม้ว่า Retrieve ได้ Document"

อาการ: ระบบ hallucination detection แจ้งว่า attribution score ต่ำมาก (เช่น 30%) ทั้งที่ retrieve ได้ document ที่เกี่ยวข้อง

สาเหตุ: - Document ที่ retrieve มาอาจไม่ตรงกับคำถามจริงๆ - LLM อาจใช้ข้อมูลจาก prior context แทน retrieved document - Chunk size ใหญ่เกินไปทำให้ข้อมูลสำคัญถูกกระจาย

วิธีแก้ไข:

# แก้ไข: ใช้ smaller chunk + keyword filtering
from rank_bm25 import BM25Okapi
import re

def smart_retrieve(query: str, documents: list, top_k: int = 5) -> list:
    """
    Hybrid retrieval: combine vector similarity + BM25
    """
    
    # Tokenize documents
    tokenized_docs = [re.findall(r'\w+', doc.lower()) for doc in documents]
    
    # BM25 scoring
    bm25 = BM25Okapi(tokenized_docs)
    query_tokens = re.findall(r'\w+', query.lower())
    bm25_scores = bm25.get_scores(query_tokens)
    
    # Combine with vector scores (assuming you have vector_scores)
    # vector_scores = get_vector_similarities(query, documents)
    # combined = 0.5 * normalize(bm25_scores) + 0.5 * vector_scores
    
    # Get top-k by BM25 (quick fix)
    doc_scores = list(zip(documents, bm25_scores))
    sorted_docs = sorted(doc_scores, key=lambda x: x[1], reverse=True)
    
    return [doc for doc, score in sorted_docs[:top_k]]

เพิ่มเติม: ตรวจสอบ keyword overlap

def check_keyword_overlap(question: str, retrieved_doc: str) -> float: """ ตรวจสอบว่าคำสำคัญในคำถามมีอยู่ใน document หรือไม่ """ question_keywords = set(re.findall(r'\w{3,}', question.lower())) doc_keywords = set(re.findall(r'\w{3,}', retrieved_doc.lower())) overlap = len(question_keywords & doc_keywords) total = len(question_keywords) return overlap / total if total > 0 else 0

ถ้า keyword overlap < 0.3 แสดงว่า retrieve ผิด

question = "นโยบายการคืนเงิน" retrieved = "การรับประกันสินค้า 1 ปี" overlap = check_keyword_overlap(question, retrieved) print(f"Keyword overlap: {overlap:.2%}") # จะได้ค่าต่ำมาก

กรณีที่ 2: "Inconsistent Answers for Same Question"

อาการ: ถามคำถามเดียวกันหลายครั้ง ได้คำตอบไม่เหมือนกัน บางครั้ง hallucination บางครั้งไม่

สาเหตุ: - Temperature สูงเกินไป - Context window มีข้อมูลค้างจาก conversation ก่อนหน้า - ไม่ได้ใช้ system prompt ที่ชัดเจน

วิธีแก้ไข:

# แก้ไข: ใช้ deterministic generation
import requests

BASE_URL = "https://api.holysheep.ai/v1"

def consistent_rag_query(question: str, context: str, api_key: str) -> str:
    """
    Query RAG ที่ให้คำตอบสม่ำเสมอ
    """
    
    # System prompt ที่ชัดเจนและเคร่งครัด
    system_prompt = """คุณคือผู้ช่วยที่ตอบคำถามจากเอกสารที่ให้มาเท่านั้น

กฎบังคับ:
1. ตอบจากเอกสารที่ให้มาเท่านั้น ห้ามเดา
2. ถ้าไม่มีข้อมูลในเอกสาร ตอบ: "ข้อมูลนี้ไม่อยู่ในเอกสารที่ให้มา"
3. คำตอบต้องสั้น กระชับ ได้ใจความ

รูปแบบการตอบ:
- เริ่มต้นด้วยสรุปคำตอบหลัก
- ถ้ามีข้อมูลเพิ่มเติม ให้ระบุแหล่งที่มา"""

    response = requests.post(
        f"{BASE_URL}/chat/completions",
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        },
        json={
            "model": "gpt-4.1",
            "messages": [
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"เอกสาร: {context}\n\nคำถาม: {question}"}
            ],
            "temperature": 0.1,  # ต่ำมาก = คำตอบคงที่
            "max_tokens": 500,
            "top_p": 0.9
        }
    )
    
    return response.json()["choices"][0]["message"]["content