litellm 这个开源项目,我第一次注意到它是在一个 AI 应用的架构评审会上。当时团队的代码已经快被各家模型服务商的 SDK 塞满了,切换一个模型要改连接方式、换鉴权逻辑、重新处理返回结构,纯粹是体力活。后来有人提了一句“用 litellm 做个统一网关”,我才开始认真研究它。简单说,litellm 就是一个大模型 API 的统一代理层,把不同服务商五花八门的请求和响应格式翻译成一套标准格式,同时把重试、限流、负载均衡、预算控制这些基础设施问题一并接管。这篇文章不打算复述官方文档,而是从我把它接入真实项目的经验出发,聊聊它解决什么问题、核心设计逻辑是什么、完整部署一次要经过哪些步骤,以及我在实操中踩过的坑。
1. 它到底解决什么问题
1.1 多家模型 API 的“翻译层”
做 AI 应用的人基本都经历过这种痛苦:你最初只接了一家服务商的接口,一切正常。后来想对比另一家的效果,或者想做模型 fallback,结果发现每一家的 SDK 都有自己的脾气——有的请求体叫messages,有的叫prompt;有的用max_tokens,有的用max_new_tokens;鉴权方式更是各搞一套。把这些差异全部封装一遍,代码里就长出了一大堆 if-else 和适配函数,而且每接入一家新的服务商,你都要重新读一遍文档、写一遍适配。
你可以把 litellm 想象成电源转换头。你家的电器插头各式各样,但墙上的插座只有一种标准接口。litellm 做的就是把这些插头统统转换成同一个规格,让你业务代码只需要面对一种接口语言。
它在底层维护了一张庞大的“翻译映射表”,知道每个服务商的模型对应的真实请求格式是什么,然后把统一格式转换成目标服务商能识别的样子。对业务层来说,你永远只调一套接口。这个思路本身并不复杂,但真正难的是把几十家服务商的差异细节都处理好,包括鉴权、超时、重试、流式输出、上下文长度、错误码映射等等。litellm 的价值恰恰在于它把这些脏活累活都做完了。
1.2 从 SDK 到 Proxy 的两级演进
litellm 实际上提供了两种使用方式,理解这两者的区别很重要。
第一种是把它当 Python 库直接嵌入到你的项目里。你在代码里调用litellm.completion(),传入的参数是统一格式,它内部负责找到对应的服务商并完成调用。这种方式适合脚本、原型、单体应用,改动最小,也不需要额外部署服务。
第二种是把它跑成一个独立的 Proxy 服务。这个服务对外暴露一个统一的 HTTP 接口,你所有的后端服务都请求这个网关,再由网关去请求真正的模型服务商。这种方式适合团队协作、微服务架构、需要权限隔离和成本管控的场景。
| 对比维度 | Python SDK 模式 | Proxy 模式 |
|---|---|---|
| 部署成本 | 无额外服务,直接装库 | 需要一个常驻服务 |
| 适用场景 | 脚本、简单后端 | 多业务线、多团队、生产环境 |
| 密钥管理 | 服务商密钥分散在各处 | 集中存放在网关侧 |
| 权限控制 | 基本没有 | 支持虚拟密钥和预算隔离 |
| 功能覆盖 | 核心调用能力 | 包含负载均衡、缓存、审计日志 |
实际项目里,我建议刚接触的人先用 SDK 模式跑通流程,理解 litellm 是怎么统一接口的;等确实有了多模型切换、成本管控、密钥安全这些需求,再平滑迁移到 Proxy 模式。因为 Proxy 模式的所有能力,本质上都是在 SDK 模式的能力之上堆叠出来的,理解了底层,上层用起来才不会觉得黑盒。
2. 核心设计拆解
2.1 统一接口标准是怎么统一的
litellm 最核心的设计思路,是把所有模型服务商的接口抽象成一套“规范格式”。你只需要按照这套格式发请求,litellm 负责完成剩下的翻译工作。
以文本生成接口为例,你向 litellm 提交的请求体大致是:
{ "model": "main-chat", "messages": [ {"role": "system", "content": "你是一个助手"}, {"role": "user", "content": "解释一下什么是统一网关"} ], "temperature": 0.7, "max_tokens": 1000 }这个格式是不是很眼熟?它基本沿用了业界主流兼容接口的约定。litellm 拿到这个请求之后,会根据你在配置文件里指定的服务商和模型名,把它翻译成目标服务商需要的真实格式。比如有的服务商用system字段而不是messages里的 system 角色,有的服务商对temperature的取值有特殊限制,有的服务商要求单独的上下文管理参数,这些细节都由 litellm 处理掉了。
响应格式也是一样。litellm 会把各家服务商不同的返回结构归一化成统一的{"choices": [...], "usage": {...}}结构,这样业务代码在处理结果时完全不用关心你实际用的是哪家服务。
更重要的是错误处理层的统一。各家服务商在限流、鉴权失败、模型不存在等场景下返回的错误码和错误体各不相同。litellm 把这些归一成一套统一的异常体系,你在业务代码里只需要针对RateLimitError、AuthenticationError、TimeoutError做处理,不用再去逐个解析各家服务商的错误响应。
这套“统一层”的价值在于,它把业务代码和服务商彻底解耦了。今天你用的是服务商 A 的模型,明天想换成服务商 B 的模型,在 litellm 里只需要改配置,业务代码一行都不用动。
2.2 Router 网关内置了哪些能力
litellm 真正让人愿意在生产环境里使用的原因,不是简单的接口翻译,而是它内置了一个功能相当丰富的大模型流量网关。我在实际使用中最常用到的能力有这些:
- 负载均衡:同一个模型可以配置多个服务商实例或密钥,litellm 会按照策略把请求分发到不同的上游。
- 自动重试:某个上游超时或报错时,自动换一个上游重试,而不是把错误直接抛给用户。
- fallback 降级:主模型不可用时,自动切换到备选模型,保证业务不中断。
- 预算控制:可以设置全局预算、每个密钥的预算,超出之后直接拒绝请求。
- 虚拟密钥管理:把真实服务商密钥藏在网关里,对外发放一串虚拟密钥,即使泄露也不会直接拿到上游权限。
- 缓存:支持简单缓存和语义缓存,命中缓存的请求不经过上游模型,省钱也省延迟。
- 审计日志:记录每一次请求的模型、令牌数、耗时和费用,方便复盘。
这些能力并不是零散地堆在一起,而是由 litellm 核心的路由器组件来统一调度。你发一个请求过来,路由器会根据配置决定:走哪个模型、哪个上游、需不需要走缓存、预算是否允许、失败了怎么重试。这整个流程是可以灵活配置的,接下来我会详细讲配置文件。
2.3 config.yaml 是核心蓝图
在 Proxy 模式下,你几乎所有的控制逻辑都写在一个 YAML 文件里。这个文件就是网关的“蓝图”。刚接触的人可能会被它吓到,其实核心只需要理解三个配置块。
model_list是模型清单,定义了你对外暴露哪些模型名,以及每个名字背后实际对应哪家服务商的哪个模型。这一块是最需要仔细设计的,因为模型名的映射关系一旦定好,后续业务代码依赖的就是这个逻辑层。
litellm_settings是 SDK 行为设置,比如是否丢弃不受支持的参数、是否启用请求缓存、默认的重试次数等。
router_settings是路由器的策略配置,比如负载均衡策略、优先级、熔断规则。
还有一个general_settings用来配置全局层面的内容,比如 master key、数据库连接地址、监控开关等。
理解这个结构的顺序很重要:先想清楚你要暴露哪些模型名,再配置每个模型名背后的真实服务商信息,最后再调路由策略和审计预算。如果顺序反了,很容易陷入细节里出不来。
3. 从零实操:部署一个统一网关
3.1 安装与最小化配置
我用的是常规的 Python 环境部署方式,安装命令很简单:
pip install 'litellm[proxy]'安装完成之后,新建一个最小化的配置文件,命名config.yaml:
model_list: - model_name: "main-chat" litellm_params: model: "provider-a/large-chat" api_key: "os.environ/PROVIDER_A_KEY" general_settings: master_key: "sk-master-local-001"注意这里有几个关键点。
model_name是你在业务代码里最终要用的模型名,它相当于给真实模型起了一个“别名”。litellm_params.model是真实的服务商标识加模型名,格式是“服务商前缀/模型名”。api_key我强烈建议不要明文写在配置文件里,而是通过os.environ/PROVIDER_A_KEY这种形式引用来环境变量。这样即使配置文件不小心提交到代码仓库里,也不会泄露密钥。
这就是一个能跑起来的最小配置。你不需要在这里配置任何路由策略,litellm 会有默认行为,先把链路跑通比什么都重要。
3.2 启动 Proxy 并验证健康状态
启动网关只需要一条命令:
litellm --config config.yaml --port 4000启动成功之后,先验证两个基础端点。第一个是健康检查:
curl http://localhost:4000/health/liveliness返回正常状态说明服务起来了。第二个是查看已注册的模型列表:
curl http://localhost:4000/v1/models这里能看到你配置的main-chat,这就说明 litellm 已经正确读取了配置,并且把模型名注册到了统一接口上。
我当时的习惯是先看健康检查,再看模型列表,最后才发真实对话请求。这样做的好处是,如果最后一步出了问题,你能快速定位到底是网关没起来、配置没加载,还是上游服务商的密钥或网络有问题。
3.3 一次性接入多家服务商
接下来演示怎么把多家服务商接进来。假设你想同时接入两家服务商,配置是这样的:
model_list: - model_name: "main-chat" litellm_params: model: "provider-a/large-chat" api_key: "os.environ/PROVIDER_A_KEY" - model_name: "main-chat" litellm_params: model: "provider-b/chat-pro" api_key: "os.environ/PROVIDER_B_KEY"注意这里我特意让两个条目的model_name都叫main-chat。这是 litellm 支持的一个很有用的特性:多个上游共享一个逻辑模型名。对业务代码来说,它根本不知道也不关心请求实际去了哪家,只知道调用main-chat就有响应。
这种设计带来的直接好处是,你可以随时在配置层面调整某个逻辑模型背后的真实来源,而不需要业务方做任何配合。比如服务商 A 的模型质量下降,你只需要把配置里对应条目的权重调低,或者干脆移除这个条目,业务方无感知。
多个上游共享一个模型名之后,路由器就会介入调度。默认情况下它会在可用的上游之间做轮询或随机分发,达到负载均衡的效果。
3.4 从客户端怎么调用
现在网关已经跑起来了,业务方怎么接入呢?这是整个方案里最让人舒服的部分:几乎零改动。
只需要在原来的客户端 SDK 里,把接口地址指向 litellm 网关,密钥换成网关的 master key,模型名换成你在配置里定义的别名:
# 以兼容统一格式的客户端为例 from demo_client import Client client = Client( base_url="http://localhost:4000", api_key="sk-master-local-001" ) resp = client.chat.completions.create( model="main-chat", messages=[ {"role": "user", "content": "你好"} ] )这个迁移成本非常低。业务方原来怎么写,现在就怎么写,只改三处:base_url、api_key、model名。我实践下来,一个中等规模的后端服务,迁移到 litellm 网关通常只需要半天时间,大部分时间都花在改配置和测试环境切换上,代码改动几乎没有。
这也是 litellm 最让人省心的地方:它不是让你重写一套调用逻辑,而是把你已有的调用逻辑完整兼容下来,让你在不知不觉中把底层基础设施换掉了。
4. 进阶玩法:把网关变成真正的流量管家
4.1 负载均衡策略怎么选
当同一个逻辑模型名背后挂了多个上游时,litellm 的负载均衡策略就起作用了。常见的策略有这么几种,我整理成了表格:
| 策略名 | 行为特点 | 适用场景 |
|---|---|---|
| 默认随机 | 随机选择一个上游 | 各上游能力和成本差异不大时快速上手 |
| 最少请求优先 | 选择当前并发请求数最少的偶尔上游 | 上游处理能力差异明显时 |
| 基于用量路由 | 综合响应时间、错误率、成本等多维度评分 | 生产环境追求稳定性和成本平衡 |
| 固定优先级 | 始终优先走排在前面的上游 | 主备切换、有明确优先级诉求 |
我建议初期先用默认随机策略跑一阵,沉淀一些请求日志和性能数据之后,再切换到基于用量的路由。因为基于用量的策略需要依赖历史数据来做判断,数据积累不够的时候,效果不一定比随机好。
配置方式是在router_settings里:
router_settings: routing_strategy: "least-busy"改完之后重启网关即可。注意观察请求日志,确认策略确实在按预期分发。
4.2 fallback 降级和重试配置
生产环境里最怕的是主模型突然不可用。litellm 的 fallback 机制能很好地解决这个问题。我之前遇到过上游服务商临时限流,导致全部请求都报错的情况,配置了 fallback 之后,请求自动切到备选模型,业务几乎零感知。
配置方式也不复杂,在model_list里给逻辑模型名增加 fallback 条目:
model_list: - model_name: "main-chat" litellm_params: model: "provider-a/large-chat" api_key: "os.environ/PROVIDER_A_KEY" - model_name: "main-chat" litellm_params: model: "provider-b/chat-pro" api_key: "os.environ/PROVIDER_B_KEY" router_settings: fallbacks: [ {"main-chat": ["main-chat-fallback"]} ]这里的意思是,如果main-chat请求彻底失败,就自动尝试列表里的备用模型。你可以把 fallback 理解为请求的最后一道保险。
重试机制也需要单独配置。注意不要盲目把重试次数调得很高,否则一个上游已经出现异常时,大量重试请求反而会加剧问题。我个人的经验是,重试次数设在 2 到 3 次,超时时间根据业务场景调整。如果业务需要快速响应,超时设短一些;如果是离线批量任务,超时可以调长一点。
4.3 预算控制和成本追踪
预算控制是 litellm 吸引企业用户的一个重要功能。它让我不用再担心“这个月模型费用超支”这种问题。
在general_settings里配置一个全局预算和一个持久化数据库:
general_settings: master_key: "sk-master-local-001" database_url: "sqlite:///litellm.db" max_budget: 1000 budget_duration: "30d"这段配置的含义是:每 30 天为一个预算周期,周期内总花费超过 1000 美元时,litellm 会拒绝新的请求。实际经验是,预算设置一定要留出足够余量,因为模型计费的粒度很细,需要在“防止超支”和“不影响正常业务”之间找一个平衡。
成本追踪依赖database_url配置。有数据库之后,litellm 会把每一次请求的模型、令牌数、费用记录到数据库里。你可以通过内置的审计端点查询,也可以直接把数据库接到你自己的监控面板上。我通常会在数据库里加一个定时查询,把每日消费汇总发到团队的消息群里,让大家对成本有个直观感知。
4.4 虚拟密钥和多租户
在 team 协作场景下,你肯定不希望把所有业务方的请求都用同一个 master key 暴露出去。litellm 支持虚拟密钥体系,也就是在网关层创建一个带有权限限制的 key,发给各个业务方使用。
创建虚拟密钥的接口大体是:
curl -X POST http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-master-local-001" \ -H "Content-Type: application/json" \ -d '{ "models": ["main-chat"], "max_budget": 100 }'返回结果里会有一个新的 key,这个 key 只能调用main-chat模型,而且预算上限是 100。业务方拿这个 key 去请求网关,完全接触不到真实服务商的密钥。
这个能力非常实用。多支团队共用同一个网关时,你可以给每个团队发一个独立 key,单独设预算和模型范围。即使某个团队的 key 泄露了,影响范围也被限制在它自身的预算和模型权限内,而不是整个网关的密钥泄露。
4.5 缓存策略
litellm 支持请求缓存,可以在两层生效:第一层是 Redis 或 SQLite 存储的响应缓存,第二层是语义缓存。
简单缓存很好理解:一模一样的请求直接返回上次的结果,不再请求上游模型。适合的场景是高频、结果固定、实时性要求不高的业务,比如常见问题回复、固定模板生成。
语义缓存更聪明一点,它不是严格匹配请求文本,而是根据语义相似度判断是否可以复用之前的响应。这个能力在某些问答场景下很好用,但也更容易踩坑——如果缓存阈值调整得不好,可能会把本该重新生成的回答错误地复用了。我的建议是,语义缓存这个功能要非常谨慎地开启,只在你能接受“近似答案”的业务场景中使用。
配置层面,在litellm_settings里加:
litellm_settings: cache: true cache_params: type: "redis" host: "localhost" port: 6379开启缓存之前,务必想清楚业务模式是否适合缓存。如果它是一个需要每次都生成不同内容的场景,缓存不但不会提速,反而会造成严重的内容重复问题。
5. 常见问题与避坑实录
5.1 鉴权失败与模型不存在怎么区分
我在接入初期经常遇到 HTTP 401 和 404,一开始总觉得是网关配置问题,排查了半天才发现是两个完全不同的原因。
401 通常是 master key 或虚拟 key 不正确。检查你请求时带的Authorization头是否和general_settings.master_key配置一致。404 则更多是模型名不对,要么model_name没写对,要么配置里的model_name和请求里的model不一致。
快速定位的方法是先看 litellm 网关的日志。它会明确写出错误是发生在入口鉴权阶段,还是在上游调用阶段。入口阶段错了,说明是客户端和网关之间的认证问题;上游阶段错了,说明是服务商密钥、模型名或服务商侧权限出了问题。
5.2 模型名的映射关系混乱
很多人在配置多模型时,分不清model_name和litellm_params.model的区别,导致后面管理混乱。
model_name是逻辑名,是对外暴露的;litellm_params.model是物理名,是对内解析的。你可以把所有上游模型都命名为main-chat,也可以让每个上游有自己的逻辑名,完全看业务需求。
我建议在团队内部约定一套命名规范。比如对外暴露的模型名统一使用业务含义,像main-chat、long-context、fast-image,内部实际模型对应关系再在配置里维护。这样即使上游模型换了,业务代码不用变。
5.3 重试和超时配置不当导致雪崩
这是我踩过比较深的一个坑。一开始我只知道配置重试,把重试次数调得很大,结果某个上游开始变慢时,大量请求在网关层排队等待重试,反而把网关的线程池打满了,连带了其他健康的上游也跟着失败。
正确的做法是分配合适的超时阈值,重试次数控制在 2 到 3 次,同时开启 fallback。如果上游持续异常,让请求快速失败,再通过 fallback 分到备用模型,这样才能保证整体可用性。
另外,如果你的服务有熔断需求,litellm 底座也支持相关策略配置。我虽然没有在文章里展开所有细节,但这个方向值得关注。
5.4 预算配置了但感觉不生效
预算配置有一个容易忽略的点:如果你没有配置database_url,litellm 无法持久化费用数据,预算也就无从算起。预算的统计依赖历史费用记录,而历史费用记录依赖数据库。三个环节缺一不可。
另外预算统计粒度也有讲究。如果你给某个虚拟 key 设置了预算,但请求时没有使用这个 key 的认证头,而是用了 master key,那这个请求的费用就记在 master key 名下,虚拟 key 的预算自然不会被触发。
5.5 成本数据没有落库
如果你开启了数据库,但发现成本记录缺失,最可能是路由配置了缓存。命中缓存的请求不会真正发到上游模型,因此也不会产生费用记录,litellm 自然无法统计到。这个逻辑是合理的,但如果你在分析成本时忽略了缓存命中的部分,会低估真实消耗。
另外一个常见情况是数据库连接串配置错误,或者 SQLite 文件路径没有写权限。litellm 启动日志里会有非常明确的提示,留意一下即可。
5.6 部署运维的细节经验
部署方面我额外补充几点。
第一,如果用 Docker 方式部署,注意把数据卷挂载出来,否则数据库和配置文件都写在容器层里,容器一重启就丢了。
第二,生产环境建议在网关前面加一层比较稳定的入口,不要把网关直接暴露到公网。至少要在网络层把端口限制到内网或指定来源。
第三,日志级别在生产环境调成info就够了,debug会打印大量请求细节,虽然排查问题方便,但日志数据量增长很快,磁盘会很快被打满。
6. 我的一些实际体会
litellm 这个项目我现在基本上每个新模型接入都会先用它跑一下。我的体会是,它并不是一个“万能平台”,更像是一个基础设施层的好零件。它帮你解决的是“接入多家服务商”这个具体问题,而不是帮你解决完整的模型治理、数据标注、评测闭环这些事情。
如果你只是在一个小项目里接了一家服务商,直接调用很简单,litellm 的优势不明显。但当你开始面对多服务商切换、成本控制、密钥安全、团队协作这些问题时,litellm 的价值就会很直观地体现出来。
我个人的建议是,不要一开始就把 litellm 复杂配置堆起来。先用最小配置跑通一条链路,确认基本功能正常,再逐步加入负载均衡、预算、缓存、虚拟密钥这些能力。每加一层功能,都在测试环境验证一遍再上生产,这样踩坑的概率会小很多。