본문 바로가기

AI 개발

LiteLLM Load Balancing 4편 — 시맨틱 라우팅, 커스텀 전략, 실전 아키텍처 3가지

반응형

1~3편에서 라우팅 전략·폴백·프로덕션 배포를 다뤘습니다. 4편은 그 다음 단계입니다. 6개의 기본 전략이 모두 "어느 배포로 보낼까"를 결정했다면, 여기서는 "어떤 모델 티어로 보낼까"를 결정하는 콘텐츠 기반 라우팅을 다룹니다. 그리고 프로덕션 현장에서 실제로 쓰이는 세 가지 아키텍처 패턴 — 고가용성 멀티 리전, 비용 최적화 3티어, 하이브리드 클라우드+로컬 — 을 완전한 config.yaml과 함께 정리합니다. 시리즈의 마무리로, LiteLLM의 한계도 솔직하게 다룹니다.


이 포스트 한 줄 요약 → Tag 라우팅: x-litellm-tags 헤더로 요청을 특정 배포 그룹으로 분류 → 복잡도 라우터: 임베딩 API 호출 없이 규칙 기반으로 서브밀리초 분류 → 시맨틱 라우터: 발화(utterance) 임베딩 유사도로 모델 선택 → CustomRoutingStrategyBase: 완전한 커스텀 전략 구현 인터페이스 → 멀티 리전 아키텍처: us-east·eu-west·ap-northeast 3리전, 레이턴시 기반 → 비용 최적화 3티어: 복잡도에 따라 Haiku → Sonnet → Opus 자동 라우팅 → 하이브리드 아키텍처: 온프렘 + 클라우드, 민감 데이터 로컬 처리 → LiteLLM 한계: 500+ RPS에서 Python 오버헤드, Go 기반 대안 존재 → 암호화 콘텐츠 어피니티: OpenAI Responses API 암호화 아이템 동일 배포 유지


1. Tag 라우팅 — 요청 메타데이터로 배포 분류

Tag 라우팅은 요청에 첨부된 태그를 기반으로 특정 배포 그룹으로 라우팅합니다. 환경 분리, 팀별 모델 분리, 프리미엄 사용자 우선 배포에 유용합니다.

# config.yaml — tag 기반 배포 분류

model_list:
  # 프로덕션 전용 고성능 배포
  - model_name: claude-sonnet
    litellm_params:
      model: claude-sonnet-4-6
      api_key: os.environ/ANTHROPIC_PROD_KEY
      rpm: 2000
    model_info:
      id: sonnet-prod
      tags:
        - production
        - premium

  # 개발/스테이징 배포 (저렴한 키)
  - model_name: claude-sonnet
    litellm_params:
      model: claude-sonnet-4-6
      api_key: os.environ/ANTHROPIC_DEV_KEY
      rpm: 500
    model_info:
      id: sonnet-dev
      tags:
        - development
        - staging

  # 팀 A 전용 배포
  - model_name: claude-sonnet
    litellm_params:
      model: bedrock/anthropic.claude-sonnet-4-6-v1
      aws_region_name: us-east-1
    model_info:
      id: sonnet-team-a
      tags:
        - team-a
        - production

router_settings:
  routing_strategy: simple-shuffle
  enable_tag_filtering: true          # Tag 라우팅 활성화
  tag_filtering_match_any: true       # ANY 매칭 (OR)
  # false로 설정 시 ALL 매칭 (AND)
from openai import OpenAI

client = OpenAI(
    api_key="sk-virtual-key",
    base_url="http://litellm-proxy:4000",
)

# 프로덕션 배포로 라우팅
response = client.chat.completions.create(
    model="claude-sonnet",
    messages=[{"role": "user", "content": "요청 내용"}],
    extra_headers={
        "x-litellm-tags": "production,premium",
        # → sonnet-prod, sonnet-team-a 배포 후보
        # → simple-shuffle로 선택
    }
)

# 개발 배포로 라우팅
response = client.chat.completions.create(
    model="claude-sonnet",
    messages=[{"role": "user", "content": "테스트 내용"}],
    extra_headers={
        "x-litellm-tags": "development",
        # → sonnet-dev만 선택됨
    }
)

