最早让我认真去研究 LiteLLM,是一次线上事故。某天生产环境里的一个对话服务突然大面积超时,排查下来发现是模型供应商调整了接口返回结构,而所有业务代码都直接按对方的原生 SDK 写的,想临时切到备用模型,至少得改十几个文件,涉及请求格式、鉴权方式、异常处理三套逻辑。那之后我把服务统一接到了一个 OpenAI 兼容的统一接口层上,这类"换供应商等于重构"的问题,就变成了改一行配置的事。
这篇文章想聊的就是这个统一接口层的开源方案——LiteLLM。它本质上是一个大模型 API 的适配层,你可以把它当成一个"通用插头":无论后端接的是哪家模型服务,你面向业务写的代码都只需要一套 OpenAI 格式的请求。适合正在做 AI 应用落地、搞 Agent 项目,或者不想把一个项目绑死在单一模型上的同学参考。我会从定位、基础用法、生产级的分流与兜底,讲到团队网关模式,最后再分享一些我实际踩过的坑。
1. LiteLLM 的定位:为什么说它是大模型世界的"通用插头"
1.1 在没有适配层之前,多模型接入的代码是怎么失控的
做过 AI 应用的读者应该都有体会:不同模型服务商的接口,看起来都是"发消息、拿回复",但实际差异相当琐碎。有的用messages,有的用prompt;有的鉴权头是Authorization: Bearer,有的是自定义 header;流式输出的协议、错误码、限流字段也各不相同。
我们之前有一个需求,要在同一个功能里让用户可选 A、B 两个模型服务。一开始是单纯加分支:if provider == "A": call_a_model() else: call_b_model()。过了一个迭代,第三个模型服务加进来,代码开始变成上百行的映射逻辑。更麻烦的是模型升级导致的字段变化,比如某个服务把"safety 拒绝"从error字段挪到了finish_reason,所有调用方都要跟着改。这种脆弱性不是靠单元测试能覆盖的。
LiteLLM 解决的就是这个适配问题。它把各家接口的差异封装在库内部,对外暴露 OpenAI 风格的请求和响应结构。你不需要在业务代码里写厂商 SDK,只要用一套messages结构,把"模型名"和"密钥"作为参数传进去。
1.2 用一套 OpenAI 格式统一所有请求,原理是什么
LiteLLM 的核心逻辑并不神秘。它内部维护了一个模型名到各服务商端点的映射表,调用时根据model参数识别当前该走哪套鉴权、哪个请求模板,然后执行对应的 HTTP 请求,再把响应统一转成 OpenAI 格式的ChatCompletion对象。
这对业务代码的最大价值是:返回值结构永远是同一套。你写response["choices"][0]["message"]["content"]取正文,写response["usage"]看 token 消耗,无论在哪个服务商后面都是通的。这样即使后端的模型服务从 A 换到 B,业务代码几乎不用动,变的只是配置。
更细一点说,LiteLLM 甚至帮你处理了部分模型服务商之间的参数映射差异。比如某个服务商的temperature范围和其他家不一样,它会做归一化转换;某些服务商有额外的system外置参数,它也会在请求构造时处理好。
1.3 SDK、CLI、网关三条入口,各自解决什么问题
LiteLLM 最让人舒服的一点,是它提供了三层使用方式,你可以按场景选:
| 使用方式 | 形态 | 典型场景 |
|---|---|---|
| SDK | Python 库 | 在业务代码里直接调用,做单机服务、脚本、Agent |
| CLI | 命令行工具 | 快速验证连接、本地调试模型响应 |
| 网关模式 | 独立服务 | 团队共享入口,统一做密钥管理、成本统计、负载均衡 |
我自己的经验是:个人项目用 SDK 就够,轻巧直接;一旦服务要给别人用、要给前端或者多个后端服务共用,就必须上网关模式。网关模式可以让所有团队成员只认一个地址,模型供应商的密钥只保存在服务端,而不是散落在每个人的环境变量里。这一点对安全性和合规性非常重要,后面会单独展开。
2. 从零跑通第一个调用:安装、密钥配置与三种接线方式
2.1 安装和密钥配置,最容易忽略的是"最小权限"原则
安装非常简单,一条命令:
pip install "litellm[proxy]"[proxy]这个扩展会把后续跑网关需要的依赖一起装好。如果只打算在脚本里调 SDK,直接pip install litellm也可以。
密钥配置方面,LiteLLM 是约定优先:默认读取环境变量,变量名通常就是厂商前缀加_API_KEY。比如:
export OPENAI_API_KEY="sk-你的key" export ANTHROPIC_API_KEY="sk-ant-你的key"这里有个很容易被忽视的问题:不要把密钥硬编码在代码里,尤其不要把真实 key 提交到 Git 仓库。我见过不少同学为了本地跑通方便,随手把 key 写在示例脚本里,结果一整年都改不过来。比较稳妥的做法是用 CI 变量、容器密钥注入,或者本地用.env并确保它被.gitignore排除。下面所有示例里的 key 都是占位符,别直接抄。
2.2 用 completion 函数接各大模型
装好之后,最基础的调用长这样:
from litellm import completion messages = [ {"role": "system", "content": "你是一个专业的运维助手。"}, {"role": "user", "content": "帮我总结一下生产环境告警处理的流程。"} ] response = completion( model="gpt-4o", messages=messages, temperature=0.7, ) print(response["choices"][0]["message"]["content"])注意看,这里model参数直接写了某个模型名,LiteLLM 会自动识别它属于哪个服务商,并去对应环境变量里找密钥。如果你想切换成另一家模型,只改一行:
response = completion( model="claude-3-5-sonnet-20241022", messages=messages, temperature=0.7, )返回结构不变,取正文、取 token 用量的代码完全不用改。这种"一行切换模型"的体验,在大量对比不同模型输出的场景里简直是救命稻草。我当时做模型效果评测时,用一个循环脚本把同样一批问题发给四五家模型,只靠改model参数就能跑完对比,代码量和我只测一家时几乎没有差别。
如果你用的是异步代码,LiteLLM 也提供了acompletion:
import asyncio from litellm import acompletion async def main(): resp = await acompletion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], ) print(resp["choices"][0]["message"]["content"]) asyncio.run(main())多路并发请求这些模型时,配合asyncio.gather就能平稳并发,这对做批量评测、批量拉取回复的工作流很合适。
2.3 用 CLI 快速验证连接
如果不想写代码,LiteLLM 命令行本身就能当一个临时调试入口。最直接的用法是直接启动一个最简网关:
litellm --model gpt-4o --port 4000启动后,你可以在另一个终端用curl验证:
curl http://localhost:4000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-本地密钥" \ -d '{ "model": "gpt-4o", "messages": [{"role": "user", "content": "ping"}] }'这里说个实际经验:命令行模式最大的价值不是"跑通",而是验证环境变量有没有配对。有时候代码里报AuthenticationError,八成是环境变量名拼错了,或者当前 shell 没有重新加载密钥。先用一条litellm --model命令调起来,再用curl试一次,能省掉很多排查时间。
3. 不那么容易挂掉的实践:分流、兜底与重试策略
3.1 fallbacks 参数:主模型挂了怎么办
接入单一模型服务的项目,最大的风险是单点依赖。某个模型服务商限流、升级、故障时,你的服务就会跟着抖动。LiteLLM 提供fallbacks参数来实现模型级别的兜底:
from litellm import completion response = completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], fallbacks=[ {"model": "claude-3-5-sonnet-20241022"}, ] )当gpt-4o调用失败(比如 429 限流、5xx 服务端错误),LiteLLM 会自动改调fallbacks里配置的模型。这个机制非常适合对可用性敏感的生产服务,比如客服对话、知识库问答。
不过要提醒一个容易忽略的点:fallback 不是无代价的。不同模型的价格、延迟差别很大,备用模型万一被大量触发,当月账单可能会超预期。所以我在实际项目里会同时打开监控,一旦 fallback 触发频率过高,就说明主模型通道有问题,应该去处理根源,而不是一直靠兜底硬扛。
3.2 Router:多把钥匙、多份配置怎么管理
如果你手里有多个模型 key、多个模型实例,或者希望按负载把请求分散到不同通道,可以试试Router。它类似于一个进程内的路由层,能维护一份模型列表,按策略分发请求。
from litellm import Router import os router = Router( model_list=[ { "model_name": "gpt-a", "litellm_params": { "model": "gpt-4o", "api_key": os.environ["OPENAI_API_KEY"], }, }, { "model_name": "claude-b", "litellm_params": { "model": "claude-3-5-sonnet-20241022", "api_key": os.environ["ANTHROPIC_API_KEY"], }, }, ] ) resp = router.completion(model="gpt-a", messages=[{"role": "user", "content": "你好"}])这里最值得注意的设计是model_name与真实模型是分开的。model_name是你在业务代码里用的"别名",litellm_params.model是真实转发时用的模型名。这样以后你想把gpt-a从gpt-4o换到某个更新的模型,只需要改 Router 配置,业务代码一行不用动。
Router 还支持路由策略,比如最简单的是simple-shuffle把请求均匀打散到同一模型的多个 key 上,适合处理多个 key 的配额;权重策略则可以把较大流量导向便宜模型,把小部分流量导向效果更好的模型做混合调度。不过这些高级策略我建议先在小流量上验证,不要一上来就用于核心链路。
3.3 超时、重试与请求队列的配置建议
LiteLLM 提供了一些全局配置参数,我常用的几个是:
import litellm litellm.request_timeout = 30 # 单次请求超时上限,秒 litellm.num_retries = 3 # 请求失败后的重试次数 litellm.retry_max_wait = 10 # 最长退避等待时间,秒关于超时,个人经验是:不要把技术层的超时设得太短。大模型服务的响应时间波动比普通 HTTP 服务大得多,同样是gpt-4o,短文本可能在几百毫秒内返回,长篇生成可能要几十秒。我之前把超时设成 10 秒,结果长文本任务频繁超时,换成 30 秒后稳定多了。更合理的做法是在业务层把请求分成"短响应"和"长生成"两类,分别设置超时预期。
重试参数要注意服务商的限流语义。有些限流是"等待几秒后可以重试",有些是"短时间内完全不可用"。如果盲目快速重试,反而会加重限流。设置一个合理的退避时间,配合上面说的 fallback,比单纯加高重试次数更有效。
4. 把 LiteLLM 变成团队网关:代理模式的完整用法
4.1 一个配置驱动的网关,让所有客户端只认一个地址
如果你的服务要交给前端、其他后端或者数据同事调用,我强烈建议直接上 LiteLLM 的网关模式。它本质上是一个独立服务,对外暴露 OpenAI 兼容的/v1/chat/completions接口,背后再接各家模型。
网关模式最常见的启动方式是用配置文件:
model_list: - model_name: gpt-4o litellm_params: model: gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: claude litellm_params: model: claude-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY启动:
litellm --config ./config.yaml --port 4000网关模式下,调用方根本不需要关心后端的模型供应商是谁。他们只需要一个 OpenAI 客户端的地址。比如用常见的 OpenAI Python 客户端:
from openai import OpenAI client = OpenAI( base_url="http://localhost:4000/v1", api_key="litellm-虚拟key或master key", ) resp = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], )这意味着团队里无论用 Python、Java、Go 还是 Node.js,只要项目里有那个语言的 OpenAI SDK,就能直接连上这个网关,不需要针对各家 API 写独立的封装。对于多语言团队,这是非常实在的收益。
4.2 虚拟密钥与预算控制:给每个成员发一把可控的钥匙
网关模式最打动我的一点,是它对 API key 的治理能力。真实模型服务商的 key 一旦泄露,可能会产生巨额调用费用;而且如果所有成员共用同一个 key,出了问题根本不知道是谁调的。
LiteLLM 支持在 master key 之上生成"虚拟密钥"。先生成主控密钥:
litellm --config ./config.yaml --master_key sk-master-这里是主控密钥然后通过接口创建子密钥:
curl http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-master-这里是主控密钥" \ -H "Content-Type: application/json" \ -d '{ "models": ["gpt-4o"], "max_budget": 10, "budget_duration": "1mo" }'返回结果里会给你一个sk-开头的虚拟 key。这个 key 可以在网关配置里被限制为只能用某些模型、每个月最多花 10 块钱。这样你给每个成员、每个项目、每条业务线发不同的虚拟 key,就能精确知道某个 key 消耗了多少。
我在团队里实践过一段时间,这套方式的收益非常明显:不用再跟成员挨个解释真实 key 应该存哪里,也不用担心某个同事误把 key 带到前端代码里导致泄露。虚拟 key 即使泄露,影响范围也局限在预算和模型范围内,而且可以立刻吊销。
4.3 成本统计与用量看板,月底对账不用再拍脑袋
成本统计是另一个容易被低估的功能。用 LiteLLM 网关之后,每次成功调用都会记录模型、token 用量、费用、耗时等信息。如果配置了数据库,还能落库查询:
general_settings: database_url: postgres://用户名:密码@数据库地址:5432/litellm在网关运行期间,可以查询用量接口:
curl http://localhost:4000/spend/logs \ -H "Authorization: Bearer sk-master-这里是主控密钥"配了数据库之后,查看历史数据会更方便,也可以拿来做月度成本复盘。我的习惯是隔几天查一次按 key 分组的消耗,发现有异常升高的 key 就及时处理。这个功能在项目早期可能觉得没必要,但用量上来之后,你会非常感谢当时的自己有这些数据。
5. 生态联动:快速接入你已有的技术栈
5.1 任何带 OpenAI 兼容客户端的语言都能接入
LiteLLM 网关对外呈现的就是 OpenAI 兼容协议,所以它几乎可以和所有主流语言的模型调用框架衔接。你在网上能找到的 OpenAI SDK 客户端,都可以直接指向 LiteLLM 网关地址。
我之前处理过一个遗留系统,它已经用 Python 的 OpenAI 客户端写了不少逻辑,但因为某些原因需要换模型供应商。当时最省事的方案就是部署一个 LiteLLM 网关,把客户端base_url指过去,其他代码一行不改。这种改造成本,比把所有调用点全部翻新一遍要小得多。
5.2 自定义回调:把每一次调用都记进自己的日志
除了网关自带的成本记录,LiteLLM SDK 也支持回调。你可以定义自己的观察函数,在每次请求成功或失败后拿到详细数据。
import litellm def inventory_callback(kwargs, completion_response, start_time, end_time): print("模型:", kwargs.get("model")) print("花费:", kwargs.get("response_cost")) print("总token:", kwargs.get("usage")) litellm.success_callback = [inventory_callback] resp = litellm.completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], )这种回调适合接入已有监控系统。比如把每次调用的耗时、费用、错误码发到日志平台,再配合告警规则,哪天模型调用慢了你就能第一时间知道,不用等用户反馈。
5.3 Embedding 和多模态请求,也能走同一条链路
LiteLLM 不只管文本对话。文本向量化接口也支持统一调用,比如:
from litellm import embedding response = embedding( model="text-embedding-3-small", input=["需要向量化的文本"], )还有图像、语音等多模态能力,只要对应模型服务商本身支持,通常也可以通过 LiteLLM 转发。这些功能在搭知识库、做 RAG 或者做多模态 Agent 时会用到。我的建议是,如果你的项目已经有文本对话的接入经验,相关的 embedding、多模态请求也尽量统一走同一个网关,保持调用风格一致,后续运维会轻松很多。
6. 这半年来我用 LiteLLM 踩过的坑,以及一些取舍建议
6.1 模型别名和真实模型不一致,造成的"预期幻觉"
有段时间我们的配置里model_name写的是gpt-4o,但litellm_params.model实际指向了一个相对便宜、效果稍弱的模型。团队成员照着文档里的别名去测试,以为自己在调一个高性能模型,实际结果却总有偏差。排查了一圈才发现是配置文件和直觉不一致。
教训是:在网关模式下,一定要把"业务别名"和"真实模型名"当成两个独立概念来管理。给模型排序、压测、容量评估时,都要以真实模型为准,别被别名骗了。最好在项目文档里维护一张别名对照表,写明每个别名当前指向的真实模型,避免出现"文档和配置脱节"。
6.2 流式输出与 fallback 组合时的体验问题
fallback 逻辑处理普通请求很顺利,但流式输出场景要更小心。流式响应一旦开始,客户端已经在逐步接收 SSE 数据;如果中途模型通道故障,LiteLLM 自动切换到备用模型,业务侧看到的表现往往是"流突然断了,然后从某个位置重新开始"。这对用户体验来说非常奇怪。
我的处理办法是:对要求低延迟的场景,前端尽量把"中断续传"的状态显式表达出来,而不是静默无缝恢复。对严格保障连续性的场景,我会把流式请求和 fallback 拆开:优先在重试和超时层面做保护,而不是在流中途切模型。这一点需要根据你的实际业务形态做取舍,不能无脑照搬。
6.3 什么时候不需要 LiteLLM:和小团队自研方案的对比
LiteLLM 很好用,但也不是万幸之选。如果团队只是在一个脚本里测试单个模型,确实没必要引入这一层,直接用一个官方 SDK 就够了。如果你的团队已经有成熟的内部网关团队,且对各家 API 的特殊逻辑做了大量定制,那不管你用不用 LiteLLM,核心问题都是"谁来长期维护这套适配层",而不是"要不要用某一个方案"。
| 场景 | 建议 |
|---|---|
| 个人脚本、一次性评测 | 直接用官方 SDK,不引入额外依赖 |
| 项目需要快速接多家模型 | 用 LiteLLM SDK,低成本试错 |
| 团队多个服务共用模型入口 | 用 LiteLLM 网关模式,做密钥和成本管理 |
| 已有自研网关且稳固 | 不必强行替换,可借鉴它的设计思路 |
最后说一个实操上的小技巧:无论你决定用 LiteLLM 还是自研网关,都先从一条非核心业务链路上切一小部分流量,观察几天的稳定性、成本和调用日志,再逐步扩大范围。我在线上做切换时,习惯先手动配置 10% 的流量走新链路,确认没有异常后再慢慢放量到 100%。这个过程里,LiteLLM 的日志和虚拟 key 会帮上大忙,你的排障效率会高很多。