ในยุคที่ข้อมูลท่วมท้น การค้นหาเอกสาร PDF ที่มีคำตอบแม่นยำกลายเป็นความท้าทายสำคัญสำหรับนักพัฒนาและองค์กร บทความนี้จะพาคุณสร้างระบบ Retrieval Augmented Generation (RAG) สำหรับ PDF โดยใช้ LangChain ตั้งแต่เริ่มต้นจนไปถึงการ deploy จริง พร้อมแนะนำวิธีประหยัดค่าใช้จ่ายด้วย HolySheep AI ที่ให้อัตรา ¥1=$1 ประหยัดได้มากกว่า 85%
ทำไมต้องใช้ LangChain RAG กับ PDF?
ระบบ RAG ช่วยให้ LLM สามารถตอบคำถามจากเอกสาร PDF ที่ไม่เคยเห็นมาก่อนได้อย่างแม่นยำ แทนที่จะตอบจากความรู้ทั่วไปที่อาจล้าสมัยหรือผิดพลาด วิธีนี้เหมาะสำหรับ:
- องค์กรที่ต้องการสร้าง knowledge base จากเอกสารทางการ
- ระบบถาม-ตอบสำหรับลูกค้าที่ต้องอ้างอิงเอกสารราชการ
- เว็บไซต์ e-commerce ที่ต้องการตอบคำถามเกี่ยวกับคู่มือสินค้า
- นักพัฒนาที่ต้องการสร้าง chatbot สำหรับเอกสารเทคนิค
การติดตั้งสภาพแวดล้อมและไลบรารีที่จำเป็น
ก่อนเริ่มต้น ให้ติดตั้งไลบรารีที่จำเป็นทั้งหมด:
pip install langchain langchain-community langchain-openai pypdf2 python-dotenv faiss-cpu tiktoken
สำหรับโปรเจกต์จริง เราจะใช้ HolySheep AI เป็น LLM provider ซึ่งให้บริการ API ที่ compatible กับ OpenAI ใช้งานง่าย ราคาถูก และ response time น้อยกว่า 50ms
โครงสร้างโปรเจกต์ PDF Question Answering System
pdf-qa-system/
├── config.py
├── pdf_loader.py
├── vector_store.py
├── chain_builder.py
├── main.py
└── requirements.txt
1. การตั้งค่า Configuration
สร้างไฟล์ config.py เพื่อจัดการการตั้งค่าทั้งหมด:
import os
from dotenv import load_dotenv
load_dotenv()
HolySheep AI Configuration
HOLYSHEEP_API_KEY = os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")
HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1" # ห้ามใช้ api.openai.com
Model Selection - ดูราคาเต็มที่ holysheep.ai/pricing
MODEL_CONFIG = {
"gpt-4.1": {"provider": "openai", "price_per_mtok": 8.00},
"claude-sonnet-4.5": {"provider": "anthropic", "price_per_mtok": 15.00},
"gemini-2.5-flash": {"provider": "google", "price_per_mtok": 2.50},
"deepseek-v3.2": {"provider": "deepseek", "price_per_mtok": 0.42},
}
Embedding Configuration
EMBEDDING_MODEL = "text-embedding-3-small"
CHUNK_SIZE = 1000
CHUNK_OVERLAP = 200
Vector Store Configuration
VECTOR_STORE_TYPE = "faiss" # หรือ "chroma", "pinecone"
2. การโหลดและประมวลผล PDF
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from typing import List
from langchain.schema import Document
class PDFDocumentLoader:
"""คลาสสำหรับโหลดและแบ่งส่วนเอกสาร PDF"""
def __init__(self, chunk_size: int = 1000, chunk_overlap: int = 200):
self.text_splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=len,
add_start_index=True,
)
def load_pdf(self, file_path: str) -> List[Document]:
"""โหลดไฟล์ PDF และส่งคืน documents"""
loader = PyPDFLoader(file_path)
documents = loader.load()
print(f"โหลดสำเร็จ {len(documents)} หน้า จาก {file_path}")
return documents
def split_documents(self, documents: List[Document]) -> List[Document]:
"""แบ่งเอกสารออกเป็น chunks ที่เหมาะสม"""
chunks = self.text_splitter.split_documents(documents)
print(f"แบ่งเอกสารเป็น {len(chunks)} ชิ้นส่วน (chunks)")
return chunks
def process_pdf(self, file_path: str) -> List[Document]:
"""โหลดและแบ่ง PDF ในขั้นตอนเดียว"""
documents = self.load_pdf(file_path)
return self.split_documents(documents)
ตัวอย่างการใช้งาน
if __name__ == "__main__":
loader = PDFDocumentLoader(chunk_size=500, chunk_overlap=50)
chunks = loader.process_pdf("sample_document.pdf")
print(f"ผลลัพธ์: {len(chunks)} chunks พร้อมใช้งาน")
3. การสร้าง Vector Store ด้วย FAISS
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.schema import Document
from typing import List, Optional
import pickle
import os
class VectorStoreManager:
"""จัดการการสร้างและค้นหา vector store"""
def __init__(
self,
api_key: str,
base_url: str,
embedding_model: str = "text-embedding-3-small"
):
self.embeddings = OpenAIEmbeddings(
api_key=api_key,
base_url=base_url, # ใช้ HolySheep API
model=embedding_model
)
self.vector_store = None
def create_vector_store(
self,
documents: List[Document],
save_path: Optional[str] = None
) -> FAISS:
"""สร้าง vector store จาก documents"""
self.vector_store = FAISS.from_documents(
documents=documents,
embedding=self.embeddings
)
if save_path:
self.vector_store.save_local(save_path)
print(f"บันทึก vector store ไปที่ {save_path}")
return self.vector_store
def load_vector_store(self, load_path: str) -> FAISS:
"""โหลด vector store ที่มีอยู่"""
self.vector_store = FAISS.load_local(
load_path,
self.embeddings,
allow_dangerous_deserialization=True
)
print(f"โหลด vector store จาก {load_path}")
return self.vector_store
def similarity_search(
self,
query: str,
k: int = 4,
filter_dict: Optional[dict] = None
) -> List[Document]:
"""ค้นหาเอกสารที่เกี่ยวข้องกับ query"""
if not self.vector_store:
raise ValueError("Vector store ยังไม่ได้ถูกสร้างหรือโหลด")
results = self.vector_store.similarity_search(
query=query,
k=k,
filter=filter_dict
)
return results
def similarity_search_with_score(
self,
query: str,
k: int = 4
) -> List[tuple]:
"""ค้นหาพร้อมคะแนนความเหมือน"""
if not self.vector_store:
raise ValueError("Vector store ยังไม่ได้ถูกสร้างหรือโหลด")
return self.vector_store.similarity_search_with_score(query, k=k)
ตัวอย่างการใช้งาน
if __name__ == "__main__":
from config import HOLYSHEEP_API_KEY, HOLYSHEEP_BASE_URL
manager = VectorStoreManager(
api_key=HOLYSHEEP_API_KEY,
base_url=HOLYSHEEP_BASE_URL
)
# ค้นหาเอกสารที่เกี่ยวข้อง
results = manager.similarity_search("วิธีการคืนสินค้า", k=5)
for i, doc in enumerate(results):
print(f"{i+1}. {doc.page_content[:100]}...")
4. การสร้าง RAG Chain ด้วย LangChain
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from langchain.schema import BaseRetriever
from typing import Optional, List
class RAGChainBuilder:
"""สร้างและจัดการ RAG chain"""
def __init__(
self,
api_key: str,
base_url: str,
model_name: str = "gpt-4.1"
):
# ใช้ HolySheep API - compatible กับ OpenAI SDK
self.llm = ChatOpenAI(
api_key=api_key,
base_url=base_url,
model=model_name,
temperature=0.3,
streaming=False
)
self.chain = None
def build_qa_chain(
self,
retriever: BaseRetriever,
return_source_documents: bool = True,
system_prompt: Optional[str] = None
) -> RetrievalQA:
"""สร้าง QA chain จาก retriever"""
if system_prompt is None:
system_prompt = """คุณเป็นผู้ช่วยที่ตอบคำถามจากเอกสาร PDF
โดยอ้างอิงข้อมูลจาก context ที่ให้มาเท่านั้น
ถ้าไม่มีข้อมูลใน context ให้ตอบว่า "ไม่พบข้อมูลที่เกี่ยวข้องในเอกสาร"
ห้ามแต่งข้อมูลหรือตอบจากความรู้ทั่วไป"""
prompt = PromptTemplate(
template=f"""{{system_prompt}}
Context: {{context}}
Question: {{question}}
Answer:""",
input_variables=["context", "question"],
partial_variables={"system_prompt": system_prompt}
)
self.chain = RetrievalQA.from_chain_type(
llm=self.llm,
chain_type="stuff", # หรือ "map_reduce", "refine"
retriever=retriever,
return_source_documents=return_source_documents,
chain_type_kwargs={"prompt": prompt}
)
return self.chain
def ask(self, question: str) -> dict:
"""ถามคำถามและรับคำตอบ"""
if not self.chain:
raise ValueError("Chain ยังไม่ได้ถูกสร้าง")
result = self.chain.invoke({"query": question})
return result
การใช้งาน
if __name__ == "__main__":
from config import HOLYSHEEP_API_KEY, HOLYSHEEP_BASE_URL
builder = RAGChainBuilder(
api_key=HOLYSHEEP_API_KEY,
base_url=HOLYSHEEP_BASE_URL,
model_name="deepseek-v3.2" # เลือกโมเดลที่เหมาะสม
)
# สมมติว่ามี retriever พร้อมใช้งาน
# chain = builder.build_qa_chain(retriever=my_retriever)
# answer = builder.ask("นโยบายการคืนสินค้าคืออะไร?")
# print(answer)
5. การรวมทุกอย่างเข้าด้วยกัน - ไฟล์ Main
import sys
from pathlib import Path
เพิ่ม path สำหรับ import
sys.path.insert(0, str(Path(__file__).parent))
from config import (
HOLYSHEEP_API_KEY,
HOLYSHEEP_BASE_URL,
EMBEDDING_MODEL,
CHUNK_SIZE,
CHUNK_OVERLAP
)
from pdf_loader import PDFDocumentLoader
from vector_store import VectorStoreManager
from chain_builder import RAGChainBuilder
def main():
"""ตัวอย่างการใช้งานระบบ PDF QA"""
# 1. โหลดและประมวลผล PDF
print("=" * 50)
print("ขั้นตอนที่ 1: โหลดและประมวลผล PDF")
print("=" * 50)
pdf_path = "sample_document.pdf"
loader = PDFDocumentLoader(
chunk_size=CHUNK_SIZE,
chunk_overlap=CHUNK_OVERLAP
)
chunks = loader.process_pdf(pdf_path)
# 2. สร้าง Vector Store
print("\n" + "=" * 50)
print("ขั้นตอนที่ 2: สร้าง Vector Store")
print("=" * 50)
vector_manager = VectorStoreManager(
api_key=HOLYSHEEP_API_KEY,
base_url=HOLYSHEEP_BASE_URL,
embedding_model=EMBEDDING_MODEL
)
vector_store = vector_manager.create_vector_store(
documents=chunks,
save_path="vector_store_faiss"
)
# 3. สร้าง RAG Chain
print("\n" + "=" * 50)
print("ขั้นตอนที่ 3: สร้าง RAG Chain")
print("=" * 50)
retriever = vector_store.as_retriever(
search_kwargs={"k": 4}
)
chain_builder = RAGChainBuilder(
api_key=HOLYSHEEP_API_KEY,
base_url=HOLYSHEEP_BASE_URL,
model_name="deepseek-v3.2" # โมเดลราคาถูก เหมาะสำหรับ RAG
)
chain_builder.build_qa_chain(retriever=retriever)
# 4. ทดสอบถาม-ตอบ
print("\n" + "=" * 50)
print("ขั้นตอนที่ 4: ทดสอบระบบ")
print("=" * 50)
questions = [
"สรุปเนื้อหาหลักของเอกสารนี้",
"มีข้อมูลเกี่ยวกับเรื่องอะไรบ้าง?",
]
for question in questions:
print(f"\nคำถาม: {question}")
result = chain_builder.ask(question)
print(f"คำตอบ: {result['result']}")
print(f"แหล่งอ้างอิง: {len(result['source_documents'])} ชิ้น")
if __name__ == "__main__":
main()
การเลือกโมเดลที่เหมาะสมสำหรับ RAG
การเลือกโมเดลที่เหมาะสมส่งผลต่อทั้งคุณภาพคำตอบและค่าใช้จ่าย โดยเฉพาะอย่างยิ่งในงาน RAG ที่ต้องประมวลผลเอกสารจำนวนมาก:
| โมเดล | ราคา ($/MTok) | ความเร็ว | เหมาะกับงาน | ข้อจำกัด |
|---|---|---|---|---|
| DeepSeek V3.2 | $0.42 | เร็วมาก | RAG ทั่วไป, งานที่ต้องการประหยัด | อาจตอบคำถามซับซ้อนได้ไม่ดีเท่า |
| Gemini 2.5 Flash | $2.50 | เร็ว | RAG สำหรับ e-commerce, ลูกค้าสัมพันธ์ | ต้องการ context ที่ดี |
| GPT-4.1 | $8.00 | ปานกลาง | งานที่ต้องการความแม่นยำสูง | ราคาสูงกว่า 19 เท่าของ DeepSeek |
| Claude Sonnet 4.5 | $15.00 | ปานกลาง | งานวิเคราะห์เอกสารซับซ้อน | ราคาสูงที่สุด |
คำแนะนำ: สำหรับระบบ RAG ทั่วไป แนะนำให้เริ่มต้นด้วย DeepSeek V3.2 ที่ราคาเพียง $0.42/MTok แล้วค่อยปรับเปลี่ยนเป็นโมเดลที่แพงกว่าหากคุณภาพไม่เพียงพอ
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
1. Error: "Authentication Error" หรือ "Invalid API Key"
สาเหตุ: API key ไม่ถูกต้องหรือไม่ได้กำหนดค่าในไฟล์ .env
# วิธีแก้ไข: ตรวจสอบไฟล์ .env
สร้างไฟล์ .env ใน root directory
ห้ามใช้ api.openai.com - ต้องใช้ HolySheep API
.env file
HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY
ห้ามใช้ OPENAI_API_KEY สำหรับ HolySheep
แนะนำสร้าง API key ที่ https://www.holysheep.ai/register
2. Error: "Connection timeout" หรือ "Request timeout"
สาเหตุ: Network issue หรือ API endpoint ไม่ถูกต้อง
# วิธีแก้ไข: ตรวจสอบ base_url และเพิ่ม timeout
from langchain_openai import ChatOpenAI
import requests
ตรวจสอบว่า base_url ถูกต้อง - ต้องเป็น holysheep.ai
client = ChatOpenAI(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1", # ห้ามใช้ api.openai.com
timeout=60.0, # เพิ่ม timeout สำหรับเอกสารขนาดใหญ่
max_retries=3 # ลองใหม่หากล้มเหลว
)
ทดสอบเชื่อมต่อ
try:
response = requests.get(
"https://api.holysheep.ai/v1/models",
headers={"Authorization": f"Bearer YOUR_HOLYSHEEP_API_KEY"}
)
print(f"สถานะ: {response.status_code}")
except requests.exceptions.Timeout:
print("Connection timeout - ลองใช้ VPN หรือตรวจสอบ network")
3. คำตอบไม่ตรงประเด็นหรือหาไม่เจอ (Retrieval Failure)
สาเหตุ: Chunk size ไม่เหมาะสม, query ไม่ตรงกับ semantic ของเอกสาร, หรือ top-k ต่ำเกินไป
# วิธีแก้ไข: ปรับ parameters หลายตัว
1. เพิ่ม chunk overlap และลด chunk size
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # ลดลงเพื่อให้ context กระชับขึ้น
chunk_overlap=100, # เพิ่ม overlap เพื่อไม่ให้ตัดคำ
)
2. เพิ่ม k ในการค้นหา
retriever = vector_store.as_retriever(
search_kwargs={
"k": 8, # เพิ่มจาก 4 เป็น 8 เพื่อให้มีตัวเลือกมากขึ้น
"fetch_k": 20 # ดึงมาก่อน 20 ชิ้น แล้วค่อย filter
}
)
3. ใช้ MMR (Maximum Marginal Relevance) เพื่อหลีกเลี่ยง context ซ้ำ
retriever = vector_store.as_retriever(
search_type="mmr", # ใช้ MMR แทน similarity
search_kwargs={
"k": 4,
"fetch_k": 20,
"lambda_mult": 0.7 # ควบคุม diversity vs relevance
}
)
4. Memory Error เมื่อประมวลผล PDF ขนาดใหญ่
สาเหตุ: เอกสารมีขนาดใหญ่เกินไปจน RAM ไม่เพียงพอ
# วิธีแก้ไข: ประมวลผลเป็น batch
from langchain_community.document_loaders import PyPDFLoader
from langchain.schema import Document
def process_large_pdf_in_batches(
file_path: str,
batch_size: int = 10,
chunk_size: int = 500
) -> list:
"""ประมวลผล PDF ขนาดใหญ่เป็น batch"""
loader = PyPDFLoader(file_path)
all_chunks = []
# แบ่งประมวลผลทีละ batch ของหน้า
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=50,
)
# ใช้ lazy loading แทนการโหลดทั้งหมด
for i, page in enumerate(loader.lazy_load()):
if i % batch_size == 0: