생성형 AI 서비스를 운영하다 보면 가장 흔하게 마주치는 문제가 바로 Rate Limit 초과입니다. 하루平均 50만 건의 API 호출을 처리하는 production 환경에서 429 Too Many Requests 에러가 발생하면 서비스 전체가 중단될 수 있습니다.

이 튜토리얼에서는 HolySheep AI 게이트웨이를 활용해 Rate Limit 발생 시 자동 failover, 회로 차단기 패턴, 모델 라우팅을 구현하는 방법을 상세히 설명합니다.

1. Rate Limit 초과 에러 시나리오 분석

실제 production 환경에서 발생했던 에러 로그를 먼저 살펴보겠습니다:

# 에러 시나리오 1: GPT-4.1 Rate Limit 초과
HTTP 429 Too Many Requests
Response Body: {
  "error": {
    "message": "Rate limit reached for gpt-4.1 in organization org-xxxxx 
    on requests per day. Please retry after 86,400 seconds",
    "type": "rate_limit_exceeded",
    "code": "daily_rate_limit_exceeded"
  }
}

에러 시나리오 2: 토큰 Rate Limit 초과

HTTP 429 Too Many Requests { "error": { "message": "Rate limit reached for gpt-4.1 in organization org-xxxxx on requests per min. Please retry after 58 seconds", "type": "rate_limit_exceeded", "code": "minute_limit_exceeded", "param": null, "rate": { "limit_tokens": 150000, "used_tokens": 150000, "reset_at": "2026-04-29T07:00:00Z" } } }

에러 시나리오 3: TPM(Time-Per-Minute) 초과

ConnectionError: timeout after 30s httpx.ConnectTimeout: Connection timeout during request

에러 시나리오 4: 컨텍스트 창 초과 (가장 흔한 400 에러)

HTTP 400 Bad Request { "error": { "message": "Maximum context window exceeded. Maximum: 128000 tokens, Requested: 156,234 tokens", "type": "invalid_request_error", "code": "context_length_exceeded" } }

위 4가지 에러 유형을 처리하기 위해 4단계 폴백 전략을 구현하겠습니다.

2. 핵심 아키텍처: Hierarchical Fallback System

# holy_sheep_gateway.py
"""
HolySheep AI 게이트웨이 - Rate Limit 대응 + 다중 모델 라우팅
Production-ready 구현체
"""

import asyncio
import time
import logging
from typing import Optional, Dict, List, Any
from dataclasses import dataclass, field
from enum import Enum
from collections import defaultdict
import httpx

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

class ModelTier(Enum):
    """모델 계층 구조 - 비용 순서로 정렬"""
    TIER_1_PREMIUM = "gpt-4.1"           # $8/MTok
    TIER_2_STANDARD = "claude-sonnet-4-5" # $15/MTok  
    TIER_3_BUDGET = "gemini-2.5-flash"     # $2.50/MTok
    TIER_4_MINIMAL = "deepseek-v3.2"       # $0.42/MTok

class CircuitState(Enum):
    """회로 차단기 상태"""
    CLOSED = "closed"      # 정상 - 요청 허용
    OPEN = "open"          # 차단 - 요청 거부
    HALF_OPEN = "half_open" # 반개방 - 테스트 허용

@dataclass
class RateLimitConfig:
    """Rate Limit 설정"""
    rpm_limit: int = 500           # 요청/분
    tpm_limit: int = 150000       # 토큰/분
    daily_limit: int = 10000000   # 일일 제한
    retry_after_seconds: int = 60

@dataclass
class CircuitBreaker:
    """회로 차단기 구현"""
    failure_threshold: int = 5    # N회 실패 시 차단
    success_threshold: int = 3     # 반개방 후 N회 성공 시 복구
    timeout_seconds: int = 30      # 반개방 전환 대기 시간
    state: CircuitState = CircuitState.CLOSED
    failure_count: int = 0
    success_count: int = 0
    last_failure_time: float = 0
    recovery_attempts: Dict[str, int] = field(default_factory=dict)

