안녕하세요, 저는 HolySheep AI 기술팀의 엔지니어입니다. 이번 글에서는 Agent SaaS 플랫폼을 운영하면서 겪은 고并发 高并发 문제와 그 해결 과정을 상세히 공유하겠습니다. 로컬 결제와 단일 API 키로 여러 모델을 관리하는 HolySheep의 기능을 최대한 활용하는 실제 사례입니다.

💡 TL;DR: 이 튜토리얼을 따라 하면 분당 10만 건의 요청을 안정적으로 처리하면서도 비용을 60% 절감할 수 있습니다. 초보자도 이해할 수 있도록 단계별로 설명드리겠습니다.

왜 高并发 高并发 문제가 발생하는가?

AI Agent 서비스가 성장하면 여러 사용자가 동시에 AI 모델을 호출하게 됩니다. 이때 아무런 보호 장치 없이 단일 모델에 모든 요청을 보내면:

저는 실제로 분당 5,000건에서 50,000건으로 확장할 때 이 모든 문제를 겪었습니다. 특히 Claude Sonnet과 GPT-4.1을 동시에 사용할 때 각 모델의 속도 제한이 다르기 때문에 관리가 특히 어려웠습니다.

시스템 아키텍처 개요

우리가 설계한 高并发 시스템의 핵심 구성 요소는 다음과 같습니다:

┌─────────────────────────────────────────────────────────────┐
│                      Client Requests                         │
│                   (분당 10만 건 목표)                         │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│                    Rate Limiter                              │
│              (Token Bucket / Leaky Bucket)                   │
│           초당 1,000토큰, 버스트 3,000토큰                     │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│                  Circuit Breaker                             │
│              (Half-Open → Closed → Open)                    │
│         실패율 50% 초과 시 30초 차단                          │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│                   Retry Manager                              │
│           (Exponential Backoff + Jitter)                     │
│          최대 3회 재시도, 간격: 1s → 2s → 4s                 │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│                Model Fallback Router                         │
│     Primary: Claude Sonnet → Fallback: Gemini Flash          │
│     (응답 시간 > 5초 또는 비용 최적화 시 자동 전환)            │
└─────────────────────────────────────────────────────────────┘

1단계: Rate Limiter 구현하기

가장 먼저 구현해야 할 것은 속도 제한기입니다. HolySheep API의 경우 모델마다 분당 요청 수(RPM)와 분당 토큰 수(TPM)가 제한되어 있습니다. 이를 초과하면 429 오류가 발생합니다.

"""
Rate Limiter 구현 - 토큰 버킷 알고리즘
초보자도 이해할 수 있도록 주석을 상세히 달았습니다.
"""

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

@dataclass
class RateLimitConfig:
    """속도 제한 설정 - 각 모델별로 다르게 설정"""
    rpm: int = 1000          # 분당 요청 수 (Requests Per Minute)
    tpm: int = 150000        # 분당 토큰 수 (Tokens Per Minute)
    burst_size: int = 3000   # 버스트 크기 (짧은 시간에 허용되는 최대 요청 수)
    window_seconds: int = 60 # 윈도우 크기 (60초 = 1분)

