콘텐츠로 이동
Data Prep
상세

LLM 라우팅 전략

개요

LLM 라우팅은 여러 모델을 상황에 맞게 선택적으로 호출하는 패턴이다. 비용 최적화, 응답 품질, 레이턴시 균형을 위해 필수적인 아키텍처 패턴으로 자리잡았다.

라우팅 패턴

1. 계층적 라우팅 (Hierarchical)

┌─────────────────────────────────────────────────────┐
│                    Router                           │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  │
│  │  Complexity │  │   Domain    │  │   Budget    │  │
│  │   Scorer    │  │  Classifier │  │   Manager   │  │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘  │
└─────────┼────────────────┼───────────────┼──────────┘
          │                │               │
          ▼                ▼               ▼
    ┌──────────┐    ┌──────────┐    ┌──────────┐
    │ GPT-4o   │    │ Claude   │    │ Llama 3  │
    │ (복잡)   │    │ (코드)   │    │ (저비용) │
    └──────────┘    └──────────┘    └──────────┘

구현:

from dataclasses import dataclass
from enum import Enum
import re

class ModelTier(Enum):
    BUDGET = "budget"      # Llama, Mistral (로컬)
    STANDARD = "standard"  # GPT-4o-mini, Claude Haiku
    PREMIUM = "premium"    # GPT-4o, Claude Sonnet
    FLAGSHIP = "flagship"  # o1, Claude Opus

@dataclass
class RoutingDecision:
    model: str
    tier: ModelTier
    reason: str
    estimated_cost: float

class HierarchicalRouter:
    def __init__(self, budget_limit: float = 10.0):
        self.budget_limit = budget_limit
        self.spent_today = 0.0

        self.model_map = {
            ModelTier.BUDGET: "llama-3.2-3b",
            ModelTier.STANDARD: "gpt-4o-mini",
            ModelTier.PREMIUM: "claude-sonnet-4",
            ModelTier.FLAGSHIP: "claude-opus-4",
        }

    def estimate_complexity(self, prompt: str) -> float:
        """0-1 복잡도 스코어"""
        score = 0.0

        # 토큰 길이
        tokens = len(prompt.split())
        if tokens > 500: score += 0.3
        elif tokens > 200: score += 0.15

        # 키워드 기반
        complex_keywords = [
            "분석", "비교", "추론", "설명해", "왜",
            "analyze", "compare", "reason", "explain"
        ]
        for kw in complex_keywords:
            if kw in prompt.lower():
                score += 0.1

        # 코드/수학
        if re.search(r'```|def |class |import ', prompt):
            score += 0.2
        if re.search(r'\$.*\$|\\frac|\\sum', prompt):
            score += 0.15

        return min(score, 1.0)

    def route(self, prompt: str) -> RoutingDecision:
        complexity = self.estimate_complexity(prompt)
        remaining_budget = self.budget_limit - self.spent_today

        # 예산 소진 시 강제 저비용
        if remaining_budget < 0.5:
            return RoutingDecision(
                model=self.model_map[ModelTier.BUDGET],
                tier=ModelTier.BUDGET,
                reason="budget_exhausted",
                estimated_cost=0.001
            )

        # 복잡도 기반 라우팅
        if complexity > 0.7:
            tier = ModelTier.FLAGSHIP
            cost = 0.15
        elif complexity > 0.4:
            tier = ModelTier.PREMIUM
            cost = 0.05
        elif complexity > 0.2:
            tier = ModelTier.STANDARD
            cost = 0.01
        else:
            tier = ModelTier.BUDGET
            cost = 0.001

        return RoutingDecision(
            model=self.model_map[tier],
            tier=tier,
            reason=f"complexity_{complexity:.2f}",
            estimated_cost=cost
        )

2. 도메인 기반 라우팅

