做 AI 应用开发的人,应该都经历过这种尴尬:昨天还在用 OpenAI,今天想换成 Claude 试试,结果发现 SDK 不一样、参数不一样、返回结构也不一样,对接代码得重写一遍。就算只盯着一家,不同阶段可能还要在 GPT 系、Claude 系、开源模型之间来回横跳,每换一次都等于重新做一次接入。LiteLLM 就是来解决这个问题的开源工具,它把主流大模型服务统一成一套接口,你只需要写一份调用代码,通过切换 model 参数就能在不同模型之间自由切换。
很多第一次接触 LiteLLM 的人会以为它只是一个 Python 库,实际上它有两个形态:一个是 SDK,帮你把各家的 API 请求封装成统一格式;另一个是网关服务,部署之后可以集中管理密钥、做负载均衡、限制预算、记录成本。如果你只是自己写脚本,用 SDK 模式就够了;如果你在带团队做项目,或者想在公司内部提供一个统一的模型接入出口,那网关模式才是它的重头戏。
这篇文章我就从实际使用角度,把 LiteLLM 的定位、核心设计、快速上手、网关部署和踩坑经验串起来讲一遍。
1. 先看清病根:多模型接入的混乱,到底乱在哪
1.1 SDK、参数、返回结构全都不一样
先举一个非常现实的场景。你在项目里先接了 OpenAI 的 GPT 系列,写好了chat.completions.create这套调用逻辑。后来老板说想试试 Claude,你打开 Anthropic 的文档,发现它用的是messages.create,请求体里的字段也变了,OpenAI 用max_tokens,Claude 的版本可能要求必填这个参数,对话历史还要区分system角色的写法,返回结果里的choices结构也不一样。这还是只是两家,如果再加上 Google Gemini、Cohere、本地跑的 Llama,每一家都有自己的 SDK、鉴权方式、数据格式。
这种混乱带来的直接后果是:业务代码被死死绑定在某一家模型上。以后想换模型,不是改一行配置,而是改掉整个对接模块,还得重新测试各种边界情况。
1.2 LiteLLM 的双层定位:SDK 封装 + 网关代理
LiteLLM 的解法很直接:在代码层做了一套统一封装,把你跟各家 SDK 的差异全部挡在外面。你调用litellm.completion(),传进去的消息结构是统一的,返回结果也是统一格式。它会根据你在 model 参数里写的模型名,自动选择合适的供应商 SDK,帮你转换参数、组装请求、解析响应。
在此基础上,它还提供了一个网关模式,相当于把统一调用这层逻辑从代码里抽出来,部署成独立服务。前端不管用的是 Python、Node.js 还是 curl,只要按标准接口请求网关,网关再帮你转发到真实的大模型服务商。这样做的工程价值有两个:第一,调用方只需要掌握一套协议,不需要安装各家 SDK;第二,密钥集中在服务端管理,不会散落在每个客户端环境里。
用生活的类比来说,SDK 模式像是一个万能遥控器,一个遥控器控制各种品牌的电视;网关模式则像一个智能家居中枢,所有电器的指令都汇总到这里,由中枢统一分发。
2. 核心设计拆解:为什么一套接口能通吃所有模型
2.1 把 OpenAI 协议当作“通用语言”
LiteLLM 在设计上有一个很关键的选择:它的统一接口长得很像 OpenAI 的接口。为什么选 OpenAI,而不是另起炉灶设计一套全新的规范?因为从实际生态看,OpenAI 的接口格式已经成了事实标准。市面上大量的开源项目、内部工具、第三方平台都兼容 OpenAI 格式,很多模型服务(包括本地部署的框架)也都提供 OpenAI 兼容端点。与其发明新格式,不如站在已有标准上,这样用户迁移成本最低。
所以你看 LiteLLM 的调用代码,会觉得很眼熟:messages列表、role、content、model,几乎跟 OpenAI SDK 一模一样。它内部把这些统一参数映射成各家 SDK 需要的格式,比如 Anthropic 要求max_tokens是必填项,LiteLLM 会在底层自动补上;Google Gemini 的消息角色体系不同,它也会转换。
2.2 provider 前缀与模型路由规则
LiteLLM 的模型名不是随便写的,它有一套明确的命名约定:供应商/具体模型名。比如:
openai/gpt-4o表示走 OpenAI 官方接口anthropic/claude-3-5-sonnet-20241022表示走 Anthropicazure/gpt-4o表示走 Azure OpenAI 服务gemini/gemini-1.5-pro表示走 Google Geminiollama/llama3表示走本地 Ollama 实例
前面这段前缀就是路由标识,LiteLLM 靠它来决定用哪家的 SDK、读哪个环境变量里的密钥、按哪种格式解析响应。如果你不加前缀,LiteLLM 会默认按 OpenAI 接口来处理。这个设计在代理模式里更关键,因为配置文件里可以给同一个对外模型名绑定多个实际供应商实例,网关层做路由和负载均衡。
2.3 参数映射与兼容策略
各家模型支持的参数不一样,这是做统一封装最头疼的部分。有的模型支持temperature,有的只支持top_p,有的规定了严格的max_tokens范围。LiteLLM 采取的策略是“尽量转换,拿不准就丢弃”。
配置里有个drop_params开关,默认行为比较保守,遇到目标模型不认识的参数会抛异常提示你;把这个开关打开之后,LiteLLM 会自动过滤掉不支持的参数,保证请求能发出去。比如同一个请求里带了logprobs,发给不支持这个参数的模型时,就会被静默移除。这个开关对兼容性非常实用,但也要留意副作用:你本来想设置的参数被丢掉了,模型输出行为可能会跟预期不完全一致。
3. 上手实操:SDK 模式怎么在五分钟内跑通
3.1 安装与第一次调用
安装只需要一行命令:
pip install 'litellm[proxy]'我建议直接带上[proxy]后缀装,这样 SDK 和网关相关的依赖一次性到位,后面部署代理不用再补装。装完之后,先设置好环境变量。以 OpenAI 为例:
export OPENAI_API_KEY="你的密钥"然后写第一个调用:
from litellm import completion response = completion( model="gpt-4o", messages=[ {"role": "system", "content": "你是一个严谨的助手。"}, {"role": "user", "content": "用一句话解释什么是消息队列。"} ] ) print(response.choices[0].message.content)这里返回的response结构跟 OpenAI SDK 基本一致,choices[0].message.content可以直接取到文本内容,不需要关心底层是哪个供应商。
3.2 切换模型到底有多丝滑
切换模型实际测试下来非常直观。比如现在要从 GPT 换成 Claude,代码改动只有 model 参数那一个字符串:
response = completion( model="anthropic/claude-3-5-sonnet-20241022", messages=[ {"role": "system", "content": "你是一个严谨的助手。"}, {"role": "user", "content": "用一句话解释什么是消息队列。"} ] )前提是提前设置好ANTHROPIC_API_KEY环境变量。消息结构不用动,返回结构不用动。如果换成本地模型,同样只有一个 model 参数的区别,比如ollama/llama3。我实际测试过这种方式做模型对比,写一个小脚本在几个模型之间轮询,比之前维护多套 SDK 省心太多了。
3.3 流式输出与函数调用
流式输出是另一个每天都在用的功能。LiteLLM 支持透传流式参数:
from litellm import completion response = completion( model="gpt-4o", messages=[{"role": "user", "content": "给我写一首简短的诗"}], stream=True ) for chunk in response: delta = chunk.choices[0].delta.content if delta: print(delta, end="", flush=True)函数调用也保留了原汁原味的体验。你按 OpenAI 的tools格式定义工具列表,LiteLLM 会转换成不同模型能理解的格式。像 Claude 的函数调用格式跟 OpenAI 不完全一样,LiteLLM 在底层做了转换,业务代码里拿到的仍然是统一的tool_calls结构。这块我强烈建议你在正式项目里先做一次回归测试,因为各家模型对工具调用的遵循程度不一致,统一接口解决的是“格式不通”的问题,解决不了“模型本身能力差异”的问题。
4. 服务化部署:用网关模式管理团队 AI 接入
4.1 一条命令启动网关服务
网关模式是 LiteLLM 最吸引团队用户的地方。装好依赖之后,最简单的启动方式:
litellm --model gpt-4o --port 4000这样会在本地 4000 端口起一个服务,它对外暴露的是 OpenAI 风格的/v1/chat/completions接口。客户端只需要把 base_url 指向这个服务,再配上本地密钥就能请求:
curl 'http://localhost:4000/v1/chat/completions' \ -H 'Content-Type: application/json' \ -H 'x-api-key: sk-1234' \ -d '{ "model": "gpt-4o", "messages": [{"role": "user", "content": "你好,请做一下自我介绍"}] }'默认情况下,网关会接受一个虚拟密钥sk-1234。生产环境一定要用--master_key指定主密钥,否则任何拿到端口访问权限的人都能借用上游模型额度。
4.2 用配置文件接管所有模型
命令行方式适合测试,真正生产环境要写配置文件。LiteLLM 的config.yaml核心是model_list,下面是实际可用的一段配置:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: claude-3 litellm_params: model: anthropic/claude-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY - model_name: local-llama litellm_params: model: ollama/llama3 api_base: http://localhost:11434启动方式变成:
litellm --config config.yaml --port 4000注意这里api_key用的是os.environ/OPENAI_API_KEY这种写法,意思是让 LiteLLM 启动时自己去读环境变量。密钥不写死在配置文件里,这个习惯值得养成,因为配置文件经常要提交到仓库里,而环境变量可以留在服务器上。
4.3 负载均衡与故障转移
如果你同一个模型配了多个来源,LiteLLM 会自动做负载均衡。比如把 GPT-4o 同时绑定 OpenAI 官方和 Azure OpenAI:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: gpt-4o litellm_params: model: azure/gpt-4o api_key: os.environ/AZURE_OPENAI_API_KEY api_base: os.environ/AZURE_OPENAI_API_BASE两个条目用了同一个对外名字gpt-4o,网关会把它们看成一个逻辑模型的两个后端实例,按负载策略分发请求。如果其中一个后端超时或返回异常,LiteLLM 会自动把请求重试到另一个可用实例上。这个机制在提高可用性上非常有用,尤其你不想让业务方感知到底层供应商切换的时候。
4.4 密钥隔离与预算控制
网关模式还有一个很多人第一天就会用上的功能:虚拟密钥管理。团队内部不用每个人都持上游密钥,而是由管理员在 LiteLLM 里生成虚拟 key,按成员或项目分配。这样就算某个成员的 key 泄露了,直接吊销虚拟 key 就行,不需要去上游服务商那边轮换主密钥。
预算控制也很实用。你可以在虚拟 key 上设置额度,也可以全局设置max_budget:
litellm_settings: max_budget: 500额度用完之后,网关会直接拒绝请求并提示超预算。做内部项目的时候,这个功能能避免有人写了个死循环把一个月预算烧光。
5. 进阶玩法:私有模型接入与使用量追踪
5.1 接入本地 OpenAI 兼容服务
很多团队会自己部署模型服务,比如用 vLLM 或其他推理框架起一个 OpenAI 兼容端点。这类服务接入 LiteLLM 很简单,核心就是把model前缀写成openai/,再指定api_base:
model_list: - model_name: self-hosted-model litellm_params: model: openai/self-hosted-model api_base: http://127.0.0.1:8000/v1 api_key: dummyapi_key写dummy是因为本地端点通常不做鉴权,但 LiteLLM 要求这个字段存在才肯发起请求。这种接入方式把内部模型和外部模型统一管理了,客户端不需要知道自己的请求会被转发到本地还是云端。
5.2 调用记录与成本统计
网关跑起来之后,每次请求的模型名、token 用量、耗时、成本都会被记录下来。LiteLLM 提供查询接口,比如请求/spend可以看到按 key 维度的花费统计。这个数据对做成本分摊和容量规划很有参考价值。
如果你想把日志接入自己的监控系统,可以配置回调。SDK 模式下也能直接用:
import litellm def log_this_call(data): # 把 data 发给你的日志服务 pass litellm.success_callback = [log_this_call]生产环境建议把成功、失败回调都接上,尤其是失败回调。上游偶尔会返回限流错误或 5xx,集中记录这些信息能帮你快速发现哪家服务不稳定。
6. 实地踩坑:高频问题与排查思路
6.1 常见报错的对照排查表
我把自己在项目里遇到过的典型报错整理成了表格,方便快速对照:
| 报错信息 | 常见原因 | 解决办法 |
|---|---|---|
| AuthenticationError: 401 | 环境变量没设、密钥错误 | 检查OPENAI_API_KEY等变量是否正确,确认网关里的虚拟 key 是否有效 |
| NotFoundError: model not found | model 名字写错、该前缀未绑定 | 核对 model 拼写,检查是否用了openai/、anthropic/这种前缀 |
| BadRequestError: context length exceeded | 输入太长,超过模型上下文窗口 | 做文本截断,或改用支持更长上下文的模型 |
| APIConnectionError: Request timed out | 上游响应慢、网络不通 | 调大超时时间,检查网络连通性,观察上游状态页 |
| RateLimitError: 429 | 触发了上游限流 | 在网关配置里加多一个同模型实例做负载均衡 |
| drop_params 后参数不生效 | 被模型丢弃了 | 确认是否真的需要该参数,必要时升级模型版本 |
6.2 一套实用的排错思路
遇到问题先别急着改代码,按下面这个顺序排查能省很多时间。
第一步,确认请求有没有出网关。在 config.yaml 里打开 verbose 日志,跑到出错的位置看最后的请求体长什么样。很多时候模型名拼写错了,或者 api_base 写错,日志里一眼就能看出来。
第二步,确认是网关层的问题还是上游的问题。直接拿 curl 测网关,再单独用商家的官方 SDK 发同样的请求。如果官方 SDK 也报错,说明问题在上游;如果官方 SDK 正常但网关报错,问题就在 LiteLLM 的配置或参数映射上。
第三步,检查环境变量有没有真的被读到。LiteLLM 读环境变量是在启动阶段完成的,你改了.env文件之后如果没重启服务,它不会自动刷新。我第一次用的时候改完环境变量直接发请求,连续报 401 查了很久,最后重启服务就好了。
6.3 容易忽略的几个细节
超时配置一定要单独设置。LLM 接口响应速度受生成长度影响很大,LiteLLM 默认请求可能比较保守,实际调长文本生成时经常碰到超时。在配置里显式设置timeout,比如 600 秒,比在客户端反复重试健康得多。
max_tokens的命名在不同模型间有差异。新版本的 OpenAI 模型可能要求max_completion_tokens,而 Claude 又用max_tokens。LiteLLM 会做映射,但为了少踩坑,建议在配置层统一留意 drop_params 的行为。
不要把主密钥写进任何配置文件。我见过有人图省事,把上游密钥直接填在 config.yaml 里,然后这个文件被提交到了内部仓库。虽然只是内部仓库,但密钥这种东西一旦进了历史记录,后续轮换成本非常高。用os.environ/引用环境变量并没有多麻烦。
最后说一点我自己的体会。最早我做一个多模型对比工具的时候,是拿 switch-case 挨个写各家 SDK 的适配,代码丑不说,加一个新模型要改一堆文件。后来把 LiteLLM 的 SDK 模式接进去,所有逻辑立刻简化成一张配置表和一行调用。再往后项目需要给团队多人提供模型访问能力,我直接上了网关模式,把密钥管理、成本统计、负载均衡一次性补齐。这个工具的定位非常贴合实际工程需求:它没有重新发明一套协议,而是站在 OpenAI 格式这个事实上,把多模型接入的成本压到了最低。如果你正在做模型接入相关的工作,我的建议是从 SDK 模式开始,跑通一个小功能验证效果,再决定要不要上网关。不要一上来就堆一堆高级配置,先把核心链路跑顺,后面扩展自然就很顺了。