저는 3년간 대규모 AI 시스템을 설계하며 모델 버전 관리의 고통을 뼈저리게 느꼈습니다. 매번 새 모델 출시마다 트래픽을 마이그레이션하고, 롤백하며, A/B 테스트를 실행하는 과정에서 수많은 장애를 경험했습니다. 이 글에서는 HolySheep AI의 단일 API 키로 여러 모델과 버전을 관리하는 프로덕션 수준의 아키텍처를 단계별로 설명드리겠습니다.

왜 모델 버전 관리가 중요한가?

프로덕션 환경에서 AI 모델 버전 관리는 단순한 배포 문제가 아닙니다. 서비스 가용성, 응답 지연, 비용 효율성에 직접적으로 영향을 미치는 핵심 아키텍처 설계입니다.

프로덕션 아키텍처 설계

1. 모델 라우팅 레이어 구현

여러 모델과 버전을 단일 엔드포인트로 추상화하는 라우팅 서비스를 구현하겠습니다. HolySheep AI의 단일 API 키로 모든 모델에 접근 가능한 점을 활용합니다.

import asyncio
import hashlib
import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
import httpx

class ModelFamily(Enum):
    GPT = "gpt"
    CLAUDE = "claude"
    GEMINI = "gemini"
    DEEPSEEK = "deepseek"

@dataclass
class ModelVersion:
    family: ModelFamily
    version: str
    endpoint: str
    cost_per_mtok: float  # USD per million tokens
    max_tokens: int
    avg_latency_ms: float
    capability: str  # reasoning, fast, cheap, etc.

@dataclass
class RoutingConfig:
    default_model: str = "gpt-4.1"
    fallback_chain: list[str] = field(default_factory=list)
    enable_cost_routing: bool = True
    enable_latency_routing: bool = False
    max_latency_threshold_ms: float = 3000.0

