เมื่อวันที่ 15 มีนาคม 2024 เวลาประมาณ 14:32 น. ระบบ API ที่ผมใช้งานอยู่เกิดปัญหา ConnectionError: timeout ส่งผลให้การประมวลผล AI ทั้งระบบหยุดชะงัก สถานการณ์นี้ทำให้ผมตระหนักว่าการมี แผนสำรองข้อมูล สำหรับ AI API เป็นสิ่งจำเป็นอย่างยิ่ง ในบทความนี้ ผมจะแบ่งปันประสบการณ์ตรงและโค้ดที่ใช้งานได้จริงในการสร้าง ระบบ Failover อัตโนมัติ สำหรับ AI API
ทำไมต้องมีแผนสำรองสำหรับ AI API?
ในปัจจุบัน ระบบ AI API เช่น HolySheep AI มีความเสถียรสูง แต่ทุกระบบย่อมมีโอกาสเกิดข้อผิดพลาด ปัจจัยที่ทำให้ระบบหยุดทำงานมีหลายประการ ได้แก่ ปัญหาเครือข่าย (NetworkError: Connection refused) การหมดเวลาการเชื่อมต่อ (TimeoutError: timed out) การหมดอายุโทเค็น (401 Unauthorized) และปัญหา Rate Limit (429 Too Many Requests) การมี ระบบสำรองอัตโนมัติ จะช่วยให้แอปพลิเคชันของคุณทำงานต่อเนื่องได้แม้ API หลักเกิดปัญหา
โครงสร้างพื้นฐานของระบบ Failover
ระบบ Failover ที่ดีต้องมีองค์ประกอบหลัก 3 ส่วน คือ Health Check ที่ตรวจสอบสถานะของ API อย่างต่อเนื่อง Automatic Switch ที่สลับไปใช้ API สำรองเมื่อ API หลักเกิดปัญหา และ Logging & Alerting ที่บันทึกข้อผิดพลาดและแจ้งเตือนผู้ดูแลระบบ โค้ดต่อไปนี้เป็นตัวอย่างระบบ Failover ที่ใช้งานได้จริง
"""
AI API Failover System - ระบบสำรอง API อัตโนมัติ
สร้างเมื่อ: 2024-03-15
เวอร์ชัน: 2.0.0
"""
import time
import logging
from typing import Optional, Dict, Any, List
from dataclasses import dataclass, field
from enum import Enum
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class APIStatus(Enum):
"""สถานะของ API"""
HEALTHY = "healthy"
DEGRADED = "degraded"
DOWN = "down"
UNKNOWN = "unknown"
@dataclass
class APIConfig:
"""การตั้งค่าสำหรับ API endpoint"""
name: str
base_url: str
api_key: str
timeout: float = 30.0
max_retries: int = 3
weight: int = 1 # น้ำหนักสำหรับการจัดสรรงาน
@dataclass
class APIHealth:
"""ข้อมูลสุขภาพของ API"""
status: APIStatus = APIStatus.UNKNOWN
latency_ms: float = 0.0
last_check: float = 0.0
consecutive_failures: int = 0
total_requests: int = 0
failed_requests: int = 0
class AIFailoverManager:
"""ตัวจัดการระบบ Failover สำหรับ AI API"""
def __init__(self, health_check_interval: int = 30):
self.apis: Dict[str, APIConfig] = {}
self.health: Dict[str, APIHealth] = {}
self.health_check_interval = health_check_interval
self.current_primary: Optional[str] = None
self.session = self._create_session()
def _create_session(self) -> requests.Session:
"""สร้าง requests session พร้อม retry strategy"""
session = requests.Session()
retry_strategy = Retry(
total=0, # เราจะจัดการ retry เอง
backoff_factor=0,
status_forcelist=[],
allowed_methods=[]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
def add_api(self, config: APIConfig) -> None:
"""เพิ่ม API ใหม่เข้าสู่ระบบ"""
self.apis[config.name] = config
self.health[config.name] = APIHealth()
logger.info(f"เพิ่ม API: {config.name} ({config.base_url})")
def check_api_health(self, api_name: str) -> APIHealth:
"""ตรวจสอบสุขภาพของ API"""
if api_name not in self.apis:
logger.warning(f"ไม่พบ API: {api_name}")
return APIHealth(status=APIStatus.UNKNOWN)
api = self.apis[api_name]
health = self.health[api_name]
try:
start_time = time.time()
response = self.session.get(
f"{api.base_url}/models",
headers={"Authorization": f"Bearer {api.api_key}"},
timeout=api.timeout
)
health.latency_ms = (time.time() - start_time) * 1000
if response.status_code == 200:
health.status = APIStatus.HEALTHY
health.consecutive_failures = 0
logger.info(f"{api_name}: สถานะปกติ (latency: {health.latency_ms:.2f}ms)")
elif response.status_code == 401:
health.status = APIStatus.DOWN
health.consecutive_failures += 1
logger.error(f"{api_name}: 401 Unauthorized - API key ไม่ถูกต้อง")
elif response.status_code == 429:
health.status = APIStatus.DEGRADED
logger.warning(f"{api_name}: Rate limit exceeded")
else:
health.status = APIStatus.DEGRADED
health.consecutive_failures += 1
logger.warning(f"{api_name}: สถานะ degraded (HTTP {response.status_code})")
except requests.exceptions.Timeout:
health.status = APIStatus.DOWN
health.consecutive_failures += 1
health.latency_ms = api.timeout * 1000
logger.error(f"{api_name}: Connection timeout ({api.timeout}s)")
except requests.exceptions.ConnectionError as e:
health.status = APIStatus.DOWN
health.consecutive_failures += 1
logger.error(f"{api_name}: Connection refused - {str(e)}")
except Exception as e:
health.status = APIStatus.UNKNOWN
health.consecutive_failures += 1
logger.error(f"{api_name}: ข้อผิดพลาดไม่ทราบสาเหตุ - {str(e)}")
health.last_check = time.time()
return health
def get_available_api(self) -> Optional[str]:
"""ดึง API ที่พร้อมใช้งาน"""
for name, health in self.health.items():
if health.status == APIStatus.HEALTHY:
return name
# ถ้าไม่มี API ที่ healthy ให้ลองใช้ degraded
for name, health in self.health.items():
if health.status == APIStatus.DEGRADED:
return name
return None
def call_api(self, prompt: str, model: str = "gpt-4") -> Dict[str, Any]:
"""เรียกใช้ AI API พร้อมระบบ Failover"""
available_api = self.get_available_api()
if not available_api:
return {
"success": False,
"error": "ไม่มี API ที่พร้อมใช้งาน",
"code": "NO_API_AVAILABLE"
}
api = self.apis[available_api]
health = self.health[available_api]
health.total_requests += 1
try:
response = self.session.post(
f"{api.base_url}/chat/completions",
headers={
"Authorization": f"Bearer {api.api_key}",
"Content-Type": "application/json"
},
json={
"model": model,
"messages": [{"role": "user", "content": prompt}]
},
timeout=api.timeout
)
if response.status_code == 200:
return {
"success": True,
"data": response.json(),
"api_used": available_api,
"latency_ms": health.latency_ms
}
elif response.status_code == 401:
health.failed_requests += 1
health.status = APIStatus.DOWN
logger.error(f"API {available_api} ได้รับ 401 - ตัดออกจากระบบ")
# ลองเรียก API อื่น
return self.call_api(prompt, model)
else:
return {
"success": False,
"error": response.text,
"code": response.status_code,
"api_used": available_api
}
except Exception as e:
health.failed_requests += 1
logger.error(f"เรียก API {available_api} ล้มเหลว: {str(e)}")
return self.call_api(prompt, model)
การใช้งาน
if __name__ == "__main__":
manager = AIFailoverManager()
# เพิ่ม HolySheep API เป็น API หลัก
manager.add_api(APIConfig(
name="holysheep",
base_url="https://api.holysheep.ai/v1",
api_key="YOUR_HOLYSHEEP_API_KEY",
timeout=30.0,
weight=3
))
# เพิ่ม API สำรองอื่นๆ
manager.add_api(APIConfig(
name="backup-api",
base_url="https://api.backup.example.com/v1",
api_key="BACKUP_API_KEY",
timeout=30.0,
weight=1
))
# ตรวจสอบสุขภาพทั้งหมด
for api_name in manager.apis.keys():
manager.check_api_health(api_name)
# เรียกใช้งาน
result = manager.call_api("ทดสอบระบบ Failover")
print(result)
ระบบ Health Check และ Auto-Switch
การตรวจสอบสุขภาพของ API อย่างสม่ำเสมอเป็นหัวใจสำคัญของระบบ Failover ในโค้ดต่อไปนี้ ผมจะสร้าง Health Monitor ที่ทำงานเป็นเธรดแยกต่างหาก พร้อมระบบแจ้งเตือนเมื่อ API หลักเกิดปัญหา ระบบนี้ใช้งานได้กับ HolySheep AI ซึ่งมีความเสถียรสูงและมี latency ต่ำกว่า 50ms ทำให้เหมาะสำหรับใช้เป็น API หลัก
"""
Health Monitor และ Auto-Switch System
ระบบตรวจสอบสุขภาพและสลับ API อัตโนมัติ
"""
import threading
import time
import queue
from datetime import datetime
from typing import Callable, Optional
from dataclasses import dataclass, asdict
import json
@dataclass
class AlertMessage:
"""ข้อความแจ้งเตือน"""
timestamp: str
severity: str # info, warning, error, critical
api_name: str
message: str
details: dict
class HealthMonitor:
"""ระบบตรวจสอบสุขภาพ API แบบ Real-time"""
def __init__(
self,
check_interval: int = 30,
failure_threshold: int = 3,
recovery_threshold: int = 2
):
self.check_interval = check_interval
self.failure_threshold = failure_threshold
self.recovery_threshold = recovery_threshold
self.alert_queue = queue.Queue()
self.is_running = False
self.monitor_thread: Optional[threading.Thread] = None
self.api_manager = None
self.callbacks: list = []
def set_api_manager(self, manager) -> None:
"""ตั้งค่า API Manager"""
self.api_manager = manager
def register_alert_callback(self, callback: Callable[[AlertMessage], None]) -> None:
"""ลงทะเบียน callback สำหรับแจ้งเตือน"""
self.callbacks.append(callback)
def _send_alert(self, api_name: str, severity: str, message: str, details: dict = None) -> None:
"""ส่งข้อความแจ้งเตือน"""
alert = AlertMessage(
timestamp=datetime.now().isoformat(),
severity=severity,
api_name=api_name,
message=message,
details=details or {}
)
self.alert_queue.put(alert)
for callback in self.callbacks:
try:
callback(alert)
except Exception as e:
print(f"Alert callback error: {e}")
def _monitor_loop(self) -> None:
"""ลูปหลักสำหรับตรวจสอบสุขภาพ"""
while self.is_running:
if self.api_manager is None:
time.sleep(self.check_interval)
continue
for api_name in self.api_manager.apis.keys():
health = self.api_manager.check_api_health(api_name)
# ตรวจสอบว่า API ล่ม
if health.consecutive_failures >= self.failure_threshold:
if health.status.value != "down":
self._send_alert(
api_name=api_name,
severity="critical",
message=f"API {api_name} หยุดทำงานหลังจาก {health.consecutive_failures} ครั้ง",
details={
"latency_ms": health.latency_ms,
"failure_count": health.consecutive_failures,
"total_requests": health.total_requests,
"failed_requests": health.failed_requests
}
)
# ตรวจสอบว่า API กลับมาทำงาน
elif health.consecutive_failures <= self.recovery_threshold and health.status.value == "healthy":
self._send_alert(
api_name=api_name,
severity="info",
message=f"API {api_name} กลับมาทำงานปกติ",
details={
"latency_ms": health.latency_ms,
"recovery_time": health.last_check
}
)
# ตรวจสอบว่า API ช้าลง
if health.latency_ms > 5000: # เกิน 5 วินาที
self._send_alert(
api_name=api_name,
severity="warning",
message=f"API {api_name} ตอบสนองช้า ({health.latency_ms:.0f}ms)",
details={"latency_ms": health.latency_ms}
)
time.sleep(self.check_interval)
def start(self) -> None:
"""เริ่มระบบตรวจสอบ"""
if self.is_running:
return
self.is_running = True
self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)
self.monitor_thread.start()
print(f"Health Monitor เริ่มทำงาน (ตรวจสอบทุก {self.check_interval} วินาที)")
def stop(self) -> None:
"""หยุดระบบตรวจสอบ"""
self.is_running = False
if self.monitor_thread:
self.monitor_thread.join(timeout=5)
print("Health Monitor หยุดทำงาน")
class TelegramAlertHandler:
"""ตัวส่งแจ้งเตือนผ่าน Telegram"""
def __init__(self, bot_token: str, chat_id: str):
self.bot_token = bot_token
self.chat_id = chat_id
self.api_url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
def send_alert(self, alert: AlertMessage) -> bool:
"""ส่งข้อความแจ้งเตือน"""
emoji = {
"info": "ℹ️",
"warning": "⚠️",
"error": "❌",
"critical": "🚨"
}
message = f"""
{emoji.get(alert.severity, "📢")} *AI API Alert*
*API:* {alert.api_name}
*ความรุนแรง:* {alert.severity.upper()}
*เวลา:* {alert.timestamp}
*ข้อความ:* {alert.message}
"""
if alert.details:
message += f"\n*รายละเอียด:*\n``json\n{json.dumps(alert.details, indent=2, default=str)}\n``"
try:
import requests
response = requests.post(
self.api_url,
json={
"chat_id": self.chat_id,
"text": message,
"parse_mode": "Markdown"
},
timeout=10
)
return response.status_code == 200
except Exception as e:
print(f"Telegram send failed: {e}")
return False
class SlackAlertHandler:
"""ตัวส่งแจ้งเตือนผ่าน Slack"""
def __init__(self, webhook_url: str):
self.webhook_url = webhook_url
def send_alert(self, alert: AlertMessage) -> bool:
"""ส่งข้อความแจ้งเตือนไป Slack"""
color = {
"info": "#36a64f",
"warning": "#ff9900",
"error": "#ff0000",
"critical": "#aa0000"
}
payload = {
"attachments": [{
"color": color.get(alert.severity, "#cccccc"),
"fields": [
{"title": "API", "value": alert.api_name, "short": True},
{"title": "Severity", "value": alert.severity.upper(), "short": True},
{"title": "Message", "value": alert.message, "short": False}
],
"footer": "AI Failover System",
"ts": datetime.now().timestamp()
}]
}
try:
import requests
response = requests.post(
self.webhook_url,
json=payload,
timeout=10
)
return response.status_code == 200
except Exception as e:
print(f"Slack send failed: {e}")
return False
การใช้งาน
if __name__ == "__main__":
from failover import AIFailoverManager, APIConfig
# สร้างระบบ Failover
manager = AIFailoverManager()
manager.add_api(APIConfig(
name="holysheep-primary",
base_url="https://api.holysheep.ai/v1",
api_key="YOUR_HOLYSHEEP_API_KEY",
timeout=30.0
))
# สร้าง Health Monitor
monitor = HealthMonitor(
check_interval=30,
failure_threshold=3,
recovery_threshold=2
)
monitor.set_api_manager(manager)
# เพิ่ม Telegram Alert
# telegram_handler = TelegramAlertHandler("BOT_TOKEN", "CHAT_ID")
# monitor.register_alert_callback(telegram_handler.send_alert)
# เพิ่ม Slack Alert
# slack_handler = SlackAlertHandler("https://hooks.slack.com/...")
# monitor.register_alert_callback(slack_handler.send_alert)
# เริ่มตรวจสอบ
monitor.start()
# ทดสอบการแจ้งเตือน
monitor._send_alert(
api_name="holysheep-primary",
severity="info",
message="ระบบเริ่มทำงาน",
details={"version": "2.0.0"}
)
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
จากประสบการณ์ในการใช้งาน AI API มาหลายปี ผมพบข้อผิดพลาดที่เกิดขึ้นบ่อยครั้งและมีวิธีแก้ไขที่ชัดเจน ต่อไปนี้คือ 3 กรณีหลัก ที่ควรทราบ
กรณีที่ 1: ConnectionError: Connection refused
ข้อผิดพลาดนี้เกิดขึ้นเมื่อไม่สามารถเชื่อมต่อกับ API endpoint ได้ สาเหตุหลักมักมาจาก URL ผิดพลาด หรือเซิร์ฟเวอร์ปิดให้บริการชั่วคราว วิธีแก้ไขคือตรวจสอบ base_url ให้ถูกต้อง และใช้ระบบ Failover เพื่อสลับไปใช้ API สำรอง ดังตัวอย่างโค้ดต่อไปนี้
"""
กรณีที่ 1: ConnectionError: Connection refused
สาเหตุ: ไม่สามารถเชื่อมต่อ API endpoint ได้
วิธีแก้ไข: ตรวจสอบ URL และใช้ Fallback
"""
import requests
from requests.exceptions import ConnectionError, Timeout, HTTPError
class RobustAPIClient:
"""Client ที่มีระบบ Fallback หลายชั้น"""
def __init__(self):
self.primary_url = "https://api.holysheep.ai/v1"
self.fallback_urls = [
"https://backup.holysheep.ai/v1",
"https://api.v2.holysheep.ai/v1"
]
self.api_key = "YOUR_HOLYSHEEP_API_KEY"
self.timeout = 30
def call_with_fallback(self, endpoint: str, payload: dict) -> dict:
"""เรียก API พร้อม Fallback หลายระดับ"""
urls_to_try = [self.primary_url] + self.fallback_urls
last_error = None
for url in urls_to_try:
try:
full_url = f"{url}/{endpoint}"
response = requests.post(
full_url,
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
},
json=payload,
timeout=self.timeout
)
# ตรวจสอบ HTTP Status
if response.status_code == 200:
return {
"success": True,
"data": response.json(),
"url_used": url
}
elif response.status_code == 401:
# API Key ผิดพลาด - ไม่ต้องลอง URL อื่น
return {
"success": False,
"error": "API Key ไม่ถูกต้อง",
"code": "INVALID_API_KEY"
}
elif response.status_code == 429:
# Rate Limit - ลอง URL ถัดไป
last_error = f"Rate limit on {url}"
continue
else:
last_error = f"HTTP {response.status_code}: {response.text}"
continue
except ConnectionError as e:
last_error = f"Connection refused: {str(e)}"
print(f"❌ เชื่อมต่อ {url} ไม่ได้: {last_error}")
continue
except Timeout as e:
last_error = f"Timeout: {str(e)}"
print(f"⏰ {url} ตอบสนองช้าเกินไป: {last_error}")
continue
except Exception as e:
last_error = f"Unexpected error: {str(e)}"
print(f"⚠️ {url} ข้อผิดพลาดไม่คาดคิด: {last_error}")
continue
# ถ้าลองหมดทุก URL แล้ว
return {
"success": False,
"error": f"ทุก API endpoint ล้มเหลว. ข้อผิดพลาดล่าสุด: {last_error}",
"code": "ALL_ENDPOINTS_FAILED",
"urls_tried": urls_to_try
}
def chat_completion(self, messages: list, model: str = "gpt-4") -> dict:
"""ส่งข้อความแชทและรับการตอบกลับ"""
payload = {
"model": model,
"messages": messages,
"temperature": 0.7,
"max_tokens": 2000
}
result = self.call_with_fallback("chat/completions", payload)
if result["success"]:
return result["data"]
else:
# ส่งข้อความแจ้งเตือน
print(f"🚨 API Call Failed: {result['error']}")
# ส่งคืนข้อความแจ้งเตือนแทน
return {
"error": True,
"message": "ขออภัย ไม่สามารถประมวลผลคำขอได้ในขณะนี้",
"technical_detail": result["error"]
}
ทดสอบการใช้งาน
if __name__ == "__main__":
client = RobustAPIClient()
# ทดสอบกรณีปกติ
response = client.chat_completion([
{"role": "user", "content": "ทดสอบระบบ Fallback"}
])
print("ผลลัพธ์:", response)
กรณีที่ 2: 401 Unauthorized - API Key หมดอายุ
ข้อผิดพลาดนี้เกิดขึ้นเมื่อ API Key ไม่ถูกต้องหรือหมดอายุ สาเหตุอาจมาจาก Key ถูก Revoke หรือมีการเปลี่ยนแปลงสิทธิ์การเข้าถึง วิธีแก้ไขคือตรวจสอบ API Key และอัปเดต รวมถึงตั้งค่า Key Rotation อัตโนมัติ ด้านล่างนี้คือโค้ดสำหรับ API Key Rotation ที่ใช้งานได้จริง
"""
กรณีที่ 2: 401 Unauthorized - API Key Rotation
สาเหตุ: API Key หมดอายุ ถูก Revoke หรือไม่ถูกต้อง
วิธีแก้ไข: ระบบ Key Rotation อัตโนมัติ
"""
import time
import threading
from typing import List, Optional, Dict
from dataclasses import dataclass
import requests
@dataclass
class APIKey:
"""ข้อมูล API Key"""
key: str
name: str
created_at