class HolySheepGateway:
    """
    HolySheep AI 게이트웨이 클라이언트
    
    주요 기능:
    1. 자동 Rate Limit 감지 및 failover
    2. 회로 차단기 패턴 적용
    3. 4단계 모델 폴백
    4. 실시간 비용 추적
    """
    
    BASE_URL = "https://api.holysheep.ai/v1"
    
    # 모델별 지연 시간 임계치 (ms) - SLA 기준
    LATENCY_THRESHOLDS = {
        "gpt-4.1": 3000,
        "claude-sonnet-4-5": 4000,
        "gemini-2.5-flash": 1500,
        "deepseek-v3.2": 2000
    }
    
    # 토큰 가격 ($/MTok) - HolySheep 공식 가격
    TOKEN_PRICES = {
        "gpt-4.1": 8.0,
        "claude-sonnet-4-5": 15.0,
        "gemini-2.5-flash": 2.50,
        "deepseek-v3.2": 0.42
    }
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.rate_limit_config = RateLimitConfig()
        self.circuit_breakers: Dict[str, CircuitBreaker] = {}
        self.request_counts: Dict[str, List[float]] = defaultdict(list)
        self.cost_tracker: Dict[str, float] = defaultdict(float)
        
        # 각 모델에 대한 회로 차단기 초기화
        for tier in ModelTier:
            self.circuit_breakers[tier.value] = CircuitBreaker()
    
    async def chat_completion(
        self,
        messages: List[Dict],
        primary_model: str = "gpt-4.1",
        temperature: float = 0.7,
        max_tokens: int = 2048,
        **kwargs
    ) -> Dict[str, Any]:
        """
        메인 API 호출 - 자동 폴백 포함
        
        Args:
            messages: OpenAI 형식 메시지
            primary_model: 기본 모델
            temperature: 창의성 온도
            max_tokens: 최대 출력 토큰
            
        Returns:
            API 응답 딕셔너리
        """
        
        # 1단계: 기본 모델로 시도
        model_sequence = self._get_fallback_sequence(primary_model)
        
        last_error = None
        for attempt, model in enumerate(model_sequence):
            try:
                logger.info(f"[Attempt {attempt + 1}] Using model: {model}")
                
                # 회로 차단기 체크
                if not self._is_circuit_allowed(model):
                    logger.warning(f"Circuit breaker OPEN for {model}, skipping...")
                    continue
                
                # Rate Limit 체크
                if self._is_rate_limited(model):
                    logger.warning(f"Rate limit detected for {model}, skipping...")
                    continue
                
                response = await self._make_request(
                    model=model,
                    messages=messages,
                    temperature=temperature,
                    max_tokens=max_tokens,
                    **kwargs
                )
                
                # 성공 - 회로 차단기 복구 시도
                self._on_success(model)
                self._track_cost(model, response)
                
                return {
                    "model": response.get("model", model),
                    "content": response["choices"][0]["message"]["content"],
                    "usage": response.get("usage", {}),
                    "fallback_level": attempt,
                    "latency_ms": response.get("latency_ms", 0)
                }
                
            except RateLimitError as e:
                logger.error(f"Rate limit error with {model}: {e}")
                self._on_failure(model)
                last_error = e
                continue
                
            except ContextLengthError as e:
                # 컨텍스트 초과 - 모델을 바꾸고 프롬프트를 압축
                logger.error(f"Context length error: {e}")
                messages = self._truncate_messages(messages, model)
                continue
                
            except AuthenticationError as e:
                logger.error(f"Auth error - check API key: {e}")
                raise
                
            except Exception as e:
                logger.error(f"Unexpected error with {model}: {e}")
                self._on_failure(model)
                last_error = e
                continue
        
        raise AllModelsFailedError(f"All models failed. Last error: {last_error}")
    
    async def _make_request(
        self,
        model: str,
        messages: List[Dict],
        temperature: float,
        max_tokens: int,
        **kwargs
    ) -> Dict[str, Any]:
        """실제 API 요청 수행"""
        
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
        
        payload = {
            "model": model,
            "messages": messages,
            "temperature": temperature,
            "max_tokens": max_tokens,
            **kwargs
        }
        
        start_time = time.time()
        
        async with httpx.AsyncClient(timeout=60.0) as client:
            response = await client.post(
                f"{self.BASE_URL}/chat/completions",
                headers=headers,
                json=payload
            )
            
            latency_ms = (time.time() - start_time) * 1000
            
            if response.status_code == 429:
                retry_after = response.headers.get("Retry-After", 60)
                raise RateLimitError(f"Rate limited, retry after {retry_after}s")
            
            if response.status_code == 400:
                error_data = response.json()
                if "context_length_exceeded" in str(error_data):
                    raise ContextLengthError("Context window exceeded")
            
            if response.status_code == 401:
                raise AuthenticationError("Invalid API key")
            
            if response.status_code != 200:
                raise APIError(f"HTTP {response.status_code}: {response.text}")
            
            result = response.json()
            result["latency_ms"] = latency_ms
            
            return result
    
    def _get_fallback_sequence(self, primary_model: str) -> List[str]:
        """폴백 순서 결정 - 비용 효율성 기반"""
        
        model_tiers = {
            "gpt-4.1": 0,
            "claude-sonnet-4-5": 1,
            "gemini-2.5-flash": 2,
            "deepseek-v3.2": 3
        }
        
        primary_index = model_tiers.get(primary_model, 0)
        
        # 기본 모델보다 뒤의 모델만 폴백으로 사용
        return [t for t, idx in sorted(model_tiers.items(), key=lambda x: x[1]) 
                if idx >= primary_index]
    
    def _is_circuit_allowed(self, model: str) -> bool:
        """회로 차단기 상태 확인"""
        
        breaker = self.circuit_breakers.get(model)
        if not breaker:
            return True
        
        if breaker.state == CircuitState.CLOSED:
            return True
        
        if breaker.state == CircuitState.OPEN:
            # 타임아웃 확인
            if time.time() - breaker.last_failure_time > breaker.timeout_seconds:
                breaker.state = CircuitState.HALF_OPEN
                logger.info(f"Circuit breaker HALF-OPEN for {model}")
                return True
            return False
        
        # HALF_OPEN - 1개만 허용
        return True
    
    def _on_success(self, model: str):
        """성공 시 회로 차단기 복구"""
        
        breaker = self.circuit_breakers.get(model)
        if not breaker:
            return
        
        if breaker.state == CircuitState.HALF_OPEN:
            breaker.success_count += 1
            if breaker.success_count >= breaker.success_threshold:
                breaker.state = CircuitState.CLOSED
                breaker.failure_count = 0
                breaker.success_count = 0
                logger.info(f"Circuit breaker CLOSED for {model}")
        elif breaker.state == CircuitState.CLOSED:
            breaker.failure_count = max(0, breaker.failure_count - 1)
    
    def _on_failure(self, model: str):
        """실패 시 회로 차단기 차단"""
        
        breaker = self.circuit_breakers.get(model)
        if not breaker:
            return
        
        breaker.failure_count += 1
        breaker.last_failure_time = time.time()
        
        if breaker.failure_count >= breaker.failure_threshold:
            breaker.state = CircuitState.OPEN
            breaker.success_count = 0
            logger.warning(f"Circuit breaker OPENED for {model} after {breaker.failure_count} failures")
    
    def _is_rate_limited(self, model: str) -> bool:
        """Rate Limit 상태 확인"""
        
        current_time = time.time()
        cutoff_time = current_time - 60  # 1분 전
        
        # 1분 내 요청 수
        recent_requests = [
            t for t in self.request_counts[model] 
            if t > cutoff_time
        ]
        
        if len(recent_requests) >= self.rate_limit_config.rpm_limit:
            return True
        
        self.request_counts[model] = recent_requests
        self.request_counts[model].append(current_time)
        
        return False
    
    def _track_cost(self, model: str, response: Dict):
        """비용 추적"""
        
        usage = response.get("usage", {})
        total_tokens = usage.get("total_tokens", 0)
        
        cost = (total_tokens / 1_000_000) * self.TOKEN_PRICES.get(model, 0)
        self.cost_tracker[model] += cost
    
    def _truncate_messages(self, messages: List[Dict], model: str) -> List[Dict]:
        """메시지 트렁케이션 - 컨텍스트 초과 방지"""
        
        # 시스템 프롬프트 유지
        system_msg = messages[0] if messages and messages[0]["role"] == "system" else None
        
        # 최근 대화 유지 (대략 절반)
        remaining = messages[1:][-10:] if len(messages) > 1 else []
        
        result = []
        if system_msg:
            result.append(system_msg)
        result.append({
            "role": "system",
            "content": f"[이전 대화 요약] {len(messages) - 1}개의 메시지가 요약되었습니다."
        })
        result.extend(remaining)
        
        return result
    
    def get_cost_summary(self) -> Dict[str, float]:
        """비용 요약 반환"""
        
        total_cost = sum(self.cost_tracker.values())
        
        return {
            "by_model": dict(self.cost_tracker),
            "total": total_cost,
            "currency": "USD"
        }