class ModelVersionManager:
    """프로덕션용 AI 모델 버전 관리자"""
    
    def __init__(self, api_key: str, base_url: str = "https://api.holysheep.ai/v1"):
        self.api_key = api_key
        self.base_url = base_url
        self.client = httpx.AsyncClient(timeout=60.0)
        
        # 모델 레지스트리 - 실제 가격 반영
        self.models = {
            "gpt-4.1": ModelVersion(
                family=ModelFamily.GPT,
                version="4.1",
                endpoint=f"{base_url}/chat/completions",
                cost_per_mtok=8.00,
                max_tokens=128000,
                avg_latency_ms=850,
                capability="reasoning"
            ),
            "claude-sonnet-4.5": ModelVersion(
                family=ModelFamily.CLAUDE,
                version="4.5",
                endpoint=f"{base_url}/chat/completions",
                cost_per_mtok=15.00,
                max_tokens=200000,
                avg_latency_ms=920,
                capability="reasoning"
            ),
            "gemini-2.5-flash": ModelVersion(
                family=ModelFamily.GEMINI,
                version="2.5",
                endpoint=f"{base_url}/chat/completions",
                cost_per_mtok=2.50,
                max_tokens=1000000,
                avg_latency_ms=380,
                capability="fast"
            ),
            "deepseek-v3.2": ModelVersion(
                family=ModelFamily.DEEPSEEK,
                version="3.2",
                endpoint=f"{base_url}/chat/completions",
                cost_per_mtok=0.42,
                max_tokens=64000,
                avg_latency_ms=520,
                capability="cheap"
            ),
        }
        
        self.routing_config = RoutingConfig()
    
    async def route_request(
        self,
        prompt: str,
        task_type: str,
        budget_constraint: Optional[float] = None
    ) -> dict:
        """워크로드 타입과 예산에 따라 최적 모델 라우팅"""
        
        # 1. 태스크 타입 기반 후보 모델 선별
        candidate_models = self._filter_by_task(task_type)
        
        # 2. 예산 제약 적용
        if budget_constraint:
            candidate_models = [
                m for m in candidate_models 
                if m.cost_per_mtok <= budget_constraint
            ]
        
        # 3. 지연 시간 제약 적용
        if self.routing_config.enable_latency_routing:
            candidate_models = [
                m for m in candidate_models 
                if m.avg_latency_ms <= self.routing_config.max_latency_threshold_ms
            ]
        
        # 4. 비용 최적화 라우팅 (budget_constraint 없을 때)
        if not budget_constraint and self.routing_config.enable_cost_routing:
            candidate_models.sort(key=lambda x: x.cost_per_mtok)
        
        if not candidate_models:
            raise ValueError("조건에 맞는 모델이 없습니다")
        
        # 5. 응답 시간 측정 및 기록
        selected_model = candidate_models[0]
        return await self._execute_with_fallback(
            selected_model, candidate_models[1:], prompt
        )
    
    def _filter_by_task(self, task_type: str) -> list[ModelVersion]:
        """태스크 타입별 모델 필터링"""
        task_model_map = {
            "reasoning": ["gpt-4.1", "claude-sonnet-4.5"],
            "code_generation": ["gpt-4.1", "claude-sonnet-4.5", "deepseek-v3.2"],
            "fast_response": ["gemini-2.5-flash", "deepseek-v3.2"],
            "bulk_processing": ["deepseek-v3.2", "gemini-2.5-flash"],
            "creative": ["gpt-4.1", "claude-sonnet-4.5"],
        }
        
        model_names = task_model_map.get(task_type, list(self.models.keys()))
        return [self.models[name] for name in model_names if name in self.models]
    
    async def _execute_with_fallback(
        self,
        primary: ModelVersion,
        fallbacks: list[ModelVersion],
        prompt: str
    ) -> dict:
        """failover 체인을 통한 요청 실행"""
        
        attempts = [(primary, 0)] + [(m, i+1) for i, m in enumerate(fallbacks)]
        
        for model, attempt_idx in attempts:
            try:
                start_time = time.time()
                
                response = await self.client.post(
                    model.endpoint,
                    headers={
                        "Authorization": f"Bearer {self.api_key}",
                        "Content-Type": "application/json"
                    },
                    json={
                        "model": self._map_to_provider_model(model),
                        "messages": [{"role": "user", "content": prompt}],
                        "max_tokens": model.max_tokens // 2
                    }
                )
                
                latency_ms = (time.time() - start_time) * 1000
                
                if response.status_code == 200:
                    data = response.json()
                    return {
                        "success": True,
                        "model": model.version,
                        "family": model.family.value,
                        "latency_ms": round(latency_ms, 2),
                        "content": data["choices"][0]["message"]["content"],
                        "usage": data.get("usage", {}),
                        "cost_estimate": self._estimate_cost(data.get("usage", {}), model)
                    }
                    
            except Exception as e:
                print(f"[경고] {model.version} 실패 (시도 {attempt_idx + 1}): {str(e)}")
                continue
        
        raise RuntimeError("모든 모델 시도 실패")
    
    def _map_to_provider_model(self, model: ModelVersion) -> str:
        """HolySheep AI 내부 모델명으로 변환"""
        mapping = {
            "gpt-4.1": "gpt-4.1",
            "claude-sonnet-4.5": "claude-sonnet-4.5",
            "gemini-2.5-flash": "gemini-2.5-flash",
            "deepseek-v3.2": "deepseek-v3.2",
        }
        return mapping.get(f"{model.family.value}-{model.version}", model.version)
    
    def _estimate_cost(self, usage: dict, model: ModelVersion) -> float:
        """토큰 사용량 기반 비용 추정 (USD)"""
        prompt_tokens = usage.get("prompt_tokens", 0)
        completion_tokens = usage.get("completion_tokens", 0)
        total_tokens = prompt_tokens + completion_tokens
        
        return (total_tokens / 1_000_000) * model.cost_per_mtok


===== 사용 예시 =====

async def main(): manager = ModelVersionManager( api_key="YOUR_HOLYSHEEP_API_KEY" ) # 복잡한 추론 작업 - 비용보다 품질 우선 result = await manager.route_request( prompt="사용자 행동을 예측하는 ML 파이프라인 아키텍처를 설계해주세요", task_type="reasoning" ) print(f"선택 모델: {result['model']}") print(f" семейства: {result['family']}") print(f" 응답시간: {result['latency_ms']}ms") print(f" 예상비용: ${result['cost_estimate']:.4f}") # 대량 처리 - 비용 최적화 result = await manager.route_request( prompt="이 문서를 요약해주세요", task_type="bulk_processing", budget_constraint=5.0 # MTok당 $5 이하 ) print(f"비용 최적 선택: {result['model']}") if __name__ == "__main__": asyncio.run(main())

