1. 为什么一个“统一网关层”成了刚需
过去一年多,我接触了不少做 AI 应用的团队,有一个感受特别深:现在真正难的不是“调不通某一个模型”,而是模型选择太多了,代码里到处是不同厂商的 SDK,今天接这个,明天换那个,工程代码改得比需求还快。
我见过一个很典型的项目:最开始只接了 OpenAI 接口,后来要接国内模型,要接 Anthropic,还要对比各家效果。结果怎么做的?在业务代码里写了一个内部封装,里面全是 if-else 判断用的是哪家模型,然后每个厂商 SDK 的请求参数、返回结构都不一样,封装层越写越胖,最终维护的人看见那个文件就想重写。
LiteLLM 解决的就是这个“接口百花齐放”的问题。它做了一个非常朴素但有效的抽象:把各种 LLM API 统一成 OpenAI 格式的调用方式。你只需要写一套代码,通过模型名前缀去指定要走哪家供应商,剩下的认证、请求构造、错误处理、重试、回退,都由它替你接手。
这个项目有两种使用形态:一种是当 Python SDK 直接嵌在代码里,适合单个服务自己用;另一种是启动一个代理网关服务,团队里所有人共用这个网关,统一管理密钥、预算、负载均衡和调用日志。我个人更推荐第二种,因为对于多数团队来说,把密钥散落在各个开发者本地,本身就是一种隐患。
1.1 各家API格式的差异比想象中更大
先说一个容易被忽视的事实:不同厂商的“消息”结构并不是同一套标准。OpenAI 的 Chat Completions 是messages数组加model、temperature、max_tokens这些参数;Anthropic 的 Messages API 有system单独字段,角色体系是user和assistant,返回结构也完全不同;Gemini 那边则是contents和parts的层级结构。
如果你只在代码里封装“发请求”这一步,模型返回后被解析成什么字段,照样需要各个分支处理。更麻烦的是错误码也不一样,有的限流返回 429,有的返回 500,有的连错误消息格式都千奇百怪。这些差异累积起来,就是维护成本的根源。
LiteLLM 的做法是:你在调用时传model="anthropic/claude-3-5-sonnet-20241010",它根据anthropic/这个前缀识别供应商,内部把 OpenAI 风格的messages转换为 Anthropic 需要的格式,再把返回结果统一成 OpenAI 风格。你拿到的是一个稳定的数据结构,choices[0].message.content,不管背后是哪家模型。
下面这个对照表可以很直观地看出差别:
| 调用方式 | 代码特征 |
|---|---|
| 直接调 OpenAI SDK | 只适用于 OpenAI 或完全兼容 OpenAI 的厂商 |
| 直接调 Anthropic SDK | 消息结构、返回字段、错误处理均为另一套 |
| 直接调 Gemini SDK | 又是另一套结构,参数风格差异很大 |
| 通过 LiteLLM 调用 | 统一messages入参,统一choices[0].message.content出参 |
这个抽象本身不复杂,但省掉的是每个新模型接入时都要重新看一遍文档、写一遍适配的那几天工作量。
1.2 自研适配层和现成网关的核心区别
很多人第一反应是:我自己也写个封装不行吗?当然行,但要注意一个关键差异:LiteLLM 不只是把请求格式“翻译”一遍,它同时维护着各家模型元数据,比如上下文长度、最大输出 token 数、成本单价、限流配额。这些数据是持续更新的。
你自研封装的时候,最容易忽略的就是“每个模型的参数约束都不一样”。有的模型max_tokens上限是 4096,有的是 8192,有的是 128K 上下文。如果你没有这些元数据,你的封装层就得每次人工确认参数是否合法,否则请求直接报错。LiteLLM 把这些元数据集中管理,而且项目迭代频繁,新增模型支持也很及时,比自己维护一份“模型参数表”省心得多。
我记得早期自己做过类似的适配层,光一个“统计每个请求花了多少钱”就折腾了挺久,因为各家计费单位不一样,有的按字符,有的按 token,还得考虑缓存命中折扣。后来换到 LiteLLM,response里直接给你cost字段,虽然不一定精确到分,但做成本监控足够了。
1.3 LiteLLM的两种使用形态
LiteLLM 实际上可以把能力拆成两个层面:
- SDK 层面:在 Python 代码里
import litellm,用它封装好的completion()方法发起调用。适合你只是希望把多模型调用统一起来的场景。 - 代理网关层面:跑一个本地或服务器上的服务,对外暴露一个 OpenAI 兼容的 HTTP 接口。所有下游代码只需配置
base_url指向网关,连litellm这个库都不用装。
这两者的关系不是二选一,而是可以叠加使用。网关本身就是基于 SDK 构建的,同时额外提供了路由、预算、密钥管理这些多用户场景的能力。对于个人项目或实验脚本,用 SDK 就够了;只要涉及“多人协作、成本分摊、密钥管控”这些事,就应该上代理模式。
2. Python SDK:把模型调用从“散装”变成“即插即用”
先把最常用的 SDK 用法过一遍,因为后面讲代理模式时很多概念是相通的。
2.1 安装和最小调用
安装很简单,理论上是pip install litellm,但如果你确定要用代理模式,建议直接装上 proxy 相关依赖:
pip install 'litellm[proxy]'这样会把服务端需要的依赖一起装好,省得后面启动网关时报缺库。
测试调用时,先设置对应供应商的环境变量。比如你想同时用 OpenAI 和 Anthropic:
export OPENAI_API_KEY="sk-..." export ANTHROPIC_API_KEY="sk-ant-..."然后写一个 Python 文件跑起来:
import litellm resp = litellm.completion( model="gpt-4o", messages=[{"role": "user", "content": "你好,用一句话介绍自己"}], ) print(resp.choices[0].message.content)注意这里的model参数没有加前缀,默认走 OpenAI。如果要调 Anthropic 的模型,改成:
resp = litellm.completion( model="anthropic/claude-3-5-sonnet-20241010", messages=[{"role": "user", "content": "你好,用一句话介绍自己"}], )只要环境变量里有对应的 API Key,传不同前缀的模型名就能切到不同供应商,代码本身不需要改动。
2.2 模型名映射规则中的重要细节
anthropic/这种前缀就是 LiteLLM 识别供应商的关键。常见的映射有openai/、azure/、anthropic/、gemini/、bedrock/,以及国内一些厂商的deepseek/、ollama/这类。
这里有一个容易忽略的点:如果不加前缀,LiteLLM 也会尝试自动识别模型名属于哪家。比如你传claude-3-5-sonnet-20241010,它知道这是 Anthropic 的模型,但为了明确和避免歧义,我建议每个非 OpenAI 模型都加上前缀。因为你依赖的是“模型名识别表”,万一哪天模型名正好撞车,不加前缀的结果就是请求被路由到你没预期的地方。
另外,如果你用的是 Azure OpenAI 这类需要额外参数的供应商,可以直接在调用时传api_key和api_base:
resp = litellm.completion( model="azure/gpt-4o", messages=messages, api_key=os.environ["AZURE_OPENAI_API_KEY"], api_base=os.environ["AZURE_OPENAI_ENDPOINT"], )这种显式传参的方式排查问题非常方便,适合做代码评审时一眼看明白这个请求发去了哪里。
2.3 重试与故障回退配置
单模型调用从来不是 LiteLLM 的最大亮点,它的亮点在于“失败之后怎么办”。我见过很多团队接了大模型之后,最怕的不是模型效果差,而是供应商一抖动,线上服务跟着雪崩。
LiteLLM 有两个很实用的参数:num_retries控制同一个模型的请求重试次数,fallbacks控制在主模型失败后切换到备用模型。看一个我实际在用的例子:
resp = litellm.completion( model="gpt-4o", messages=messages, num_retries=2, fallbacks=["anthropic/claude-3-5-sonnet-20241010", "gemini/gemini-1.5-pro"], request_timeout=30, )这样配置的含义是:先请求gpt-4o,如果因为限流、网络超时等原因失败,重试两次还不行,就切换去调 Claude,再不行就调 Gemini。对上层业务来说,它永远只看到一次completion()调用,不知道也不需要知道背后发生了什么切换。
这里要提醒一句:fallbacks不能替代你自己的业务降级方案。它适合“模型临时不可用”这种场景,但如果你的请求本身有问题,比如messages格式不对,那换成哪个模型都是一样报错。
2.4 流式、token统计与返回对象统一
流式输出也是日常高频需求,LiteLLM 的做法是传stream=True,然后迭代返回的 chunk:
response = litellm.completion( model="gpt-4o", messages=messages, stream=True, ) for chunk in response: delta = chunk.choices[0].delta.content if delta: print(delta, end="", flush=True)虽然底层各家模型的流式事件格式不同,但 LiteLLM 会统一成 OpenAI 风格的chunk.choices[0].delta.content。这意味你写的流式解析逻辑,换模型后不会改。
Token 统计这块,response.usage里已经整理好prompt_tokens、completion_tokens、total_tokens。如果你想知道这次调用花了多少钱,直接看response._hidden_params["response_cost"]或新版返回的cost字段都可以。价格数据是 LiteLLM 按模型元数据算出来的,虽然可能存在小数点后的偏差,但做成本趋势分析完全够用。
3. 代理模式实战:给整个团队一个干净的接入入口
如果你只是一个人写脚本,SDK 模式已经很好。但一旦到了团队协作阶段,密钥管理、成本分账、权限隔离这些需求就压上来了,这时候跑一个网关服务是更工程化的选择。
3.1 为什么推荐从“代理模式”起步
我们团队实际落地的时候,最大的痛点不是写代码,而是“每个人本地都有一份供应商密钥”。有人的 Key 是公司公共账号,有人的 Key 是个人账号,月底对账的时候根本说不清谁花了多少。而用了代理网关之后,所有请求都经过一个固定入口,密钥只存在服务端,下游开发者拿到的只是一个网关地址和一把限定的虚拟 Key。
这样有几个立竿见影的好处:
- 开发者的本地环境不需要配置任何供应商密钥,上手就是调网关。
- 供应商的密钥只保存在服务端,泄露面大幅缩小。
- 每个虚拟 Key 可以单独设预算、单独看用量,分账清楚。
- 模型是否可用、是否需要切换,由网关统一控制,业务代码不感知。
3.2 config.yaml的完整结构
代理模式的配置全部集中在一个 YAML 文件里,启动时加载。这个文件是整个网关的“大脑”,先看一个我最常用的配置结构:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: claude-3-5-sonnet litellm_params: model: anthropic/claude-3-5-sonnet-20241010 api_key: os.environ/ANTHROPIC_API_KEY - model_name: gemini-1.5-pro litellm_params: model: gemini/gemini-1.5-pro api_key: os.environ/GEMINI_API_KEY litellm_settings: drop_params: true set_verbose: true router_settings: num_retries: 2 request_timeout: 30这里有一个很关键的映射关系:model_name是“暴露给外部调用方的模型名”,litellm_params.model才是“真正发给供应商的模型标识”。也就是说,外面的人看到的是gpt-4o这个名字,但你可以把它映射到任何后端供应商,甚至可以把gpt-4o这个对外名字背后的实际模型偷偷换成国产替代,业务方不会有任何感知。
我把这个映射关系理解为一种“内部弹性”:今天你还可以对外叫gpt-4o,但背后实际用的是国内模型,只要效果达标,切换成本几乎为零。
3.3 启动服务并用OpenAI客户端接入
配置写好后,启动网关:
litellm --config ./config.yaml --port 4000如果加了数据库追踪需求,需要先准备一个 PostgreSQL 或 SQLite,然后把连接串通过--database_url传入,比如 SQLite 本地调试就是sqlite:///litellm.db。
下游开发者接入时,甚至不需要知道 LiteLLM 的存在,因为对外暴露的是 OpenAI 兼容接口。他们只需要改两个环境变量:
OPENAI_API_BASE=https://your-gateway-host:4000OPENAI_API_KEY=sk-your-virtual-key
然后用普通的openaiPython SDK 就能发起调用。这在很多框架里特别方便,比如你在做一个项目,只需要把base_url换成网关地址,原有代码完全不用动。
3.4 安全与密钥管理的基础配置
网关默认会用自己的 Master Key 做认证。启动后可以用下面的命令生成虚拟 Key:
curl http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-master-key" \ -H "Content-Type: application/json" \ -d '{ "models": ["gpt-4o", "claude-3-5-sonnet"], "max_budget": 50 }'返回结果里会有一个sk-...开头的虚拟 Key,把这个 Key 发给对应开发者,他只能访问指定的模型,而且总共最多花 50 美元。预算到了,这个 Key 自动失效。
不要小看这个功能,它其实就是在网关这一层做“基于 Key 的权限和额度控制”。没有它的时候,我们想限制某个同事只能用某个模型,基本靠口头约定。有了它,限制至少是系统层面的,违约成本会高很多。
4. 预算、负载均衡与用量观测:线上运营的四个硬指标
代理模式跑起来之后,第一步是“能用”,第二步是“好用”。所谓好用,我认为至少要对以下四件事心里有数:钱花到哪里去了,请求有没有被分散,谁在用什么模型,以及有没有人的请求把别人给堵了。
4.1 预算上限怎么设置才不会误伤
LiteLLM 的预算控制分两个层级:全局预算和 Key 预算。全局预算写在配置文件的litellm_settings.max_budget里,控制整个网关的总花销;Key 预算在生成虚拟 Key 时通过max_budget指定。
一个容易踩的坑是:预算单位是美元,但这个数字基于模型单价估算,不同模型的单价更新可能不完全及时。所以预算不要设得太满,比如你预计一个月花 100 美元,那就设 90 或者是 80,留一点缓冲。这个和建议不是为了省那几块钱,而是避免估算误差导致 Key 在关键时刻被提前冻结。
预算达到上限后的行为也可以配置,配置文件里可以设置:
litellm_settings: max_budget: 1000 budget_alert_threshold: 90当用量达到 90% 时,LiteLLM 会发 webhook 通知,你可以在问题发生之前去调整额度,而不是等 Key 挂了之后被业务方找上门。
4.2 多Key负载均衡的轮询逻辑
同一个供应商,如果你有多个 Key,不想某个 Key 因为限流把整个服务拖垮,可以在model_list里重复定义同一个模型,分别用不同的 Key。
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY_1 - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY_2这样网关会自动在多个 Key 之间做负载均衡。它不只是简单地轮询,还会根据每个 Key 的失败次数动态调整权重。某个 Key 连续报 429 限流,它就会暂时减少对这个 Key 的请求分配,等待它恢复。
这个设计非常实用,因为不少供应商的限流是按 Key 维度计算的,单 Key 并发一高就会被限制。多 Key 轮询相当于把限流压力分散到多个账户上,对团队而言是一种低成本扩容方式。
4.3 用量追踪与日志落库
网关启动时带上--database_url之后,每次请求的模型名、token 数、耗时、费用都会被记录到数据库里。我一般配合一个简单的查询脚本,每周拉一次各 Key 的用量:
SELECT api_key, model, total_tokens, spend, created_at FROM "LiteLLM_SpendLogs" ORDER BY created_at DESC LIMIT 100;这张表里的spend字段是网关按模型单价算出来的估算值,不能当财务账单用,但做趋势分析、成本异常发现完全够用。比如某个 Key 的 spend 突然翻了几倍,那大概率是有人跑了批量任务,或者业务逻辑出现了死循环调用。
4.4 限流策略的取舍
默认情况下,LiteLLM 不会主动帮你限流,它会把压力直接传到供应商。但如果某个下游业务方在疯狂循环调用,供应商返回 429 还是小事,更严重的是把其他正常业务方的请求也拖入超时重试的泥潭。
建议在生成 Key 时指定rpm_limit(每分钟请求数):
curl http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-master-key" \ -H "Content-Type: application/json" \ -d '{ "models": ["gpt-4o"], "rpm_limit": 60 }'这个 Key 的每秒请求数超过限制后,网关会直接拒绝并返回 429,而不会转发到上游。这样至少能把“一个人把全组额度吃完”的场景隔离掉。
5. 实测踩坑:这几个问题不看文档根本发现不了
LiteLLM 总体上手快,但真正在生产环境跑起来之后,有几个坑我是实实在在踩过的,网上讨论也比较分散,这里集中梳理一下。
5.1 超时问题是第一坑
代理模式跑起来后,最常见的现象是“请求卡住不动,过很久才有反应,或者直接报错”。排查到最后大多是超时配置太短。
供应商处理复杂请求的时间本身就不稳定,尤其是长了点的上下文加上要生成千字回复,耗时十秒以上很正常。如果你在网关层设了一个 10 秒的request_timeout,那就等着经常失败吧。我现在的做法是:
router_settings: request_timeout: 60同时,如果网关前面还挂了 Nginx,Nginx 的proxy_read_timeout也要相应调大。这个坑特别隐蔽,因为表现和网络故障一模一样,查配置的时候很容易只盯着应用层,忽略反向代理那一层。
5.2 上下文长度与max_tokens的隐性差异
各家模型对max_tokens的理解和限制不一样。有的模型如果你不传max_tokens,它会使用默认值,但默认值不一定符合你的预期;有的模型则把max_tokens硬性限制在一个较小范围,传大了直接报错。
LiteLLM 做了很多参数适配,但如果你设置的参数超出了后端模型支持范围,它不一定每次都帮你裁剪。我的建议是:在litellm_settings里开启drop_params: true。这个开关的作用是,当某个模型不支持某些参数时(比如temperature在某些推理模型上被禁用),自动丢弃而不是报错。
这个开关在我接一个新模型时减少了大量“参数不支持”类报错。代价是你会丢失一些“显式传错参数”的报错能力,但总体收益远大于代价。
5.3 与既有框架配合时的回调问题
如果你用了 LangChain 这类框架,要注意它的回调系统和 LiteLLM 的回调可能产生重复事件。LiteLLM 默认支持 Langfuse、Prometheus 这些可观测性工具的集成,配置方式是在配置里加:
litellm_settings: success_callback: ["langfuse"] failure_callback: ["langfuse"]但如果你在业务代码里同时用了 LangChain 的 Callback 往同一套系统里写数据,就会出现双份日志。这会使成本统计、耗时分析失真。我的经验是:可观测性数据要么从业务层取,要么从网关层取,不要两头都开。
5.4 版本演进带来的兼容性处理
LiteLLM 的迭代速度很快,新版本可能会调整配置项名称或者默认行为。我第一次升级小版本后,配置文件里的某个router_settings字段被标记为弃用,日志里虽然只是 warning,但确实影响了某个特性的行为。
针对这种情况,我现在的做法是锁版本号安装:
pip install 'litellm[proxy]==1.57.5'升级前先看 Release Notes,重点检查配置项和模型元数据库有没有大的变动。因为它把模型元数据内置在包里,所以升级包本身就可能带来“某些模型的成本单价变了”这类隐性问题。
6. 选型判断:什么场景下该用它,什么场景该绕开
聊完了功能,最后说说选型。任何工具都有边界,LiteLLM 不是万能的,有些场景确实不该用。
6.1 与自研封装、其他开源网关的取舍
自研封装的唯一优势是“完全可控”,代码是自己写的,出问题能立刻改。但代价是你得持续维护各家模型适配,模型元数据更新、新模型接入、错误码映射,这些都是长期成本。团队只有一两个模型时,自研封装问题不大;一旦模型数量超过五个,维护适配层的工作量就会开始挤占业务开发时间。
和其他开源网关类项目相比,LiteLLM 的最大特点是对“多供应商”支持很广,而且其 OpenAI 兼容层做得比较完整,很多框架和工具可以直接通过改base_url接进来。如果你主要模型都来自 OpenAI 和 Anthropic 这几家,LiteLLM 的模型前缀体系用起来非常有优势。如果你主要用的是某个小众私有化模型,它的支持列表里可能没有对应实现,不要硬上。
6.2 明确不适用的情况
第一,严格离线环境。LiteLLM 整体上是面向“外呼 API”设计的,虽然你也可以用它接入本地 Ollama 或 vLLM,但这些场景真正需要的往往是更轻量的一层转发,而非完整的预算、密钥、负载均衡体系。第二,供应商 API 形状特别特殊、LiteLLM 支持又很滞后的情形,比如某些闭源平台提供了额外参数,LiteLLM 暂时没有暴露对应字段,这时候你得评估等待官方支持还是自己处理更好。
还有一个容易被忽略的点:LiteLLM 的模型元数据文件是随包发布、在线更新也依赖 GitHub Release 链路。虽然代理运行时不强制联网,但建议阶段会需要网络。如果你的环境对出网有很严限制,模型元数据更新会是一个额外负担。
从我的实际体验来说,LiteLLM 解决了一个很现实的问题:让团队在模型选择上保持灵活,而不是被迫绑定在某一家供应商身上。它并没有魔法,也不是万能银弹,但它确实把“多模型接入”这件事的复杂度,从业务代码里抽离到了配置层。你不必一开始就搭建复杂的网关体系,个人项目用 SDK,团队协作时再逐步过渡到代理模式,这也是我认为比较平滑的一条落地路径。