如果你手上同时要接好几家大模型的 API,估计你早就被“每家一套请求格式、鉴权方式、限流策略”磨到没脾气。litellm就是干这个的:一个 Python 库,也是一个可以独立部署的代理网关,把各家大模型接口统一成一套调用方式,屏蔽底层差异。它最常用的做法是提供一个OpenAI 兼容的 HTTP 接口,你后端只管按同一套协议发请求,底层到底是哪家模型、要不要重试、怎么控制预算,全部交给 litellm 处理。
这篇文章我会从“它到底解决了什么问题”讲起,然后分别用 SDK 和代理网关两条路线带你跑通一个最小示例,再深入配置、缓存、日志、常见问题排查,最后把我实际踩过的坑和习惯做法分享出来。适合正在做多模型接入、AI 应用落地、或者想在企业内部搭一套统一 LLM 网关的开发者参考。
1. LiteLLM 到底在解决什么问题
1.1 大模型 API 的“方言”问题
说是“统一”,本质上就是解决大模型生态里的接口碎片化。今天一个应用要接大模型,面临的往往是这样的局面:有的服务商返回 JSON 里的choices字段,有的返回content,有的返回text;有的传参叫max_tokens,有的叫max_tokens_to_sample;鉴权方式也五花八门,有的是Authorization: Bearer,有的还要额外加请求头。你为一个模型写的调用逻辑,换到另一个模型通常得改一片。
我最早遇到这个痛点是在做一个“模型对比评估”的内部工具。当时要同时测五六个模型,每个模型的 SDK 用法都不一样,代码里全是if provider == "xxx": ... else: ...这种分支。后来把 litellm 引进来,所有模型统一走一个completion()方法,参数结构固定,返回结构也固定。切换模型从“改代码”直接变成“改一个字符串”,整个项目清爽得多。
1.2 一个“库”和一个“网关”两种形态
litellm 有两种用法,这是很多人一开始会混淆的地方。
第一种是SDK 模式,就是在你的 Python 代码里直接import litellm,调用litellm.completion()、litellm.embedding()。这种模式适合单机脚本、数据处理任务、快速原型,不需要额外起服务。
第二种是Proxy 模式,也叫代理网关。你启动一个 litellm 服务进程,它会对外暴露一个/v1/chat/completions之类的 HTTP 端点,你的客户端——不管是什么语言写的——只需要按照 OpenAI 兼容格式发请求。这种模式适合多语言团队、微服务架构,因为网关把模型切换、密钥管理、预算控制都收敛到了服务端,客户端不需要知道底层细节。
两种模式各有使用场景,下面我分别展开。
| 对比项 | SDK 模式 | Proxy 模式 |
|---|---|---|
| 集成方式 | pip 安装后直接调用 | 部署独立服务,通过 HTTP 访问 |
| 客户端语言限制 | 主要面向 Python | 任意语言,只要发 HTTP 请求 |
| 密钥管理 | 分散在代码或环境变量中 | 集中在网关配置和服务端环境变量 |
| 团队协作 | 适合个人脚本 | 适合多人/多服务共享同一套模型入口 |
| 成本与预算控制 | 需要自己实现 | 内置预算、限流、令牌统计 |
很多人以为 litellm 只是“调用大模型的一个库”,其实它的网关能力才是生产环境里最有价值的部分。把它理解成“通用遥控器”——你用同一个遥控器去控制不同的设备,按键位置不变,只是背后适配的电器不同。
1.3 什么时候你才真正需要它
不是所有项目都需要 litellm。如果项目只固定用一家模型,短期也不打算换,那直接官方 SDK 是最简单的。但下面这些情况,litellm 的价值会非常明显:
- 你需要做多模型热切换或灰度,比如先把 10% 流量切到新模型,剩下的继续走旧模型;
- 你需要在多个供应商之间做故障降级,一个挂了自动换下一个;
- 你希望给团队里多个服务提供统一的模型入口,同时控制总预算;
- 你需要记录每一次请求的 token 消耗和费用,做成本归因;
- 你不想给每个后端服务都配一份不同厂商的 API Key,而是通过一个集中网关统一放行。
我个人的建议是:哪怕项目目前只接了一个模型,也值得在封装层上按 litellm 的思路设计。因为大模型领域变化太快,你永远不知道半年后会不会因为价格、效果或者合规原因要换供应商,到时候已经铺好统一抽象层,切换成本会低很多。
2. 十分钟上手:SDK 调通第一个模型
2.1 安装与第一个调用
先用 pip 安装 litellm。如果你后面打算跑代理网关,建议直接装带服务依赖的版本:
pip install 'litellm[proxy]'如果只想要核心 SDK,不需要额外组件,装litellm本体就够了:
pip install litellm装完先做一个最简单的文本生成请求。注意,为了明确告诉 litellm 走哪一家服务商,模型名前面最好带上provider/前缀,例如openai/gpt-3.5-turbo:
import litellm response = litellm.completion( model="openai/gpt-3.5-turbo", messages=[ {"role": "user", "content": "用一句话介绍 litellm"} ], api_key="sk-你的密钥" ) print(response["choices"][0]["message"]["content"])如果你不想在代码里显式传api_key,也可以设置环境变量,比如export OPENAI_API_KEY="sk-xxx"。litellm 会自动读取对应服务商的环境变量,这样代码里就不需要出现任何密钥了。
我第一次跑通这个例子的时候,最大的感受是“跟调用 OpenAI 官方 SDK 几乎一样”。实际上 litellm 就是刻意保持了这个熟悉的形状:messages数组、choices[0].message.content,你之前写 OpenAI 调用的经验可以无缝迁移过来。
2.2 切换模型只改一个字符串
SDK 模式下最爽的一点,就是换模型几乎等于改一个字符串。比如我想换成另一家服务商的模型:
response = litellm.completion( model="anthropic/claude-3-haiku-20240307", messages=[ {"role": "user", "content": "用一句话介绍 litellm"} ], api_key="sk-ant-你的密钥" )注意,这里模型名带了anthropic/前缀,litellm 就知道应该按 Anthropic 的路由方式去请求,也会把返回格式转换成统一结构。所以你的上层业务代码完全不需要改,只是传入的model参数变了。
更极端一点的场景,你可以把模型名称放到配置中心或数据库里,业务逻辑读取配置后再调用。这样发版都不需要动代码,运营同学直接改配置就能切换模型。我们之前做“模型 A/B 测试”就是这么干的:同一个接口,一部分用户走了openai/gpt-4o,另一部分走了anthropic/claude-3-5-sonnet-20240620,两边返回的数据结构一摸一样,下游处理逻辑完全共用。
2.3 异步调用、重试与超时
生产环境里光能“跑通”是不够的,还得考虑性能与稳定性。litellm 的异步写法也很直观:
import litellm import asyncio async def main(): resp = await litellm.acompletion( model="openai/gpt-3.5-turbo", messages=[{"role": "user", "content": "你好"}], api_key="sk-你的密钥", timeout=15, num_retries=2 ) print(resp["choices"][0]["message"]["content"]) asyncio.run(main())这里我加了两个参数:timeout=15表示单次请求超时 15 秒,num_retries=2表示失败后最多自动重试 2 次。官方提供方默认会有内置的重试策略,但你最好根据自己的业务容忍度,显式设置这两个参数。过高的重试次数会导致接口响应时间不稳定,过短的超时又会在大模型推理稍慢时误杀请求。我一般习惯把超时设在 20 秒左右,简单对话场景可以短一点,生成长文或复杂推理场景要留足缓冲。
如果需要在脚本里同时并发调用多个模型做对比,可以用asyncio.gather把多个acompletion任务包起来。litellm 的异步实现是线程池包装,但在大多数业务并发下已经够用;真正高并发的场景,建议走后面的代理网关模式,让网关帮你管理连接池和并发控制。
3. 代理网关模式:用配置管理一切
3.1 部署一个最小网关
SDK 模式解决了“代码统一”的问题,但生成环境里,我不想把密钥和模型配置散落在每个服务的启动环境变量里。这时候就把 litellm 当作一个独立服务跑起来,让它承担统一代理职责。
先写一个最简单的config.yaml:
model_list: - model_name: text-main litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: text-fast litellm_params: model: anthropic/claude-3-haiku-20240307 api_key: os.environ/ANTHROPIC_API_KEY启动网关:
litellm --config config.yaml --port 4000启动日志出现Uvicorn running on http://0.0.0.0:4000之类的提示后,就可以用标准的 OpenAI 风格请求来访问了:
curl http://localhost:4000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-你的虚拟密钥" \ -d '{ "model": "text-main", "messages": [{"role": "user", "content": "你好"}] }'注意两点:第一,这里的model用的是配置文件里的别名text-main,不是真实的底层模型名;第二,请求头里的Authorization: Bearer如果网关没开鉴权,随便填个非空字符串也能过,但我强烈建议在生产环境开启认证,后面会讲到。
3.2 模型别名、负载均衡与降级配置
代理模式的核心就是config.yaml。很多团队把它当成模型资源的“目录”:对外是稳定别名,对内是实际模型地址和密钥配置。这样底层供应商换了,你不用通知所有调用方,只需改网关配置。
如果同一个别名下面配置多个模型实例,litellm 会自动做负载均衡。比如我有两个服务商的同档模型,希望轮流分摊流量:
model_list: - model_name: text-main litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: text-main litellm_params: model: anthropic/claude-3-5-sonnet-20240620 api_key: os.environ/ANTHROPIC_API_KEY两个条目的model_name都是text-main,客户端请求text-main时,litellm 会在两个底层模型之间轮询分发。这是做成本优化的一个关键技巧:把不同价位的模型配到同一个别名下,配合预算策略,就能在“效果”和“成本”之间做动态平衡。
再说降级配置。生产环境里某个上游供应商可能出现区域性故障,你希望自动切换到备用通道,可以在配置里加fallbacks:
litellm_settings: fallbacks: - {"text-main": ["text-fast"]}这样当主模型连续报错时,litellm 会把请求自动降级到text-fast。注意降级不是“立即切换”,它本身的触发条件和重试次数相关。我建议你把降级模型的名字放在同一个配置文件里,并且明确区分“主力模型”和“兜底模型”,避免关键时刻业务完全不可用。
3.3 开启鉴权与虚拟密钥
网关对外暴露服务后,第一个要解决的就是“谁可以调用”的问题。litellm 代理网关支持虚拟密钥(Virtual Keys),你可以理解成它帮你把真实服务商密钥藏起来,每次给下游签发一个“子密钥”。
先启用鉴权,配置里加:
general_settings: master_key: sk-master-123456master_key是管理密钥,用来访问/key/generate等一系列管理接口。然后启动网关,调用下面接口生成虚拟密钥:
curl http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-master-123456" \ -X POST返回的 JSON 里会有一个key字段,比如sk-abc123...。下游客户端拿着这个虚拟 key 访问/v1/chat/completions,litellm 会验证身份后再去调用真实模型。虚拟 key 还能设置配额、速率限制和预算,让不同团队或不同项目各用各的额度。
我实际用下来,这一步非常解决团队协作中的“密钥治理”问题。以前每个服务都要配一份不同厂商的真实密钥,出问题后根本分不清是谁在调、调了多少。现在所有人都走网关,每个人的虚拟 key 都带着自己的标记,日志里可以直接按 key 归因到团队和项目。
3.4 预算、限流与用量控制
成本控制是 litellm 代理模式里最吸引人的功能之一。你可以在litellm_settings里配置总预算:
litellm_settings: max_budget: 2000 budget_duration: 30d意思是 30 天内这个网关所有请求的费用上限是 2000 美元,超过之后 litellm 会拒绝新的请求。你也可以针对单个虚拟 key 设定预算,比如给某个内部工具只放 100 美元试用额度,防止有人写了个死循环把账单刷爆。
配合限流设置,可以给同一模型、同一 key、同一 IP 设置每分钟请求数限制。生产环境里我强烈建议开启“按虚拟 key 限流”,而不是只依赖模型上游限流。原因是你的网关前面可能有多个应用共享同一底层模型,任何一个应用的异常流量都可能连累其他应用;有了网关层限流,可以把失控应用隔离在入口处。
4. 进阶功能:缓存、日志与可观测性
4.1 响应缓存:省钱与降延迟
大模型请求不是廉价的,而且很多用户提问会很相似。比如客服知识库里“如何退款”这个问题,可能一天被问几百次。如果每次都要调大模型生成一遍,费用和延迟都不划算。litellm 支持在网关层开启语义缓存。
在配置里加一行:
litellm_settings: cache: true开启后,litellm 会对相同请求内容直接返回缓存的响应,而不触发底层模型调用。不同服务商对缓存策略的细节略有差异,但基本思路都是对请求的 messages 内容做哈希或语义匹配。我在实际项目里观察到,对高频重复问题,缓存命中后响应时间能从平均 8 秒直接掉到几十毫秒,费用也明显下降。
不过要小心长尾场景:如果你的业务里每次对话上下文都高度个性化,缓存命中率会很低,反而可能因为缓存存储多一层开销。开缓存前最好先用日志分析一下请求重复度,再决定要不要开。
4.2 日志、成功率和耗时追踪
litellm 支持把请求日志输出到本地文件,也可以接入监控系统。最简单的,启动时指定日志文件:
litellm --config config.yaml --port 4000 --detailed_debug--detailed_debug会打印每次请求的详细路由过程。但生产环境别一直开着这个级别,日志量很大,排查问题时再临时开启会更合适。
更推荐的做法是用代码注册回调。比如在代理的入口脚本里写:
import litellm def log_event(kwargs, response, start_time, end_time): print(kwargs["model"], kwargs["messages"], response.usage, end_time - start_time) litellm.success_callback = [log_event]回调里可以拿到本次请求用的模型、消息、token 用量和耗时。把这些数据写到 ClickHouse、Postgres 或 Prometheus,就能做出一套完整的模型调用监控看板。毕竟想回答“这个月花了多少钱、哪个模型请求最多、平均耗时多少”这类问题,没有日志数据就是拍脑袋。
litellm 也支持直接输出 Prometheus 指标,配合 Grafana 做可视化。这个对长期运营非常重要:我建议每个准备长期使用网关的团队,至少把请求量、错误率、P95 延迟、单模型成本这几个面板搭起来,不用一开始做得太复杂,先有数据再说。
4.3 多云环境下的密钥统一管理
代理模式最大的“隐藏福利”其实是密钥统一管理。假设团队里有五个后端服务,每个服务都需要调用两三家模型厂商的 API。如果各自持有真实密钥,一旦某家服务商要求轮换密钥,你得挨个服务去更新环境变量再重启;如果某个同事把密钥硬编码到代码里,还会带来泄露风险。
把 litellm 网关作为唯一的“出口”后,所有真实服务商密钥只存在于网关所在的服务端环境变量或安全的密钥管理服务中。下游服务只需要一个 litellm 网关签发的虚拟 key,权限和额度都可以随时回收。即使某个下游服务代码被拖走,泄露的也只是虚拟 key,通过管理接口立刻就能吊销。
这一点在安全审计时非常加分。你不再需要问“到底谁手里有真实密钥”,因为真实密钥只藏在网关层,可泄露面小了很多。
5. 生产环境实战:常见问题与排查技巧
5.1 模型名报错“Model not found”
这是新手最容易踩的坑。LiteLLM 的模型名规则是provider/model-name,例如openai/gpt-4o、anthropic/claude-3-haiku-20240307。如果你只写gpt-4o,litellm 一般会默认按 OpenAI 处理,但如果你写的是claude-3-haiku却没有带anthropic/前缀,它可能去 OpenAI 的接口找,自然找不到。
排查思路:先用 Python 简单调一下litellm.completion(model=...),看报错信息里实际请求的 URL 是哪个。如果请求的是api.openai.com,但你本意是另一家厂商,那就是前缀写错了。生产环境里更要检查config.yaml的litellm_params.model字段是否写全,而不是看model_name别名。
5.2 鉴权一直 401
如果在网关模式下,客户端传了虚拟 key,但请求返回 401,优先检查general_settings.master_key是否设置正确。如果你忘记开启鉴权,随便传个 key 是能通过的,反而说明配置没生效。
如果你直接使用 SDK 并设置api_key参数,要确认环境变量是否真的被读取,比如用命令echo $OPENAI_API_KEY看是否为空。还有一个常见问题:配置文件里写api_key: os.environ/OPENAI_API_KEY,但网关启动进程时环境变量没加载进去。这种情况我一般会在启动脚本里先 source 环境变量文件,再用litellm --config config.yaml启动,避免手动敲命令时漏掉。
5.3 超时和重试策略导致响应变慢
litellm 的默认重试逻辑在个别场景下会放大延迟。比如一个上游接口已经明显异常,每次要等 30 秒超时才报错,然后 litellm 又自动重试 2 次,相当于一个请求拖了 90 秒才返回失败。更合理的做法是缩短单次超时时间,并调低无效重试次数。
我在一个内部工具里把超时设置为 10 秒,重试设置为 0,然后让网关层的降级配置接管失败请求。这样主模型超时后直接切换备用模型,而不是反复打同一个已经异常的通道。重试和降级不要同时过度配置,两者合在一起会让故障响应时间变得很长。
5.4 预算统计与实际账单有出入
litellm 的费用计算是基于内置的模型价格表,以及每次响应返回的 token 用量。但要注意,实际账单里可能还有额外费用,比如缓存写入费用、输入缓存命中费用、网络出口流量费等,litellm 不一定全部计入。
我自己经历过一次看似“预算控得好好的”,月底账单却超出预期。后来发现是模型价格表没有及时更新,某个模型的新版本价格已经变了,litellm 内置表还是旧价格。最稳妥的做法是:定期手动核对 litellm 版本升级公告中的价格表变更;同时以服务商后台的账单为准,litellm 的预算更适合做“硬控制闸门”,而不是绝对财务预测。
5.5 网关日志过多导致磁盘爆炸
--detailed_debug打开的调试日志量非常大,尤其在高并发请求下,一天可能写出几个 GB。我见过不止一次线上服务因为日志文件占满磁盘而挂掉的。
建议生产环境把日志级别调到 INFO 或 WARNING,并且配合 logrotate 做日志轮转。如果你已经把日志输出到标准输出,再交给 Docker 或 K8s 的日志采集服务管理,尽量别同时再写一份本地文件,否则就是重复存储和重复计费。
6. 避坑与建议:我实际用下来的几点心得
6.1 先在 SDK 模式下做验证,再上代理网关
我见过不少团队上来就直接搭网关,结果配置里的模型名写错了,请求路由不生效,排查半天。其实最稳妥的方法是先在本地 Python 脚本里用 SDK 模式把你需要用到的模型全部跑一遍,确认模型名、参数、返回结构都符合预期,然后再把模型名迁移到config.yaml中。
这个“先 SDK 后 Proxy”的顺序还有一个好处:可以直接在本地用--detailed_debug看每个模型的实际请求和返回,不用反复重启网关服务。配置问题定位会比在远程服务器上快很多。
6.2 用环境变量管理密钥,不要写进配置文件
虽然配置文件里可以硬编码 API Key,但我强烈建议不要这么干。你的config.yaml很可能会被提交到代码仓库、在同事之间分享、甚至粘贴到调试群里。一旦真实 key 泄露,损失的不是一条 key 的钱,而是整个账户的安全。
正确做法是:配置文件里写api_key: os.environ/某变量名,密钥本体放在网关所在机器的环境变量、或者专门的密钥管理服务里。我甚至会为不同环节准备不同的 key:开发环境一套,生产环境一套,单独可以吊销,互不影响。
6.3 降级配置要做“故障演练”
很多人配好 fallbacks 之后就不管了,觉得已经高枕无忧。但 fallback 触发条件往往和错误类型、重试次数相关,你不实际演练,根本不知道在真实故障下它会怎么表现。
我建议你在大促或重要活动之前,专门挑一个低峰期,临时把主模型改成不存在的模型名,或者故意把 API Key 改错,然后发几个测试请求,观察网关是否按预期把请求降级到了备用模型,以及客户端侧的耗时变化。这个演练成本很低,但能提前暴露降级逻辑里的配置错误,比线上故障时手忙脚乱强得多。
6.4 给模型名加语义清晰的别名
网关模式下,下游能看到的是model_name别名,而不是底层真实模型名。你可以把别名按“用途”命名,而不是按“厂商型号”命名。比如text-main、text-fast、embedding-general,会比gpt-4o、claude-3-5这种名字更稳定。
原因很简单:当底层模型要从gpt-4o换成gpt-4o-mini时,上游text-main不需要变,客户端也不用发版。如果直接把真实模型名暴露给下游,你每次调整模型组合都相当于一次接口变更,会牵连大量调用方。别名虽然多写一步配置,但长期维护轻松很多。
6.5 版本升级不要太随意
litellm 迭代速度很快,新功能很诱人,但我不建议在核心网关环境追最新。尤其是从大版本跨越升级时,配置文件的兼容性可能会有变化。我吃过一次亏:升级后原来的fallbacks写法失效,请求不再降级,直到一次故障演练才发现。
现在我的习惯是:升级前先看 changelog,重点搜breaking change;升级后立即跑一遍完整的冒烟测试,包括多模型调用、鉴权、降级、预算这几个核心链路。日常开发预发环境可以追新,生产环境适当保守一点。
最后再分享一个小习惯:我会在项目里放一个“网关健康检查”脚本,每天定时用虚拟 key 请求一次text-fast模型,并把成功率和耗时记录到监控面板。这样底层供应商有没有波动、网关配置有没有被误改,第二天一早就能发现。litellm 把多模型接入抽象得足够简单之后,这类基础设施型的小工具反而成了最值得投入的部分。