做AI应用开发的人,早晚都会撞上同一个烦心事:项目里接了三五个模型,OpenAI、Anthropic、国产开源模型各有一套API规范,调用方式、参数命名、返回结构全都不一样。每次切换模型都要改代码、调参数,测试下游效果时更是折腾。后来我在项目里引入了litellm,这个工具把这些问题基本全解决了。这篇就聊聊它的核心原理、实际用法,以及我在生产环境里踩过的一些坑,希望能给你省点时间。
litellm是一个开源的LLM(大语言模型)API网关和统一接口层。简单说,它把市面上主流模型提供商的API风格标准化为OpenAI格式,你只需要写一套代码,就能通过它调用不同厂商的模型。它同时支持SDK调用模式和代理服务模式,还自带成本统计、负载均衡、超时重试、预算控制等能力。适合任何需要在多个模型之间做切换、对比、调度,或者想让团队统一接入AI能力的开发者使用。
1. 为什么会有这么个东西:多模型接入的一天噩梦
先还原一个真实场景。假设你在做一个智能客服Demo,老板说要对比三家模型的效果,再决定最终用哪家。于是你开始接API:第一家是OpenAI格式的,用/v1/chat/completions接口,请求体长这样:
{ "model": "gpt-4o-mini", "messages": [ {"role": "system", "content": "你是一个客服助手"}, {"role": "user", "content": "我想退货"} ], "temperature": 0.7 }第二家是Anthropic格式的,接口路径变成/v1/messages,请求体变成了这样:
{ "model": "claude-sonnet-4-20250514", "system": "你是一个客服助手", "messages": [ {"role": "user", "content": "我想退货"} ], "max_tokens": 1024 }第三家是个国产开源模型的兼容接口,虽然号称兼容OpenAI格式,但max_tokens参数名改成了max_new_tokens,流式返回的数据结构也做了微调。一个模型一套写法,你只好写一堆适配代码,用if-else去拼不同的请求体。这还只是调用层的问题,到了切换模型对比效果的时候更痛苦:日志格式不统一,成本不好统计,每次调用都要单独记录token用量,然后手动换算成人民币。
这个状态持续了一段时间,代码越来越难维护。我当时想过自己写一个适配层,但转念一想,这不是重复造轮子吗?做模型接入相关的开发者这么多,一定已经有成熟的方案了。后来在某篇文章里看到了litellm,试用了一次就决定全面引入。它解决的核心问题,就是把所有模型的API映射到一套统一的接口上,让你从"适配各家规范"的琐碎工作里解放出来。
这个思路其实很朴素,类比一下就是电源插头转换器。全世界有各种标准的插座,你不能要求每个电器都自带所有插头,只能用一个转换器来抹平差异。litellm在AI应用里的角色,就是这个转换器。
2. litellm到底做了什么:接口归一化与参数映射的价值
litellm的核心是一个归一化层。外面统一暴露OpenAI风格的API,里面针对每个模型提供商写好了适配逻辑。你在代码里永远只用messages、model、temperature、max_tokens这些标准字段,剩下的事情交给它去翻译。
2.1 参数级别的精细映射
很多人以为归一化就是改个URL、换个key,实际上没有这么简单。光是参数字段映射就有很多细节:
temperature在大多数模型里都存在,但OpenAI的取值范围是0到2,Anthropic是0到1,litellm会在内部做转换或直接透传。max_tokens这个概念在不少模型里的叫法都不一样,有的叫max_new_tokens,有的叫max_completion_tokens。litellm统一处理后,你传一个max_tokens,它会自动翻译成目标模型认识的字段。system消息的处理方式差异很大。OpenAI把system作为messages数组里的一种角色,Anthropic在旧版API里要求system单独作为一个顶层字段,还有些模型根本不支持system角色。litellm处理多轮对话时会在内部重组消息结构。- 停止符
stop在部分模型里是个字符串,在另一些里是字符串数组,它也会根据目标API类型做适配。
如果只做SDK层面的适配,litellm也就是个好用的库而已。真正的重头戏是它的代理(Proxy)模式。
2.2 代理模式:把一整套接入逻辑变成服务
代理模式下,litellm会起一个本地HTTP服务,对外提供/v1/chat/completions这样的OpenAI兼容接口。你的应用只需要把base_url指向这个服务,把api_key设为任意值,就可以像调用OpenAI一样调用它配置好的任何模型。这意味着什么呢?
第一,你的业务代码可以直接复用所有基于OpenAI SDK写好的逻辑,不管底层换成了Claude还是国产模型,业务代码一行都不用改,改的是服务的配置。
第二,模型切换可以做到运行时动态完成。比如你在配置里同时挂了gpt-4o-mini和claude-sonnet-4-20250514两个模型,只要在请求里改一下model字段,就能立刻切换,不需要发布新版本。
第三,它是团队协作的利器。后端同学写好一套调用代码,前端、测试、算法同学都可以直接连这个代理调试,不用各自去申请各个厂商的API Key。所有key都集中在代理服务这一侧,安全性和可管理性都提升了一大截。
还有一个容易被忽略的价值是成本统计。模型调用按token计费,不同模型的价格差异很大。litellm的代理模式会在内部记录每次请求的输入输出token数,并按照价格表换算成美元,你可以随时查看每个用户、每个模型的累计花费。这对于企业场景来说几乎是刚需。
3. 从零接入一个真实项目:安装、配置与第一个请求
接下来直接进入实践环节。我用一个虚构的智能翻译项目来演示,从安装到跑通完整链路,整个过程大概需要十分钟。
3.1 环境准备与安装
建议用Python 3.9+的版本,创建一个干净的虚拟环境再装。安装命令:
pip install 'litellm[proxy]'[proxy]这个extra会一并装上代理模式需要的依赖,比如FastAPI和uvicorn。如果你只需要SDK模式,直接pip install litellm就行。
装完之后验证一下版本:
python -c "import litellm; print(litellm.__version__)"我刚开始用的时候,遇到过装完了但proxy命令找不到的情况,后来排查发现是虚拟环境没有激活,命令装到了别的环境里。这种基础问题其实很常见,先确认环境再往下走。
3.2 SDK模式:先跑通一个最小的翻译功能
SDK模式是最灵活的用法,适合在业务代码里直接调用。先设置环境变量:
export OPENAI_API_KEY="你的OpenAI密钥" export ANTHROPIC_API_KEY="你的Anthropic密钥"然后写一段用于调用多个模型的代码,做一个翻译功能:
import litellm def translate_with_fallback(text, source_lang, target_lang, primary_model, fallback_model): response = litellm.completion( model=primary_model, messages=[ {"role": "system", "content": f"你是资深翻译,负责把{source_lang}翻译成{target_lang},只输出译文"}, {"role": "user", "content": text} ], temperature=0.2, max_tokens=1000, fallbacks=[fallback_model] ) return response["choices"][0]["message"]["content"]这里有个值得留意的点:fallbacks参数。我在实际项目中非常依赖它。当主模型因为限流、超时、服务故障等原因调用失败时,litellm会自动尝试后面的备用模型,并且会把这个过程记录下来。也就是说,你不需要自己写重试逻辑,它已经替你处理好了。
如果使用异步非阻塞方式,可以用AsyncClient:
from litellm import AsyncClient client = AsyncClient() async def async_translate(text): response = await client.completion( model="openai/gpt-4o-mini", messages=[{"role": "user", "content": f"翻译成英文:{text}"}] ) return response["choices"][0]["message"]["content"]注意这里的模型名带上了前缀openai/、anthropic/。这是litellm推荐的写法,用来消除不同提供商之间的模型名歧义。虽然有些场景下不带前缀也能自动识别,但显式指定会更保险,尤其是在自建的兼容接口上。
不同的模型前缀对照如下:
| 模型来源 | 前缀示例 | 完整模型名示例 |
|---|---|---|
| OpenAI | openai/ | openai/gpt-4o-mini |
| Anthropic | anthropic/ | anthropic/claude-sonnet-4-20250514 |
| 谷歌 | gemini/ | gemini/gemini-1.5-pro |
| 阿里云 | dashscope/ | dashscope/qwen-plus |
| 智谱 | zhipu/ | zhipu/glm-4-flash |
3.3 代理模式:用配置文件管理多个模型
如果你的项目是团队协作,或者想统一管理多个模型的key和策略,代理模式更合适。创建一个config.yaml:
model_list: - model_name: gpt-4o-mini litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY - model_name: claude-sonnet litellm_params: model: anthropic/claude-sonnet-4-20250514 api_key: os.environ/ANTHROPIC_API_KEY model_info: max_tokens: 8192 - model_name: qwen-plus litellm_params: model: dashscope/qwen-plus api_key: os.environ/DASHSCOPE_API_KEY litellm_settings: drop_params: true set_verbose: true general_settings: master_key: sk-your-master-key database_url: os.environ/DATABASE_URL启动代理服务:
litellm --config config.yaml --port 4000服务跑起来后,用OpenAI SDK来调用:
from openai import OpenAI client = OpenAI( base_url="http://localhost:4000/v1", api_key="sk-your-master-key" ) # 调用gpt-4o-mini resp1 = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": "把'你好'翻译为英文"}] ) # 调用claude模型,只需要改model字段 resp2 = client.chat.completions.create( model="claude-sonnet", messages=[{"role": "user", "content": "把'你好'翻译为英文"}] )用curl验证也很方便:
curl http://localhost:4000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-your-master-key" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "你好"}] }'实测跑通之后,整个体验会非常丝滑——所有模型共享一套调用方式,切换模型只是在请求里换一个字符串。
4. 实测最有价值的三个隐藏能力:成本追踪、负载均衡与模型路由
用了大半年之后,我逐渐发现litellm最值钱的地方不全在API归一化上。它的进阶能力才是让它在生产环境里脱颖而出的原因。
4.1 成本追踪与预算控制
做AI应用的人都知道,模型调用的费用是随着用户量增长而快速膨胀的。一个聪明的方案必须在第一天就考虑成本度量。litellm把所有请求的记录都写入数据库,你可以查询:
- 每天、每个模型、每个API Key的花费明细
- 每个请求的输入token数、输出token数、延迟、缓存命中情况
- 按用户或团队维度的累计花费
配置预算也非常方便。在Config里加一段:
general_settings: max_budget: 100 # 总预算,单位美元 budget_duration: 30d # 预算周期一旦达到预算上限,代理会自动拒绝新的请求并返回错误提示。我还试过给不同API Key设置不同预算,比如给测试环境一个5美元的月预算,给生产环境一个80美元的月预算。对于初创团队来说,这些功能能在早期就避免很多费用失控的麻烦。
4.2 负载均衡:让重活分摊到多个模型
当你的应用流量上来了,单一模型入口很容易被限流。一个实际的案例是:我在一个批量处理任务里,高峰期每分钟要调用上千次,但单账号的并发限制远达不到这个量。litellm支持把同一个逻辑模型名绑定到多个实际模型配置上,实现请求在它们之间轮转。
model_list: - model_name: gpt-4o-mini litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY model_info: rpm: 500 - model_name: gpt-4o-mini-2 litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY2 model_info: rpm: 500配置两个不同的API Key指向同一个模型,当客户端请求model: gpt-4o-mini时,litellm会在两个配置之间做负载均衡调度。这样一个账号的配额耗尽,另一个账号顶上,整体吞吐量直接翻倍。配合rpm参数(requests per minute)设置,代理会尽量避免把请求发到接近限流阈值的配置上。
4.3 模型路由策略:不满足条件就降级
模型路由是litellm另一个我很喜欢的特性。你可以在配置里定义规则,比如当请求的模型不可用时,自动降级到其他模型;或者根据请求内容的关键词,路由到不同模型。
router_settings: routing_strategy: "simple-shuffle" fallbacks: [ {"gpt-4o": ["claude-sonnet", "qwen-plus"]} ] context_window_fallbacks: [ {"gpt-4o": ["claude-sonnet"]} ]这里的含义是:如果gpt-4o不可用,依次尝试claude-sonnet和qwen-plus;如果请求的token总量超过了gpt-4o的上下文窗口,就直接使用claude-sonnet。这些策略组合起来,可以搭建一个高可用的模型调用链路,对用户体验的提升是比较明显的。
5. 代理模式下的安全与权限控制细节
把API Key集中在代理这一侧后,安全问题就成了重点。实践过程中有几个关键配置值得注意。
首先是master key。所有通过代理发出去的请求都必须带一个有效的key,master key是最高权限。除此之外,你可以为不同用途生成独立的虚拟key:
curl http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-your-master-key" \ -H "Content-Type: application/json" \ -d '{ "models": ["gpt-4o-mini", "claude-sonnet"], "max_budget": 10, "expires": "2025-12-31T23:59:59" }'这个虚拟key只能访问指定的模型,预算10美元,到期自动失效。把这样的key发给团队成员,既保障了安全,又便于审计每个人的用量。
其次是IP白名单和速率限制。在config.yaml里可以开启:
general_settings: allowed_ip_list: - "10.0.0.0/8" - "192.168.1.100" max_parallel_requests: 100 cooldown_time: 30如果业务方需要限制某个虚拟key每分钟最多调用多少次,litellm也支持在生成key时指定rpm_limit和tpm_limit。这些能力组合起来,基本可以满足中小团队对API网关的绝大部分安全要求。
我自己在接代理到内网时,还会加一层反向代理(比如用Nginx)做TLS终止和基础访问控制。litellm本身解决的是模型路由和归一化问题,把它放在内网反向代理后面是一种更稳妥的组合方式。
6. 踩过的坑和处理方法:从环境变量到参数透传
再好的工具也有它的小脾气。下面这几个坑是我在真实使用中遇到的,每一个都花了不少时间排查,写出来给你避避雷。
6.1 代理模式启动后接口404
第一次启动litellm --config config.yaml --port 4000后,我访问http://localhost:4000/v1/models能通,但访问/v1/chat/completions就404。查了半天发现,问题出在Config文件的格式上:model_list里的model_name必须和客户端请求里的model字段严格一致,而当时我的客户端请求写的模型名是gpt-4o,配置里却是gpt-4o-mini,导致路由不到任何模型。
排查思路是:先直接请求/v1/models看代理暴露了哪些模型名,再对照客户端代码里的model字段,确保完全匹配。这个问题其实很基础,但混合了大小写、短横线之后很容易看走眼。
6.2 SDK模式下的max_tokens失效问题
在SDK模式里,我给一个国产模型传了max_tokens=8192,结果返回的内容明显短于预期。后来翻日志发现,该模型不支持max_tokens,而是使用max_new_tokens。litellm在默认情况下不会强行转换所有参数,而是选择直接忽略不认识的字段,或者根据配置决定是否透传。
解决方案是在Config里开启drop_params: true,或者在代码里设置:
import litellm litellm.drop_params = True加上之后,max_tokens在目标模型不支持时会被静默丢弃,不会报错,但代价是生成长度可能不受控。更可靠的做法是:查一下目标模型支持的参数,在请求里直接使用该模型的原生参数名。litellm允许你在标准参数之外额外传provider_specific_params,比如:
response = litellm.completion( model="dashscope/qwen-plus", messages=[{"role": "user", "content": "写一首诗"}], max_new_tokens=1024 # 原生参数会被透传 )6.3 环境的动态切换问题
litellm在初始化时会读取环境变量来确定目标provider的base_url和key。我有一次在配置文件里写了api_key: os.environ/OPENAI_API_KEY,但启动服务的环境变量里忘了设置,于是所有请求都报认证失败。这个错误信息比较模糊,我还一度以为是网络问题。
建议是写好配置后,先启动代理,然后直接curl /v1/models确认每个模型的状态,再看日志里是否有缺失key的警告。把环境变量统一放在一个.env文件里,配合类似dotenv的加载工具,能少掉很多麻烦。
6.4 流式响应场景下的兼容性坑
流式输出是聊天类应用的常见需求,偏偏也是兼容性问题最容易爆发的地方。有些模型在流式模式下返回的每一条增量(chunk)里都带一个空的content字段,有些则会省略role字段。litellm的代理模式会尽量把各家的流式响应统一成OpenAI格式,但个别模型在stream_options: {"include_usage": true}这种参数下会返回异常的usage字段。
我现在的做法是:在业务代码里对增量内容做一层额外的容错处理,先判断delta.content是否为空再拼接。不要指望任何网关能把所有细节都磨平,客户端保持一定的健壮性是必要的。
6.5 异步回调阻塞事件循环
在异步代码里使用litellm.completion而不是await client.completion会导致事件循环阻塞。这是新手容易犯的问题。所有在async环境里的调用都推荐使用AsyncClient,并且注意每次请求创建一个新的client实例或者复用同一个实例,不要让client跨请求并发混乱。实测中,复用client实例配合asyncio.Semaphore做并发控制,性能是最稳的。
6.6 token计数误差
litellm在计算token时,对多数主流模型使用的是其tokenizer的结果,但国产一些模型的tokenizer不是公开的,它只能靠估算。这意味着成本统计和实际账单可能有偏差。如果你对成本精确性要求很高,建议开启litellm的缓存计量,并定期和模型提供商的账单后台核对。差几个百分点是正常的,不必太焦虑。
7. 什么时候不该用litellm:边界与替代方案
说了这么多好处,也该泼点冷水。litellm并不是所有场景的最优解。
如果你的项目只使用单一模型,并且非常确定未来一两年都不会换,那引入它就是过度设计。多一层网关意味着多一个故障点、多一份运维成本,还会增加请求延迟。在这种场景下,直接用模型厂商的官方SDK是更合理的选择。
如果你的需求是深度定制某个模型的特殊能力,比如使用OpenAI的Assistants API,或者需要调用Anthropic独特的工具调用高级特性,通用网关往往不能及时跟上厂商的最新接口。这类场景还是建议先用原生SDK,等能力稳定后再考虑是否抽象。
另外,如果你的团队本身就有完整的API网关体系,有专门的网关团队维护流量转发和鉴权,那么把模型调用也纳入你们自己的网关管理可能更合适。litellm的价值主张是"开箱即用的AI模型网关",但如果组织内已有成熟的基础设施团队,你需要的可能只是它提供的模型路由能力,而不是它整个架子。
对我来说,选择工具的标准很简单:能不能降低长期的维护成本。litellm的价值不在于它今天能帮你省掉多少if-else,而在于当模型格局发生变化、公司需要引入新模型时,你不用重写核心调用链路。
从第一次接入到现在,我在好几个项目里都用了它。最开始带给我的是编码体验的提升,后来是团队协作效率的提升,再后来是成本可控性的提升。现在我已经养成了习惯:任何涉及多模型的项目,第一天就把litellm的代理模式搭起来,后续的模型切换都会轻松很多。如果你正在做类似的项目,不妨从一个小Demo开始试试,可能也会得到和我类似的结论。