如何自托管 LiteLLM 作为 LLM 网关
用 LiteLLM 在 VPS 上提供单一 OpenAI 兼容端点,配置虚拟密钥、每个密钥的预算、故障转移,并固定 Docker 镜像版本。
自托管 LLM 网关的作用
LiteLLM 是一个由您自行托管的开源 LLM 网关:它提供一个 HTTP 端点,所有应用都调用该端点,网关再将每个请求转发给应答的服务商。LLM 是大型语言模型的缩写。该网关使用 OpenAI 聊天补全 API(应用程序编程接口),因此任何已经能够调用 OpenAI 的客户端库,只需修改两项配置即可使用:基础 URL 和密钥。
这一层间接关系正是网关的价值所在。应用不再保存服务商凭据。更换模型时,只需修改服务器上的一行配置,而不必修改 5 个服务中的代码。由于所有调用都经过同一个进程,您可以在此设置预算,并记录费用支出。
运行后,您将获得以下功能:
- 一个端点。 应用访问
https://gateway.example.com/v1,并请求您自定义的模型名称,例如bulk或strong。 - 虚拟密钥。 每个应用都有独立的密钥、模型允许列表和支出上限。撤销某个密钥不会影响其他密钥。
- 故障转移。 请求失败或提示词过大时,网关会自动改用其他模型重试。
- 日志记录。 每个请求都会写入一条包含费用的记录,因此可以查明“哪个应用产生了这笔支出”。
为什么自行运行网关
托管路由器的结构相同,只是每个请求中间都有第三方进程参与。自行运行后,服务提供商密钥和提示词文本都保存在您控制的服务器上。代价也很明确:您需要运维这个所有应用都依赖的组件。本指南的最后一节将介绍这项代价,因为大多数文章都会忽略这一部分。
所需条件
- 一台运行 Ubuntu 24.04 的 VPS(虚拟专用服务器),并已安装 Docker 和 Compose 插件。
- 一个指向该 VPS 的域名。如果服务器外部的设备将通过 TLS(传输层安全)访问网关,则需要此域名。
- 至少一个提供商 API 密钥。
网关不执行推理。它只转发请求并流式返回响应,因此 CPU 负载取决于请求量,而不是模型大小。一台配备 1 vCPU 的服务器可以稳定运行多个内部应用。增长较快的是数据库,因为网关会为每个请求写入一条支出记录。
先编写 config.yaml
配置文件决定客户端可以请求哪些模型。需要关注4个顶级部分: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 会在发生可重试错误时重试同一个部署。只有这些重试全部用尽后,才会触发备用部署。allowed_fails: 3 配合 cooldown_time: 30 使用时,某个部署连续失败3次后,会将其移出轮询30秒。因此,返回500错误的供应商不会在每个请求中都被重复尝试。
fallbacks 和 context_window_fallbacks 的触发条件不同,后者很有用,但经常被忽略。
fallbacks在主调用失败时触发。context_window_fallbacks在供应商因请求长度超过该模型的上下文窗口而拒绝请求时触发。这样,过大的提示词会发送到有足够容量的模型,而不是向调用方返回错误。
此外还有 content_policy_fallbacks,用于处理供应商因内容策略原因拒绝请求的情况。只有在您有合适的目标可以接收这些请求时,才应设置它。
在 VPS 上使用 Docker Compose 部署 LiteLLM
创建一个目录,并在其中放置 3 个文件: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 月的当前版本。部署时请查看项目的 releases 页面,并固定当时的当前版本。每个版本都会发布签名,因此可以在信任镜像前先进行验证:
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 处 Admin UI 的密码。任何应用都不应持有它。
LITELLM_SALT_KEY 用于加密存储在数据库中的提供商凭据。设置一次后保持不变。如果之后修改它,已存储的凭据将无法解密。因此,网关会正常启动,但随后对这些提供商的每次调用都会因身份验证失败而失败。
STORE_MODEL_IN_DB=True 允许您直接在 Admin UI 中添加和编辑模型,无需修改 config.yaml。这很方便,但也会让配置的唯一真实来源分成两处。确定哪一处是权威来源,并将决定记录在 config 旁边。
将密钥排除在配置文件之外的理由,同样适用于不将密钥交给提供给代理使用的工具。避免将提供商机密交给 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,因为对于无法查询任何虚拟密钥的网关,liveness 仍会保持正常。
需要身份验证的检查会与提供商通信:
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会返回最近一次结果。因此,轮询该端点不会每次都向提供商发送测试请求。
虚拟密钥和每个密钥的预算
每个应用都有自己的密钥,该密钥基于主密钥生成。
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
}'响应包含一个以 sk- 开头的 key 字段。该字符串就是应用获得的内容,也是应用能够获得的唯一内容。
models用于列出该密钥可以请求的内容。上面的密钥只能请求bulk。max_budget: 5配合budget_duration: "30d"使用时,滚动 30 天的预算为 5 美元;预算用尽后,该密钥将停止工作。rpm_limit和tpm_limit分别限制该密钥自身每分钟的请求数和每分钟的 token 数。key_alias是您在六周后查看支出日志时用来识别该密钥的名称。务必设置。
预算用尽后,请求会返回 HTTP 401,响应正文格式如下:
ExceededBudget: Current spend for token: 7.2e-05; Max Budget for Token: 2e-07正是这个状态码导致问题难以判断。客户端库会将 401 报告为身份验证问题,因此查看堆栈跟踪的开发者会开始检查密钥是否有效。应将响应正文与状态码一起记录,否则每次预算用尽都会看起来像凭据失效。
通过同一个管理 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 agent 成本控制 的基础。
将批量任务发送到低成本模型
将客户端指向网关。配置基础 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,而不是直接返回错误。分类任务或摘要积压等批量工作默认使用低成本模型,只有复杂请求会产生更高费用。
对于使用工具的代理,这也是网关发挥作用的场景。位于同一 VPS 上的 MCP(模型上下文协议)服务器和驱动它的代理都可以指向同一个端点。这样无需重新部署任一组件,就可以更换它们背后的模型。
如何判断是否发生了回退?
这是最容易造成成本损失的故障模式,因为表面上看不到任何异常。成功回退时会返回 HTTP 200,以及普通的响应正文。您的低价模型可能停机一天,而每次调用都悄悄由高价模型处理;直到收到账单,您才会看到第一个证据。
证据确实存在于响应头中。请获取这些响应头:
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 时触发告警。这个数值决定了路由策略是否正常工作,也能及时发现路由策略是否已悄悄变成“始终使用高价模型”。
完整方案是分布式追踪,需要单独配置:自行托管 Langfuse 以追踪代理调用。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,Admin UI 会从 /ui 读取这张表。该表会随流量增长,因此在磁盘较小时应监控其大小。
将网关置于反向代理之后
外部任何流量都不应访问 4000 端口。在 nginx 或 Caddy 中终止 TLS,并将请求转发到回环地址。
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 很重要,因为流式生成结果由一系列服务器发送事件组成。启用 nginx 缓冲后,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 是最简便的方案。如果服务器已经运行多个容器,在多个 Compose 应用前使用 Traefik 可以集中处理路由和证书。
网关现在是单点故障
请如实评估当前架构。现在,您管理的每个应用都依赖一台 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 header 测量自身开销。
在前面加上 nginx 后,流式传输为什么停止工作?
因为 nginx 默认会缓冲上游响应,而流式完成响应由一系列 server-sent events 组成。启用 proxy_buffering 后,nginx 会收集这些分块,直到响应完成后才释放它们,因此客户端会一直等待,最后一次性收到完整答案。请在 location block 中设置 proxy_buffering off;。同时在同一 block 中增大 proxy_read_timeout,否则长时间生成会超过 nginx 默认的 60 second 超时,客户端会收到 504。
virtual key 用完预算后会发生什么?
调用会失败,并返回 HTTP 401,响应体格式为 ExceededBudget: Current spend for token: 7.2e-05; Max Budget for Token: 2e-07。401 是容易误判的地方:客户端库会将其报告为身份验证失败,因此用户会开始检查 key 是否有效,而不是读取错误消息。请将响应体与状态码一起记录。使用主 key 通过 /key/info?key=sk-... 确认该 key 的实际位置;如果预算设置过低,则通过 /key/update 提高上限。
网关是否既能路由到本地模型,也能路由到托管模型?
可以,这会成为 model_list 中的另一项。请使用带有 api_base 的 ollama_chat/ 前缀,例如将 model: ollama_chat/llama3.1 与 api_base: http://ollama:11434 一起使用。在容器内部,localhost 指向该容器自身,因此应使用 Compose 服务名称或主机在 Docker 网络中的地址,不能使用 127.0.0.1。启动本地模型是另一项工作,请参阅 在 VPS 上使用 Ollama 自托管 LLM。