如果你最近在做 AI 应用,手里同时捏着好几家大模型服务的 API Key,大概率跟我一样被“接口格式五花八门”这事烦过:字段命名、鉴权方式、错误码各家各派,业务代码里写满了 if-else 适配逻辑,每新增一家供应商就要连带改一遍调用层。litellm 是我实际用下来比较顺手的一个统一接入层,它用一套兼容主流聊天补全格式的 SDK,把不同模型提供方的 API 拢成同一种调用方式,同时把代理网关、限流、预算、自动重试这些生产环境刚需做成了开箱即用的功能。如果你是做 AI 应用开发、需要接多个模型做对比或备灾,或者想给团队提供一个统一的模型访问入口,这篇实操笔记应该对你有用。
1. 项目定位与核心设计思路
1.1 它到底在解决什么痛点
我最早接触 litellm 是在一个多模型接入的模拟项目 X 里。当时业务方要求同时接主流的闭源模型和开源模型,用来做效果对比和故障切换。表面上看,各家提供的都是 HTTP 接口,都是“发一段对话消息,收一段回复”,可真写起代码来完全不是一回事:
- 请求体的字段名不一样,有的叫
prompt,有的叫messages,有的把系统提示和用户消息拆成不同结构; - 鉴权方式不一样,有的用请求头带固定 Key,有的需要单独维护一个 access token 流程;
- 返回结构不一样,有的直接给文本,有的包了几层 choices、message、content;
- 错误码和限流语义更乱,同样是“请求太频繁”,有的返回 429,有的返回 200 但塞了一段错误文本。
这些差异累加起来,谁接入谁知道。最难受的不是第一次接入,而是后续每次版本升级、新增模型、切换供应商,都要把调用层重新过一遍。后来我看到了 litellm 的思路,简单说就是加一层“翻译适配层”:业务代码永远只对着同一套格式写,由 litellm 在底层把请求翻译成目标供应商真正认识的格式。这个心智模型非常朴素,但确实解决了真实问题。
1.2 为什么“统一格式”这条路能走通
有人会问:不同模型的内部实现差那么多,统一格式是不是强行抽象、得不偿失?我的理解是,之所以这事能成立,关键在于大语言模型调用本质上可以被压缩成一个非常稳定的模式——把一组对话消息送进去,拿到一段补全出来的 tokens。文本对话这个抽象级别已经足够高,各家模型的差异基本都发生在序列化细节和鉴权细节上,而这些细节恰好是适配层最容易处理的部分。
litellm 的具体做法是给每个供应商写一个适配器,入参统一收,再按目标格式转发。因为大多数供应商都在向主流聊天补全接口靠拢,兼容成本被持续压低。这个设计还有一个很实际的好处:它不强迫你改变写代码的习惯。你原来怎么调模型接口,用 litellm 还是怎么调,只是把底层实现换了一下。业务代码里的 messages、temperature、max_tokens 这些核心参数都能保留,迁移成本低得多。
我在项目里实际体会更深的是它的“轻”。litellm 是一个纯 Python 库,没有引入笨重的重组件,也不需要单独起一个服务才能用最简单的调用场景。你可以先在自己的脚本里直接调用,验证某个模型的效果,再决定要不要把代理模式架起来给团队共用。这种渐进式采用路径,比一开始就让你上全套平台的做法务实不少。
2. 快速上手与基础调用
2.1 安装与第一个请求
先说最基础的东西。安装就是一条命令:
pip install litellm装完之后,最简单的调用方式是这样:
import litellm resp = litellm.completion( model="provider-a/model-x", messages=[ {"role": "system", "content": "你是一个简洁的助手"}, {"role": "user", "content": "用一句话介绍一下你自己"}, ], api_key="sk-xxxx", ) print(resp.choices[0].message.content)这里有两个细节值得新手注意。
第一是model参数的命名规则,标准格式是“供应商标识/模型标识”。比如你接的是厂商 A 的模型 X,那就写provider-a/model-x。这个前缀很关键,litellm 靠它判断该走哪个适配器。如果你只写模型名不加前缀,它默认会按主流格式去匹配,接非默认供应商时就会出问题。
第二是api_key参数。你也可以不传这个参数,改为提前在环境变量里配置好对应的 Key。很多供应商的 SDK 都是这个套路,litellm 保持一致,省得在代码里裸奔敏感信息。实际项目里我更推荐环境变量或专门的密钥管理方案,而不是把 Key 写死在源码里,这个后面会专门说。
初次跑通之后,建议立刻试一下流式输出,也就是把请求里的stream参数设为True。流式响应在你做对话类产品时几乎是必选项,litellm 对这块的处理和主流 SDK 的习惯一致,返回一个生成器,你可以逐段消费内容:
resp = litellm.completion( model="provider-a/model-x", messages=[{"role": "user", "content": "讲一个短故事"}], stream=True, ) for chunk in resp: if chunk.choices and chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="")2.2 用主流客户端的方式调用
如果你已经在用主流聊天补全客户端类库,litellm 也提供了一个几乎零成本的迁移路径:换一下 import,剩下的代码基本不动。
from litellm import OpenAI client = OpenAI( api_key="sk-xxxx", ) resp = client.chat.completions.create( model="provider-b/model-y", messages=[{"role": "user", "content": "写一首关于秋天的短诗"}], ) print(resp.choices[0].message.content)这背后的设计用意很明显:litellm 不想让你重学一套新 API,而是让你已有的代码继续工作,只要把model里的供应商前缀改一改,就能切换到另一个模型。我在迁移老项目时,大部分函数体都没动,主要改的是 import 和 model 字符串,收益非常直接。
除了基础调用,litellm 还提供了几个很实用的辅助能力,我建议尽快用起来:
litellm.acompletion():异步版本,适合高并发场景;litellm.mock_response:测试时不需要真实 Key,直接返回固定内容,用来验证链路;timeout参数:控制请求超时时间,避免长时间无响应;num_retries参数:设置自动重试次数。
我特别想强调mock_response的价值。你在没有 Key、或者不想消耗真实额度的时候,可以用它先把业务逻辑跑通。它让前端联调和接口自测不再依赖外部服务是否可用,这个细节看起来小,实际省事不少。
3. 从统一 SDK 到代理网关
3.1 一键起一个模型网关
基础调用只是 litellm 的一半价值,真正让它从“个人工具”变成“团队基础设施”的,是内置的代理服务器模式。简单说,你可以把 litellm 起成一个独立服务,所有业务后端都请求这个服务,由它负责转发到真正的大模型服务。这样做的好处是:各家供应商的 Key 只存在代理服务端,业务端一律走内部网关,密钥暴露面小很多;多模型切换、限流、预算这些策略也集中在网关层,不需要每个服务各写一遍。
启动方式非常直接:
litellm --config config.yaml --port 4000配一个config.yaml,把你要接的模型都列出来:
model_list: - model_name: chat/main litellm_params: model: provider-a/model-x api_key: sk-xxx - model_name: chat/fast litellm_params: model: provider-b/model-y api_key: sk-yyy - model_name: chat/fallback litellm_params: model: provider-c/model-z api_key: sk-zzz这里有一个非常关键的设计:model_name是你自己定义的逻辑名,业务代码只认这个名字;真正对接哪个供应商的哪个模型,由配置决定。也就是说,你可以在完全不改业务代码的前提下,把chat/main从厂商 A 的模型 X 切换成厂商 B 的模型 Y,改配置重启一下就行。
网关起好后,业务端直接请求:
POST http://localhost:4000/v1/chat/completions请求体还是你最熟悉的格式,服务端会自动按model_name找到对应的真实模型,把请求翻译过去,再把响应翻译回来。我在项目里测试过,从业务代码视角看,它就是一个普通的大模型接口,完全无感。
3.2 限流、预算、重试与密钥安全
代理模式一旦面向团队开放,光能转发是不够的,生产环境要考虑稳定性和可控性。litellm 在这块给了我不少惊喜,主要集中在这几件事上:
- 密钥管理:网关持有各家的真实 Key,业务端只认网关自己的身份标识。即使某个业务服务被拖库,也不会直接泄露大模型供应商的 Key。
- 限流控制:可以按调用方、按用户、按 IP 做 RPM(每分钟请求数)和 TPM(每分钟 token 数)限制。比如测试环境只给 10 RPM,防止有人拿脚本把额度刷爆。
- 预算管理:给某个 Key 或团队设置 token 用量上限或费用上限,超过阈值直接拒绝请求。这个我强烈建议早配上,尤其是团队里多人共用网关时,预算失控的账单会非常难看。
- 自动重试与故障转移:请求失败时,可以配置重试次数,也可以配置 fallback 模型。比如主模型超时,自动切到备用模型。
我见过不少团队在初期图省事,业务代码直接拿着各家 Key 满天飞,结果要么是某个同学不小心把 Key 提交到仓库,要么是一个服务限流把整个业务拖崩。用代理模式集中管理后,这些问题的面会小很多。注意,代理模式不是银弹,它只是把复杂性收敛到了一处,网关本身的可用性和安全策略仍然需要你投入精力。
3.3 多模型路由与动态切换
除了基础转发,litellm 还支持模型路由能力。你可以把一组模型定义成一个逻辑组,让请求按策略分发。最典型的场景是“主模型 + 备用模型”:主模型在高峰期限流或报错时,请求可以自动切到备用模型,保证业务不中断。
路由配置的核心是Router,在 Python 代码里可以这样用:
from litellm import Router router = Router( model_list=[ { "model_name": "chat/main", "litellm_params": { "model": "provider-a/model-x", "api_key": "sk-xxx", }, }, { "model_name": "chat/main", "litellm_params": { "model": "provider-b/model-y", "api_key": "sk-yyy", }, }, ] ) resp = router.completion( model="chat/main", messages=[{"role": "user", "content": "你好"}], )路由器的价值不只是负载均衡。它内部会跟踪每个上游模型的健康状态,对连续失败的实例做摘除,避免请求持续打到坏掉的模型上。这个机制在真实业务里非常有用,因为任何一家上游服务都不可能承诺 100% 可用,与其自己维护一套故障转移逻辑,不如让网关层把这个做掉。
4. 生产环境实战与避坑
4.1 高频报错与快速定位
实际跑生产之后,你会遇到一些非常典型的问题。我把最常碰到的现象和处理思路整理成了表格,方便你排查时直接对上号。
| 现象 | 常见原因 | 处理思路 |
|---|---|---|
| 401 鉴权失败 | Key 缺失、错误或环境变量没生效 | 确认 Key 正确性;检查环境变量是否真的注入到运行进程;避免代码里覆盖环境变量 |
| 400 / 404 模型不存在 | model 命名不规范,供应商前缀写错 | 按“供应商标识/模型标识”格式核对;在网关配置里确认 model_name 映射正确 |
| 429 限流 | 触达上游 RPM/TPM 限制 | 配置重试与退避;降低并发;扩大上游配额;检查是否有调用方在疯狂循环 |
| 请求超时 | 上游响应慢、网络链路问题、长输出任务 | 显式设置 timeout;开启流式响应;为长任务合理设置更长的读超时 |
| context length exceeded | 输入加输出超过模型上下文窗口 | 截断或压缩早期对话;拆分长文本;换用上下文更长的模型 |
| 返回内容异常截断 | 达到 max_tokens 上限 | 调大 max_tokens;设置更合理的停止条件;检查流式拼接逻辑是否丢 chunk |
这里我要多说一句 429 的问题。litellm 在重试时会比较智能地处理限流类错误,但你自己写重试逻辑时要注意别把 429 和企业内部失败混为一谈。无脑重试不仅浪费额度,还可能把故障放大。更合理的做法是区分错误类型:429 和 5xx 可以重试,4xx 代表请求本身有问题,重试没意义,应该直接暴露给调用方。
4.2 超时、网络与重试调优
我第一次把 litellm 代理部署到服务器上时,犯过一个典型错误:没有显式设置超时。结果某天上游模型服务响应卡住,我的代理进程也跟着一直挂起,请求队列越堆越长,最终把整个后端拖到不可用。后来我学乖了,所有转发请求都强制设置timeout,并且把超时分为连接超时和读超时来处理。
在实际配置里,我会这样处理:
litellm.timeout = 60 # 全局默认 60 秒如果某个模型需要处理长文档,我单独给它放宽;如果是对话类短请求,我会设更短的超时,比如 15 秒,然后配合重试。这样做的意义是避免“一个慢请求拖死一批并发”。
网络层还有一个容易踩的坑:如果你所在环境存在内部代理或特殊网络策略,litellm 转发到外部大模型服务的链路可能会不稳定。排查时不要只盯着应用日志,也要检查服务端到目标供应商之间的实际连通性。这类问题往往表现为“调用偶尔成功偶尔超时”,定位起来最耗时间。
重试策略我建议遵循“指数退避 + 抖动”的思路:第一次等待 1 秒,第二次 2 秒,第三次 4 秒,再加一点随机抖动,避免多个请求同时重试形成惊群效应。litellm 本身支持配置重试次数,但我仍然建议在业务层面对“哪些错误允许重试”做一层判断,别无脑开。
4.3 成本、日志与安全
成本管控是生产环境绕不开的话题。litellm 的响应里带 usage 信息,包含 token 用量明细,网关侧也有预算管理能力。我在团队里的做法是:每个调用方分配独立 Key,设置月度预算上限,超过就自动拒绝请求,再配合一个定时任务把用量统计发到内部群。这样即使某个同学写了个死循环调用,也不会把当月的模型额度一次性打光。
日志和敏感信息处理是我特别想提醒的一点。发往大模型服务的请求内容,往往包含业务数据和用户输入,这些数据如果全量打进日志,会有不可控的泄露风险。我见过有人把完整请求体和响应体直接 print 到日志,排查问题时确实方便,但一旦日志被第三方看到,后果很严重。建议日志里只记录消息长度、模型名称、耗时、错误码这些元信息,对实际内容做截断或脱敏。
密钥安全更是老生常谈:不要把真实 Key 提交进代码仓库,不要在配置库里用明文保存,不要让业务端直接接触上游供应商 Key。统一收口到网关,再配合定期轮换,才能把风险控制在可接受范围。
5. 我的真实使用体验与选型建议
5.1 什么场景适合用它,什么场景要谨慎
用了一段时间后,我对 litellm 的适用边界有了比较清晰的认识。如果你遇到这些情况,它大概率能帮上忙:
- 业务需要同时接入多个大模型供应商,做效果对比或价格对比;
- 团队内部想统一模型访问入口,不想让每个服务各自适配一遍;
- 需要给不同项目、不同成员设置独立的调用配额和预算;
- 希望具备模型级故障转移能力,避免单点依赖某一家服务。
反过来,如果你对模型调用协议有极强的定制需求,或者你们已经在自研整套推理路由体系,那引入 litellm 会带来一层额外抽象,反而增加复杂度。它更适合作为“快速接入 + 统一治理”的中间层,而不是替代你所有底层基础设施。
还有一个建议:不要一上来就追求把所有模型都接入。先选两个真实的业务场景,把代理网关跑稳,再逐步扩展。我在项目里的感受是,litellm 的价值随着接入模型数量增加而放大,只有一两个模型时反而感受不到多少收益。
5.2 我踩过的几个深坑
最后分享几个我实际踩过的坑,希望能帮你省点时间。
第一个坑是 model 命名不统一。一开始我在配置里把model_name直接取成上游模型的原始标识,结果上游模型改名或切版本时,业务代码里的逻辑名也跟着乱掉。后来我强制规定:model_name一律用业务语义,比如chat/main、embedding/zh,上游真正用哪个模型版本,只在网关配置里维护。逻辑名和物理模型彻底解耦后,切模型变成了一件很轻的事。
第二个坑是没配预算。曾经有段时间,一个内部测试服务因为循环调用没退出来,一个晚上消耗了相当于平时一周的 token 量。从那以后,“任何 Key 都带预算上限”成了我这边部署的铁律。你可以把预算设得很宽松,但不能没有。
第三个坑是测试时偷懒。早期我用真实 Key 直接跑自动化测试,既慢又费钱,偶尔还会因为限流导致测试失败。后来改用mock_response做链路测试,只有专门的集成测试才打到真实模型,速度和质量都上来了。
如果你准备在项目里引入 litellm,我建议从最简单的方式开始:先在你的本地脚本里把litellm.completion用起来,体验一下多模型切换的便利;再架代理,把 Key 收口;最后才是限流、预算、路由这些治理能力。一步一步来,比一开始就铺开整套方案要稳得多。
我现在的习惯是,凡是需要接多个大模型供应商的新项目,都会优先考虑用一个统一接入层把入口收住。litellm 这个项目解决了很多“手搓适配层”的重复劳动,虽然它不能替代你对模型本身的判断,但作为模型与业务之间的翻译官和守门员,表现确实扎实。