⚠️ 보안 경계 주의: Tag 라우팅은 트래픽 분류 힌트입니다. 보안 경계로 사용하면 안 됩니다. x-litellm-tags 헤더는 모든 API 소비자가 임의로 설정할 수 있습니다. 접근 제어는 반드시 Virtual Key + 팀 기반으로 처리해야 합니다.


2. 복잡도 라우터 — 임베딩 없이 서브밀리초 분류

LiteLLM의 Complexity Router는 임베딩 API 호출 없이 규칙 기반 점수화로 요청의 복잡도를 분류합니다. 레이턴시 추가가 거의 없습니다.

from litellm import Router
from litellm.router import RouterConfig

# 복잡도 라우터를 사용한 3티어 설정
router = Router(
    model_list=[
        # SIMPLE 티어 — 짧고 간단한 요청
        {
            "model_name": "simple-tier",
            "litellm_params": {
                "model": "claude-haiku-4-5-20251001",
                "api_key": "...",
                "rpm": 10000,
            },
        },
        # STANDARD 티어 — 일반적인 요청
        {
            "model_name": "standard-tier",
            "litellm_params": {
                "model": "claude-sonnet-4-6",
                "api_key": "...",
                "rpm": 2000,
            },
        },
        # COMPLEX 티어 — 추론이 필요한 요청
        {
            "model_name": "complex-tier",
            "litellm_params": {
                "model": "claude-opus-4-7",
                "api_key": "...",
                "rpm": 500,
            },
        },
    ],
    routing_strategy="simple-shuffle",
)

# Complexity Router를 직접 사용
from litellm.proxy.auto_routing.complexity_router import ComplexityRouter

complexity_router = ComplexityRouter(
    simple_model="simple-tier",
    standard_model="standard-tier",
    complex_model="complex-tier",
    # 추론 마커 2개 이상 감지 시 → complex_model로 강제 라우팅
    # (think step by step, reason through, etc.)
)

async def route_by_complexity(messages: list[dict]) -> str:
    """메시지 복잡도를 분류해 적절한 모델 티어 반환"""
    complexity = await complexity_router.classify(messages)
    # complexity: "SIMPLE" | "STANDARD" | "COMPLEX"
    return complexity_router.get_model_for_tier(complexity)

복잡도 분류 기준:

SIMPLE  → 짧은 메시지 (단어 수 적음)
         단순 질문 패턴 ("What is...", "List 3...")
         코드 없음, 멀티스텝 없음

STANDARD → 중간 길이 메시지
           일반적인 코딩, 설명, 요약 요청
           단일 태스크

COMPLEX → 긴 메시지 또는
          추론 마커 2개 이상:
          "think step by step",
          "reason through",
          "analyze",
          "design a distributed..."
          → 자동으로 COMPLEX 강제 라우팅

3. 시맨틱 라우터 — 발화 유사도 기반 모델 선택

복잡도 분류보다 더 세밀한 콘텐츠 기반 라우팅이 필요하면 시맨틱 라우터를 씁니다. 사용자가 정의한 발화(utterance) 예시와의 임베딩 유사도로 모델을 선택합니다.

LiteLLM은 semantic-router 라이브러리와 통합되어 있습니다. model=auto_router_name 형태로 사용합니다.

# config.yaml — 시맨틱 라우터 설정

model_list:
  # 코드/프로그래밍 전문 모델
  - model_name: code-model
    litellm_params:
      model: claude-opus-4-7
      api_key: os.environ/ANTHROPIC_API_KEY

  # 시각/멀티모달 모델
  - model_name: vision-model
    litellm_params:
      model: gpt-5.5
      api_key: os.environ/OPENAI_API_KEY

  # 기본 범용 모델
  - model_name: default-model
    litellm_params:
      model: claude-sonnet-4-6
      api_key: os.environ/ANTHROPIC_API_KEY

# 시맨틱 라우터 정의
router_settings:
  auto_routing:
    - router_name: smart-router          # model=smart-router 로 요청
      embedding_model: text-embedding-3-small
      routes:
        - name: coding
          utterances:
            - "write a Python function"
            - "debug this code"
            - "implement this algorithm"
            - "fix the bug in"
            - "explain this code"
            - "코드를 작성해줘"
            - "함수를 구현해줘"
          target_model: code-model

        - name: visual
          utterances:
            - "describe this image"
            - "what's in the picture"
            - "analyze the chart"
            - "이미지를 분석해줘"
          target_model: vision-model

        - name: default
          utterances:
            - "help me with"
            - "explain"
            - "what is"
          target_model: default-model
