☰
统一多模型接入实战:用 litellm 搞定 API 适配、密钥管理与成本治理
2026/10/14 8:30:24 网站建设 项目流程

这两年做 AI 应用开发,我最大的一个体会就是:换模型,不是改个名字那么简单。不同厂商的鉴权方式、请求体结构、流式格式、返回字段全都不一样,代码里只要换一家服务商,就要跟着改一堆接口适配逻辑。最夸张的一次,我只是想从一个通用模型切到另一个更便宜的模型,光适配协议就花了两天。后来我把 litellm 引入项目,这是目前我用下来最省事的统一多模型接入方案。它做两件事:一个统一接口的 Python SDK,外加一个可以独立部署的 API 网关。前者让业务代码里不用再塞满各家服务的私有 SDK,后者让团队里其他人根本不需要关心模型来自哪个厂商。这篇文章不打算写成官方文档的翻译,只聊我怎么用它、怎么部署、以及真实跑起来之后踩过哪些坑。适合正在做 AI 应用集成、被多模型调用折腾得头疼、又需要给团队做成本和密钥管理的开发者。

1. 接口碎片化到什么程度,litellm 才会显得这么香

1.1 三个让我崩溃的协作场景

先说第一个场景。算法那边想比较各家模型的回答质量,于是把三个不同服务商的模型都调一遍。结果同样的temperature参数,在 A 服务里生效,在 B 服务里被叫做别的名字,在 C 服务里直接不接受。我当时写了一个又一个if provider == xxx的分支,把同一个业务参数翻译成不同服务的格式,整个代码变得又臭又长。这种适配代码没有技术含量,但就是费时间,而且每次新接入一家,都要把人家的文档从头翻一遍。

第二个场景是密钥管理。服务一多,API Key 也多,散落在不同配置文件、环境变量和同事的聊天记录里。谁用了哪个 Key、被调了多少次、花了多少钱,完全没有概念。后来我干脆把 Key 统一放到一个配置中心,但这样又带来一个新问题:每个服务拿到配置之后,Key 等于对所有代码可见,泄漏风险非常高。

第三个场景是成本统计。不同服务返回的 token 计费方式不一样,我想按月统计每个业务线花了多少钱,只能手动去各家控制台导出账单,再用 Excel 交叉比对。一旦某个业务同时用了两三个模型,这笔账根本算不清楚。

litellm 解决的就是这三件事。它对外提供了一个统一的 Python 函数,把模型名和消息传进去,剩下的请求翻译、鉴权拼接、响应格式化都由它内部完成。如果团队规模再大一点,它就变身成一个网关服务,所有 HTTP 请求先进到网关,由网关统一路由到上游模型,顺便把 Key 和预算管起来。

1.2 统一接口的底层逻辑

你可能会好奇,它凭什么能把这么多家的接口统一起来?原理并不神秘。litellm 内部维护了一张映射表,记录了各家服务的请求格式、鉴权方式和响应字段结构。当你调用completion(model="provider_a/text-001", messages=[...])的时候,它先解析出provider_a这个前缀,然后根据服务商规则,把统一参数改写成对方期望的 JSON 结构,再附带正确的鉴权头发出请求。收到响应后,又反向把各家五花八门的返回格式还原成统一的choices和usage结构。

这个过程你可以把它理解成一个“万能插头转换器”。原来每个国家的插座形状都不一样,你出差得带一堆转接头,现在 litellm 就是那个把各种插孔都统一成同一个标准接口的适配器。重点是这个适配器还内置了多口排插的功能,也就是网关模式。某个上游挂了,它会自动尝试另一个;某个 Key 超预算了,它直接拒绝放行。对我来说,这才是它区别于普通 SDK 封装的最大价值。

2. 动手之前先分清楚:SDK 直连和网关代理到底给谁用

2.1 两种模式的能力边界

