모든 쿼리에 GPT-4를 쓰는 낭비
"날씨 알려줘"와 "복잡한 계약서 분석"에 같은 모델을 쓰는 것은 비효율적입니다. LLM 라우팅과 앙상블은 정확도는 유지하면서 비용을 30~70% 절감할 수 있습니다.
LLM 라우팅: 쿼리 복잡도 기반 분류
from sklearn.linear_model import LogisticRegression
import numpy as np
class QueryRouter:
"""쿼리 복잡도를 분류해 적합한 모델로 라우팅"""
COMPLEXITY_LEVELS = {
"simple": "claude-haiku-4-5", # 단순 조회, FAQ
"medium": "claude-sonnet-4-6", # 일반 추론
"complex": "claude-opus-4-7", # 복잡한 분석, 코드
}
def __init__(self):
self.classifier = LogisticRegression()
# 특징: 토큰 수, 코드 포함 여부, 수치 복잡도, 도메인 키워드 수
self.feature_extractors = [
lambda q: len(q.split()),
lambda q: int(any(kw in q for kw in ["코드", "함수", "구현", "sql"])),
lambda q: q.count("?"),
lambda q: int(len(q) > 200),
]
def extract_features(self, query: str) -> np.ndarray:
return np.array([f(query) for f in self.feature_extractors]).reshape(1, -1)
def route(self, query: str) -> str:
if not hasattr(self.classifier, "classes_"):
return "medium" # 미학습 시 기본값
level = self.classifier.predict(self.extract_features(query))[0]
return self.COMPLEXITY_LEVELS[level]
async def query(self, user_input: str) -> str:
model = self.route(user_input)
return await llm_call(model, user_input)규칙 기반 라우팅 (빠르고 예측 가능)
def simple_router(query: str) -> str:
tokens = len(query.split())
has_code = any(kw in query.lower() for kw in ["코드", "sql", "함수", "알고리즘"])
has_numbers = sum(c.isdigit() for c in query) > 5
is_long = tokens > 100
if has_code or (is_long and has_numbers):
return "claude-opus-4-7"
elif tokens > 30 or has_numbers:
return "claude-sonnet-4-6"
else:
return "claude-haiku-4-5"폴백 체인: 정확도 보장
빠른 모델이 실패하거나 신뢰도가 낮으면 더 강력한 모델로 승격합니다.
async def escalating_query(query: str) -> dict:
models = [
("claude-haiku-4-5", 0.85), # (모델, 신뢰도 임계값)
("claude-sonnet-4-6", 0.90),
("claude-opus-4-7", None), # 최종 — 임계값 없음
]
for model, threshold in models:
response = await llm_call(model, query)
confidence = await estimate_confidence(response)
if threshold is None or confidence >= threshold:
return {
"answer": response,
"model_used": model,
"confidence": confidence
}
print(f"{model} 신뢰도 {confidence:.2f} < {threshold} → 상위 모델로 승격")앙상블: Mixture-of-Experts 경량 구현
async def ensemble_query(query: str, models: list, weights: list = None) -> str:
responses = await asyncio.gather(*[llm_call(m, query) for m in models])
if weights is None:
weights = [1.0] * len(models)
# 응답을 임베딩으로 변환 후 가중 평균 클러스터링
embeddings = await asyncio.gather(*[embed(r) for r in responses])
centroid = np.average(embeddings, axis=0, weights=weights)
# 클러스터 중심에 가장 가까운 응답 선택
distances = [np.linalg.norm(e - centroid) for e in embeddings]
best_idx = np.argmin(distances)
return responses[best_idx]비용 대비 성능 측정 결과
실제 고객 서비스 챗봇 적용 결과 (월간 10만 건 기준):
| 전략 | 비용 | 정확도 | P95 지연 |
|---|---|---|---|
| 모두 Opus | $1,200 | 92% | 3.2s |
| 모두 Sonnet | $380 | 85% | 1.8s |
| 라우팅 (본문) | $340 | 91% | 2.1s |
| 폴백 체인 | $420 | 93% | 2.4s |
라우팅 전략이 단일 Opus 대비 72% 비용 절감, 정확도 1%p 차이로 최적점을 보여줍니다.
프로덕션 적용 시 주의사항
- 라우터 학습 데이터: 최소 500개 레이블된 쿼리 필요
- A/B 테스트: 라우터 도입 전후 품질 지표 비교 필수
- 폴백 로깅: 어떤 쿼리가 상위 모델로 승격되는지 추적
- 비용 모니터링: 라우터 오분류로 비용이 증가할 수 있음
이 3편 시리즈로 LLM 성능 향상의 핵심(RAG→파인튜닝→라우팅/앙상블)이 완성됩니다.
이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.
댓글
첫 번째 댓글을 남겨보세요.