# 시맨틱 라우터 사용 — model 이름만 바꾸면 됨
response = client.chat.completions.create(
    model="smart-router",   # ← 라우터 이름 사용
    messages=[{
        "role": "user",
        "content": "이 Python 코드에서 버그를 찾아줘"
    }]
)
# → "코드 → code-model → claude-opus-4-7" 으로 자동 라우팅

시맨틱 라우터 vs 복잡도 라우터 선택:

복잡도 라우터 (Complexity Router)
  ✅ 레이턴시 추가 없음 (서브밀리초)
  ✅ 임베딩 API 비용 없음
  ✅ 단순/복잡 2~3단계 분류로 충분한 경우
  ❌ "이게 코딩 요청인가 분석 요청인가" 구분 불가

시맨틱 라우터 (Semantic Router)
  ✅ 콘텐츠 유형별 세밀한 분류 가능
  ✅ 발화 예시 추가로 정확도 개선 가능
  ❌ 임베딩 API 호출 비용 + 레이턴시 추가
  ❌ 임베딩 모델 설정 필요
  → 발화를 충분히 정의하지 않으면 오라우팅 발생

4. 커스텀 라우팅 전략 — CustomRoutingStrategyBase

6개 기본 전략과 시맨틱 라우터로 해결되지 않는 요건에는 완전한 커스텀 전략을 구현합니다. CustomRoutingStrategyBase를 상속해 async_get_available_deployment를 구현하면 됩니다.

예시: 사용자 ID 기반 고정 배포 (스티키 세션)

특정 사용자의 요청이 항상 같은 배포로 라우팅되어야 할 때 씁니다. 멀티턴 대화에서 컨텍스트 일관성 유지, 배포별 파인튜닝 모델 사용, A/B 테스트에 활용합니다.

import hashlib
from typing import Optional, Union, Dict, List
from litellm.router import CustomRoutingStrategyBase

class StickyUserRoutingStrategy(CustomRoutingStrategyBase):
    """
    사용자 ID를 해싱해 항상 같은 배포로 라우팅.
    멀티턴 대화 일관성, 배포별 A/B 테스트에 활용.
    """

    async def async_get_available_deployment(
        self,
        model: str,
        messages: Optional[List[Dict[str, str]]] = None,
        input: Optional[Union[str, List]] = None,
        specific_deployment: Optional[bool] = False,
        request_kwargs: Optional[Dict] = None,
    ):
        """
        request_kwargs에서 user_id를 추출해
        해당 user_id를 특정 배포로 고정 매핑.
        """
        # 요청에서 user_id 추출
        user_id = None
        if request_kwargs:
            user_id = request_kwargs.get("user") or \
                      request_kwargs.get("metadata", {}).get("user_id")

        # 해당 model_name의 건강한 배포 목록 조회
        healthy_deployments = await self._get_healthy_deployments(model)

        if not healthy_deployments:
            return None

        if user_id:
            # 사용자 ID → 해시 → 배포 인덱스
            hash_val = int(hashlib.md5(user_id.encode()).hexdigest(), 16)
            idx = hash_val % len(healthy_deployments)
            selected = healthy_deployments[idx]
        else:
            # user_id 없으면 랜덤 선택
            import random
            selected = random.choice(healthy_deployments)

        return selected

    def get_available_deployment(
        self,
        model: str,
        messages=None,
        input=None,
        specific_deployment=False,
        request_kwargs=None,
    ):
        """동기 버전 (async 래퍼)"""
        import asyncio
        return asyncio.run(self.async_get_available_deployment(
            model, messages, input, specific_deployment, request_kwargs
        ))

    async def _get_healthy_deployments(self, model: str) -> list:
        """쿨다운 중이 아닌 건강한 배포 목록 반환"""
        all_deployments = self.router.get_model_list(model_name=model) or []
        healthy = []
        for deployment in all_deployments:
            deployment_id = deployment.get("model_info", {}).get("id", "")
            if not await self.router.async_is_deployment_cooling_down(deployment_id):
                healthy.append(deployment)
        return healthy


