안녕하세요, 저는 HolySheep AI의 시니어 엔지니어입니다. 이번 튜토리얼에서는 LangChain의 스트리밍(Streaming) 출력机制을 깊이 파고들고, 이를 프론트엔드와 실시간 통합하는 프로덕션 레벨 아키텍처를 다루겠습니다. API 호출 지연 시간부터 동시성 제어, 비용 최적화까지 실전에서 검증된 전략을 공유합니다.

왜 스트리밍이 중요한가?

AI 응답의 체감 지연 시간은用户体验의 핵심 지표입니다. 스트리밍 없이 30초 소요되는 응답을 스트리밍 적용 시 TTFT(Time To First Token)를 500ms 이하로 줄일 수 있어, Claude Sonnet 4.5나 GPT-4.1 같은 고가 모델에서 사용자 이탈률을 크게 낮출 수 있습니다.

HolySheep AI 게이트웨이(지금 가입)는 스트리밍 응답에 최적화된 인프라를 제공하며, DeepSeek V3.2의 경우 $0.42/MTok라는 혁신적인 가격으로 스트리밍 기반 애플리케이션의 운영 비용을 획기적으로 절감할 수 있습니다.

아키텍처 설계

스트리밍 아키텍처의 핵심은 이벤트 기반 파이프라인입니다. 다음은 전체 데이터 플로우입니다:

┌─────────────┐     ┌─────────────────┐     ┌──────────────┐     ┌─────────────┐
│   Frontend   │────▶│   FastAPI/BFF   │────▶│  LangChain   │────▶│  HolySheep  │
│  (SSE/WSS)   │◀────│  (Streaming)    │◀────│  Callback    │◀────│  AI Gateway │
└─────────────┘     └─────────────────┘     └──────────────┘     └─────────────┘
     │                     │                      │
     ▼                     ▼                      ▼
┌─────────────┐     ┌─────────────────┐     ┌──────────────┐
│  사용자 화면 │     │  버퍼링/재시도   │     │ 토큰 카운팅  │
│  실시간 갱신 │     │  에러 처리      │     │  비용 추적   │
└─────────────┘     └─────────────────┘     └──────────────┘

1. LangChain 스트리밍 기본 구현

1.1 커스텀 Callback Handler 만들기

LangChain의 스트리밍 핵심은 CallbackHandler 인터페이스입니다. 저는 실제 프로덕션 환경에서 검증된 StreamingCallbackHandler를 구현하겠습니다.

import asyncio
import time
from typing import Any, Dict, List, Optional
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.outputs import LLMResult
from dataclasses import dataclass, field
from datetime import datetime
import logging

logger = logging.getLogger(__name__)


@dataclass
class TokenMetrics:
    """토큰 메트릭 수집용 데이터 클래스"""
    tokens_per_second: float = 0.0
    total_tokens: int = 0
    first_token_latency_ms: float = 0.0
    completion_time_ms: float = 0.0
    start_time: Optional[float] = None
    first_token_time: Optional[float] = None


