如果你手头同时维护着两个以上大模型接口,litellm 这个名字应该不陌生。第一次看到它时,我的第一反应是:这不就是一个把各家模型 API 搓到一起的代理吗?后来真正在团队里用起来才发现,它的价值远不止“统一接口”这么简单。litellm 是一个开源的大模型网关服务,帮你把不同模型厂商的 API 协议差异、密钥管理、路由策略、成本统计全部收敛到一个独立的服务里。业务侧只需要记住一个地址和一把密钥,剩下的复杂逻辑都由网关处理。这篇文章我会从一个实际使用者的角度,把 litellm 的设计思路、部署方式、团队落地经验以及踩过的坑一次讲清楚。
1. 先看问题:多模型接入到底乱在哪
1.1 一个应用接三个模型,代码就快失控
先说痛点。很多项目一开始只接一个模型,代码写得很开心。等第二个、第三个模型进来的时候,麻烦就开始冒头了。
各家服务商的接口长得都不一样。有的参数叫max_tokens,有的叫max_new_tokens;有的用top_p控制随机性,有的靠temperature;有的把消息格式叫messages,有的还分system、user、assistant之外额外带一个history字段。更别提那些微妙的差异:同一个温度参数,在模型 A 上表现正常,换到模型 B 上可能生成风格完全变了。错误码也是各写各的,上游限流返回 429,网络超时返回 5xx,但具体字段千奇百怪,你没法用一套逻辑统一处理。
如果业务上还需要做模型对比、灰度切流,那就更难受。今天想试一个新模型,得改代码、做测试、重新发布,整个链路走完可能大半天。结果试下来效果不好,又要回滚。几个回合之后,项目里就会堆出一层又一层封装代码,谁都不敢动。
我见过最夸张的情况:一个服务里同时塞了四五个模型 SDK,每个 SDK 初始化方式不同、重试策略不同,连日志格式都对不上。出了问题,排查要翻遍各个 SDK 的源码。这种状态一旦持续下去,后面每加一个新模型都是一次痛苦的重构。
1.2 litellm 到底解决了什么
litellm 的核心思路很直接:它把你对模型厂商的调用全部集中到网关里面,网关对外暴露一套稳定的 HTTP 接口。你不再需要关心上游是谁、请求格式是什么、密钥怎么传,只需要按照一个约定好的格式发请求。
它内部维护一个“模型目录”。你可以给每个虚拟模型名分配一个或多个真实模型。请求进来之后,网关根据虚拟模型名去配置里找对应的上游,然后完成协议转换、鉴权注入、超时控制、重试、负载均衡,最后再把上游的响应转成统一格式返回给调用方。
在这套机制下,业务代码里写死的不是某个厂商的模型名,而是你自己定义的虚拟名字。以后想切换供应商,只要改网关配置,业务代码一行都不用动。
我实际用下来的感觉是:它解决的不只是“格式转换”,而是一整套“模型接入治理”问题。尤其是当你的团队里有多条业务线、多套服务都在调用大模型时,网关带来的收益会非常明显。
1.3 什么时候该上网关
先说结论:如果只有一个服务、一个模型,规模也很小,那直接调厂商接口是最省事的。但出现下面任意一条,网关就值得考虑:
- 多个服务共用同一个模型,密钥散落在各个环境变量里;
- 需要在不同模型之间切换、灰度、做 A/B 对比;
- 想要统计每个业务线花了多少 token、多少钱;
- 需要控制不同团队能用哪些模型、不能超多少预算;
- 担心某家供应商单点故障,想要一个故障转移机制。
我从个人经验看,很多团队都是等到“模型越来越多、密钥管理失控、成本对不上账”这三个问题同时爆发时,才想起来上网关。其实早点上,后面能省很多事。
2. 核心设计拆解:为什么“网关”而不是“SDK”
2.1 代理模式和 SDK 封装的本质区别
市面上已经有不少模型封装库,它们在代码里帮你去适配各家 API。litellm 本身最初也有纯 Python 库的模式,可以直接在业务代码里调用。但我现在更推荐把它当独立网关来用,原因很现实。
SDK 是进程内的依赖,要跟业务代码一起部署。一旦多个服务都引用同一个 SDK,SDK 升级就得所有服务一起升,否则行为不一致。而且密钥仍然在每个服务的环境变量里,安全边界很模糊。如果某个服务的日志把请求体打出来,密钥也就跟着泄了。
独立网关则把这些问题收敛到了一起。所有业务服务通过 HTTP 访问网关,业务代码里几乎没有模型厂商的概念。网关升级不影响业务,密钥只放在网关这一层,模型路由的变化由网关处理。代价是多一跳网络延迟,但绝大多数场景下这点延迟是可以忽略的。
我做过一个对比,方便理解:
| 维度 | SDK 封装 | litellm 网关 |
|---|---|---|
| 部署方式 | 跟随业务服务 | 独立部署 |
| 升级成本 | 所有引用服务发版 | 重启/滚动升级即可 |
| 密钥管理 | 分散在各服务 | 集中在网关 |
| 多服务共享 | 不方便 | 天然支持 |
| 额外延迟 | 无 | 多一跳内网请求 |
| 故障影响 | 只影响当前服务 | 网关不可用则全链路影响 |
网关的问题也很明显:它变成了一个关键依赖,部署上需要做高可用。但这和数据库、缓存、消息队列一样,属于基础设施建设成本,可以接受。
2.2 模型别名与路由机制
litellm 最核心的抽象是“模型别名”。配置中的model_name并不是上游真实模型名,而是你业务系统里定义的逻辑名。比如业务侧都叫my-chat,网关配置里可以把这个逻辑名同时指向 A 厂商和 B 厂商的模型。
请求进来后,网关会按路由策略从一组真实模型里选一个去转发。这背后做了几件事:
- 同一个别名可以对应多个上游,天然支持负载均衡;
- 某个上游连续失败时,网关会自动把它隔离一段时间,并尝试其他上游;
- 可以配置 fallback,主模型失败后自动走备用模型;
- 可以通过权重控制流量分配,比如 90% 走模型 A,10% 走模型 B。
这套机制对业务侧完全透明。业务方只知道我发了一个请求给my-chat,具体是谁在响应,业务不关心。
2.3 虚拟密钥和预算管理
我推荐团队使用 litellm 的一个重要理由是它的密钥管理设计。它没有直接把上游厂商的 key 暴露给业务方,而是自己生成一套虚拟 key。
这套虚拟 key 可以绑定模型、限定预算、设置速率和有效期。开发者手里拿到的只是自己那把 key,网关负责统计用量,超过额度直接拒绝请求。这个设计很像银行卡的副卡,主卡权限很大,副卡额头和额度都有限。
对内部平台来说,这个能力非常实用。每个团队一把 key,每个 key 设置自己的月预算,谁超了谁负责,财务对账的时候也不用再去爬厂商账单。
2.4 日志、指标与可观测性
网关还有一个好处:所有请求都经过它,日志和指标自然汇聚到一个点。litellm 支持把调用日志写入 SQLite、PostgreSQL、MySQL、Redis 等存储,记录每次请求的模型、输入输出 token 数、响应时间、调用方身份。
标准指标格式的暴露能力也很有用。接上常见的监控告警体系之后,就能看到“某个虚拟模型今天平均延迟飙升”“某个 key 请求量异常高”“调用成功率下降”这些信号。团队里如果有运维或者平台角色,这一步基本是标配。
3. 快速上手:把一个 litellm 网关跑起来
3.1 环境准备
先明确一点:litellm 依赖 Python,生产环境我更推荐用容器方式跑,避免污染本机环境。
本地快速体验可以直接安装:
pip install "litellm[proxy]"安装完之后命令行就会多一个litellm命令。如果想用容器,可以先把配置写好再启动。下面这种启动方式适合本地调试:
docker run -d -p 4000:4000 \ -v $(pwd)/config.yaml:/app/config.yaml \ -e PROVIDER_A_KEY=xxx \ -e PROVIDER_B_KEY=xxx \ --name litellm-gateway \ your-registry/litellm:stable注意,这里your-registry/litellm:stable要替换成你们实际镜像仓库里的地址。配置文件和密钥通过挂载和环境变量传进去,镜像本身保持只读。
3.2 写第一份 config.yaml
litellm 的配置就是一个 YAML 文件,核心是model_list。下面这份配置可以当作最小示例:
model_list: - model_name: my-chat litellm_params: provider: provider_a model: provider_a/general-chat api_key: os.environ/PROVIDER_A_KEY - model_name: my-chat litellm_params: provider: provider_b model: provider_b/general-chat api_key: os.environ/PROVIDER_B_KEY general_settings: master_key: sk-master-demo解释一下关键字段:
model_name:对外暴露的虚拟模型名,业务方只用这个名字调用。litellm_params:真正发给上游服务的参数集合。provider:服务商标识,请根据 litellm 官方文档填对应值,这里用provider_a和provider_b做占位。model:上游模型名,通常写作“服务商标识/模型名”的形式。api_key:建议用环境变量引用,不要直接写在 YAML 里。master_key:管理端主密钥,用来生成虚拟 key、查询日志、管理配置。
为什么my-chat出现了两次?这不是配置冲突,而是故意让同一个虚拟名对应两个上游,形成一组可切换、可负载均衡的模型池。如果暂时不想做多供应商冗余,只保留一项也可以。
配置写好后启动服务:
litellm --config config.yaml --port 4000看到日志输出监听端口后,用 curl 验证:
curl http://localhost:4000/v1/chat/completions \ -H "Authorization: Bearer sk-master-demo" \ -H "Content-Type: application/json" \ -d '{ "model": "my-chat", "messages": [{"role": "user", "content": "你好"}] }'如果你能拿到上游模型的正常回复,说明网关已经跑通了。注意这里用master_key测试只是因为刚起步,正式环境应该生成独立虚拟 key。
3.3 配置负载均衡与故障转移
多供应商的核心价值是故障转移。假设你的主用模型比较稳,但也想保留一个备用模型,可以在model_list里再加一项:
model_list: - model_name: my-chat litellm_params: provider: provider_a model: provider_a/general-chat api_key: os.environ/PROVIDER_A_KEY - model_name: my-chat litellm_params: provider: provider_b model: provider_b/general-chat api_key: os.environ/PROVIDER_B_KEY - model_name: backup-chat litellm_params: provider: provider_c model: provider_c/fallback-chat api_key: os.environ/PROVIDER_C_KEY router_settings: routing_strategy: simple-shuffle fallbacks: - my-chat: ["backup-chat"] cooldown_time: 30 allowed_fails: 3 num_retries: 2这里router_settings的意思是:
routing_strategy: simple-shuffle:在可用上游之间做随机轮询,避免每次都打同一个厂商。fallbacks:当my-chat对应的两个上游都不可用时,自动尝试backup-chat。cooldown_time:某个上游连续失败后,冷却 30 秒再放流量过去。allowed_fails:连续失败多少次后判定为不健康。num_retries:网关对单次请求的最大重试次数。
这套配置落地之后,你就不用再写“if 上游挂了 else 备用模型”的逻辑了。网关帮你做了健康判定和灰度和转移。
3.4 业务侧接入方式
业务侧接入其实是最简单的部分。不管前端、后端还是脚本,只需要把请求地址指向 litellm,模型名改成虚拟名,密钥换成网关生成的 key 即可。
Python 里用最普通的 HTTP 请求就能完成:
import requests resp = requests.post( "http://litellm-gateway:4000/v1/chat/completions", headers={ "Authorization": "Bearer sk-虚拟key", "Content-Type": "application/json", }, json={ "model": "my-chat", "messages": [ {"role": "system", "content": "你是一个助手"}, {"role": "user", "content": "帮我总结一下这篇文章"}, ], "temperature": 0.3, "stream": False, }, ) print(resp.json()["choices"][0]["message"]["content"])这一步的关键点是:业务代码里不应该再出现任何真实厂商的模型名和密钥。模型名统一叫my-chat,密钥统一从环境变量或配置中心获取。以后加模型换模型,只发生在网关那一层。
4. 团队落地:密钥、预算与成本治理
4.1 虚拟密钥的生命周期
litellm 提供管理接口来生成虚拟 key。用它而不是 master key 去业务场景里调用,是标准做法。
curl http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-master-demo" \ -H "Content-Type: application/json" \ -d '{ "models": ["my-chat"], "max_budget": 100, "budget_duration": "30d", "max_parallel_requests": 5 }'这个接口的含义是:生成一把虚拟 key,只允许访问my-chat这个模型,30 天内预算上限 100 元,并发请求最多 5 个。
返回结果里会包含key字段,这把 key 只在生成时完整出现一次,之后只能看到脱敏信息。所以拿到之后要立刻存好,分发到对应服务的环境变量里。
你可以为每个业务线生成一把 key,甚至为每个人生成一把 key。这样出问题的时候,按 key 一查就能定位到是哪个服务、哪个调用方在捣乱。
4.2 配额限流与团队隔离
预算之外,限流同样重要。如果某个测试服务不小心写了个死循环,疯狂发请求,不仅消耗预算,还可能拖垮整个网关。通过虚拟 key 的max_parallel_requests和max_budget可以限制并发和总量,让一个团队的行为不至于影响其他人。
更细一点的团队隔离可以结合 key 绑定信息来做。不同项目组使用不同的 key,每个 key 只能看到自己可用的模型和预算。这样一来,A 团队超预算挂掉的时候,B 团队完全不受影响,排查问题也能按 key 快速分界。
4.3 成本追踪怎么落
成本追踪的第一步是配置持久化存储。litellm 默认可以跑,但重启之后日志就没了,团队场景肯定不行。可以在general_settings里指定database_url:
general_settings: master_key: sk-master-demo database_url: postgresql://user:password@localhost:5432/litellm配置好数据库之后,每次调用都会记录输入输出 token 数、模型名、调用方 key、时间戳和估算成本。通过这些数据可以做几件事:
- 按虚拟 key 汇总每个团队的月度消耗;
- 按模型维度统计哪个模型最烧钱;
- 对比不同供应商同规格模型的成本差异;
- 把用量和财务账单对账。
如果团队已经有监控告警体系,还可以在网关层采集调用量、延迟和异常率,设置“某个模型成功率低于 95% 就告警”这样的规则。成本问题越早暴露越好,等月底账单出来再追查,通常已经晚了。
4.4 安全实践注意事项
网关集中了所有上游密钥,等于把安全风险也集中了。这里说几个我自己实际踩过或者见别人踩过的规矩:
- master key 绝对不要出现在前端代码、日志、或者是发给第三方调用的接口文档里。
- 生产环境网关应该跑在内部网络,不能对公网裸奔。
- 每条业务线用独立虚拟 key,定期轮换;人员离职时及时吊销对应 key。
- 日志采集时注意过滤请求体中的敏感内容,有些业务数据不该落到日志里。
- 如果通过公网访问,必须开启 TLS。
我见过一个团队把 master key 写进前端配置里,结果 key 直接被浏览器网络请求面板看到了。这种事故虽然低级,但一旦发生,所有上游密钥都得跟着换,非常麻烦。
5. 常见问题与排查实录
5.1 所有请求都打到了同一个模型上
有段时间我发现某个虚拟模型名下的两个上游,流量几乎没有按预期均分,全部打到了第一个上面。后来排查发现是配置里第二个上游的健康检查没过,网关认为它不可用,所以一直不往那边转发。
检查思路:
- 先看两个上游的
api_key是否正确,环境变量有没有真正加载。 - 确认第二个上游的模型名是否写对,有没有手滑少个字符。
- 调大日志级别,看网关转发时的实际报错。
还有一种情况是配置顺序问题。litellm 会依次处理 model list,如果不做 shuffle 类策略,流量顺序可能并不均匀。把routing_strategy明确写上,目前体验最稳。
5.2 流式输出不生效或者卡住
接入流式输出是踩坑重灾区。常见现象是:直连上游模型流式正常,但经过 litellm 之后就感觉变卡,甚至会一直转圈。
排查清单:
- 请求体里是否带了
"stream": true,以及客户端是不是也在等增量。 - 如果是通过 Nginx 转发,必须关掉缓冲,否则 SSE 流会被积压。Nginx 配置里给对应 location 加上
proxy_buffering off;。 - 网关和上游之间是否设置了合理的超时。流式场景下,上游可能长时间不发数据,需要把超时时间调大,避免中间层提前断开。
- 确认客户端没有把聚合请求响应解析成普通 JSON。
流式链路比普通请求多几个环节,建议先用curl -N测试经过网关的原始流,看看是否正常,然后再回来看客户端代码。
5.3 超时和重试的尺度问题
上游模型响应慢很正常,尤其是生成长文本的时候。最初我遇到过网关默认超时太短,稍微长一点的请求就被切断。解决办法是在litellm_params里给单个模型调大超时:
litellm_params: provider: provider_a model: provider_a/general-chat api_key: os.environ/PROVIDER_A_KEY timeout: 600超时值并不是越大越好。太大意味着一个慢请求会长时间占用网关连接,太小则会把本来能成功的请求误杀。我习惯先观察线上 P95 耗时,再设定 1.5 到 2 倍于 P95 的超时时间。
重试也要讲究。普通文本请求可以自动重试,但流式请求如果已经向用户返回了一部分内容,再重试就会产生重复片段。这类请求我建议关掉自动重试,让客户端自己做幂等控制。
5.4 数据库启动失败和日志积压
小规模试用时用 SQLite 很方便,但并发一上来,SQLite 的写锁问题就会冒出来,表现为页面卡顿、日志写入失败、启动报数据库被锁定。团队环境直接切到 PostgreSQL 或者 MySQL,别在这个问题上挣扎太久。
另外日志表会涨得很快。每一条请求都会落库,一天的读写量可能几十万行。建议给日志表做分区或者定期归档,不然一个月后查询会越来越慢。
以前我碰到过一次启动失败,原因是数据库连接串里的密码带特殊字符,没有做 URL 编码。这个坑很无聊但很常见,看到数据库相关报错时,先检查连接串。
5.5 鉴权 401 和 403
用 master key 测试没问题,换虚拟 key 就报 403,大概率是 key 没有授权对应的模型。虚拟 key 在生成时绑定了models,如果后续在配置里新增了一个虚拟模型名,旧 key 默认没权限访问它。
排查方法是用管理接口查 key 的信息,看绑定模型列表。需要扩容时,重新生成或者更新 key 的权限记录。
还有一个小坑:调用方如果误把 master key 当普通 key 传给业务服务,风险很大。master 和虚拟 key 要严格分区,一个管运营,一个管业务调用。
6. 最后聊聊我的使用习惯
文章的最后,不打算给你一份功能清单,只想分享一点实际经验。
我习惯把 litellm 配置纳入版本管理,但密钥永远只放环境变量或密钥管理服务。配置变更走 MR 评审,发布时由 CI/CD 重新加载配置。这样任何一个模型参数的修改都能留痕,回滚也容易。
环境上,开发、测试、生产各跑一套独立网关,数据库、虚拟 key、预算策略都分开。开发环境的预算可以设得很低,防止误操作烧钱。生产环境则只对受控的虚拟 key 开放。
新模型接入的时候,我不会直接切全部流量,而是先把它挂到一个单独的虚拟模型名下,用一小部分请求做对比测试,没问题再改配置切流量。这种做法在 litellm 里实现成本很低,收益却很大。
如果非要说一个最值得立刻做的事:先把所有上游 key 收回到网关里。只要做到这一步,后面加模型、换供应商、做成本分摊都会顺畅很多。litellm 这类工具并不神秘,核心价值就是把“接入模型”这件事从业务代码里抽离出来,变成一份可以管理、可以观察、可以控制的配置。希望这篇内容能帮你少踩几个坑。