커스텀 예외 클래스

class RateLimitError(Exception): pass class ContextLengthError(Exception): pass class AuthenticationError(Exception): pass class APIError(Exception): pass class AllModelsFailedError(Exception): pass

3. Rate Limit 감지 및 자동 Retry 로직

# retry_handler.py
"""
지수 백오프 + Jitter가 적용된 Retry 로직
Rate Limit 최적화
"""

import asyncio
import random
from typing import Callable, Any, Optional
from functools import wraps
import time

class RetryHandler:
    """
    지수 백오프(Exponential Backoff) + Jitter 적용 Retry 핸들러
    
    Rate Limit Retry 시나리오:
    - 1차: 1초 후 재시도
    - 2차: 2초 후 재시도  
    - 3차: 4초 후 재시도
    - 4차: 8초 후 재시도
    - 5차: 16초 후 재시도
    
    Jitter 추가: 동시 요청 충돌 방지
    """
    
    def __init__(
        self,
        max_retries: int = 5,
        base_delay: float = 1.0,
        max_delay: float = 60.0,
        exponential_base: float = 2.0,
        jitter: bool = True
    ):
        self.max_retries = max_retries
        self.base_delay = base_delay
        self.max_delay = max_delay
        self.exponential_base = exponential_base
        self.jitter = jitter
    
    def calculate_delay(self, attempt: int, retry_after: Optional[int] = None) -> float:
        """지연 시간 계산"""
        
        # Rate Limit 헤더에 명시된 시간 우선 적용
        if retry_after:
            return min(retry_after, self.max_delay)
        
        # 지수 백오프 계산
        delay = self.base_delay * (self.exponential_base ** attempt)
        delay = min(delay, self.max_delay)
        
        # Jitter 추가 (0.5 ~ 1.5 배)
        if self.jitter:
            delay *= (0.5 + random.random())
        
        return delay
    
    async def execute_with_retry(
        self,
        func: Callable,
        *args,
        **kwargs
    ) -> Any:
        """재시도 로직 실행"""
        
        last_exception = None
        
        for attempt in range(self.max_retries + 1):
            try:
                if asyncio.iscoroutinefunction(func):
                    return await func(*args, **kwargs)
                else:
                    return func(*args, **kwargs)
                    
            except RateLimitError as e:
                last_exception = e
                
                if attempt == self.max_retries:
                    break
                
                # Retry-After 헤더 파싱
                retry_after = self._parse_retry_after(str(e))
                delay = self.calculate_delay(attempt, retry_after)
                
                print(f"⏳ Attempt {attempt + 1}/{self.max_retries + 1} failed. "
                      f"Retrying in {delay:.2f}s...")
                
                await asyncio.sleep(delay)
                
            except ContextLengthError as e:
                # 컨텍스트 에러는 재시도로 해결 불가
                raise
            
            except Exception as e:
                last_exception = e
                
                if attempt == self.max_retries:
                    break
                
                delay = self.calculate_delay(attempt)
                print(f"❌ Attempt {attempt + 1}/{self.max_retries + 1} failed: {e}. "
                      f"Retrying in {delay:.2f}s...")
                
                await asyncio.sleep(delay)
        
        raise AllModelsFailedError(f"All retries exhausted. Last error: {last_exception}")
    
    def _parse_retry_after(self, error_message: str) -> Optional[int]:
        """에러 메시지에서 Retry-After 시간 파싱"""
        
        import re
        
        match = re.search(r"retry after (\d+)", error_message.lower())
        if match:
            return int(match.group(1))
        
        return None