class StreamingCallbackHandler(BaseCallbackHandler):
    """
    HolySheep AI 스트리밍 응답용 커스텀 콜백 핸들러
    
    주요 기능:
    - 실시간 토큰 스트리밍
    - TTFT(Time To First Token) 측정
    - 토큰 처리량 모니터링
    - 비용 추적 (HolySheep AI 가격 정책 적용)
    """
    
    def __init__(
        self,
        token_buffer: asyncio.Queue,
        model_name: str = "gpt-4.1",
        api_provider: str = "holysheep"
    ):
        self.token_buffer = token_buffer
        self.metrics = TokenMetrics()
        self.model_name = model_name
        self.api_provider = api_provider
        self._tokens_received = []
        
        # HolySheep AI 가격 정책 (2024년 기준, USD per 1M tokens)
        self.pricing = {
            "gpt-4.1": 8.0,          # $8/MTok
            "claude-sonnet-4": 15.0,  # $15/MTok
            "gemini-2.5-flash": 2.5,  # $2.50/MTok
            "deepseek-v3.2": 0.42,   # $0.42/MTok
        }
        
    def on_llm_start(
        self,
        serialized: Dict[str, Any],
        prompts: List[str],
        **kwargs: Any
    ) -> None:
        """LLM 호출 시작 시 실행"""
        self.metrics = TokenMetrics()
        self.metrics.start_time = time.perf_counter()
        self._tokens_received = []
        logger.info(f"[LLM Start] Model: {self.model_name}, Prompt tokens: ~{len(prompts[0])}")

    def on_llm_new_token(
        self,
        token: str,
        *,
        chunk: Optional[Any] = None,
        **kwargs: Any
    ) -> None:
        """새 토큰 수신 시 실행 - 스트리밍 핵심"""
        current_time = time.perf_counter()
        
        # TTFT 측정
        if self.metrics.first_token_time is None:
            self.metrics.first_token_time = current_time
            if self.metrics.start_time:
                self.metrics.first_token_latency_ms = (
                    current_time - self.metrics.start_time
                ) * 1000
                logger.info(
                    f"[TTFT] {self.metrics.first_token_latency_ms:.2f}ms"
                )
        
        # 토큰 버퍼에 추가
        self._tokens_received.append(token)
        self.metrics.total_tokens = len(self._tokens_received)
        
        # 버퍼가 가득차면 비동기로 전달
        if len(token) > 0:
            asyncio.create_task(self.token_buffer.put({
                "token": token,
                "timestamp": current_time,
                "total_so_far": self.metrics.total_tokens
            }))

    def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
        """LLM 호출 완료 시 실행"""
        if self.metrics.start_time:
            self.metrics.completion_time_ms = (
                time.perf_counter() - self.metrics.start_time
            ) * 1000
            
            if self.metrics.completion_time_ms > 0:
                self.metrics.tokens_per_second = (
                    self.metrics.total_tokens / 
                    (self.metrics.completion_time_ms / 1000)
                )
        
        # 비용 계산
        cost_usd = self._calculate_cost()
        logger.info(
            f"[LLM End] Total tokens: {self.metrics.total_tokens}, "
            f"Time: {self.metrics.completion_time_ms:.2f}ms, "
            f"TPS: {self.metrics.tokens_per_second:.2f}, "
            f"Cost: ${cost_usd:.6f}"
        )
        
    def _calculate_cost(self) -> float:
        """HolySheep AI 가격 정책 기반 비용 계산"""
        price_per_mtok = self.pricing.get(
            self.model_name, 
            self.pricing["gpt-4.1"]
        )
        # 출력 토큰만 계산 (입력 토큰은 프로ンプ트에 따라 다름)
        return (self.metrics.total_tokens / 1_000_000) * price_per_mtok
    
    def get_metrics(self) -> TokenMetrics:
        """수집된 메트릭 반환"""
        return self.metrics

1.2 HolySheep AI 게이트웨이 연동

이제 HolySheep AI의 LangChain 통합을 설정합니다. 반드시 https://api.holysheep.ai/v1을 base_url로 사용해야 합니다.

import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
import asyncio


