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 |
비용 절감 전략
- 80/20 규칙: 80%의 요청은 저비용 모델로 처리 가능
- 캐싱: 동일 질문 캐시 → 100% 절감
- 배치 처리: 비동기 배치로 throughput API 활용
- 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