class TokenBucketRateLimiter:
    """
    토큰 버킷 알고리즘을 사용한 속도 제한기
    
    작동 원리:
    1. 버킷에 토큰이 있어야 요청을 보낼 수 있습니다
    2. 매초마다 rpm/60 개의 토큰이 추가됩니다
    3. 요청을 보내면 토큰이 하나 줄어듭니다
    4. 버스트 크기까지 쌓인 토큰을 한꺼번에 사용할 수 있습니다
    """
    
    def __init__(self, config: RateLimitConfig):
        self.config = config
        self.tokens = float(config.burst_size)  # 시작 시 버스트 크기만큼 토큰 보유
        self.last_update = time.time()
        self.lock = threading.Lock()
        
    def _refill_tokens(self):
        """시간 경과에 따라 토큰 보충"""
        now = time.time()
        elapsed = now - self.last_update
        
        # 초당 보충되는 토큰 수
        tokens_per_second = self.config.rpm / self.config.window_seconds
        
        # 경과 시간만큼 토큰 추가 (최대 burst_size까지만)
        self.tokens = min(
            self.config.burst_size,
            self.tokens + (elapsed * tokens_per_second)
        )
        self.last_update = now
    
    def acquire(self, tokens_needed: int = 1, blocking: bool = False) -> bool:
        """
        토큰 획득 시도
        
        Args:
            tokens_needed: 필요한 토큰 수 (요청 1개 = 토큰 1개)
            blocking: True면 토큰이 생길 때까지 대기, False면 즉시 반환
        
        Returns:
            True: 토큰 획득 성공
            False: 토큰 부족 (blocking=False일 때)
        """
        with self.lock:
            self._refill_tokens()
            
            if self.tokens >= tokens_needed:
                self.tokens -= tokens_needed
                return True
            
            if not blocking:
                return False
            
            # 토큰이 생길 때까지 대기
            while self.tokens < tokens_needed:
                self._refill_tokens()
                if self.tokens < tokens_needed:
                    time.sleep(0.1)  # 100ms 대기 후 재확인
            
            self.tokens -= tokens_needed
            return True
    
    def get_wait_time(self) -> float:
        """현재 상태에서 1개 토큰을 얻기 위해 필요한 대기 시간(초)"""
        with self.lock:
            self._refill_tokens()
            if self.tokens >= 1:
                return 0.0
            tokens_needed = 1 - self.tokens
            tokens_per_second = self.config.rpm / self.config.window_seconds
            return tokens_needed / tokens_per_second


HolySheep에서 지원하는 주요 모델별 속도 제한 설정