class HolySheepStreamingChain:
    """
    HolySheep AI 스트리밍 체인
    
    HolySheep AI의 단일 API 키로 여러 모델 지원:
    - GPT-4.1: $8/MTok (고품질 작업)
    - Claude Sonnet 4.5: $15/MTok (복잡한 추론)
    - Gemini 2.5 Flash: $2.50/MTok (대량 처리/비용 최적화)
    - DeepSeek V3.2: $0.42/MTok ( budget-friendly)
    """
    
    def __init__(
        self,
        api_key: str = None,
        model: str = "gpt-4.1",
        temperature: float = 0.7,
        max_tokens: int = 2048
    ):
        # HolySheep AI 필수 설정
        self.base_url = "https://api.holysheep.ai/v1"
        self.api_key = api_key or os.environ.get("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")
        self.model = model
        self.temperature = temperature
        self.max_tokens = max_tokens
        
        # 모델별 최적 설정
        self.model_configs = {
            "gpt-4.1": {"response_format": "text"},
            "claude-sonnet-4": {"thinking": {"type: "enabled", "budget_tokens": 1024}},
            "gemini-2.5-flash": {"response_mime_type": "text/plain"},
            "deepseek-v3.2": {}
        }
        
        self._llm = None
        
    def _get_llm(self) -> ChatOpenAI:
        """지연 초기화로 LLM 인스턴스 생성"""
        if self._llm is None:
            self._llm = ChatOpenAI(
                model=self.model,
                api_key=self.api_key,
                base_url=self.base_url,
                temperature=self.temperature,
                max_tokens=self.max_tokens,
                streaming=True,  # 스트리밍 활성화
                **self.model_configs.get(self.model, {})
            )
        return self._llm
    
    async def astream_with_callback(
        self,
        prompt: str,
        token_buffer: asyncio.Queue,
        system_prompt: str = None
    ):
        """
        스트리밍 출력 + 콜백 핸들러
        
        Args:
            prompt: 사용자 입력 프롬프트
            token_buffer: 토큰 수신용 asyncio 큐
            system_prompt: 시스템 프롬프트 (선택)
        
        Yields:
            각 토큰을 실시간으로 yield
        """
        from main import StreamingCallbackHandler  # 이전에 정의한 핸들러
        
        # 프롬프트 구성
        messages = []
        if system_prompt:
            messages.append(("system", system_prompt))
        messages.append(("human", prompt))
        
        prompt_template = ChatPromptTemplate.from_messages(messages)
        chain = prompt_template | self._get_llm() | StrOutputParser()
        
        # 콜백 핸들러 설정
        callback_handler = StreamingCallbackHandler(
            token_buffer=token_buffer,
            model_name=self.model
        )
        
        # 스트리밍 실행
        full_response = ""
        async for token in chain.astream(
            input={},
            config={"callbacks": [callback_handler]}
        ):
            full_response += token
            yield token
            
        # 최종 메트릭 반환
        return callback_handler.get_metrics()


사용 예시

async def main(): chain = HolySheepStreamingChain( api_key="YOUR_HOLYSHEEP_API_KEY", model="deepseek-v3.2" # 비용 최적화 모델 ) token_queue = asyncio.Queue() print("Starting streaming response...\n") async for token in chain.astream_with_callback( prompt="AI의 미래에 대해 500단어로 설명해줘", token_buffer=token_queue ): print(token, end="", flush=True) # 메트릭 확인 metrics = await token_queue.get() print(f"\n\n[Metrics] Total: {metrics.total_tokens} tokens") if __name__ == "__main__": asyncio.run(main())

2. FastAPI 백엔드 통합 (SSE)

프론트엔드와 실시간 통신하려면 Server-Sent Events(SSE)가 가장 효율적입니다. WebSocket보다 구현이 간단하고 HTTP/2环境下에서 최적의 성능을 발휘합니다.

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
from typing import Optional, AsyncGenerator
import asyncio
import json
import logging
from datetime import datetime

from main import HolySheepStreamingChain, StreamingCallbackHandler
from langchain_core.callbacks import BaseCallbackHandler

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

app = FastAPI(title="HolySheep AI Streaming API")

CORS 설정 (프론트엔드 도메인에 맞게 조정)

app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000", "https://your-domain.com"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) class ChatRequest(BaseModel): """채팅 요청 스키마""" message: str = Field(..., min_length=1, max_length=10000) system_prompt: Optional[str] = Field(None, max_length=2000) model: str = Field(default="deepseek-v3.2") temperature: float = Field(default=0.7, ge=0, le=2.0) max_tokens: int = Field(default=2048, ge=1, le=8192) session_id: Optional[str] = None class TokenEvent: """SSE 이벤트 포맷터""" @staticmethod def format(token: str, event_type: str = "token") -> bytes: """SSE 형식으로 변환""" data = { "type": event_type, "content": token, "timestamp": datetime.utcnow().isoformat() } return f"data: {json.dumps(data, ensure_ascii=False)}\n\n".encode("utf-8") @staticmethod def format_metrics(metrics) -> bytes: """메트릭 데이터 포맷""" data = { "type": "metrics", "total_tokens": metrics.total_tokens, "ttft_ms": round(metrics.first_token_latency_ms, 2), "tps": round(metrics.tokens_per_second, 2), "total_time_ms": round(metrics.completion_time_ms, 2) } return f"event: metrics\ndata: {json.dumps(data)}\n\n".encode("utf-8") @staticmethod def format_error(error: str) -> bytes: """에러 포맷""" data = { "type": "error", "message": error } return f"event: error\ndata: {json.dumps(data)}\n\n".encode("utf-8") @staticmethod def heartbeat() -> bytes: """연결 유지용 하트비트""" return b": heartbeat\n\n" class StreamAggCallback(BaseCallbackHandler): """ SSE 스트리밍을 위한 LangChain 콜백 핸들러 토큰을 수집하여 SSE 포맷으로 변환 """ def __init__(self, queue: asyncio.Queue): self.queue = queue async def on_llm_new_token(self, token: str, **kwargs): await self.queue.put(("token", token)) async def generate_sse_stream( chain: HolySheepStreamingChain, request: ChatRequest ) -> AsyncGenerator[bytes, None]: """ SSE 스트리밍 응답 생성기 HolySheep AI 게이트웨이를 통한 실시간 토큰 스트리밍을 SSE 형식으로 프론트엔드에 전달 """ token_queue: asyncio.Queue[dict] = asyncio.Queue() callback = StreamingCallbackHandler( token_buffer=token_queue, model_name=request.model ) # 하트비트 간격 (연결 유지) heartbeat_interval = 15 # 15초 async def token_collector(): """백그라운드 토큰 수집 태스크""" try: async for token in chain.astream_with_callback( prompt=request.message, token_buffer=token_queue, system_prompt=request.system_prompt ): pass # 토큰은 queue에 이미 저장됨 except Exception as e: logger.error(f"Chain execution error: {e}") await token_queue.put(("error", str(e))) # 토큰 수집 시작 collector_task = asyncio.create_task(token_collector()) try: last_heartbeat = datetime.utcnow() while True: try: # 토큰 또는 이벤트 대기 (최대 30초) item = await asyncio.wait_for( token_queue.get(), timeout=30.0 ) if item[0] == "error": yield TokenEvent.format_error(item[1]) break # 토큰 이벤트 전송 if item[0] == "token": yield TokenEvent.format(item[1]) # 메트릭 확인 if item.get("total_so_far", 0) > 0: metrics = callback.get_metrics() # 100토큰마다 메트릭 전송 if item["total_so_far"] % 100 == 0: yield TokenEvent.format_metrics(metrics) last_heartbeat = datetime.utcnow() except asyncio.TimeoutError: # 타임아웃 시 하트비트 전송 now = datetime.utcnow() if (now - last_heartbeat).seconds >= heartbeat_interval: yield TokenEvent.heartbeat() last_heartbeat = now except asyncio.CancelledError: logger.info("SSE stream cancelled") finally: collector_task.cancel() try: await collector_task except asyncio.CancelledError: pass # 최종 메트릭 전송 metrics = callback.get_metrics() yield TokenEvent.format_metrics(metrics) # 스트림 종료 신호 yield b"event: done\ndata: {}\n\n" @app.post("/api/chat/stream") async def chat_stream(request: ChatRequest): """ 스트리밍 채팅 엔드포인트 POST /api/chat/stream Request Body: { "message": "안녕하세요, AI에 대해 알려주세요", "model": "deepseek-v3.2", "temperature": 0.7 } Response: Server-Sent Events (SSE) """ # 모델 유효성 검사 valid_models = ["gpt-4.1", "claude-sonnet-4", "gemini-2.5-flash", "deepseek-v3.2"] if request.model not in valid_models: raise HTTPException( status_code=400, detail=f"Invalid model. Choose from: {valid_models}" ) chain = HolySheepStreamingChain( model=request.model, temperature=request.temperature, max_tokens=request.max_tokens ) return StreamingResponse( generate_sse_stream(chain, request), media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no" # Nginx 버퍼링 비활성화 } ) @app.get("/health") async def health_check(): """헬스체크 엔드포인트""" return {"status": "healthy", "provider": "HolySheep AI"} if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)

3. 프론트엔드 통합 (React + SSE)

import React, { useState, useCallback, useRef, useEffect } from 'react';


interface StreamMessage {
  type: 'token' | 'metrics' | 'error' | 'done';
  content?: string;
  total_tokens?: number;
  ttft_ms?: number;
  tps?: number;
  total_time_ms?: number;
  message?: string;
}

interface UseStreamingChatOptions {
  apiUrl?: string;
  apiKey?: string;
  model?: string;
  onComplete?: (metrics: StreamMessage) => void;
  onError?: (error: Error) => void;
}

interface UseStreamingChatReturn {
  messages: Array<{role: 'user' | 'assistant'; content: string}>;
  isStreaming: boolean;
  metrics: StreamMessage | null;
  sendMessage: (message: string, systemPrompt?: string) => Promise;
  error: Error | null;
}

export function useStreamingChat(
  options: UseStreamingChatOptions = {}
): UseStreamingChatReturn {
  const {
    apiUrl = 'http://localhost:8000/api/chat/stream',
    apiKey,
    model = 'deepseek-v3.2',
    onComplete,
    onError
  } = options;

  const [messages, setMessages] = useState<
    Array<{role: 'user' | 'assistant'; content: string}>
  >([]);
  const [isStreaming, setIsStreaming] = useState(false);
  const [metrics, setMetrics] = useState(null);
  const [error, setError] = useState(null);
  
  const abortControllerRef = useRef(null);
  const currentResponseRef = useRef('');

  const sendMessage = useCallback(async (
    message: string,
    systemPrompt?: string
  ) => {
    // 이전 요청 취소
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }
    
    // 새 AbortController 생성
    abortControllerRef.current = new AbortController();
    
    // 사용자 메시지 추가
    setMessages(prev => [...prev, { role: 'user', content: message }]);
    setIsStreaming(true);
    setError(null);
    currentResponseRef.current = '';

    try {
      const response = await fetch(apiUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          // HolySheep AI API 키 (필요시)
          ...(apiKey && { 'Authorization': Bearer ${apiKey} })
        },
        body: JSON.stringify({
          message,
          system_prompt: systemPrompt,
          model,
          temperature: 0.7,
          max_tokens: 2048
        }),
        signal: abortControllerRef.current.signal
      });

      if (!response.ok) {
        throw new Error(HTTP ${response.status}: ${response.statusText});
      }

      const reader = response.body?.getReader();
      if (!reader) {
        throw new Error('Response body is not readable');
      }

      const decoder = new TextDecoder();
      let buffer = '';
      let finalMetrics: StreamMessage | null = null;

      // SSE 파싱 및 실시간 렌더링
      while (true) {
        const { done, value } = await reader.read();
        
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split('\n');
        buffer = lines.pop() || '';

        for (const line of lines) {
          if (line.startsWith('event: ')) {
            const eventType = line.slice(7).trim();
            
            // 다음 줄에서 데이터 읽기
            const dataLineIndex = lines.indexOf(line) + 1;
            if (dataLineIndex < lines.length) {
              const dataLine = lines[dataLineIndex];
              if (dataLine.startsWith('data: ')) {
                try {
                  const data = JSON.parse(dataLine.slice(6));
                  
                  if (eventType === 'token' || data.type === 'token') {
                    // 토큰 실시간 추가
                    currentResponseRef.current += data.content || data;
                    setMessages(prev => {
                      const lastMsg = prev[prev.length - 1];
                      if (lastMsg?.role === 'assistant') {
                        return [
                          ...prev.slice(0, -1),
                          { ...lastMsg, content: currentResponseRef.current }
                        ];
                      }
                      return [...prev, { role: 'assistant', content: data.content || data }];
                    });
                  } else if (eventType === 'metrics') {
                    finalMetrics = data;
                    setMetrics(data);
                  } else if (eventType === 'error') {
                    throw new Error(data.message || 'Unknown error');
                  } else if (eventType === 'done') {
                    // 스트리밍 완료
                  }
                } catch (parseError) {
                  // JSON 파싱 실패는 무시 (빈 줄 등)
                }
              }
            }
          } else if (line.startsWith('data: ') && !line.includes('event:')) {
            // event 없이 data만 오는 경우
            try {
              const data = JSON.parse(line.slice(6));
              if (data.type === 'token' || data.content) {
                currentResponseRef.current += data.content || '';
                setMessages(prev => {
                  const lastMsg = prev[prev.length - 1];
                  if (lastMsg?.role === 'assistant') {
                    return [
                      ...prev.slice(0, -1),
                      { ...lastMsg, content: currentResponseRef.current }
                    ];
                  }
                  return [...prev, { role: 'assistant', content: data.content || '' }];
                });
              }
            } catch {
              // 토큰 데이터
              if (!line.includes('event:')) {
                currentResponseRef.current += line.replace('data: ', '');
                setMessages(prev => {
                  const lastMsg = prev[prev.length - 1];
                  if (lastMsg?.role === 'assistant') {
                    return [
                      ...prev.slice(0, -1),
                      { ...lastMsg, content: currentResponseRef.current }
                    ];
                  }
                  return [...prev, { role: 'assistant', content: line.replace('data: ', '') }];
                });
              }
            }
          }
        }
      }

      // 완료 콜백
      if (finalMetrics && onComplete) {
        onComplete(finalMetrics);
      }

    } catch (err) {
      if ((err as Error).name !== 'AbortError') {
        const error = err as Error;
        setError(error);
        if (onError) onError(error);
      }
    } finally {
      setIsStreaming(false);
    }
  }, [apiUrl, apiKey, model, onComplete, onError]);

  // 컴포넌트 언마운트 시 요청 취소
  useEffect(() => {
    return () => {
      if (abortControllerRef.current) {
        abortControllerRef.current.abort();
      }
    };
  }, []);

  return { messages, isStreaming, metrics, sendMessage, error };
}