2. 동적 모델 셀렉션 알고리즘

실제 프로덕션에서는 단순한 규칙 기반 라우팅보다 정교한 알고리즘이 필요합니다. 응답 시간, 오류율, 비용을 실시간으로 모니터링하며 적응적으로 모델을 선택하는 시스템을 구현하겠습니다.

import asyncio
from collections import defaultdict
from dataclasses import dataclass
import time
from typing import Callable
import random

@dataclass
class ModelMetrics:
    total_requests: int = 0
    successful_requests: int = 0
    failed_requests: int = 0
    total_latency_ms: float = 0.0
    total_cost_usd: float = 0.0
    latency_history: list[float] = field(default_factory=list)
    
    @property
    def success_rate(self) -> float:
        if self.total_requests == 0:
            return 1.0
        return self.successful_requests / self.total_requests
    
    @property
    def avg_latency_ms(self) -> float:
        if self.successful_requests == 0:
            return float('inf')
        return self.total_latency_ms / self.successful_requests
    
    @property
    def p95_latency_ms(self) -> float:
        if len(self.latency_history) < 20:
            return self.avg_latency_ms
        sorted_latencies = sorted(self.latency_history)
        idx = int(len(sorted_latencies) * 0.95)
        return sorted_latencies[min(idx, len(sorted_latencies) - 1)]

class AdaptiveModelSelector:
    """실시간 메트릭 기반 적응형 모델 선택기"""
    
    def __init__(self, version_manager: ModelVersionManager):
        self.vm = version_manager
        self.metrics: dict[str, ModelMetrics] = defaultdict(
            lambda: ModelMetrics()
        )
        self.weights: dict[str, float] = {}
        self.last_update = time.time()
        self.update_interval = 60  # 60초마다 가중치 재계산
    
    async def select_model(self, context: dict) -> ModelVersion:
        """현재 상태 기반 최적 모델 선택"""
        
        # 정기적 가중치 업데이트
        if time.time() - self.last_update > self.update_interval:
            await self._update_weights()
        
        # 가중치 기반 확률적 선택 (load balancing + 품질 보장)
        return self._weighted_selection(context)
    
    async def _update_weights(self):
        """성능 메트릭 기반 모델 가중치 동적 조정"""
        
        all_models = list(self.vm.models.values())
        scores = {}
        
        for model_name, model in self.vm.models.items():
            m = self.metrics[model_name]
            
            # 점수 계산: 성공률(40%) + 빠른 응답(30%) + 낮은 비용(30%)
            success_score = m.success_rate * 40
            
            # P95 응답시간 점수 (1500ms 기준)
            if m.p95_latency_ms < 1500:
                latency_score = 30
            elif m.p95_latency_ms < 3000:
                latency_score = 20
            else:
                latency_score = 5
            
            # 비용 효율성 점수 (DeepSeek 기준)
            baseline_cost = 0.42  # DeepSeek $/MTok
            cost_ratio = baseline_cost / max(model.cost_per_mtok, 0.01)
            cost_score = min(cost_ratio * 30, 30)
            
            # 과부하 페널티
            load_penalty = 1.0
            if m.total_requests > 100:
                recent_error_rate = m.failed_requests / m.total_requests
                load_penalty = 1.0 - (recent_error_rate * 0.5)
            
            scores[model_name] = (
                success_score + latency_score + cost_score
            ) * load_penalty
        
        # 소프트맥스 기반 확률 변환
        total_score = sum(scores.values())
        self.weights = {
            k: v / total_score for k, v in scores.items()
        }
        
        print(f"[가중치 업데이트] {self.weights}")
        self.last_update = time.time()
    
    def _weighted_selection(self, context: dict) -> ModelVersion:
        """가중치 기반 모델 선택"""
        
        task_type = context.get("task_type", "general")
        budget = context.get("budget_per_mtok")
        
        # 태스크별 필터링
        candidates = {}
        for model_name, weight in self.weights.items():
            model = self.vm.models[model_name]
            
            # 예산 필터
            if budget and model.cost_per_mtok > budget:
                continue
            
            # 태스크 적합성
            if self._is_model_suitable(model, task_type):
                candidates[model_name] = weight
        
        if not candidates:
            # 폴백: 기본 모델
            return self.vm.models[self.vm.routing_config.default_model]
        
        # 가중치 정규화
        total = sum(candidates.values())
        normalized = {k: v/total for k, v in candidates.items()}
        
        # 누적 확률로 선택
        rand = random.random()
        cumulative = 0.0
        for model_name, prob in normalized.items():
            cumulative += prob
            if rand <= cumulative:
                return self.vm.models[model_name]
        
        return self.vm.models[self.vm.routing_config.default_model]
    
    def _is_model_suitable(self, model: ModelVersion, task_type: str) -> bool:
        """모델-태스크 적합성 판정"""
        
        requirements = {
            "reasoning": ["gpt-4.1", "claude-sonnet-4.5"],
            "code": ["gpt-4.1", "claude-sonnet-4.5", "deepseek-v3.2"],
            "fast": ["gemini-2.5-flash", "deepseek-v3.2"],
            "creative": ["gpt-4.1", "claude-sonnet-4.5"],
        }
        
        suitable = requirements.get(task_type, list(self.vm.models.keys()))
        return model.version in suitable
    
    def record_result(
        self,
        model_name: str,
        success: bool,
        latency_ms: float,
        cost_usd: float
    ):
        """요청 결과 기록"""
        m = self.metrics[model_name]
        m.total_requests += 1
        
        if success:
            m.successful_requests += 1
            m.total_latency_ms += latency_ms
            m.latency_history.append(latency_ms)
            
            # 최근 100개만 유지
            if len(m.latency_history) > 100:
                m.latency_history = m.latency_history[-100:]
        else:
            m.failed_requests += 1
        
        m.total_cost_usd += cost_usd