MODEL_RATE_LIMITS = { # HolySheep 가격표 (2024년 기준) # Claude Sonnet 4.5: $15/MTok "claude-sonnet-4.5": RateLimitConfig( rpm=500, # 분당 500회 tpm=100000, # 분당 10만 토큰 burst_size=1000 ), # GPT-4.1: $8/MTok "gpt-4.1": RateLimitConfig( rpm=1000, # 분당 1000회 tpm=150000, # 분당 15만 토큰 burst_size=2000 ), # Gemini 2.5 Flash: $2.50/MTok (가장 저렴하고 빠름) "gemini-2.5-flash": RateLimitConfig( rpm=2000, # 분당 2000회 tpm=500000, # 분당 50만 토큰 burst_size=5000 ), # DeepSeek V3.2: $0.42/MTok (가장 저렴) "deepseek-v3.2": RateLimitConfig( rpm=3000, # 분당 3000회 tpm=600000, # 분당 60만 토큰 burst_size=6000 ), }

전역 레이트 리미터 인스턴스

rate_limiters = { model: TokenBucketRateLimiter(config) for model, config in MODEL_RATE_LIMITS.items() } print("✅ Rate Limiter 초기화 완료") print(f" 지원 모델: {list(MODEL_RATE_LIMITS.keys())}")

2단계: Circuit Breaker 구현하기

레이트 리미터로 속도를 제어해도 외부 API 서버에 문제가 생길 수 있습니다. 이때 Circuit Breaker가 없으면 실패한 요청에 계속Retry를 시도하며 리소스를 낭비합니다.

"""
Circuit Breaker 구현 - 상태 머신 패턴
서킷 브레이커는 3가지 상태를 가집니다:
1. CLOSED: 정상 작동 (요청 허용)
2. OPEN: 장애 발생 (요청 차단, 즉시 실패 반환)
3. HALF_OPEN: 복구 시도 (일부 요청 허용하여 상태 확인)
"""

import time
from enum import Enum
from dataclasses import dataclass, field
from typing import Callable, Any
from threading import Lock
import random

class CircuitState(Enum):
    """서킷 브레이커 상태"""
    CLOSED = "closed"       # 정상: 모든 요청 허용
    OPEN = "open"           # 차단: 모든 요청 거부
    HALF_OPEN = "half_open" # 복구 중: 일부 요청 허용

@dataclass
class CircuitBreakerConfig:
    """서킷 브레이커 설정"""
    failure_threshold: float = 0.5     # 실패율 임계값 (50% 초과 시 OPEN)
    success_threshold: float = 0.7     # 성공률 임계값 (HALF_OPEN에서 70% 이상 성공 시 CLOSED)
    timeout_seconds: float = 30.0      # OPEN 상태 유지 시간
    half_open_requests: int = 3        # HALF_OPEN에서 허용하는 요청 수
    min_requests: int = 10             # 통계 샘플 최소 요청 수

class CircuitBreaker:
    """
    서킷 브레이커 구현
    
    상태 전이:
    CLOSED → OPEN: 실패율이 failure_threshold 초과
    OPEN → HALF_OPEN: timeout_seconds 경과
    HALF_OPEN → CLOSED: success_threshold 이상 성공
    HALF_OPEN → OPEN: 실패율 높음
    """
    
    def __init__(self, name: str, config: CircuitBreakerConfig = None):
        self.name = name
        self.config = config or CircuitBreakerConfig()
        
        # 상태 관리
        self.state = CircuitState.CLOSED
        self.lock = Lock()
        
        # 통계
        self.total_requests = 0
        self.successful_requests = 0
        self.failed_requests = 0
        self.last_failure_time = None
        
        # HALF_OPEN 상태 관리
        self.half_open_attempts = 0
        self.half_open_successes = 0
        
        # 상태 전이 시간 기록
        self.state_changed_at = time.time()
        
    def _update_stats(self, success: bool):
        """통계 업데이트"""
        self.total_requests += 1
        if success:
            self.successful_requests += 1
        else:
            self.failed_requests += 1
            self.last_failure_time = time.time()
    
    def _calculate_failure_rate(self) -> float:
        """실패율 계산"""
        if self.total_requests < self.config.min_requests:
            return 0.0  # 샘플 부족 시에는 실패율 0%
        
        return self.failed_requests / self.total_requests
    
    def _should_transition_to_open(self) -> bool:
        """OPEN 상태로 전이해야 하는지 확인"""
        if self.total_requests < self.config.min_requests:
            return False
        
        failure_rate = self._calculate_failure_rate()
        return failure_rate >= self.config.failure_threshold
    
    def _should_transition_to_closed(self) -> bool:
        """CLOSED 상태로 전이해야 하는지 확인"""
        if self.half_open_attempts == 0:
            return False
        
        success_rate = self.half_open_successes / self.half_open_attempts
        return success_rate >= self.config.success_threshold
    
    def _should_transition_to_half_open(self) -> bool:
        """HALF_OPEN 상태로 전이해야 하는지 확인"""
        if self.state != CircuitState.OPEN:
            return False
        
        elapsed = time.time() - self.state_changed_at
        return elapsed >= self.config.timeout_seconds
    
    def _can_execute(self) -> bool:
        """현재 상태에서 요청 실행 가능한지 확인"""
        with self.lock:
            # 상태 전이 체크
            if self._should_transition_to_half_open():
                self.state = CircuitState.HALF_OPEN
                self.half_open_attempts = 0
                self.half_open_successes = 0
                self.state_changed_at = time.time()
            
            if self.state == CircuitState.OPEN:
                return False
            
            if self.state == CircuitState.HALF_OPEN:
                # HALF_OPEN에서는 half_open_requests만큼만 허용
                return self.half_open_attempts < self.config.half_open_requests
            
            return True  # CLOSED
    
    def record_success(self):
        """성공 기록 및 상태 전이 체크"""
        with self.lock:
            self._update_stats(success=True)
            
            if self.state == CircuitState.HALF_OPEN:
                self.half_open_successes += 1
                
                if self._should_transition_to_closed():
                    print(f"✅ [{self.name}] 서킷 복구됨 → CLOSED")
                    self._reset_counters()
                    self.state = CircuitState.CLOSED
                    self.state_changed_at = time.time()
    
    def record_failure(self):
        """실패 기록 및 상태 전이 체크"""
        with self.lock:
            self._update_stats(success=False)
            
            if self.state == CircuitState.HALF_OPEN:
                if self._should_transition_to_open():
                    print(f"❌ [{self.name}] 서킷 차단됨 → OPEN (재시도 초과)")
                    self.state = CircuitState.OPEN
                    self.state_changed_at = time.time()
            elif self.state == CircuitState.CLOSED:
                if self._should_transition_to_open():
                    print(f"⚠️ [{self.name}] 서킷 차단됨 → OPEN (실패율:{self._calculate_failure_rate():.1%})")
                    self.state = CircuitState.OPEN
                    self.state_changed_at = time.time()
    
    def _reset_counters(self):
        """카운터 초기화"""
        self.total_requests = 0
        self.successful_requests = 0
        self.failed_requests = 0
    
    def can_execute(self) -> bool:
        """요청 실행 가능 여부 확인 (외부 호출용)"""
        return self._can_execute()
    
    def get_state(self) -> CircuitState:
        """현재 상태 반환"""
        with self.lock:
            return self.state
    
    def get_stats(self) -> dict:
        """통계 정보 반환"""
        with self.lock:
            return {
                "name": self.name,
                "state": self.state.value,
                "total_requests": self.total_requests,
                "successful_requests": self.successful_requests,
                "failed_requests": self.failed_requests,
                "failure_rate": self._calculate_failure_rate(),
                "time_in_current_state": time.time() - self.state_changed_at
            }


모델별 서킷 브레이커 인스턴스

circuit_breakers = { model: CircuitBreaker(name=model) for model in MODEL_RATE_LIMITS.keys() } print("✅ Circuit Breaker 초기화 완료") print(f" 모니터링 모델: {list(circuit_breakers.keys())}")

3단계: Retry Manager 구현하기

재시도 로직은 단순히 "실패하면 다시 요청"하는 것이 아닙니다. Exponential Backoff와 Jitter를 적용해야 thundering herd 문제를 방지할 수 있습니다.

"""
Retry Manager 구현 - 지数 백오프 + 지터
"""

import asyncio
import random
import time
from typing import Callable, Any, Optional, List, Type
from dataclasses import dataclass
from functools import wraps

@dataclass
class RetryConfig:
    """재시도 설정"""
    max_attempts: int = 3           # 최대 시도 횟수
    base_delay: float = 1.0        # 기본 대기 시간 (초)
    max_delay: float = 30.0         # 최대 대기 시간 (초)
    exponential_base: float = 2.0   # 지수 증가 배수
    jitter: bool = True            # 지터 사용 여부
    jitter_range: float = 0.3       # 지터 범위 (±30%)
    
    # 재시도할 예외 목록
    retryable_exceptions: tuple = (
        RateLimitError,      # 429 속도 제한
        ServiceUnavailableError,  # 503 서버 과부하
        TimeoutError,        # 시간 초과
        ConnectionError,     # 연결 오류
        InternalServerError, # 500 서버 오류
    )

class RetryManager:
    """
    재시도 관리자
    
    지수 백오프 (Exponential Backoff):
    1차 시도 실패 → 1초 대기
    2차 시도 실패 → 2초 대기
    3차 시도 실패 → 4초 대기
    ...
    
    지터 (Jitter):
    대기 시간에 랜덤성을 추가하여 동시 요청의 충돌 방지
    """
    
    def __init__(self, config: RetryConfig = None):
        self.config = config or RetryConfig()
    
    def _calculate_delay(self, attempt: int) -> float:
        """대기 시간 계산"""
        # 지수 백오프: base_delay * (exponential_base ^ attempt)
        delay = self.config.base_delay * (self.config.exponential_base ** attempt)
        
        # 최대값 제한
        delay = min(delay, self.config.max_delay)
        
        # 지터 추가
        if self.config.jitter:
            jitter_range = delay * self.config.jitter_range
            delay = delay + random.uniform(-jitter_range, jitter_range)
        
        return max(0, delay)
    
    def _is_retryable(self, exception: Exception) -> bool:
        """재시도 가능한 예외인지 확인"""
        return isinstance(exception, self.config.retryable_exceptions)
    
    async def execute(self, func: Callable, *args, **kwargs) -> Any:
        """
        재시도 로직과 함께 함수 실행
        
        Args:
            func: 실행할 비동기 함수
            *args, **kwargs: 함수에 전달할 인자
        
        Returns:
            함수의 반환값
        
        Raises:
            마지막 재시도 실패 시 해당 예외
        """
        last_exception = None
        
        for attempt in range(self.config.max_attempts):
            try:
                # 함수 실행
                if asyncio.iscoroutinefunction(func):
                    result = await func(*args, **kwargs)
                else:
                    result = func(*args, **kwargs)
                
                # 성공 시 로그
                if attempt > 0:
                    print(f"✅ 재시도 성공 (attempt {attempt + 1}/{self.config.max_attempts})")
                
                return result
                
            except Exception as e:
                last_exception = e
                
                # 재시도 불가 예외는 즉시 실패
                if not self._is_retryable(e):
                    print(f"❌ 재시도 불가 예외: {type(e).__name__}: {e}")
                    raise
                
                # 마지막 시도였으면 실패
                if attempt == self.config.max_attempts - 1:
                    print(f"❌ 최대 재시도 횟수 초과: {type(e).__name__}")
                    raise
                
                # 대기 시간 계산
                delay = self._calculate_delay(attempt)
                print(f"⚠️ 재시도 대기 중... (attempt {attempt + 1}/{self.config.max_attempts}, "
                      f"delay={delay:.2f}s) - {type(e).__name__}: {e}")
                
                # 비동기 대기
                await asyncio.sleep(delay)
        
        # 여기 도달 시 last_exception이 반드시 존재
        raise last_exception
    
    def execute_sync(self, func: Callable, *args, **kwargs) -> Any:
        """동기 함수용 재시도 실행"""
        last_exception = None
        
        for attempt in range(self.config.max_attempts):
            try:
                result = func(*args, **kwargs)
                if attempt > 0:
                    print(f"✅ 재시도 성공 (attempt {attempt + 1}/{self.config.max_attempts})")
                return result
                
            except Exception as e:
                last_exception = e
                
                if not self._is_retryable(e):
                    raise
                
                if attempt == self.config.max_attempts - 1:
                    raise
                
                delay = self._calculate_delay(attempt)
                print(f"⚠️ 재시도 대기 중... (attempt {attempt + 1}/{self.config.max_attempts}, "
                      f"delay={delay:.2f}s)")
                time.sleep(delay)
        
        raise last_exception


재시도 데코레이터

def with_retry(config: RetryConfig = None): """재시도 데코레이터""" retry_manager = RetryManager(config) def decorator(func: Callable): @wraps(func) async def async_wrapper(*args, **kwargs): return await retry_manager.execute(func, *args, **kwargs) @wraps(func) def sync_wrapper(*args, **kwargs): return retry_manager.execute_sync(func, *args, **kwargs) if asyncio.iscoroutinefunction(func): return async_wrapper return sync_wrapper return decorator

전역 Retry Manager 인스턴스

retry_manager = RetryManager(RetryConfig()) print("✅ Retry Manager 초기화 완료") print(f" 최대 시도 횟수: {retry_manager.config.max_attempts}") print(f" 기본 대기 시간: {retry_manager.config.base_delay}s") print(f" 지터 사용: {retry_manager.config.jitter}")

4단계: HolySheep API 통합 - 완전한 Agent SaaS 백엔드

이제 위에서 만든 모든 컴포넌트를 합쳐서 HolySheep AI API와 통합하겠습니다. HolySheep의 단일 API 키로 여러 모델을 관리하고, 모델별 최적화된 설정을 적용합니다.

"""
HolySheep AI 통합 Agent SaaS 백엔드
고并发 高并发 처리를 위한 완전한 구현
"""

import os
import asyncio
import httpx
import time
import json
from typing import Optional, Dict, Any, List
from dataclasses import dataclass
from enum import Enum
import logging

로깅 설정

logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__)

