1. 先说清楚:litellm 到底解决了什么问题
去年有段时间,我在做一个内部工具,需求很简单:同一个功能,今天用 OpenAI 的模型跑,明天可能要换成 Anthropic 的,后天客户说要支持国产模型。最痛苦的不是写功能,而是每次换厂商都要改一遍调用代码。OpenAI 的 SDK 用client.chat.completions.create,Anthropic 的用client.messages.create,Gemini 又是另一套generate_content。这还不算完,有的返回体字段叫choices,有的叫content,有的直接给你text,异常处理更是各写各的。
那段时间我的代码里全是if provider == "openai": ... elif provider == "anthropic": ...,改一个模型参数恨不得翻三个文档。后来发现 litellm 这个库,核心思路极其简单粗暴:把所有模型的调用方式统一成 OpenAI 的 chat completion 格式,你在代码里只用写一套逻辑,剩下的事它帮你转换。
简单说,litellm 就是一个统一大模型调用层的工具。你用它调 100 个模型,代码长的都一样,模型名从gpt-4o换成claude-3-5-sonnet只需要改一个字符串。它支持 OpenAI、Anthropic、Google、Azure、AWS Bedrock、国内各家厂商等几百个模型,还有本地跑的模型。对我这种要维护多套 API 的开发者来说,这玩意儿把脏活累活全包了。
2. 为什么这套方案值得用:核心设计思路拆解
2.1 统一接口的价值不是“少写几行代码”那么简单
很多人第一反应是:多写几个函数封装一下不就完了?确实可以,但 litellm 的思路比单纯的封装更深一层。它模仿 OpenAI 的接口设计,这不是随便选的。OpenAI 的接口约定俗称,社区里大量工具、文档、示例代码都基于这套格式,很多团队内部也已经有基于 OpenAI SDK 写的业务逻辑。litellm 选择兼容 OpenAI 格式,意味着你的代码可以直接用,不用为了换模型重新学一套 API。
更重要的是,统一接口让“模型可替换”成为可能。我之前做的一个客服问答系统,上线时用的是某主流模型,后来发现客户想要的回答风格更倾向于另一个厂商的模型。如果没有统一层,换模型意味着改动业务代码、重跑测试、处理新 SDK 的坑,最起码要一两天。用了 litellm 之后,我改动的地方只有配置中心的模型名,业务代码一行没碰。这种“解耦”才是真正解决痛点的地方。
2.2 它不只是转发请求,还在做格式翻译
litellm 内部其实做了大量工作,不只是把请求换个 URL 发出去。各大厂商的模型 API 虽然都叫“聊天补全”,但参数和返回结构差异很大,比如:
- OpenAI 的
temperature和top_p某些版本里直接传就行,但有的厂商要求取值范围是 0-100,有的要求传max_tokens却对超长输入有限制。 - 有的模型支持
stop参数传字符串数组,有的只支持单个字符串。 - 返回体里,有的给
content数组,里面每个段有type字段区分文本和图片,有的直接返回纯文本。
litellm 做的事就是把这些参数映射成目标 API 的格式,把返回结果再翻译成统一的 OpenAI 风格结构。你在代码里拿到的永远是response.choices[0].message.content,不管背后是什么模型。
2.3 你不需要管理每一家的 SDK 版本
这个点比较隐蔽,但实际项目中很容易踩坑。比如 Anthropic 的 SDK 升级了一个版本,把某个参数废弃了,或者 Python 3.12 下某个依赖装不上。如果你直接用官方 SDK,这些问题都得自己处理。litellm 把这一层包住了,你升级 litellm 就行。虽然它不能保证所有厂商的 SDK 都能跟上更新,但至少出了问题,社区一起扛,比个人硬扛好受得多。
3. 上手实操:从安装到跑通第一个请求
3.1 安装与基础配置
安装很简单:
pip install litellm安装完建议顺手看一下版本:
python -c "import litellm; print(litellm.__version__)"litellm 现在迭代很快,接口偶尔会有调整,请以你安装版本的官方文档为准。我第一次用的时候,某些参数和现在文档里写的就有差别。
环境变量配置也是基本功。用哪个模型的 API,就把对应的 API key 配到环境变量里:
export OPENAI_API_KEY="sk-xxxx" export ANTHROPIC_API_KEY="sk-ant-xxxx"如果不喜欢环境变量,也可以直接传参:
import litellm response = litellm.completion( model="claude-3-5-sonnet-20241022", messages=[{"role": "user", "content": "你好"}], api_key="sk-ant-xxxx" )但我不建议把 key 写死在代码里,尤其在团队协作时,很容易因为 key 混在代码提交里导致账号泄露。配置管理用环境变量或者专用的配置中心,是更稳的做法。
3.2 第一次调用:感受“一套代码调所有模型”
跑通最基础的调用很直白:
import litellm response = litellm.completion( model="gpt-4o", messages=[ {"role": "system", "content": "你是一位资深的Python开发者。"}, {"role": "user", "content": "写一个快速排序,只要代码,不要解释。"} ], temperature=0.7 ) print(response.choices[0].message.content)想要换模型,改model参数就够了。比如换成claude-3-5-sonnet-20241022,其它代码不用动。这个体验你只要试一次,就再也回不去了。
模型名有个需要留意的地方:model参数传什么,取决于 litellm 内置的模型列表,常见格式是“厂商前缀/模型名”,有的是纯模型名就能自动识别。保险的做法是传完整的模型名,比如bedrock/claude-3-5-sonnet和openai/gpt-4o。不确定就先跑一下,报错了再调整。
3.3 流式输出:让回复一个字一个字蹦出来
很多场景需要流式输出,比如聊天机器人,等完整回复会让人感觉卡顿。litellm 支持stream=True:
response = litellm.completion( model="gpt-4o", messages=[{"role": "user", "content": "讲个冷笑话"}], stream=True ) for chunk in response: delta = chunk.choices[0].delta if delta and delta.content: print(delta.content, end="", flush=True)注意流式输出时,返回的每个 chunk 结构跟非流式略有差异,内容是放在delta.content里的。我第一次用的时候按非流式的写法chunk.choices[0].message.content,结果一直拿不到内容,排查半天才反应过来。
3.4 模型路由:一个入口切多个模型,负载均衡和降级
litellm 还支持 Router 模式,适合生产环境。比如我有两个模型提供方,希望按优先级轮流调用,实现负载均衡;或者主模型挂了自动切到备用模型。Router 用起来也很方便:
from litellm import Router router = Router(model_list=[ { "model_name": "gpt-4o", "litellm_params": { "model": "openai/gpt-4o", "api_key": "sk-key-for-openai" } }, { "model_name": "gpt-4o", "litellm_params": { "model": "azure/gpt-4o", "api_key": "key-for-azure", "api_base": "https://your-resource.openai.azure.com/" } } ]) response = router.completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}] )核心思路是:Router 把同一个逻辑模型(这里是gpt-4o)映射到多个物理端点,请求会按策略分发。当第一个端点报错时,自动重试第二个,避免单点故障。
3.5 预算追踪:老板要的资源用量统计,交给工具生成
我之前习惯用 Excel 记 API 消耗,月底对账累得半死。后来发现 litellm 自带 token 统计和成本计算:
response = litellm.completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}] ) print(response.usage) # 输出示例: Usage(prompt_tokens=10, completion_tokens=5, total_tokens=15)它还提供了成本计算的工具函数:
from litellm import cost_per_token cost = cost_per_token(model="gpt-4o", prompt_tokens=10, completion_tokens=5) print(cost)这里有个细节:cost_per_token的返回结构会因模型不同而变化,有的返回两个数值(输入/输出),有的返回一个 dict。用之前最好在 REPL 里先跑一下看看输出,免得对接自己系统时踩结构不一致的坑。
4. 避坑指南:我踩过的几个典型案例
4.1 参数不兼容:越报错越要冷静
litellm 虽然做了参数转换,但某些厂商模型有特殊限制,对应参数没法强行映射。比如你想给某个不支持response_format的模型传 JSON mode 参数,它会直接报错,甚至可能静默忽略。遇到这种情况,我建议:先去掉这个参数试试,再决定是不是要用代理模型来处理格式化输出。litellm 还提供了一种lf.参数前缀,可以在调用时临时设置供应商的特殊字段,但我建议只在英文特殊文档确认过的情况下使用,否则容易出奇怪问题。
4.2 环境变量不生效
这个坑特别常见,我一度以为自己写的代码有问题:在 shell 里export OPENAI_API_KEY=...之后,从同一个终端启动 Python,正常能读到。但如果 IDE 里启动进程,而 IDE 没继承终端环境变量,就会报 401。解决方案很朴素:要么在启动脚本里统一export,要么在应用代码里从配置中心读取 key 再传给 litellm,别依赖操作系统环境变量。
4.3 超时设置:别让请求吊死
默认情况下,litellm 超时时间比较宽松。我方请求第三方模型的接口,如果对方服务不稳定,默认超时可能让我们的服务一直挂着。建议显式加上:
import litellm litellm.request_timeout = 30或者调用时:
response = litellm.completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], timeout=15 )对于高并发场景,我还建议加上重试策略。litellm 内置了一定的重试能力,但我们自己业务里,一般在调用失败或者网络超时时由上层业务再试一次,避免把重试逻辑压在一个工具上,导致高峰时请求翻倍。
4.4 返回字段结构差异
litellm 主流的返回结构是 OpenAI 风格的,但在少数兼容层模型下,字段可能略有变化。比如有的流式返回里,finish_reason不是出现在每个 chunk 里,而是最后一个 chunk 才给。用的时候建议先 print 一下原始返回,习惯性看结构再取值,不要想当然。
5. 进阶玩法:把 litellm 用出花
5.1 用代理模式接入内部网关
很多团队里有自己的模型网关,要求只暴露一个统一入口。litellm 可以启动代理服务器模式:
litellm --model openai/gpt-4o --port 8000起一个兼容 OpenAI 格式的本地服务,其他同事可以直接用 OpenAI SDK 指向http://localhost:8000,体验完全一致。这种方式非常适合团队内多人共享 key、统一做审计和限流。
5.2 批量跑评测
我们在评测模型效果时,经常要把一组 prompt 同时扔给不同模型对比。litellm 的batch_completion可以一次跑多个模型或多个 prompt,省不少时间:
from litellm import batch_completion responses = batch_completion( model_list=["gpt-4o", "claude-3-5-sonnet"], messages=[{"role": "user", "content": "写一句产品介绍文案"}] )需要注意它们之间可能存在的限流问题,如果遇到 429 错误,我一般拆成小批次,或者加个简单 sleep。批量评测的代码,最好设计成可断点续跑,避免中途挂了全丢。
5.3 函数调用(Tool Calling)
litellm 对函数调用的支持比较成熟。比如定义一个工具让模型选择,代码如下:
tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市天气", "parameters": { "type": "object", "properties": { "city": {"type": "string"} }, "required": ["city"] } } } ] response = litellm.completion( model="gpt-4o", messages=[{"role": "user", "content": "北京今天天气怎么样?"}], tools=tools )如果模型决定调用函数,返回里会出现tool_calls字段。这里有个常见坑:某些模型虽然声称支持 tool calling,但格式实现不同,litellm 可能需要在后台做特殊适配。遇到模型声称支持但返回不对时,先去看该模型在 litellm 的兼容性说明,别盲目认为是自己的代码问题。
5.4 自定义回调:把请求日志统一送到监控
litellm 支持回调函数,你可以在每次请求完成时记录信息,统一发到日志系统或监控告警:
import litellm def log_usage(kwargs, completion_response, start_time, end_time): print(f"Model: {kwargs.get('model')}, Time: {end_time - start_time}") litellm.success_callback = [log_usage]这个用法在排查线上问题、分析成本趋势时特别有用。我一般会把 model、prompt token、completion token、耗时、是否有重试记录全都打出来,方便事后复盘。
6. 团队协作时的落地建议
如果你跟我一样,不是一个人用,而是团队里多个人都在调大模型,建议从一开始就统一工具选型和调用约定。
搭一个简单的内部 Model Gateway 是比较推荐的路径:用 litellm 的代理模式部署一个中心服务,团队里其他人不用装 litellm,直接按 OpenAI 格式对接即可。这套架构的好处是:
- 业务代码完全不用感知模型厂商是哪个,只依赖约定好的虚拟模型名。
- 密钥集中管理,不会有人把 key 写进代码库。
- 成本可按服务维度拆分,谁用的多少一清二楚。
- 月底还是季度要换模型,改动只发生在网关层。
但也别一开始就把系统设计得太重。如果项目规模不大,先用单个服务加一个路由配置就好。等真的多业务线都接上了,再考虑引入专用 gateway 组件也不迟。
7. 性能与稳定性:几个容易被忽略的细节
7.1 连接复用
litellm 底层使用 HTTP 客户端,默认会有连接池。但如果你的并发量上来了,注意给底层配置合适的连接池大小。否则可能出现大量 TIME_WAIT 或者 socket 连接被丢弃的问题。我在高并发场景下会自己封装一层连接管理的逻辑,通过对 litellm 的调用做线程池限制,避免自己的服务被打崩。
7.2 缓存提示词或结果
很多场景下,相同输入可以缓存输出的结果,不必每次都花钱调模型。litellm 支持 Redis 缓存等功能,也可以自己做。如果需求只是简单缓存,可以按 prompt 的哈希当作 key,存内存或 Redis。注意缓存失效和脏数据问题,模型更新后缓存可能需要清空,不能想当然认为旧结果永远合理。
7.3 优雅降级
生产环境最怕的事:主用的模型 API 故障,整个服务不可用。解决方案是结合前面提到的 Router,配置备用模型。更精细一点的降级策略是:先按优先级路由到主模型,失败后自动切到备用模型。此时用户侧几乎是无感知的,最多响应慢一点。
8. 最后分享两个我在实际项目中的体会
第一,litellm 并不是万能的,遇到它没适配好的新模型,可能还是要自己动手扩展。它提供了重试、回调、代理等机制,但无法替我们解决所有“模型自身能力不足”的问题。好几百个模型每个都有细微差异,全做到 100% 完美不现实。
第二,我一直建议团队把基础封装看成基础设施,而不是某次需求的临时代码。一旦花时间把它搭好,后续所有接模型的需求都会变得很快。大家最该关注的,是业务本身的场景和效果,而不是反复折腾各家 API 的调用差异。
如果你现在正被多模型接入搞到头大,或者想给团队搭一套统一调用层,litellm 是目前我实测下来最省事的选择之一。先用最简单的调用方式跑通,再逐步加上路由、缓存、回调这些机制,你会越来越顺手。