很多第一次接触 litellm 的同学容易懵,因为它有两种使用方式。第一种是 SDK 模式,就是直接把litellm作为 Python 依赖装进自己的项目里,在代码里调用统一函数。第二种是代理网关模式,把 litellm 启动成一个独立进程,对外暴露 HTTP 接口,业务方像调用普通模型服务一样调用它。两种方式不能随便混用,先想清楚自己处在什么阶段。

我画了一张对比表,方便你判断:

对比项SDK 直连模式代理网关模式
部署形态作为 Python 包嵌入业务进程独立服务,暴露 HTTP 端口
调用方式completion()或acompletion()标准 REST 请求/v1/chat/completions
密钥管理业务方直接持有上游 Key上游 Key 只在网关侧,业务方拿虚拟 Key
成本统计可以拿返回值自己记账网关统一记日志,按 Key/用户/模型拆分
路由与限流通过 Router 类在代码里做在网关配置里做,全局生效
适合场景个人脚本、单个服务、快速原型多团队共享、多模型切换、治理需求高

你会发现,SDK 模式其实也支持路由和 fallback,但它只管自己这个进程。公司里只要有多个服务、多个人在调模型,各自 SDK 里的配置就很容易漂移,A 服务改了一个模型,B 服务还在用旧的。网关模式最大的好处是治理逻辑集中了,大家在同一个配置源下工作。

2.2 我的选择标准

我自己的习惯是:个人写脚本、跑实验、做一次性任务,直接用 SDK 模式,轻量、没负担。代码里大概也就几十行,不需要为一个定时任务单独起服务。可一旦事情变成“别人也要来调用”,或者“模型选择要经常调”,我就会第一时间上网关。

判断的节点很简单:有没有第二个人来问你“模型地址是多少”?有没有出现“你改了模型但我这边没用上”的沟通成本?有没有人找你要一个能限制额度的 Key?只要命中其中一条,就该考虑网关。网关并不是越早越好,因为它多了一层网络跳转,多一个进程要运维。但等到模型调用变成团队基础设施级别的时候,它带来的收益远大于那点额外部署成本。

3. 从安装到首发:完整跑通的最小闭环

3.1 安装与准备

安装没什么特殊之处。Python 版本建议至少 3.10 以上,然后用 pip 安装。我一般直接装带网关功能的版本,避免后面想上代理模式又发现少了依赖:

pip install "litellm[proxy]"

接下来是准备上游模型服务的 API Key。litellm 默认会从环境变量读 Key,所以我的习惯是用一个.env文件统一管理。针对本文里虚拟的服务商,我会这样写:

PROVIDER_A_KEY=你的A服务密钥 PROVIDER_B_KEY=你的B服务密钥 LITELLM_MASTER_KEY=网关主密钥

注意,.env文件一定要加进.gitignore,否则密钥提交到代码仓库里,后面所有麻烦都是自找的。litellm 也支持在配置里写死 Key,但我强烈不推荐,除非是本地五分钟的验证。生产环境请坚持使用环境变量或者密钥管理服务。

3.2 Python SDK 最小调用

先看最核心的 SDK 用法。把模型名按服务商前缀/模型名的格式传给model参数:

import litellm response = litellm.completion( model="provider_a/text-001", messages=[ {"role": "user", "content": "你好,帮我写一份周报模板"} ], temperature=0.3, ) print(response.choices[0].message.content)

这段代码里,唯一需要注意的就是model参数的写法。provider_a是 litellm 认识的服务商前缀,text-001是你在对应服务商那边的真实模型标识。如果前缀写错,litellm 会直接报不认识,这和很多封装库的处理方式不太一样,它不会默默试错。

异步场景下,把completion换成acompletion,前面加await:

import litellm async def main(): response = await litellm.acompletion( model="provider_a/text-001", messages=[{"role": "user", "content": "你好"}], ) print(response.choices[0].message.content)

这个改法在高并发 Web 服务里非常关键。很多人图省事,在异步框架里直接调用同步版completion,结果把事件循环卡死,QPS 稍微上来一点整个服务就变慢。后面我会专门再讲这个坑。

3.3 启动代理网关

SDK 验证通过之后,再来跑网关。先写一个最简配置文件config.yaml:

model_list: - model_name: text-001 litellm_params: model: provider_a/text-001 api_key: os.environ/PROVIDER_A_KEY

这个配置的意思是:对外暴露的模型名叫text-001,实际由provider_a/text-001处理,Key 从环境变量读取。启动命令也非常简单:

litellm --config config.yaml --port 4000

启动成功之后,litellm 会打印一行日志告诉你网关已经监听在 4000 端口。如果你是第一次跑,建议加一个环境变量把详细日志打开:

LITELLM_LOG=DEBUG litellm --config config.yaml --port 4000

这样请求进来时能看到完整的转发详情,排查问题会方便很多。不过调通之后记得关掉 DEBUG,不然高并发下日志量能把磁盘打满。

3.4 走 HTTP 接口调用

网关起来之后,业务方就不再需要安装 litellm 了。用命令行也可以用通用 HTTP 客户端,直接请求网关:

curl http://localhost:4000/v1/chat/completions \ -H "Authorization: Bearer $LITELLM_MASTER_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "text-001", "messages": [{"role": "user", "content": "你好"}] }'

返回结构和标准模型接口几乎一致,choices[0].message.content就是模型回复。这套接口也意味着,以前基于标准格式写的客户端 SDK,只要把base_url指到http://localhost:4000,模型名改成text-001,几乎不用改其他代码就能无缝迁移。

网关模式下,对外暴露的model_name和内部真实模型名是解耦的。你在配置里把它改成text-beta、fine-tuned-v2都行,上游在变,业务方的代码却可以一直不动。这也是我后面所有团队治理功能的地基。

4. 路由、密钥和预算:团队接入时真正值钱的地方

4.1 多上游路由和 failover 配置

单模型接入只是开胃菜,团队场景下的核心需求是:同一个model_name背后挂多个上游,一个挂了立刻切换,一个太贵了就分流到另一个。litellm 的 Router 对象就是干这个的。假如我有两个虚拟上游provider_a和provider_b,配置可以这样写:

from litellm import Router model_list = [ { "model_name": "text-001", "litellm_params": { "model": "provider_a/text-001", "api_key": "替换成A的Key", }, }, { "model_name": "text-001", "litellm_params": { "model": "provider_b/text-001", "api_key": "替换成B的Key", }, }, ] router = Router(model_list=model_list) response = router.completion( model="text-001", messages=[{"role": "user", "content": "你好"}], fallbacks=[{"model": "provider_b/text-001"}], )

同一个model_name有两个litellm_params,Router 会把它当成同一个模型的多个部署,默认采用简单轮询或按使用情况路由。fallbacks参数表示:如果主用模型失败,就降级到备用的provider_b/text-001。我在生产环境里就靠这个配置解决过一次很现实的问题:一个上游服务商下午官方限流,请求大面积超时,网关自动把流量切到另一个,业务侧完全无感。

这里要提醒一句:fallback 不是越多越好。曾经有同事为了“稳定”,挂了五个备胎,结果某个上游抖动时,所有请求都在超时——重试——切换——再超时的循环里打转,最终雪崩比单点故障还严重。合理做法是保留一到两个质量可靠、并且和主模型能力接近的备选。

4.2 virtual key 与预算控制

团队一旦多起来,直接发上游 Key 就是不负责的行为。上游 Key 权限太大,一个人超预算,整个账单都兜不住;而且只要 Key 出现在某个同事的本地环境里,泄漏风险就不可控。litellm 网关内置的虚拟 Key 机制可以解决这件事。

启动网关时配置了LITELLM_MASTER_KEY,这个就是超级 Key,可以调用管理接口。用它生成一个虚拟 Key:

curl -X POST "http://localhost:4000/key/generate" \ -H "Authorization: Bearer $LITELLM_MASTER_KEY" \ -H "Content-Type: application/json" \ -d '{ "user_id": "team-a-sre", "models": ["text-001"], "budget": 50, "budget_duration": "30d" }'

