ในระบบ AI ที่ทำงานจริง การพึ่งพา API ภายนอกอย่าง สมัครที่นี่ HolySheep AI เป็นสิ่งที่หลีกเลี่ยงไม่ได้ แต่ปัญหาที่พบบ่อยคือ latency ที่ไม่แน่นอน การ timeout แบบไม่คาดคิด และต้นทุนที่พุ่งสูงเมื่อ traffic ล้น บทความนี้จะสอนการ implement ระบบ Circuit Breaker และ Fallback ที่ทำให้ระบบแข็งแกร่งแม้ AI API จะล่ม

ทำไมต้องมี Circuit Breaker?

จากประสบการณ์ในการ deploy ระบบหลายสิบตัว สิ่งที่ทำให้ระบบล่มไม่ใช่ AI API ตายทั้งระบบ แต่คือ cascade failure — เมื่อ API ช้า คำขอจะ queue จน memory เต็ม แล้วระบบทั้งหมดล่ม การ implement Circuit Breaker ช่วยหยุดการเรียก API ที่มีปัญหาทันที และ redirect ไปยัง fallback ที่เตรียมไว้

สถาปัตยกรรม Multi-Tier Fallback

ระบบที่แข็งแกร่งต้องมี fallback หลายชั้น เริ่มจาก API หลัก ไปยัง API สำรอง แล้วจบที่ cache หรือ rule-based response แต่ละชั้นต้องมี timeout และเงื่อนไขการเรียกที่ชัดเจน

"""
AI API Circuit Breaker + Multi-Tier Fallback System
Production-Grade Implementation สำหรับ HolySheep AI API
"""

import asyncio
import time
import logging
from enum import Enum
from dataclasses import dataclass, field
from typing import Optional, Callable, Any
from collections import OrderedDict
import hashlib

logger = logging.getLogger(__name__)


class CircuitState(Enum):
    CLOSED = "closed"      # ปกติ เรียก API ปกติ
    OPEN = "open"          # API มีปัญหา ข้ามไป fallback
    HALF_OPEN = "half_open"  # ทดสอบว่า API กลับมาหรือยัง


@dataclass
class CircuitBreakerConfig:
    failure_threshold: int = 5          # จำนวนครั้งที่ล้มเหลวก่อนเปิด circuit
    success_threshold: int = 2          # จำนวนครั้งที่ต้องสำเร็จก่อนปิด circuit
    timeout: float = 30.0               # วินาที ก่อนลอง half-open
    half_open_max_calls: int = 3        # จำนวนคำขอที่ให้ผ่านใน half-open


@dataclass
class FallbackTier:
    name: str
    func: Callable
    timeout: float = 10.0
    enabled: bool = True


class CircuitBreaker:
    """
    Circuit Breaker Implementation สำหรับ AI API
    States: CLOSED -> OPEN -> HALF_OPEN -> CLOSED/OPEN
    """
    
    def __init__(self, name: str, config: CircuitBreakerConfig):
        self.name = name
        self.config = config
        self.state = CircuitState.CLOSED
        self.failure_count = 0
        self.success_count = 0
        self.last_failure_time: Optional[float] = None
        self.half_open_calls = 0
    
    def record_success(self):
        if self.state == CircuitState.HALF_OPEN:
            self.success_count += 1
            if self.success_count >= self.config.success_threshold:
                self._close_circuit()
        else:
            self.failure_count = 0
    
    def record_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.time()
        
        if self.state == CircuitState.HALF_OPEN:
            self._open_circuit()
        elif self.failure_count >= self.config.failure_threshold:
            self._open_circuit()
    
    def can_attempt(self) -> bool:
        if self.state == CircuitState.CLOSED:
            return True
        
        if self.state == CircuitState.OPEN:
            if time.time() - self.last_failure_time >= self.config.timeout:
                self._half_open_circuit()
                return True
            return False
        
        # HALF_OPEN
        return self.half_open_calls < self.config.half_open_max_calls
    
    def _open_circuit(self):
        logger.warning(f"Circuit {self.name}: OPENED after {self.failure_count} failures")
        self.state = CircuitState.OPEN
        self.success_count = 0
    
    def _half_open_circuit(self):
        logger.info(f"Circuit {self.name}: HALF_OPEN - testing recovery")
        self.state = CircuitState.HALF_OPEN
        self.half_open_calls = 0
        self.success_count = 0
    
    def _close_circuit(self):
        logger.info(f"Circuit {self.name}: CLOSED - recovered")
        self.state = CircuitState.CLOSED
        self.failure_count = 0
        self.success_count = 0
        self.half_open_calls = 0


