先讲个我自己经历过的场景。某个内部对话服务,最早只对接了一家大模型服务商的接口,后来因为成本和供应商策略,要求同时支持另一家的对话模型,再往后运维又塞进来一个本地部署的开源模型跑低优先级流量。结果同一个补全需求,代码里要维护三套SDK、三套鉴权头、三套错误处理分支,每次上游模型升级接口,都得跟着改一版调用层。最怕的就是产品经理那句"能不能再加一个模型"。
后来我把所有外部调用统一收口到LiteLLM后面,整件事就变成了配置文件里多写几行。这篇文章不是官方文档的复读,是从单机实验到生产代理完整走下来的记录:先说清楚它到底在解决什么,再给一份能直接抄的上手流程,然后把预算、负载均衡、故障转移这些生产配置挨个拆开讲,最后记录我实际踩过的坑和一组性能数据。
适用的人很明确:正在被多供应商API碎片化折磨的后端开发者、要给团队搭模型网关的负责人,以及想在多个模型之间灵活切换又不愿重复造轮子的人。
1. 接口碎片化是真痛点:三家SDK教会我的事
在聊LiteLLM之前,先得把问题本身聊透。很多人觉得"多接一家模型不就是换个HTTP请求的事",真上手做一次就会发现,这里头的隐性成本比想象中大得多。
1.1 同一个功能,各家接口三种写法
以最基础的对话补全为例,我当年同时对接过三家服务,差异可以列一张表:
| 对比维度 | 服务商A | 服务商B | 本地开源模型 |
|---|---|---|---|
| 请求路径 | /v1/chat/completions | /v1/messages | 各框架各不相同 |
| 鉴权方式 | Authorization Bearer | 自定义请求头 | 通常无鉴权或简单Token |
| 系统提示词 | messages里的system角色 | 顶级system参数 | 格式不统一 |
| 输出结构 | choices[0].message.content | content[0].text | 格式不统一 |
| 错误码 | 标准HTTP+错误体 | 自定义错误结构 | 各自为政 |
也就是说,业务代码里每接一家供应商,就要多写一层适配:请求体要重构、鉴权头要分派、响应解析要单独写、错误重试策略要单独调。更要命的是,这些适配代码往往散落在业务逻辑里,写着写着就变成一坨剪不断理还乱的胶水层。
1.2 LiteLLM做了什么:统一入口加标准输出
LiteLLM的核心思路很朴素——把所有供应商的差异挡在外面,对外暴露一个统一的OpenAI风格接口。你在代码里永远只写chat/completions这一套,模型名写成openai/gpt-4o、anthropic/claude-3-5-sonnet、ollama/llama3.1这种"供应商前缀+模型名"的形式,剩下的事情由LiteLLM内部完成。
它的形态分两种:一个Python SDK,可以直接在业务代码里调用;一个代理服务(Proxy),可以独立部署成一个网关,让全团队通过一个地址访问所有模型。SDK适合单体应用快速接入,代理适合多服务、多团队共享模型资源。实际项目里我几乎只用代理模式,原因后面会讲。
1.3 请求在LiteLLM内部是怎么流转的
理解它的工作原理,排查问题会省很多力气。当一个请求进来,LiteLLM会先根据模型名的前缀选处理器(比如openai/走OpenAI处理器,anthropic/走Anthropic处理器),然后做三件事:
- 把统一格式的请求体翻译成对应供应商的原始请求结构;
- 把供应商返回的响应标准化成OpenAI格式(
choices、usage这些字段保持一致); - 在中间环节插入预算检查、缓存查询、重试判断、日志上报等横切逻辑。
所以它本质上是一个"翻译网关":上游是五花八门的供应商,下游是你熟悉的统一接口,中间夹着生产环境需要的公共能力。想通这一点,后面配置里那些字段就不难理解了。
2. 从一行安装命令到全量代理:LiteLLM快速上手全记录
直接进入实操。我按自己当时跑通的顺序写,每一步都标注了为什么这么做,方便你复现的时候少走弯路。
2.1 安装与第一份配置
我用的Python 3.11环境,安装命令很简单:
pip install 'litellm[proxy]'[proxy]这个extra会把代理服务需要的依赖一起装好,包括FastAPI、uvicorn这些。如果你只想在代码里用SDK,装基础包就行,但既然都看到这篇文章了,我建议直接装完整版。
装完后确认版本号,不同的主版本配置格式差异挺大,后面所有示例都以我当前使用的稳定版本为准:
python -c "import litellm; print(litellm.__version__)"然后是环境变量。LiteLLM默认从环境变量里读密钥,这是我最喜欢的点——密钥不落盘,进Git也不会泄漏:
export OPENAI_API_KEY='sk-你的密钥' export ANTHROPIC_API_KEY='sk-ant-你的密钥'2.2 Python SDK直调:换模型只改一个字符串
先别急着上代理,用SDK把基本路径跑通。下面这段代码是对三个供应商发同样请求的完整示例,注意model参数的前缀:
from litellm import completion messages = [ {"role": "system", "content": "你是一个简洁的助手,回答控制在三句话以内。"}, {"role": "user", "content": "用一句话解释什么是数据库索引"}, ] # 服务商A的模型 resp = completion( model="openai/gpt-4o", messages=messages, ) # 服务商B的模型 resp2 = completion( model="anthropic/claude-3-5-sonnet", messages=messages, ) print(resp.choices[0].message.content) print(resp2.choices[0].message.content)completion()返回的对象在主要字段上和OpenAI SDK保持一致,choices[0].message.content直接就能拿文本,usage字段也能拿到token统计。也就是说,业务代码里根本不用关心供应商是谁,换模型就是改一行字符串的事。
还有个细节值得多说一句:completion()几乎不用传api_key,因为LiteLLM会按供应商前缀自动去环境变量里找对应的密钥。这对代码整洁度帮助很大——你永远不会在业务代码里看到密钥。
2.3 代理服务跑起来:一条命令接通全量模型
SDK直调只是热身,代理模式才是重头戏。代理模式的核心是一个配置文件,我用的是config.yaml:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: claude-3-5-sonnet litellm_params: model: anthropic/claude-3-5-sonnet api_key: os.environ/ANTHROPIC_API_KEY - model_name: llama3.1 litellm_params: model: ollama/llama3.1:8b api_base: http://localhost:11434 litellm_settings: drop_params: true general_settings: master_key: sk-master-key解释几个关键点:
model_list里的model_name是团队对外的模型别名,litellm_params.model才是真正要调用的供应商模型。这样做的好处是:以后供应商换模型了,只改配置,客户端代码不用动。api_key: os.environ/OPENAI_API_KEY表示从这个环境变量读取密钥,值不会写死在文件里。drop_params: true很实用,意思是上游模型不支持的参数会自动丢弃,而不是报错。比如有些模型不支持max_tokens,带上这个配置就能直接忽略,省去大量参数兼容代码。master_key是代理的管理员密钥,后面创建虚拟密钥、查看用量都要用它。
启动代理就一条命令:
litellm --config config.yaml --port 4000看到Uvicorn running on http://0.0.0.0:4000就算起来了。验证一下:
curl http://localhost:4000/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-master-key" \ -d '{ "model": "claude-3-5-sonnet", "messages": [ {"role": "user", "content": "你好,用一句话介绍你自己"} ] }'返回的JSON结构和OpenAI官方格式几乎一样。到这里,你的团队就拥有一个统一模型网关地址了:http://localhost:4000。
3. 生产环境才见真章:预算、路由、降级与缓存配置拆解
单机跑通只是第一步。真正让我决定把LiteLLM留下来长期用的,是它在生产场景下的那套组合能力。下面这些配置我全部在真实流量里验证过,按优先级排序讲。
3.1 用量追踪与预算限额:先让自己睡得着觉
接入多个供应商后,最怕的是月底账单爆掉。LiteLLM的general_settings里可以直接设预算:
general_settings: master_key: sk-master-key budget_duration: 1mo max_budget: 200意思是每月预算上限200美元,超了代理直接拒绝新请求并返回错误。配合/spend/logs接口,可以按API Key、按模型、按用户维度查消费明细。
我的实际经验是:预算这条要第一天就配上,别等出账单再追悔。另外,它统计的用量是按各家API返回的usage字段折算的,不同模型的定价规则不太一样,建议在LiteLLM的模型定价表里核对一遍,偏差基本可控。
3.2 多部署负载均衡:同一模型多份配置轮询
如果你同一个模型的请求量很大,或者想多账号分摊限额,可以做负载均衡。核心思路是:对外暴露同一个模型名,但背后挂多个部署:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY_1 - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY_2两个条目用同一个model_name,LiteLLM会自动在它们之间做流量分配。路由策略可以通过routing_strategy控制,默认是轮流分发,也可以改成usage-based-routing按剩余配额权重分配。
我当时用这个方案解决了两个账号的并发配额限制,效果很直接。需要注意:负载均衡只在代理模式下生效,SDK的Router对象也有类似能力,但不如配置声明式直观。
3.3 故障转移与重试:主模型挂了别让线上跟着挂
这是我认为LiteLLM最值钱的能力之一。配置里声明fallback,主模型失败后自动切换备用模型:
litellm_settings: fallbacks: [ {"gpt-4o": ["claude-3-5-sonnet", "llama3.1"]}, {"claude-3-5-sonnet": ["llama3.1"]} ]实际执行时,如果gpt-4o返回错误、超时或触发限流,代理会自动把请求转发给claude-3-5-sonnet,再失败就转向llama3.1。对客户端来说,它感知不到任何切换,只知道请求成功返回了。配合retry_policy可以精细控制什么错误才触发重试:
litellm_settings: retry_policy: Timeout: 2 RateLimitError: 1我的使用心得是:fallback别配得太激进,比如"超时5秒就切",否则上游只是抖动一下,流量就会全部倒向备用模型。我最后调成主模型重试1次、失败才切fallback,整体稳定很多。
3.4 缓存层:把重复计费的请求拦截下来
聊到成本控制,缓存是性价比最高的手段。LiteLLM支持本地内存缓存和Redis缓存,生产环境我用Redis:
litellm_settings: caching: true cache_params: type: redis host: localhost port: 6379 ttl: 3600它的缓存是基于请求内容的哈希做的,同样的model + messages组合直接命中返回,不再真实调用供应商API。对闲聊对话、客服FAQ这类高重复度场景,缓存命中率相当可观,省下的全是真金白银。
需要留个心眼:缓存默认是精确匹配,temperature这类采样参数也在哈希范围内,所以别指望它替你智能去重。如果要用语义缓存这类高级能力,得自己接向量库,这块不在默认配置内。
3.5 虚拟密钥与多团队隔离
代理模式跑起来后,不可能让所有人都用master_key。LiteLLM支持虚拟密钥体系:用master_key调/key/generate接口,生成一串sk-开头的虚拟密钥,每个密钥可以单独设置额度、限流和过期时间:
curl http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-master-key" \ -H "Content-Type: application/json" \ -d '{ "max_budget": 50, "models": ["gpt-4o", "claude-3-5-sonnet"], "expires": "2025-12-31T23:59:59Z" }'这样每个团队、每个服务各用各的Key,超了谁的额度就只挂谁,互不干扰。配合/key/info还能查每个Key的实时用量,审计链路很完整。我的做法是:内部每个业务线一把Key,评审会上一摊开,谁烧钱一目了然。
3.6 日志与可观测性:调用链路不再黑盒
模型调用最难受的是一旦出问题,根本不知道是供应商挂了、参数错了还是网络抖动。LiteLLM的callback机制可以把你选定的观测平台接进请求链路:
litellm_settings: callbacks: ["prometheus"]装了prometheus客户端后,代理会暴露/metrics端点,请求量、延迟分布、token消耗、错误码都能以指标形式拉取。如果想要更细的链路追踪,可以用langfuse这类专为大模型设计的可观测平台,每次请求的输入、输出、token用量、延迟都会完整记录。
我自己是"prometheus做监控告警,追踪平台做问题复现"的组合,线上出问题的定位时间从小时级降到了分钟级。
4. 我的踩坑链路:三个隐蔽问题与性能实测数据
光讲漂亮配置不够,把坑也抖出来才真实。下面三个问题都是我在真实环境里碰到、并走完整排查链路的,每个都附了根因和解决办法。
4.1 坑一:代理起了但请求打到了默认地址
现象是:代理服务正常运行,但用OpenAI官方的Python客户端指向我代理地址时,部分请求没走代理,直接打到了供应商官方接口。排查链路:
- 第一反应查代理日志,发现根本没有对应请求进来;
- 检查客户端的
base_url传参,发现代码里传了base_url,但构造时被环境变量OPENAI_BASE_URL覆盖了; - 确认根因——OpenAI客户端在同时存在显式传参和环境变量时,环境变量优先级在某些版本里更高。
解决办法很朴素:要么统一走环境变量,要么统一显式传参,别混用。我在项目里强制约定所有服务都显式传base_url,并在部署脚本里清空相关环境变量,自此没再犯过。
4.2 坑二:流式响应的usage突然变空
业务侧做token统计时,发现流式模式下拿不到usage字段。一开始我以为是LiteLLM没透传,抓包对比后发现是上游请求没带stream_options参数。OpenAI风格的流式接口需要用这个参数显式要求返回用量:
response = completion( model="gpt-4o", messages=messages, stream=True, stream_options={"include_usage": True} )加上之后,流的最后一个chunk里就会带usage字段。这个坑属于"官方文档写了但我没细看"的典型,希望后来的人少走一次。
4.3 坑三:多套环境变量互相打架
集成到新服务时遇到诡异问题:配置里指向A供应商,请求却用B供应商的密钥做鉴权,报401。排查链路:
- 先在LiteLLM里开启
set_verbose: true,看完整请求日志; - 发现请求打到了A供应商的地址,但Authorization头用的却是B密钥;
- 追查容器环境,发现公共镜像里预置了
API_KEY这种通用名变量,被LiteLLM误读到了。
根因是环境变量命名太通用。解决办法:容器里禁止使用API_KEY这种无前缀命名,统一改为OPENAI_API_KEY、ANTHROPIC_API_KEY这种带供应商前缀的规范名,问题彻底消失。
4.4 性能开销实测:多一跳到底亏多少
很多人在引入代理前的灵魂拷问就是:多一跳网络,延迟吃得消吗?我专门做了一组对比测试,同一段输入、同一个模型,分别走直连和走LiteLLM代理,各跑50次取中位数:
| 场景 | 首token延迟中位数 | 完整响应耗时中位数 |
|---|---|---|
| 直连供应商API | 520ms | 2.8s |
| 经LiteLLM代理 | 535ms | 2.9s |
额外开销大约在10~20ms量级,占整体耗时的1%不到。对绝大多数业务来说,这点开销完全可以用统一治理、负载均衡、fallback这些能力换回来。当然,如果你的场景对延迟极其敏感、且只有单一供应商,代理确实没必要上,这个我在下一节展开。
5. 该不该上LiteLLM:适用边界与替代方案
最后聊点务实的判断。任何工具都有适用范围,LiteLLM不是银弹,我也见过硬上之后反而添乱的案例。
5.1 接入上层框架:让现有代码无缝迁移
如果你的项目已经用LangChain这类框架,LiteLLM的接入成本会更低。LangChain内部很多组件走的是OpenAI兼容协议,只要把base_url指向代理地址即可。我迁移过一个内部工具,改动量基本就是改一个基础URL和API Key,业务层零改动。
更妙的是,因为代理对外是OpenAI格式,所以凡是用OpenAI SDK的服务,都可以通过换base_url无痛接入,这对存量系统的迁移价值巨大。
5.2 省钱的两条实践路径:低档路由与本地模型兜底
预算紧张时,LiteLLM还能帮你做两件务实的事。第一件,把非关键流量路由到便宜模型——比如把日志摘要、标题生成这类低敏感任务导到开源模型,只有核心对话走闭源大模型。第二件,把本地模型作为fallback兜底——闭源服务抖动或限流时,流量自动切到本地部署的开源模型,虽然效果差一点,但服务不会挂,业务连续性保住了。
这两条路径都不需要改业务代码,纯配置实现,是我在成本优化项目里最常用的组合拳。
5.3 不推荐的场景:什么时候别硬上LiteLLM
以下三类场景我个人会劝退:
一是长期只用一个固定供应商、也不打算换的。这种情况加一层代理纯属自找复杂度,直接调SDK更省事。
二是对延迟要求极其苛刻的实时场景。虽然实测开销只有10~20ms,但每一毫秒都千金不换的地方,能不引入中间层就别引入。
三是重度依赖供应商专属特性的。比如某些平台的特定参数、特定计费模式、特殊流式协议,LiteLLM的标准化反而可能成为约束。虽然它支持透传参数,但"能透传"和"原生体验"之间还是有差距的。
如果确定要走自研,替代方案其实就两条路:要么自己维护一套适配层,把供应商差异封装成内部接口;要么直接用各家SDK,靠业务代码里if-else硬扛。前者测试成本和维护成本都不低,后者在供应商超过三家后会明显失控。我最终选择LiteLLM,就是因为不想把这部分复杂度背在自己身上。
最后给个我个人很坚持的建议:LiteLLM版本升级前一定要看changelog,它的配置格式在几个大版本间有过破坏性调整,我就在一次升级后因为字段改名导致代理起不来。生产环境务必把配置文件和版本号一起锁进Git,升级前先在测试环境把配置验证一遍。这些坑踩过一次,后面就顺了。