返回结果里会拿到一个虚拟 Key。之后团队内所有请求都带这个虚拟 Key,而不是真正的上游 Key。网关收到请求后,用自己的上游 Key 去转发。虚拟 Key 还能绑定模型白名单和预算,超过 50 刀后网关直接拒绝请求,不会等月底账单一来才傻眼。

我后来把这个流程接到一个内部申请页面上,同事填单、自动审批、自动生成虚拟 Key、到预算自动禁用。整个流程里,真实的云账号 Key 永远只存在于网关所在的环境里,连我都不需要每次手动复制。

4.3 日志与可观测

团队治理还少不了一个东西:日志。litellm 网关可以把每次请求的 token 数、花销、耗时、虚拟 Key、模型名都记录下来。配置里加一个数据库地址,数据就会落库:

general_settings: database_url: 你的数据库连接串 master_key: os.environ/LITELLM_MASTER_KEY

只要网关启动时能连上数据库,后续每次调用就会自动写入一条请求记录。之后想翻某段时间、某个虚拟 Key 调了多少次、花了多少钱,通过网关的管理接口就能查。

我不建议把数据库连接串写死在配置文件里,来源换成环境变量更安全。数据库选择可以根据团队已有的基础设施来,只要能提供 SQL 连接串,litellm 都能适配。如果暂时没有数据库,它也会在内存里存日志,但重启就丢了。生产环境必须落库,否则出问题时连个回溯依据都没有。

5. 真实项目中我连续踩过的几个坑,以及我怎么按平的

5.1 401 不一定是你 Key 错了

第一次在网关里配好虚拟上游之后,我调接口疯狂报 401。当时第一反应是 Key 配错了,来回检查了十遍,甚至重新生成了上游 Key,问题依旧。最后打开LITELLM_LOG=DEBUG才看到,litellm 构造请求时读取的 Key 是空的——因为我把它写进了config.yaml,但api_key: os.environ/PROVIDER_A_KEY这个写法在网关进程里并没有正确继承 Shell 的.env环境变量。

解决办法其实很简单:在启动命令前用export把变量加载好,或者让 systemd 之类的进程管理器负责注入环境变量。后来我养成了一个习惯,任何 Key 相关的问题,第一步永远是先确认进程实际环境变量里有没有这个 Key,而不是盯着配置文件怀疑人生。

5.2 长请求秒断:timeout 设置问题

又一个非常隐蔽的坑。早期我用网关跑一个长文本生成任务,几百行的输出,差不多每次都在大概 60 秒附近断掉。一开始以为是上游不稳定,后来才发现是 litellm 的默认超时时间不够长。模型生成速度和网络带宽都有波动,一旦超过默认超时阈值,网关就会主动断开。

处理方式是在litellm_settings里统一调大超时时间:

litellm_settings: timeout: 600 max_retries: 2

这招对长上下文总结、几十秒才能出一段的长文生成特别有效。但同时要注意,超时调太大,上游真的挂了时,前端要等很久才感知到。我一般把超时设成合理的业务接受上限,同时在客户端侧也配一个比网关略小的超时,让用户体验更可控。

5.3 重试引发的连锁雪崩

litellm 本身带重试能力,这个能力用不好就是灾难。有一次上游服务出现性能问题,单次请求耗时从 1 秒涨到 10 秒,网关这边每个请求都在等超时,然后自动重试第二次、第三次。本来是 100 个并发请求,实际上给上游造成了 300 倍的请求压力,整个服务彻底打挂。

后来我限制了重试次数,并严格控制 fallback 的目标数量:

litellm_settings: max_retries: 1 request_timeout: 30

重试这个事,不要迷信“多试几次更稳”。所有模型的 API 都受速率支配,上游一旦过载,重试只会加重过载。正确的思路是限流、降级、熔断,而不是无限重试。litellm 能帮你做重试,但次数一定要小,并且要给每次重试配上适当的退避策略。

5.4 异步框架里别用同步函数