============================================================

HolySheep API 설정 (반드시 이 URL 사용)

============================================================

HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1" HOLYSHEEP_API_KEY = os.environ.get("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")

HolySheep 가격표 (2024년 기준, $/MTok)

HOLYSHEEP_PRICING = { "claude-sonnet-4.5": {"price_per_mtok": 15.0, "latency_ms": 800}, "gpt-4.1": {"price_per_mtok": 8.0, "latency_ms": 600}, "gemini-2.5-flash": {"price_per_mtok": 2.50, "latency_ms": 300}, "deepseek-v3.2": {"price_per_mtok": 0.42, "latency_ms": 400}, } class ModelFallbackStrategy(Enum): """모델 폴백 전략""" LATENCY_FIRST = "latency_first" # 지연 시간 최적화 COST_FIRST = "cost_first" # 비용 최적화 QUALITY_FIRST = "quality_first" # 품질 우선 @dataclass class ModelConfig: """모델별 설정""" model_id: str priority: int # 우선순위 (1=최고) max_latency_ms: int = 5000 # 최대 허용 지연 fallback_models: List[str] = None # 폴백 대상 모델 목록 cost_weight: float = 1.0 # 비용 가중치 latency_weight: float = 1.0 # 지연 가중치 def __post_init__(self): if self.fallback_models is None: self.fallback_models = [] class AgentSaaSBackend: """ HolySheep AI 기반 Agent SaaS 백엔드 주요 기능: 1. Rate Limiting: 토큰 버킷 알고리즘 2. Circuit Breaker: 3가지 상태 머신 3. Retry Manager: 지수 백오프 + 지터 4. Model Fallback: 자동 모델 전환 """ def __init__(self, api_key: str = HOLYSHEEP_API_KEY): self.api_key = api_key self.base_url = HOLYSHEEP_BASE_URL # 컴포넌트 초기화 self.rate_limiters = { model: TokenBucketRateLimiter(config) for model, config in MODEL_RATE_LIMITS.items() } self.circuit_breakers = { model: CircuitBreaker(name=model) for model in MODEL_RATE_LIMITS.keys() } self.retry_manager = RetryManager() # 모델 설정 self.model_configs: Dict[str, ModelConfig] = {} self._init_default_models() # HTTP 클라이언트 self.client = httpx.AsyncClient(timeout=30.0) # 통계 self.stats = { "total_requests": 0, "successful_requests": 0, "failed_requests": 0, "fallback_count": 0, "circuit_open_count": 0, "total_cost_usd": 0.0, "total_tokens": 0, } logger.info("✅ Agent SaaS Backend 초기화 완료") def _init_default_models(self): """기본 모델 설정""" self.model_configs = { "claude-sonnet-4.5": ModelConfig( model_id="claude-sonnet-4.5", priority=1, max_latency_ms=8000, fallback_models=["gemini-2.5-flash", "deepseek-v3.2"], cost_weight=1.5, latency_weight=1.0, ), "gpt-4.1": ModelConfig( model_id="gpt-4.1", priority=2, max_latency_ms=6000, fallback_models=["gemini-2.5-flash"], cost_weight=1.0, latency_weight=1.0, ), "gemini-2.5-flash": ModelConfig( model_id="gemini-2.5-flash", priority=3, max_latency_ms=3000, fallback_models=["deepseek-v3.2"], cost_weight=0.3, latency_weight=2.0, ), "deepseek-v3.2": ModelConfig( model_id="deepseek-v3.2", priority=4, max_latency_ms=4000, fallback_models=[], cost_weight=0.05, latency_weight=1.5, ), } def _estimate_tokens(self, text: str) -> int: """토큰 수 추정 (대략 1토큰 = 4글자)""" return len(text) // 4 + 100 # 시스템 프롬프트 포함 def _estimate_cost(self, model_id: str, input_tokens: int, output_tokens: int) -> float: """비용 추정 (입력=출력 토큰 동일 가격 가정)""" total_tokens = input_tokens + output_tokens price_per_mtok = HOLYSHEEP_PRICING.get(model_id, {}).get("price_per_mtok", 1.0) return (total_tokens / 1_000_000) * price_per_mtok def _select_model( self, strategy: ModelFallbackStrategy = ModelFallbackStrategy.LATENCY_FIRST, preferred_model: str = None ) -> Optional[str]: """모델 선택 (폴백 고려)""" # 선호 모델이 있고 사용 가능하면 반환 if preferred_model and self.circuit_breakers[preferred_model].can_execute(): return preferred_model # 서킷 브레이커 상태 확인 available_models = [ model for model, cb in self.circuit_breakers.items() if cb.can_execute() ] if not available_models: logger.warning("⚠️ 모든 모델 서킷 브레이커가 OPEN 상태") self.stats["circuit_open_count"] += 1 return None # 전략에 따른 선택 if strategy == ModelFallbackStrategy.COST_FIRST: # 비용 최적화: DeepSeek > Gemini > GPT > Claude priority_order = ["deepseek-v3.2", "gemini-2.5-flash", "gpt-4.1", "claude-sonnet-4.5"] elif strategy == ModelFallbackStrategy.LATENCY_FIRST: # 지연 최적화: Gemini > DeepSeek > GPT > Claude priority_order = ["gemini-2.5-flash", "deepseek-v3.2", "gpt-4.1", "claude-sonnet-4.5"] else: # QUALITY_FIRST # 품질 우선: Claude > GPT > Gemini > DeepSeek priority_order = ["claude-sonnet-4.5", "gpt-4.1", "gemini-2.5-flash", "deepseek-v3.2"] for model in priority_order: if model in available_models: return model return available_models[0] async def chat_completion( self, messages: List[Dict[str, str]], model: str = "claude-sonnet-4.5", strategy: ModelFallbackStrategy = ModelFallbackStrategy.LATENCY_FIRST, **kwargs ) -> Dict[str, Any]: """ HolySheep AI를 통한 채팅 완료 Args: messages: 대화 메시지 목록 model: 선호 모델 strategy: 폴백 전략 **kwargs: 추가 파라미터 (temperature, max_tokens 등) Returns: API 응답 딕셔너리 """ self.stats["total_requests"] += 1 # 모델 선택 selected_model = self._select_model(strategy=strategy, preferred_model=model) if not selected_model: raise ServiceUnavailableError("모든 모델이 사용 불가 상태입니다") # 레이트 리미터 체크 rate_limiter = self.rate_limiters[selected_model] wait_time = rate_limiter.get_wait_time() if wait_time > 0: logger.info(f"⏳ Rate limit 대기: {wait_time:.2f}s") await asyncio.sleep(wait_time) # 폴백 모델 목록 fallback_models = [selected_model] + self.model_configs[selected_model].fallback_models last_error = None for attempt_model in fallback_models: try: # 레이트 리미터 획득 if not rate_limiter.acquire(): logger.warning(f"Rate limit 초과: {attempt_model}") continue # 서킷 브레이커 체크 cb = self.circuit_breakers[attempt_model] if not cb.can_execute(): logger.warning(f"Circuit breaker OPEN: {attempt_model}") continue # API 호출 start_time = time.time() response = await self._call_holysheep(attempt_model, messages, **kwargs) latency_ms = (time.time() - start_time) * 1000 # 성공 기록 cb.record_success() # 통계 업데이트 input_tokens = self._estimate_tokens(str(messages)) output_tokens = self._estimate_tokens(response.get("content", "")) cost = self._estimate_cost(attempt_model, input_tokens, output_tokens) self.stats["successful_requests"] += 1 self.stats["total_cost_usd"] += cost self.stats["total_tokens"] += input_tokens + output_tokens if attempt_model != selected_model: self.stats["fallback_count"] += 1 logger.info(f"🔄 폴백 발생: {selected_model} → {attempt_model}") return { "content": response.get("content", ""), "model": attempt_model, "latency_ms": latency_ms, "cost_usd": cost, "tokens": {"input": input_tokens, "output": output_tokens}, } except RateLimitError as e: logger.warning(f"Rate limit 에러: {attempt_model} - {e}") self.circuit_breakers[attempt_model].record_failure() last_error = e continue except (ServiceUnavailableError, InternalServerError) as e: logger.warning(f"서버 에러: {attempt_model} - {e}") self.circuit_breakers[attempt_model].record_failure() last_error = e continue except Exception as e: logger.error(f"예상치 못한 에러: {attempt_model} - {e}") self.circuit_breakers[attempt_model].record_failure() last_error = e continue # 모든 모델 실패 self.stats["failed_requests"] += 1 raise last_error or ServiceUnavailableError("모든 모델 호출 실패") async def _call_holysheep( self, model: str, messages: List[Dict[str, str]], **kwargs ) -> Dict[str, Any]: """HolySheep API 호출""" headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", } payload = { "model": model, "messages": messages, **kwargs } # 모델 매핑 (HolySheep 내부 모델명) model_mapping = { "claude-sonnet-4.5": "anthropic/claude-sonnet-4-20250514", "gpt-4.1": "openai/gpt-4.1", "gemini-2.5-flash": "google/gemini-2.5-flash-preview-05-20", "deepseek-v3.2": "deepseek/deepseek-v3.2", } api_model = model_mapping.get(model, model) response = await self.client.post( f"{self.base_url}/chat/completions", headers=headers, json={"model": api_model, "messages": messages, **kwargs} ) if response.status_code == 429: raise RateLimitError("Rate limit exceeded") elif response.status_code == 503: raise ServiceUnavailableError("Service unavailable") elif response.status_code >= 500: raise InternalServerError(f"Server error: {response.status_code}") elif response.status_code != 200: raise Exception(f"API error: {response.status_code} - {response.text}") data = response.json() return { "content": data["choices"][0]["message"]["