LiteLLM 폴백과 라우팅
여러 provider를 같은 인터페이스로 호출할 수 있게 되어도 운영 단계에서는 다른 문제가 남습니다. 같은 논리적인 모델이 여러 region이나 account에 배포되어 있을 수 있고, 특정 deployment가 rate limit, timeout, provider 장애 때문에 일시적으로 사용할 수 없게 될 수도 있습니다.
LiteLLM의 Router는 이 문제를 provider API를 변환하는 계층보다 위에서 여러 deployment 중 실제 실행 대상을 선택하고 실패를 처리하는 routing 계층으로 다룹니다. 호출부는 하나의 논리적인 model name을 사용하고, Router는 그 이름에 연결된 deployment 중 하나를 routing strategy에 따라 선택합니다.
여기서 먼저 구분해야 할 것이 deployment routing과 model fallback입니다. 같은 model group 안에 여러 region이나 account가 있다면 Router는 그중 하나의 deployment를 선택합니다. 반면 해당 model group으로 요청을 처리하기 어렵다면 fallbacks를 이용해 다른 model group으로 요청을 넘길 수 있습니다.
실패 처리도 하나의 동작으로 묶이지 않습니다. Retry는 실패한 요청을 다시 시도하는 정책이고, fallback은 다른 model group으로 실행 대상을 바꾸는 정책입니다. Cooldown은 반복해서 실패하는 deployment를 일정 시간 routing 후보에서 제외해 계속 같은 장애 지점으로 요청을 보내는 것을 줄이는 장치입니다. 어떤 오류를 몇 번 retry할지, 언제 fallback할지, 어떤 deployment를 얼마나 오래 cooldown할지는 Router에 설정한 policy에 따라 달라집니다.
Logical Model
↓
Router
↓
Deployment 선택
├─ Region A
├─ Region B
└─ Region C
↓
요청 실행
↓
실패 정책
├─ Retry
├─ Cooldown
└─ 다른 Model Group으로 Fallback
먼저 하나의 model group과 그 아래 여러 deployment가 어떤 관계인지 살펴보고, routing strategy가 실제 실행 위치를 어떻게 선택하는지 봅니다. 이어서 retry, fallback, cooldown이 각각 어떤 종류의 실패를 처리하는 장치인지 연결하고, exception별 retry policy와 context-window fallback처럼 실패 원인에 따라 정책을 분리하는 방법까지 살펴봅니다.
중요한 것은 routing option의 개수를 외우는 것이 아니라 같은 모델의 여러 배포 중 어디에서 실행할 것인가와, 그 모델 그룹 자체로 처리할 수 없을 때 어디로 넘어갈 것인가를 구분하는 것입니다.
1장. completion()과 Router의 역할 차이
completion()은 "어떤 모델을 호출할지"가 이미 정해진 상태에서 한 번의 호출을 처리합니다. Router는 그보다 앞에서 "실제로 어느 배포를 호출할지"를 결정합니다.
애플리케이션
│
│ model="chat-model"
▼
Router
│
├── 배포 A
├── 배포 B
└── 배포 C
│
▼
LiteLLM 호출
운영에서는 같은 논리 모델을 여러 실제 배포로 구성하는 경우가 많습니다. 예를 들어 chat-model이라는 이름 아래 Azure의 두 리전과 OpenAI 직접 호출을 같이 둘 수 있습니다. 호출부는 chat-model만 알고, 어느 배포로 갈지는 Router가 정합니다.
2장. model_list와 모델 그룹
Router의 기본 입력은 model_list입니다.
from litellm import Router
router = Router(
model_list=[
{
"model_name": "chat-model",
"litellm_params": {
"model": "azure/gpt-5-deployment-a",
"api_key": "...",
"api_base": "https://region-a.example.com",
},
},
{
"model_name": "chat-model",
"litellm_params": {
"model": "openai/gpt-5",
"api_key": "...",
},
},
]
)
response = router.completion(
model="chat-model",
messages=[{"role": "user", "content": "안녕하세요"}],
)
model_name이 같은 항목들이 하나의 모델 그룹입니다.
chat-model
├── azure/gpt-5-deployment-a
└── openai/gpt-5
이 분리가 중요한 이유는 애플리케이션이 논리 모델을 선택하고, Router가 실제 배포를 선택하게 만들기 때문입니다. 배포를 추가하거나 리전을 바꿔도 호출부는 그대로 둘 수 있습니다.
3장. 재시도와 폴백은 다르다
실패 처리에서 가장 먼저 구분해야 할 것이 재시도와 폴백입니다.
재시도 같은 배포 또는 같은 모델 그룹에서 다시 시도
폴백 다른 모델 그룹으로 넘어감
일시적인 타임아웃이나 레이트리밋은 다시 시도할 가치가 있지만, 인증 오류나 잘못된 요청은 같은 요청을 반복해도 해결되지 않습니다. 그래서 LiteLLM은 예외 타입별 재시도 정책을 둘 수 있습니다.
from litellm import Router
from litellm.types.router import RetryPolicy
router = Router(
model_list=[...],
retry_policy=RetryPolicy(
RateLimitErrorRetries=3,
TimeoutErrorRetries=2,
AuthenticationErrorRetries=0,
BadRequestErrorRetries=0,
ContentPolicyViolationErrorRetries=0,
InternalServerErrorRetries=2,
),
)
여기서 중요한 것은 숫자 자체가 아니라 실패의 성격에 따라 정책을 다르게 둔다는 원칙입니다.
인증 오류를 세 번 재시도하면 호출 횟수만 늘어납니다. 반대로 일시적인 429나 네트워크 타임아웃은 짧은 재시도로 회복될 수 있습니다.
4장. 실패 이유에 따라 폴백 경로를 나눈다
폴백도 한 종류로 처리하면 안 됩니다.
예를 들어 컨텍스트 한도를 넘은 요청을 같은 크기의 모델로 다시 보내면 또 실패합니다. 콘텐츠 정책 거부 역시 일반 장애와는 성격이 다릅니다.
LiteLLM은 이런 이유를 별도 폴백으로 표현할 수 있습니다.
router = Router(
model_list=[...],
fallbacks=[
{"chat-model": ["backup-model"]}
],
context_window_fallbacks=[
{"chat-model": ["long-context-model"]}
],
content_policy_fallbacks=[
{"chat-model": ["policy-backup-model"]}
],
)
정리하면 다음과 같습니다.
| 실패 | 다음 경로 |
|---|---|
| 일반적인 제공자 장애 | 일반 폴백 |
| 컨텍스트 초과 | 더 긴 컨텍스트를 가진 모델 |
| 콘텐츠 정책 거부 | 별도로 정한 정책 폴백 |
1편에서 제공자별 오류를 공통 예외로 바꿨기 때문에 이런 분기가 가능합니다. 오류 번역이 먼저 있고, 그 위에 Router의 신뢰성 정책이 올라갑니다.
5장. 쿨다운으로 죽은 배포를 잠시 뺀다
재시도만으로는 반복적으로 실패하는 배포를 계속 고르게 될 수 있습니다. 이때 쿨다운을 사용합니다.
router = Router(
model_list=[...],
allowed_fails=3,
cooldown_time=60,
)
일정 시간 안에 실패가 반복된 배포를 후보에서 잠시 제외해, 이미 문제가 있다는 것을 아는 엔드포인트로 계속 요청을 보내지 않게 합니다.
이 기능은 회로 차단기와 비슷합니다.
정상 배포
│
├─ 실패가 누적되지 않음 ──► 계속 후보
│
└─ 실패가 임계치 초과 ───► cooldown ──► 일정 시간 후보 제외
운영에서는 allowed_fails와 cooldown_time을 크게 잡는 것이 안전한 것도, 작게 잡는 것이 안전한 것도 아닙니다. 트래픽과 장애 패턴을 보고 조정해야 합니다.
6장. 어느 배포를 고를지 정하는 전략
같은 모델 그룹 안에 정상 배포가 여러 개 있으면 Router가 하나를 골라야 합니다. LiteLLM은 여러 라우팅 전략을 제공합니다.
| 전략 | 무엇을 우선하나 |
|---|---|
simple-shuffle |
가중치를 반영한 단순 분산 |
least-busy |
진행 중 요청이 적은 배포 |
usage-based-routing |
TPM/RPM 여유 |
latency-based-routing |
최근 지연 |
cost-based-routing |
비용 |
선택 기준은 목적과 맞춰야 합니다.
배포들의 성능과 가격이 거의 같으면 simple-shuffle로도 충분합니다. 레이트리밋이 실제 병목이면 usage 기반이 맞고, 여러 리전 사이 지연 차이가 크면 latency 기반이 더 의미 있습니다.
운영에서 자주 하는 실수는 "더 똑똑한 전략"을 고르면 항상 좋아질 것이라고 생각하는 것입니다. 라우터가 참고하는 관측값이 불안정하거나 트래픽이 적으면 복잡한 전략이 오히려 흔들릴 수 있습니다.
7장. 실패하기 전에 후보를 줄인다
폴백은 실패한 뒤 움직입니다. 가능하면 실패하기 전에 부적합한 배포를 빼는 편이 더 빠릅니다.
router = Router(
model_list=[...],
enable_pre_call_checks=True,
)
pre-call check는 요청을 보내기 전에 컨텍스트 한도나 레이트리밋 같은 조건을 확인해 후보를 줄이는 데 사용됩니다.
최근 Router에는 배포 친화도와 세션 친화도 같은 개념도 더해졌습니다. 특히 긴 대화에서는 매 요청마다 모델이나 배포를 바꾸면 Prompt Caching을 잃을 수 있어, 대화 세션을 가능한 한 같은 경로에 유지하는 것 자체가 비용 최적화가 될 수 있습니다.
즉 라우팅은 매 요청의 최저 비용만 보는 문제가 아닙니다. 캐시, 세션 연속성, 지역성까지 함께 봐야 합니다.
8장. 요청 내용으로 모델을 고르는 Auto-Router
앞의 전략들은 배포 상태를 보고 어디로 보낼지 정했습니다. Auto-Router 계열은 요청 자체를 보고 어떤 모델이 맞는지 결정합니다.
LiteLLM의 complexity router는 요청 길이, 코드 신호, reasoning 표현, 기술 용어 같은 여러 신호를 점수화해 난이도 티어로 나눕니다. 기본 휴리스틱 분류는 외부 모델 호출 없이 로컬에서 수행할 수 있습니다.
간단한 질문 ──► 저비용 모델
일반 요청 ──► 중간 모델
복잡한 기술 요청 ──► 상위 모델
강한 추론 요청 ──► reasoning 모델
최근 구조에서는 complexity 분류에 여러 모델을 한 티어로 묶고, session affinity나 adaptive 선택을 결합하는 방향으로 확장됐습니다. Adaptive Router는 모델별 품질·비용 신호를 이용해 선택을 조정하는 영역으로, 관련 API와 전략은 빠르게 발전하고 있습니다.
여기서 학습 포인트는 특정 옵션 이름이 아닙니다.
라우팅 기준이 두 층으로 나뉜다는 것이 중요합니다.
배포 라우팅 같은 논리 모델을 어느 엔드포인트에서 실행할까
모델 라우팅 이 요청 자체를 어느 모델에 맡길까
두 문제를 섞으면 설계가 복잡해집니다. 먼저 논리 모델을 고르고, 그 안에서 실제 배포를 고르는 식으로 계층을 나누는 편이 이해하기 쉽습니다.
9장. 타임아웃 예산을 먼저 계산한다
재시도와 폴백을 많이 걸면 신뢰성이 올라가는 것처럼 보이지만, 최악 지연도 함께 늘어납니다.
예를 들어 각 호출의 타임아웃이 20초이고 같은 요청이 재시도와 폴백을 거치면 사용자에게 보이는 지연은 쉽게 수십 초를 넘어갑니다.
그래서 운영에서는 다음 순서로 잡는 편이 안전합니다.
- 사용자 요청의 전체 SLA를 정한다.
- 한 번의 모델 호출에 줄 수 있는 타임아웃을 정한다.
- 그 안에서 재시도 횟수와 폴백 깊이를 배분한다.
- 실패율과 실제 p95/p99 지연을 보고 다시 조정한다.
num_retries는 개별 재시도 정책과 연결되고, num_retries_per_request는 폴백까지 포함한 요청 전체 횟수를 제한할 때 사용됩니다. 이름이 비슷하므로 실제 호출 횟수를 테스트로 확인하는 것이 좋습니다.
여러 Router 인스턴스가 같은 배포 한도와 쿨다운 상태를 공유해야 한다면 Redis 같은 공용 상태 저장소가 필요해집니다. 단일 프로세스에서 시작할 때와 분산 배포할 때의 운영 요구가 다릅니다.
10장. 정리
Router의 핵심은 "모델을 자동으로 고른다"보다 더 구체적입니다.
논리 모델
│
├── 어느 배포를 고를까
├── 어떤 오류는 재시도할까
├── 어떤 오류는 다른 모델로 보낼까
├── 반복 실패한 배포를 언제 뺄까
└── 전체 지연을 어디까지 허용할까
재시도는 같은 실패를 다시 시도하는 문제이고, 폴백은 다른 경로로 넘어가는 문제입니다. 컨텍스트 초과와 콘텐츠 정책처럼 실패 이유가 다르면 폴백 경로도 달라져야 합니다.
여기까지는 애플리케이션 안의 Python 객체로 해결할 수 있습니다. 여러 서비스와 팀이 같은 Router 정책을 공유하고 키·예산·레이트리밋까지 중앙에서 관리해야 한다면 다음 단계가 LiteLLM Proxy Server입니다.
용어 정리
| 용어 | 뜻 |
|---|---|
Router |
여러 모델 배포의 선택과 실패 처리를 담당하는 LiteLLM 계층 |
model_list |
Router가 사용할 실제 배포 목록 |
| 모델 그룹 | 같은 model_name으로 묶인 여러 실제 배포 |
| 재시도 | 실패한 호출을 다시 시도하는 것 |
| 폴백 | 실패 후 다른 모델 그룹으로 넘어가는 것 |
RetryPolicy |
예외 타입별 재시도 횟수를 정의하는 정책 |
| cooldown | 반복 실패한 배포를 일정 시간 후보에서 제외하는 기능 |
| pre-call check | 실제 호출 전에 부적합한 배포를 후보에서 제거하는 검사 |
| session affinity | 같은 세션을 가능한 한 같은 모델·배포 경로에 유지하는 정책 |
| Auto-Router | 요청 특성에 따라 모델 자체를 선택하는 라우팅 계층 |
참고자료
'AI Agent > Frameworks' 카테고리의 다른 글
| LiteLLM (4) - 내부 구조와 Translation Layer (0) | 2024.11.11 |
|---|---|
| LiteLLM (3) - Proxy와 AI Gateway 운영 (0) | 2024.11.08 |
| LangGraph (4) - 에이전트와 커스텀 그래프 (0) | 2024.10.10 |
| LiteLLM (1) - 전체 구조와 SDK (0) | 2024.08.29 |
| LangGraph (3) - 체크포인터, Interrupt와 Memory (0) | 2024.08.22 |
댓글