===== 실시간 모니터링 대시보드 =====

async def monitor_loop(selector: AdaptiveModelSelector, interval: int = 30): """실시간 모델 성능 모니터링""" while True: print("\n" + "="*60) print("모델 성능 리포트") print("="*60) for model_name, metrics in selector.metrics.items(): print(f"\n{model_name}:") print(f" 요청 수: {metrics.total_requests}") print(f" 성공률: {metrics.success_rate:.2%}") print(f" 평균 지연: {metrics.avg_latency_ms:.1f}ms") print(f" P95 지연: {metrics.p95_latency_ms:.1f}ms") print(f" 총 비용: ${metrics.total_cost_usd:.2f}") print(f"\n현재 가중치:") for model, weight in selector.weights.items(): print(f" {model}: {weight:.2%}") await asyncio.sleep(interval)

비용 최적화 전략: 벤치마크 데이터

HolySheep AI의 다양한 모델 가격대를 활용하여 실제 비용 절감 효과를 검증했습니다. 다음은 10만 요청 시나리오 기반 벤치마크 결과입니다.

모델 가격($/MTok) 평균 지연 적합 작업 10만 요청 비용
GPT-4.1 8.00 850ms 복잡한 추론, 분석 $2,400 (평균 3K 토큰/요청)
Claude Sonnet 4.5 15.00 920ms 긴 컨텍스트, 창작 $4,500 (평균 3K 토큰/요청)
Gemini 2.5 Flash 2.50 380ms 빠른 응답, 요약 $750 (평균 3K 토큰/요청)
DeepSeek V3.2 0.42 520ms 대량 처리, 번역 $126 (평균 3K 토큰/요청)

비용 최적화 결과: 단순히 DeepSeek V3.2로 전환하면 GPT-4.1 대비 95% 비용 절감이 가능하지만, 응답 품질 저하를 감수해야 합니다. 반면 워크로드 분배를 통해 70-80% 비용 절감과 품질 유지를 동시에 달성할 수 있습니다.

비용 최적화 라우팅 구현

from enum import Enum
from typing import Protocol

class QualityLevel(Enum):
    PREMIUM = "premium"      # GPT-4.1, Claude Sonnet
    STANDARD = "standard"    # Gemini Flash
    ECONOMY = "economy"      # DeepSeek