# Router에 커스텀 전략 등록
router = Router(
    model_list=[
        {
            "model_name": "claude-sonnet",
            "litellm_params": {"model": "claude-sonnet-4-6", "api_key": "..."},
            "model_info": {"id": "sonnet-deploy-1"},
        },
        {
            "model_name": "claude-sonnet",
            "litellm_params": {"model": "claude-sonnet-4-6", "api_key": "..."},
            "model_info": {"id": "sonnet-deploy-2"},
        },
    ],
    # 커스텀 전략 인스턴스를 직접 전달
    routing_strategy_args={},
)
router.set_custom_routing_strategy(StickyUserRoutingStrategy())

# 사용: user 파라미터로 user_id 전달
response = await router.acompletion(
    model="claude-sonnet",
    messages=[{"role": "user", "content": "이전 대화 이어서"}],
    user="user-12345",   # → 항상 같은 배포로 라우팅
)

5. 실전 아키텍처 1 — 고가용성 멀티 리전

목표: 단일 리전 장애에도 서비스 무중단. 글로벌 사용자에게 가장 가까운 리전으로 자동 라우팅.

# config.yaml — 고가용성 멀티 리전

model_list:
  # ── US East (기본 리전) ─────────────────────────
  - model_name: llm
    litellm_params:
      model: bedrock/anthropic.claude-sonnet-4-6-v1
      aws_region_name: us-east-1
      aws_access_key_id: os.environ/AWS_KEY
      aws_secret_access_key: os.environ/AWS_SECRET
      rpm: 2000
      tpm: 400000
    model_info:
      id: sonnet-us-east
      order: 1

  # ── EU West ──────────────────────────────────────
  - model_name: llm
    litellm_params:
      model: bedrock/anthropic.claude-sonnet-4-6-v1
      aws_region_name: eu-west-1
      aws_access_key_id: os.environ/AWS_KEY
      aws_secret_access_key: os.environ/AWS_SECRET
      rpm: 1500
      tpm: 300000
    model_info:
      id: sonnet-eu-west
      order: 2

  # ── AP Northeast ─────────────────────────────────
  - model_name: llm
    litellm_params:
      model: bedrock/anthropic.claude-sonnet-4-6-v1
      aws_region_name: ap-northeast-1
      aws_access_key_id: os.environ/AWS_KEY
      aws_secret_access_key: os.environ/AWS_SECRET
      rpm: 1000
      tpm: 200000
    model_info:
      id: sonnet-ap-northeast
      order: 3

  # ── 크로스 프로바이더 최후 폴백 ──────────────────
  - model_name: llm-xprovider
    litellm_params:
      model: claude-sonnet-4-6
      api_key: os.environ/ANTHROPIC_API_KEY
      rpm: 1000
      tpm: 200000

  # ── 고용량 폴백 (트래픽 스파이크용) ─────────────
  - model_name: llm-surge
    litellm_params:
      model: gpt-5.5
      api_key: os.environ/OPENAI_API_KEY
      rpm: 5000

router_settings:
  # 레이턴시 기반 — 가장 빠른 리전 우선
  routing_strategy: latency-based-routing
  routing_strategy_args:
    ttl: 60                    # 60초 응답시간 평균
    lowest_latency_buffer: 0.3 # 최저 레이턴시 ±30% 이내 모두 후보

  # 멀티 인스턴스 Redis 동기화
  redis_host: os.environ/REDIS_HOST
  redis_port: 6379
  redis_password: os.environ/REDIS_PASSWORD

  # 동일 그룹(Bedrock 멀티 리전) 내 먼저 재시도
  enable_weighted_failover: true

  num_retries: 2
  timeout: 25
  allowed_fails: 3
  cooldown_time: 60

  fallbacks:
    - llm:
        - llm-xprovider
        - llm-surge
  context_window_fallbacks:
    - llm:
        - llm-xprovider
  default_fallbacks:
    - llm-surge

litellm_settings:
  # 응답 캐싱 (멀티 리전 공유)
  cache: true
  cache_params:
    type: redis
    host: os.environ/REDIS_HOST
    port: 6379
    password: os.environ/REDIS_PASSWORD
    ttl: 3600

  success_callback:
    - langfuse
    - prometheus

레이턴시 기반 라우팅이 이 아키텍처에서 작동하는 방식:

US 서버에서 요청 발생:
  us-east-1: 평균 120ms
  eu-west-1: 평균 380ms
  ap-northeast-1: 평균 210ms

→ latency_buffer=0.3 적용:
   120ms × 1.3 = 156ms 이내 후보: us-east-1만
→ us-east-1 선택

EU 서버에서 요청 발생:
  us-east-1: 평균 340ms
  eu-west-1: 평균 115ms
  ap-northeast-1: 평균 490ms

→ 115ms × 1.3 = 150ms 이내 후보: eu-west-1만
→ eu-west-1 선택

us-east-1 리전 장애:
  CooldownCache에 us-east-1 등록 (60초)
→ 나머지에서 레이턴시 재계산 → eu-west-1 선택

6. 실전 아키텍처 2 — 비용 최적화 3티어

목표: 요청 복잡도에 맞는 모델을 자동 선택해 비용 최소화. 단순 요청에 Opus를 쓰는 낭비 제거.

# config.yaml — 비용 최적화 3티어

model_list:
  # ── FAST 티어: 단순·빠른 요청 ───────────────────
  - model_name: fast
    litellm_params:
      model: claude-haiku-4-5-20251001
      api_key: os.environ/ANTHROPIC_API_KEY
      rpm: 10000
      tpm: 2000000

  # FAST 폴백
  - model_name: fast
    litellm_params:
      model: gemini-3.5-flash
      api_key: os.environ/GEMINI_API_KEY
      rpm: 5000
      tpm: 1000000

  # ── STANDARD 티어: 일반 요청 ─────────────────────
  - model_name: standard
    litellm_params:
      model: claude-sonnet-4-6
      api_key: os.environ/ANTHROPIC_API_KEY
      rpm: 2000
      tpm: 400000

  - model_name: standard
    litellm_params:
      model: bedrock/anthropic.claude-sonnet-4-6-v1
      aws_region_name: us-east-1
      rpm: 2000
      tpm: 400000

  # ── POWER 티어: 복잡한 추론 ──────────────────────
  - model_name: power
    litellm_params:
      model: claude-opus-4-7
      api_key: os.environ/ANTHROPIC_API_KEY
      rpm: 500
      tpm: 100000

  # POWER 폴백
  - model_name: power
    litellm_params:
      model: gpt-5.5
      api_key: os.environ/OPENAI_API_KEY
      rpm: 300

router_settings:
  routing_strategy: simple-shuffle
  redis_host: os.environ/REDIS_HOST

  fallbacks:
    - standard:
        - power    # standard 실패 시 → power로 업그레이드
    - fast:
        - standard # fast 실패 시 → standard로 업그레이드

general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY
  database_url: os.environ/DATABASE_URL
# 애플리케이션에서 복잡도별 티어 선택 라우터

from anthropic import Anthropic
import tiktoken

# LiteLLM Proxy를 통한 클라이언트
client = Anthropic(
    api_key="sk-virtual-key",
    base_url="http://litellm-proxy:4000",
)

FAST_MODEL     = "fast"      # claude-haiku
STANDARD_MODEL = "standard"  # claude-sonnet
POWER_MODEL    = "power"     # claude-opus

def classify_request(messages: list[dict]) -> str:
    """
    요청 특성을 분석해 적절한 티어 반환.
    실제 복잡도 라우터 대신 간단한 규칙 기반 예시.
    """
    # 전체 메시지 토큰 수 추정
    total_text = " ".join(m.get("content", "") for m in messages)
    word_count = len(total_text.split())

    # 추론 마커 감지
    reasoning_markers = [
        "think step by step", "reason through", "analyze",
        "design a", "architect", "compare and contrast",
        "단계별로", "분석해줘", "설계해줘", "비교해줘",
    ]
    has_reasoning = any(
        marker in total_text.lower() for marker in reasoning_markers
    )

    # 코드 복잡도 감지
    code_markers = ["implement", "refactor", "debug", "optimize",
                    "구현", "리팩토링", "디버깅", "최적화"]
    has_complex_code = any(m in total_text.lower() for m in code_markers)

    # 티어 결정
    if has_reasoning or has_complex_code or word_count > 300:
        return POWER_MODEL
    elif word_count > 50:
        return STANDARD_MODEL
    else:
        return FAST_MODEL