class SimpleCache:
    """LRU Cache สำหรับเก็บ fallback response"""
    
    def __init__(self, max_size: int = 1000, ttl: float = 3600.0):
        self.max_size = max_size
        self.ttl = ttl
        self.cache: OrderedDict = OrderedDict()
        self.timestamps: dict = {}
    
    def _make_key(self, prompt: str, model: str) -> str:
        content = f"{model}:{prompt}"
        return hashlib.sha256(content.encode()).hexdigest()[:32]
    
    def get(self, prompt: str, model: str) -> Optional[str]:
        key = self._make_key(prompt, model)
        if key in self.cache:
            if time.time() - self.timestamps[key] < self.ttl:
                self.cache.move_to_end(key)
                return self.cache[key]
            else:
                del self.cache[key]
                del self.timestamps[key]
        return None
    
    def set(self, prompt: str, model: str, response: str):
        key = self._make_key(prompt, model)
        if key in self.cache:
            self.cache.move_to_end(key)
        self.cache[key] = response
        self.timestamps[key] = time.time()
        
        if len(self.cache) > self.max_size:
            oldest = next(iter(self.cache))
            del self.cache[oldest]
            del self.timestamps[oldest]


Fallback Tiers

async def fallback_cached(cache: SimpleCache, prompt: str, model: str) -> Optional[str]: """Tier 1: ดึงจาก cache""" cached = cache.get(prompt, model) if cached: logger.info("Using cached response") return cached return None async def fallback_gpt_4o_mini(prompt: str, api_key: str) -> str: """Tier 2: ใช้ model ราคาถูกกว่า (GPT-4o-mini)""" import aiohttp url = "https://api.holysheep.ai/v1/chat/completions" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } data = { "model": "gpt-4o-mini", "messages": [{"role": "user", "content": prompt}], "max_tokens": 500, "temperature": 0.3 } async with aiohttp.ClientSession() as session: async with session.post(url, json=data, headers=headers, timeout=aiohttp.ClientTimeout(total=15)) as resp: if resp.status == 200: result = await resp.json() return result["choices"][0]["message"]["content"] raise Exception(f"Fallback failed: {resp.status}") async def fallback_rule_based(prompt: str) -> str: """Tier 3: Rule-based response (ล่าสุด)""" prompt_lower = prompt.lower() if any(word in prompt_lower for word in ["error", "bug", "issue", "problem"]): return "ขอข้อมูลเพิ่มเติมเกี่ยวกับ error message และ stack trace ที่พบ" elif any(word in prompt_lower for word in ["how", "what", "why"]): return "กรุณาระบุ context เพิ่มเติมเพื่อให้คำตอบที่แม่นยำยิ่งขึ้น" else: return "ระบบ AI ชั่วคราวไม่พร้อมให้บริการ กรุณาลองใหม่ในภายหลัง" class AIAggregator: """ AI API Aggregator พร้อม Circuit Breaker และ Multi-Tier Fallback """ def __init__(self, api_key: str): self.api_key = api_key self.cache = SimpleCache(max_size=5000, ttl=7200) self.circuit = CircuitBreaker( "holysheep-gpt4", CircuitBreakerConfig( failure_threshold=3, success_threshold=2, timeout=60.0 ) ) self.metrics = { "total_calls": 0, "primary_success": 0, "fallback_tier1": 0, "fallback_tier2": 0, "fallback_tier3": 0, "circuit_open": 0, "avg_latency": 0.0 } async def complete(self, prompt: str, model: str = "gpt-4o", use_cache: bool = True, **kwargs) -> dict: """ Main entry point สำหรับ AI completion Returns: {"response": str, "source": str, "latency_ms": float, "cost_estimate": float} """ start_time = time.time() self.metrics["total_calls"] += 1 # Tier 0: Cache check if use_cache: cached = await fallback_cached(self.cache, prompt, model) if cached: self.metrics["fallback_tier1"] += 1 return { "response": cached, "source": "cache", "latency_ms": (time.time() - start_time) * 1000, "cost_estimate": 0.0 } # Tier 1: Primary API (HolySheep) if self.circuit.can_attempt(): try: response = await self._call_primary(prompt, model, **kwargs) self.circuit.record_success() self.metrics["primary_success"] += 1 if use_cache: self.cache.set(prompt, model, response) latency = (time.time() - start_time) * 1000 self._update_latency_metric(latency) return { "response": response, "source": "primary", "latency_ms": latency, "cost_estimate": self._estimate_cost(model, response) } except Exception as e: self.circuit.record_failure() logger.error(f"Primary API failed: {e}") # Tier 2: Fallback to cheaper model self.metrics["circuit_open"] += 1 try: response = await fallback_gpt_4o_mini(prompt, self.api_key) self.metrics["fallback_tier2"] += 1 return { "response": response, "source": "fallback-cheap", "latency_ms": (time.time() - start_time) * 1000, "cost_estimate": self._estimate_cost("gpt-4o-mini", response) } except Exception as e: logger.error(f"Fallback tier 2 failed: {e}") # Tier 3: Rule-based self.metrics["fallback_tier3"] += 1 response = await fallback_rule_based(prompt) return { "response": response, "source": "fallback-rule", "latency_ms": (time.time() - start_time) * 1000, "cost_estimate": 0.0 } async def _call_primary(self, prompt: str, model: str, **kwargs) -> str: import aiohttp url = "https://api.holysheep.ai/v1/chat/completions" headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } data = { "model": model, "messages": [{"role": "user", "content": prompt}], "max_tokens": kwargs.get("max_tokens", 1000), "temperature": kwargs.get("temperature", 0.7) } timeout = kwargs.get("timeout", 30.0) async with aiohttp.ClientSession() as session: async with session.post( url, json=data, headers=headers, timeout=aiohttp.ClientTimeout(total=timeout) ) as resp: if resp.status != 200: text = await resp.text() raise Exception(f"API error {resp.status}: {text}") result = await resp.json() return result["choices"][0]["message"]["content"] def _estimate_cost(self, model: str, response: str) -> float: tokens = len(response) // 4 # Rough estimate prices = { "gpt-4o": 8.0, # $8 per 1M tokens "gpt-4o-mini": 0.6, "gpt-4-turbo": 30.0, "claude-3-5-sonnet": 15.0, "gemini-2.5-flash": 2.5 } return (tokens / 1_000_000) * prices.get(model, 8.0) def _update_latency_metric(self, latency: float): total = self.metrics["total_calls"] current_avg = self.metrics["avg_latency"] self.metrics["avg_latency"] = (current_avg * (total - 1) + latency) / total def get_metrics(self) -> dict: return { **self.metrics, "circuit_state": self.circuit.state.value, "cache_size": len(self.cache.cache) }