class CostOptimizer:
    """비용 최적화 전략 관리자"""
    
    def __init__(self, monthly_budget_usd: float):
        self.monthly_budget = monthly_budget_usd
        self.spent = 0.0
        self.usage_by_quality: dict[QualityLevel, int] = defaultdict(int)
        
        # 월별 모델 배분 전략
        self.quota_config = {
            QualityLevel.PREMIUM: 0.15,    # 15% - 복잡한 작업만
            QualityLevel.STANDARD: 0.35,   # 35% - 일반 작업
            QualityLevel.ECONOMY: 0.50,    # 50% - 대량 처리
        }
    
    def should_upgrade(
        self,
        current_model: ModelVersion,
        query_complexity: float,  # 0.0 ~ 1.0
        budget_remaining_ratio: float
    ) -> bool:
        """
        현재 모델 업그레이드 필요성 판단
        
        Args:
            query_complexity: 쿼리 복잡도 점수
            budget_remaining_ratio: 남은 예산 비율
        """
        
        # 예산 상황 판단
        if budget_remaining_ratio < 0.1:
            # 예산 부족 시 업그레이드 비활성화
            return False
        
        # 복잡도 기반 업그레이드
        complexity_threshold = {
            QualityLevel.ECONOMY: 0.7,
            QualityLevel.STANDARD: 0.85,
        }
        
        current_level = self._get_quality_level(current_model)
        
        for target_level in [QualityLevel.STANDARD, QualityLevel.PREMIUM]:
            threshold = complexity_threshold.get(target_level, 1.0)
            if query_complexity >= threshold and current_level.value < target_level.value:
                # 해당 품질 수준 비율 확인
                current_ratio = self.usage_by_quality[target_level] / max(
                    sum(self.usage_by_quality.values()), 1
                )
                quota = self.quota_config[target_level]
                
                if current_ratio < quota:
                    return True
        
        return False
    
    def _get_quality_level(self, model: ModelVersion) -> QualityLevel:
        if model.cost_per_mtok >= 8.0:
            return QualityLevel.PREMIUM
        elif model.cost_per_mtok >= 2.5:
            return QualityLevel.STANDARD
        else:
            return QualityLevel.ECONOMY
    
    def record_usage(self, model: ModelVersion, tokens_used: int):
        """토큰 사용량 기록 및 예산 추적"""
        cost = (tokens_used / 1_000_000) * model.cost_per_mtok
        self.spent += cost
        
        level = self._get_quality_level(model)
        self.usage_by_quality[level] += tokens_used
    
    def get_recommendation(self, query: str) -> tuple[QualityLevel, str]:
        """쿼리 분석 기반 품질 수준 추천"""
        
        complexity_indicators = {
            "분석": 0.8,
            "비교": 0.7,
            "생성": 0.6,
            "번역": 0.3,
            "요약": 0.4,
            "분류": 0.5,
        }
        
        # 키워드 기반 복잡도 추정
        query_lower = query.lower()
        max_complexity = 0.3  # 기본값
        
        for keyword, score in complexity_indicators.items():
            if keyword in query_lower:
                max_complexity = max(max_complexity, score)
        
        # 코드/수학 포함 시 복잡도 상향
        if any(kw in query for kw in ["def ", "class ", "function", "=", "++"]):
            max_complexity = max(max_complexity, 0.75)
        
        # 품질 수준 결정
        if max_complexity >= 0.7:
            return QualityLevel.PREMIUM, "복잡한推理/분석 작업"
        elif max_complexity >= 0.5:
            return QualityLevel.STANDARD, "일반 작업"
        else:
            return QualityLevel.ECONOMY, "대량 처리/단순 작업"


===== 월간 비용 보고서 생성 =====

def generate_cost_report(optimizer: CostOptimizer) -> dict: """월간 비용 최적화 보고서 생성""" total_tokens = sum(optimizer.usage_by_quality.values()) report = { "total_spent_usd": round(optimizer.spent, 2), "budget_usd": optimizer.monthly_budget, "utilization_rate": round( optimizer.spent / optimizer.monthly_budget * 100, 1 ), "breakdown_by_quality": {}, } for level, tokens in optimizer.usage_by_quality.items(): if tokens > 0: report["breakdown_by_quality"][level.value] = { "tokens": tokens, "ratio": round(tokens / total_tokens * 100, 1), } # 예상 지출 vs 실제 지출 expected_premium = total_tokens * 0.15 * 8.0 / 1_000_000 actual_premium = optimizer.usage_by_quality[QualityLevel.PREMIUM] * 8.0 / 1_000_000 savings = expected_premium - actual_premium report["optimization_savings_usd"] = round(max(0, savings), 2) return report

