บทนำ: ทำไมต้องสร้าง Agent แบบ Multi-modal
ในปี 2025 ที่ระบบ AI ก้าวหน้ามากขึ้นเรื่อยๆ ผมได้มีโอกาสพัฒนาระบบดูแลลูกค้าอีคอมเมิร์ซแห่งหนึ่งที่ต้องรองรับการส่งภาพสินค้าเข้ามาวิเคราะห์ และตอบกลับเป็นเสียงพูดแบบเรียลไทม์ ความท้าทายอยู่ที่การทำให้การตอบสนองเป็นแบบ Streaming เพื่อให้ผู้ใช้รู้สึกว่าการสนทนาเป็นธรรมชาติและตอบสนองได้ทันที บทความนี้จะพาคุณไปดูว่าผมใช้ HolySheep AI ในการสร้างระบบ Multi-modal Agent ที่รองรับทั้งการวิเคราะห์ภาพและการตอบกลับด้วยเสียงได้อย่างไร พร้อมโค้ดตัวอย่างที่พร้อมใช้งานจริงMulti-modal Agent คืออะไรและทำงานอย่างไร
Multi-modal Agent คือระบบ AI ที่สามารถประมวลผลข้อมูลหลายรูปแบบพร้อมกัน ไม่ว่าจะเป็นข้อความ ภาพ เสียง หรือวิดีโอ ในกรณีของระบบที่ผมพัฒนา เราต้องการ:- Input ภาพ: รับภาพสินค้าจากลูกค้าแล้ววิเคราะห์ว่าสินค้านั้นคืออะไร สภาพเป็นอย่างไร
- Output เสียง: ตอบกลับลูกค้าด้วยเสียงพูดแบบ Streaming เพื่อให้รู้สึกเป็นธรรมชาติ
- Context ต่อเนื่อง: จำสถานการณ์การสนทนาก่อนหน้าได้
โครงสร้างพื้นฐานของ Multi-modal Streaming Agent
ก่อนจะเข้าสู่โค้ด ผมอยากอธิบายสถาปัตยกรรมที่ใช้งานได้จริง:- ชั้น Vision Processing: รับภาพ → ส่งไปประมวลผลด้วยโมเดลที่รองรับภาพ
- ชั้น LLM Processing: รวมข้อความ + คำอธิบายภาพ → สร้างคำตอบแบบ Streaming
- ชั้น Text-to-Speech: แปลงข้อความ Streaming → เสียงพูดแบบเรียลไทม์
- ชั้น Audio Streaming: ส่งเสียงออกไปทีละส่วนเพื่อให้ผู้ใช้ได้ยินทันที
โค้ดตัวอย่าง: Multi-modal Agent พร้อม Vision และ Text Streaming
ต่อไปนี้คือโค้ดที่ผมใช้งานจริงในโปรเจกต์ โดยใช้ HolySheep AI API ซึ่งมีความเร็วในการตอบสนองน้อยกว่า 50 มิลลิวินาที ทำให้เหมาะมากสำหรับงาน Streaming:import requests
import base64
import json
from typing import Iterator
class MultiModalAgent:
"""Agent ที่รองรับภาพและส่งคำตอบแบบ Streaming"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.holysheep.ai/v1"
def encode_image(self, image_path: str) -> str:
"""แปลงภาพเป็น base64"""
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
def analyze_image_streaming(self, image_path: str, user_question: str) -> Iterator[str]:
"""
วิเคราะห์ภาพและตอบกลับแบบ Streaming
ใช้ GPT-4o ที่รองรับ Vision ในตัว
"""
# เตรียมข้อมูลภาพในรูปแบบที่ API รองรับ
image_base64 = self.encode_image(image_path)
# สร้าง messages ที่มี content หลายประเภท
messages = [
{
"role": "user",
"content": [
{
"type": "text",
"text": f"คุณคือผู้ช่วยดูแลลูกค้าอีคอมเมิร์ซ กรุณาตอบคำถามนี้โดยดูจากภาพที่แนบมา:\n\n{user_question}"
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image_base64}"
}
}
]
}
]
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": "gpt-4o", # โมเดลที่รองรับ Vision
"messages": messages,
"max_tokens": 1000,
"stream": True # เปิดโหมด Streaming
}
# ส่ง request แบบ Streaming
response = requests.post(
f"{self.base_url}/chat/completions",
headers=headers,
json=payload,
stream=True
)
# ประมวลผล Streaming response
for line in response.iter_lines():
if line:
# ข้อมูล Streaming จะมาเป็น "data: {...}"
line_text = line.decode('utf-8')
if line_text.startswith("data: "):
data = json.loads(line_text[6:])
if data.get("choices") and data["choices"][0].get("delta"):
content = data["choices"][0]["delta"].get("content")
if content:
yield content
วิธีใช้งาน
agent = MultiModalAgent(api_key="YOUR_HOLYSHEEP_API_KEY")
print("กำลังวิเคราะห์ภาพสินค้า...")
for chunk in agent.analyze_image_streaming(
image_path="product.jpg",
user_question="สินค้าในภาพนี้คืออะไร และสภาพสินค้าเป็นอย่างไร"
):
print(chunk, end="", flush=True) # แสดงผลทันทีที่ได้รับ
โค้ดตัวอย่าง: รวม Text-to-Speech สำหรับการตอบกลับเสียง
หลังจากได้ข้อความ Streaming แล้ว ขั้นตอนต่อไปคือการแปลงเป็นเสียง ผมใช้ HolySheep TTS API เพื่อสร้างเสียงพูดจากข้อความที่ได้รับ:import requests
import base64
import io
import wave
import threading
from queue import Queue
class StreamingVoiceAgent:
"""Agent ที่ตอบกลับเป็นเสียงแบบ Streaming พร้อมกับข้อความ"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.holysheep.ai/v1"
self.tts_base_url = "https://api.holysheep.ai/v1"
self.text_queue = Queue()
self.audio_buffer = bytearray()
self.is_streaming = False
def text_to_speech_streaming(self, text: str) -> Iterator[bytes]:
"""
แปลงข้อความเป็นเสียงแบบ Streaming
ใช้ TTS API ของ HolySheep ที่มีความเร็วสูง
"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": "tts-1", # โมเดล TTS คุณภาพสูง
"input": text,
"voice": "alloy", # เสียงที่เป็นธรรมชาติ
"response_format": "mp3",
"stream": True
}
response = requests.post(
f"{self.tts_base_url}/audio/speech",
headers=headers,
json=payload,
stream=True
)
# ส่งข้อมูลเสียงทีละส่วน
for chunk in response.iter_content(chunk_size=1024):
if chunk:
yield chunk
def process_llm_stream(self, llm_stream):
"""
ประมวลผล LLM Stream แล้วแปลงเป็นเสียงทันที
นี่คือหัวใจของการทำ Voice Streaming
"""
buffer = ""
self.is_streaming = True
for text_chunk in llm_stream:
buffer += text_chunk
print(text_chunk, end="", flush=True)
# เมื่อมีข้อความสะสมพอ (ประมาณ 10 ตัวอักษร) จะแปลงเป็นเสียง
if len(buffer) >= 10:
# ส่งไป TTS แบบ Streaming
audio_chunks = self.text_to_speech_streaming(buffer)
for audio_chunk in audio_chunks:
# ในที่นี้เราเพียงรวบรวมข้อมูล
# ในการใช้งานจริงจะส่งไป Audio Player ทันที
self.audio_buffer.extend(audio_chunk)
buffer = "" # ล้าง buffer
# ประมวลผลข้อความที่เหลือ
if buffer:
audio_chunks = self.text_to_speech_streaming(buffer)
for audio_chunk in audio_chunks:
self.audio_buffer.extend(audio_chunk)
self.is_streaming = False
def get_audio(self) -> bytes:
"""ดึงข้อมูลเสียงที่รวบรวมได้"""
return bytes(self.audio_buffer)
ตัวอย่างการใช้งาน
import time
voice_agent = StreamingVoiceAgent(api_key="YOUR_HOLYSHEEP_API_KEY")
จำลอง LLM Stream (ในการใช้งานจริงจะใช้โค้ดจากตัวอย่างก่อนหน้า)
def mock_llm_stream():
response = "สวัสดีครับ จากภาพที่คุณส่งมา ผมเห็นว่าเป็นรองเท้าผ้าใบยี่ห้อ Nike รุ่น Air Max สีขาว-แดง สภาพสินค้ายังใหม่มาก ไม่มีรอยขีดข่วน ขนาดที่เห็นในภาพน่าจะเป็นไซส์ 42 ครับ มีราคาปกติ 4,500 บาท ขณะนี้มีโปรโมชันลด 20% เหลือ 3,600 บาทครับ"
# ส่งทีละตัวอักษรเหมือน Streaming จริง
for char in response:
yield char
time.sleep(0.02) # จำลองความเร็วของ API จริง
print("เริ่มประมวลผล...")
start_time = time.time()
voice_agent.process_llm_stream(mock_llm_stream())
elapsed = time.time() - start_time
print(f"\n\nเสร็จสิ้นใน {elapsed:.2f} วินาที")
print(f"ข้อมูลเสียงที่สร้างได้: {len(voice_agent.get_audio())} ไบต์")
โครงสร้างระบบ RAG สำหรับ Multi-modal Agent
ในกรณีที่ต้องการให้ Agent ตอบคำถามเกี่ยวกับสินค้าจากฐานข้อมูลองค์กร ผมแนะนำให้ใช้ร่วมกับระบบ RAG (Retrieval-Augmented Generation) โดยมีโครงสร้างดังนี้:from typing import List, Dict, Any
import requests
import json
class MultiModalRAGAgent:
"""Agent ที่ใช้ RAG ร่วมกับ Vision และ Voice Streaming"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.holysheep.ai/v1"
def retrieve_relevant_context(self, query: str, image_description: str) -> List[Dict]:
"""
ค้นหาข้อมูลที่เกี่ยวข้องจาก Knowledge Base
ในการใช้งานจริงจะเชื่อมต่อกับ Vector Database เช่น Pinecone, Weaviate
"""
# รวม query กับคำอธิบายภาพเพื่อค้นหา
combined_query = f"คำถาม: {query}\nรายละเอียดจากภาพ: {image_description}"
# ในที่นี้จำลองการค้นหา
# ในการใช้งานจริงจะเรียก Vector Search API
mock_contexts = [
{
"source": "product_catalog",
"content": "Nike Air Max 270 - รองเท้าผ้าใบลำลอง ราคา 4,500 บาท มี 5 สี รองรับทุกกิจกรรม",
"relevance_score": 0.92
},
{
"source": "faq",
"content": "นโยบายการคืนสินค้า: สามารถคืนได้ภายใน 30 วัน โดยสินค้าต้องไม่ผ่านการใช้งาน",
"relevance_score": 0.85
},
{
"source": "promotions",
"content": "โปรโมชันเดือนนี้: ลด 20% สำหรับรองเท้าทุกรุ่น สมาชิก VIP ลดเพิ่ม 5%",
"relevance_score": 0.88
}
]
return mock_contexts
def build_rag_prompt(self, question: str, image_description: str, contexts: List[Dict]) -> List[Dict]:
"""สร้าง prompt ที่มี context จาก RAG"""
# รวม context เป็นข้อความเดียว
context_text = "\n\n".join([
f"[{ctx['source']}]: {ctx['content']}"
for ctx in contexts
])
prompt = f"""คุณคือผู้ช่วยดูแลลูกค้าอีคอมเมิร์ซชื่อ "Holy"
ใช้ข้อมูลต่อไปนี้ในการตอบคำถามลูกค้า:
📚 ข้อมูลที่เกี่ยวข้อง:
{context_text}
📷 สิ่งที่เห็นจากภาพ: {image_description}
❓ คำถามลูกค้า: {question}
กรุณาตอบเป็นภาษาไทยอย่างเป็นธรรมชาติ โดยอ้างอิงข้อมูลจากฐานความรู้ด้านบน"""
return [
{"role": "system", "content": "คุณคือผู้ช่วยดูแลลูกค้าที่เป็นมิตรและเชี่ยวชาญ"},
{"role": "user", "content": prompt}
]
def query_with_rag_streaming(self, question: str, image_description: str) -> Iterator[str]:
"""
ค้นหาข้อมูลจาก RAG แล้วตอบแบบ Streaming
"""
# ขั้นตอนที่ 1: ค้นหา context
contexts = self.retrieve_relevant_context(question, image_description)
# ขั้นตอนที่ 2: สร้าง prompt พร้อม context
messages = self.build_rag_prompt(question, image_description, contexts)
# ขั้นตอนที่ 3: ส่งไป LLM แบบ Streaming
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": "gpt-4o",
"messages": messages,
"max_tokens": 1500,
"stream": True
}
response = requests.post(
f"{self.base_url}/chat/completions",
headers=headers,
json=payload,
stream=True
)
for line in response.iter_lines():
if line:
line_text = line.decode('utf-8')
if line_text.startswith("data: "):
data = json.loads(line_text[6:])
if data.get("choices") and data["choices"][0].get("delta"):
content = data["choices"][0]["delta"].get("content")
if content:
yield content
ตัวอย่างการใช้งาน
agent = MultiModalRAGAgent(api_key="YOUR_HOLYSHEEP_API_KEY")
print("=== ทดสอบ Multi-modal RAG Agent ===\n")
for chunk in agent.query_with_rag_streaming(
question="สินค้านี้ราคาเท่าไหร่ และมีส่วนลดไหม",
image_description="รองเท้าผ้าใบ Nike สีขาว-แดง รุ่น Air Max สภาพใหม่"
):
print(chunk, end="", flush=True)
print("\n")
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
ในการพัฒนาระบบนี้ ผมเจอปัญหาหลายอย่างที่อยากแบ่งปันวิธีแก้ไข:1. ภาพใหญ่เกินไปทำให้ API Timeout
อาการ: เมื่อส่งภาพที่มีขนาดใหญ่ (เกิน 5MB) ไปยัง API จะเกิด timeout หรือ error 500สาเหตุ: API มีข้อจำกัดเรื่องขนาด payload และเวลาในการประมวลผล
วิธีแก้:
from PIL import Image
import io
def compress_image_for_api(image_path: str, max_size_kb: int = 500) -> bytes:
"""
บีบอัดภาพให้มีขนาดเหมาะสมก่อนส่งไป API
ใช้ไลบรารี Pillow ช่วยในการประมวลผล
"""
img = Image.open(image_path)
# ถ้าภาพใหญ่เกินไป ให้ลดขนาด
max_dimension = 1024
if max(img.width, img.height) > max_dimension:
ratio = max_dimension / max(img.width, img.height)
new_width = int(img.width * ratio)
new_height = int(img.height * ratio)
img = img.resize((new_width, new_height), Image.LANCZOS)
# บีบอัดเป็น JPEG จนกว่าจะมีขนาดพอดี
quality = 85
output = io.BytesIO()
while quality > 30:
output.seek(0)
output.truncate()
img.save(output, format='JPEG', quality=quality, optimize=True)
size_kb = len(output.getvalue()) / 1024
if size_kb <= max_size_kb:
break
quality -= 10
return output.getvalue()
วิธีใช้
compressed = compress_image_for_api("large_product.jpg")
print(f"ขนาดหลังบีบอัด: {len(compressed)/1024:.1f} KB")
2. Streaming Response ขาดหายหรือไม่ต่อเนื่อง
อาการ: ได้รับข้อความ Streaming แต่บางครั้งข้อความจะขาดหายไป หรือไม่ต่อเนื่องสาเหตุ: การจัดการ buffer ไม่ดี หรือการ parse JSON จาก SSE (Server-Sent Events) ผิดพลาด
วิธีแก้:
import json
import re
def parse_sse_stream(response) -> Iterator[str]:
"""
Parse Server-Sent Events (SSE) อย่างถูกต้อง
รองรับทั้งกรณีที่ข้อมูลมา Complete และ Error
"""
buffer = ""
for chunk in response.iter_content(decode_unicode=True, chunk_size=1):
buffer += chunk
# ตรวจสอบว่ามี complete event หรือไม่
if "[DONE]" in buffer or buffer.endswith("data: [DONE]\n\n"):
break
# ดึงข้อมูล JSON ออกมา
if buffer.startswith("data: "):
try:
# ใช้ regex ดึง JSON object
match = re.search(r'data: (\{.*?\})\n\n', buffer, re.DOTALL)
if match:
json_str = match.group(1)
data = json.loads(json_str)
# ดึง content ออกมา
if data.get("choices"):
delta = data["choices"][0].get("delta", {})
if delta.get("content"):
yield delta["content"]
# ล้าง buffer หลัง parse สำเร็จ
buffer = buffer[match.end():]
except json.JSONDecodeError:
# JSON ยังไม่ครบ ให้รอ chunk ต่อไป
continue
except (KeyError, IndexError):
# โครงสร้าง JSON ไม่ตรงตามที่คาดหวัง
continue
การใช้งานที่ถูกต้อง
response = requests.post(url, headers=headers