上个月我帮一位朋友排查他项目里的多模型调用问题,他的代码里接了三家大模型服务,每家SDK的鉴权方式不同、参数命名不同、返回结构也不同,业务代码里写满了if provider == "..."之类的分支。这其实是很多做AI应用的人都会撞上的墙:模型越来越多,但每个厂商的接口风格都不一样,团队维护成本直线上升。我当时给他推荐了一个工具叫 LiteLLM,正好今天就把这套经验整理出来,聊聊它到底解决了什么问题、怎么用、以及哪些地方容易踩坑。
LiteLLM 是一个 Python 编写的统一大模型调用层,核心思路很简单:只学一套 API 调用方式,就能对接几十家大模型服务商,包括 OpenAI、Anthropic、Google Gemini、Azure OpenAI、各类国内厂商,甚至是你自己私有化部署的模型服务。它开源、一直在迭代、社区活跃,而且支持两种用法:一种是作为 SDK 直接在代码里调用,另一种是启动一个代理服务,让整个团队共用这个统一入口。这篇文章适合两类读者:一是想在项目里快速切换或对比多个大模型的开发者,二是需要把模型调用能力交给团队使用、同时要做预算和权限控制的负责人。我会从最基础的接入讲起,再深入代理模式的配置,最后分享一些实际使用中积累的坑和技巧。
1. 多模型API混用的割裂感,和我为什么最终选LiteLLM
先还原一下很多人都会遇到的场景。你一开始接的是 OpenAI,一切都很顺利。后来业务需要支持 Claude,或者你想在几个模型之间做效果对比,于是你又去读了 Anthropic 的文档。你会发现每家都有自己的 Request 格式、自己的鉴权 Header、自己的错误码体系,连 message 里的 role 叫法都略有差异。如果每个模型都直接调官方 SDK,项目里就会出现一层一层的适配代码,换来换去,还要小心哪家 SDK 升级后改了行为。
我当时也想过去写一个自己的适配层,把各家请求统一封装。但真正动手前认真想了几个问题:新模型发布后,适配层要跟着更新,谁来维护?各家的流式输出、工具调用、token 统计这些细节,要不要都处理一遍?团队里其他人接手这套适配层,学习成本又有多高?一个社区活跃、已经把这些脏活累活做好的开源项目,明显是更合理的起点。这也是我选 LiteLLM 的核心原因:它把“多模型适配”这件事从你自己的代码里剥离出去了。
LiteLLM 的统一思路很直观。它把 OpenAI 的请求格式当作“共同语言”,你只写一份请求,LiteLLM 负责翻译成目标厂商能理解的格式,再把厂商的响应翻译回统一的返回结构。这样一来,你的业务代码只需要维护一套调用逻辑,模型名的标识也遵循固定规则,程序会按照规则找到对应的厂商和模型。
我实际用过之后,觉得它的价值还可以拆成三层。第一层是给个人开发者用的,省去读各家 SDK 文档的时间,写代码时只用记忆一套规则。第二层是给团队用的,把鉴权 Key 集中管理起来,不再把各家厂商的 Key 散落在代码仓库里。第三层是给运维和成本管理用的,通过代理模式统一做限流、预算、日志和监控。很多人一开始只觉得它是“调模型的一个封装”,但真正把它架到团队层面之后,才会发现后面的两层价值更值钱。
2. SDK直连模式:最小改动接入,代码里只留一套调用逻辑
如果你和我一样,属于那种“先跑起来再说”的人,那直接从 SDK 模式入手是最合适的。这个模式不需要额外部署服务,只需要在你的 Python 项目里安装一个依赖包,然后开始写代码。
2.1 安装与最基础的完成调用
安装过程没什么特殊的,用常规包管理器直接装就行:
pip install litellm装完之后,最核心的调用函数是completion。看这个例子:
from litellm import completion response = completion( model="gpt-4o-mini", messages=[{"role": "user", "content": "用一句话介绍你自己"}] ) print(response.choices[0].message.content)这段代码读起来和直接调用 OpenAI 的 SDK 非常像,因为 LiteLLM 本来就把 OpenAI 的请求格式作为通用格式。你传进去的是一个messages列表,返回的response也保持类似的结构。
2.2 多厂商切换:关键在模型名的前缀规则
真正厉害的地方在于切换模型。如果你想换成 Claude,代码基本不用改,只需要换模型名:
response = completion( model="claude-3-5-sonnet-20240620", messages=[{"role": "user", "content": "用一句话介绍你自己"}] )想换 Gemini 的话,在模型名前面加上gemini/前缀:
response = completion( model="gemini/gemini-1.5-pro", messages=[{"role": "user", "content": "用一句话介绍你自己"}] )这里面的规则就是 LiteLLM 的核心约定:厂商前缀/模型名。例如azure/gpt-4o、bedrock/claude-3-5-sonnet、ollama/llama3.1。如果没有前缀,它默认你走的是 OpenAI。这个设计的意图很明显,就是让开发者能够通过一个字符串快速定位到底要请求哪家服务,也方便后续把模型名放在配置文件里动态切换。
鉴权方面,LiteLLM 的做法是读取环境变量。它会根据模型名自动判断你需要哪个厂商的 Key,然后去对应的环境变量里取。比如 OpenAI 的 Key 对应OPENAI_API_KEY,Anthropic 的 Key 对应ANTHROPIC_API_KEY,Gemini 的 Key 对应GEMINI_API_KEY。你甚至可以写一个.env文件,一次性把所有厂商的 Key 都配好,代码里完全不用管这些细节。
2.3 直连模式的实际边界
我试过在一个脚本里循环调用四五家模型做效果对比,整个代码逻辑非常清爽。但这不意味着直连模式在所有场景都够用。直连模式有几个明显的边界:第一,API Key 存放在客户端,如果项目是多人协作,Key 的管理和轮换会比较混乱。第二,没有统一的调用日志和成本统计,你想知道每个项目花了多少钱,得自己去各家后台查。第三,没有集中限流和重试策略,每个调用方都得自己处理异常。
这些边界什么时候会真正制约你?我个人的判断是:一旦你的项目有多个后端服务同时在调模型,或者团队里超过三个人需要共用模型能力,直连模式就开始显得吃力了。这时候该切换到代理模式。
3. 服务化部署:用Proxy模式搭一个团队共享的模型网关
代理模式是 LiteLLM 一个很重要的能力。简单说,你本地或服务器上起一个服务,这个服务对外暴露一个 OpenAI 兼容的接口,所有需要调用大模型的业务服务都往这个接口发请求,由它统一转发给真正的模型厂商。这样做的好处是:Key 收口到网关层、集中做限流和预算、方便加日志和监控,业务侧代码甚至不需要使用 LiteLLM SDK,只要会调 OpenAI 接口就能用。
3.1 一份最小可用的网关配置
代理模式依赖一个配置文件,通常是 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-3-5-sonnet litellm_params: model: anthropic/claude-3-5-sonnet-20240620 api_key: os.environ/ANTHROPIC_API_KEY启动服务很直接:
litellm --config config.yaml --port 4000启动后,同一个地址就变成了你要发的接口。业务服务只需要把请求发到http://localhost:4000/v1/chat/completions,请求体仍然是 OpenAI 的格式。LiteLLM 在网关层根据你传入的model参数去匹配配置,再转发到对应厂商。
这里有一个值得注意的点:model_name是你在代理层暴露给业务方的名字,你可以自由定义。也就是说,业务方根本不需要知道你背后到底接的是 OpenAI 还是 Anthropic,只要知道“我调gpt-4o-mini这个名字就行”。将来你想替换提供商,只要改网关配置,业务方无感知。
3.2 路由、缓冲与故障转移
代理模式的另一层价值在于可以提供路由策略。litellm_settings里可以配置路由行为,比如多个相同能力模型之间的加权轮询,或者主模型失败后的自动降级。
举一个实际配置片段:
litellm_settings: drop_params: true set_verbose: true router_settings: routing_strategy: "usage-based-routing" model_group_alias: gpt-4o-group: [gpt-4o-mini, claude-3-5-sonnet]注意这里的model_group_alias并不是官方标准字段名,我这里是表达一个思路:把多个模型编成一组,让网关自动决定具体走哪个。LiteLLM 对负载均衡和故障转移的支持在不同版本里演进得比较快,建议使用时直接查对应版本的配置文档,因为字段名和策略名更新得比较频繁。
我踩过一次坑是手动配置 fallback 时没有正确设置 timeout。网关把超时时间设置得太短,导致响应略慢的模型被频繁判为失败,大量请求被自动转到备用模型,两次调用之间的结果风格差异反而影响了业务效果。后来我调整了合理的超时和重试次数,并且专门用压测脚本验证过,路由策略稳定多了。
3.3 代理模式下的统一异常与日志
代理模式对团队研发最友好的点在于,它把异常也统一化了。各家厂商的错误码五花八门,网关层会把它们转换成相对统一的响应结构,业务侧只需要判断status_code和错误类型。同时,LiteLLM 支持通过回调把每次请求的耗时、token 用量、成功失败状态写进日志系统,常见的监控平台或者数据库都可以接入。网关层相当于一个记录仪,每一次模型调用的成本和质量都留痕。
4. 预算、令牌与可观测性:从“能调通”到“可管理”
很多人把 LiteLLM 部署起来之后,发现它不只是“一个转发代理”,更像是一个模型调用的管理面板。企业落地时真正关心的问题,往往不是“哪个模型效果最好”,而是“这个月模型调用花了多少钱”“各个部门/项目分别用了多少”“有没有人误配了 Key 导致费用暴涨”。LiteLLM 的这套管控逻辑,我认为值得单独拿出来讲。
4.1 虚拟密钥的设计逻辑
你可能已经注意到,如果代理网关保存了真实的厂商 Key,那么业务方一般不应该直接拿到这个 Key。LiteLLM 提供了一套虚拟密钥机制:管理员可以通过网关生成一个个独立的虚拟 Key 发给不同团队或应用。每个虚拟 Key 都可以设置自己的预算上限、速率限制和可用模型范围,后面它再做转发时,会自动代换成真实的厂商 Key。
这套设计和很多云平台的子账号体系很像,但它实现得更轻。它允许你按项目维度去控制成本上限,防止某个应用的异常流量把整个账号的预算吃光。
4.2 预算限制的几个关键字段
在网关配置里,你可以设定很多维度的限制。最常见的配置维度包括:
- 按虚拟 Key 设置的总额度上限,达到上限后直接拒绝请求。
- 按时间窗口设置的速率限制,例如每分钟最多多少次请求,防止突发流量。
- 按指定模型设置的白名单或黑名单,控制某个团队能用哪些模型。
我在实际项目中比较推荐的做法是:先给每个应用设置比预期略低一点的“熔断阈值”,跑一段时间看真实用量,再把阈值调到合理区间。宁可偶尔因为额度用完而拒绝请求,也不要让失控调用造成大额账单。
4.3 成本统计和令牌计算
LiteLLM 内置了模型定价表,所以它能在请求生命周期中记录 token 数并估算成本。你可以通过回调函数把每次调用的 token 数、模型名、花费金额写入数据库。我见过一些团队基于这些数据做了一份简单的成本看板,每天跑一遍汇总,哪个模型消耗最大、哪个应用调用量增长最快,一目了然。
这里要提醒一点:不同模型的 token 计算方式存在差异,LiteLLM 的估算值和厂商后台的账单不完全一致。它更适合做趋势分析和相对对比,而不是作为财务对账的绝对依据。如果你的场景要求精确到每一分钱,还是以厂商账单口径为准。
4.4 可观测性配置的参考写法
在网关的config.yaml里,可观测性的核心是回调配置。以把用量写入数据库为例,可以这样写:
from litellm.integrations.custom_logger import CustomLogger class MyLogger(CustomLogger): async def log_success_event(self, kwargs, response_obj, start_time, end_time): print("success", kwargs.get("model"), kwargs.get("usage", {}))然后在配置里把 logger 挂载到success_callback和failure_callback。这样网关每次转发成功或失败,你的代码都会收到事件,可以接上日志、消息推送或者数据库存储。长期跑下来,这些数据会变成你优化模型选择和成本控制的依据。
5. 实测后想提醒你的坑,以及我建议你先做的小实验
最后这部分,我想聊聊真正在项目里落地时容易遇到的问题。这些问题在文档里不一定会被单独拎出来强调,但实际运行时影响很大。
5.1 超时、重试与流式输出
第一个坑是默认超时。在默认情况下,LiteLLM 接口的超时设置偏保守,当你使用上下文很长的模型,或者模型本身响应较慢时,偶尔会出现超时报错。我的建议是显式配置timeout参数,时长根据业务接口的容忍度来定,同时配好重试次数。
response = completion( model="gpt-4o-mini", messages=[{"role": "user", "content": "写一篇长文"}], timeout=60, num_retries=3 )重试时还有一个细节:要区分哪些错误值得重试,哪些不值得。像网络抖动这种临时错误可以重试,但如果是鉴权错误或模型不存在这种确定性错误,重复重试只会浪费时间和额度。LiteLLM 内部对不同类型的错误有自己的处理策略,但你自己在捕获异常时也最好做一下分类。
第二个坑是流式输出。如果你调用时开启了stream=True,返回结果的处理方式和非流式完全不同。新手最容易犯的错误是,用同一个处理函数去消费两种模式的返回值。建议把流式和非流式分支拆开,各自维护一套解析逻辑,不要让它们互相干扰。
5.2 模型别名产生的混乱
在代理模式里,模型别名是一把双刃剑。它能让业务方无感知切换底层模型,但也容易让排障的人混淆。曾经有团队成员反馈“我调的是 A 模型,为什么返回结果看起来像是 B 模型?”查了半天才发现,网关配置里早就把 A 这个名称重定向到了另一个模型。这个设计本身是好用的,但一定做好配置注释和管理,避免“名称以为是 A,实际是 B”的认知偏差。
5.3 错误处理时不要一刀切
我在前面提到,LiteLLM 会把不同厂商的错误统一成相近的结构,但这不是说你可以把所有异常都当成同一类。不同厂商在触发限流、内容过滤、上下文超长时的状态码和错误原因都不一样,你在做业务兜底时,最好根据错误类型分别处理。
举个例子:当某个请求因为内容被过滤而失败时,简单重试大概率没有意义,而应该走一条有别于普通服务器错误的提示路径。在接入初期就把这些分支理清楚,比上线后被各种线上问题追着跑要舒服得多。
5.4 我建议你先跑的小实验
如果你现在正在评估 LiteLLM,我建议先做一个非常具体的实验:准备同一组问题,通过直连模式分别调用 OpenAI 和 Claude,记录每次的响应内容、耗时、token 消耗和出错情况。这个实验花不了多少时间,但能帮你直观感受到它的多模型切换体验。第二步,再搭一个小规模代理,让项目里的另一个服务通过 OpenAI SDK 调用代理,测试一下它在统一接口、日志记录上的表现。这两个实验做完,你基本就能判断 LiteLLM 是否适合你的项目。
我个人现在维护项目时的习惯是,把所有环境的模型名、Key 策略、超时参数都写成配置项,不散落在代码里。LiteLLM 的价值恰恰就在于此——它不只是帮你把多个 API 的调用方式“拧成一股绳”,更逼着你把调用代码和具体厂商解耦。一开始你可能只是想省点事,但用久了会发现,自己在选型、切换、成本管理上的余地变大了很多。这就是我认为这个工具最值得投入时间去掌握的地方。