async def smart_complete(messages: list[dict]) -> str:
    """복잡도 기반 자동 티어 선택 완성"""
    tier = classify_request(messages)

    response = await asyncio.to_thread(
        client.messages.create,
        model=tier,
        max_tokens=2048,
        messages=messages,
    )
    return response.content[0].text


# 비용 절감 예시:
# 전체 요청의 60%가 fast(Haiku)로 처리된다면:
# 기존: 100% × $3.00/1M = $3.00/1M
# 최적: 60% × $0.25 + 35% × $3.00 + 5% × $5.00 = $1.45/1M
# → 비용 52% 절감

7. 실전 아키텍처 3 — 하이브리드 클라우드 + 온프레미스

목표: 민감한 데이터는 온프레미스(Ollama)로, 일반 트래픽은 클라우드로. 데이터 주권 요건 충족.

# config.yaml — 하이브리드 클라우드+로컬

model_list:
  # ── 온프레미스 로컬 모델 (데이터가 외부로 나가지 않음) ──
  - model_name: secure-llm
    litellm_params:
      model: ollama/llama3.3-70b          # 온프레미스 Ollama
      api_base: http://internal-ollama:11434
      rpm: 100
    model_info:
      id: local-llama
      tags:
        - sensitive
        - internal
        - on-prem

  - model_name: secure-llm
    litellm_params:
      model: ollama/qwen3-32b             # 백업 로컬 모델
      api_base: http://internal-ollama-backup:11434
      rpm: 80
    model_info:
      id: local-qwen
      tags:
        - sensitive
        - internal
        - on-prem

  # ── 클라우드 모델 (일반 트래픽) ───────────────────
  - model_name: cloud-llm
    litellm_params:
      model: claude-sonnet-4-6
      api_key: os.environ/ANTHROPIC_API_KEY
      rpm: 2000
    model_info:
      id: cloud-sonnet
      tags:
        - general
        - cloud

  - model_name: cloud-llm
    litellm_params:
      model: gpt-5.5
      api_key: os.environ/OPENAI_API_KEY
      rpm: 1000
    model_info:
      id: cloud-gpt
      tags:
        - general
        - cloud

  # ── 온프레미스 오버플로우 시 클라우드 폴백 ─────────
  # (로컬 용량 초과 시만 사용, 민감도 낮은 데이터만)
  - model_name: secure-llm-fallback
    litellm_params:
      model: claude-haiku-4-5-20251001
      api_key: os.environ/ANTHROPIC_API_KEY
      rpm: 5000
    model_info:
      id: cloud-haiku-fallback

router_settings:
  routing_strategy: least-busy  # 온프레미스 용량 균등 분산
  enable_tag_filtering: true
  redis_host: os.environ/REDIS_HOST

  # 온프레미스 과부하 시 클라우드 폴백
  # (비즈니스 정책: 용량 부족은 허용, 데이터 유출은 불가)
  fallbacks:
    - secure-llm:
        - secure-llm-fallback  # ⚠️ 민감 데이터는 이 폴백 사용 금지 정책 필요
  context_window_fallbacks:
    - secure-llm:
        - cloud-llm  # 컨텍스트 초과는 로컬 처리 불가 → 클라우드
  default_fallbacks:
    - cloud-llm

  allowed_fails: 2
  cooldown_time: 120   # 로컬 모델은 재시작 시간이 길어 쿨다운 길게

  num_retries: 2
  timeout: 60    # 로컬 모델은 레이턴시가 높을 수 있음
# 애플리케이션에서 데이터 민감도에 따라 모델 선택
async def process_request(
    messages: list[dict],
    contains_pii: bool = False,
    data_classification: str = "public",
) -> str:
    """
    데이터 분류에 따라 적절한 LLM 라우팅.
    """
    if contains_pii or data_classification in ("confidential", "restricted"):
        # 민감 데이터 → 온프레미스만 사용
        model = "secure-llm"
        headers = {"x-litellm-tags": "sensitive,internal"}
    else:
        # 일반 데이터 → 클라우드 허용
        model = "cloud-llm"
        headers = {"x-litellm-tags": "general,cloud"}

    response = client.chat.completions.create(
        model=model,
        messages=messages,
        extra_headers=headers,
    )
    return response.choices[0].message.content