这是一个非常常见的性能杀手。我在一个异步 Web 服务里接 litellm,刚开始用的是同步版completion,压测到几十并发的时候,响应时间开始指数级上升。原因很简单:同步函数会阻塞事件循环,原本应该并发的请求全被排队了。

改成异步版之后,同样的机器和压测条件,QPS 翻了接近三倍:

response = await litellm.acompletion( model="text-001", messages=[{"role": "user", "content": "你好"}], )

请记住一个判断原则:如果你所在的项目是 FastAPI、aiogram 这类基于async的框架,默认就不要用同步版调用。除非你有专门的线程池去跑,否则 event loop 里出现同步 IO 调用,就是对性能的浪费。

5.5 配置文件改了不生效

最后说一个运维层面的坑。我一度以为改了config.yaml后网关会自动重新加载,结果发现改了模型列表之后,新请求还在走旧配置。后来才确认,litellm 的 gateway 不是所有版本都带配置热更新,很多场景下改配置就是需要重启进程。

实操建议是:不要在生产环境手动改配置。把config.yaml放进容器镜像,通过部署流程滚动更新;每次更新前先在测试环境启动一次,确认模型列表能正确加载。如果实在接受不了重启带来的短暂中断,可以搭建多网关节点,前面挂负载均衡,逐个节点更新。基础设施层面麻烦点,但换来的是配置变更的完全可控。

6. 成本数据不等于账单:litellm 的成本追踪机制能信多少

6.1 cost 是怎么算出来的

litellm 在做每次请求时,会拿到返回的usage数据,里面有 prompt 的 token 数和生成的 token 数。它再根据你使用的模型单价,把这些 token 数换算成金额。对于它内置的那些常见模型,单价表是现成的;对于自定义模型,需要在配置里手动补上价格信息:

model_list: - model_name: custom-001 litellm_params: model: provider_a/custom-001 input_cost_per_token: 0.000002 output_cost_per_token: 0.000006

这里的单位是“美元每 token”。如果你习惯用每百万 token 多少美元来计价,换算成单 token 价格时记得除以一百万。写错一位小数,成本统计就会偏得离谱。

6.2 它和实际账单的偏差来源

litellm 的成本统计更适合观察趋势,而不是直接对账。因为它用的是简单 token 单价模型,很多云厂商账单里的免费额度、批量折扣、cache 命中优惠、特殊时段计费,它都很难完全模拟。我见过有团队拿这个数字跟厂商账单比,差了一截,于是怀疑 litellm 算错了,其实两边统计口径压根不一样。

我的做法是:把它当成内部成本的三级仪表盘。第一级,按虚拟 Key 看每个业务线的调用量级,用它可以发现异常暴涨;第二级,按模型看不同模型切换后的成本变化;第三级,月底拿厂商正式账单做一次校准,找出偏差比较大的模型,再去补配置里的单价细节。

这套机制跑得久了我反而觉得,成本统计准确到小数点后几位不是最重要的,重要的是变化可观测。今天比昨天多花了 3 倍钱,一定是有原因的,litellm 能让我快速定位到是哪个 Key、哪个模型、哪个时间段在烧钱,这就够了。

6.3 给后来者的维护建议

最后再分享一点个人经验。litellm 整个体系里,最需要你长期维护的其实是那份config.yaml。每接入一个新模型,就把模型名、Key、单价、超时、fallback 写清楚;每停用一个模型,就把它从model_list里移除,不然 Router 还会傻乎乎地拿它做 fallback。我吃过这个亏:一个下线的旧模型还留在配置里,结果主模型故障时,网关把流量切到了一个已经不可用的备胎上,故障时间拉长了很多。

另外一个习惯是把 litellm 版本固定住。它迭代速度很快,配置格式和接口行为在部分大版本之间有变化。我现在会在项目里锁一个经过验证的版本,升级时先在测试环境完整跑一遍回归脚本,确认所有模型调用都正常再上生产。工具是提高效率的,不是增加不确定性的。做得好,换模型这件事就能从“改两天代码”变成“改五分钟配置”,这句话值得你亲身验证一遍。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询