// 사용 예시 컴포넌트
export function ChatComponent() {
  const { messages, isStreaming, metrics, sendMessage, error } = useStreamingChat({
    model: 'deepseek-v3.2',
    onComplete: (metrics) => {
      console.log('Streaming complete:', metrics);
    }
  });

  const [input, setInput] = useState('');

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (input.trim() && !isStreaming) {
      sendMessage(input);
      setInput('');
    }
  };

  return (
    
{/* 메시지 목록 */}
{messages.map((msg, i) => (
message ${msg.role}}> {msg.role}
{msg.content}
))} {isStreaming && (
AI가 입력 중...
)}
{/* 메트릭 표시 */} {metrics && (
토큰: {metrics.total_tokens} TTFT: {metrics.ttft_ms}ms TPS: {metrics.tps?.toFixed(2)}
)} {/* 에러 표시 */} {error && (
오류: {error.message}
)} {/* 입력 폼 */}
setInput(e.target.value)} placeholder="메시지를 입력하세요..." disabled={isStreaming} />
); }

성능 최적화 전략

3.1 동시성 제어

다중 사용자 환경에서 API 호출 동시성을 제어하지 않으면 HolySheep AI의 Rate Limit에 도달하거나 비용이 급증할 수 있습니다. 저는 asyncio.Semaphore와 Redis 기반 분산 락을 조합하여 프로덕션 레벨의 동시성 제어를 구현합니다.