Usage Example

async def main(): aggregator = AIAggregator(api_key="YOUR_HOLYSHEEP_API_KEY") # Simulate requests for i in range(100): result = await aggregator.complete( f"Explain concept {i} in one sentence", model="gpt-4o", use_cache=True ) print(f"[{i}] Source: {result['source']}, Latency: {result['latency_ms']:.1f}ms") print("\n=== Metrics ===") for k, v in aggregator.get_metrics().items(): print(f"{k}: {v}") if __name__ == "__main__": asyncio.run(main())

การจัดการ Concurrency และ Rate Limiting

ปัญหาสำคัญอีกอย่างคือ burst traffic ที่ทำให้ API rate limit เกิน ต้อง implement semaphore-based concurrency control และ exponential backoff สำหรับ retry

"""
Advanced Concurrency Control สำหรับ AI API
Semaphore + Rate Limiter + Exponential Backoff
"""

import asyncio
import time
import logging
from typing import Optional
from dataclasses import dataclass
from collections import deque

logger = logging.getLogger(__name__)


@dataclass
class RateLimitConfig:
    requests_per_second: float = 10.0
    burst_size: int = 20
    max_queue_size: int = 100


class TokenBucketRateLimiter:
    """Token Bucket Algorithm สำหรับ rate limiting"""
    
    def __init__(self, config: RateLimitConfig):
        self.config = config
        self.tokens = float(config.burst_size)
        self.last_update = time.time()
        self.refill_rate = config.requests_per_second
    
    def _refill(self):
        now = time.time()
        elapsed = now - self.last_update
        self.tokens = min(
            self.config.burst_size,
            self.tokens + elapsed * self.refill_rate
        )
        self.last_update = now
    
    async def acquire(self, timeout: float = 30.0) -> bool:
        start = time.time()
        
        while True:
            self._refill()
            
            if self.tokens >= 1.0:
                self.tokens -= 1.0
                return True
            
            if time.time() - start >= timeout:
                return False
            
            wait_time = (1.0 - self.tokens) / self.refill_rate
            await asyncio.sleep(min(wait_time, 0.1))