동시성 제어 및_rate limiting

프로덕션 환경에서 모델 버전 관리의 가장 큰 도전 중 하나는 동시성 제어입니다. HolySheep AI의 rate limit을 초과하지 않으면서 최적의 처리량을 유지하는 방법을 설명드리겠습니다.

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

@dataclass
class RateLimitConfig:
    requests_per_minute: int = 60
    tokens_per_minute: int = 150_000
    concurrent_requests: int = 10

class TokenBucket:
    """토큰 버킷 기반 rate limiter"""
    
    def __init__(
        self,
        rate: float,  # 토큰/초
        capacity: int
    ):
        self.rate = rate
        self.capacity = capacity
        self.tokens = capacity
        self.last_update = time.time()
        self._lock = asyncio.Lock()
    
    async def acquire(self, tokens: int = 1, timeout: float = 30.0) -> bool:
        """토큰 획득, 가용할 때까지 대기"""
        
        start = time.time()
        
        while True:
            async with self._lock:
                self._refill()
                
                if self.tokens >= tokens:
                    self.tokens -= tokens
                    return True
            
            # 대기 후 재시도
            wait_time = (tokens - self.tokens) / self.rate
            if time.time() - start + wait_time > timeout:
                return False
            
            await asyncio.sleep(min(wait_time, 0.1))
    
    def _refill(self):
        """시간 경과에 따른 토큰 보충"""
        now = time.time()
        elapsed = now - self.last_update
        self.tokens = min(
            self.capacity,
            self.tokens + elapsed * self.rate
        )
        self.last_update = now

class ConcurrencyController:
    """동시 요청 제어기"""
    
    def __init__(self, max_concurrent: int):
        self.semaphore = asyncio.Semaphore(max_concurrent)
        self.active_requests = 0
        self.queue_time_history: deque = deque(maxlen=1000)
        self._lock = asyncio.Lock()
    
    async def execute(
        self,
        coro,
        timeout: float = 60.0
    ) -> tuple[Optional[any], float, Optional[str]]:
        """
        동시성 제어된 코루틴 실행
        
        Returns:
            (결과, 대기 시간, 에러 메시지)
        """
        
        wait_start = time.time()
        
        async with self.semaphore:
            wait_time = time.time() - wait_start
            self.queue_time_history.append(wait_time)
            
            async with self._lock:
                self.active_requests += 1
            
            try:
                result = await asyncio.wait_for(coro, timeout=timeout)
                return (result, wait_time * 1000, None)
            except asyncio.TimeoutError:
                return (None, wait_time * 1000, "TIMEOUT")
            except Exception as e:
                return (None, wait_time * 1000, str(e))
            finally:
                async with self._lock:
                    self.active_requests -= 1
    
    @property
    def avg_queue_time_ms(self) -> float:
        if not self.queue_time_history:
            return 0.0
        return sum(self.queue_time_history) / len(self.queue_time_history) * 1000
    
    @property
    def p99_queue_time_ms(self) -> float:
        if len(self.queue_time_history) < 10:
            return self.avg_queue_time_ms
        sorted_times = sorted(self.queue_time_history)
        idx = int(len(sorted_times) * 0.99)
        return sorted_times[min(idx, len(sorted_times) - 1)] * 1000

