1. 统一接口的出发点:多供应商接入的隐性成本
很多团队在体验过一两个大模型平台的 API 之后,很容易产生一种错觉:接入新模型不就是换一个model参数的事吗?真做起来才会发现,各家供应商的接口看起来都叫“聊天补全”,实际用起来差异比想象中大得多。
举个常见的对比。同样是让模型帮你总结一段文本,平台 A 的 SDK 要求你把密钥放在请求头里,平台 B 的 SDK 要求你先初始化一个客户端对象,平台 C 的本地开源模型又希望你走自定义的 HTTP 地址。参数方面,有的平台叫temperature,有的平台叫top_p,还有的平台干脆把这两个参数合并成一个控制开关。返回结构更不用说,有的直接给你choices[0].message.content,有的要把output.data层层拆开,还有的在流式场景下会额外包装一层事件字段。
连返回体都不是一套,就意味着每次切换供应商,调用方代码都要跟着改。如果公司内部有搜索、客服、内容生成、数据分析五六个业务线同时使用不同模型,每个业务线自己封装一遍,出了问题各查各的文档,这种重复成本是很惊人的。
我当时调研 LiteLLM 的时候,其实已经被这类问题烦了挺久。它做的事情用一句话讲:把所有供应商的模型 API 统一成同一套调用格式,让业务代码只认“模型名 + 消息列表 + 生成参数”,底层是平台 A 还是平台 B,是云端服务还是本地开源模型,都由中间层去适配。
1.1 各家接口到底差在哪
把差异列成表会直观很多。
| 差异维度 | 典型表现 |
|---|---|
| 鉴权方式 | 有的是静态密钥,有的要额外配置组织 ID,有的支持临时凭证 |
| 入参格式 | 消息结构、上下文轮数限制、参数命名各不相同 |
| 返回结构 | choices、output、results等顶层字段不统一 |
| 流式协议 | 有的用标准数据流事件,有的用自定义文本段 |
| 限流规则 | 按分钟请求数、按 token 数、按用户级别配额,规则混杂 |
| 计费口径 | 输入输出分开计价,部分平台还按模型版本加价 |
| 错误码 | 同样的 429 语义在不同平台对应不同处理策略 |
这些差异单独看都不难处理,麻烦的是它们叠加在一起。你为了适配平台 A 写的代码,到了平台 B 上几乎全部要推翻重来。再加上每个平台都有自己的 SDK 版本,SDK 一升级,兼容性问题又冒出来一堆。
1.2 LiteLLM 解决的是哪一层的问题
LiteLLM 不是一个“更好的模型”,也不是模型调度平台本身,它更像一个通用适配层。你可以把它理解成电脑上的打印机驱动——上层应用只需要调用“打印”这个动作,具体是哪个品牌、哪个型号的打印机,驱动帮你搞定。对上层应用来说,不管后端接的是哪个模型供应商,调用方式始终是一致的。
从实现来看,LiteLLM 主要做了三件事:
- 统一请求格式:将不同供应商的参数差异在内部做转换,包括鉴权、模型名映射、消息体组装、生成参数翻译。
- 统一返回格式:把各家的响应、流式事件、异常结构全部归一化成同一套结构。
- 统一运维策略:在代理模式下集中处理密钥管理、预算控制、负载均衡、重试和故障转移。
正因为这三层被抽象出来,业务侧才不需要关心“这个模型是谁家的、怎么鉴权、怎么解析返回值”。
1.3 什么样的场景适合用它
不是所有项目都适合引入一个中间层。我给自己定过三个判断标准:
第一个问题:你是不是真的要在多个模型之间来回切换?如果公司长期只绑死一家平台,并且没有迁移计划,那直接用官方 SDK 最省事,没必要多学一层封装。
第二个问题:团队里是不是有多条业务线要用模型能力?多个业务各自申请密钥、各自对接供应商,很容易出现密钥泄漏和成本失控。统一走网关后,权限、配额、审计都能集中管理,这比“研发各自为政”安全得多。
第三个问题:你能不能让网关本身成为一个可观测的依赖?代理模式会多一个网络跳点,意味着生产环境要部署和管理这个服务。如果团队连基础监控都没有,直接上网关会比较吃力。
我见过反面的例子:有团队把 LiteLLM 引进项目后,只是当成普通 SDK 在代码里直接调用,代理模式完全没用起来。结果还是各业务线自己配密钥、自己算成本,等于只换了个调用口,收益很有限。真正要把它的价值发挥出来,还是得把网关角色立起来。
2. 在 Python 里直接调用:先把统一接口用熟
不管最终要不要上代理模式,我都建议先从 Python 库本身入手。因为它的核心思想就是“代码里只写一套调用逻辑,模型名随便换”,这个手感先建立起来,后面看配置项才不会晕。
2.1 安装、鉴权与最小示例
安装非常简单:
pip install litellm之后写一个最小调用:
from litellm import completion resp = completion( model="platform-a/chat-4o-mini", messages=[ {"role": "system", "content": "你是一个精简的技术文档助手。"}, {"role": "user", "content": "用一句话解释什么是幂等性。"} ], temperature=0.3 ) print(resp["choices"][0]["message"]["content"])这里有个关键点:model参数里的platform-a/chat-4o-mini,前面的platform-a是供应商标识,斜杠后面才是具体模型名。LiteLLM 靠这个前缀决定去哪个平台鉴权、用什么格式调底层接口。
如果你在model参数里不写任何前缀,比如直接写"chat-4o-mini",它默认会走最通用的那套 HTTP 接口协议,同时去读取一个指定的环境变量来拿密钥。所以严格来说,“模型名”这个参数不只是模型名,还隐含着路由信息和控制信息。
2.2 模型名与供应商前缀的语义
我把前缀机制单独拎出来讲,因为它很容易被忽略。
假设你有三个平台要接:
- 闭源平台 A,模型叫
chat-4o-mini - 闭源平台 B,模型叫
sonnet-3-5 - 本地部署的开源模型,走自定义地址,模型叫
local-llm-8b
在 LiteLLM 里,模型名可以分别写成:
# 平台 A resp_a = completion(model="platform-a/chat-4o-mini", messages=msgs) # 平台 B resp_b = completion(model="platform-b/sonnet-3-5", messages=msgs) # 本地模型,指定 api_base 指向本地服务 resp_local = completion( model="platform-local/local-llm-8b", api_base="http://127.0.0.1:8000/v1", api_key="local-dummy-key", messages=msgs )注意最后这个用法,api_base和api_key都是可以手动覆盖的。这意味着你不需要等官方 SDK 去适配某个本地推理框架,只要对方暴露了兼容的 HTTP 接口,你就能直接把模型名指向它。
这个能力对做技术选型特别有用。我们当时做模型对比,要在两个闭源平台上跑同一批评测问题,代码完全复用,只把model字符串换掉就行。评测结果出来之后想再验证本地模型,也只需要加一行api_base指向本地服务,半个小时就能完成一轮横向对比。
2.3 流式输出与异步调用
流式输出是聊天类产品的刚需。LiteLLM 的做法是把流式响应也统一成同一个迭代结构:
resp_stream = completion( model="platform-a/chat-4o-mini", messages=msgs, stream=True ) for chunk in resp_stream: delta = chunk["choices"][0]["delta"].get("content") if delta: print(delta, end="", flush=True)这个写法和主流平台官方 SDK 的流式体验基本一致。换成平台 B 的模型,Stream 的结构不会变,你的 UI 层代码完全不用动。
异步方面,LiteLLM 提供了acompletion。在需要并发请求多个模型的场景下,配合asyncio.gather,可以一次性发出多个请求,不必为每个平台写单独的并发封装。
import asyncio from litellm import acompletion async def run_concurrent(): tasks = [ acompletion(model="platform-a/chat-4o-mini", messages=msgs), acompletion(model="platform-b/sonnet-3-5", messages=msgs), ] results = await asyncio.gather(*tasks) for r in results: print(r["choices"][0]["message"]["content"]) asyncio.run(run_concurrent())同步、异步、流式,全部走同一种消息结构。这个一致性带来的好处,只有真正维护过多套 SDK 代码的人才能体会。
2.4 成本核算别等账单出来才做
另外一个我很喜欢的功能是内置成本计算。LiteLLM 内置了大量主流模型的单价表,你只需要在拿到响应结果后调用一个函数:
from litellm import completion_cost resp = completion(model="platform-a/chat-4o-mini", messages=msgs) cost = completion_cost(resp) print(f"本次调用成本: {cost} 美元")对于流式响应,因为最终 token 数还不知道,可以传模型名和消息内容手动估算:
from litellm import completion_cost cost = completion_cost( model="platform-a/chat-4o-mini", messages=msgs, completion_tokens=estimated_tokens )这个功能在企业内部很有价值。业务方再也不需要拿着平台账单手动对账,每一次请求花了多少钱,代码里直接能看到。配合代理模式,还能按团队、按项目、按 API 密钥维度拆分明细。
3. 代理模式:从“一个人能跑”到“一个团队能用”
Python 库解决的是个人开发者“少写代码”的需求。但真正让 LiteLLM 发挥组织级价值的,是它自带的代理网关。在代理模式下,LiteLLM 会启动一个本地 HTTP 服务,对外暴露一个标准的聊天补全接口,内部再根据配置把请求转发到不同模型供应商。
3.1 为什么企业内部需要一个网关
我在好几个公司都见过同样的问题:前端直接把模型平台的密钥写在代码里,后端每个服务各维护一套自己的供应商鉴权逻辑,上了生产环境之后,成本数据散落在各平台控制台,财务问起来也没人说得清总开销。
LiteLLM 代理解决的问题,本质上是把“模型访问权”集中起来。所有业务线不再直接接触供应商密钥,而是向网关申请一个虚拟密钥。每个虚拟密钥可以绑定指定模型,可以设预算上限,可以随时吊销。这样即便某条业务线的前端代码泄漏了密钥,影响范围也只是一个虚拟密钥,而不是整个平台账号。
代理模式还天然成为一层可观测设施。谁在什么时候调用了哪个模型、花了多少钱、成功率是多少,网关侧都有记录。后台团队可以像监控普通接口一样去监控模型调用,而不需要去各个平台的控制台里翻日志。
3.2 五分钟上线一个最小网关
代理模式的核心是一个 YAML 配置文件。你在这个文件里定义“对外暴露哪些模型”“底层对应哪些供应商”。
下面是一个最简配置,我把配置文件命名为config.yaml:
model_list: - model_name: fast-chat litellm_params: model: platform-a/chat-4o-mini api_key: os.environ/PLATFORM_A_API_KEY - model_name: smart-chat litellm_params: model: platform-b/sonnet-3-5 api_key: os.environ/PLATFORM_B_API_KEY然后启动:
pip install 'litellm[proxy]' litellm --config config.yaml --port 8000启动后,网关会暴露一个类似/v1/chat/completions的接口。你直接用curl测一下:
curl http://localhost:8000/v1/chat/completions \ -H "Authorization: Bearer sk-any-value" \ -H "Content-Type: application/json" \ -d '{ "model": "fast-chat", "messages": [{"role": "user", "content": "你好"}] }'注意,这里model用的是配置里的fast-chat,而不是底层真正的模型名。外界只知道“我调了一个叫 fast-chat 的模型”,完全不知道背后接的是哪个平台。以后你想把fast-chat从平台 A 换成平台 B,业务方不用改一行代码,只需要改网关配置并重启。
提示:生产环境部署时,一定要给网关设置底层主密钥,否则任何人都能往你的网关发请求。配置方式是在
config.yaml里加:
general_settings: master_key: sk-your-master-key3.3 高级路由配置:重试、负载均衡、故障转移
只做模型转发的话,价值还不够。代理模式真正强大的地方,是能把运维策略也写进配置。
负载均衡:同一个对外模型名可以配置多个后端。比如你给平台 A 申请了两个配额不同的密钥,写成两条model_list配置,对外都叫fast-chat,网关会自动在它们之间做负载均衡:
model_list: - model_name: fast-chat litellm_params: model: platform-a/chat-4o-mini api_key: os.environ/PLATFORM_A_KEY_1 - model_name: fast-chat litellm_params: model: platform-a/chat-4o-mini api_key: os.environ/PLATFORM_A_KEY_2重试策略:在router_settings里可以指定出错后的重试次数:
router_settings: retry_policy: Timeout: 3 RateLimitError: 2这样遇到限流或超时,网关会自动重试,业务代码里不用自己写重试逻辑。
故障转移:这是我最推荐配置的一项。假设主供应商是平台 A,但平台 A 偶尔会出现大面积限流。你可以定义一个备用模型:
model_list: - model_name: main-chat litellm_params: model: platform-a/chat-4o-mini api_key: os.environ/PLATFORM_A_API_KEY - model_name: backup-chat litellm_params: model: platform-b/sonnet-3-5 api_key: os.environ/PLATFORM_B_API_KEY litellm_settings: fallbacks: - {"main-chat": ["backup-chat"]}当main-chat连续失败超过阈值时,网关会自动把请求转向backup-chat。对调用方来说,它仍然只是在调main-chat,感知不到后端发生了切换。这种能力在做多供应商容灾设计时非常有用。
3.4 虚拟密钥、限额与审计
代理模式下,你可以给不同团队签发不同的虚拟密钥。比如给 A 组发一个只能访问fast-chat的密钥,给 B 组发一个只能访问smart-chat且月预算 20 美元的密钥。
用管理员密钥调用一个接口就能生成:
curl -X POST "http://localhost:8000/key/generate" \ -H "Authorization: Bearer sk-your-master-key" \ -H "Content-Type: application/json" \ -d '{ "models": ["fast-chat"], "max_budget": 20 }'返回结果里包含一个新的虚拟密钥,这个密钥在网关侧有独立的配额和模型白名单。它看起来和正式平台密钥完全不一样,外界无法通过它推断出底层供应商的真实账号信息。
在审计方面,网关会记录每次请求的模型、耗时、token 数、预估成本。你可以把它接到公司已有的日志系统或监控面板,这样模型调用就变成了和普通接口一样可观测的系统资源。
4. 在真实项目里落地时的关键细节
代理模式跑通之后,你会面对一堆“看起来简单、做起来有讲究”的落地问题。下面这几个细节是我在实际项目里反复遇到的。
4.1 语言无关:不只有 Python 能用
LiteLLM 代理暴露的是标准 HTTP 接口,所以它不仅服务 Python 项目。Java、Go、Node.js、C# 等项目,只要能发 HTTP 请求或者支持自定义 base_url 的 SDK,都能直接接入。
一个典型的 Node.js 项目接入方式:
const client = createClient({ baseURL: "http://gateway-internal:8000/v1", apiKey: "sk-virtual-key-xxxx" }); const resp = await client.chat.completions.create({ model: "fast-chat", messages: [{ role: "user", content: "你好" }] });你只需要把原来的供应商 SDK 地址换成网关地址,把密钥换成虚拟密钥。这样不同语言、不同团队各自负责自己的接入,但底层访问的都是同一个模型资源池。
4.2 与现有主流生态的兼容性
LiteLLM 代理暴露的接口风格,和市面上很多主流开源框架、工具链天然兼容。也就是说,你以前写在框架里的模型调用代码,只需要改一下 base_url 和密钥,就能改走 LiteLLM 网关。
这个兼容性带来一个实际好处:团队里某个人对某一套生态特别熟,可以继续用他熟悉的工具链,不需要被强制切换到新库。引入 LiteLLM 不应该让现有代码重写,只是让流量多经过一层网关。
4.3 不只是聊天:嵌入、图像等扩展能力
除了聊天补全,LiteLLM 还覆盖了一些其他常见模型类型。比如文本嵌入模型,调用方式是统一的:
from litellm import embedding resp = embedding( model="platform-a/embed-v3-small", input=["给我一个向量化表示"] )图像生成、语音转写等能力它也在持续集成。不过从我实际使用体验来看,聊天和嵌入是最稳定的场景,其他类型的覆盖程度不如聊天补全那么彻底。如果你要用到比较冷门的模型能力,建议先查一下当前版本是否支持,不要默认“它一定能转”。
4.4 网络拓扑与内网部署
代理模式会引入一个中间跳点,所以生产环境里网关服务本身要部署在内网,业务服务通过内网地址访问。如果团队成员分布在多个机房,或者要用到多云架构,网关前面可能还需要再做一层负载均衡。
这里有几个部署层面的建议:
- 网关服务和应用服务尽量部署在同一个网络区域,减少公网绕行。
- 为网关配置独立的健康检查接口,方便服务发现组件管理实例状态。
- 网关配置文件最好用配置中心管理,避免每次改动都要登录服务器手工改 YAML。
- 如果有多实例部署需求,按官方推荐引入外部状态存储来共享密钥和预算数据,否则会出现各实例各自记账的情况。
5. 我踩过的坑和一些排查建议
用 LiteLLM 一段时间,多少会遇到一些“看起来不太对”的现象。这里整理几个高频坑,帮你少走弯路。
5.1 密钥设置不对,为什么没报错
最阴间的一个坑是:你明明写了错误的密钥,但模型调用还是成功了。原因很可能是环境变量名不对,LiteLLM 找不到对应平台密钥,兜底走了默认平台协议,然后碰巧那个平台接受匿名请求或公开模型。
排查思路很简单:先确认消息里没有“隐式路由”。你要调platform-b/sonnet-3-5,就一定要写完整带前缀的模型名,并且确认环境变量里PLATFORM_B_API_KEY这种变量名拼写正确。另外可以把日志级别打开:
export LITELLM_LOG=DEBUG开了调试日志后,LiteLLM 会打印实际请求的目标地址和鉴权信息,一眼就能看出它到底把请求转发给了谁。
5.2 接口超时和重试导致的多倍费用
网关自动重试确实是好事,但如果上游供应商响应特别慢,重试可能叠加出好几倍的成本。有一次我们遇到一个模型高峰期响应超过 30 秒,网关默认的重试策略连续重试,结果一次用户请求背后实际产生了三次供应商计费。
建议在配置里明确超时时间和重试次数:
router_settings: timeout: 30 retry_policy: Timeout: 1 RateLimitError: 2重试次数不要太激进,尤其是生成类任务。考虑到模型输出有随机性,一次失败后的重试会带来额外的 token 消耗。
5.3 上下文超限与参数不兼容
有些模型对上下文长度有严格要求。调用时如果传入的超长文本突破了模型上下文限制,LiteLLM 会把上游的报错原样传回来,这类错误信息往往让人看得一头雾水。
应对办法是提前在调用层做 token 截断或摘要,不要把完整长文本直接塞给模型。另外,不同模型支持的生成参数差异很大,某平台不支持frequency_penalty,但你传了,就会报参数错误。
LiteLLM 提供了一个很实用的配置项drop_params,开启后会自动丢弃当前模型不支持的参数:
litellm_settings: drop_params: true这个开关不是万能的,但能避免因为参数兼容问题导致的调用失败。
5.4 别让网关成为单点
代理模式提升了管理能力,但也引入了一个可用性风险。如果网关挂了,所有业务线的模型调用都会中断。所以生产环境至少部署两个实例,前面放负载均衡。
多实例部署时,密钥和预算数据需要共享。按官方文档建议,引入外部状态存储来存放这些状态,才能保证多个网关实例之间数据一致。否则你在实例 A 上生成的虚拟密钥,实例 B 上可能不识别。
5.5 先想清楚预算上限再放量
最后提醒一个业务层面的坑。虚拟密钥虽然能设预算,但预算达到上限后,网关默认是直接拒绝请求而不是告警提醒。如果某个业务对模型可用性要求很高,建议把预算上限设得宽一些,同时设置告警阈值,在接近上限时提前通知负责人。
我个人实际使用下来的体会是,LiteLLM 最划算的用法是把它定位成“公司内部模型流量的统一入口”,而不是“一个人手里的便捷 SDK”。Python 库的便捷只是敲门砖,真正能降低多模型维护成本、把密钥风险和成本失控问题管住的,是后面那套代理网关。初期可以在一个低流量项目里试跑,熟悉配置和排查手段,再逐步把核心业务切进来。模型供应商还会越来越多,现在把这一层接口治理好,后面每一次模型切换都会轻松很多。