返回归档
🧭LLM 与 Agent

LiteLLM 架构拆解:请求链路、重试回退与多租户表结构

一条请求如何过鉴权、限流、Router 的 retry/fallback,再进 SDK 翻译层;Spend 为何异步落库。与 Bifrost 的 trade-off,以及组件化与 HPA/KEDA。

文章目录

一句话主线: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.mdLife of a Requestschema.prismaFallbacks 为准;组件化 Helm 示例版本见第 5 节。

1. 何时上 Gateway

单团队、单应用、低量、单一 Provider、不做内部结算时,直接 SDK 更轻。Gateway 会多出 Postgres、Redis 和运维面。

共享一出现就不一样:多团队共用预算、需要 chargeback、多 Provider 故障转移、统一观测——其中任意两条成立,Gateway 通常就值得上。薄 Proxy 的实际收益是解耦:加模型改 transformation.py,改策略动 Proxy hooks。

直连共享真实 Key;经 Gateway 后策略在控制面执行

直连 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。

默认 Transform;需要完整原生能力再 Passthrough

扩展性主要落在各 Provider 的 transformation.py。默认走 Transform;只有原生特性或缺省 SDK 迁入成本明显更高时,再考虑 Passthrough。

3. 一条请求怎么走完

用虚拟 Key 打 POST /v1/chat/completions。响应头里常立刻有 x-litellm-response-cost;此时 LiteLLM_SpendLogs 可能还没有这一行——要等后台约 60 秒的 update_spend

下面先分清控制面 / 数据面各自管什么,再沿源码路径走完同步调用与异步记账。实现细节对照官方 ARCHITECTURE.md

同步鉴权路由调用与异步 Spend 落库

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.pyCustomLLM
状态存储 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.pyroute_llm_request.pycommon_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.pyproxy/auth/proxy/hooks/router.pyrouter_strategy/

3.2 鉴权:user_api_key_auth.py + DualCache

user_api_key_auth.pyBearer sk-... 收成后续都认的身份卡片:

  1. InternalUsageCacheproxy/utils.py)读 DualCache:先进程内存,再 Redis(caching/dual_cache.pycaching/redis_cache.py)。
  2. miss 查 Postgres LiteLLM_VerificationToken(Prisma)。token 列是哈希。
  3. auth_checks.py 做预算与模型权限:有 team_idkey.models ∩ team.modelsKey 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__.pyPROXY_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.pylowest_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_fallbackscontent_policy_fallbacks;另有 default_fallbacks
  • max_fallbacks 限制跨组跳转次数。请求体可带 fallbacksdisable_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_fallbacksoriginal_model_group;响应头有 x-litellm-attempted-fallbacksx-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"]

基类 BaseConfigllms/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 成本归因

同步成本链:

  1. acompletion 返回到 utils 包装
  2. update_response_metadata()llm_response_utils/response_metadata.py
  3. logging_obj._response_cost_calculator()litellm_logging.py
  4. litellm.completion_cost()cost_calculator.py
  5. 写入 response._hidden_params["response_cost"]
  6. proxy/common_request_processing.py 抽到 x-litellm-response-cost(及 call-id、model-id、model-name、cache-key 等)
  7. async_success_handler()_ProxyDBLogger.async_log_success_event()
  8. DBSpendUpdateWriter.update_database() 入队 Redis
  9. 后台 update_spend 刷 Postgres

前 6 步在返回客户端前完成;7–9 是侧车。

3.7 异步侧车与基础设施

DBSpendUpdateWriterdb_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 队列 RedisCacheDualCache
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

  1. 新建 llms/your_provider/chat/transformation.py,继承 BaseConfig
  2. 实现 transform_request / transform_response
  3. 注册;tests/llm_translation/ 单测转换,不必打真 API。
  4. 简单 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 测试,避免只修了一条入口。

扩展时改 transformation,不改 Handler

数据访问层在 litellm/models/ + repositories/,供 gateway 与 SDK 共用,避免到处 import proxy 内部;第 4 节结合 schema 展开。

4. DB schema:隔离落到哪些字段

完整定义见 schema.prisma。开源心智:

Organization(企业)
  └── Team          ← OSS 顶层
        ├── User / TeamMembership
        └── VerificationToken(Virtual Key)

Team 为开源顶层;spend 上滚,预算逐层挡

持久化实体在 litellm/models/(Pydantic),访问层在 litellm/repositories/BaseRepository + 各实体 Repository),gateway 与 SDK 都能用,而不必 import proxy/ 内部。约定包括:

  • JSON 列:写入 dumps、读出 loads
  • 删除 Team/Token:同一事务里先写入 LiteLLM_Deleted*,再删原行(Archive-then-Delete)
  • 字段名与列名不一致时(如 org_idorganization_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_UserTableTeamMembership

User:user_idteams[]max_budget / spendmodels,用户级 rpm/tpm,user_roleorganization_idsso_user_id

LiteLLM_TeamMembership:复合主键 (user_id, team_id),含 spendtotal_spendbudget_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 BudgetTableSpendLogs、Daily

BudgetTablemax_budgetsoft_budget、tpm/rpm、model_max_budgetbudget_durationallowed_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_fallbacksoriginal_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 一次成功请求如何写库

  1. 鉴权读 VerificationToken(多半缓存)。
  2. 限流读 Redis,对照表上的限额配置。
  3. Router 可能同组 retry / 跨组 fallback;cooldown 写 Redis。
  4. cost 进响应头;Spend 入队。
  5. 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_idDailyTeamSpend 结算。

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-idx-litellm-attempted-fallbacks:调用与回退痕迹

排障「这次花了多少、落到哪」优先看头,不必等库。

异步:SpendLogs 与 Daily 表

成功后 _ProxyDBLoggerDBSpendUpdateWriter 入队,约 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_metriclitellm_proxy_failed_requests_metric RPS / 失败率;KEDA 按 rate 扩 gateway
Pod 负载 litellm_in_flight_requests 探针前排队深度;也可打 /health/backlog;上游变慢时比 CPU 更敏感
延迟 litellm_request_total_latency_metriclitellm_llm_api_latency_metriclitellm_overhead_latency_metric 区分网关开销 vs 上游;告警多于盲目扩缩
Deployment litellm_deployment_cooled_downlitellm_deployment_successful_fallbacks 与第 3 节 retry/fallback 对上看;适合告警,不适合单独当扩容主信号
花费 / token litellm_spend_metriclitellm_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」并不矛盾:

  1. 某企业在 Kubernetes 上跑 两个 gateway 副本(仍是单体进程:推理 + 管理同一容器)。
  2. Dashboard 拉 两年 Usage 聚合 → 服务端按约 730 天 × user/key/model 做聚合,大量工作在进程内。
  3. 聚合占满事件循环 → /v1/messages/health/liveliness 响应不上。
  4. kubelet 认为探针失败 → 杀掉正在扛推理的那个 Pod

管理查询拖死事件循环后,探针误杀推理 Pod

来源:Componentized Deployments

单体共享命运的四条机制:

  1. 共享事件循环:CPU 密集聚合挡住所有 coroutine,含数据面。
  2. 共享健康检查:K8s 分不清「分析接口慢」和「进程死了」,一律杀 Pod。
  3. 共享扩缩单元:为看板加副本,等于推理也一起加;反过来亦然。
  4. 共享 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。

Ingress:数据面走 gateway,管理 API 走 backend,UI 走 nginx

左:单体共享命运;右:组件化后独立扩缩(博文配图

同一事故重放:两年聚合打到 backend,只堵 backend 的事件循环;gateway 继续答 /health/liveliness/v1/messages。backend 挂了,K8s 只回收 backend,推理面还在。

gateway / backend 可各自挂 HPA;YAML 与按业务指标扩缩见 §5.4。

可选 Postgres read replicafind_* / count / group_by 等重读走 reader,spend 写仍走 primary,减轻分析读对 update_spend 连接池的挤压。

分析读走副本,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 / HPA scaleDown.stabilizationWindowSeconds 偏保守,少打断长流式。
  • 多 worker:设 PROMETHEUS_MULTIPROC_DIR,否则 KEDA 看到的指标不完整。
  • Pending PodmaxReplicas 开大了但一直 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 做同条件复测。