class RequestQueue:
    """Priority Queue สำหรับจัดการ request ที่รอ"""
    
    def __init__(self, max_size: int = 100):
        self.queue: asyncio.Queue = asyncio.Queue(maxsize=max_size)
        self.overflow_count = 0
    
    async def put(self, item, priority: int = 0):
        try:
            await asyncio.wait_for(
                self.queue.put((priority, time.time(), item)),
                timeout=1.0
            )
        except asyncio.TimeoutError:
            self.overflow_count += 1
            logger.warning(f"Queue overflow, dropped request #{self.overflow_count}")


class ConcurrentAIClient:
    """
    High-Concurrency AI Client พร้อม Rate Limiting
    """
    
    def __init__(self, api_key: str, config: RateLimitConfig):
        self.api_key = api_key
        self.rate_limiter = TokenBucketRateLimiter(config)
        self.semaphore = asyncio.Semaphore(config.burst_size)
        self.metrics = {
            "total_requests": 0,
            "completed": 0,
            "rate_limited": 0,
            "timeouts": 0,
            "errors": 0
        }
    
    async def complete_with_retry(
        self,
        prompt: str,
        model: str = "gpt-4o",
        max_retries: int = 3,
        base_delay: float = 1.0,
        max_delay: float = 60.0
    ) -> dict:
        """
        Complete request พร้อม Exponential Backoff + Jitter
        """
        self.metrics["total_requests"] += 1
        
        for attempt in range(max_retries):
            try:
                # Acquire rate limit permit
                if not await self.rate_limiter.acquire(timeout=30.0):
                    self.metrics["rate_limited"] += 1
                    continue
                
                async with self.semaphore:
                    result = await self._make_request(prompt, model)
                    self.metrics["completed"] += 1
                    return result
                    
            except TimeoutError:
                self.metrics["timeouts"] += 1
                logger.warning(f"Timeout on attempt {attempt + 1}")
                
            except Exception as e:
                self.metrics["errors"] += 1
                logger.error(f"Request failed: {e}")
            
            # Exponential backoff with jitter
            if attempt < max_retries - 1:
                delay = min(base_delay * (2 ** attempt), max_delay)
                jitter = delay * 0.1 * (time.time() % 1)
                await asyncio.sleep(delay + jitter)
        
        return {"error": "Max retries exceeded", "source": "none"}
    
    async def _make_request(self, prompt: str, model: str) -> dict:
        import aiohttp
        import random
        
        # Simulate different API errors
        if random.random() < 0.05:  # 5% failure rate simulation
            raise Exception("Simulated API error")
        
        url = "https://api.holysheep.ai/v1/chat/completions"
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
        data = {
            "model": model,
            "messages": [{"role": "user", "content": prompt}],
            "max_tokens": 500
        }
        
        start = time.time()
        
        async with aiohttp.ClientSession() as session:
            async with session.post(
                url, json=data, headers=headers,
                timeout=aiohttp.ClientTimeout(total=25.0)
            ) as resp:
                response_time = (time.time() - start) * 1000
                
                if resp.status == 429:
                    raise Exception("Rate limited")
                
                if resp.status == 500:
                    raise Exception("Server error")
                
                result = await resp.json()
                
                return {
                    "response": result["choices"][0]["message"]["content"],
                    "latency_ms": response_time,
                    "status": resp.status
                }
    
    async def batch_complete(self, prompts: list[str], 
                           model: str = "gpt-4o",
                           max_concurrent: int = 10) -> list[dict]:
        """Process multiple prompts concurrently with limit"""
        
        semaphore = asyncio.Semaphore(max_concurrent)
        
        async def limited_complete(prompt):
            async with semaphore:
                return await self.complete_with_retry(prompt, model)
        
        tasks = [limited_complete(p) for p in prompts]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        return [
            r if not isinstance(r, Exception) else {"error": str(r)}
            for r in results
        ]


Benchmark

async def benchmark(): """Benchmark Concurrent Client Performance""" import statistics client = ConcurrentAIClient( api_key="YOUR_HOLYSHEEP_API_KEY", config=RateLimitConfig( requests_per_second=50.0, burst_size=20, max_queue_size=500 ) ) prompts = [f"Analyze data point #{i}" for i in range(100)] start_time = time.time() results = await client.batch_complete(prompts, max_concurrent=15) total_time = time.time() - start_time latencies = [r.get("latency_ms", 0) for r in results if "latency_ms" in r] print(f"=== Benchmark Results ===") print(f"Total requests: {len(prompts)}") print(f"Completed: {sum(1 for r in results if 'response' in r)}") print(f"Total time: {total_time:.2f}s") print(f"Throughput: {len(prompts)/total_time:.1f} req/s") print(f"Avg latency: {statistics.mean(latencies):.1f}ms") print(f"P99 latency: {sorted(latencies)[int(len(latencies)*0.99)]:.1f}ms") print(f"\n=== Metrics ===") for k, v in client.metrics.items(): print(f"{k}: {v}") if __name__ == "__main__": asyncio.run(benchmark())

