Awalnya cuma satu API key provider di .env. Enam bulan kemudian ada empat service, tiga provider, dan satu thread Slack yang nanya kenapa tagihan Anthropic naik tiga kali lipat. Tiap service punya salinan key sendiri, loop retry sendiri, dan definisi sendiri soal model mana yang murah.
Bikin shared client library kelihatan seperti solusinya, sampai logic retry-nya beda antar repo dan pinning versinya jadi perang kecil. Gateway menyelesaikan ini di layer network: semua service nembak satu endpoint yang kompatibel OpenAI, dan gateway yang mutusin provider mana yang nanganin request, apa yang terjadi kalau provider itu balikin 500, dan berapa budget tiap key.
LiteLLM Proxy adalah gateway itu, dan cukup jalan di VM yang sama dengan aplikasi kamu. Di akhir tutorial ini kamu punya dua alias model, jalur fallback yang tetap hidup saat satu provider mati, dan virtual key dengan limit $5 per 30 hari. Semua langkah di bawah jalan lokal. Buat production kamu cuma ganti tag image, bind address, dan password Postgres.
Yang perlu disiapkan
- Docker Engine 24+ dengan Compose v2 (
docker compose version) - API key dari dua provider, di sini OpenAI dan Anthropic
curldanjq- Python 3.10+ cuma buat tes SDK di bagian akhir
LiteLLM 1.108.3 (versi yang dipakai saat artikel ini ditulis) support Python 3.10 sampai 3.14. Container-nya bawa runtime sendiri, jadi batasan itu cuma relevan kalau kamu install SDK langsung.
Langkah 1: jalankan gateway dan Postgres
Budget dan pencatatan spend disimpan di database. Tanpa Postgres, proxy tetap melayani request, tapi semua pengecekan budget gagal terbuka. Ini lebih bahaya daripada nggak punya budget sama sekali, karena kamu merasa sudah aman.
Bikin file env dulu. LITELLM_SALT_KEY mengenkripsi API key provider yang disimpan di database dan nggak bisa dirotasi di tempat, jadi simpan file ini baik-baik.
printf 'LITELLM_MASTER_KEY=sk-%s\nLITELLM_SALT_KEY=sk-%s\nPOSTGRES_PASSWORD=%s\n' \
"$(openssl rand -hex 32)" "$(openssl rand -hex 32)" "$(openssl rand -hex 16)" > .env
Tambahkan key provider ke file yang sama:
printf 'OPENAI_API_KEY=%s\nANTHROPIC_API_KEY=%s\n' \
"sk-key-openai-kamu" "sk-ant-key-anthropic-kamu" >> .env
Sekarang docker-compose.yml:
services:
litellm:
image: docker.litellm.ai/berriai/litellm:latest
ports:
- "127.0.0.1:4000:4000"
environment:
LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY}
LITELLM_SALT_KEY: ${LITELLM_SALT_KEY}
DATABASE_URL: postgresql://litellm:${POSTGRES_PASSWORD}@db:5432/litellm
OPENAI_API_KEY: ${OPENAI_API_KEY}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
volumes:
- ./config.yaml:/app/config.yaml:ro
command: ["--config", "/app/config.yaml", "--port", "4000"]
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
environment:
POSTGRES_USER: litellm
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: litellm
healthcheck:
test: ["CMD-SHELL", "pg_isready -U litellm"]
interval: 5s
timeout: 5s
retries: 10
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Dua hal penting di sini. Port-nya di-bind ke 127.0.0.1 supaya gateway nggak bisa diakses dari internet sebelum kamu pasang TLS di depannya. Dan tag image pakai latest karena itu yang dipakai quickstart resmi; cek release notes dan pin versi tertentu sebelum dipakai buat traffic nyata.
Langkah 2: definisikan alias model
config.yaml adalah tempat semua routing. Aplikasi kamu cuma akan nembak fast atau smart.
model_list:
- model_name: fast
litellm_params:
model: openai/gpt-6-luna
api_key: os.environ/OPENAI_API_KEY
- model_name: smart
litellm_params:
model: anthropic/claude-sonnet-5
api_key: os.environ/ANTHROPIC_API_KEY
router_settings:
routing_strategy: latency-based-routing
num_retries: 2
timeout: 30
allowed_fails: 3
cooldown_time: 30
fallbacks: [{"fast": ["smart"]}]
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
model_name adalah alias yang dikirim aplikasi. litellm_params.model adalah string provider asli, dan prefix-nya ngasih tahu LiteLLM SDK serta gaya auth mana yang dipakai. Memisahkan keduanya bikin ganti provider jadi urusan config, bukan perubahan kode.
Setting router-nya layak dibaca sekali. num_retries: 2 retry di deployment yang sama. allowed_fails: 3 masukin deployment ke cooldown selama cooldown_time detik setelah tiga kali gagal, jadi satu key rusak nggak makan semua request semenit ke depan. fallbacks ngirim traffic fast ke smart setelah retry habis.
Jalankan:
docker compose up -d
curl -s http://localhost:4000/health/readiness | jq
Output yang diharapkan:
{"status": "healthy", "db": "connected"}
Endpoint itu ngecek proses dan database. Buat ngecek provider benar-benar jawab atau nggak, panggil route health yang butuh auth. Perlu diingat route ini ngirim request nyata ke tiap deployment, jadi ada biaya beberapa token setiap panggilan:
curl -s http://localhost:4000/health \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq '.healthy_count, .unhealthy_count'
Langkah 3: terbitkan key dengan budget
Service kamu jangan pernah pegang master key. Key itu bisa bikin dan hapus key lain, baca semua spend log, dan ubah routing. Terbitkan virtual key per service.
curl -s http://localhost:4000/key/generate \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"key_alias": "checkout-service",
"models": ["fast", "smart"],
"max_budget": 5,
"budget_duration": "30d",
"rpm_limit": 60
}' | jq -r .key
Simpan nilai yang muncul sebagai LITELLM_KEY. Response itu satu-satunya kesempatan lihat key lengkapnya.
List models itu allowlist, bukan saran. Dua alias dimasukkan karena fallback bisa mendarat di salah satunya. max_budget dalam USD dan budget_duration reset hitungannya tiap 30 hari, jadi key ini dapat $5 per bulan, bukan $5 total. rpm_limit membatasi 60 request per menit, cukup buat nahan loop liar yang mau habisin budget dalam semenit.
Langkah 4: panggil dari aplikasi
Gateway ini ngomong format chat completions OpenAI, jadi kode SDK yang sudah ada tetap jalan cuma dengan ganti base URL.
curl -i http://localhost:4000/v1/chat/completions \
-H "Authorization: Bearer $LITELLM_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "fast", "messages": [{"role": "user", "content": "Say hello in five words."}]}'
Cek header response. x-litellm-model-api-base kasih tahu upstream mana yang benar-benar melayani request, dan itu cara tercepat memastikan fallback jalan.
import os
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:4000/v1",
api_key=os.environ["LITELLM_KEY"],
)
resp = client.chat.completions.create(
model="fast",
messages=[{"role": "user", "content": "Classify this ticket: my card was charged twice."}],
)
print(resp.choices[0].message.content)
Nggak ada satu baris pun di file itu yang tahu soal Anthropic. Kalau besok kamu putuskan fast pakai model Gemini, edit config.yaml dan restart container. Aplikasi tetap kirim alias yang sama.
Langkah 5: buktikan fallback-nya jalan
Fallback yang belum pernah dipicu cuma tebakan. Rusak key utama dengan sengaja dan lihat header-nya berubah.
OPENAI_API_KEY=sk-invalid docker compose up -d --force-recreate litellm
Variable shell menang atas .env saat interpolasi, jadi container restart dengan key yang bakal gagal auth. Kirim request yang sama dan periksa header:
curl -s -D - -o /dev/null http://localhost:4000/v1/chat/completions \
-H "Authorization: Bearer $LITELLM_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "fast", "messages": [{"role": "user", "content": "ping"}]}' \
| grep -i x-litellm-model-api-base
Harusnya muncul API base Anthropic, bukan OpenAI. Balikin key aslinya setelah selesai:
docker compose up -d --force-recreate litellm
Tes ini juga ngasih gambaran soal latency. Satu percobaan auth gagal plus retry nambah delay ke request itu, jadi jaga num_retries tetap kecil dan timeout tetap ketat. Fallback yang butuh 40 detik bukan fallback buat traffic interaktif.
Langkah 6: baca spend-nya
curl -s "http://localhost:4000/key/info?key=$LITELLM_KEY" \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq '.info.spend'
Biaya diambil dari price map LiteLLM, jadi ini estimasi berdasarkan harga per token yang dipublikasikan, bukan kontrak hasil nego kamu. Buat dashboard tim biasanya cukup akurat. Buat nagih ke customer, cocokkan dengan invoice provider sebulan sekali.
Saat key lewat max_budget, request mulai gagal dengan error budget, bukan lanjut diam-diam. Sambungkan error itu ke log aplikasi, kalau nggak, orang pertama yang sadar adalah user.
Yang jebol di production
- Cuma satu replika. Dengan dua container proxy, cooldown dan rate limit hidup di memory masing-masing. Tambah Redis dan arahkan
router_settings.redis_hostke situ sebelum scale out. - Master key ikut dikirim ke aplikasi. Perlakukan seperti root. Rotasi kalau sampai masuk ke bundle frontend atau log CI.
- Rotasi
LITELLM_SALT_KEY. Nggak ada rotasi di tempat. Ganti key ini bikin semua API key provider yang tersimpan nggak terbaca sampai kamu masukin ulang. - Budget tanpa database. Pengecekannya dilewati, dan kamu cuma dapat satu baris warning saat startup.
Kapan pakai ini, kapan pakai alternatif
SDK provider langsung di tiap service aman kalau kamu cuma punya satu provider dan satu tim. Begitu service kedua butuh fallback, kamu bakal nyalin kode routing ke repo kedua.
OpenRouter lebih cepat dimulai kalau kamu mau routing hosted dan nggak mau ngurus Postgres. Tukarnya, traffic dan data spend kamu ada di gateway orang lain, dan budget per key ikut aturan mereka.
Wrapper FastAPI buatan sendiri masuk akal cuma kalau routing-nya tergantung logic bisnis, misalnya tenant enterprise diarahkan ke deployment tertentu. Meski begitu kamu tetap bakal bikin ulang retry, cooldown, dan tabel harga, yang justru sebagian besar isi proxy ini.
Langkah berikutnya
Tambah deployment kedua dengan model_name yang sama buat load balance dua API key dari satu provider. Pindah dari key per service ke team kalau mau budget bareng dengan limit per anggota. Dan sebelum replika kedua, pasang Redis supaya state router-nya dibagi.
Config di artikel ini sekitar 30 baris. Itu intinya: routing, failover, dan kontrol spend berhenti jadi kode aplikasi dan jadi infrastruktur yang bisa dibaca dalam satu layar.