前阵子接手了一个内部 AI 中台项目,第一件事就是把散落在各业务线的大模型调用全部迁到 litellm 网关下面。做出这个决定并不轻松,毕竟之前大家各调各的:有人直接用某模型供应商的官方 SDK,有人自己拼 JSON 调接口,密钥放在各自的配置中心,预算也是各付各的。等到真要排查线上问题的时候,光是确认"这个请求到底打到哪家、用了谁的 key"就要折腾半天。litellm 作为一个统一的大模型 API 网关,确实把这一摊子事情理顺了。
这篇文章不是泛泛介绍 litellm 是什么,而是我从零开始把它落地到生产环境后的完整复盘。包括部署方式怎么选、配置文件的坑、负载均衡和限流的细节、客户端如何零成本接入,以及几个我实际踩过的性能问题。如果你正在被"多供应商、多 key、多团队"的模型调用管理搞得焦头烂额,这篇应该能帮你省下不少时间。
1. 统一大模型网关到底解决什么问题:从一次事故说起
1.1 一个典型的混乱现场
我接手的中台项目,最头疼的其实不是模型效果,而是调用方式太乱。业务 A 的代码里直接用着某家的 Python SDK,业务 B 用的是另一家的 SDK,业务 C 干脆自己用 requests 手写请求逻辑。三个团队的 API key 各自放在各自的配置中心,预算也是各付各的。
一次灰度上线,业务 B 的模型频繁报限流,业务 A 想帮忙分流,却发现根本没法统计 B 的实时调用量。更麻烦的是,不同供应商的返回结构不一样,有的带 usage 字段,有的要自己解析 content,有的错误码是 429,有的却是 500 加一个限流提示。业务代码里塞满了各种兼容逻辑,谁都不敢动。
坦白说,这种混乱并不是某一个人的责任,而是"直连所有供应商"这种模式天然会暴露的问题。只要团队一多、模型一多,SDK 版本、请求格式、错误处理、成本统计这些细节就会被无限放大。
1.2 集中式网关解决的五类问题
| 痛点 | 直连多供应商时的表现 | 接入 litellm 网关后 |
|---|---|---|
| 接口协议不统一 | 每家 SDK 风格不同,返回结构不同 | 统一为 OpenAI 风格的 chat completions 格式 |
| 密钥分散难以治理 | key 散落在代码、配置中心、同事电脑里 | 密钥集中在网关配置,下游使用虚拟 key |
| 限流与预算不可控 | 只能依赖各家供应商控制台 | 网关侧配置 rpm/tpm 与预算,超限即停 |
| 故障定位成本高 | 需要去多个控制台翻日志 | 网关日志统一记录模型、key、token、费用 |
| 模型切换风险大 | 切换供应商要改业务代码 | 只改网关配置,下游无感知 |
其中我感受最深的是最后一条。统一网关本质上是把"模型供应商"和"业务调用方"解耦了。业务方只需要知道"我有一个叫 chat 的模型可用",至于这个 chat 背后是 gpt-4o 还是别的模型,业务方完全不需要关心。这在直连模式下是不可能的,因为 SDK 和请求格式已经写死在代码里了。
1.3 litellm 的准确角色定位
很多人第一次接触 litellm 会误以为它是一个"模型聚合平台"或者"模型市场"。实际上不是,它做的事情更纯粹:一个 API 代理 / 网关。你在配置文件里声明每个模型由哪个供应商提供、用哪个密钥、限流多少,网关启动后,下游拿到的就是一个标准的/chat/completions接口。
它和自己在业务代码里写一个"统一封装层"是有本质区别的。封装库需要每个服务都引进来,版本升级要靠所有团队同步更新,出了问题还得逐个服务排查。而网关把所有适配集中在一处,升级只在网关侧操作。对我来说,这个边界感非常重要:litellm 只管转发和翻译,业务逻辑还是留在各自的服务里。
2. 两种部署方式怎么选:先跑起来再谈优化
2.1 本地 pip 验证 vs Docker 固定版本
litellm 的部署方式非常灵活,最常见的两种是pip install和 Docker 镜像。对于刚接触的人,我建议先在本地用 pip 跑通最小配置,再考虑上生产。
pip install 'litellm[proxy]'这个命令安装的是带有代理功能的全量版本。本地跑起来后,改配置重启都很快,适合验证思路。生产环境我推荐用官方 Docker 镜像,固定版本号,放到自己的镜像仓库里,然后做滚动发布。好处是版本可控,不会出现今天这台机器是 v1.2、明天那台是 v1.5 的尴尬局面。
用 Docker 时,关键目录和端口要单独挂出来:
docker run -d \ --name litellm-proxy \ -p 4000:4000 \ -e DATABASE_URL=postgresql://user:pass@host:5432/litellm \ -e MASTER_KEY=sk-master-xxx \ -v $(pwd)/config.yaml:/app/config.yaml \ ghcr.io/berriai/litellm:main-latestDATABASE_URL是用来持久化存储虚拟 key、模型调用记录和成本数据的,生产环境一定要配。MASTER_KEY是管理接口的访问凭证,相当于整个网关的超级管理员密码,千万不能省略,否则任何拿到网关地址的人都能调用管理 API。
2.2 最小可用的 config.yaml
不管用哪种方式启动,核心都是配置文件。下面这个配置是任何项目都能起步的最小例子:
model_list: - model_name: chat litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY general_settings: master_key: sk-master-xxxmodel_name是对外暴露的名字,下游请求时用的就是这个;litellm_params里才是真正发给上游的模型标识和密钥。这种"外部名 + 内部参数"的双层设计是整个配置文件的灵魂,后面我会专门展开讲。
然后启动:
litellm --config config.yaml --port 4000看到服务监听在 4000 端口,网关就算跑起来了。
2.3 用一次 curl 验证链路是否打通
配置好后,我习惯先发一个最简单的请求,确认链路通没通:
curl http://localhost:4000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-master-xxx" \ -d '{ "model": "chat", "messages": [{"role": "user", "content": "hello"}] }'返回结构应该是标准的 OpenAI 风格,包括id、object、choices、usage等字段,同时还会附加类似model、cost的额外信息。第一次看到这个返回的时候,我特意确认了一下请求确实是打到了上游模型上,而不是网关伪造的响应——因为返回值太像真实的 OpenAI 响应了,几乎无缝。
这里有一个小建议:先测一个不带流式的请求,确认链路稳定之后,再测试stream: true的情况。流式输出走的是 SSE,很多问题往往只会在流式场景下暴露,后面我会专门说。
2.4 生产环境必须补充的启动参数
本地验证通过之后,真正上生产之前,有几个启动参数需要重点关注:
--num_workers:默认单进程可能扛不住并发,一般按 CPU 核数设置,比如 4 核机器就开 4 个 worker。--database_url:不配数据库的话,虚拟 key 和调用记录都只存在内存里,重启就丢,生产必须配。--detailed_logging:开启详细日志,能看到每次请求的响应耗时、token 消耗、费用估算。--port:默认 4000,如果团队有端口规范就按规范来。
生产环境部署多台实例时,litellm 本身是无状态的,状态都存在数据库里,所以直接在前面挂一层负载均衡就行,横向扩容非常方便。我自己的做法是先在一台机器上把配置跑稳,再复制到另外两台,组成一个小集群。
3. config.yaml 的核心结构逐项解读
3.1 双层命名:外部名与真实模型标识
litellm 的配置核心是model_list,里面的每一项都有model_name和litellm_params两层信息。第一遍看配置的时候会觉得有点绕,但理解了之后你会觉得这个设计非常合理。
model_name是暴露给下游的名字,下游代码里永远只认这个名字;litellm_params里的model字段才是真正发给上游的模型标识。比如我希望内部统一提供一个名为chat的模型接口,实际后端可以是openai/gpt-4o,也可以是某个开源模型的托管服务:
model_list: - model_name: chat litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY这个双层命名最大的好处是:切换供应商时,下游代码一行都不用改。今天chat后面挂的是 gpt-4o,明天想换成另一家的模型,只需要把配置里的litellm_params.model换掉,重启网关即可。对于业务团队来说,他们感知不到任何变化。
3.2 同一外部名挂多个后端:路由、轮询与自动降级
litellm 还支持把一个model_name对应多个后端,这是实现负载均衡和故障转移的基础:
model_list: - model_name: chat litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY_1 - model_name: chat litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY_2同一个model_name配置了多个litellm_params,请求会自动在多个 key 之间轮询。如果某个 key 触发了上游限流,litellm 会尝试下一个 key。这在供应商有每分钟请求数限制时特别有用,相当于用多个账号把限流额度"拼"出来。
不过有一个使用上的注意点:不要把一个高成本模型和一个低成本模型挂在同一个外部名下。虽然技术上完全支持,但下游业务方无法预期每次调用的延迟和计费,出了问题很难解释。我一般只把同一型号、同一档位、价格差异很小的模型做聚合。
3.3 密钥管理机制:环境变量注入、多 key 轮询与权限隔离
密钥管理是很多团队接入 litellm 时的第一道坎,其实处理起来非常套路化。首先是密钥不能写死在配置文件里,litellm 支持从环境变量引用:
litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY这样 config.yaml 可以纳入代码仓库版本管理,真正的密钥只在部署环境的环境变量里。谁都能拉配置,但拿不到真实密钥。
其次是多 key 轮询,上游供应商一般会限制单个 key 的每分钟请求数,如果你有多个 key,直接多配几条,litellm 会自动做轮询分摊。我实测下来,两个 key 的限流上限基本是单 key 的两倍左右,效果非常直接。
最后是权限隔离。litellm 支持用 master key 管理整个网关,也支持为每个下游团队生成独立的虚拟 key。虚拟 key 的调用范围、模型白名单、预算上限都可以单独配置,这样就算某一个团队的 key 泄露了,影响也是可控的,不会牵连整个网关。
4. 生产级流量治理:负载均衡、限流、重试与预算控制
4.1 多 key 负载均衡是怎么工作的
上一节提到的多 key 配置,本质就是负载均衡。litellm 默认会按轮询方式调度多个相同model_name的条目。遇到 429 或上游 5xx 错误时,它会自动把故障的 key 临时摘除,把请求切换到其他健康的 key 上。
这里我踩过一个小坑:最开始我把两个 key 配置到了同一个上游,以为 litellm 会自动做健康探测,实际上它默认没有太复杂的健康检查机制。某个 key 如果被上游封禁,它只有在真实请求失败时才会感知并切换。所以我的建议是,除了依赖 litellm 的自愈能力,最好定期关注上游控制台里各 key 的状态,提前发现异常的 key。
4.2 限流参数配置:从上游限制到网关侧限制
接入 litellm 之后,限流可以分成两层理解。第一层是上游供应商的限制,第二层是网关本身对下游调用方的限制。第二层其实是很多团队迫切需要的,因为不可能让每个业务方都直接跟供应商控制台打交道。
在 config.yaml 里可以这样配:
model_list: - model_name: chat litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY rpm: 100 tpm: 50000rpm是每分钟允许的请求数,tpm是每分钟允许的 token 数。超过限制的请求会直接被网关拒绝并返回限流错误,不会继续打到上游,这对成本保护非常重要。我通常会把 rmp 设置为上游实际限制的 80% 左右,留出缓冲,避免触发供应商的惩罚性限流。
4.3 失败重试与供应商降级的策略设置
litellm 默认对部分错误码有重试机制,比如 429、503 这些临时性错误。重试的间隔和次数可以通过环境变量调整。这里要特别提醒一点:重试机制对成本敏感场景要谨慎。如果业务方主要做的是实时问答,重试次数太多会显著增加响应等待时间;如果上游故障持续时间较长,重试反而会堆积更多请求。
我自己的做法是:普通请求保留默认重试,最多重试 1-2 次;流式长任务不重试,因为用户已经看到一半输出,从头再来体验很差。同时结合降级策略——在 config 里配置多个后端时,litellm 可以对同一个model_name指定优先顺序,首选模型不可用时自动切换到备用模型。不过降级的前提是备用模型的响应质量可接受,这个需要业务方提前确认。
4.4 虚拟 key 与成本归属:账单不再是一笔糊涂账
多团队共用网关之后,成本归属是最容易引发矛盾的。litellm 的虚拟 key 机制很适合解决这类问题。通过管理 API 可以生成一个虚拟 key:
curl -X POST http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-master-xxx" \ -H "Content-Type: application/json" \ -d '{ "models": ["chat"], "max_budget": 100, "budget_duration": "30d" }'返回的key字段就是给下游团队用的虚拟 key。它能指定可以调用哪些模型、预算是多少、有效期多长。每次请求的 token 数和估算费用都会记录到数据库里。下游团队用了多少、还剩多少预算,一目了然。
需要说明的是,litellm 的费用是基于模型单价和 token 数估算的,不同供应商的计费规则有差异,所以这个费用适合做内部核算,不适合直接当对外账单的最终依据。但对内部成本分摊来说,已经足够精确了。
5. 客户端接入只改一个 base_url 的适配技巧
5.1 Python 客户端一行改动完成切换
litellm 最让我满意的就是客户端接入成本极低。如果你已经在用 OpenAI 官方 Python SDK,只需要改base_url和api_key,其他代码不用动:
from openai import OpenAI client = OpenAI( base_url="http://localhost:4000/v1", api_key="sk-master-xxx" ) response = client.chat.completions.create( model="chat", messages=[{"role": "user", "content": "Hello"}] ) print(response.choices[0].message.content)注意model参数填的已经不再是上游的真实模型名,而是 config.yaml 里的外部名chat。这个改动的意义是:业务代码与具体的模型供应商彻底解耦了。之后哪怕后端模型换掉,业务服务只需要重新部署一次环境变量或配置中心的值,代码完全不用动。
5.2 Node.js 和其他语言的接入方式
Node.js 生态用 OpenAI 官方包也是一样的逻辑:
import OpenAI from 'openai'; const client = new OpenAI({ baseURL: 'http://localhost:4000/v1', apiKey: 'sk-master-xxx', }); const completion = await client.chat.completions.create({ model: 'chat', messages: [{ role: 'user', content: 'Hello' }], }); console.log(completion.choices[0].message.content);其他语言只要实现了 OpenAI 兼容接口的 SDK,都可以用同样的方式接入。这其实是 litellm 的一个隐藏优势:它让"OpenAI 接口规范"成为你公司内部的模型调用标准,不管上游是什么,下游永远只面对一种接口风格。
5.3 非 OpenAI 供应商的响应统一:网关在背后做了什么
很多人在第一次使用时会好奇:上游根本不是 OpenAI 系的模型,为什么返回结构这么标准?原因在于 litellm 在网关内部做了两层翻译:下游发过来的 OpenAI 风格请求会被转换成上游供应商的请求格式;上游返回的结果又会被转换回 OpenAI 风格的响应。业务侧完全看不到中间的差异。
不过有一点还是要提醒:虽然 litellm 做了大量兼容工作,但个别供应商特有的参数(比如某些多模态输入格式、工具调用的细节)不一定在所有版本中都支持透传。切换模型时必须测试完整用例,不能只看一个简单的文本问答就认为没问题了。我一般会在测试环境跑一遍包含多轮对话、流式输出、工具调用等场景的回归脚本。
6. 实际生产环境里踩过的坑与调优建议
6.1 连接池与并发上限:默认配置扛不住峰值流量
我第一次对 litellm 做压测时,并发一上去就发现大量请求排队,单请求延迟飙升。当时第一反应是模型本身的性能问题,查了半天才发现是网关进程的连接池和 worker 数不够。litellm 默认配置适合低流量验证,不适合直接承受生产峰值。
解决办法分两层:一是启动时配置足够的--num_workers,二是调大上游连接池相关的环境变量。我的经验是先按预估峰值 qps 的 2 倍预留并发能力,再根据压测结果逐步收敛。压测时不要只看平均延迟,要看 p99 和队列积压情况,这两个指标才能反映网关的真实瓶颈。
6.2 流式输出与超时设置:不同场景要分开处理
流式输出踩坑是我记忆最深的。短问答场景用默认超时没问题,但换成流式长文本生成后,客户端经常报读超时。原因是很多 HTTP 客户端的默认读取超时对 SSE 流式响应并不适用,需要把客户端的 read timeout 调大。反过来,普通短请求如果超时设置得太长,会导致故障响应变慢,用户等半天才发现失败。
我的建议是给网关和客户端两侧都设置分级超时:普通请求 30 秒以内,流式请求按模型最大输出时长设置,比如 5 分钟。同时把超时错误和上游限流错误分开监控,两者反映的问题完全不一样。
6.3 日志与监控:看哪些指标判断网关健康度
litellm 的日志信息量非常大,每次请求会记录模型名、使用的 key、token 数、响应延迟、估算费用。这些日志对排查问题很有帮助。生产环境我建议把 metrics 接入 Prometheus 体系,重点看四个指标:
- p99 延迟:反映整个链路的真实体验。
- 429 错误比例:判断是否接近上游限流阈值。
- token 吞吐量:观察模型使用量的长期趋势。
- 虚拟 key 的预算消耗:提前发现预算超限风险。
日志和监控本质上是在回答同一个问题:"这个网关到底是在正常干活,还是在替上游扛锅?"有数据支撑后,跟供应商沟通限流问题、跟业务方对齐成本问题,都会更有底气。
6.4 什么时候其实不需要 litellm
最后说一个反直觉的观点:不是所有项目都适合上 litellm。如果你只有一个小应用、只用一个模型、只有一个 API key,也没有其他团队接入,那直接调 SDK 就足够了,完全没必要多加一层网关。引入网关意味着多一个需要维护的组件、多一层故障可能性,这是客观成本。
litellm 真正的价值在于"复杂度到了一定程度"之后的集中治理。至少 3 个以上模型、多个团队共用 key、预算成本归属混乱、或者正在准备切换供应商,这些诉求中出现两三个的时候,它才值得引入。如果只是尝鲜,建议先在自己的笔记本上跑通最小配置,感受一下"统一网关"带来的变化,再决定生产环境是否要上。
最后分享一个我自己的操作习惯:config.yaml 一定纳入版本管理,但所有密钥一律用os.environ/XXX引用;Docker 镜像固定具体版本,不追 latest;每次升级 litellm 之前,先看一遍变更记录,因为个别版本调整过数据库字段和路由策略。这个项目目前迭代力度很大,版本之间如果跨得太多,迁移成本可能会超出预期。掌握好这个小节奏,litellm 会是你长期管理多模型调用的得力帮手。