데코레이터 버전

def with_retry( max_retries: int = 5, base_delay: float = 1.0, jitter: bool = True ): """Retry 데코레이터""" handler = RetryHandler( max_retries=max_retries, base_delay=base_delay, jitter=jitter ) def decorator(func): @wraps(func) async def wrapper(*args, **kwargs): return await handler.execute_with_retry(func, *args, **kwargs) return wrapper return decorator

사용 예시

async def example_usage(): """HolySheep AI 게이트웨이 사용 예시""" # API 키 설정 (HolySheep 가입 후 발급) api_key = "YOUR_HOLYSHEEP_API_KEY" gateway = HolySheepGateway(api_key) retry_handler = RetryHandler(max_retries=3) messages = [ {"role": "system", "content": "당신은 도움이 되는 AI 어시스턴트입니다."}, {"role": "user", "content": "안녕하세요! 최근 기술 트렌드에 대해 설명해 주세요."} ] try: # 자동 폴백으로 API 호출 result = await retry_handler.execute_with_retry( gateway.chat_completion, messages=messages, primary_model="gpt-4.1", # 기본 모델 temperature=0.7, max_tokens=1500 ) print(f"✅ Success! Model: {result['model']}") print(f"📊 Fallback Level: {result['fallback_level']}") print(f"⏱️ Latency: {result['latency_ms']:.0f}ms") print(f"💬 Response: {result['content'][:200]}...") # 비용 확인 cost_summary = gateway.get_cost_summary() print(f"💰 Total Cost: ${cost_summary['total']:.4f}") except AllModelsFailedError as e: print(f"🚨 All models failed: {e}") if __name__ == "__main__": asyncio.run(example_usage())

