1. 先从一团乱麻说起:为什么最后选了 litellm
过去一年我花在模型接口上的时间,比写业务逻辑还多。项目里先是接了一家厂商的模型,后来想对比不同模型的效果,又接第二家、第三家。结果每个模型都要走一套独立的 SDK,参数风格不一样,系统提示词的写法不一样,连最基础的流式输出格式都各有各的脾气。为了切换模型,代码里出现了大量 if-else 分支,每多接一家,就多一层适配代码。
这种时候最容易想到两条路:一是继续硬写封装层,把每家 API 都包装成自己定义的一套统一格式;二是引入一个现成的网关层,让流量先打到网关上,再由网关转去各个模型服务。第一条路看着简单,实际维护成本会慢慢压过来,因为每家接口都在升级,字段在变,限流策略在变,模型名在变。第二条路比较省心,但以前一直没有合适的开源方案,直到我认真用了 litellm 之后,才觉得这条路终于通了。
litellm 做的事情,简单说就是把各个模型厂商的接口统一成一套 OpenAI 风格的协议。你发给它的请求长一个样,它到上游请求时按各家要求重新拼装,上游返回之后,它再把结果归一化回统一格式。底层逻辑不复杂,但真正让我留下来用的是那些配套能力:多模型路由、密钥管理、预算控制、用量统计、缓存、限流、自动重试。这些功能如果自己从零写一遍,至少能消耗掉一个中型周期,而且写得还不一定比现成方案稳。
如果你是做 AI 应用开发的工程师,或者要在团队内部搭一套多人共用的模型接入平台,又或者在一个项目里同时接多家模型做效果对比,这个项目会非常值得研究。下面我把我实际使用的全过程、核心机制理解、踩过的坑都整理出来,给你一条能直接照着走的路线。
2. litellm 的核心机制拆解:一个请求从进入到返回发生了什么
2.1 请求流转链路:网关到底替你做掉了多少活
先把一次最普通的请求是怎么走通的串一遍。客户端(你自己的后端服务)把一个符合 OpenAI 风格的 JSON 请求发给 litellm,比如包含 model、messages、temperature 这些字段。litellm 收到以后,先做两件事:鉴权和模型匹配。鉴权就是验证你的虚拟 key 是否有权限、是否在预算内;模型匹配则是在配置好的 model_name 列表里找到对应的上游模型配置。
找到模型配置之后,litellm 根据配置里的 provider 前缀和 base_url 来决定调用方式。比如有的是走标准 HTTP 接口,有的需要先拼 signed 参数,有的要按对方 SDK 的内部格式构造请求,这些差异都在这一步被抹平。它对上游发起请求,把上游返回的 JSON 再转回用户熟悉的结构,包括 usage、choices、finish_reason 这些字段。流式模式也一样,上游下发的 SSE 格式会被重新包装成统一的事件流。
这一步其实就是整个项目的核心价值所在。你可以把 litellm 理解成一个翻译官,你只说一种语言,它去跟你脚下的每一家对接。这样一来,业务代码根本不需要知道某个模型厂商的接口长什么样,只需要记住 litellm 提供的统一协议就够了。我实际代码里甚至不需要引入各家的 SDK,只需要一个支持 OpenAI 协议的基础 HTTP 客户端就能完成所有模型接入。
2.2 模型与密钥的配置组织:YAML 里藏着的设计思路
litellm 的配置核心是一个 YAML 文件,几乎所有行为都由这个文件定义。它里面最关键的一块是 model_list,每一行定义了一个对外可用的模型条目。我按照自己项目的习惯,会写成这样:
model_list: - model_name: host-gpt litellm_params: model: provider_a/gpt-4o-class api_key: os.environ/PROVIDER_A_KEY - model_name: host-chat litellm_params: model: provider_b/chat-class api_key: os.environ/PROVIDER_B_KEY rpm: 120 tpm: 60000这里面的逻辑值得展开说一下。model_name 是对外暴露的名字,你在调用的时候填这个名字,它不一定跟上游模型一致。litellm_params 里才真正指定上游模型、密钥、速率限制、超时等参数。这种把“外部名”和“内部真实模型”拆开的做法非常有用,它可以支持模型灰度甚至无缝迁移:今天 host-gpt 对应 A 家的模型,明天想换成 B 家的,只需要改配置文件,业务代码完全不用动。
密钥这块的设计也很讲究。litellm 用一个主 key(master key)来管理所有虚拟 key,虚拟 key 才是给团队里各成员或各业务线使用的。虚拟 key 的背后可以绑定真实的上游 API key,但不会把真实 key 暴露给调用方。意味着即使团队某个人把虚拟 key 泄露了,你也可以单独吊销这一把,不需要把全部上游密钥推倒重配。
2.3 容易被忽略但很值钱的能力:缓存、限流、重试与预算
网关类工具的可怕之处不在于转发请求,而在于那些看起来“附加”的能力。litellm 提供了一层 Redis 或本地缓存,可以把相同请求的响应缓存下来,如果模型响应是可复用的,比如抽取类的固定问答或者摘要任务,命中率相当可观。它同时还支持一种语义缓存,基于向量相似度判断两个请求是否语义相同,不过这个功能要额外接向量数据库,如果团队没有现成的嵌入服务,可以先不启用。
限流方面,除了刚才配置里的 rpm(每分钟请求数)和 tpm(每分钟令牌数),litellm 还支持在 key、模型、团队几个维度分别设置。这是一个很实用的分层思路。比如上游给了你每小时 1 万次请求的总配额,你不可能把它均匀分给所有使用者,否则几个高并发业务就能把你的配额耗尽。你可以在 litellm 里给核心业务分配多一点的 tpm,给内部测试分配少一点,某条业务线超了直接返回限流错误,而不必担心它把你的真实上游配额打爆。
重试机制也很值得一说。上游接口经常出现瞬时抖动,尤其流式请求跑到一半连接断开。litellm 内置了基于状态码的重试策略,可以设置最多重试次数、重试间隔、指数退避参数。它还支持 fallback 模型列表,也就是当前模型异常时自动切换下一个候选模型。这个能力在我做稳定性压测时帮了大忙,正常情况下根本感知不到模型切换,只有在日志里能看到。
预算控制是我认为最被低估的功能。对于个人开发者来说,预算可能没那么敏感,但对一个团队来说,不控制预算的下场就是月底账单吓人。litellm 允许为每个 key 或每个团队设置半年、月、日预算,超过之后直接拒绝请求。它还记录每一笔消耗,统计到模型、key、团队三个维度,这样每月的成本分摊就不用再靠人工核对账单。
3. 从零跑通 litellm 的完整实操记录
3.1 安装与最小配置:五分钟起一个本地网关
安装其实很直接,用 Python 环境就行,我建议单独建一个虚拟环境,避免依赖冲突。命令很简单:
pip install 'litellm[proxy]'装完先做一个最小配置文件,假设你手头已经有一家上游模型服务的 API key。我自己的最小配置长这样:
model_list: - model_name: demo-model litellm_params: model: provider_a/demo-chat api_key: os.environ/PROVIDER_A_KEY general_settings: master_key: sk-your-master-key-here database_url: sqlite:///litellm.db启动方式同样简单:
export PROVIDER_A_KEY=你的真实上游key litellm --config config.yaml --port 4000看到日志输出 "Uvicorn running on http://0.0.0.0:4000" 就说明服务起来了。这里注意两点:一是 master_key 是你作为管理员用来管理整个网关的钥匙,要设置得足够复杂;二是 database_url 务必配置上,否则 litellm 的用量统计和预算能力就是空谈。我第一次用的时候偷懒没配数据库,结果虚拟 key 一重启就消失,所有配置都得重来,所以这一步不要省。
3.2 第一次真实调用:从 curl 到流式输出
网关起来之后,先用 curl 打一发看看效果:
curl http://localhost:4000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-your-master-key-here" \ -d '{ "model": "demo-model", "messages": [{"role": "user", "content": "你好,请介绍一下你自己"}], "stream": false }'如果配置没问题,你会拿到一份标准的 OpenAI 风格响应,包含 id、choices、usage 等字段。响应里的 model 字段显示的还是 demo-model,而不是上游真实模型名,说明映射关系生效了。我习惯在正式接入代码之前,先这样手动验证一遍模型映射和密钥是否正确,能省掉很多后面排查代码的时间。
流式调用也支持,把上面的 stream 改成 true 就行。终端里会看到一串 SSE 事件,每个事件里都是一个增量的 delta 块。这一块对于做聊天机器人的朋友是刚需,因为很多模型的后端流式格式差异其实挺明显的,litellm 归一化之后,你的前端代码可以只处理一种流式协议。
3.3 用 Python 接入业务代码
对于实际业务,我更推荐直接用 HTTP 客户端接 litellm,而不需要加载任何特定 SDK。这样做的好处是业务代码与网关彻底解耦。假如后面换掉 litellm 或者改用其他网关,业务代码基本不用动。
下面是一个最小 Python 客户端示例,用的是官方 OpenAI 兼容 SDK 的接口方式:
import os from openai import OpenAI client = OpenAI( api_key="sk-your-virtual-key-here", base_url="http://localhost:4000/v1" ) resp = client.chat.completions.create( model="demo-model", messages=[{"role": "user", "content": "今天天气怎么样?"}] ) print(resp.choices[0].message.content)这段代码几乎可以原样搬运到你的服务里,只需要把 base_url 指向你部署的 litellm 地址。如果你项目里已经有了一套基于 OpenAI 协议的调用代码,把 base_url 改一下就全部切过来了,这是我当时决定引入 litellm 的一个重要原因:迁移成本低到几乎为零。
3.4 用 Docker 部署给团队用:配置与磁盘细节
如果是个人本地调试,直接用 Python 跑没有问题。但如果要团队共用,我强烈建议用 Docker 方式部署。好处一是环境独立,二是进程管理简单,三是内存和网络资源可以明确限制。
一个比较典型的 docker-compose 配置我贴在这里:
services: litellm: image: ghcr.io/berriai/litellm:main-latest ports: - "4000:4000" volumes: - ./config.yaml:/app/config.yaml - litellm-data:/app/data environment: - PROVIDER_A_KEY=${PROVIDER_A_KEY} - DATABASE_URL=sqlite:////app/data/litellm.db - LITELLM_MASTER_KEY=${LITELLM_MASTER_KEY} command: ["--config", "/app/config.yaml", "--port", "4000"] restart: unless-stopped volumes: litellm-data:这里有几个经验值得分享。第一是数据库文件一定要通过 volume 持久化,否则容器一重建,所有虚拟 key、预算、日志数据全部清零。第二是敏感的密钥不要写死在 compose 文件里,用环境变量引用,然后放到 .env 文件并从版本库里忽略掉。第三是如果访问量比较大,给 litellm 容器加上日志轮转,它在高并发下打印的访问日志非常密集,磁盘增长快得超出想象。
部署完成后,团队成员只需要知道网关地址和自己专属的虚拟 key,就能在几乎零学习成本的情况下接入各种模型。业务方不用再关心上游模型选型、密钥管理、限流配额这些事,全部由网关统一承担。
4. 实际踩坑记录:常见问题与排查思路
4.1 上游限流导致的 429,不只是加个重试那么简单
用了一段时间后,第一个高频问题就是 429。遇到这种错误,很多人的第一反应是加代码重试,但这样往往会把限流问题放大,造成雪崩。
litellm 里的正确思路是多层配合。首先给每个模型条目设置 rpm 和 tpm,这相当于在源头控制流量;其次配置 fallback 列表,当主模型限流时自动转备用模型;最后再开启内置重试,让瞬时抖动被静默吞掉。比如我某个场景的主模型经常在高峰期 429,我就把该模型 rpm 降到合理值,并配置了一个候选模型作为兜底。实测下来业务侧成功率从 98.2% 提到了 99.9% 以上。
排查 429 另一个容易忽略的点是上游的 tpm 限制。有时候你的请求数并不高,但每次请求的上下文都特别长,导致令牌数瞬间超标。litellm 返回的错误信息里一般会标明是 rate limit 还是 quota exceeded,读清楚再做针对性处理,不要盲目调参。
4.2 模型名映射不一致:call 的时候报 model not found
这个坑几乎每个新人都要踩一次。配置里明明写了 model_name,调用的时候却提示对应的模型不存在。我第一次排这个错花了大半个小时,后来发现是我同时配置了多个相同 model_name 的条目,后面的配置覆盖了前面的,而覆盖后的条目上游模型名写错了。
这里有个规律:litellm 的 model_list 中,model_name 相同的情况下,默认采用第一个还是最后一个,取决于具体版本行为,所以最好不要依赖覆盖顺序。做法是给每个 model_name 一个唯一且有意义的名字,比如 host-chat-v2,然后用独立配置管理工具的校验逻辑在启动前就检查重复。另外,litellm 提供了一个模型列表接口,实际暴露了哪些模型,可以直接看 /v1/models 返回的结果,排查时先看这个接口再对照配置,能少走弯路。
4.3 虚拟 key 使用中的隐患:别把真实 key 留在日志里
虚拟 key 机制本身很安全,但我在运维中发现一个容易疏忽的细节:某些上游 SDK 或 HTTP 客户端在调试日志里会完整打印请求头,而真实上游 key 恰好就在 header 里。如果你的应用把调试日志接入日志采集系统,真实 key 就可能以明文形式出现在日志平台。
我处理这个问题的方法是:把日志级别调到 WARNING 以上,并且对日志系统设置敏感字段脱敏规则,在采集端把 Authorization 内容替换成占位符。另一个好习惯是定期轮换上游 key,我目前的做法是每 60 天换一次,并把旧的 key 在 litellm 的配置里同步替换。这样即使发生泄露,影响窗口也被控制住了。
此外,给 litellm 的管理接口加一层网络访问控制,不要把它直接暴露到公网。虚拟 key 虽然可以做权限隔离,但管理接口一旦暴露,恶意攻击者有可能通过管理员操作干扰正常服务。我的做法是部署在内网网关,只允许特定网段访问管理端口。
4.4 日志与耗时异常:连接慢不一定是模型的锅
有段时间我发现某些请求特别慢,经常要等十几秒才返回。查了很久 upstream,后来才发现问题出在 litellm 所在的机器访问上游服务的网络链路上,DNS 解析和代理转发拖慢了整体耗时。
排查这类问题有个实用顺序:先看 litellm 日志里每个请求的耗时分段,确认慢的是网关处理阶段还是上游调用阶段;再对比不同时间段和不同模型的响应时间,排除上游本身的抖动;最后用基础网络工具直接测到上游服务器的连接延迟。经过几次这样的排查,我把 litellm 服务移到了距离上游更近的区域节点,加了 DNS 缓存,整体 p95 延迟从 8 秒降到了 1.5 秒,效果非常明显。
这里也提醒一点:litellm 日志默认的输出粒度可能不够精细,你可以开启更详细的请求日志模式,让它记录每个阶段的耗时。虽然会增加日志量,但遇到性能问题时会省下大量猜测时间。
5. 延伸玩法:litellm 在真实项目里的更多价值
5.1 多模型路由与故障转移:业务代码一行都不用改
litellm 最让我满意的一点是,我可以在完全不改业务代码的情况下,做模型灰度切换。比如某天一个上游模型质量下降,我可以立即在配置文件里把该模型条目切换到另一家模型,重启网关之后所有调用自动走新模型。如果新旧模型能力差异不大,甚至可以在配置里让同一个 model_name 按权重分配到两个不同上游,实现流量灰度。
这种能力在真实项目里非常有价值,尤其是做对稳定性要求高的线上服务时。我遇到过一次上游模型突然大面积不可用,整个服务面临瘫痪风险,当时靠 litellm 的 fallback 配置把流量在 1 分钟内切到了备用模型,业务侧几乎无感。
5.2 团队模型网关:权限、配额与成本分账一把抓
如果团队有多个项目同时使用模型,litellm 的 team 机制就很合适。你可以创建不同 team,比如算法组、前端组、运营组,给每个 team 分配各自的虚拟 key 和预算额度。这样每个组的调用量、花费都能从 litellm 的管理界面分得清清楚楚。
对管理者来说,这不是一个简单的“省心”功能,它是预算控制的基础设施。以前项目结束复盘成本时,要靠财务导出账单再人工匹配业务归属,现在直接看 litellm 后台报表就行。而且给某个高消耗业务设置月度预算硬上限后,再也不用担心有人在测试环境刷爆账单。
5.3 接入本地部署的开源模型:兼容性与并发的平衡
不少团队在私有化场景下要接入本地部署的开源模型服务。litellm 可以对接很多支持 OpenAI 兼容协议的本地方案,只需要把配置里的 base_url 指向本地服务地址,api_key 随便填一个占位符就可以了。
不过本地模型并发能力通常有限,接入的时候注意在 litellm 层做好限流。我一开始没做这个限制,结果测试请求一多,本地模型直接被压到超时,排队时间长到不可接受。后来我把该模型条目的 rpm 调低,同时开启默认的请求排队策略,问题就解决了。如果你要接本地模型,建议先压测出它的真实承载能力,再反向设置 litellm 的限流参数,而不是盲目给高并发。
关于模型效果对比,litellm 也可以帮上忙。把不同厂商、不同版本模型配置成不同 model_name,然后在同一批测试集上轮询调用,可以很方便地做横向评测。评测结果汇总之后,决策换模型就变得有数据支撑,而不是拍脑袋。
最后分享一个我在运维 litellm 后期总结出来的小习惯:数据库文件不要跟系统盘放在一起,最好单独挂一块数据盘。litellm 在长时间高负载运行之后,日志和用量明细占用的磁盘空间增长很快,我亲眼见过一次磁盘写满导致服务不可用的场景。单独挂盘、分层清理日志、定期归档数据库快照,能让网关服务长期稳定地跑下去。这个项目表面上只是一个接口转换工具,但真正把它用顺之后,你会发现在模型驱动的系统架构里,它其实扮演着“模型接入总线”的角色。