前阵子一个朋友问我,他手里有三四个大模型厂商的接口,有的按 token 计费、有的按时间段打折、有的时不时限流,团队里有人用 OpenAI 的 SDK,有人用各家原生的包,代码越写越碎。有没有一个办法,把这一堆接口统一收口,让上层业务只认一套调用方式,底层随便换供应商?我当时就说,你去看一下 LiteLLM。
LiteLLM 是最近两三年里在大模型工程圈里被反复提起的一个开源项目,核心目标就一句话:给所有主流大模型接口提供一个统一入口。它可以当作 Python 包在代码里直接调用,也可以跑成一个独立网关服务,让团队里所有服务通过一套 OpenAI 兼容接口访问几十家模型供应商。这篇文章我会从它解决的痛点讲起,拆解核心功能,再给出一套从零搭建网关的完整配置过程,最后把我实际部署中踩过的坑一并列出来,希望能帮你少走点弯路。
1. 先搞清楚 LiteLLM 到底解决什么问题
1.1 大模型接口碎片化带来的麻烦
现在市面上能调的大模型已经远不止两三家了。OpenAI 系、各大云厂商的自研模型、开源模型托管平台、专注长上下文的、专注代码生成的,各自都有自己的一套 HTTP 接口协议。虽然大部分接口在结构上相似,但细节差异非常磨人。
- 请求体字段不同:有的用
max_tokens,有的用max_new_tokens,有的要求n必须搭配temperature。 - 鉴权方式不同:有的用静态 key,有的需要动态签名,有的还得带额外的 header。
- 返回结构不同:有些返回内容直接嵌在
choices[0].message.content,有些把生成结果塞在嵌套字段里,甚至存在返回标量和数组两种形态。 - 限流策略不同:每家对 QPS 的计算口径不一样,有的按分钟配额,有的按并发连接数,有的按金额。
如果你只有一两个模型,这些差异顶多算小事。一旦团队同时接四五个模型,并且打算在部分场景做跨模型冗余、在部分场景做成本分流,那这堆兼容代码很快就会变成项目的隐形负担。每个业务方都得知道当前调用的是哪一家、参数怎么填、费用怎么算,换供应商等于改代码。
LiteLLM 的思路就是做一个中间层。它把所有供应商的请求格式统一转成 OpenAI 风格的消息结构,底层再做协议适配。业务侧永远只写一套代码,模型供应商的变化被挡在这层之后。
1.2 两种使用形态:SDK 嵌入与网关代理
LiteLLM 提供两种主要用法,我建议你先分清这两种形态,因为很多部署问题都是因为没弄明白自己在用哪种。
第一种是 SDK 嵌入形态。你在 Python 代码里直接import litellm,然后调用completion()或embedding()。这种模式下 LiteLLM 更像一个“翻译层”,进程内完成协议转换、重试、超时控制。优点是部署简单,不需要额外维护服务,适合脚本、小服务、快速原型。
第二种是网关代理形态。把 LiteLLM 启动为一个独立进程,暴露一个 HTTP 端口,比如http://你的服务器:4000。业务方只需要把原来的 OpenAI SDK 的base_url指向这个地址,key 换成你分配给他们的虚拟 key,就完成了迁移。这种模式适合多服务、多团队、生产环境,因为路由、密钥、限流、预算、日志全部集中在了网关这一层。
我的经验是:生产环境如果没有特殊封锁,直接上代理形态。原因很简单,SDK 形态只解决了协议统一问题,但没法解决密钥管理、配额控制、成本审计这些团队协作问题。网关形态把这些能力都做进去了,后期运维会轻松很多。
1.3 不只聊天:嵌入、重排、图像、语音它都接
很多人对 LiteLLM 的认知停留在 chat 接口上,实际上它支持的能力面相当广。聊天补全自然是核心,另外还能处理 embedding 向量化、rerank 重排序、图像生成、音频转写和语音合成。这些能力同样走 OpenAI 风格的统一格式,比如需要 embedding 的就直接以列表形式传文本,返回的向量格式也是对齐的。
在 RAG 应用里这一步尤其方便。同一个向量化需求,开发环境可能用的是便宜的本地小模型,生产环境切到云上更稳定的付费接口,因为有了中间层,切换只是改配置的问题。团队里不同项目各用各的向量模型,也不需要逼所有人去学不同的 SDK。
2. 核心功能拆解:路由、回退、密钥、预算到底怎么工作
2.1 model_list 就是将真实模型映射成抽象别名
要理解 LiteLLM 的网关逻辑,先看它的核心配置文件:config.yaml。这个文件里最重要的部分是model_list,它做了一件事:把“业务侧看到的模型名”与“真实的供应商模型名”解耦。
举个例子,业务代码里大家习惯写model="gpt-4",但某段时间你想把部分流量切到某云厂商的平替模型上,并且希望业务方无感知。你不需要让他们改参数,只需要在网关配置里重新定义gpt-4这个别名对应的真实上游。我这里写一个典型配置:
model_list: - model_name: gpt-4 # 业务侧看到的模型名 litellm_params: model: openai/gpt-4 # 真实供应商 + 模型 api_key: os.environ/OPENAI_API_KEY api_base: https://api.openai.com/v1 - model_name: claude-sonnet # 另一个别名 litellm_params: model: anthropic/claude-3-5-sonnet api_key: os.environ/ANTHROPIC_API_KEY这样配置之后,业务方调用model="gpt-4"时,网关实际把请求转发给 OpenAI;哪天你想切到别的上游,就改配置里的litellm_params,把流量转到另一个供应商。注意这里的model字段带一个前缀,比如openai/、anthropic/、bedrock/、azure/。这个前缀不是装饰,它告诉 LiteLLM 该用哪一套协议适配器去解析请求和响应。
2.2 虚拟密钥:好用又危险的设计
LiteLLM 的密钥系统是我特别喜欢又特别提醒自己小心的部分。网关模式下,你可以不直接向下游暴露各家供应商的真实 key,而是自己在 LiteLLM 里生成“虚拟 key”。业务方拿着虚拟 key 来访问网关,网关在转发时用自己的真实上游 key 去调用厂商,同时对虚拟 key 做额度控制和调用日志记录。
这个设计的好处很多。比如某个实习生把 key 泄漏到了公网仓库,你只需要在网关里删除对应的虚拟 key,不用去各家控制台重新生成一堆真实 key。再比如你给 A 项目分配一个虚拟 key,给 B 项目分配另一个,月底对账的时候,哪些项目花了多少钱一目了然。
但虚拟 key 也有前提:它只解决网关之后的部分。如果某个人直接拿到了你配置里的真实 key,那依然可以绕过网关去调用上游。所以生产环境里还是要把配置文件的权限管住,环境变量不要乱打印,密钥轮换该做还是要做。
2.3 负载均衡与故障回退:两个容易混淆的概念
我经常看到有人把 LiteLLM 的负载均衡和回退当成一回事,其实它们解决的是不同问题。
负载均衡是“同时”“主动”地把请求分摊到多个上游。比如你有两个供应商,权重各 50%,那么正常情况下每个供应商收到一半请求。配置方式给同一个model_name配置多条litellm_params,再用routing_strategy控制策略。支持普通权重轮询、请求延迟优化、预算感知路由等。
回退是“被动”的兜底。它指的是某一次请求在第一个上游失败之后,自动把这次请求重新发到另外一个上游。典型配置里可以指定fallbacks,比如当 OpenAI 返回限流错误时,自动转给某云厂商的模型。这里要注意,重试和回退不是白给的,盲目配置可能放大问题,后面第五节我会专门聊。
2.4 预算管理:从单 key 到项目级成本控制
LiteLLM 的预算能力分几个粒度,从“全局预算”到“Key 预算”到“Team 预算”。你可以给每个虚拟 key 设置一个每月的花费上限,超过就拒绝请求;也可以按团队、按项目设置整体预算池。这个功能对交付类项目特别重要,尤其是你给甲方做了一套带模型调用的系统,不能让他把预算一路打穿。
它统计成本的方式不是简单看上游账单,而是在网关内部根据 token 用量和模型单价实时估算。所以你要注意一个细节:哪些模型的单价被正确配置了。如果模型单价没填,或者填成了 0,预算控制就是不完整的。我在后面的配置示例里会提到model_info这个字段,它的用途之一就是给模型标注单价。
3. 实操部署:从零搭一个可以上生产的 LLM 网关
3.1 安装与基础启动
LiteLLM 的安装不复杂,Python 环境装完就行。
pip install 'litellm[proxy]'装完之后,先用一个入门配置把服务拉起来。我这里建议你把基础配置和密钥分开,比如 config.yaml 只写模型路由,真实的 key 从环境变量读取。启动命令是这个:
litellm --config ./config.yaml --port 4000启动日志会显示一个本地绑定的端口,以及当前实际加载了哪些模型。这里有个小坑:默认绑定地址可能是0.0.0.0,如果你把它暴露到了公网,又没有做访问控制,别人就能直接扫到你的网关。生产环境务必加一层网络策略或鉴权。
3.2 用 OpenAI SDK 接入网关
网关起来之后,你不需要安装任何特殊 SDK,直接用官方 OpenAI SDK 就能测。
from openai import OpenAI client = OpenAI( base_url="http://127.0.0.1:4000", api_key="sk-你的虚拟key" ) resp = client.chat.completions.create( model="gpt-4", # 这是你在 config.yaml 里定义的别名 messages=[ {"role": "user", "content": "写一段自我介绍"} ], temperature=0.7 ) print(resp.choices[0].message.content)只要你把base_url指向 LiteLLM,把api_key换成虚拟 key,原来写的 OpenAI 调用代码几乎不用改。这就是它兼容性做得最到位的地方。很多团队迁移的时候,只改一个配置文件和一个 base_url,业务代码几乎不动。
3.3 SDK 嵌入用法:不做网关时的轻量方案
有些场景你确实不想起网关进程,比如一个后台定时任务,一次性跑一批 prompt。那直接用 Python SDK 嵌入模式会更合适。
import litellm resp = litellm.completion( model="openai/gpt-4", # 这里必须带供应商前缀 messages=[{"role": "user", "content": "你好"}], ) print(resp.choices[0].message.content)注意嵌入模式里的model参数必须带openai/、anthropic/这种供应商前缀,否则 LiteLLM 不知道要用哪套协议去解析。而代理网关模式里的model_name是你在配置里自定义的别名,两者不是一个概念。很多人栽在这地方,以为同一个字符串两边通用。
3.4 加入供应商的带前缀配置
如果你要接的模型比较多,建议把所有模型都列在 config.yaml 里集中管理,不要散落在环境变量。下面这个示例展示了多种主流供应商的接入方式:
model_list: - model_name: openai-gpt4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: azure-gpt4o litellm_params: model: azure/gpt-4o api_key: os.environ/AZURE_API_KEY api_base: https://xxx.openai.azure.com/ api_version: "2024-06-01" - model_name: anthropic-sonnet litellm_params: model: anthropic/claude-3-5-sonnet api_key: os.environ/ANTHROPIC_API_KEY - model_name: openrouter-qwen litellm_params: model: openrouter/qwen/qwen-2.5-72b-instruct api_key: os.environ/OPENROUTER_API_KEY这里我把每个业务别名定义成一眼能看出上游来源的名字,这是一个好习惯。实际生产里你完全可以把openai-gpt4o命名为primary-chat,然后随时切换它指向的上游模型。但建议至少在配置里保留注释或命名前缀,方便后面的人维护。
4. 进阶场景:多模型路由、高可用与成本可视化
4.1 把两个供应商放进同一个别名实现负载均衡
要让两个模型平分流量,配置里需要为同一个model_name写多个litellm_params。我给出一个权重均衡的配置示例:
model_list: - model_name: dual-chat litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY model_info: cost_per_million_tokens: 2.5 - model_name: dual-chat litellm_params: model: anthropic/claude-sonnet-4 api_key: os.environ/ANTHROPIC_API_KEY model_info: cost_per_million_tokens: 3.0这种情况下,dual-chat这个别名有两个可用的上游。如果routing_strategy用默认的simple-shuffle,请求就会按权重随机分发。这个功能适合你在做模型 A/B 测试时使用,也适合你想把流量从贵模型往便宜模型迁移时使用。先把便宜模型加进同一别名,流量跑一段时间,观察质量达标后再逐步调高便宜模型的权重。
4.2 回退配置的典型模板
配置回退时,我建议在一个独立的router_settings里写,不要到处铺。下面这段配置演示了:当主模型返回限流或超时错误时,自动改走另一个模型。
router_settings: routing_strategy: simple-shuffle fallbacks: - {"callbacks": ["openai/gpt-4o"], "error_codes": ["429", "500", "529"]}这里我简化了写法,实际你用的版本字段名可能会有细微差别,以你当前版本的文档为准。关键是理解回退触发条件:不是所有报错都应该回退。429 表示限流,可以回退;500 表示上游故障,可以回退;400 表示业务参数错误,回退十次也没用,反而会污染日志。如果业务方传的 prompt 触发了内容安全策略,那更不应该回退到别的供应商。
4.3 限流与超时的合理设置
LiteLLM 网关本身也有限流能力,可以按虚拟 key 设置每分钟请求数。但这里要小心:你是在给“经过网关的请求”做限流,而上游供应商通常也有自己的限流。如果你在网关限流设得比上游还高,那么上游一限流,你的请求还是会被打回去,依旧触发回退或报错。所以我建议先做一层保守的全局速率限制,再靠回退兜底。
超时是另一个容易踩的坑。某些长文本生成任务本身耗时就很长,你把连接超时设成 10 秒,那基本每次都要重试。我通常把timeout调成 600 秒,把连接阶段的短超时和生成阶段的长超时分开看。如果你用了httpx作为底层客户端,LiteLLM 在部分版本里也支持设置连接超时,建议把这两类时间明确区分。
4.4 跟踪调用链路与成本
网关模式下,LiteLLM 支持把请求日志写到数据库,也支持对接外部观测平台。我自己的偏好是至少把每个请求的关键字段落库:时间、模型别名、实际上游模型、虚拟 key、prompt token 数、completion token 数、估算成本、响应延迟、错误类型。有了这组数据,后面做成本分析、模型选型、容量规划都方便。
如果你用的是 Postgres,LiteLLM 会在数据库里创建一堆表,专门存 key、预算、请求日志等。启动时如果检测到没有建表,它会自动迁移。这里提醒一句:给数据库账号足够的权限,别用只读账号跑迁移,否则启动半天,界面看起来正常,结果 key 都存不进去。
5. 实战中容易踩的坑与排查建议
5.1 模型名前缀决定协议,不能乱写
嵌入模式里model="anthropic/claude-3-5-sonnet"和model="claude-3-5-sonnet"是两种完全不同的行为。带前缀的是让 LiteLLM 按 anthropic 的协议处理,不带前缀的可能会被默认按 openai 协议处理。一旦协议适配错了,最常见的问题就是请求发出去了,报告码 200,返回内容却解析不出来,或者字段全都为空。
同样的情况也会出现在代理模式里。你在 config.yaml 里定义业务别名时,litellm_params.model必须写完整带前缀的字符串。很多人图省事直接写gpt-4o,结果 LiteLLM 用默认协议去解析某云厂商的返回,最后拿到一堆空 choices。排查这种问题有个笨办法:打开 debug 日志,对比一下出错的响应体和你实际模型文档里的返回结构。
5.2 响应格式差异:有的直接返回文本,有的返回 Markdown
流量切到不同供应商后,最容易被内容侧发现的差异是输出风格。同一个 prompt,OpenAI 系列可能习惯输出带结构的 Markdown,某云厂商的模型可能吐出来更口语化。这不是网关能解决的事,这是模型本身的行为差异。我在做分流验证时,一定会准备一份业务输出验收用例,拿真实场景跑一轮,而不是只看单个回复是否正常。否则你很容易把“能通”误解成“能上线”。
5.3 密钥权限与 Key 泄漏处理
我见过一个团队把虚拟 key 硬编码在前端页面里,网关对外完全裸奔。过了一阵子,上游账单突然暴增,因为有人拿这个 key 在网上跑刷单任务。LiteLLM 虽然支持按 key 设置预算上限,但如果你没配预算,这个风险就是敞开的。
我的建议是:每个虚拟 key 都必须设置预算上限,哪怕预算设成 100 元也好。不为省钱,为的是泄露出问题时损失可控。同时,网关本身要放在内网或加了访问控制的安全网络里,不要把虚拟 key 当成防外部入侵的唯一闸门。
5.4 多线程与异步客户端的使用注意
LiteLLM 的异步接口是litellm.acompletion(),Python 里用asyncio.run()包一层就能跑。但如果你的业务代码本身是同步的,又用多线程去并发调用litellm.completion(),要小心底层连接池被打满。性能好的做法是优先用异步接口,或者限制线程池大小。
网关模式下这个问题相对小一些,因为网关自己有并发管理能力。但如果你直接在两三百个进程里分别起一个 SDK 嵌入式 LiteLLM 实例,上游供应商那边看到的可能是无数个不同来源的并发请求,对限流判断很不友好。这种场景下还是统一走网关更稳妥。
5.5 版本升级导致的字段漂移
LiteLLM 的迭代速度很快,版本之间的配置字段经常有变动。比如某些旧版本里fallbacks写在外层,新版本要求写进router_settings;再比如model_list里部分字段的命名也调整过。生产环境升级前,一定要看 changelog。
我吃过一次亏:有个服务跑了三四个月没动,某天重启后模型全部 404。排查下来发现,新版本不再支持某个旧格式的配置字段,而我又没升级配置文件。从那以后,我把 LiteLLM 的版本号固定在了部署文件里,不轻易追新。毕竟网关这种基础组件,稳定性比新功能重要得多。
5.6 上游成本统计失真
预算模块依赖模型单价配置。你要是从没有在model_info里填过单价,那么预算统计就可能把成本算成 0,导致配额永远不触发。确认方法很简单:人工发起一条请求,然后查一下日志里记录的估算成本字段是否是一个合理数值。如果是 0,回头检查单价的字段来源。
6. 一些个人经验总结
LiteLLM 这个项目最让我欣赏的一点是它把“统一接入”这事做到了工程可用,而不是停留在 Demo 阶段。我实际把它用在过内部工具上,也帮朋友的项目搭过网关。最直观的变化是团队里不再有人为了“某供应商 SDK 版本升级”而被迫加班改代码。供应商接口变化时,只需要在网关配置层做一次升级,业务侧毫无感知。
但也要说句实在话,LiteLLM 不是万能的。它更像一个“路由与协议中间层”,并不解决模型能力选择问题,也不解决 prompt 调试问题。模型本身输出质量不好,加不加网关都一样。该做的评测、监控、业务侧兜底,一样都不能省。
如果你现在只调一两个模型的简单场景,可以先不用网关,用 SDK 嵌入模式就够了。当出现了三位数以上调用频率、多个 key 要管理、多个供应商并存的情况,再上代理网关,性价比是最高的。而且从 SDK 嵌入模式迁到网关模式,并不需要重写业务代码,配置平滑过渡的成本比很多方案都低。
最后分享一个我常用的检测技巧:新配置上线后,先在网关日志里盯前二十分钟的错误码分布。如果是清一色的 401 或 403,多半是密钥填错或权限不足;如果全是 429 或 529,那是供应商限流或过载;如果出现一堆奇怪的 404,先检查模型名是否带对前缀。把错误码分布认清楚,很多问题在爆发前就能定位。