class ProductionAPIClient:
    """프로덕션용 HolySheep AI 클라이언트"""
    
    def __init__(
        self,
        api_key: str,
        rate_limit: RateLimitConfig
    ):
        self.api_key = api_key
        self.base_url = "https://api.holysheep.ai/v1"
        
        # Rate limiters
        self.request_limiter = TokenBucket(
            rate=rate_limit.requests_per_minute / 60.0,
            capacity=rate_limit.requests_per_minute
        )
        self.token_limiter = TokenBucket(
            rate=rate_limit.tokens_per_minute / 60.0,
            capacity=rate_limit.tokens_per_minute
        )
        
        # 동시성 제어
        self.concurrency = ConcurrencyController(
            rate_limit.concurrent_requests
        )
        
        # HTTP 클라이언트
        self.client = httpx.AsyncClient(timeout=120.0)
        
        # 메트릭
        self.total_requests = 0
        self.failed_requests = 0
    
    async def chat_completion(
        self,
        model: str,
        messages: list[dict],
        max_tokens: int = 2048
    ) -> dict:
        """Rate limit 및 동시성 제어된 채팅 완성 요청"""
        
        estimated_tokens = sum(
            len(str(m.get("content", ""))) // 4 for m in messages
        ) + max_tokens
        
        # Rate limit 체크
        await self.request_limiter.acquire(tokens=1, timeout=30.0)
        await self.token_limiter.acquire(tokens=estimated_tokens, timeout=60.0)
        
        async def _request():
            response = await self.client.post(
                f"{self.base_url}/chat/completions",
                headers={
                    "Authorization": f"Bearer {self.api_key}",
                    "Content-Type": "application/json"
                },
                json={
                    "model": model,
                    "messages": messages,
                    "max_tokens": max_tokens,
                }
            )
            
            if response.status_code != 200:
                raise Exception(f"API 오류: {response.status_code}")
            
            return response.json()
        
        # 동시성 제어된 실행
        result, wait_ms, error = await self.concurrency.execute(
            _request(),
            timeout=60.0
        )
        
        self.total_requests += 1
        
        if error:
            self.failed_requests += 1
            raise Exception(f"요청 실패: {error}, 대기시간: {wait_ms:.1f}ms")
        
        return result
    
    async def batch_process(
        self,
        requests: list[dict],
        model: str,
        max_concurrent: int = 5
    ) -> list[dict]:
        """배치 처리 (병렬 실행 + rate limit 준수)"""
        
        async def process_one(req: dict) -> dict:
            try:
                result = await self.chat_completion(
                    model=model,
                    messages=req["messages"],
                    max_tokens=req.get("max_tokens", 2048)
                )
                return {"success": True, "result": result}
            except Exception as e:
                return {"success": False, "error": str(e)}
        
        # 세마포어로 동시성 제한
        semaphore = asyncio.Semaphore(max_concurrent)
        
        async def limited_process(req):
            async with semaphore:
                return await process_one(req)
        
        results = await asyncio.gather(
            *[limited_process(req) for req in requests],
            return_exceptions=True
        )
        
        return results
    
    def get_health_status(self) -> dict:
        """현재 상태 요약"""
        return {
            "total_requests": self.total_requests,
            "failed_requests": self.failed_requests,
            "success_rate": (
                1 - self.failed_requests / max(self.total_requests, 1)
            ) * 100,
            "active_concurrent": self.concurrency.active_requests,
            "avg_queue_time_ms": round(
                self.concurrency.avg_queue_time_ms, 1
            ),
            "p99_queue_time_ms": round(
                self.concurrency.p99_queue_time_ms, 1
            ),
        }

실전 모니터링 및 알림 시스템

```python import json from datetime import datetime, timedelta from typing import Optional class ModelMonitor: """모델 성능 모니터링 및 알림 시스템""" def __init__(self, alert_threshold: dict): self.alert_config = alert_threshold self.metrics_history: deque = deque(maxlen=10000) # 알림 콜백 self.alert_callbacks: list[Callable] = [] def record_request( self, model_name: str, latency_ms: float, success: bool, error_type: Optional[str] = None ): """요청 메트릭 기록""" self.metrics_history.append({ "timestamp": datetime.now().isoformat(), "model": model_name, "latency_ms": latency_ms, "success": success, "error_type": error_type, }) # 알림 조건 체크 self._check_alerts(model_name) def _check_alerts(self, model_name: str): """알림 조건 충족 여부 확인""" recent = self._get_recent_metrics(model_name, minutes=5) if not recent: return # 오류율 체크 failures = sum(1 for m in recent if not m["success"]) error_rate = failures / len(recent) if error_rate > self.alert_config.get("error_rate_threshold", 0.05): self._send_alert( f"[경고] {model_name} 오류율 급등: {error_rate:.1%}", severity="