1. 从一个真实困境说起:多模型调用的碎片化之痛
如果你正在做AI应用开发,大概率遇到过这样的场景:项目初期用OpenAI的接口跑通了原型,效果不错,上线后老板说"能不能换成国产模型降本",或者产品经理要求"同时接入三家模型做A/B测试"。这时候你打开代码一看,每个模型的SDK调用方式、参数命名、返回结构、错误码全都不一样,光是写适配层就能耗掉一周。
我自己就踩过这个坑。去年做一个智能客服的Demo,前后对接了四个不同厂商的大模型接口,每个厂商的鉴权方式不同、流式输出的解析逻辑不同、连temperature的取值范围都有差异。代码里到处是if-else分支,维护成本极高。后来接触到LiteLLM这个工具,才算是找到了一个相对优雅的解法。
LiteLLM的核心价值用一句话概括:用一套OpenAI格式的接口,调用市面上几乎所有主流大模型。你只需要把API Key和模型名称传进去,剩下的格式转换、异常处理、重试逻辑它帮你搞定。对于需要快速切换模型、做多模型对比、或者构建模型无关的AI应用的开发者来说,这是一个能实实在在省时间的工具。
这篇文章我会从实际使用角度出发,把LiteLLM的安装配置、核心用法、多模型路由策略、代理服务部署、以及我在使用过程中踩过的坑,完整地梳理一遍。不管你是刚接触大模型API的新手,还是已经在做多模型集成的老手,应该都能从中找到有用的东西。
2. LiteLLM到底解决了什么问题:拆解它的核心设计思路
2.1 统一接口层的本质:格式转换与协议适配
LiteLLM做的事情,本质上是在你的代码和各个大模型厂商的API之间,加了一个中间层。这个中间层对外暴露的是OpenAI风格的接口格式,对内则负责把请求翻译成各个厂商能理解的格式,再把返回结果翻译回OpenAI的格式。
为什么选择OpenAI格式作为统一标准?原因很实际:OpenAI的Chat Completions接口是目前事实上的行业标准,绝大多数开发者对它最熟悉,大量的开源工具和框架也都默认支持这个格式。LiteLLM选择它作为"通用语言",学习成本最低。
具体来说,当你调用completion(model="某模型", messages=[...])的时候,LiteLLM内部会做这几件事:
- 根据模型名称判断属于哪个厂商,比如
gpt-4走OpenAI、claude-3-opus走Anthropic、gemini-pro走Google - 把统一的messages格式转换成目标厂商要求的格式,比如Anthropic要求system prompt单独传,LiteLLM会自动拆分
- 处理参数映射,比如OpenAI的
max_tokens在某些厂商那里叫max_output_tokens - 发送请求并接收响应
- 把响应转换回OpenAI的格式,包括
choices、usage等字段
这个过程对调用方完全透明,你不需要关心底层发生了什么。
2.2 支持的模型范围:从闭源商业模型到本地部署
LiteLLM支持的模型列表相当长,我按类别梳理一下常见的:
| 类别 | 代表模型 | 接入方式 |
|---|---|---|
| 商业闭源 | GPT系列、Claude系列、Gemini系列 | 官方API Key |
| 国产大模型 | 通义千问、智谱GLM、月之暗面、百川 | 各自API Key |
| 开源托管 | Llama系列、Mistral系列 | Together AI、Replicate等平台 |
| 本地部署 | 任意兼容OpenAI格式的本地服务 | 自定义base_url |
| 云平台 | 各大云厂商的模型服务 | 对应SDK或API |
这个覆盖面基本能满足绝大多数场景。我实测下来,主流模型的支持都比较稳定,小众模型偶尔会有参数映射不完整的情况,但社区更新很活跃,遇到问题提Issue通常很快能得到响应。
2.3 和直接调用SDK相比,多了一层会不会有性能损失
这是很多人关心的问题。我的实测结论是:网络请求本身的耗时远大于格式转换的耗时,LiteLLM带来的额外开销基本可以忽略。
格式转换是纯内存操作,微秒级别。真正耗时的是网络往返,这部分不管你用不用LiteLLM都一样。我用同一个模型做了对比测试,直接调用SDK和通过LiteLLM调用,端到端延迟差异在5%以内,大部分时候在统计误差范围内。
唯一需要注意的是,如果你用了LiteLLM的代理模式(后面会讲),多了一次本地HTTP转发,会多几毫秒,但相比大模型动辄几秒的推理时间,这点开销不值一提。
3. 环境搭建与基础调用:从零跑通第一个例子
3.1 安装与API Key配置的几种方式
安装很简单:
pip install litellm如果你需要用到代理服务功能,建议装完整版:
pip install 'litellm[proxy]'API Key的配置方式有好几种,我按推荐程度排序:
方式一:环境变量(最推荐)
export OPENAI_API_KEY="你的key" export ANTHROPIC_API_KEY="你的key"LiteLLM会自动读取对应厂商的环境变量,代码里不用传key,干净利落。
方式二:代码中显式传入
import litellm response = litellm.completion( model="gpt-4", messages=[{"role": "user", "content": "你好"}], api_key="你的key" )这种方式适合快速测试,但key硬编码在代码里有泄露风险,不建议在生产环境用。
方式三:配置文件
LiteLLM支持用YAML配置文件管理多个模型的key和参数,适合模型数量多的场景。配置文件里可以给每个模型起别名,调用时用别名就行。
注意:不管用哪种方式,key都不要提交到代码仓库。我见过有人把key写在代码里推到公开仓库,结果被扫到后产生大量异常调用,这个教训很深刻。
3.2 第一次调用:用三行代码跑通
装好之后,最简单的调用长这样:
from litellm import completion response = completion( model="gpt-3.5-turbo", messages=[{"role": "user", "content": "用一句话解释什么是机器学习"}] ) print(response.choices[0].message.content)如果你之前用过OpenAI的SDK,会发现这个调用方式几乎一模一样。区别只在于model参数可以填其他厂商的模型名。
换成Claude试试:
response = completion( model="claude-3-sonnet-20240229", messages=[{"role": "user", "content": "用一句话解释什么是机器学习"}] )代码结构完全没变,只改了模型名。这就是LiteLLM最直观的价值。
3.3 流式输出与异步调用:实际项目中的必备技能
做聊天类应用,流式输出是刚需。LiteLLM的流式用法:
response = completion( model="gpt-4", messages=[{"role": "user", "content": "写一首关于秋天的诗"}], stream=True ) for chunk in response: content = chunk.choices[0].delta.content if content: print(content, end="", flush=True)异步版本用acompletion:
import asyncio from litellm import acompletion async def main(): response = await acompletion( model="gpt-4", messages=[{"role": "user", "content": "你好"}] ) print(response.choices[0].message.content) asyncio.run(main())异步在高并发场景下非常关键。我之前做一个批量内容生成的工具,同步调用100条要等好几分钟,改成异步并发后缩短到十几秒。
4. 多模型路由与降级策略:让应用更稳的关键配置
4.1 模型别名:让代码和具体模型解耦
实际项目中,我强烈建议用模型别名。原因很简单:今天用GPT-4,明天可能因为成本或合规原因换成别的,如果代码里到处写死模型名,切换时改起来很痛苦。
配置方式:
import litellm litellm.model_alias = { "default": "gpt-4", "fast": "gpt-3.5-turbo", "cheap": "claude-3-haiku-20240307" } response = completion( model="default", messages=[{"role": "user", "content": "你好"}] )这样代码里只用别名,切换模型时改配置就行。
4.2 降级与重试:一个模型挂了自动切下一个
这是LiteLLM很实用的一个功能。生产环境中,某个厂商的API偶尔会抽风,如果只有一个模型,服务就挂了。用fallback配置可以自动切换:
response = completion( model="gpt-4", messages=[{"role": "user", "content": "你好"}], fallbacks=["claude-3-sonnet-20240229", "gpt-3.5-turbo"] )当gpt-4调用失败时,会自动尝试claude,再失败就试gpt-3.5-turbo。
重试配置:
response = completion( model="gpt-4", messages=[{"role": "user", "content": "你好"}], num_retries=3, timeout=30 )num_retries控制重试次数,timeout控制超时时间。我的经验是,对于推理类任务,timeout设60秒比较稳妥,太短容易误杀正常请求。
4.3 负载均衡:多个key轮询分摊压力
如果你有多个API Key(比如团队共用),可以用轮询方式分摊:
response = completion( model="gpt-4", messages=[{"role": "user", "content": "你好"}], api_key=["key1", "key2", "key3"] )LiteLLM会自动轮询使用这些key。这在做压测或者key有速率限制时很有用。
实操心得:fallback的顺序有讲究。我一般把质量最好的放第一位,性价比高的放第二位,最便宜的放最后兜底。这样正常情况用最好的,出问题时降级到能用的,不至于服务中断。
5. 代理服务模式:把LiteLLM变成一个统一的API网关
5.1 为什么需要代理模式
前面讲的都是Python代码里调用LiteLLM。但如果你团队里有多个项目,有的用Python、有的用Node.js、有的用Go,总不能每个语言都集成一遍LiteLLM。
代理模式解决的就是这个问题:把LiteLLM跑成一个HTTP服务,对外暴露OpenAI兼容的API。任何语言、任何工具,只要能发HTTP请求,就能通过它调用所有模型。
5.2 配置文件详解与启动步骤
先写一个配置文件config.yaml:
model_list: - model_name: gpt-4 litellm_params: model: gpt-4 api_key: os.environ/OPENAI_API_KEY - model_name: claude litellm_params: model: claude-3-sonnet-20240229 api_key: os.environ/ANTHROPIC_API_KEY general_settings: master_key: sk-你的管理密钥启动服务:
litellm --config config.yaml --port 4000启动后,你就可以用任何HTTP客户端调用:
curl http://localhost:4000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-你的管理密钥" \ -d '{ "model": "gpt-4", "messages": [{"role": "user", "content": "你好"}] }'5.3 代理模式下的用量追踪与成本控制
代理模式自带用量记录功能,可以在配置里开启:
general_settings: master_key: sk-你的管理密钥 database_url: postgresql://user:pass@localhost:5432/litellm配上数据库后,每次调用的token消耗、费用、延迟都会被记录,可以在管理界面查看。对于需要控制成本的团队来说,这个功能很实用。
我自己的用法是:给每个项目分配一个虚拟key,设置月度预算上限,超了就拒绝请求。这样能有效防止某个项目意外跑飞导致账单爆炸。
6. 踩坑实录:那些文档里没写的注意事项
6.1 参数映射的坑:不是所有参数都能通用
LiteLLM虽然做了参数映射,但不同厂商支持的参数集不完全一样。比如OpenAI的logit_bias、presence_penalty这些参数,很多国产模型根本不支持。传了不支持的参数,有的厂商会忽略,有的会直接报错。
我的做法是:只传最通用的参数(model、messages、temperature、max_tokens、stream),其他参数按厂商分别处理。如果确实需要某个厂商特有的功能,用extra_body传:
response = completion( model="某国产模型", messages=[{"role": "user", "content": "你好"}], extra_body={"某厂商特有参数": "值"} )6.2 流式输出的边界情况:空chunk和异常中断
流式输出时,偶尔会遇到chunk里delta.content为None的情况,直接拼接会报错。稳妥的写法是加判断:
for chunk in response: if chunk.choices and chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="")另外,网络中断时流可能突然断掉,要做好异常捕获和重连逻辑。我在生产环境里加了一层包装,记录已经输出的内容,断掉后从断点继续。
6.3 版本升级带来的兼容性问题
LiteLLM更新很频繁,偶尔会有breaking change。我有一次升级后,原来能跑的fallback配置突然不生效了,排查半天发现是新版本改了参数名。
建议:生产环境锁定版本号,升级前先在测试环境验证。用pip install litellm==指定版本来锁定。
6.4 本地模型接入的常见问题
接入本地部署的模型时,最常见的问题是base_url配置。如果你本地跑了一个兼容OpenAI格式的服务,配置方式:
response = completion( model="openai/你的模型名", messages=[{"role": "user", "content": "你好"}], api_base="http://localhost:8000/v1", api_key="随便填一个" )注意model前面要加openai/前缀,告诉LiteLLM用OpenAI兼容协议。api_key本地服务通常不校验,但有些实现要求非空,随便填一个就行。
7. 几个典型场景的完整实现思路
7.1 场景一:多模型A/B测试框架
做模型对比时,可以用LiteLLM快速搭建测试框架:
import litellm from litellm import completion models = ["gpt-4", "claude-3-sonnet-20240229", "通义千问模型名"] prompt = "解释一下什么是注意力机制" results = {} for model in models: try: response = completion( model=model, messages=[{"role": "user", "content": prompt}], timeout=60 ) results[model] = { "content": response.choices[0].message.content, "tokens": response.usage.total_tokens, "cost": litellm.completion_cost(response) } except Exception as e: results[model] = {"error": str(e)}completion_cost能直接算出这次调用的费用,做成本对比很方便。
7.2 场景二:成本敏感的智能路由
根据问题复杂度自动选择模型:
def smart_route(question): # 简单问题用便宜模型 if len(question) < 50: model = "gpt-3.5-turbo" else: model = "gpt-4" return completion( model=model, messages=[{"role": "user", "content": question}] )实际实现可以更精细,比如先用小模型判断问题类型,再路由到合适的模型。这套逻辑配合LiteLLM的别名机制,切换底层模型时上层代码完全不用动。
7.3 场景三:带缓存的重复请求优化
LiteLLM支持缓存,相同请求直接返回缓存结果:
import litellm litellm.cache = litellm.Cache(type="local") response = completion( model="gpt-4", messages=[{"role": "user", "content": "你好"}], caching=True )对于FAQ类场景,缓存能大幅降低成本。不过要注意,缓存key是基于请求内容的,如果temperature不为0,相同请求可能期望不同结果,这时候要谨慎使用。
8. 我个人的使用建议与选型参考
用了一年多LiteLLM,说几点真实体会。
适合用的场景:需要快速切换模型做验证、多模型fallback保证可用性、团队多语言项目需要统一网关、想低成本做模型对比测试。这些场景下LiteLLM能省掉大量适配工作。
不太适合的场景:只需要调用单一模型且不打算换、对延迟极度敏感(虽然差异很小但确实存在)、需要用到某厂商非常底层的特有功能。这些情况下直接调SDK可能更合适。
版本选择:生产环境建议用稳定版,不要追最新。我一般等新版本发布两周后再升级,让社区先踩踩坑。
监控不能省:不管用不用代理模式,都要记录每次调用的模型、耗时、token数、是否成功。这些数据是后续优化和排查问题的依据。
最后说一个容易被忽略的点:LiteLLM的异常类型做了统一封装,捕获litellm.exceptions下的异常比捕获各厂商原生异常方便得多。建议在代码里统一处理RateLimitError、Timeout、APIError这几类,配合重试和fallback,服务的稳定性会有明显提升。