如何自架 LiteLLM 作為統一 LLM Gateway
透過自架 LiteLLM 整合多個 LLM 供應商,提供 OpenAI 相容端點。本指南說明如何設定虛擬金鑰、預算控管、自動備援機制與請求日誌,讓您在 VPS 上安全管理所有 API 呼叫與成本支出。
自架 LLM gateway 的功能
LiteLLM 是一個可自行架設的開源 LLM gateway:它提供單一 HTTP 端點供所有應用程式呼叫,並將請求轉發至指定的服務供應商。LLM 指的是大型語言模型(Large Language Model)。此 gateway 支援 OpenAI chat completions API,因此任何現有的 OpenAI 客戶端程式庫,只需修改 base URL 與 key 兩項設定,即可直接對接使用。
這種間接層正是其核心價值。應用程式無需再儲存供應商的憑證。若要更換模型,只需修改伺服器設定檔中的一行內容,無須更動五個服務的程式碼。由於所有呼叫皆通過單一處理程序,您能統一控管預算並記錄所有支出。
系統運作後,您將擁有以下功能:
- 單一端點。 應用程式指向
https://gateway.example.com/v1並請求您自訂的模型名稱,例如bulk或strong。 - 虛擬金鑰。 每個應用程式皆擁有專屬金鑰,並可設定個別的模型白名單與支出上限。您可以撤銷其中一個金鑰,而不影響其他應用程式。
- 備援機制。 若呼叫失敗或提示詞(prompt)過大,系統會自動改用其他模型重試。
- 完整日誌。 每一筆請求都會記錄其成本,讓您能明確追蹤「是哪個應用程式產生了該筆費用」。
為何要自行架設閘道器
託管式路由器在處理每個請求時,都會經過第三方程序。自行架設可確保供應商金鑰與提示詞(prompt)皆儲存於您掌控的機器中。這確實需要付出代價:您現在必須維護每個應用程式所依賴的核心組件。本指南最後一節將探討此成本,因為這是大多數技術文章所忽略的部分。
準備工作
- 一台執行 Ubuntu 24.04 的 VPS(虛擬專用伺服器),並已安裝 Docker 與 Compose 外掛程式。
- 若外部機器需透過 TLS(傳輸層安全性協定)連線至閘道,則需準備一個指向該伺服器的網域名稱。
- 至少一組供應商 API key。
此閘道本身不執行推論。它僅負責轉送請求並回傳串流答案,因此 CPU 負載取決於請求量而非模型大小。一台 1 vCPU 的伺服器即可順暢執行多個內部應用程式。真正會隨時間增長的是資料庫,因為閘道會為每個請求寫入一筆費用記錄。
先編寫 config.yaml
設定檔決定了用戶端可以請求哪些模型。其中四個頂層區段至關重要:model_list、litellm_settings、router_settings 與 general_settings。
model_list:
- model_name: bulk
litellm_params:
model: anthropic/claude-haiku-4-5
api_key: os.environ/ANTHROPIC_API_KEY
- model_name: strong
litellm_params:
model: anthropic/claude-sonnet-5
api_key: os.environ/ANTHROPIC_API_KEY
- model_name: strong
litellm_params:
model: openai/gpt-5.5
api_key: os.environ/OPENAI_API_KEY
litellm_settings:
num_retries: 2
request_timeout: 120
allowed_fails: 3
cooldown_time: 30
json_logs: true
set_verbose: false
router_settings:
fallbacks: [{"bulk": ["strong"]}]
context_window_fallbacks: [{"bulk": ["strong"]}]
general_settings:
background_health_checks: true
health_check_interval: 300model_name 是用戶端發送的名稱。litellm_params.model 則是實際使用的模型,寫法為 provider/model。請以工作任務而非供應商名稱來命名模型。當應用程式請求 bulk 時,即使下個月您決定將 bulk 替換為其他模型,應用程式仍能正常運作。
api_key: os.environ/ANTHROPIC_API_KEY 會指示 LiteLLM 在執行時期讀取該變數。設定檔中不會出現實際的金鑰字串,這點很重要,因為 config.yaml 是您會提交至版本控制的檔案。
有兩個項目刻意共用了 strong 這個名稱。當多個部署項目具有相同的 model_name 時,路由器會將它們視為可互換的,並在第一個部署失敗時嘗試另一個。這就是 strong 如何在供應商發生故障時維持運作的方式。
num_retries: 2 會在發生可重試的錯誤時,對同一個部署進行重試。只有在重試次數耗盡後,才會觸發後備機制(fallback)。allowed_fails: 3 搭配 cooldown_time: 30 可在部署失敗 3 次後,將其從輪詢中移除 30 秒,確保持續回傳 500 錯誤的供應商不會在每個請求中被重複嘗試。
fallbacks 與 context_window_fallbacks 的觸發條件不同,後者是人們常忽略但非常有用的功能。
fallbacks會在主要呼叫失敗時觸發。context_window_fallbacks會在供應商因請求長度超過模型上下文視窗(context window)而拒絕時觸發,這樣過大的提示詞(prompt)會被轉送到有足夠空間的模型,而不是直接回傳錯誤給呼叫端。
此外還有 content_policy_fallbacks,用於處理供應商因內容政策而拒絕請求的情況。請僅在您有合適的轉送目標時才設定此項。
在 VPS 上使用 Docker Compose 部署 LiteLLM
建立一個目錄,並在其中放入 config.yaml、docker-compose.yml 與 .env 三個檔案。官方快速入門指南會拉取 latest 標籤。請改為鎖定特定版本標籤,這樣即使 docker compose up -d 下個月執行時,也能確保取得與今日相同的閘道器版本,並讓版本回滾只需一行指令即可完成。
services:
litellm:
image: ghcr.io/berriai/litellm:v1.95.0
restart: unless-stopped
command: ["--config", "/app/config.yaml", "--num_workers", "1"]
ports:
- "127.0.0.1:4000:4000"
volumes:
- ./config.yaml:/app/config.yaml:ro
env_file: .env
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: litellm
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
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:Compose 在此處會讀取兩次 .env。第一次是為了替換 compose 檔案內部的 ${POSTGRES_PASSWORD},第二次則是透過 env_file 將所有變數傳遞至容器內。
v1.95.0 是 2026 年 8 月時的當前版本。請查看該專案的發布頁面,並在部署時鎖定當時最新的版本。每個發布版本都會發布簽章,因此您可以在信任該映像檔前先進行驗證:
cosign verify --key https://raw.githubusercontent.com/BerriAI/litellm/v1.95.0/cosign.pub ghcr.io/berriai/litellm:v1.95.0連接埠設定行是 127.0.0.1:4000:4000,這僅會將連接埠發布在 loopback 介面上。若改寫為 4000:4000,您的閘道器將可從網際網路存取,因為 Docker 會在 iptables 的 FORWARD 鏈中加入自己的規則,且這些規則的評估優先於 ufw,因此 ufw deny 4000 無法阻擋此流量。這是自架閘道器最常見的對外開放方式:請參閱 Docker 如何繞過 ufw 發布容器連接埠。外部流量應透過反向代理進入。
將供應商金鑰排除在映像檔之外
.env 檔案存放所有機密資訊。該檔案在執行階段以環境變數形式傳入,因此絕不會被寫入映像檔,也不會被提交至版本控制系統。
LITELLM_MASTER_KEY=sk-REPLACE_ME
LITELLM_SALT_KEY=sk-REPLACE_ME_TOO
POSTGRES_PASSWORD=REPLACE_ME_AS_WELL
DATABASE_URL=postgresql://litellm:REPLACE_ME_AS_WELL@db:5432/litellm
STORE_MODEL_IN_DB=True
LITELLM_MODE=PRODUCTION
LITELLM_LOG=ERROR
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-proj-...請使用真正的隨機數產生兩組 LiteLLM 金鑰,並鎖定該檔案權限:
printf 'sk-%s\n' "$(openssl rand -hex 32)"
chmod 600 .envLITELLM_MASTER_KEY 是管理員憑證。它用於驗證管理 API,也是 /ui 管理介面的登入密碼。任何應用程式都不應儲存此憑證。
LITELLM_SALT_KEY 用於加密儲存在資料庫中的供應商憑證。請設定一次後便不再更動。若事後變更,已儲存的憑證將無法解密,導致閘道器雖然能正常啟動,但後續所有對供應商的呼叫都會因驗證失敗而中斷。
STORE_MODEL_IN_DB=True 讓您無需修改 config.yaml 即可透過管理介面新增與編輯模型。這雖然方便,但會導致設定來源分散。請決定哪一個來源具備最高權限,並將此決策記錄在 config 檔案旁。
將金鑰排除在設定檔之外的邏輯,與將金鑰排除在交給代理程式(agent)的工具之外的邏輯相同。將供應商機密排除在 AI 代理程式之外 涵蓋了此模式,而 Docker Compose 中的環境變數檔案與機密管理 則說明了相關技術細節。
啟動服務並監控首次開機過程:
docker compose up -d
docker compose logs -f litellm確認服務運作正常
目前有兩個未經身分驗證的探測端點,以及一個需經身分驗證的端點,它們會因不同原因而失敗。
curl -s http://127.0.0.1:4000/health/liveliness
curl -s http://127.0.0.1:4000/health/readiness/health/liveliness 不需要身分驗證,只要處理程序正在執行,就會回應 "I'm alive!"。/health/readiness 同樣不需要身分驗證。它會回傳包含 "status": "healthy" 與 db 欄位的 JSON 物件;若無法連線至資料庫,則會回傳 503 錯誤。請將監控指向 readiness,因為即使閘道器無法查詢任何虛擬金鑰,liveliness 仍會顯示為正常(綠色)。
需經身分驗證的檢查項目會與供應商進行通訊:
curl -s http://127.0.0.1:4000/health \
-H "Authorization: Bearer $LITELLM_MASTER_KEY"它會以 healthy_endpoints 與 unhealthy_endpoints 陣列進行回應。若模型位於 unhealthy_endpoints 且出現身分驗證錯誤,代表 .env 中的供應商金鑰錯誤或遺失,這正是您目前需要排查的故障點。由於已設定 background_health_checks: true,代理程式會自行每 health_check_interval 秒執行一次這些探測,並由 /health 回傳最後一次的結果,因此輪詢該端點並不會每次都向您的供應商發送測試請求。
虛擬金鑰與個別金鑰預算
每個應用程式都會取得一組專屬金鑰,並由主金鑰(master key)進行簽發。
curl -s http://127.0.0.1:4000/key/generate \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-H 'Content-Type: application/json' \
-d '{
"key_alias": "nightly-summariser",
"models": ["bulk"],
"max_budget": 5,
"budget_duration": "30d",
"rpm_limit": 60,
"tpm_limit": 200000
}'回應中包含一個 key 欄位,開頭為 sk-。該字串即為應用程式所取得的內容,也是應用程式唯一會接觸到的資訊。
models是此金鑰可請求項目的允許清單。上述金鑰僅能請求bulk,不得請求其他項目。max_budget: 5搭配budget_duration: "30d"代表每 30 天滾動週期內預算為 5 美元,超過後金鑰將停止運作。rpm_limit與tpm_limit分別針對此金鑰單獨限制每分鐘請求數與每分鐘 Token 數。key_alias是您在六週後的支出日誌中識別該金鑰的依據。請務必設定此欄位。
當預算用盡時,API 呼叫會失敗並回傳 HTTP 401 狀態碼,回應主體格式如下:
ExceededBudget: Current spend for token: 7.2e-05; Max Budget for Token: 2e-07狀態碼的選擇容易造成混淆。客戶端程式庫會將 401 視為驗證問題,導致開發者在閱讀堆疊追蹤(stack trace)時,會誤以為是金鑰失效。請務必將回應主體與狀態碼一併記錄,否則預算耗盡時,每次看起來都像是憑證損壞。
透過相同的管理 API 檢查並調整金鑰:
curl -s "http://127.0.0.1:4000/key/info?key=sk-..." \
-H "Authorization: Bearer $LITELLM_MASTER_KEY"
curl -s -X POST http://127.0.0.1:4000/key/update \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-H 'Content-Type: application/json' \
-d '{"key": "sk-...", "max_budget": 25}'在閘道端強制執行的預算,即使在代理程式(agent)本身發生錯誤時依然有效,這也是為何它是 VPS 上 AI 代理程式成本控制 的核心機制。
將大量工作發送至低成本模型
將客戶端指向閘道器。設定 Base URL、金鑰與模型名稱:
curl -s http://127.0.0.1:4000/v1/chat/completions \
-H "Authorization: Bearer sk-<the virtual key>" \
-H 'Content-Type: application/json' \
-d '{
"model": "bulk",
"messages": [{"role": "user", "content": "Say hello in five words."}]
}'任何 OpenAI 客戶端程式庫的操作方式皆相同:將 base_url 設定為 https://gateway.example.com/v1,並將 api_key 設定為虛擬金鑰。
此時,config.yaml 中的路由策略會在呼叫端不知情的情況下自動套用。針對 bulk 的請求會導向低成本模型。若該呼叫在重試後失敗,請求將會改為對 strong 進行重試。若提示詞長度超過 bulk 的限制,context_window_fallbacks 會將其轉送至 strong,而非直接回傳錯誤。分類任務或摘要積壓工作等大量作業預設會以低成本執行,僅有困難的請求才會產生較高費用。
這也是閘道器在處理使用工具的代理程式(agent)時展現價值之處。位於 同一台 VPS 上的 MCP (model context protocol) 伺服器 與驅動它的代理程式皆可指向同一個端點,因此無需重新部署兩者即可更換後端模型。
如何確認發生了後備(fallback)機制?
這是最耗費成本的故障模式,因為表面上一切正常。成功的後備會回傳 HTTP 200 與正常的響應主體。您的廉價模型可能已停擺一天,所有請求皆由昂貴的模型默默處理,直到收到帳單時才會發現。
證據存在於響應標頭(response headers)中。請檢查以下內容:
curl -s -D - -o /dev/null http://127.0.0.1:4000/v1/chat/completions \
-H "Authorization: Bearer sk-<the virtual key>" \
-H 'Content-Type: application/json' \
-d '{"model":"bulk","messages":[{"role":"user","content":"ping"}]}' \
| grep -i '^x-litellm'x-litellm-model-group是客戶端請求的目標。x-litellm-model-id是實際回應的部署版本。當兩者不一致時,即代表發生了後備。x-litellm-attempted-fallbacks與x-litellm-attempted-retries用於計算次數。在正常的請求中,兩者皆應為 0。x-litellm-response-cost是該次請求以美元計算的成本。x-litellm-call-id是您在日誌中追蹤該次請求的識別碼。
請記錄每次請求的 x-litellm-attempted-fallbacks,並在數值不為 0 時發出警報。這個數字是判斷路由策略是否正常運作,或是已悄悄變成「永遠使用昂貴模型」的關鍵指標。
完整的解決方案是追蹤(tracing),這需要獨立的設定:自架 Langfuse 以追蹤 Agent 請求。LiteLLM 內建了回呼功能,只需兩行程式碼與憑證即可完成串接。
litellm_settings:
success_callback: ["langfuse"]
failure_callback: ["langfuse"]LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_HOST=https://langfuse.example.com請務必設定 failure_callback 與 success_callback。若忽略此步驟,您將只會保留請求成功的追蹤紀錄。此外,LiteLLM 會將每筆請求的費用寫入 Postgres,而管理介面 /ui 會讀取該資料表。隨著流量增加,資料表會持續成長,請務必監控磁碟空間。
將閘道器置於反向代理之後
外部網路不應直接存取 4000 埠。請在 Nginx 或 Caddy 進行 TLS termination,並將請求轉發至 loopback 位址。
location / {
proxy_pass http://127.0.0.1:4000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 600s;
}其中兩行設定常被忽略。proxy_buffering off 至關重要,因為串流完成(streaming completion)是一連串的 server-sent events;若開啟緩衝(buffering),Nginx 會在回應結束前扣住所有區塊,導致客戶端在等待期間毫無反應,隨後一次性收到所有內容。proxy_read_timeout 600s 同樣重要,因為長時間的生成任務會超過 Nginx 預設的 60 秒逾時限制,一旦發生,客戶端將收到 504 錯誤,且錯誤日誌會記錄 upstream timed out (110: Connection timed out) while reading response header from upstream。
關於憑證,在 Nginx 上使用 Certbot 與 Let's Encrypt 是最快的途徑。若該伺服器已運行多個容器,使用 Traefik 處理多個 Compose 應用程式 可在單一位置統一管理路由與憑證。
閘道器現在成為單點故障
請務必誠實看待您所建構的架構。您擁有的所有應用程式現在都依賴於單一台 VPS 上的單一個容器。當該容器停機時,任何模型都無法被呼叫,即使提供商本身的服務完全正常也一樣。這衍生出四個重點:
- 錯誤的設定會導致全線癱瘓。
restart: unless-stopped會重啟崩潰的服務,但它也會不斷重啟一個無法解析 config.yaml 的容器。每次修改設定後請務必閱讀docker compose logs litellm,並在您有時間監控時才進行設定變更。 - Postgres 位於請求路徑上。 虛擬金鑰查詢與費用記錄皆會使用它。
/health/readiness回傳 503 是您的警告,代表閘道器雖在執行中,但兩者皆無法運作。 - 透過增加實例來擴展,而非單純加大單一實例。 本專案的建議是每個實例配置一個 worker (
--num_workers 1),並讓多個實例共用同一個資料庫。在負載平衡器後方部署兩個小型閘道器,即可消除單一容器帶來的風險。但這無法消除資料庫的單點風險。 - 備份無法重新產生的資料。 這指的是
config.yaml與.env,以及資料庫的pg_dump。遺失LITELLM_SALT_KEY會導致備份檔中加密的提供商憑證失效,因此環境變數檔案與資料庫備份檔應納入同一個備份作業:使用 restic 備份至外部儲存空間。
升級方式為編輯映像檔標籤並執行 docker compose up -d。LiteLLM 預設會在啟動時執行 prisma migrate deploy,因此新容器會在首次開機時遷移資料庫結構。在變更標籤前請務必先進行備份,因為將映像檔改回舊版本並無法復原已經執行過的遷移作業。
FAQ
LiteLLM 是否會對每次呼叫增加明顯的延遲?
根據 2026 年 8 月的 README,該專案宣稱在每秒 1000 次請求下,第 95 百分位數的延遲為 8 ms。請將此視為廠商提供的數據。真正影響延遲的關鍵在於應用程式與閘道器之間的網路距離,因為這會在每次呼叫中增加一次往返。請將閘道器部署在與呼叫端應用程式相同的區域,並透過實際回應中的 x-litellm-overhead-duration-ms 標頭來測量您環境的額外開銷。
為何在 nginx 後方串流功能就失效了?
因為 nginx 預設會緩衝上游回應,而串流完成是由一系列伺服器推送事件(server-sent events)組成的。當 proxy_buffering 開啟時,nginx 會收集所有區塊,直到回應結束才一次釋出,導致客戶端在等待期間毫無反應,隨後一次收到完整答案。請在 location 區塊中設定 proxy_buffering off;。同時,請在該區塊中調高 proxy_read_timeout,否則長時間的生成過程會超過 nginx 預設的 60 秒限制,導致客戶端收到 504 錯誤。
當虛擬金鑰(virtual key)預算用盡時會發生什麼事?
呼叫會失敗並回傳 HTTP 401,回應主體格式為 ExceededBudget: Current spend for token: 7.2e-05; Max Budget for Token: 2e-07。401 是個陷阱:客戶端函式庫會將其回報為驗證失敗,導致使用者開始檢查金鑰是否有效,而非閱讀錯誤訊息。請務必將回應主體與狀態碼一併記錄。請使用 /key/info?key=sk-... 對照主金鑰以確認該金鑰的實際狀態,若預算設定過低,請使用 /key/update 調高上限。
閘道器是否能同時路由至本機模型與託管模型?
可以,這只是 model_list 中的另一個項目。請使用 ollama_chat/ 前綴搭配 api_base,例如將 model: ollama_chat/llama3.1 與 api_base: http://ollama:11434 並列。在容器內部,localhost 指的是該容器本身,因此請使用 Docker 網路中的 Compose 服務名稱或主機位址,切勿使用 127.0.0.1。架設本機模型是獨立的作業:請參閱 在 VPS 上使用 Ollama 自架 LLM。