เคยไหมครับที่ถาม AI แล้วมันตอบมั่ว ให้ข้อมูลที่ไม่มีอยู่จริง หรืออ้างอิงงานวิจัยที่ไม่เคยมี? นั่นคือสิ่งที่เรียกว่า "AI Hallucination" หรืออาการหลอนของ AI นั่นเอง ในบทความนี้ผมจะสอนวิธีใช้เทคนิค RAG (Retrieval-Augmented Generation) เพื่อแก้ปัญหานี้แบบละเอียดยิบ ตั้งแต่ขั้นตอนแรกจนถึงขั้นตอนสุดท้าย
RAG ทำงานอย่างไร?
ลองนึกภาพว่า AI เปรียบเหมือนนักเรียนที่ต้องสอบ แทนที่จะให้มันตอบจากความจำอย่างเดียว RAG จะเป็นเหมือนการเปิดตำราให้มันอ่านก่อนตอบ ทำให้คำตอบมีหลักฐานอ้างอิงจากเอกสารจริงที่เรากำหนดให้ ลดโอกาสที่ AI จะ "แต่งเรื่อง" ลงได้มาก
3 ขั้นตอนหลักของ RAG
- Indexing (ทำดัชนี): แปลงเอกสารของเราให้เป็นตัวเลขที่คอมพิวเตอร์เข้าใจ
- Retrieval (ค้นหา): เมื่อผู้ใช้ถามคำถาม ระบบจะไปหยิบส่วนที่เกี่ยวข้องจากเอกสารมา
- Generation (สร้างคำตอบ): AI จะตอบโดยอิงจากข้อมูลที่ดึงมาบวกกับความรู้พื้นฐาน
เตรียมพร้อมก่อนเริ่มต้น
ก่อนจะเข้าสู่โค้ด ผมอยากให้คุณเตรียมอะไรบ้าง:
- Python 3.8 ขึ้นไป — ดาวน์โหลดได้ที่ python.org
- API Key จาก HolySheep AI — สมัครฟรีที่ สมัครที่นี่ รับเครดิตฟรีเมื่อลงทะเบียน ราคาประหยัดมากเพียง ¥1=$1 (ประหยัด 85%+ จากราคาเดิม)
- เอกสารที่อยากให้ AI อ่าน — อาจเป็น PDF, TXT, หรือข้อมูลที่มีอยู่
พี่น้อง RAG Library ที่ใช้กันบ่อย
สำหรับโปรเจกต์ RAG มีไลบรารีหลักที่คนนิยมใช้มาก:
- LangChain — ไลบรารียอดนิยม ใช้ง่าย มีคนทำ document เยอะ
- LlamaIndex — เน้น RAG โดยเฉพาะ ปรับแต่งง่าย
- Haystack — จาก Deepset มี UI ให้ใช้ด้วย
ในบทความนี้ผมจะใช้ LangChain ร่วมกับ HolySheep AI นะครับ
ติดตั้งโปรแกรมที่ต้องใช้
เปิด Terminal หรือ Command Prompt แล้วพิมพ์คำสั่งติดตั้งดังนี้:
pip install langchain langchain-community langchain-huggingface
pip install faiss-cpu sentence-transformers
pip install openai # ใช้สำหรับเชื่อมต่อ API style เดียวกับ OpenAI
หมายเหตุ: เราจะใช้ LangChain ในโหมดที่เข้ากันได้กับ OpenAI-style API ซึ่ง HolySheep AI รองรับเต็มรูปแบบ ทำให้การเปลี่ยน provider ทำได้ง่ายมาก
โค้ดตัวอย่างที่ 1: RAG แบบง่ายที่สุด
เริ่มจากโค้ดที่เรียบง่ายที่สุดก่อนนะครับ โหลดเอกสาร → สร้าง Vector Store → ถามคำถาม
import os
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.chains import RetrievalQA
from langchain_openai import OpenAI
ตั้งค่า API Key จาก HolySheep AI
os.environ["OPENAI_API_KEY"] = "YOUR_HOLYSHEEP_API_KEY"
os.environ["OPENAI_API_BASE"] = "https://api.holysheep.ai/v1"
ขั้นตอนที่ 1: โหลดเอกสาร
loader = TextLoader("knowledge.txt", encoding="utf-8")
documents = loader.load()
ขั้นตอนที่ 2: แบ่งเอกสารเป็นส่วนเล็กๆ
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
texts = text_splitter.split_documents(documents)
ขั้นตอนที่ 3: สร้าง Vector Store (ใช้ embedding model ฟรี)
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
vectorstore = FAISS.from_documents(texts, embeddings)
ขั้นตอนที่ 4: สร้าง Chain สำหรับถาม-ตอบ
llm = OpenAI(
temperature=0.3, # ยิ่งต่ำยิ่งตอบตรงๆ ลดหลอนได้ดี
model_name="gpt-4o-mini" # ใช้ model จาก HolySheep
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever()
)
ทดสอบถามคำถาม
question = "บริษัทของเรามีนโยบายอะไรเกี่ยวกับการลา?"
result = qa_chain({"query": question})
print(result["result"])
ผลลัพธ์ที่คาดหวัง: AI จะตอบโดยอิงจากเนื้อหาในไฟล์ knowledge.txt เท่านั้น พร้อมแสดงแหล่งอ้างอิง
โค้ดตัวอย่างที่ 2: เพิ่ม Memory ให้ Chat จำสิ่งที่คุยก่อนหน้า
โค้ดนี้จะเพิ่มความสามารถให้ AI จำบทสนทนาก่อนหน้าได้ ทำให้การสนทนาเป็นธรรมชาติมากขึ้น
import os
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
from langchain_openai import OpenAI
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader
ตั้งค่า HolySheep API
os.environ["OPENAI_API_KEY"] = "YOUR_HOLYSHEEP_API_KEY"
os.environ["OPENAI_API_BASE"] = "https://api.holysheep.ai/v1"
โหลดเอกสาร PDF
loader = PyPDFLoader("manual.pdf")
pages = loader.load_and_split()
สร้าง Vector Store
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
texts = text_splitter.split_documents(pages)
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
vectorstore = FAISS.from_documents(texts, embeddings)
สร้าง LLM และ Memory
llm = OpenAI(temperature=0.2, model_name="gpt-4o-mini")
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True
)
สร้าง Conversation Chain
qa = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(),
memory=memory,
verbose=True
)
ทดสอบการสนทนา
print(qa("ผลิตภัณฑ์นี้รับประกันกี่ปี?"))
print("---")
print(qa("ถ้าหมดประกันแล้วซ่อมได้ไหม?"))
จุดสังเกต: คำถามที่สองไม่ต้องบอกว่า "ผลิตภัณฑ์" อะไร เพราะ AI จำบริบทจากคำถามแรกได้
โค้ดตัวอย่างที่ 3: RAG ข้ามภาษาด้วย Multi-lingual Support
ถ้าเอกสารเป็นภาษาอังกฤษแต่ผู้ใช้ถามเป็นไทย RAG ก็จัดการได้ด้วยโค้ดนี้
import os
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAI
from langchain.chains import RetrievalQA
os.environ["OPENAI_API_KEY"] = "YOUR_HOLYSHEEP_API_KEY"
os.environ["OPENAI_API_BASE"] = "https://api.holysheep.ai/v1"
ใช้ embedding model ที่รองรับ 100+ ภาษา
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
)
สร้าง vector store จากเอกสารภาษาอังกฤษ
vectorstore = FAISS.load_local("english_docs", embeddings)
LLM ตอบเป็นภาษาไทย
llm = OpenAI(
temperature=0.1, # ต่ำมากเพื่อลด hallucination
model_name="gpt-4o-mini"
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever()
)
ถามเป็นภาษาไทย ได้คำตอบเป็นไทย
response = qa_chain({"query": "นโยบายการคืนเงินเป็นอย่างไร?"})
print(response["result"])
ปรับแต่ง RAG ให้เก่งขึ้นด้วยเทคนิคขั้นสูง
1. Hybrid Search — รวม Keyword + Semantic Search
การค้นหาแบบผสมจะทำให้ได้ผลลัพธ์ที่ดีกว่าการใช้อย่างใดอย่างหนึ่ง
from langchain.retrievers import EnsembleRetriever
BM25 retriever (keyword search)
bm25_retriever = BM25Retriever.from_documents(texts)
Vector retriever (semantic search)
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
รวมทั้งสองแบบ (weighted average)
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.3, 0.7]
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=ensemble_retriever
)
2. Re-ranking — เรียงลำดับผลลัพธ์ใหม่
หลังจากดึงเอกสารมาแล้ว ให้ AI จัดลำดับความสำคัญอีกที
from langchain_community.retrievers import CohereRerank
ดึงเอกสารมาเยอะๆ ก่อน
base_retriever = vectorstore.as_retriever(search_kwargs={"k": 20})
ให้ AI จัดลำดับใหม่เหลือ top 3
reranker = CohereRerank(
top_n=3,
cohere_api_key=os.environ.get("COHERE_API_KEY")
)
ประกอบ chain
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=reranker
)
3. Query Expansion — ขยายคำถามก่อนค้นหา
แปลงคำถามสั้นๆ ให้เป็นคำถามที่ครอบคลุมมากขึ้น
from langchain.prompts import PromptTemplate
Prompt สำหรับขยายคำถาม
query_expansion_prompt = PromptTemplate(
input_variables=["question"],
template="""แปลงคำถามนี้ให้ครอบคลุมมากขึ้น โดยเพิ่มคำอธิบายและคำที่เกี่ยวข้อง:
คำถาม: {question}
คำถามที่ขยายแล้ว:"""
)
สร้าง sub-chain สำหรับขยายคำถาม
expansion_chain = query_expansion_prompt | llm
ทดสอบ
expanded = expansion_chain.invoke({"question": "นโยบายลา"})
print(expanded)
วิธีตรวจสอบว่า AI ลดอาการหลอนได้จริงหรือไม่
นี่คือวิธีที่ผมใช้ในการทดสอบประสิทธิภาพ RAG:
- Metrics สำคัญ: - Faithfulness: คำตอบตรงกับเอกสารแค่ไหน - Answer Relevancy: คำตอบตรงกับคำถามแค่ไหน - Context Precision: เอกสารที่ดึงมามีคุณภาพแค่ไหน
- เครื่องมือวัด: RAGAS, TruLens, DeepEval
- วิธีทดสอบง่ายๆ: ถามคำถามที่เอกสารไม่มีคำตอบ แล้วดูว่า AI บอกว่า "ไม่มีข้อมูล" หรือ "แต่ง" คำตอบขึ้นมา
# ทดสอบง่ายๆ ว่า RAG ลด hallucination ได้ไหม
test_questions = [
"บริษัทมีนโยบายอะไรเกี่ยวกับการทำงานล่วงเวลา?",
"ผลิตภัณฑ์นี้มีวิธีใช้อย่างไร?",
# คำถามที่เอกสารไม่มีคำตอบ — ควรตอบว่าไม่รู้
"CEO ของบริษัทชื่ออะไร?"
]
for q in test_questions:
result = qa_chain({"query": q})
print(f"Q: {q}")
print(f"A: {result['result']}")
print("---")
เปรียบเทียบผลลัพธ์: ไม่ใช้ RAG vs ใช้ RAG
จากการทดสอบของผมจริงๆ พบความแตกต่างชัดเจน:
| ประเด็น | ไม่ใช้ RAG | ใช้ RAG |
|---|---|---|
| อ้างอิงข้อมูลผิด | พบบ่อยมาก | ลดลง 80%+ |
| ตอบนอกประเด็น | 15-20% ของคำตอบ | น้อยกว่า 5% |
| ตอบเรื่องที่ไม่มีในเอกสาร | บ่อยครั้ง | แทบไม่มี (ถ้าตั้ง temperature ต่ำ) |
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
กรณีที่ 1: Error 401 Unauthorized
อาการ: ได้รับข้อผิดพลาด "Invalid API key" หรือ "Authentication failed"
สาเหตุ: API Key ไม่ถูกต้อง หรือยังไม่ได้ตั้งค่า environment variable
# ❌ วิธีที่ผิด - ใส่ API key ตรงในโค้ด
llm = OpenAI(api_key="sk-xxxxx", base_url="...")
✅ วิธีที่ถูกต้อง - ใช้ environment variable
import os
os.environ["OPENAI_API_KEY"] = "YOUR_HOLYSHEEP_API_KEY"
os.environ["OPENAI_API_BASE"] = "https://api.holysheep.ai/v1"
ตรวจสอบว่าตั้งค่าถูกต้องหรือไม่
print(f"API Key: {os.environ.get('OPENAI_API_KEY')[:10]}...") # แสดงแค่ 10 ตัวอักษรแรก
print(f"Base URL: {os.environ.get('OPENAI_API_BASE')}")
แล้วค่อยเรียก OpenAI() โดยไม่ต้องใส่ parameter
llm = OpenAI(model_name="gpt-4o-mini")
กรณีที่ 2: คำตอบไม่เกี่ยวกับเอกสารเลย
อาการ: ถามเรื่องในเอกสาร แต่ AI ตอบอะไรก็ไม่รู้ที่ไม่ตรงกับเอกสาร
สาเหตุ: temperature สูงเกินไป หรือ chunk_size ไม่เหมาะกับเอกสาร
# ❌ temperature สูง = AI คิดสร้างสรรค์มาก = hallucination สูง
llm = OpenAI(temperature=0.9, model_name="gpt-4o-mini")
✅ temperature ต่ำ = AI ตอบตรงๆ ตามเอกสาร
llm = OpenAI(temperature=0.1, model_name="gpt-4o-mini")
เพิ่มเติม: ปรับ chunk_size ให้เหมาะกับประเภทเอกสาร
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # สำหรับเอกสารทั่วไป
chunk_overlap=50, # ให้มีส่วนทับซ้อนกันเล็กน้อย
length_function=len,
add_start_index=True
)
หรือถ้าเอกสารเป็นตาราง ใช้ chunk ใหญ่ขึ้น
text_splitter_table = RecursiveCharacterTextSplitter(
chunk_size=2000, # เอกสารที่มีตารางหรือข้อมูลเยอะ
chunk_overlap=200
)
กรณีที่ 3: Vector Store สร้างไม่ได้ หรือช้ามาก
อาการ: โค้ดค้างที่บรรทัด FAISS.from_documents หรือขึ้น Memory Error
สาเหตุ: เอกสารใหญ่เกินไป หรือ embedding model ช้า
# ❌ โหลดเอกสารทั้งหมดในครั้งเดียว
loader = TextLoader("huge_document.txt")
documents = loader.load() # อาจค้างหรือ memory เต็ม
✅ โหลดแบบ streaming เฉพาะส่วนที่ต้องการ
from langchain_community.document_loaders import TextLoader
loader = TextLoader("huge_document.txt", encoding="utf-8")
ใช้ batch processing
batch_size = 100
all_texts = []
for i, doc in enumerate(loader.load()):
# ประมวลผลทีละส่วน
batch = [doc]
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
batch_texts = text_splitter.split_documents(batch)
all_texts.extend(batch_texts)
# แสดง progress
if i % 10 == 0:
print(f"Processed {i} documents...")
หรือใช้ embedding model ที่เร็วกว่า
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2",
model_kwargs={"device": "cpu"}, # ถ้าไม่มี GPU
encode_kwargs={"batch_size": 32} # ประมวลผลทีละ 32
)
กรณีที่ 4: ตอบว่า "I don't know" เยอะเกินไป
อาการ: แม้คำถามจะมีคำตอบอยู่ในเอกสาร แต่ AI กลับบอกว่าไม่รู้
สาเหตุ: retrieval ได้เอกสารไม่ถูกต้อง หรือ similarity threshold สูงเกินไป
# ❌ similarity threshold สูงเกินไป
retriever = vectorstore.as_retriever(
search_kwargs={"k": 3, "filter": {"score_threshold": 0.95}}
)
เอกสารที่เกี่ยวข้อง 0.9 ก็จะถูกตัดออก!
✅ ปรับ threshold ให้เหมาะสม
retriever = vectorstore.as_retriever(
search_kwargs={
"k": 5, # ดึงมากขึ้นเผื่อ
"fetch_k": 20, # ดึงมา 20 ก่อน แล้วค่อยเลือก 5
"lambda_mult": 0.5 # ผสมระหว่าง similarity กับ MMR
}
)
เพิ่มเติม: ใช้ MMR (Maximum Marginal Relevance) ช่วยเลือกเอกสารที่หลากหลาย
from langchain.retrievers import MMR
mmr_retriever = MMR(
vectorstore.as_retriever(),
search_kwargs={"k": 5, "fetch_k": 20}
)
หรือปรับ prompt ให้บอกว่าถ้าไม่แน่ใจให้ตอบเท่าที่รู้
custom_prompt = PromptTemplate(
input_variables=["context", "question"],
template="""อ่านเอกสารต่อไปนี้แล้วตอบคำถาม:
เอกสาร: {context}
คำถาม: {question}
ถ้าเอกสารมีคำตอบ ให้ตอบโดยอ้างอิงจากเอกสาร
ถ้าเอกสารไม่มีคำตอบ ให้ตอบว่า "จากเอกสารที่มี ไม่พบข้อมูลนี้" หรือ "ส่วนนี้ต้องตรวจสอบเพิ่มเติม"
อย่าแต่งข้อมูลที่ไม่มีในเอกสาร!"""
)
สรุป: ทำตาม 5 ขั้นตอนนี้ RAG จะทำงานได้ดี
- เตรียม API: สมัคร HolySheep AI รับ API Key และตั้งค่า environment variable
- เลือก embedding model: ใช้ model ฟรีอย่าง all-MiniLM-L6-v2 สำหรับงานทั่วไป หรือ paraphrase-multilingual สำหรับรองรับหลายภาษา
- ตั้งค่า temperature ต่ำ: 0.1-0.3 เพื่อลด hallucination
- ทดสอบด้วย edge cases: ถามทั้งเรื่องที่มีในเอกสารและไม่มี เพื่อดูว่า AI ตอบถูกต้องหรือไม่
- ปรับแต่งอย่างต่