ในยุคที่ AI ต้องเข้าใจทั้งข้อความและรูปภาพ การสร้าง Multi-Modal RAG (Retrieval-Augmented Generation) จึงกลายเป็นทักษะที่ Developer ทุกคนควรมี ในบทความนี้ผมจะพาทุกท่านไปสัมผัสประสบการณ์จริงในการสร้าง Knowledge Base ที่รองรับทั้งรูปภาพและเอกสารข้อความ พร้อมวัดผลด้านความหน่วง อัตราความสำเร็จ และต้นทุนการใช้งาน
Multi-Modal RAG คืออะไร และทำไมต้องสนใจ
Multi-Modal RAG คือระบบที่ผสานความสามารถในการค้นหาข้อมูลจากหลายรูปแบบ (รูปภาพ ข้อความ ตาราง) เข้าด้วยกัน ทำให้ AI สามารถตอบคำถามที่ต้องอาศัยข้อมูลจากทั้งสองแหล่งได้อย่างแม่นยำ สมมติว่าคุณมีคู่มือผลิตภัณฑ์ที่มีทั้งรูปภาพประกอบและคำอธิบายเทคนิค Multi-Modal RAG จะช่วยให้ผู้ใช้ถามคำถามเช่น "รูปนี้คือส่วนไหนของเครื่อง" หรือ "ชิ้นส่วนในรูปเข้ากันได้กับรุ่นไหนบ้าง" ได้ทันที
สถาปัตยกรรมระบบ Multi-Modal RAG
ระบบที่ผมพัฒนาขึ้นประกอบด้วย 4 ส่วนหลัก ได้แก่ Vector Store สำหรับเก็บ Embeddings, Image Encoder สำหรับแปลงรูปภาพเป็นเวกเตอร์, Text Splitter สำหรับแบ่งเอกสารข้อความ และ Fusion Retrieval สำหรับรวมผลลัพธ์จากทั้งสองแหล่ง โดยใช้ HolySheep AI สมัครที่นี่ เป็น LLM Backend หลัก ซึ่งให้อัตราแลกเปลี่ยนที่คุ้มค่ามาก: ¥1=$1 ประหยัดสูงสุด 85% เมื่อเทียบกับผู้ให้บริการอื่น รองรับการชำระเงินผ่าน WeChat และ Alipay และมีความหน่วงต่ำกว่า 50 มิลลิวินาที
การติดตั้งและเตรียม Environment
# ติดตั้ง Library ที่จำเป็น
pip install langchain openai tiktoken pillow torch transformers faiss-cpu
pip install python-multipart pydantic
สร้างไฟล์ .env สำหรับเก็บ API Key
cat > .env << 'EOF'
HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY
HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1
EOF
ตรวจสอบการเชื่อมต่อ
python -c "from openai import OpenAI; \
client = OpenAI(api_key='YOUR_HOLYSHEEP_API_KEY', base_url='https://api.holysheep.ai/v1'); \
print(client.models.list())"
โค้ดสำหรับสร้าง Multi-Modal Knowledge Base
import os
import base64
from pathlib import Path
from typing import List, Dict, Tuple
from PIL import Image
import faiss
import numpy as np
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import PyPDFLoader, TextLoader
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS
class MultiModalKnowledgeBase:
"""ระบบ Knowledge Base รองรับทั้งรูปภาพและข้อความ"""
def __init__(self, api_key: str, base_url: str = "https://api.holysheep.ai/v1"):
from openai import OpenAI
self.client = OpenAI(api_key=api_key, base_url=base_url)
self.embeddings = OpenAIEmbeddings(
model="text-embedding-3-large",
openai_api_key=api_key,
openai_api_base=base_url
)
# สร้าง FAISS Index สำหรับ Text และ Image แยกกัน
self.text_index = None
self.image_index = None
self.text_chunks = []
self.image_metadata = []
def encode_image_to_base64(self, image_path: str) -> str:
"""แปลงรูปภาพเป็น Base64 สำหรับส่งไปยัง API"""
with open(image_path, "rb") as img_file:
return base64.b64encode(img_file.read()).decode('utf-8')
def get_image_embedding(self, image_path: str) -> np.ndarray:
"""สร้าง Embedding จากรูปภาพโดยใช้ Vision Model"""
base64_image = self.encode_image_to_base64(image_path)
response = self.client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}},
{"type": "text", "text": "Describe this image in detail for semantic search. Include objects, colors, text, layout, and any technical details."}
]
}],
max_tokens=500
)
# ใช้คำตอบจาก Vision Model สร้าง Text Embedding
description = response.choices[0].message.content
return np.array(self.embeddings.embed_query(description))
def process_documents(self, documents_path: str) -> List:
"""ประมวลผลเอกสารข้อความ PDF และ TXT"""
docs = []
path = Path(documents_path)
for file in path.glob("**/*"):
if file.suffix == '.pdf':
loader = PyPDFLoader(str(file))
docs.extend(loader.load())
elif file.suffix == '.txt':
loader = TextLoader(str(file))
docs.extend(loader.load())
# แบ่งเอกสารเป็น Chunks
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
return text_splitter.split_documents(docs)
def process_images(self, images_path: str) -> List[Dict]:
"""ประมวลผลรูปภาพทั้งหมดในโฟลเดอร์"""
results = []
path = Path(images_path)
for img_file in path.glob("**/*"):
if img_file.suffix.lower() in ['.jpg', '.jpeg', '.png', '.gif', '.webp']:
try:
# ตรวจสอบขนาดรูปก่อนประมวลผล
with Image.open(img_file) as img:
width, height = img.size
print(f"กำลังประมวลผล: {img_file.name} ({width}x{height})")
embedding = self.get_image_embedding(str(img_file))
results.append({
"path": str(img_file),
"filename": img_file.name,
"embedding": embedding,
"size": f"{width}x{height}"
})
except Exception as e:
print(f"ข้อผิดพลาดกับ {img_file.name}: {e}")
return results
def build_indexes(self, text_docs: List, image_data: List[Dict]):
"""สร้าง Vector Index สำหรับ Text และ Image"""
# สร้าง Text Index
if text_docs:
self.text_chunks = text_docs
self.text_index = FAISS.from_documents(text_docs, self.embeddings)
print(f"สร้าง Text Index สำเร็จ: {len(text_docs)} chunks")
# สร้าง Image Index
if image_data:
embeddings_matrix = np.array([item["embedding"] for item in image_data])
dimension = embeddings_matrix.shape[1]
self.image_index = faiss.IndexFlatL2(dimension)
self.image_index.add(embeddings_matrix)
self.image_metadata = [
{"path": item["path"], "filename": item["filename"], "size": item["size"]}
for item in image_data
]
print(f"สร้าง Image Index สำเร็จ: {len(image_data)} images")
def search(self, query: str, top_k: int = 4) -> Dict:
"""ค้นหาข้อมูลจากทั้ง Text และ Image Index"""
results = {"text_results": [], "image_results": [], "combined": []}
# ค้นหาใน Text Index
if self.text_index:
text_results = self.text_index.similarity_search(query, k=top_k)
results["text_results"] = [
{"content": doc.page_content[:200], "source": doc.metadata.get("source", "unknown")}
for doc in text_results
]
# ค้นหาใน Image Index
if self.image_index:
query_embedding = np.array(self.embeddings.embed_query(query)).reshape(1, -1)
distances, indices = self.image_index.search(query_embedding, top_k)
results["image_results"] = [
{
"filename": self.image_metadata[idx]["filename"],
"path": self.image_metadata[idx]["path"],
"size": self.image_metadata[idx]["size"],
"distance": float(distances[0][i])
}
for i, idx in enumerate(indices[0]) if idx < len(self.image_metadata)
]
# รวมผลลัพธ์ (Weighted Fusion)
results["combined"] = self._fusion_results(results["text_results"], results["image_results"])
return results
def _fusion_results(self, text_results: List, image_results: List) -> List:
"""รวมผลลัพธ์จาก Text และ Image ด้วย Weighted Score"""
combined = []
for i, tr in enumerate(text_results):
score = 1.0 / (i + 1) * 0.6 # Text Weight: 60%
combined.append({"type": "text", "score": score, **tr})
for i, ir in enumerate(image_results):
score = 1.0 / (i + 1) * 0.4 # Image Weight: 40%
combined.append({"type": "image", "score": score, **ir})
return sorted(combined, key=lambda x: x["score"], reverse=True)
ตัวอย่างการใช้งาน
def main():
api_key = "YOUR_HOLYSHEEP_API_KEY"
kb = MultiModalKnowledgeBase(api_key)
# ประมวลผลเอกสารและรูปภาพ
print("เริ่มสร้าง Knowledge Base...")
text_docs = kb.process_documents("./documents")
image_data = kb.process_images("./images")
# สร้าง Index
kb.build_indexes(text_docs, image_data)
# ค้นหาข้อมูล
query = "วิธีการติดตั้งชิ้นส่วน A1-B2"
results = kb.search(query)
print(f"\nผลการค้นหาสำหรับ: '{query}'")
print(f"- พบ Text: {len(results['text_results'])} รายการ")
print(f"- พบ Image: {len(results['image_results'])} รายการ")
if __name__ == "__main__":
main()
โค้ด RAG Chain สำหรับตอบคำถาม Multi-Modal
import time
from openai import OpenAI
class MultiModalRAGChain:
"""RAG Chain ที่รวมข้อมูลจากทั้ง Text และ Image"""
def __init__(self, knowledge_base, api_key: str):
self.kb = knowledge_base
self.client = OpenAI(api_key=api_key, base_url="https://api.holysheep.ai/v1")
def generate_response(self, query: str, model: str = "gpt-4o") -> Dict:
"""สร้างคำตอบโดยใช้ RAG กับ Multi-Modal Context"""
start_time = time.time()
# 1. ค้นหาข้อมูลที่เกี่ยวข้อง
retrieved = self.kb.search(query, top_k=4)
# 2. สร้าง Context สำหรับ LLM
context_parts = []
# เพิ่ม Text Context
for tr in retrieved["text_results"][:2]:
context_parts.append(f"[Text from {tr['source']}]:\n{tr['content']}\n")
# เพิ่ม Image Context (แปลงเป็น Base64)
for ir in retrieved["image_results"][:2]:
try:
with open(ir["path"], "rb") as img_file:
img_b64 = base64.b64encode(img_file.read()).decode('utf-8')
context_parts.append(
f"[Image: {ir['filename']} ({ir['size']})]\n"
f"![{ir['filename']}](data:image/jpeg;base64,{img_b64})"
)
except Exception as e:
print(f"ไม่สามารถโหลดรูป {ir['filename']}: {e}")
context = "\n---\n".join(context_parts)
# 3. สร้าง Prompt สำหรับ Multi-Modal RAG
system_prompt = """คุณเป็นผู้ช่วยผู้เชี่ยวชาญ ตอบคำถามโดยอ้างอิงจาก Context ที่ได้รับ
หาก Context มีรูปภาพ ให้อธิบายหรืออ้างอิงถึงรูปภาพนั้นด้วย
ตอบเป็นภาษาไทย ชัดเจน และมีประโยชน์"""
user_prompt = f"""Context:
{context}
คำถาม: {query