도메인 추천 모델 이유
코드 생성 Claude Sonnet, DeepSeek Coder 구조화된 출력
수학/논리 o1, DeepSeek R1 CoT 추론
창작/글쓰기 Claude, GPT-4o 자연스러운 문체
한국어 Claude, GPT-4o 다국어 품질
RAG 요약 GPT-4o-mini, Gemini Flash 속도/비용
긴 문서 Claude, Gemini 200K+ 컨텍스트
class DomainRouter:
    DOMAIN_MODELS = {
        "code": ["claude-sonnet-4", "deepseek-coder-v2"],
        "math": ["o1", "deepseek-r1"],
        "writing": ["claude-opus-4", "gpt-4o"],
        "korean": ["claude-sonnet-4", "gpt-4o"],
        "rag_summary": ["gpt-4o-mini", "gemini-2.0-flash"],
        "long_context": ["claude-sonnet-4", "gemini-2.0-pro"],
    }

    def classify_domain(self, prompt: str) -> str:
        # 간단한 키워드 기반 분류
        if re.search(r'```|코드|함수|클래스|python|javascript', prompt, re.I):
            return "code"
        if re.search(r'수학|증명|계산|equation|\$', prompt, re.I):
            return "math"
        if re.search(r'글|작성|소설|에세이|write', prompt, re.I):
            return "writing"
        if re.search(r'요약|summarize|문서', prompt, re.I):
            return "rag_summary"
        return "general"

    def route(self, prompt: str) -> str:
        domain = self.classify_domain(prompt)
        candidates = self.DOMAIN_MODELS.get(domain, ["gpt-4o-mini"])
        return candidates[0]  # 첫 번째 후보 반환

3. 캐스케이드 라우팅

저비용 모델로 시작해서 실패 시 상위 모델로 에스컬레이션:

┌──────────┐    실패/불확실    ┌──────────┐    실패    ┌──────────┐
│  Tier 1  │ ───────────────▶ │  Tier 2  │ ────────▶ │  Tier 3  │
│  (빠름)  │                  │  (균형)  │           │  (정확)  │
└──────────┘                  └──────────┘           └──────────┘
   Llama 3                     GPT-4o-mini            Claude Opus
import asyncio
from typing import Optional

class CascadeRouter:
    TIERS = [
        {"model": "llama-3.2-3b", "timeout": 5, "confidence_threshold": 0.8},
        {"model": "gpt-4o-mini", "timeout": 15, "confidence_threshold": 0.6},
        {"model": "claude-opus-4", "timeout": 60, "confidence_threshold": 0.0},
    ]

    async def route(self, prompt: str) -> dict:
        for tier in self.TIERS:
            result = await self.try_model(
                prompt, 
                tier["model"], 
                tier["timeout"]
            )

            if result and result["confidence"] >= tier["confidence_threshold"]:
                return result

            # 다음 티어로 에스컬레이션
            continue

        return {"error": "all_tiers_failed"}

    async def try_model(
        self, 
        prompt: str, 
        model: str, 
        timeout: int
    ) -> Optional[dict]:
        try:
            # 모델 호출 (pseudo-code)
            response = await asyncio.wait_for(
                self.call_llm(prompt, model),
                timeout=timeout
            )

            # 신뢰도 추정
            confidence = self.estimate_confidence(response)

            return {
                "model": model,
                "response": response,
                "confidence": confidence
            }
        except asyncio.TimeoutError:
            return None

4. A/B 라우팅 (실험용)

import random
import hashlib

class ABRouter:
    def __init__(self, experiments: dict):
        """
        experiments = {
            "pricing_v2": {
                "control": {"model": "gpt-4o-mini", "weight": 0.5},
                "treatment": {"model": "claude-haiku", "weight": 0.5},
            }
        }
        """
        self.experiments = experiments

    def route(self, prompt: str, user_id: str, experiment: str) -> str:
        if experiment not in self.experiments:
            return "gpt-4o-mini"  # 기본값

        exp = self.experiments[experiment]

        # 사용자별 일관된 할당 (해시 기반)
        hash_input = f"{user_id}:{experiment}"
        hash_val = int(hashlib.md5(hash_input.encode()).hexdigest(), 16)
        roll = (hash_val % 1000) / 1000

        cumulative = 0
        for variant, config in exp.items():
            cumulative += config["weight"]
            if roll < cumulative:
                return config["model"]

        return list(exp.values())[0]["model"]

라우팅 신호

입력 기반 신호