import asyncio
from typing import Dict, Optional
from datetime import datetime, timedelta
import time
from collections import defaultdict
import threading


class RateLimiter:
    """
    HolySheep AI API를 위한 동시성 및 Rate Limit 제어기
    
   HolySheep AI 모델별 Rate Limit:
    - GPT-4.1: 500 RPM / 80,000 TPM
    - Claude Sonnet 4.5: 300 RPM / 200,000 TPM
    - Gemini 2.5 Flash: 1000 RPM / 1,000,000 TPM
    - DeepSeek V3.2: 2000 RPM / 1,000,000 TPM
    """
    
    def __init__(
        self,
        rpm_limit: int = 500,
        tpm_limit: int = 80000,
        concurrent_limit: int = 10
    ):
        self.rpm_limit = rpm_limit
        self.tpm_limit = tpm_limit
        self.concurrent_limit = concurrent_limit
        
        # Rate Limiters
        self._rpm_semaphore = asyncio.Semaphore(concurrent_limit)
        self._request_timestamps: Dict[str, list] = defaultdict(list)
        self._token_counts: Dict[str, list] = defaultdict(list)
        self._lock = asyncio.Lock()
        
        # 모니터링
        self._stats = {
            "total_requests": 0,
            "total_tokens": 0,
            "rejected_requests": 0,
            "avg_latency_ms": 0
        }
        
    async def acquire(
        self,
        client_id: str,
        estimated_tokens: int = 500
    ) -> Optional[float]:
        """
        Rate Limit 내에서 실행 허용 여부 확인
        
        Returns:
            대기 시간 (초) 또는 None (즉시 실행 가능)
        """
        async with self._lock:
            now = time.time()
            window_start = now - 60  # 1분 윈도우
            
            # 윈도우 내 요청 수 확인
            self._request_timestamps[client_id] = [
                ts for ts in self._request_timestamps[client_id]
                if ts > window_start
            ]
            
            # RPM 체크
            if len(self._request_timestamps[client_id]) >= self.rpm_limit:
                oldest_request = min(self._request_timestamps[client_id])
                wait_time = 60 - (now - oldest_request)
                return wait_time if wait_time > 0 else 0
            
            # TPM 체크
            self._token_counts[client_id] = [
                (ts, tokens) for ts, tokens in self._token_counts[client_id]
                if ts > window_start
            ]
            
            current_tpm = sum(
                tokens for _, tokens in self._token_counts[client_id]
            )
            
            if current_tpm + estimated_tokens > self.tpm_limit:
                oldest_token = min(
                    ts for ts, _ in self._token_counts[client_id]
                )
                wait_time = 60 - (now - oldest_token)
                return wait_time if wait_time > 0 else 0
            
            # 요청 허용
            self._request_timestamps[client_id].append(now)
            self._token_counts[client_id].append((now, estimated_tokens))
            
            return None
    
    async def execute_with_limit(
        self,
        client_id: str,
        coro,
        estimated_tokens: int = 500
    ):
        """Rate Limit 내에서 코루틴 실행"""
        # 대기 시간 확인
        wait_time = await self.acquire(client_id, estimated_tokens)
        
        if wait_time:
            self._stats["rejected_requests"] += 1
            raise RateLimitError(
                f"Rate limit reached. Retry after {wait_time:.2f} seconds"
            )
        
        # 동시성 제한 내에서 실행
        start_time = time.perf_counter()
        
        async with self._rpm_semaphore:
            try:
                result = await coro
                
                # 성공 메트릭 업데이트
                latency_ms = (time.perf_counter() - start_time) * 1000
                self._update_stats(estimated_tokens, latency_ms)
                
                return result
                
            except Exception as e:
                # 실패 시 통계 업데이트
                self._stats["rejected_requests"] += 1
                raise
                
    def _update_stats(self, tokens: int, latency_ms: float):
        """통계 업데이트"""
        self._stats["total_requests"] += 1
        self._stats["total_tokens"] += tokens
        
        # 이동 평균으로 지연 시간 업데이트
        n = self._stats["total_requests"]
        current_avg = self._stats["avg_latency_ms"]
        self._stats["avg_latency_ms"] = (
            (current_avg * (n - 1) + latency_ms) / n
        )
        
    def get_stats(self) -> Dict:
        """통계 반환"""
        return {
            **self._stats,
            "active_requests": self._rpm_semaphore._value,
            "timestamp": datetime.utcnow().isoformat()
        }


class RateLimitError(Exception):
    """Rate Limit 초과 에러"""
    pass


모델별 Rate Limiter 인스턴스

rate_limiters = { "gpt-4.1": RateLimiter(rpm_limit=500, tpm_limit=80000, concurrent_limit=10), "claude-sonnet-4": RateLimiter(rpm_limit=300, tpm_limit=200000, concurrent_limit