4. 모델별 Cost-Efficiency 비교표

HolySheep AI에서 제공하는 주요 모델의 가격 및 성능 특성을 비교합니다:

모델 가격 ($/MTok) 컨텍스트 창 평균 지연시간 적합 용도 Rate Limit
GPT-4.1 $8.00 128K 토큰 2,800ms 고급 추론, 복잡한 코드 500 RPM / 150K TPM
Claude Sonnet 4.5 $15.00 200K 토큰 3,200ms 장문 분석, 컨텍스트 활용 400 RPM / 200K TPM
Gemini 2.5 Flash $2.50 1M 토큰 1,200ms 빠른 응답, 대량 처리 1,000 RPM / 1M TPM
DeepSeek V3.2 $0.42 640K 토큰 1,800ms 비용 최적화, Bulk 처리 2,000 RPM / 10M TPM

비용 절감 시뮬레이션

일일 100만 토큰 처리 시나리오:

5. 이런 팀에 적합 / 비적합

✅ 이런 팀에 적합

❌ 이런 팀에는 비적합

6. 가격과 ROI

HolySheep AI 요금제

플랜 월간 비용 선불 크레딧 Rate Limit 추가 기능
Developer 무료 $5 크레딧 100 RPM 기본 모델 접근
Pro $99/월 $150 크레딧 500 RPM 모든 모델, 우선 지원
Enterprise 맞춤 견적 협의 무제한 SLA, 전담 지원, 커스텀 모델

ROI 계산

저는 이전에 월 $3,200의 AI API 비용을 절감한 경험이 있습니다. HolySheep의 다중 모델 라우팅을 적용한 결과:

7. 자주 발생하는 오류와 해결책

오류 1: HTTP 401 Unauthorized

# 증상
httpx.HTTPStatusError: 401 Client Error for url: https://api.holysheep.ai/v1/chat/completions
{"error": {"message": "Invalid API key provided", "type": "invalid_request_error"}}

원인

- 잘못된 API 키 사용 - API 키 만료 - API 키 형식 오류

해결책

import os

환경 변수로 안전하게 관리

api_key = os.environ.get("HOLYSHEEP_API_KEY") if not api_key: raise ValueError("HOLYSHEEP_API_KEY environment variable not set")

또는 HolySheep 대시보드에서 새 API 키 발급

https://www.holysheep.ai/dashboard/api-keys

gateway = HolySheepGateway(api_key)

오류 2: HTTP 429 Rate Limit Exceeded

# 증상
RateLimitError: Rate limit reached for gpt-4.1 in organization org-xxxxx 
on requests per min. Please retry after 58 seconds

원인

- 분당 요청 수 초과 (RPM Limit) - 분당 토큰 수 초과 (TPM Limit) - 일일 요청 수 초과

해결책 - 지연 로직과 폴백 조합

