1. 为什么我会盯上 litellm:多模型接入的真实痛点
先说下我盯上这个项目的背景。我们团队在做一个内部 AI 助手,功能不复杂,本质上是把大模型能力接到几个业务场景里,比如知识库问答、信息抽取、内容生成。一开始一切都挺美好,因为只需要调一家模型。但等产品经理提出“想让用户自己选模型”之后,问题就来了:代码里开始出现 openai 库的调用、另一个同事用 anthropic 官方库、还有一个同学写的是 google 的 SDK,每个人都按自己的习惯写,消息结构、鉴权方式、错误码都不太一样。这个状态持续到月底,我们财务想按业务线拆分成本,才发现账单分散在几个不同的控制台,根本对不上。
这就是我最终决定引入 litellm 的原因。它是一个把各种大模型 API 统一成同一套调用格式的开源工具,既能当 Python SDK 用,也能部署成一个 API 网关服务。你可以继续写原来的 OpenAI 风格代码,但模型名一换,底层就自动切换到 Claude、Gemini、Azure OpenAI 或者其他兼容服务。这篇文章我不打算复述官方文档,而是从实际落地角度,把“为什么值得用”“怎么配置”“有哪些坑”完整梳理一遍。适合正在做 AI 应用开发的后端、算法、SRE 同学参考,也适合产品团队想快速验证多模型方案的场景。
1.1 三套 SDK、三种计费:新手团队最容易踩的坑
很多团队刚开始接大模型时,心态都是“先用最顺手的”,但多接两家之后,维护成本会迅速超过预期。先说格式差异。OpenAI 的消息结构是messages数组,角色分 system、user、assistant;Anthropic 的消息结构虽然相似,但 system 指令要单独放在顶层参数里,不能混在 messages 里;Gemini 的接口又是另一套,请求里有contents、parts这种字段。如果你只做一次对接,这些都是小问题,可一旦要做多模型切换、A/B 对比、模型兜底,这些差异就会变成一层又一层的 if-else。
计费差异更隐蔽。各家模型的 token 计算规则不一样,有的按字符、有的按词元,同一段中文在不同模型下计费差异可能很大。如果团队里每个人各自记录成本,月底汇总时会出现大量口径不一致的数据,根本没法给管理层一个清晰的数字。密钥管理也是麻烦事:多个模型对应多个 key,散落在环境变量、配置文件甚至前端代码里,安全风险不说,光是在不同环境里维护这些变量就已经很烦人。
我们当时就是在这种状态下意识到,缺的不是“再封装一层函数”,而是一个真正能承载路由、重试、鉴权、成本记录的中间层。这个中间层需要让业务代码保持稳定,无论上游模型怎么换,调用方的代码都不需要跟着变。
1.2 litellm 的设计思路:把“多模型”伪装成“一套接口”
litellm 的核心思路可以类比成 USB-C 接口。如今很多设备都统一用 USB-C 充电,但你插到不同充电器上,内部协议可能是 Power Delivery 或者 Qualcomm Quick Charge,只是这些细节被适配器消化掉了。litellm 做的就是这件事:对外,你只能看到 OpenAI 风格的接口,model、messages、temperature这些参数都是你熟悉的写法;对内,它负责把请求转换成对应厂商真正需要的格式,并把返回结果再转换成统一结构交还给你。
为什么选择 OpenAI 格式作为统一标准,而不是另创一个格式?最主要的原因是生态普及度。大多数模型服务商要么原生兼容 OpenAI 格式,要么提供兼容端点,开发者最熟悉的也是这套写法。litellm 内部也把 OpenAI 格式当作中间表示:请求进来先规范化,根据 model 字符串判断该路由到哪家上游,再由适配器完成字段映射、密钥注入、错误码转换。这套设计的直接好处是,业务代码的迁移成本极低,你甚至不需要换 SDK。
你可能会担心格式转换会不会带来明显的性能损耗。从我实测来看,这种转换主要是内存里的字段映射,单次请求的额外耗时在毫秒级别,相比模型推理本身动辄几秒的耗时,完全可以忽略。真正需要花心思的地方,反而是后续的配置细节,比如路由权重、重试策略、预算控制。这些我放到第三章详细说。
2. litellm 两种使用形态:SDK 直连与 API 网关
litellm 最容易被新用户忽略的一点是:它不只是个 Python 包,还能作为一个独立服务跑起来。这两种形态解决的是完全不同的问题。如果你只是自己写脚本、做实验,SDK 模式足够;如果你的团队有多个服务、多个人、多套密钥,网关模式才是真正能改变协作方式的东西。
2.1 SDK 模式:五分钟把 OpenAI 调用切到 Gemini
SDK 模式最直观。先安装库,然后直接写代码调用。默认情况下,你用 OpenAI 格式传入消息,模型名决定走哪条上游通道:
from litellm import completion response = completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], ) print(response.choices[0].message.content)想换成 Claude,只需要改 model 字符串:
response = completion( model="anthropic/claude-3-5-sonnet-20241022", messages=[{"role": "user", "content": "你好"}], )换成 Gemini 也一样,加gemini/前缀:
response = completion( model="gemini/gemini-1.5-pro", messages=[{"role": "user", "content": "你好"}], )这里有个关键约定:model 字符串里/前面是供应商前缀,后面是真实模型名。不带前缀时,litellm 默认按 OpenAI 兼容接口处理。也就是说,如果你公司内网有私有化部署的 OpenAI 兼容服务,直接写model="your-model-name"再加上api_base就能接进来。
实测下来,SDK 模式对个人开发者非常友好。你不写api_key也能跑,litellm 会自动读取对应厂商的环境变量,比如ANTHROPIC_API_KEY、GEMINI_API_KEY。我通常是在做模型效果对比时用它,写一个小脚本轮着切换模型,比在多个官方 SDK 之间来回切舒服多了。
2.2 网关模式:一套密钥、一套地址,全团队零改动接入
SDK 模式虽方便,但解决不了团队协作的问题。你想一想这个场景:前端同学只会在浏览器里调接口,后端有 Python 服务也有 Node 服务,每个人都要各自维护 API key,可能还会有人把 key 直接写进前端代码里。这时候把 litellm 部署成一个网关服务,就能把所有模型请求收敛到一个出入口。
网关模式对外暴露的是 OpenAI 兼容接口,也就是说你不需要在 Node 或其他语言里安装 litellm 专属 SDK,直接用 openai 官方客户端把 base_url 指到网关地址就行。这样做的好处非常实际:
第一,模型密钥不会散落到各个服务里。网关里统一管理上游密钥,调用方只需要拿到一个“网关 Key”,权限和额度都由网关控制。第二,不同语言栈的团队零成本接入,反正都是标准 OpenAI 风格接口。第三,所有请求都会经过网关,天然形成了统一的日志、限流、预算审核点。出了问题时,你不需要翻遍所有服务的日志,只查网关日志就够了。
我第一次用网关模式时也有点顾虑,觉得“多了一层服务会不会反而复杂”。但实际跑起来之后,这个顾虑很快消失了。它就是一个小小的 FastAPI 服务,部署成本不高,却把所有模型切换动作收敛成了修改配置文件。团队里某天决定把默认模型从 A 换成 B,只需要改配置,业务代码完全不动。
3. 核心配置拆解:模型路由、负载均衡与重试机制
litellm 入门容易,但真正用好要理解几个高级配置。很多人在网上看到“统一多模型调用”的截图就以为这只是个格式转换器,实际上它的价值在路由、负载均衡、重试、限流这些网关能力上。这些配置决定了你的系统在真实流量下是否可靠。
3.1 model_list 配置与权重路由到底怎么填
网关模式的一切核心都在一个 YAML 配置文件里。刚开始接入时,建议先把每个模型一条条列清楚。举个例子,下面是三个模型的配置:
model_list: - model_name: gpt-4o litellm_params: model: gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: claude-sonnet litellm_params: model: anthropic/claude-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY - model_name: gemini-pro litellm_params: model: gemini/gemini-1.5-pro api_key: os.environ/GEMINI_API_KEY这里最容易理解错的是model_name和litellm_params.model的区别。model_name是暴露给调用方的逻辑名,只在你自己的系统内有意义;litellm_params.model才是上游真实的模型标识。这个设计非常有用:以后想把“claude-sonnet”对应的上游换成别家的模型,调用方不需要改任何代码,只需要调整这条配置把它指向新模型。
如果需要做负载均衡,可以在同一逻辑名下配置多个上游,并给它们分配权重:
model_list: - model_name: my-gpt litellm_params: model: gpt-4o model_info: weight: 3 - model_name: my-azure litellm_params: model: azure/gpt-4o api_base: https://your-resource.openai.azure.com/ model_info: weight: 1这里的 weight 是相对权重,含义是每 4 次请求里大约 3 次走gpt-4o、1 次走 Azure 上的gpt-4o。如果某个模型经常被上游限流,你还可以开启健康检查,litellm 会定期探测每个上游节点的可用性,自动把不健康的实例摘除。对生产环境来说,这个能力比手工切换可靠得多。
3.2 重试策略:什么时候该重试,什么时候该直接报错
重试策略是很多新手忽略的细节。默认情况下 litellm 有基础重试机制,但生产环境里你需要显式定义哪些错误值得重试、哪些错误重试也没用。我用 Router 时的典型配置是:
from litellm import Router router = Router( model_list=model_list, retry_policy={ "429": 3, "500": 2, "503": 2, "400": 0, }, num_retries=2, timeout=60, ) response = router.completion(model="my-gpt", messages=[...])网关模式下,这些参数写在 router_settings 里,效果一样。为什么 429 值得重试?因为上游返回这个状态码通常意味着“你打得太快,但还有配额”,它可能带着 Retry-After 告诉你多久后重试。为什么 400 不值得重试?因为 400 表示请求本身有问题,比如消息格式错误、模型名不存在、参数不合法,重复发送只会重复浪费。5xx 这类错误则要保守处理,可以重试一两次,但次数太多反而会加重上游压力。
还有一个经验是设置合理的 timeout。大模型请求和其他 API 请求不同,输出长文本时可能持续几十秒甚至超过一分钟。如果 timeout 设置得太短,会出现一种很迷惑的现象:上游明明在正常处理,客户端却先超时断开了。我的习惯是普通问答给 60 秒,需要输出长文档或做 Agent 类任务时至少给到 300 秒。
3.3 成本追踪与按 Key 限流:让每一分钱都有去处
成本追踪是我认为 litellm 最被低估的功能。很多团队接多模型时只关注“能不能调通”,却忽视了“每个业务线花了多少钱”。用网关模式后,可以用主 Key 生成多个子 Key,并按业务线分配预算和速率限制。
比如我们给“搜索增强”这个模块单独生成一个 Key:
curl http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-master-key" \ -H "Content-Type: application/json" \ -d '{ "max_budget": 50, "budget_duration": "30d", "rpm_limit": 100, "tpm_limit": 50000, "metadata": {"team": "search"} }'rpm_limit 是每分钟请求数,tpm_limit 是每分钟 token 数。返回的 Key 下发给对应团队使用,一旦超额,网关会直接拒绝并返回 429。这样一来,月底对账就变成了按 Key 维度汇总,谁用了多少、哪个模型烧钱多,一目了然。
成本日志默认可以打到 PostgreSQL 等数据库里,网关会记录每次请求的模型、token 计数、估算费用、Key 标识和时间戳。需要注意,litellm 的费用是基于 token 数估算的,与实际账单之间可能有一定偏差,尤其是中文、代码混合这类复杂文本。我的建议是把它当作“相对趋势和异常发现”的工具,最终结算还是以各家服务商控制台的账单为准。
4. 动手实操:从零部署一个 litellm 网关服务
前面说的都是概念,这一章我给出一套可以直接照着做的实操流程。整个过程中你会在命令行里看到大量输出,别慌,很多是正常日志。
4.1 安装与最小配置:先把网关跑起来
首先安装基础包:
pip install litellm网关模式需要额外的扩展依赖,具体安装方式以官方 README 为准,不同版本可能略有区别。安装完成后,准备一个最小配置文件。我强烈建议密钥不要直接写在 YAML 里,而是用环境变量引用:
model_list: - model_name: gpt-4o litellm_params: model: gpt-4o api_key: os.environ/OPENAI_API_KEY然后在终端设置环境变量:
export OPENAI_API_KEY="sk-your-key"启动网关,指定端口和主 Key:
litellm --config config.yaml --port 4000 --master_key sk-master-key看到类似Application startup complete的日志后就说明服务起来了。这时可以访问http://localhost:4000/health检查健康状态,或访问http://localhost:4000/v1/models查看已注册的模型列表。如果你不想污染本机环境,官方也提供 Docker 镜像,原理上就是把配置文件和密钥注入容器,然后映射端口,适合团队内部统一部署。
4.2 让客户端切换到网关:只需要改一个 base_url
网关起来之后,客户端代码几乎是零改动。以 Python 的 openai 官方 SDK 为例:
from openai import OpenAI client = OpenAI( base_url="http://localhost:4000", api_key="sk-any-virtual-key", ) response = client.chat.completions.create( model="claude-sonnet", messages=[{"role": "user", "content": "介绍一下你自己"}], ) print(response.choices[0].message.content)这里有两个关键点。第一,base_url指向你的网关,而不是官方地址。第二,model传的是配置文件里定义的逻辑名claude-sonnet,而不是上游真实模型名。客户端只需要知道这两个信息,剩余的事情全部由网关完成。
在生产环境里,不要直接把master_key给所有调用方,而是应该用前面说过的/key/generate生成子 Key。你可以为每个服务生成独立 Key,这样即使某个 Key 泄露了,也只是影响对应服务,不会泄露全局配置。
4.3 用 curl 验证路由与检查响应头
有时需要快速验证网关是否正常工作,直接 curl 一下是最快的:
curl http://localhost:4000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-master-key" \ -d '{ "model": "claude-sonnet", "messages": [{"role": "user", "content": "Hello"}] }'看到正常的 completion 响应后,记得加-i参数再看一眼响应头。litellm 会在响应头里写入一些非常有用的调试信息,比如实际的模型 ID、模型版本、这次请求的估算费用、使用的 Key 别名。我排查问题时经常靠这些响应头确认“请求到底有没有走我预期的上游”。
5. 常见问题与排查技巧实录
这一章是我真正在项目中踩过、帮别人排查过的坑汇总。很多问题在官方文档里看着很明白,实际遇到时却容易绕圈子。
5.1 模型名写错与密钥失效的迷惑表现
模型名写错最常见的现象是返回 404,错误信息类似Unknown model。第一次遇到时,我一度以为是网关坏了,后来才发现是在 SDK 模式下少写了供应商前缀。比如claude-3-5-sonnet-20241022如果不加anthropic/,litellm 会默认把它当成 OpenAI 兼容模型去查,自然查不到。在网关模式下,你要调用的是model_list里定义的model_name,而不是上游模型真名,这种情况还会出现在有人直接拿上游模型名来请求网关的时候。
密钥失效的典型表现是 401 或AuthenticationError。排查顺序其实非常简单:先确认环境变量是否已经加载,再确认配置文件里引用变量名的写法是否正确,最后看日志。litellm 在调试时的日志非常详细,会明确告诉你它实际请求的上游地址和使用的模型字符串。我建议遇到问题时先打开详细日志,不要对着配置瞎猜。日志里一句话,往往比你看半小时配置更有效。
5.2 负载均衡不生效、重试重复扣费这类坑
负载均衡不生效的原因有三个,每一个我都见过。第一,model_list里同一个model_name只配置了一个上游,这种情况当然不会出现路由。第二,调用方请求的模型名与配置里的model_name不一致,请求只会命中一个上游。第三,虽然配置了多个上游,但某个上游被健康检查标记为不健康,流量自然只走另一个,从日志看好像是“负载均衡没生效”,其实是健康的“均衡”。
重试重复扣费是个更隐蔽的问题。当一个请求发出后,上游已经完成推理但在返回结果时网络断开,客户端重试同一个请求,上游实际上被调用了两次,账单上就多了一笔费用。这个问题不是 litellm 特有的,任何重试机制都可能会遇到。如果你对成本非常敏感,我建议在业务层做请求幂等处理,或者至少知道这个风险存在,不要在自动重试上设置过高的次数。
429 重试仍然失败也很常见。很多人一看到 429 就想着无限重试,这是错误的。429 代表上游配额暂时用完或速率超限,一味增加重试次数只会让自己更早被打进“黑名单”。正确的做法是查看上游返回的 Retry-After 响应头,把重试间隔调大,或者优化请求频率。
5.3 常见错误码速查表
| 现象 | 常见原因 | 排查与处理 |
|---|---|---|
| 401 AuthenticationError | 上游密钥无效或未注入 | 检查环境变量、配置文件里的 os.environ 引用 |
| 404 Unknown model | 模型名或逻辑名不对 | 查看 /v1/models 列表,SDK 模式检查供应商前缀 |
| 429 RateLimitError | 上游限流或子 Key 超配额 | 调大重试间隔,检查 Key 的 rpm/tpm 设置 |
| 5xx Server Error | 上游不稳定或网关配置问题 | 查看上游状态,做有限次数重试 |
| 400 BadRequest | 消息格式或参数不被上游支持 | 按上游返回的错误修正消息结构 |
| 客户端超时 | timeout 设置过短或上游响应慢 | 调大 timeout,或改成异步任务 |
| 响应内容为空 | 流式处理或上下文过长 | 检查是否开启流式,截断过长上下文 |
这张表不一定覆盖所有场景,但对排查 90% 的新手问题都有帮助。凡是出现“看似网络问题、实则是配置问题”的情况,先按表里对应行检查,基本不会跑偏。
6. 我的项目落地建议:什么场景下该用 litellm
写到最后,聊聊我的落地建议。任何工具都有它的适用边界,litellm 也不是万能的。判断“该不该用”比“怎么用”更重要。
6.1 适合与不适合的几种场景
如果你的团队同时满足以下任意两条,我认为就很值得尝试。第一,多个服务或多个语言栈都在调大模型。第二,产品需要给用户提供“自选模型”的功能,或经常切换默认模型。第三,需要对不同业务线做成本拆分和限流。第四,团队成员对各家 SDK 不熟悉,想统一优化调用路径。
反过来,如果项目只是个人实验、只调用一家固定模型,那用 litellm 确实有点杀鸡用牛刀,直接厂商 SDK 反而更直接。如果业务对请求链路极度敏感,比如每个请求都要精确控制经过的每一跳服务,那么额外引入一个常驻网关服务也需要评估,毕竟多一个组件就多一个运维点。
6.2 从一个小模块开始:渐进式引入的正确姿势
我第一次落地时,并没有把核心业务直接切过去,而是先挑了一个非核心的“标题生成”功能做试点。原因是这个功能对延迟不敏感,模型好坏也容易人工判断。跑了一周之后,确认了 cost 估算的误差范围、重试策略是否符合预期、日志是否能满足对账需求,然后才逐步把其他服务迁移进来。
我个人体会是:litellm 的价值不在于“把代码写得多优雅”,而在于它把模型切换从“改代码、改发版”变成了“改配置、热更新”。如果你只是把它当格式转换器,那确实感受不到多大好处;但一旦你在网关层面接入了预算控制、按 Key 限流、统一的成本日志,整个团队的协作方式都会变得清爽很多。
最后分享一个小技巧:上了网关之后,建议把每个业务线的 Key 元数据写清楚,比如负责人、业务线、提醒邮箱。等月底看到某个 Key 消耗异常时,你能立刻定位到是谁的、该找谁。这个细节不复杂,但能帮你少一次半夜被财务拉群质问的经历。