数据截至 (上游 commit 3f15dc32871c)
第 6 章:AI Gateway —— 把 SDK 包成带认证与计费的多租户网关
前五章讲的都是库:你
pip install litellm,在自己进程里调completion()。这一章讲的是服务:同一套代码被塞进一个 FastAPI 应用,变成一个对外的 HTTP 网关,别人拿一把sk-…开头的 key 来调,你能管住「谁能调、能调多少、花了多少」。
6.1 为什么 Router 之上还要再包一层
05 章的 Router 已经解决了「一个模型组下面挂多个部署、坏了自动冷却、超了自动回退」。但 Router 是进程内对象:它信任调用方,没有身份概念,也不知道钱花在谁头上。
一个组织要把 LLM 能力开放给二十个团队用,还缺三件东西:
| 缺的东西 | 具体是什么 | 本章对应小节 |
|---|---|---|
| 身份 | 这个请求是谁发的?属于哪个团队/组织? | 6.4 |
| 配额 | 他这分钟还能发几个请求?这个月还剩多少预算? | 6.5 |
| 账本 | 这次调用花了多少钱?怎么落到数据库、按天按团队聚合? | 6.7 |
Proxy 层就是补这三样。它不重新实现调模型——真正发请求的还是 Router 和 01–04 章那套 SDK。它做的是在 SDK 前后各加一段:前面认人、后面记账。
一句话直觉: 把 SDK 当成「数据库引擎」,Proxy 就是套在外面的「数据库服务器」——加了连接认证、权限、配额和审计日志,SQL 执行内核没变。
6.2 顶层全景:一次请求穿过网关的六道工序
先看请求怎么走。从上往下读,每一格是一道必须过的工序,任何一道拒绝就直接返回错误:
HTTP POST /v1/chat/completions
Authorization: Bearer sk-… 客户端只认 OpenAI 协议
│
▼
① 认证 key/JWT → 身份对象(user/team/org + 各级配额)
│ proxy/auth/user_api_key_auth.py
▼
② 闸门 RPM/TPM/并发限流、预算检查与「预留」
│ proxy/hooks/*
▼
③ 统一预处理 注入 metadata、建日志对象、跑输入侧 guardrail
│ proxy/common_request_processing.py
▼
④ 分发 HTTP 路由 → Router 的哪个方法(acompletion / aembedding / …)
│ proxy/route_llm_request.py
▼
⑤ Router + SDK 模型组选部署 → 翻译层 → 供应商 HTTP
│ (第 05 章 + 第 01-03 章)
▼
⑥ 响应 OpenAI 格式的 body + x-litellm-response-cost 等响应头
部件与落点。 每道工序对应的真实入口:
| 工序 | 干什么 | 文件 | 关键符号 |
|---|---|---|---|
| HTTP 端点 | 暴露 82 个 @router.* 路由,含 OpenAI 全套兼容端点 | litellm/proxy/proxy_server.py | app = FastAPI(...)(:1379)、chat_completion(:9888) |
| 配置面 | 读 YAML + 从数据库热加载部署 | litellm/proxy/proxy_server.py | ProxyConfig(:4123)、load_config(:4679)、add_deployment(:6574) |
| 认证 | 校验 key、拉团队/用户/组织、跑多级检查 | litellm/proxy/auth/user_api_key_auth.py | user_api_key_auth(:2628) |
| 钩子 | 限流、预算、内容安全 | litellm/proxy/hooks/ | PROXY_HOOKS(hooks/__init__.py:19) |
| 统一处理 | 所有 LLM 端点共用的前后处理 | litellm/proxy/common_request_processing.py | ProxyBaseLLMRequestProcessing(:784) |
| 分发 | 路由类型 → Router 方法 | litellm/proxy/route_llm_request.py | route_request(:249) |
| 计费落库 | 成本进队列、后台批量写 Postgres | litellm/proxy/db/db_spend_update_writer.py | DBSpendUpdateWriter(:97) |
端点长什么样。 一个 OpenAI 兼容端点的骨架非常薄——认证挂在 FastAPI 依赖上,主体交给统一处理类:
@router.post("/v1/chat/completions", dependencies=[Depends(user_api_key_auth)], ...)
async def chat_completion(request, fastapi_response, model=None,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth)):
见 litellm/proxy/proxy_server.py:9867-9888。同一个函数上叠了四个 @router.post,把 /v1/chat/completions、/chat/completions、/engines/{model}/chat/completions、/openai/deployments/{model}/chat/completions(Azure 风格)映到同一实现 ——这就是「客户端换个 base_url 就能用」的来源。
6.3 配置面:YAML 三段 + 数据库热加载
6.3.1 三段结构
网关的行为几乎全由一个 YAML 决定。仓库根的 proxy_server_config.yaml 是最完整的样例,分三段:
| 段名 | 管什么 | 典型条目 |
|---|---|---|
model_list | 对外暴露哪些模型名,每个名字底下挂哪些真实部署 | model_name / litellm_params / model_info(proxy_server_config.yaml:1-60) |
litellm_settings | 直接写进 SDK 全局的开关 | drop_params、num_retries、success_callback、cache(proxy_server_config.yaml:168-195) |
general_settings | 网关自己的运维参数 | master_key、store_model_in_db、proxy_batch_write_at、pass_through_endpoints(proxy_server_config.yaml:223-251) |
另有 router_settings 段,原样喂给 05 章的 Router(proxy_server_config.yaml:215-221:routing_strategy: usage-based-routing-v2 + Redis 连接)。litellm/proxy/proxy_config.yaml 是个更小的开发用样例,展示了 mcp_servers 段。
读取路径。 ProxyConfig.load_config(proxy_server.py:4679)是唯一的装配点,顺序是:先把 environment_variables 灌进 os.environ,再逐段消费——litellm_settings 写进 litellm.* 全局(:4650 起),general_settings 取出来存成模块级全局(:5074),model_list 变成 router_params["model_list"](:5316)最终构造 litellm.Router(:5399),guardrails 段交给 init_guardrails_v2(:5418)。
配置值里的 os.environ/OPENAI_API_KEY 这种写法由 get_secret 解析,所以 YAML 本身不含密钥。
6.3.2 启动:一个 lifespan 把所有东西点起来
FastAPI 的 lifespan=proxy_startup_event(proxy_server.py:1379、:971)按固定顺序做四件事:
- 找配置文件(
CONFIG_FILE_PATH或WORKER_CONFIG)并load_config; - 连数据库 ——
ProxyStartupEvent._setup_prisma_client(:9387); - 注册计费回调 ——
cost_tracking()(:2342)把_ProxyDBLogger挂进litellm.callbacks(:2347); - 起后台任务 ——
ProxyStartupEvent.initialize_scheduled_background_jobs(:8726)。