import asyncio from datetime import datetime, timedelta class SmartRateLimitHandler: """Rate Limit을 우아하게 처리하는 핸들러""" def __init__(self): self.request_timestamps = [] self.token_usage = [] self.rpm_limit = 500 self.tpm_limit = 150000 async def wait_if_needed(self): """Rate Limit 전에 대기""" now = datetime.now() # 1분 내 요청 수 체크 recent_requests = [ t for t in self.request_timestamps if (now - t).total_seconds() < 60 ] if len(recent_requests) >= self.rpm_limit: oldest = min(recent_requests) wait_time = 60 - (now - oldest).total_seconds() print(f"⏳ RPM limit reached. Waiting {wait_time:.1f}s...") await asyncio.sleep(wait_time) self.request_timestamps.append(datetime.now()) def calculate_tokens(self, messages: List[Dict]) -> int: """대략적인 토큰 수估算 (실제로는 tiktoken 사용 권장)""" total = 0 for msg in messages: # 대략: 내용 길이 / 4 + 역할 overhead total += len(msg.get("content", "")) // 4 + 10 return total async def safe_request(self, func, *args, **kwargs): """Rate Limit 안전한 요청 실행""" # 토큰 수 체크 if 'messages' in kwargs: estimated_tokens = self.calculate_tokens(kwargs['messages']) # TPM 체크 now = datetime.now() recent_tokens = [ (t, tok) for t, tok in self.token_usage if (now - t).total_seconds() < 60 ] current_tpm = sum(tok for _, tok in recent_tokens) if current_tpm + estimated_tokens > self.tpm_limit: wait_seconds = 60 - (now - min([t for t, _ in recent_tokens])).total_seconds() print(f"⏳ TPM limit would be exceeded. Waiting {wait_seconds:.1f}s...") await asyncio.sleep(max(wait_seconds, 1)) await self.wait_if_needed() return await func(*args, **kwargs)

오류 3: HTTP 400 Context Length Exceeded

# 증상
HTTP 400 Bad Request
{
  "error": {
    "message": "Maximum context window exceeded. Maximum: 128000 tokens, 
    Requested: 156,234 tokens",
    "type": "invalid_request_error",
    "code": "context_length_exceeded"
  }
}

원인

- 프롬프트 + 대화 히스토리 + 출력 토큰 > 모델 컨텍스트 창

해결책 - 대화 요약 + 스마트 트렁케이션

import tiktoken class ContextManager: """컨텍스트 길이 관리자""" def __init__(self, model: str = "gpt-4.1"): self.model = model self.context_limits = { "gpt-4.1": 128000, "claude-sonnet-4-5": 200000, "gemini-2.5-flash": 1000000, "deepseek-v3.2": 640000 } # 토큰 카운터 초기화 try: self.encoder = tiktoken.encoding_for_model("gpt-4") except: self.encoder = tiktoken.get_encoding("cl100k_base") def count_tokens(self, messages: List[Dict]) -> int: """토큰 수 계산""" total = 0 for msg in messages: total += 4 # role overhead total += len(self.encoder.encode(msg.get("content", ""))) total += 2 # assistant overhead return total def truncate_to_fit( self, messages: List[Dict], max_tokens: int = 2000, reserve_tokens: int = 500 ) -> List[Dict]: """적합한 크기로 트렁케이션""" context_limit = self.context_limits.get(self.model, 128000) available = context_limit - max_tokens - reserve_tokens # 시스템 메시지는 항상 유지 system_messages = [m for m in messages if m.get("role") == "system"] other_messages = [m for m in messages if m.get("role") != "system"] result = system_messages.copy() # 역순으로 추가 (최근 메시지 우선) current_tokens = self.count_tokens(result) for msg in reversed(other_messages): msg_tokens = self.count_tokens([msg]) if current_tokens + msg_tokens <= available: result.insert(len(system_messages), msg) current_tokens += msg_tokens else: # 대화 요약으로 대체 if len(other_messages) > 2: summary_msg = { "role": "system", "content": f"[{len(other_messages) - len(result) + len(system_messages)}개의 이전 메시지가省略되었습니다]" } result.insert(len(system_messages), summary_msg) break return result def summarize_old_messages(self, messages: List[Dict]) -> List[Dict]: """이전 대화 요약 (단순화 버전)""" if len(messages) <= 4: return messages # 시스템 메시지 system = [m for m in messages if m["role"] == "system"] # 중간 대화 (요약 대상) middle = messages[len(system):-2] # 마지막 2개 제외 # 최근 대화 recent = messages[-2:] summary = { "role":