신호 측정 방법 라우팅 영향
토큰 수 len(prompt.split()) 긴 입력 → 고성능 모델
코드 존재 regex 매칭 코드 특화 모델
수학 기호 LaTeX 패턴 추론 모델
언어 langdetect 다국어 모델
대화 턴 수 history 길이 컨텍스트 확장 모델

출력 기반 신호 (실시간)

신호 측정 방법 대응
응답 길이 부족 len(response) < threshold 재시도/상위 모델
거부 응답 "할 수 없습니다" 감지 다른 모델 시도
JSON 파싱 실패 json.loads() 예외 구조화 특화 모델
저신뢰 응답 logprobs 분석 상위 모델 에스컬레이션

비용 최적화

모델별 비용 비교 (2026.03 기준)

모델 Input ($/1M) Output ($/1M) 상대 비용
Llama 3.2 3B (로컬) ~0 ~0 1x
GPT-4o-mini $0.15 $0.60 10x
Claude Haiku $0.25 $1.25 15x
GPT-4o $2.50 $10.00 100x
Claude Sonnet $3.00 $15.00 150x
Claude Opus $15.00 $75.00 750x
o1 $15.00 $60.00 600x

비용 절감 전략

  1. 80/20 규칙: 80%의 요청은 저비용 모델로 처리 가능
  2. 캐싱: 동일 질문 캐시 → 100% 절감
  3. 배치 처리: 비동기 배치로 throughput API 활용
  4. Prompt 압축: 불필요한 컨텍스트 제거
class CostOptimizer:
    def __init__(self, daily_budget: float):
        self.daily_budget = daily_budget
        self.spent = 0.0
        self.cache = {}

    def optimize_prompt(self, prompt: str) -> str:
        """프롬프트 압축"""
        # 중복 공백 제거
        prompt = re.sub(r'\s+', ' ', prompt)
        # 불필요한 서문 제거
        prompt = re.sub(r'^(안녕하세요|Hi|Hello)[,.]?\s*', '', prompt)
        return prompt.strip()

    def should_use_premium(self) -> bool:
        """예산 잔여량 기반 판단"""
        remaining_ratio = 1 - (self.spent / self.daily_budget)
        return remaining_ratio > 0.3

    def get_cached(self, prompt: str) -> Optional[str]:
        """캐시 조회"""
        cache_key = hashlib.md5(prompt.encode()).hexdigest()
        return self.cache.get(cache_key)

프로덕션 아키텍처

전체 구조

┌─────────────────────────────────────────────────────────────────┐
│                         API Gateway                              │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                      Router Service                              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐         │
│  │ Complexity│  │  Domain  │  │  Budget  │  │   A/B    │         │
│  │  Scorer   │  │ Classifier│ │ Manager  │  │  Router  │         │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘         │
└─────────────────────────────────────────────────────────────────┘
              ┌────────────────┼────────────────┐
              │                │                │
              ▼                ▼                ▼
       ┌───────────┐    ┌───────────┐    ┌───────────┐
       │  vLLM     │    │  OpenAI   │    │ Anthropic │
       │  (Local)  │    │   API     │    │    API    │
       └───────────┘    └───────────┘    └───────────┘
              │                │                │
              └────────────────┴────────────────┘
                      ┌───────────────┐
                      │   Response    │
                      │   Aggregator  │
                      └───────────────┘

모니터링 메트릭

메트릭 설명 알림 기준
routing_accuracy 라우팅 정확도 < 85%
tier_distribution 티어별 요청 비율 premium > 30%
cost_per_request 요청당 비용 > $0.05
cascade_escalation_rate 에스컬레이션 비율 > 40%
latency_p95 95번째 백분위 레이턴시 > 10s

체크리스트

  • [ ] 복잡도 스코어러 구현
  • [ ] 도메인 분류기 구현
  • [ ] 예산 관리 로직 추가
  • [ ] 캐시 레이어 구성
  • [ ] 캐스케이드 폴백 설정
  • [ ] A/B 실험 프레임워크 연동
  • [ ] 모니터링 대시보드 구축
  • [ ] 비용 알림 설정

참고 자료


마지막 업데이트: 2026-03-04