8. 암호화 콘텐츠 어피니티 — OpenAI Responses API

OpenAI Responses API를 멀티 배포로 로드밸런싱할 때 특수한 문제가 있습니다. 암호화된 rs_... 아이템은 생성한 배포의 API 키로만 복호화됩니다. 다른 배포로 라우팅되면 복호화 실패.

# config.yaml — 암호화 콘텐츠 어피니티 (LiteLLM >= 1.82.3)

router_settings:
  enable_pre_call_checks:
    - encrypted_content_affinity  # 🆕 암호화 아이템 → 원본 배포로 고정

model_list:
  - model_name: responses-model
    litellm_params:
      model: openai/gpt-5.5
      api_key: os.environ/OPENAI_KEY_1
    model_info:
      id: openai-key-1

  - model_name: responses-model
    litellm_params:
      model: openai/gpt-5.5
      api_key: os.environ/OPENAI_KEY_2
    model_info:
      id: openai-key-2

encrypted_content_affinity pre-call check가 활성화되면 요청에 rs_ 아이템이 포함됐을 때 해당 아이템을 생성한 배포(동일 API 키)로 강제 라우팅합니다. 다른 요청은 계속 정상적으로 로드밸런싱됩니다.


LiteLLM의 한계 — 솔직한 평가

4편을 마무리하면서 LiteLLM이 적합하지 않은 상황을 명확히 합니다.

고트래픽 레이턴시 (500+ RPS)

LiteLLM은 Python 기반입니다. 500 RPS 이상에서 Python의 async 오버헤드가 수백 마이크로초~밀리초를 추가합니다. 5,000 RPS 벤치마크에서 Go 기반 Bifrost는 11μs 오버헤드인 반면 LiteLLM은 수백μs~ms를 추가합니다. 초고트래픽 요건에서는 Go 기반 게이트웨이를 고려해야 합니다.

실제 Circuit Breaker 패턴

LiteLLM의 allowed_fails + cooldown_time은 failure budget입니다. Half-open 상태를 갖는 진정한 Circuit Breaker가 아닙니다. Half-open 프로빙이 필요하다면 커스텀 콜백이나 외부 서비스 메시(Istio 등)가 필요합니다.

엔터프라이즈 거버넌스

SSO/SAML, 감사 로그, 고급 RBAC 같은 엔터프라이즈 기능은 LiteLLM의 유료 라이선스 영역입니다. 오픈소스만으로는 일부 엔터프라이즈 요건을 충족하기 어렵습니다.


✅ 시리즈 마무리 — LiteLLM 로드밸런싱 완전 정복

편 핵심 내용

1편 Router 구조, 6가지 전략 원리와 선택 기준
2편 폴백 3종, 재시도 정책, 쿨다운 회로 차단기
3편 Redis 연동, Docker/K8s 배포, Virtual Key 예산
4편 콘텐츠 기반 라우팅, 커스텀 전략, 3가지 실전 아키텍처

최종 선택 기준:

단순 멀티 프로바이더 폴백:
  → simple-shuffle + fallbacks → 1~2일 설정

팀 규모 운영 (비용 추적, 팀별 한도):
  → Proxy + PostgreSQL + Virtual Key → 1주일

엔터프라이즈 고가용성:
  → Redis + 멀티 리전 + HPA + 모니터링 → 2~3주

500+ RPS 초고트래픽:
  → Go 기반 게이트웨이 (Bifrost 등) 고려

LiteLLM은 1억 건 이상의 프로덕션 요청, 2억 4천만 Docker 풀이 증명하는 성숙한 도구입니다. 올바른 전략·폴백·인프라 조합으로 단일 프로바이더 의존에서 벗어나 진정한 멀티 프로바이더 고가용성 LLM 게이트웨이를 구축할 수 있습니다.

 

LiteLLM 시리즈 완결

  1. ✅ Router 구조와 라우팅 전략 6가지  https://cell-devlog.tistory.com/273
  2. ✅ 폴백 전략과 장애 대응 https://cell-devlog.tistory.com/274
  3. ✅ 프로덕션 배포: Redis + Proxy 서버 https://cell-devlog.tistory.com/275
  4. ✅ 고급 라우팅과 실전 아키텍처 https://cell-devlog.tistory.com/276
반응형