1. 认识LiteLLM:打通大模型API的那个“转换头”
先说我自己的处境。有段时间我手头好几个项目,分别接了不同家的大模型接口:一个用的OpenAI兼容接口,一个用某云厂商的托管模型,还有一个需要切换国内外的不同模型做效果对比。刚开始还好,每个项目各写各的客户端,但等到要统一做日志、统计成本、限制每个用户调用量的时候,麻烦就来了——各家的返回格式不一样,计费口径也不一样,错误码更是五花八门。改一个功能,三个项目都要动一遍,那段时间我最大的感受就是:“我就想调个模型,怎么还要当API适配工程师?”
后来接触到LiteLLM,这个问题才算真正解决。它的核心作用,说白了就是一个大模型API的“转换头”:用同一套代码风格、同一种数据格式,去调用市面上主流的大模型服务,包括各云厂商的托管模型、开源自托管模型、以及各种兼容OpenAI格式的平台。你不需要再为每一家单独写一套HTTP请求封装,也不需要记忆每家不同的鉴权方式。
这篇文章主要写给谁呢?如果你正在做这类事情,应该会有共鸣:
- 在你的应用里接了大模型API,但现在被绑定在某一家上,想随时换到别家。
- 需要统一管理多个模型的服务,比如团队内部同时用好几家的模型,想用一个入口搞定。
- 想给项目的API调用做预算管控、用量统计、限流,但又不想自己从零开发一套网关。
- 打算搭一个本地代理,把所有模型请求转发到统一地址,让前端和业务后端都不感知上游变化。
LiteLLM提供了两种核心使用方式:一种是作为Python SDK直接在代码里调用,另一种是起一个代理服务,对外暴露OpenAI兼容的接口,业务端只需要把base_url指向它就能用。下面我会把这两种方式的原理、配置和踩过的坑都展开讲。
2. LiteLLM的核心设计思路
2.1 OpenAI兼容格式为什么成了“最大公约数”
很多人第一次用LiteLLM,最直观的感受是:调用格式和openai那个Python包几乎一模一样。这其实是LiteLLM有意为之。
# 直接使用openai SDK时: from openai import OpenAI client = OpenAI(api_key="sk-xxx") resp = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], ) # 使用LiteLLM时: from litellm import completion resp = completion( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], )两者的传参结构高度相似,messages、temperature、max_tokens这些核心字段几乎可以无缝迁移。为什么LiteLLM要把OpenAI的格式作为基准?原因很简单:OpenAI的chat completion格式是市面上普及度最高的,绝大多数模型厂商——包括那些完全不相关的平台——都主动兼容了它。即使某家原始接口不是这个格式,它们也提供了OpenAI兼容的端点。与其自己发明一套“通用标准”,不如直接站在事实标准上,这是最省力也最稳妥的做法。
在内部实现上,LiteLLM会把你的OpenAI风格请求翻译成各家真实需要的格式。比如Anthropic的模型用的是messages结构加system字段分开传,它会在底层帮你重组;Google的模型有自己的一套参数,它也会做映射。对你来说,一次学习,处处使用,这就是它存在的最大价值。
2.2 SDK模式与代理模式:什么时候该用哪种
LiteLLM给了两条路,很多人刚接触时会纠结到底该用哪种。我建议按使用场景来分:
SDK模式适合“你在写代码,且代码完全由你掌控”的场景。比如你写一个数据清洗脚本,需要调用模型做实体识别,直接from litellm import completion,然后把各家的key配置好,代码里就可以随时切换模型。优点是轻量、灵活,不需要额外启动任何服务,写进定时任务里就行。缺点是如果多个服务都要调用,每个服务都得自己配一遍key和模型映射,管控能力会比较弱。
代理模式适合“你有一个统一的服务入口”的场景。你在服务器上起一个LiteLLM代理进程,它对外暴露/v1/chat/completions这样的OpenAI兼容端点,业务服务只需要把openai客户端的base_url改成代理地址,其他代码几乎不用动。团队里不同的项目、不同的语言,都可以通过这个入口访问模型。代理模式真正解决的是“接入层统一”的问题,key统一管理、预算统一管控、日志统一采集,全部在一个点上完成。
我的实际经验是:个人脚本和实验代码用SDK模式就够了;只要涉及多业务接入、有成本管控需求,直接上代理模式,哪怕最开始只有一两个服务,也值得。否则到后面服务多了再迁移,付出的成本要高得多。
2.3 模型名映射和路由:灵活性的来源
LiteLLM有个很核心的抽象概念:你可以给模型起“别名”,然后把这个别名映射到某一家的真实模型上。这理解起来就像DNS:业务代码里永远只写model="gpt-4o",但这个gpt-4o背后到底指向哪家,由配置决定。
# config.yaml中配置模型映射 model_list: - model_name: gpt-4o litellm_params: model: azure/gpt-4o api_key: os.environ/AZURE_API_KEY api_base: os.environ/AZURE_API_BASE - model_name: claude-profile litellm_params: model: anthropic/claude-3-5-sonnet-20240620 api_key: os.environ/ANTHROPIC_API_KEY这样做的好处我想多说几句:第一,业务代码和实际模型解耦了。你今天用A家,明天想换成B家,只要改配置,业务端一个字符都不用动。第二,你可以为同一个别名配置多个真实模型,让LiteLLM做负载均衡或失败切换。比如gpt-4o同时在Azure和另一家都有部署,一个出问题,另一个自动顶上,对业务端完全透明。第三,这对测试很有用——同一个模型名,在预发环境指向便宜的测试模型,在生产环境指向正式模型,配置分开就行。
3. 从零开始落地LiteLLM:环境准备与第一个请求
3.1 安装与最小可用配置
LiteLLM的安装非常简单,pip一把梭:
pip install "litellm[proxy]"这里建议直接装带proxy扩展的版本,因为即使你现在只想用SDK,后面大概率也会用到代理功能,省得再补一次安装。装完可以验证一下版本:
python -c "import litellm; print(litellm.__version__)"然后你需要准备至少一家模型服务的API Key。以OpenAI兼容接口为例,配置环境变量:
export OPENAI_API_KEY="sk-你的key"如果你是用的Azure OpenAI或者其他家的服务,LiteLLM支持的环境变量名各有不同,可以在官方文档里查对应的前缀。比如Azure的key通常配置为AZURE_API_KEY,AZURE_API_BASE,AZURE_API_VERSION。这些变量名在设计上非常直白,基本一看就懂。
3.2 发送第一个请求:三行代码跑通
代码非常简单,但在跑之前我想提醒一个很多新手会忽略的点:模型名必须写LiteLLM能识别的完整形式。
from litellm import completion response = completion( model="gpt-4o", # 注意,这里是LiteLLM格式的模型名 messages=[ {"role": "system", "content": "你是一个简洁的助手。"}, {"role": "user", "content": "用一句话介绍自己。"} ], temperature=0.7, ) print(response.choices[0].message.content)如果你发现返回的内容是错误信息,先别急,大概率是模型名没有加对应的“提供商前缀”。比如直接写gpt-4o默认走OpenAI;如果用Azure,需要写azure/gpt-4o;如果用Anthropic的模型,要写anthropic/claude-3-5-sonnet-20240620。这种providers/model的写法,是LiteLLM区分不同上游的关键,刚开始很容易漏。
LiteLLM在底层会自动处理重试、超时、错误映射这些事,所以即使第一次调用失败,也不用急着改代码,先看报错信息里有没有给出重试的建议。它默认对一些临时错误会尝试重试,这对不稳定的网络环境很友好。
3.3 部署一个本地代理服务
代理模式是LiteLLM真正体现价值的地方。用一个配置文件启动,就能把一个完整的大模型网关跑起来。
先写配置文件,假设叫config.yaml:
model_list: - model_name: gpt-4o litellm_params: model: gpt-4o api_key: os.environ/OPENAI_API_KEY general_settings: master_key: sk-my-master-key # 访问代理的管理密钥然后启动:
litellm --config config.yaml --port 8000看到启动日志里出现“Server started”之类的内容后,你就可以在任意一个支持OpenAI SDK的项目里这样调用了:
from openai import OpenAI client = OpenAI( base_url="http://localhost:8000/v1", api_key="sk-my-master-key", # 这个key是代理的key,不是上游的key ) resp = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], ) print(resp.choices[0].message.content)注意到一个关键点:业务端完全用的是openai SDK,只是换掉了base_url。这就是我刚才说的“接入层统一”的威力——你现有代码里但凡写的是openai SDK,只需要把client的地址换掉,就能通过LiteLLM代理访问所有配置好的模型,连改模型名都只需要改配置。
4. 进阶功能实战:多模型管理、预算与监控
4.1 一个代理接入多厂商模型
当我们把多个厂商的模型写进同一个config.yaml,才算真正体会到LiteLLM的便利。假设我有三家:OpenAI、Azure托管的模型、还有Anthropic的模型:
model_list: - model_name: gpt-4o litellm_params: model: gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: azure-gpt-4o litellm_params: model: azure/gpt-4o api_key: os.environ/AZURE_API_KEY api_base: os.environ/AZURE_API_BASE api_version: "2024-02-01" - model_name: claude-sonnet litellm_params: model: anthropic/claude-3-5-sonnet-20240620 api_key: os.environ/ANTHROPIC_API_KEY现在业务端只需要通过model参数选择对应的模型名即可。我可以把不同模型暴露给不同的业务团队,每个团队只看到自己需要的那几个模型名,而不需要知道上游是哪个厂商。这种解耦在稍微大一点的团队里极其实用——权限控制、模型版本管理、灰度切换,全都通过配置来完成。
有一个细节值得提一下:配置里的model_name是自己起的逻辑名,litellm_params.model才是真正的上游模型名。这两者的关系容易搞混,我在配置时一般会把逻辑名起得贴近业务含义,比如内部服务专用的小模型叫lite-fast,而不是直接叫某个厂商的产品名,这样以后换上游也不会影响业务代码。
4.2 用量限制与预算告警配置
模型API的成本是很多人心里的痛,尤其当多个团队共用同一个入口时,月底账单一出,往往没人承认自己调得多。LiteLLM的预算控制和用量追踪在这时候特别有用。
它支持设置全局预算、每个API Key的预算、每个模型的预算。以每个Key为例,你可以在代理启动后,通过管理接口创建虚拟Key,给这个Key设置额度:
curl -X POST "http://localhost:8000/key/generate" \ -H "Authorization: Bearer sk-my-master-key" \ -H "Content-Type: application/json" \ -d '{ "models": ["gpt-4o"], "max_budget": 10.0, "budget_duration": "30d" }'这个请求会返回一个api_key,业务端用这个Key访问代理,30天内的花费超过10美元就会触发限制。你还可以设置soft_budget,在接近上限时告警,而不是直接掐断请求。这种“先告警后断流”的设计在实际使用中比一刀切合理得多。
对于成本敏感的场景,我强烈建议开启每个请求的日志记录,这样你能看到每个Key、每个模型、每次请求的token消耗和估算费用。后续做成本分摊、异常排查的时候,这些数据都是最直接的证据。
4.3 负载均衡与容灾切换配置
如果同一个逻辑模型配置了多个上游,LiteLLM可以承担负载均衡和容灾的角色。比如我把gpt-4o同时配置在两家不同的服务上:
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_API_KEY api_base: os.environ/AZURE_API_BASE配置里出现了两个相同model_name的条目,LiteLLM会自动处理路由。默认策略是每次请求随机挑一个健康的端点,如果某个端点返回错误或超时,会自动尝试下一个。你还可以手动指定路由策略,比如usage-based-routing(根据成本和速度打分)或者latency-based-routing(优先选延迟低的)。
我在实际项目中体验到的最明显收益是:上游平台偶尔的限流或短时故障,对业务端完全无感。以前遇到接口报错只能干等着,现在代理直接切换到了备用端点。需要提醒的是,两个端点最好配置不同厂商的key,如果都依赖同一个上游,那容灾的意义就打了折扣。
5. 常见问题与排查实录
5.1 模型名映射错误:最常见的翻车现场
我在刚开始用LiteLLM时踩过的最多的坑,就是模型名没有加前缀。举个具体的例子:
from litellm import completion # 错误示范:直接写模型名,默认找OpenAI response = completion( model="claude-3-5-sonnet-20240620", messages=[{"role": "user", "content": "hi"}], )这段代码会报错,因为LiteLLM把claude-3-5-sonnet-20240620当成OpenAI的模型去找,自然找不到。正确写法是anthropic/claude-3-5-sonnet-20240620。
排查思路很简单:确认上游厂商后,在模型名前面加上对应的provider前缀。如果不确定某个模型在LiteLLM里的准确写法,有两个办法:一是查官方支持列表,二是直接在代码里把想用的模型名传进去,看它抛出的错误信息——LiteLLM的错误提示一般会列出符合前缀的模型选项,按提示改就行。
5.2 超时与重试参数调优
默认配置对大多数场景是够用的,但有些模型响应特别慢,或者你在做流式输出,就必须自己调整超时参数。我遇到过好几次,长文本生成任务跑到一半连接断开,没有重试机制的话整个请求就废了。
from litellm import completion response = completion( model="gpt-4o", messages=[{"role": "user", "content": "写一篇长文章"}], timeout=120, # 单位:秒 num_retries=3, # 失败重试次数 retry_strategy="exponential_backoff_retry", )关于重试策略,我个人的建议是:对临时性错误(限流、超时、5xx)放心重试;对明确性的参数错误(如400)不要重试,因为重试多少次结果都一样,只会浪费成本。这个判断逻辑在LiteLLM中有默认实现,它也允许你自定义回调函数来决定是否重试。值得花一点时间研究,尤其在对接不稳定的上游时。
5.3 流式响应与工具调用的坑
流式输出是聊天类应用的基础能力,LiteLLM对stream=True支持得挺好,但有一个体验上的坑:有些上游模型不支持流式,或者流式格式差异很大。如果你在代理模式里统一开关流式,确实有可能会遇到某些模型直接报错。
解决办法是:检查该模型是否明确支持流式;如果上游不支持,可以考虑在代理配置里对该模型禁用流式,或者由代理层做缓冲,等完整结果返回后再发给客户端。但缓冲会带来首字延迟变长,交互体验会打折,所以最好是选本身就支持流式的模型来对外提供服务。
工具调用(Function Calling)是另一个容易出现兼容问题的点。各家模型对工具的描述格式细节有差异,LiteLLM虽然在尽力把OpenAI格式转换成各家格式,但工具的嵌套结构一复杂,偶尔还是会出幺蛾子。我的经验是:尽量保持工具参数结构简单,少用深层JSON嵌套;配置好之后一定要先用多轮工具调用的测试用例跑一遍,不要等到业务上线了才发现某个工具的参数对应不上。
5.4 预算告警与实际账单对不上
预算统计模块是LiteLLM的一大亮点,但它算出来的费用终究是根据token用量乘以单价估算的,不是上游给你的最终账单。不同厂商的计费规则差异很大,比如某些模型输入和输出单价不同、有的还按缓存命中打折,估算值和真实账单有一定误差是正常的。
如果发现偏差很大,先检查你配置的模型名是否在LiteLLM内置的模型成本表中。很多自定义模型或较新的模型,成本表可能没有及时更新。这时候可以通过配置里的cost_per_token字段手动维护成本参数。光这一条,就能解决大多数“预算告警频繁误报”的问题。
5.5 代理模式下的“通配Key”
最后补充一个代理模式的安全细节。默认情况下,如果配置了master_key,业务端必须带上这个key才能访问代理。有的团队为了方便内部调试,会把master_key直接写在前端代码里,这是非常危险的,因为一旦前端源码泄露,别人就拿到了你整个代理的管理权限。正确做法是给每个业务方单独申请虚拟Key,并为每个Key限定可访问的模型范围和额度上限。这样即使某个Key泄露了,影响面也仅仅局限于一个业务线,而不是整个模型网关。
6. 写在最后:几个让我收益很大的细节
如果用一句话总结LiteLLM的价值,我会说:它把“接入多家大模型”这件事从两周的重复劳动变成了一下午的配置工作。但真正让我长期用下去的理由,反而不是它能省多少代码,而是它在“模型治理”上的用心。
举个例子:我在一个项目里把正式环境和测试环境的API Key分开,用的就是LiteLLM的多Key管理,测试Key额度只有几块钱,偶尔有同事拿测试Key跑大数据量的任务,预算上限自动把它拦住了。而业务端完全感知不到这些管控逻辑的存在,他们要做的只是把base_url指向代理,把一个Key填进环境变量。这种“底下复杂,上面简单”的架构设计,正是我理想中接入层该有的样子。
还有一个小技巧:如果你和我一样经常在各家模型之间做效果对比,不妨把不同的模型都配置到代理里,然后写一个简单的轮询脚本,在同一个业务问题集上逐一调用,记录各自的延迟和输出结果。用LiteLLM做这件事非常顺手——上游切换只需要改model参数,成本数据也自动记下来了,横向对比的效率比之前高了一个量级。
最后再说一个容易忽略的点:LiteLLM的SDK和代理都在持续更新,新模型发布后,建议及时升级版本,因为内置的成本表、模型名映射和各家厂商的新特性适配都跟着版本走。我吃过一次亏,在某个旧版本上,新发布的某个模型已经支持了,但代理配置里怎么都调不通,排查了半天,最后发现是版本太旧。从那以后,我养成了每隔一两周就看一眼更新日志的习惯。
如果你手头正有“多个模型来回切换”的烦恼,不妨先从一个小场景试起:把两三个常用模型挂到本地代理下,业务代码切过去,感受一下统一入口带来的变化。大概率你用过之后就回不去了。