การ Optimize ต้นทุนด้วย Smart Routing

วิธีที่ฉลาดที่สุดในการประหยัดคือ route request ไปยัง model ที่เหมาะสมตาม task complexity งานง่ายใช้ model ถูกๆ งานซับซ้อนใช้ model แพงแต่ทรงพลัง

Benchmark Results

จากการทดสอบบน production load (1,000 requests/minute):

ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข

1. Timeout ไม่เหมาะสมทำให้ระบบค้าง

ปัญหา: ตั้ง timeout สั้นเกินไป (เช่น 5 วินาที) ทำให้ request ที่ใช้เวลานานถูก cancel หมด แม้ API จะทำงานได้ แต่ระบบคิดว่า API ล่ม

# ❌ ผิด: Timeout สั้นเกินไป
async with session.post(url, timeout=aiohttp.ClientTimeout(total=3.0)) as resp:
    ...

✅ ถูก: Timeout ที่เหมาะสม (30 วินาทีสำหรับ complex tasks)

async with session.post( url, timeout=aiohttp.ClientTimeout(total=30.0) ) as resp: ...

✅ ดีที่สุด: Dynamic timeout ตาม task

async def get_timeout(model: str, max_tokens: int) -> float: base_timeouts = { "gpt-4o": 60.0, "gpt-4o-mini": 30.0, "claude-3-5-sonnet": 45.0 } estimated_time = (max_tokens / 100) * 2 # Rough estimate return min(base_timeouts.get(model, 30.0), estimated_time + 10.0)

2. ไม่มี error handling ที่เหมาะสมทำให้ระบบ crash

ปัญหา: Exception ไม่ได้ catch ทำให้ whole request fails แม้แค่บาง endpoint มีปัญหา

# ❌ ผิด: ไม่มี proper error handling
async def complete(prompt: str):
    result = await call_api(prompt)  # ไม่มี try/except
    return result

✅ ถูก: Comprehensive error handling

class AIError(Exception): pass class APITimeoutError(AIError): pass class APIRateLimitError(AIError): pass async def complete(prompt: str, model: str = "gpt-4o"): try: result = await call_api(prompt, model) return {"success": True, "data": result} except asyncio.TimeoutError: logger.error(f"Timeout calling {model} API") raise APITimeoutError(f"API timeout after 30s for model {model}") except aiohttp.ClientResponseError as e: if e.status == 429: raise APIRateLimitError("Rate limit exceeded") elif e.status >= 500: raise AIError(f"Server error: {e.status}") else: raise AIError(f"API error: {e.status} - {e.message}") except Exception as e: logger.exception("Unexpected error in AI completion") raise AIError(f"Unexpected error: {str(e)}")

3. Circuit Breaker state ไม่ถูก reset หลัง recovery

ปัญหา: Circuit ถูกเปิดค้างแม้ API กลับมาทำงานปกติแล้ว เพราะไม่มี logic สำหรับ half-open state

# ❌ ผิด: ไม่มี recovery mechanism
class BadCircuitBreaker:
    def __init__(self):
        self.failed = False
    
    def record_failure(self):
        self.failed = True
    
    # ไม่มีวิธีกลับมาปกติ!

✅ ถูก: Proper state machine

class GoodCircuitBreaker: CLOSED = "closed" OPEN = "open" HALF_OPEN = "half_open" def __init__(self, failure_threshold: int = 5, timeout: float = 60.0, success_threshold: int = 3): self.failure_count = 0 self.success_count = 0 self.state = self.CLOSED self.last_failure_time = None self.failure_threshold = failure_threshold self.timeout = timeout self.success_threshold = success_threshold def record_success(self): if self.state == self.HALF_OPEN: self.success_count += 1 if self.success_count >= self.success_threshold: self._transition_to_closed() else: self.failure_count = 0 def record_failure(self): self.failure_count += 1 self.last_failure_time = time.time() if self.state == self.HALF_OPEN: self._transition_to_open() elif self.failure_count >= self.failure_threshold: self._transition_to_open() def can_attempt(self) -> bool: if self.state == self.CLOSED: return True if self.state == self.OPEN: if time.time() - self.last_failure_time >= self.timeout: self._transition_to_half_open() return True return False return self.state == self.HALF_OPEN def _transition_to_open(self): logger.warning("Circuit OPENED - API is failing") self.state = self.OPEN self.success_count = 0 def _transition_to_half_open(self): logger