一句话主线:LiteLLM 用薄 Proxy 做治理、厚 SDK 做调用;一条请求先过鉴权与限流,再在 Router 里按「同组重试 / 跨组 fallback」选路,最后异步记账——隔离是否成立,看 Team / Virtual Key / SpendLogs。
多个团队共用几把 Provider Key。预算拆不开,配额互相踩,审计只能翻散落日志,fallback 也只能各写一套。SDK 往往能调通;缺的是共享时的边界。
正文按 LiteLLM 写:宽模型面、Virtual Key / Team 预算、OpenAI 兼容迁入,这三项现在压过「网关自身微秒级开销」。和 Bifrost 的对照、以及什么约束变了该重测,放在 §1.1。
LiteLLM 把共享边界做成开源网关。分工可以这样记:
- 控制面(control plane,主要在
litellm/proxy/):Virtual Key 鉴权、预算、RPM/TPM、Team/多租户、Router 策略配置、把 cost 写进响应头、异步 Spend/SpendLogs、Proxy hooks。 - 数据面(data plane,主要在
litellm/SDK):transform_request/transform_response、HTTP 调用 Provider、流式、completion_cost计价、LLM 响应缓存。
形态是 薄 Proxy + 厚 SDK:Gateway 叠在 SDK 之上;Proxy 不自己直连上游。第 3 节把两边怎么交接写清楚。
开源顶层租户边界是 Team + Virtual Key。Organization、完整 SSO、Audit Logs 等企业能力只在边界点明。
读完后比较有用的几件事:分清控制面/数据面文件落点;画出同步路径与异步 Spend,并说清 retry / fallback;指出隔离对应哪些表字段;需要和 Bifrost 对标时看 §1.1。源码与表结构以当前 main 上的 ARCHITECTURE.md、Life of a Request、schema.prisma、Fallbacks 为准;组件化 Helm 示例版本见第 5 节。
1. 何时上 Gateway
单团队、单应用、低量、单一 Provider、不做内部结算时,直接 SDK 更轻。Gateway 会多出 Postgres、Redis 和运维面。
共享一出现就不一样:多团队共用预算、需要 chargeback、多 Provider 故障转移、统一观测——其中任意两条成立,Gateway 通常就值得上。薄 Proxy 的实际收益是解耦:加模型改 transformation.py,改策略动 Proxy hooks。
| 直连 Provider | LiteLLM Gateway | |
|---|---|---|
| 鉴权 | 真实 Key 散落 | Virtual Key;上游 Key 留网关 |
| 预算 | 事后对账 | Key / User / Team 层级 |
| 限流 | 各服务自建 | Global / Key / User / Team RPM·TPM |
| 失败转移 | 业务自拼 | Router:同组 retry,跨组 fallback |
| 成本 | 难以及人 | 响应头同步 cost;Spend 异步落库 |
AWS Multi-Provider Generative AI Gateway 以 LiteLLM 为统一入口,可作外部参照。开源用 Team + Virtual Key 做多团队隔离即可起步;Org / 完整 SSO / Audit Logs 按合规再评估。
1.1 同类网关:我们看过什么、怎么 trade-off
「上 Gateway」定了之后,下一问是上哪一家。先和 Bifrost 对齐坐标——不是功能越多越好,是哪几项约束现在最紧。
| 项目 | 语言 | 高 RPS 下网关开销(公开数字) | 模型面 | 治理(虚拟 Key / 预算) | 部署体感 | 更贴的场景 |
|---|---|---|---|---|---|---|
| LiteLLM | Python | 中等(热路径还要 Redis/Postgres;官方有 1K RPS 量级压测,见 §5.2) | 极宽(100+ Provider) | 强:Virtual Key、Team、RPM/TPM、Spend | Docker/K8s + 常配 Redis/PG | 快速收口多模型、实验面宽、治理先落地 |
| Bifrost | Go | 厂商压测称 ~11µs overhead @ 5k RPS(Maxim 基准;方法与硬件见原文) | 宽(厂商称 20+ Provider / 大量模型) | 强+:分层预算、RBAC、SSO、Vault 等 | 依赖更轻、Helm/一键启动 | 生产高并发、网关自身延迟预算极紧 |
数字边界先钉死:Bifrost 的「11µs / 5k RPS / 相对 LiteLLM 几十倍 P99」多来自 Maxim(Bifrost 方)公开基准,硬件与 mock upstream 设定见其 benchmarks / DEV 复盘。第三方套件(例如 Ferro Labs 的对比)方法不同,结论也会不同——不要把厂商 headline 直接当成你们集群上的吞吐。LiteLLM 侧自己的 1K RPS 数字同样是受控实验(§5.2),假上游、非真实计费 Provider。
和 LiteLLM 对位最紧的是 Bifrost,能力上可以这样记:
| 维度 | LiteLLM | Bifrost(公开材料) | 对我们意味着什么 |
|---|---|---|---|
| Key 收口 / 虚拟 Key | Virtual Key + 预算 / 限额 / Team | 虚拟 Key + 分层预算 + RBAC / SSO / Vault | 治理两边都够用;企业 SSO/Vault 绑得更深时 Bifrost 更贴 |
| 重试 / 故障转移 | Router:同组 retry、跨组 fallback、cooldown(本文 §3.4) | adaptive failover + 多种路由 | LiteLLM 策略面已够;「更自适应」要实测,不是词面赢 |
| OpenAI 兼容 | 强:业务多半改 base_url + Key |
强:统一 OpenAI 形态 | 迁入成本接近平手 |
| 高并发 | Python 代理 + Redis/PG;组件化收故障影响面(§5.3) | Go;厂商强调微秒级开销与高 RPS | 延迟预算极紧、网关自身是瓶颈时,Bifrost 更值得立项验证 |
| 缓存 | Redis / 响应缓存等,生产常外挂 | 厂商强调内置 semantic caching | 已有 Redis 时 LiteLLM 不吃亏;想少依赖再比 |
| Spend / 观测 | SpendLogs、Prometheus、可接 Langfuse(§5.1) | 用法追踪 + Prometheus + tracing / audit | LiteLLM 与现有观测栈好接;Bifrost 更「网关内建」 |
| 部署 | 生产常 Redis + Postgres;Helm 组件化 | 依赖更少、启动更轻(厂商叙事) | 我们已在 K8s 上跑 relay 时,迁移成本要单独算 |
| 生态 | 社区大、Provider 面极宽、issue 也多 | 较新、性能叙事强、生态仍在长 | 冷门模型 / 长尾 Provider 往往 LiteLLM 先有适配 |
我们当前落在 LiteLLM 的判断(带失败条件):
- 优先要 模型面宽 + 迁入快(OpenAI SDK 改 endpoint),以及 OSS 侧就能用的 Virtual Key / Team / Spend——选 LiteLLM。
- 热路径靠 DualCache + 异步 Spend + 组件化 / HPA·KEDA(§3、§5)把 Python 代理的风险收在已知边界内;接受「网关不是微秒级」这件事。
- 何时重开 trade-off:网关自身 P99 /
in_flight已成主矛盾,或合规强制 SSO/Vault/审计一体,或目标 RPS 明确压在厂商基准那一档——立项对 Bifrost 做同机、同上游、同鉴权开启的复现压测,再决定是否迁。
本文后面不再平行展开别家源码,只把 LiteLLM 的请求链路、表结构和部署约束写清楚——选型账在上面这节;机制账从下一节开始。
2. 能力表面
客户端多半继续用 OpenAI SDK,改 base_url + 虚拟 Key。高频端点包括 /chat/completions、/embeddings、/images、/batches、/files、/responses、/v1/messages 等,完整列表见 Supported Endpoints。
Transform:统一协议 + 成本 + 观测,走翻译层。
Passthrough:不做协议翻译,仍走鉴权与日志;能定价才算 cost。
扩展性主要落在各 Provider 的 transformation.py。默认走 Transform;只有原生特性或缺省 SDK 迁入成本明显更高时,再考虑 Passthrough。
3. 一条请求怎么走完
用虚拟 Key 打 POST /v1/chat/completions。响应头里常立刻有 x-litellm-response-cost;此时 LiteLLM_SpendLogs 可能还没有这一行——要等后台约 60 秒的 update_spend。
下面先分清控制面 / 数据面各自管什么,再沿源码路径走完同步调用与异步记账。实现细节对照官方 ARCHITECTURE.md。
3.0 控制面与数据面分别负责什么
控制面叠在 SDK 之上;Proxy 不直连上游:
flowchart LR Client["Client"] --> Gateway["Gateway<br/>proxy/ 控制面"] --> SDK["SDK<br/>litellm/ 数据面"] --> Provider["Provider API"]
Gateway 叠加 authentication、rate limiting、budgets、routing;SDK 负责 provider 调用、request/response transformation、streaming。
落到职责表:
控制面(多在 proxy/) |
数据面(多在 litellm/ SDK) |
|
|---|---|---|
| 身份 | Virtual Key 鉴权、user_api_key_auth、models 交集 |
不关心谁在调,只认进来的请求参数 |
| 配额 | 预算、RPM/TPM、Team/Key/User 限制(hooks + Redis) | 无租户预算语义 |
| 选路策略 | Router 的 model group、fallbacks、cooldown 配置来自 proxy/config | router.py 执行 LB / retry / fallback;最终 completion |
| 协议 | 暴露 OpenAI 兼容与各原生/passthrough 入口 | transform_request / transform_response、HTTP、流式 |
| 成本 | 把 cost 写进响应头;异步 Spend / SpendLogs | completion_cost() 算单价 × token |
| 扩展点 | Proxy hooks(PROXY_HOOKS)、管理 API、Team/Key CRUD |
新 Provider 的 transformation.py、CustomLLM |
| 状态存储 | Postgres(Key/Team/Spend)、Redis(限流/缓存/队列) | Router DualCache、可选 LLM response cache |
还有第二层「部署向」的控制面/数据面(gateway 推理进程 vs backend 管理/分析),见第 5 节。下面先说架构层划分。
控制面内先鉴权、限流,再交给 Router;真正出站在数据面 Handler 之后:
sequenceDiagram
participant Client
participant Proxy as proxy_server.py
participant Auth as user_api_key_auth
participant Cache as DualCache / Redis
participant PG as Postgres
participant Hooks as budget / rate hooks
participant Router as router.py
participant Main as main.py acompletion
Client->>Proxy: POST /v1/chat/completions
Proxy->>Auth: user_api_key_auth()
Auth->>Cache: 查 Key 缓存
alt cache miss
Cache->>PG: 读 LiteLLM_VerificationToken
PG-->>Cache: Key / Team / 预算
end
Cache-->>Auth: 身份卡片
Auth-->>Proxy: 通过
Proxy->>Hooks: max_budget / RPM·TPM
Hooks->>Cache: Redis 计数
Hooks-->>Proxy: 允许或拒绝
Proxy->>Router: route_llm_request
Note over Router,Cache: DualCache 跟踪 cooldown / TPM
Router->>Main: 进入 SDK 数据面
Main-->>Client: 响应(出站细节见下一图)
整条链路含同步 cost 头与异步 Spend:
sequenceDiagram participant Client participant Proxy as proxy_server.py participant Auth as user_api_key_auth.py participant Hooks as budget + rate limit participant Router as route / router.py participant Main as acompletion main.py participant Handler as BaseLLMHTTPHandler participant Provider as HTTP Provider participant Cost as completion_cost participant Async as DBSpendUpdateWriter participant Redis as Redis 队列 participant PG as Postgres Client->>Proxy: Bearer sk-... Proxy->>Auth: DualCache / Redis<br/>miss 查 Postgres Auth-->>Proxy: 身份卡片 Proxy->>Hooks: Redis 计数 Hooks-->>Proxy: 通过 Proxy->>Router: 选 deployment<br/>retry / fallback Router->>Main: litellm.acompletion Main->>Handler: completion() Handler->>Handler: transform_request Handler->>Provider: HTTP Provider-->>Handler: 原生响应 Handler->>Handler: transform_response Handler-->>Main: ModelResponse Main->>Cost: update_response_metadata Cost-->>Main: _hidden_params.response_cost Main-->>Proxy: ModelResponse + cost Proxy->>Proxy: 写 x-litellm-response-cost 等头 Proxy-->>Client: 同步返回 Proxy->>Async: async_success_handler Async->>Redis: Spend 入队 Note over Redis,PG: 约 60s update_spend Redis->>PG: 批量写 SpendLogs / 滚动 spend
3.1 入口:proxy_server.py 与端点目录
litellm/proxy/proxy_server.py 是主 API。/v1/chat/completions、/embeddings 等在此挂依赖:先 user_api_key_auth,再进入 litellm_pre_call_utils.py、route_llm_request.py、common_request_processing.py 一带。
其它常见入口(鉴权仍过,转化路径不同):
| 入口 | 目录 |
|---|---|
/v1/messages |
proxy/anthropic_endpoints/ |
/vertex-ai/* |
proxy/vertex_ai_endpoints/ |
/gemini/* |
proxy/google_endpoints/ |
/v1/images/* |
proxy/image_endpoints/ |
/v1/batches / /v1/files / /v1/fine_tuning |
对应 batches_ / openai_files_ / fine_tuning_endpoints/ |
/v1/rerank / /v1/responses / /v1/vector_stores |
各自 endpoints 目录 |
| 通用 passthrough | proxy/pass_through_endpoints/ |
关键 proxy 文件:proxy_server.py、proxy/auth/、proxy/hooks/、router.py、router_strategy/。
3.2 鉴权:user_api_key_auth.py + DualCache
user_api_key_auth.py 把 Bearer sk-... 收成后续都认的身份卡片:
- 经
InternalUsageCache(proxy/utils.py)读 DualCache:先进程内存,再 Redis(caching/dual_cache.py、caching/redis_cache.py)。 - miss 查 Postgres
LiteLLM_VerificationToken(Prisma)。token列是哈希。 auth_checks.py做预算与模型权限:有team_id时key.models ∩ team.models(Key Auth)。
失败文案带 key / team / user / org 前缀。Access group、wildcard 在检查时展开。
哨兵:空/* 不限制;all-team-models 继承 team(无 team 可能滑成不限制);no-default-models 逼走 team。master key 不进 VerificationToken、绕过 models——适合运维,不适合业务。
全局 spend 读路径上,并发 miss 曾 stampede;EventDrivenCacheCoordinator 做 in-flight 合并。
3.3 Pre-call hooks
控制面扩展点。常见 Proxy Hooks:
| Hook | 文件 | 作用 |
|---|---|---|
max_budget_limiter |
max_budget_limiter.py |
预算封顶 |
parallel_request_limiter |
parallel_request_limiter_v3.py |
Key/User/Team 等限流 |
cache_control_check |
cache_control_check.py |
缓存相关校验 |
responses_id_security |
responses_id_security.py |
Responses ID 校验 |
litellm_skills |
skills_injection.py |
Skills 注入 |
注册在 proxy/hooks/__init__.py 的 PROXY_HOOKS;新 hook 实现 CustomLogger 再注册。
parallel_request_limiter_v3 用 Redis + Lua 做窗口计数。检查层级含 Global / Key / User / Team。无 Redis 时多实例限流会漂。Guardrail 也可挂 pre-call。Team 级 RPM 隔离与后面 deployment cooldown 是不同层。
3.4 Router:负载均衡 + retry / fallback / cooldown
请求进入 router.py(负载均衡、fallbacks;策略在 router_strategy/,如 simple_shuffle.py、lowest_latency.py)。统一端点都走它。
Router 内部顺序(retry 留在组内,fallback 换组):
flowchart LR Call["统一调用"] --> FB["function_with_fallbacks<br/>跨组"] --> RT["function_with_retries<br/>同组"] --> Comp["completion / acompletion"]
model group:同一 model_name 下的多 deployment,可负载均衡。
Retry:留在组内
num_retries:Router 参数;也可按 deployment /model_group_retry_policy覆盖。- 同组换可用 deployment;
request_timeout超时进入失败链。 - Retry 留在当前 model group,不会因为重试跳到 fallback 链上的另一组。
Fallback:离开当前组
fallbacks=[{"gpt-3.5-turbo": ["gpt-4"]}],按序尝试(Fallbacks)。- 三类:
fallbacks(一般错误)、context_window_fallbacks、content_policy_fallbacks;另有default_fallbacks。 max_fallbacks限制跨组跳转次数。请求体可带fallbacks;disable_fallbacks可关。
Cooldown:摘单个 deployment
allowed_fails/cooldown_time(router 级或model_info级)。- 429、高失败率、部分非可重试错误会冷却;状态在 DualCache/Redis。
- 作用对象是 deployment,不是整组。
Weighted failover
enable_weighted_failover + simple-shuffle:可重试失败后先在同组按 weight/rpm/tpm 排除已失败部署再选;组内耗尽再跨组。文档写明主要 async 路径。
配置示例
model_list:
- model_name: gpt-4o
litellm_params:
model: azure/gpt-4o
api_base: https://eastus.example.azure.com
api_key: os.environ/AZURE_EAST_KEY
- model_name: gpt-4o
litellm_params:
model: azure/gpt-4o
api_base: https://swedencentral.example.azure.com
api_key: os.environ/AZURE_SWEDEN_KEY
- model_name: gpt-4o-fallback
litellm_params:
model: openai/gpt-4o
api_key: os.environ/OPENAI_API_KEY
router_settings:
num_retries: 2
request_timeout: 10
allowed_fails: 3
cooldown_time: 30
fallbacks: [{ "gpt-4o": ["gpt-4o-fallback"] }]
fallbacks 的键值是 model_name,不是某个 region 的 deployment id。同组换机房靠 retry;换到 gpt-4o-fallback 才是跨组 fallback。
SpendLogs.metadata 可含 attempted_fallbacks、original_model_group;响应头有 x-litellm-attempted-fallbacks、x-litellm-model-id。
合在一起看:retry 同组,fallback 换组,cooldown 摘单个 deployment。可靠性策略集中在 Router。生产里我更倾向同组多 deployment + 有限 retry;跨 Provider 的 fallback 单独看成本与合规。上下文/内容策略用专用 fallback;fallback 到指定 model_info.id 时可跳过 cooldown 检查——逃生口本身也可能承压。
Router 自己也持 DualCache:TPM/RPM、cooldown、client 缓存。
3.5 数据面:SDK 请求流
Router 最终调 litellm.acompletion()(main.py)。SDK 侧路径(主链横排;Langfuse 等异步回调挂在 ModelResponse 之后,不挡主路径):
flowchart LR Main["acompletion"] --> Prov["get_llm_provider"] --> Handler["BaseLLMHTTPHandler"] --> TR["transform_request"] --> HTTP["HTTP"] --> API["Provider"] --> TResp["transform_response"] --> Stream["streaming?"] --> MR["ModelResponse"]
基类 BaseConfig 在 llms/base_llm/chat/transformation.py。Handler 负责调用 transform_*,一般不必改 Handler 本身。
常见翻译文件:
| 入口 / 目标 | 文件 |
|---|---|
| chat → Anthropic | llms/anthropic/chat/transformation.py |
| chat → Bedrock Converse | llms/bedrock/chat/converse_transformation.py |
| chat → Bedrock Invoke | llms/bedrock/chat/invoke_transformations/... |
| chat → Gemini / Vertex | llms/gemini/...、llms/vertex_ai/gemini/... |
| chat → OpenAI | llms/openai/chat/gpt_transformation.py |
/v1/messages passthrough |
llms/anthropic/experimental_pass_through/... 等 |
| Proxy passthrough | proxy/pass_through_endpoints/llm_provider_handlers/ |
调试「只在一条路径通」时,对比对应 transformation(官方用 prompt caching:Converse vs Invoke 对 cache_control 的处理)。Passthrough 跳过完整翻译,仍过鉴权与日志。
SDK 另有 LLMCachingHandler(响应缓存),与 Proxy 的 Key/限流 DualCache 不是同一块:一块管「要不要再打上游」,一块管「身份与配额热数据」。
3.6 成本归因
同步成本链:
acompletion返回到 utils 包装update_response_metadata()(llm_response_utils/response_metadata.py)logging_obj._response_cost_calculator()(litellm_logging.py)litellm.completion_cost()(cost_calculator.py)- 写入
response._hidden_params["response_cost"] proxy/common_request_processing.py抽到x-litellm-response-cost(及 call-id、model-id、model-name、cache-key 等)async_success_handler()→_ProxyDBLogger.async_log_success_event()DBSpendUpdateWriter.update_database()入队 Redis- 后台
update_spend刷 Postgres
前 6 步在返回客户端前完成;7–9 是侧车。
3.7 异步侧车与基础设施
DBSpendUpdateWriter(db_spend_update_writer.py):增量进内存列表或 Redis(RedisUpdateBuffer);再批量 _insert_spend_log_to_db、_update_key_db / _update_user_db / _update_team_db 等。是否走 Redis 由 use_redis_transaction_buffer 一类配置决定。
基础设施角色:
| 组件 | 作用 | 关键类型 |
|---|---|---|
| Redis | 限流、Key 缓存、TPM/RPM、cooldown、响应缓存、Spend 队列 | RedisCache、DualCache |
| Postgres | Key、Team、User、SpendLogs | Prisma |
| InternalUsageCache | Proxy 侧限流 + Key 缓存 | proxy/utils.py |
| Router.cache | 部署用量、cooldown | DualCache |
| LLMCachingHandler | SDK 响应缓存 | caching/ |
| DBSpendUpdateWriter | 批量写 spend | 上文 |
_ProxyDBLogger |
成本回调进库 | proxy_track_cost_callback.py |
后台任务在 Proxy 启动时由 ProxyStartupEvent.initialize_scheduled_background_jobs() 挂上 APScheduler,例如:
| Job | 间隔 | 作用 |
|---|---|---|
update_spend |
~60s | 刷 SpendLogs / 累计 spend |
reset_budget |
10–12min | 预算重置 |
add_deployment |
~10s | 从 DB 同步 deployment |
cleanup_old_spend_logs |
cron | 清理旧明细 |
check_batch_cost / check_responses_cost |
~30min | 批处理/Responses 补算 |
process_rotations |
~1h | Key 轮换 |
Spend 不在返回前写 Postgres:先入 Redis 队列,再由后台批量落库。代价是账单最多大约晚一分钟;换来的是热路径几乎不做 DB 写。官方 FAQ 也写明:DB 事务不绑请求生命周期。多实例下 scheduler 谁跑、会不会重复刷,要单独想清楚。
| 时刻 | 客户端 | Redis / 热路径 | Postgres |
|---|---|---|---|
| T+0 成功 | cost 头 | RPM+1;spend 入队 | SpendLogs 可能仍无该行 |
| T+0 超限流 / 权限拒 | 429 / 4xx | 触顶或拒绝 | 通常无成功明细 |
| 同组 retry 成功 | cost 头 | 可能换过 deployment | 成功才滚动 |
| 跨组 fallback 成功 | 模型可能已变 | — | metadata 可区分原组 |
| T+~60s | — | 队列消费 | SpendLogs + 各层 spend + Daily |
排障:响应头 → Redis 窗口/冷却 → SpendLogs / Daily。
3.8 Adapter
- 新建
llms/your_provider/chat/transformation.py,继承BaseConfig。 - 实现
transform_request/transform_response。 - 注册;
tests/llm_translation/单测转换,不必打真 API。 - 简单 OpenAI 兼容可改
openai_like/providers.json;完全自定义用CustomLLM。
class YourProviderConfig(BaseConfig):
def transform_request(self, model, messages, optional_params, litellm_params, headers):
return {"messages": ..., **optional_params}
def transform_response(self, model, raw_response, model_response, logging_obj, ...):
return model_response
这段要证明的是:扩展点在翻译层——优先改 transformation,而不是改 Handler。单测可以直接实例化 Config、喂假 messages,断言输出里出现预期字段(例如 Bedrock Converse 的 cachePoint),不必打真 API。加特性时,建议在 OpenAI / Anthropic / Bedrock Invoke·Converse / Vertex / Gemini 等路径各跑一轮 translation 测试,避免只修了一条入口。
数据访问层在 litellm/models/ + repositories/,供 gateway 与 SDK 共用,避免到处 import proxy 内部;第 4 节结合 schema 展开。
4. DB schema:隔离落到哪些字段
完整定义见 schema.prisma。开源心智:
Organization(企业)
└── Team ← OSS 顶层
├── User / TeamMembership
└── VerificationToken(Virtual Key)
持久化实体在 litellm/models/(Pydantic),访问层在 litellm/repositories/(BaseRepository + 各实体 Repository),gateway 与 SDK 都能用,而不必 import proxy/ 内部。约定包括:
- JSON 列:写入
dumps、读出loads - 删除 Team/Token:同一事务里先写入
LiteLLM_Deleted*,再删原行(Archive-then-Delete) - 字段名与列名不一致时(如
org_id↔organization_id)由 repository 双向翻译 - 数组成员追加用 Prisma
push,避免读写改竞态
这和请求热路径的 DualCache 是两条线:热路径尽量不写库;配置与归属最终仍落在这些表上。
4.1 LiteLLM_TeamTable
主键 team_id。和隔离直接相关:
| 字段 | 含义 |
|---|---|
team_alias |
展示名 |
organization_id |
所属 Org;OSS 可空 |
admins / members / members_with_roles |
成员与角色 |
max_budget / soft_budget / spend |
硬/软预算与累计花费 |
models |
Team 允许的模型 |
rpm_limit / tpm_limit / max_parallel_requests |
速率与并发 |
budget_duration / budget_reset_at |
预算周期 |
blocked |
整队阻断 |
model_spend / model_max_budget |
按模型花费与上限(Json) |
router_settings |
Team 级路由覆盖(可含 fallbacks 等) |
删除走 LiteLLM_DeletedTeamTable,保留历史 spend 与 deleted_at / deleted_by。
4.2 LiteLLM_UserTable 与 TeamMembership
User:user_id,teams[],max_budget / spend,models,用户级 rpm/tpm,user_role,organization_id,sso_user_id。
LiteLLM_TeamMembership:复合主键 (user_id, team_id),含 spend、total_spend、budget_id——同一用户在 A 队和 B 队可以有不同额度。
4.3 Virtual Key:LiteLLM_VerificationToken
请求鉴权读得最多的表。token 是哈希主键。
| 字段 | 含义 |
|---|---|
token |
Key 哈希;SpendLogs.api_key 与之对应 |
key_alias / key_name |
别名 |
user_id / team_id / project_id / organization_id |
归属 |
models |
与 team.models 取交集 |
spend / max_budget / soft_budget_cooldown |
花费与预算状态 |
rpm_limit / tpm_limit / max_parallel_requests |
Key 级限流 |
budget_id |
挂共享 BudgetTable |
blocked / expires |
禁用与过期 |
router_settings |
Key 级路由覆盖 |
allowed_routes / metadata |
路由与扩展元数据 |
rotation_* / last_active |
轮换与最近使用 |
索引含 (user_id, team_id)、team_id、(budget_reset_at, expires)。删除归档到 LiteLLM_DeletedVerificationToken;轮换宽限期有 DeprecatedVerificationToken。
生产共享我倾向 Team 级 Service Account Key(挂 team_id);个人调试再绑 user_id。
4.4 BudgetTable、SpendLogs、Daily
BudgetTable:max_budget、soft_budget、tpm/rpm、model_max_budget、budget_duration、allowed_models 等,可被多方引用。
SpendLogs 是异步批量写入的收据:
| 字段 | 含义 |
|---|---|
request_id |
主键 |
api_key |
哈希虚拟 Key |
spend / tokens |
花费与用量 |
startTime / endTime / completionStartTime |
时序 |
model / model_group / custom_llm_provider / api_base |
模型与上游 |
team_id / user / end_user / organization_id |
归属 |
metadata |
含 attempted_fallbacks、original_model_group 等 |
messages / response |
可选;体积与隐私需克制 |
Daily 表(DailyUserSpend / DailyTeamSpend / …)按日预聚合,UI 读它。另有 DailyGatewayRequests 记边缘成功/失败。错误路径用 ErrorLogs。
4.5 配置态 vs 热态
| 种类 | 在哪 | 例子 |
|---|---|---|
| 配置 / 累计 | Postgres | models、max_budget、rpm 配置值、滚动后的 spend |
| 窗口 / 缓冲 | Redis | 当前 RPM 计数、Key 缓存、Spend 队列、deployment cooldown |
改表上的 rpm_limit 不等于瞬时窗口已变。看「这分钟超没超」查 Redis;看「这月花多少」查表或 Daily。
4.6 一次成功请求如何写库
- 鉴权读 VerificationToken(多半缓存)。
- 限流读 Redis,对照表上的限额配置。
- Router 可能同组 retry / 跨组 fallback;cooldown 写 Redis。
- cost 进响应头;Spend 入队。
update_spend写 SpendLogs,滚动 Key/User/Team.spend;Daily 随后聚合。
预算从内向外挡;模型权限取交集。两业务拆两个 Team、两把 Key;塞同一 Team,限流窗口会重新黏住。
逻辑示例:
Team batch rpm=200 max_budget=500 models=[gpt-4o-mini] key=sk-batch-...
Team support rpm=60 max_budget=200 models=[gpt-4o-mini] key=sk-cs-...
批处理打满 200 RPM 时,只应卡住 batch;support 仍通。月末按 SpendLogs.team_id 或 DailyTeamSpend 结算。
schema 里还有 Project、MCP、Guardrail、Agent 等表,而且在持续增加。开源多团队场景,我倾向先把 Team + Virtual Key + SpendLogs 用熟,再评估 Org。SpendLogs 不是强一致账本;master key 适合运维、不适合业务;messages / response 落库前要想清楚隐私与体积。
5. 可观测、高并发、组件化与高可用
第 3 节的热路径少写库,决定了「账单稍后到」;这一节回答:生产里怎么看、怎么撑量,以及什么时候该从单 container 拆成 gateway / backend / ui。
5.1 可观测怎么分层
一次请求的可观测不是一层,而是四层,时间语义不同。
同步:响应头
请求返回时,common_request_processing.py 已把本次结果写进 HTTP 头,例如:
x-litellm-response-cost:本次费用x-litellm-model-id/x-litellm-model-name/x-litellm-model-group:最终打到哪条部署x-litellm-call-id、x-litellm-attempted-fallbacks:调用与回退痕迹
排障「这次花了多少、落到哪」优先看头,不必等库。
异步:SpendLogs 与 Daily 表
成功后 _ProxyDBLogger → DBSpendUpdateWriter 入队,约 60 秒 update_spend 写入 LiteLLM_SpendLogs,并滚动 Key/User/Team 的 spend;Daily 表供 Usage 看板。chargeback 读这一层,但不要把「刚打完立刻 SELECT」当成强一致。
回调:Langfuse 等
litellm_settings.callbacks 可接 Langfuse、S3、OTEL 等,把 prompt/completion 轨迹送到外部系统。Enterprise 另有 Team-Based Logging(按 team 路由到不同 Langfuse project、或按 team 关闭日志),见 Enterprise 对照表。
指标:Prometheus /metrics
给 K8s 抓。官方 Prometheus 文档 写明:LiteLLM 暴露 /metrics。启用:
litellm_settings:
callbacks:
- prometheus
在 Kubernetes 里,这条路径通常由 Prometheus Operator 的 ServiceMonitor / PodMonitor,或 scrape annotation 去拉各 Pod。和后面 §5.4 的 HPA / KEDA 同一套信号:先能 scrape,才能按 in_flight / RPS 扩缩。
抓取时注意几件 K8s 侧的事:
- 鉴权:自 v1.85.0 起
/metrics默认要 LiteLLM API Key。scrape 配 Bearer(Secret 挂到 Prometheus / ServiceMonitor);若集群内网可接受匿名,文档提供require_auth_for_metrics_endpoint: false。 - 多 worker:单 Pod 内多 uvicorn worker 时设
PROMETHEUS_MULTIPROC_DIR(可挂 emptyDir),否则进程间指标对不齐,KEDA 看到的值会偏。 - 目标选择:组件化后优先 scrape gateway 的
/metrics做推理面扩缩;backend 另盯管理面延迟 / DB 压力,不要混成一个 job 却共用一套阈值。
Enterprise 对照表 将 Prometheus metrics 列在 OSS 侧;Enterprise 在可观测上主要加 per-key/per-team 日志路由、管理操作日志、合规导出等。OSS 开 callbacks: ["prometheus"] 即可抓 spend、token、延迟、deployment 成功/失败、cooldown、fallback;个别 Managed Batch 相关计数仅企业版发出。
常用指标(摘自 Prometheus 文档),也是后面 KEDA 的候选:
| 类别 | 例子 | K8s 上怎么用 |
|---|---|---|
| Proxy 流量 | litellm_proxy_total_requests_metric、litellm_proxy_failed_requests_metric |
RPS / 失败率;KEDA 按 rate 扩 gateway |
| Pod 负载 | litellm_in_flight_requests |
探针前排队深度;也可打 /health/backlog;上游变慢时比 CPU 更敏感 |
| 延迟 | litellm_request_total_latency_metric、litellm_llm_api_latency_metric、litellm_overhead_latency_metric |
区分网关开销 vs 上游;告警多于盲目扩缩 |
| Deployment | litellm_deployment_cooled_down、litellm_deployment_successful_fallbacks |
与第 3 节 retry/fallback 对上看;适合告警,不适合单独当扩容主信号 |
| 花费 / token | litellm_spend_metric、litellm_input_tokens_metric |
按 key/team/model 看用量;偏财务与配额,不是 HPA 主指标 |
| 依赖健康 | service_callback: ["prometheus_system"] → litellm_postgres_* / litellm_redis_* |
Redis/Postgres 变慢时,先扩依赖或限流,而不是只加 gateway 副本 |
| Spend 队列 | litellm_redis_spend_update_queue_size 等 |
异步记账堆积;副本涨起来后要一起看 |
Grafana 看板可参考官方 cookbook:cookbook/litellm_proxy_server/grafana_dashboard。具体 ScaledObject 写法见 §5.4。
5.2 高并发:和热路径设计是同一件事
官方 1K RPS 负载测试(受控实验,不是生产采样):
- 机器:LiteLLM 4 ×
t2.large(2 vCPU / 8GB) - 上游:fake OpenAI endpoint(不是真实计费 Provider)
- 结果量级:约 1174+ RPS,median ~96ms
数字只在「假上游、该机型组合」下成立;换真实模型、开 streaming、或改副本数,吞吐会差一截。另一组带上游 RPM 配额的实验里,大量失败来自 Provider 配额,不是 Proxy 先垮——这说明瓶颈经常在下游配额,而不是网关进程本身。
和第 3 节同一条设计:鉴权尽量走 DualCache,限流走 Redis,Spend 异步入队。多副本生产通常需要 Redis;热路径上尽量少同步写库。
5.3 组件化部署:单 container 共享命运,为何要拆
官方博文 Announcing Componentized Deployments(2026-05)讲的是:同一个 LiteLLM 容器同时干两件完全不同的事。
| 角色 | 流量特征 | 例子 |
|---|---|---|
| LLM 数据面 | 高 QPS、突发、额外延迟希望个位数毫秒 | /chat/completions、/v1/messages、embeddings、passthrough、/health、/metrics |
| 管理控制面 | 偶发、但单次可能扫百万行、吃 CPU | keys/teams/orgs、SSO、audit、spend/usage 分析、Dashboard API |
两者跑在同一 asyncio 事件循环上时,控制面最慢的那次查询,会决定数据面的可靠性地板。
事故:两副本单体如何被看板拖死
博文里的现场很具体,而且和「已经有两个 replica」并不矛盾:
- 某企业在 Kubernetes 上跑 两个 gateway 副本(仍是单体进程:推理 + 管理同一容器)。
- Dashboard 拉 两年 Usage 聚合 → 服务端按约 730 天 × user/key/model 做聚合,大量工作在进程内。
- 聚合占满事件循环 →
/v1/messages与/health/liveliness响应不上。 - kubelet 认为探针失败 → 杀掉正在扛推理的那个 Pod。

单体共享命运的四条机制:
- 共享事件循环:CPU 密集聚合挡住所有 coroutine,含数据面。
- 共享健康检查:K8s 分不清「分析接口慢」和「进程死了」,一律杀 Pod。
- 共享扩缩单元:为看板加副本,等于推理也一起加;反过来亦然。
- 共享 DB 连接池:分析大读与
update_spend写抢同一批连接,Spend 刷库会堆。
拆成什么
Helm 组件化把 LiteLLM 拆成三个微服务 + 一次性 migrations Job:
| 组件 | 端口 | 表面 |
|---|---|---|
| gateway | 4000 | 数据面:chat/messages/embeddings/audio/batches/passthrough、/health、/metrics |
| backend | 4001 | 管理/UI API:keys、users、teams、orgs、SSO、audit、spend & usage 分析 |
| ui | 3000 | Next.js Dashboard(静态资源,nginx) |
| migrations | Job | prisma migrate deploy(pre-install / pre-upgrade hook) |
Ingress 按路径分流:数据面前缀 → gateway;UI 静态 → ui;其余管理 API → backend。

左:单体共享命运;右:组件化后独立扩缩(博文配图)
同一事故重放:两年聚合打到 backend,只堵 backend 的事件循环;gateway 继续答 /health/liveliness 和 /v1/messages。backend 挂了,K8s 只回收 backend,推理面还在。
gateway / backend 可各自挂 HPA;YAML 与按业务指标扩缩见 §5.4。
可选 Postgres read replica:find_* / count / group_by 等重读走 reader,spend 写仍走 primary,减轻分析读对 update_spend 连接池的挤压。

EKS 参考拓扑含 ALB、Aurora、ElastiCache 等(博文)
Helm 发布在 oci://ghcr.io/berriai/litellm/chart/litellm。敏感项用 Secret 引用;database.writer / database.reader 分开配。博文示例用 --version 1.86.0-dev 安装;形态固定为 gateway + backend + ui + migrations Job。
和「现在这种部署」的关系
很多线上环境(包括类似下面这种)仍是 单 container 模式:一个镜像里同时跑推理与管理,只是用 Deployment 拉了多个副本:
k get po -n litellm-relay
litellm-business-relay-...-ngj8p 1/1 Running
litellm-business-relay-...-tdg5t 1/1 Running
这是「单体多副本」,不是组件化部署。两个 Pod 都能吃流量,也都能被 Usage 大盘拖死——博文事故恰好就是两副本单体。副本解决的是容量与部分可用性;解决不了「分析与推理共享事件循环 / 共享探针」的命运绑定。
| 形态 | 特征 | 典型风险 |
|---|---|---|
| 单 container × N 副本(现状常见) | 一进程既服务 /chat/completions 又服务看板聚合 |
长区间 Usage 拖死探针 → 杀推理 Pod |
| 组件化 gateway / backend / ui | 推理与管理分 Deployment、分探针、分 HPA | 运维面更复杂,但故障影响面更可控 |
什么时候值得从单体迁到组件化:
- Dashboard / 长区间 spend 分析已经或很可能拖慢健康检查
- 希望推理副本数只跟 QPS 走,不被看板尖刺带着加机器
update_spend与分析大读开始抢 DB 连接(可再加 read replica)
什么时候可以先维持单体多副本:
- 流量与看板查询都还轻,没有出现「分析拖死探针」
- 运维暂时不想维护三套 Deployment + Ingress 分流
组件化把推理与管理拆开,探针与 HPA 也独立;单体多副本仍然共享进程内命运。已经在 K8s 上跑双副本单体、且团队会用 Admin Usage 拉长区间时,我会把组件化当成明确演进项,而不是等事故再发生一次。它替代不了 Redis/Postgres 的容量规划,也清不掉错误的 fallback 链或 Team 配额配置。
5.4 HPA / KEDA 自动扩容
固定 replicas: 2 只能扛住「已知水位」。流量突发、上游变慢导致 in_flight 堆积时,最先值得上的往往是 按指标扩副本——HPA / KEDA 门槛最低:集群里通常已有 metrics-server;有 Prometheus 就能挂 KEDA。PDB、多 AZ 打散、节点自动扩缩可以后补,不挡这一步。
HPA 和 KEDA 差在哪
| 看什么 | 动什么 | LiteLLM 上怎么用 | |
|---|---|---|---|
| HPA | CPU / 内存,或经 metrics-adapter 的自定义指标 | Deployment 副本数 | 组件化 Helm 默认可开 CPU HPA;实现简单 |
| KEDA ScaledObject | Prometheus 查询等 | 创建/管理底层 HPA | 按 RPS、in_flight 等业务指标扩;本站另有 KEDA 文 |
KEDA 不替代 HPA:它把 Prometheus 结果喂给 HPA。VPA(改单个 Pod 的 requests)对 LLM 网关通常不是主路径——突发靠加副本。
组件化之后,扩缩对象拆开:
flowchart LR Prom["Prometheus<br/>抓 /metrics"] KEDA["KEDA / HPA"] GW["gateway<br/>跟 QPS / in_flight"] BE["backend<br/>跟看板与管理 API"] Prom --> KEDA KEDA --> GW KEDA --> BE
推理尖刺只加 gateway;Usage 大盘尖刺只加 backend。单体多副本上挂一个 HPA,分析一忙仍可能拖死同进程里的推理——扩缩管容量,组件化管故障影响面,两条线。
先上 CPU HPA
组件化 Helm 即可开。Componentized Deployments 示例:
gateway:
hpa:
enabled: true
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 70
backend:
hpa:
enabled: true
minReplicas: 1
maxReplicas: 4
targetCPUUtilizationPercentage: 70
盲区:上游 Provider 变慢时,LiteLLM CPU 未必升高,litellm_in_flight_requests 却会涨——按 CPU 扩不出来。官方 latency 排查把「高 in_flight + 高 ALB TargetResponseTime」标成该 scale out 的信号。下一步再上 KEDA。
再上 KEDA:按 LiteLLM 指标扩
前置:§5.1 已开 callbacks: ["prometheus"],Prometheus 能 scrape /metrics(默认要 Bearer)。
按近 1 分钟代理 RPS 扩 gateway(阈值按压测改):
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: litellm-gateway-rps
namespace: litellm-relay
spec:
scaleTargetRef:
name: litellm-gateway
minReplicaCount: 2
maxReplicaCount: 10
pollingInterval: 15
cooldownPeriod: 300
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus.monitoring.svc:9090
query: |
sum(rate(litellm_proxy_total_requests_metric_total[1m]))
threshold: "50"
更贴近排队深度的是 litellm_in_flight_requests(也可打 /health/backlog):
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus.monitoring.svc:9090
query: |
sum(litellm_in_flight_requests)
threshold: "40"
Midas 有按每 Pod 每分钟 acompletion 扩的写法——仍是业务吞吐,不是 CPU。
失败 / cooldown / fallback 计数更适合告警或第二 trigger;上游全面 429 时按失败率狂扩,只会放大打到 Provider 的请求。
backend 用更保守的 CPU HPA,或单独 ScaledObject;不要和 gateway 共用一条规则。
单体双副本也能先挂
像 litellm-business-relay 两个 Pod:仍可先挂 HPA/KEDA,让副本随 RPS / in_flight 浮动。
| 能解决 | 不能解决 |
|---|---|
| 推理 QPS 上涨时自动加 Pod | Usage 大盘拖死事件循环(分析与推理同进程) |
| 某一 Pod 挂了,维持容量水位 | 探针把「分析慢」误判成「进程死」 |
先上 KEDA 再拆 gateway/backend,或反过来,都可以。
扩缩时顺手盯的几项
- Redis / Postgres:副本上去,限流与 Spend 队列压力也上去。
- 下游 Provider 配额:扩 LiteLLM 不会提高 Azure/OpenAI RPM。
- 缩容冷静期:
cooldownPeriod/ HPAscaleDown.stabilizationWindowSeconds偏保守,少打断长流式。 - 多 worker:设
PROMETHEUS_MULTIPROC_DIR,否则 KEDA 看到的指标不完整。 - Pending Pod:
maxReplicas开大了但一直 Pending,多半是节点资源不够——那是下一步的事,不挡先把 HPA/KEDA 跑起来。
flowchart LR Traffic["突发 /chat"] --> Metrics["Prometheus<br/>RPS · in_flight"] --> Scale["KEDA / HPA"] --> Pods["gateway 副本"] --> Deps["Redis / Postgres / Provider"]
推理面优先 in_flight 或 RPS,CPU 作辅助。LiteLLM /metrics 可直接喂 KEDA Prometheus scaler。和扩缩直接相关的约束:有 Redis;热路径少同步写库;gateway 与 backend 分开扩。
共享不重时不必上 Gateway。共享已重时,先把 Virtual Key、Team 限额和有限的 fallback 链配清楚。排障倒着查:响应头 cost / model-id → Redis 窗口或冷却 → 约一分钟后的 SpendLogs。网关自身 P99 或合规把选型推过临界点时,回到 §1.1 对 Bifrost 做同条件复测。