上个月我在给一个内部工具接大模型接口的时候,差点被一堆API文档搞崩溃。同一个功能,换个模型服务商,就要重新写一遍请求格式、鉴权逻辑和错误处理,再加上各家限流策略还不一样,联调一次少说半天。后来我把所有接入层统一到了一个库后面,代码从几百行缩到了几十行,换模型基本只改一行配置。这个库就是LiteLLM。
这篇文章我不打算照官方文档的顺序讲,而是从我实际接入和使用的时间线出发,把核心原理、上手流程、生产环境配置,以及容易让人摔跟头的地方,一次说清楚。如果你正在做多模型调度、想给团队搭一个统一的模型网关,或者只是希望以后换模型不用大改代码,这篇文章应该能帮你省下不少时间。
先说结论:LiteLLM本质上是一个"LLM万能转接头"——它把不同提供商的API全部翻译成同一套调用格式,再用配置文件统一管理模型名、密钥和路由策略。理解这一点,后面所有用法都能顺下来。
1. 为什么我需要一个"LLM万能转接头"
1.1 多模型接入的真实场景
很多人一开始只接一个模型,觉得统一封装是过度设计。但只要你做过下面任意一件事,痛点马上就会出现。
第一类是模型对比评测。我在做某类文本分类任务时,需要同时跑三个模型看效果——闭源厂商A的新模型、闭源厂商B的老牌模型、还有本地部署的开源模型。如果各自写一套调用代码,光维护请求格式差异就够头疼,更别提评测脚本要同时兼容三套返回结构。
第二类是用户可选的模型切换。产品里给用户提供"智能模式"和"极速模式",背后对应不同质量和延迟的模型。如果代码里到处硬编码model参数,每次切换都要发版。
第三类是故障降级。主模型偶尔抽风,需要自动切到备用模型。没有统一封装时,这种故障转移逻辑基本靠手写,写出来还不一定可靠。
我身边不少朋友最初也觉得"反正就一个模型",结果半年后因为成本或者效果原因换供应商时,改代码改到怀疑人生。LiteLLM就是在这个场景下切入的。
1.2 各家API的差异到底有多大
说句实话,我刚开始也觉得"都是发HTTP请求,能差到哪去"。真上手才发现差异不是一点点,我整理了一个粗略对比。
| 差异点 | 厂商A | 厂商B | 本地开源服务(兼容格式) |
|---|---|---|---|
| 请求体结构 | 统一消息列表,参数较全 | 消息列表+独立系统字段 | 大多兼容标准格式,但参数实现不一 |
| 鉴权方式 | Header头携带密钥 | 独立请求头字段 | 通常同左,本地可随意 |
| 参数命名 | 输出长度/随机性等参数名不同 | 有自己的叫法 | 有的支持,有的忽略 |
| 返回结构 | 嵌套较深 | 结构略有差异 | 基本一致但有微量元素不同 |
| 错误码语义 | 限流、超限区分明确 | 限流返回码不一致 | 自行实现,千奇百怪 |
| 计费维度 | 按输入输出分别计价 | 按代币整体计价 | 通常不计费 |
这意味着,你的业务代码每适配一家,就要处理一遍请求构造、响应解析、异常映射。更麻烦的是,流式输出(streaming)的格式各家也不一样,有的给事件类型区分,有的只能靠字段判断,真是折磨。
1.3 LiteLLM的解决思路
LiteLLM的切入方式很朴素:一套统一的请求格式进来,它负责翻译成各家API的真实格式,再把各家返回翻译成统一结构给你。你只需要关心模型名、消息列表和少量参数。就像去不同国家旅行,不用带一堆转换插头,一个万能转接头就够。
它同时提供了两种使用形态:SDK模式,直接用Python函数调;代理模式,起一个轻量HTTP服务,让任何语言的客户端通过一个统一网关访问。前者适合内部脚本和单机应用,后者适合团队共享和微服务架构。
明白这个定位,后面所有配置和代码都顺理成章。
2. 核心原理拆解:统一接口、模型映射与密钥隔离
2.1 为什么一切向"兼容格式"看齐
LiteLLM内部把"某种广泛兼容的API格式"作为基准格式。你调用时传入的messages结构、model字段、temperature这类参数,都是按这套基准格式写的;它内部再把这个格式翻译成厂商A或厂商B的请求体。
这带来一个很实际的好处:你用惯了某个SDK的写法,在LiteLLM里几乎零成本迁移。消息列表的写法、返回对象的字段定位方式,跟那个SDK基本一致。第一次用的时候,我甚至没翻文档,凭直觉就写对了。
要注意的是,这里说的"兼容格式"是指请求协议层面的东西,不是某个具体公司的商业服务。它是一种事实上的API规范,很多开源模型部署框架和本地服务都直接实现了这套接口。所以LiteLLM的model前缀里有一个很常见的做法:给任何实现了兼容格式的服务加个前缀,配上自定义地址就能直接调用。
2.2 模型名称映射机制
这是LiteLLM最有价值的设计之一。它把"模型名"分成了两层。
一层是逻辑名,也就是你自己定义的、面向业务的名称,比如main-chat、fast-answer。另一层是真实上游模型,比如某厂商的旗舰模型、某厂商的轻量模型、或者本地部署的某个开源模型。
配置文件里要做的,就是把这些逻辑名映射到真实模型和对应的访问凭据。举个例子:
model_list: - model_name: main-chat litellm_params: model: providerA/model-a-pro api_key: os.environ/PROVIDER_A_API_KEY - model_name: fast-answer litellm_params: model: providerB/model-b-turbo api_key: os.environ/PROVIDER_B_API_KEY代码里你永远只跟main-chat打交道:
import litellm response = litellm.completion( model="main-chat", messages=[{"role": "user", "content": "你好"}], )想换模型?改配置,不改代码。想灰度切换?把main-chat指向新的上游模型,跑一天观察效果,不满意再切回来。这种"逻辑名与实现解耦"的思路,做过后端服务的人应该秒懂——跟数据库连接串、缓存地址不写在业务代码里一个道理。
2.3 密钥隔离与安全边界
用代理模式时,真实密钥只保存在运行LiteLLM的服务端环境变量或配置里,客户端拿到的只是一个"访问令牌"。这样即使前端页面被逆向,泄露的也不是真实服务商密钥,风险边界被大大压缩。
我在团队内部搭代理时,明确定了一条规则:业务开发人员不需要也不应该知道任何上游密钥。他们只需要知道网关地址和分配好的令牌,就能完成所有模型调用。密钥由负责基础设施的人统一管理、统一轮换,出问题能快速定位到人。
这也是我比较喜欢LiteLLM的一个点:它把安全边界的标准做法直接做进了工具里,而不是留给使用者自己发挥。
2.4 同步与异步双实现
LiteLLM同时提供了同步的completion和异步的acompletion。异步版本在并发场景下特别重要。
我在写批量评测脚本时,一开始用的同步调用,500条文本跑了一个多小时。改成异步并发后,同样的量十几分钟就跑完。异步版本处理并发请求时,连接复用、连接池管理这些底层细节都帮你处理好了,你只要写await就行。
更骚的是配合Router(后面第五章细说),异步请求还能自动做负载均衡和故障转移,并发高的时候不会因为某个上游抖动而大面积失败。这在线上的价值,比省那几分钟时间大得多。
3. 十分钟跑通:安装与第一个完整调用
3.1 环境要求与安装
LiteLLM是纯Python库,要求Python 3.8以上。安装很简单:
pip install litellm如果你要起代理服务,需要装带proxy扩展的版本:
pip install 'litellm[proxy]'装完之后设置环境变量,把上游服务的密钥配上:
export PROVIDER_A_API_KEY=你的密钥我建议密钥一律走环境变量,不要写死在代码或配置文件里。特别是配置文件如果会提交到仓库,写死密钥等于裸奔。
3.2 第一段完整的调用代码
装好之后,最简调用长这样:
import os import litellm response = litellm.completion( model="providerA/model-a-pro", messages=[ {"role": "system", "content": "你是一个技术助手。"}, {"role": "user", "content": "用三句话解释什么是负载均衡。"}, ], api_key=os.environ.get("PROVIDER_A_API_KEY"), temperature=0.3, ) print(response.choices[0].message.content)注意两点:第一,model参数用的是"供应商前缀/模型名"的格式,前缀告诉LiteLLM该走哪套请求协议;第二,api_key可以直接显式传,也可以不传然后靠环境变量匹配。如果你同时接入了多个供应商,最好显式传,避免LiteLLM猜错环境变量。
3.3 随时切换模型
等价的模型切换,只需要换model这一个字符串。比如从厂商A换到厂商B:
response = litellm.completion( model="providerB/model-b-sonnet", messages=same_messages, api_key=os.environ.get("PROVIDER_B_API_KEY"), temperature=0.3, )返回结构完全一致。业务代码零改动,这就是统一接口的价值。
如果你要接本地部署的开源模型,通常它已经暴露了兼容格式的HTTP接口,那么只需要同时指定api_base:
response = litellm.completion( model="openai-compatible/my-local-model-7b", messages=same_messages, api_key="EMPTY", api_base="http://127.0.0.1:8000/v1", )这里的api_base指向本地服务的地址。我一开始不太理解为什么还要带openai-compatible前缀,后来明白了:LiteLLM要靠这个前缀知道,对上游应该用什么样的请求协议去沟通。本地服务一般兼容标准格式,所以用这个前缀最稳妥。
3.4 流式输出与异步调用
对话类产品基本都要流式输出。LiteLLM的流式用法跟非流式几乎一样:
response = litellm.completion( model="providerA/model-a-pro", messages=same_messages, stream=True, ) for chunk in response: delta = chunk.choices[0].delta.content if delta: print(delta, end="", flush=True)流式返回的是迭代器,逐块吐出增量内容。这里有个容易踩的小坑:不同模型的流式分块粒度不同,有的按字吐,有的按句吐,不要依赖固定的块数量做业务逻辑。
异步版本更简单:
import asyncio import litellm async def main(): response = await litellm.acompletion( model="providerA/model-a-pro", messages=same_messages, ) print(response.choices[0].message.content) asyncio.run(main())如果你要并发处理大量请求,强烈建议用异步。我用下来感觉性能提升非常明显,尤其是在短文本生成场景,IO等待基本都被并发吃掉了。
3.5 参数透传
temperature、max_tokens、top_p这类通用参数,按直觉传就行。厂商特有参数LiteLLM也支持透传,只是命名空间不同——这个我建议用到时再去翻对应文档,不要硬记。
如果你希望LiteLLM忽略上游不支持的参数,可以在全局设置里打开"自动丢弃参数"开关。这能避免不少"模型报错但看起来莫名其妙"的情况,后面第六章我会专门讲。
4. 代理服务器模式:把模型接入变成一个网关
4.1 什么场景应该上代理
SDK模式适合单机脚本和简单应用。但一旦出现下面几种情况,就该考虑代理模式了:
- 团队多人、多项目共用同一批模型,不想每个项目各自配一遍密钥
- 前端或非Python服务也需要调模型,统一HTTP接口方便
- 希望集中管控密钥、价格、限流策略,不想散落在各个业务代码里
代理模式本质上就是把LiteLLM跑成一个HTTP服务。所有客户端只需要往这个服务发请求,服务自己去做供应商翻译、密钥管理、路由转发。
4.2 极简配置文件与启动
先写一个最简配置config.yaml:
model_list: - model_name: main-chat litellm_params: model: providerA/model-a-pro api_key: os.environ/PROVIDER_A_API_KEY litellm_settings: drop_params: true然后启动:
litellm --config config.yaml --port 8000看到类似Uvicorn running on http://0.0.0.0:8000的日志,服务就起来了。注意--port指定端口,生产环境建议放内网加网关鉴权,不要直接裸奔在公网。
4.3 客户端怎么接入
客户端不需要装LiteLLM,只需要一个兼容标准格式的HTTP客户端。以常见的SDK为例,把base_url指向代理地址就行:
from openai import OpenAI client = OpenAI( api_key="sk-any-string", base_url="http://127.0.0.1:8000/v1", ) resp = client.chat.completions.create( model="main-chat", messages=[{"role": "user", "content": "你好"}], ) print(resp.choices[0].message.content)这里的api_key只要随便填一个非空字符串,因为代理服务会用自己的真实密钥去上游鉴权。model填的是逻辑名main-chat,由代理在服务端完成真实映射。
这个模式太适合给团队用了。前端同学、测试同学、甚至产品同学,只要拿到网关地址,就能用他们最熟悉的HTTP工具来调模型。后端同学只需要维护好配置和网关的稳定性。
4.4 管理端点与虚拟密钥
代理模式还提供了一套管理接口,我比较常用的是其中关于访问令牌的部分。你可以为每个项目、每个成员生成独立的虚拟密钥,单独控制它能调哪些模型、月度预算上限是多少。用虚拟密钥的好处是:
- 某个密钥泄露了,直接吊销那一个,不影响整体
- 不同项目用不同密钥,方便成本归因
- 可以给密钥单独设置预算,防止有人把月成本跑穿
这套机制对做内部平台的人来说,属于"用了就回不去"的功能。
5. 生产环境绕不开的三件事:负载均衡、超时重试与预算控制
5.1 给同一个模型配置多个上游
高可用不只是一句口号。我在实际生产里遇到过上游服务商限流导致线上接口大面积超时的情况。解决方案就是给同一个逻辑名配置多个上游,让LiteLLM自动做负载均衡和故障转移。
配置方式很简单,还是改配置文件:
model_list: - model_name: main-chat litellm_params: model: providerA/model-a-pro api_key: os.environ/PROVIDER_A_API_KEY - model_name: main-chat litellm_params: model: providerB/model-b-sonnet api_key: os.environ/PROVIDER_B_API_KEY逻辑名都是main-chat,但后面跟了两个供应商。代码里继续用main-chat,LiteLLM会根据负载策略决定这次请求发给谁;如果某个上游连续失败,它会自动暂时把流量切到另一个。
在代码里手动创建Router也是一样的思路:
from litellm import Router router = Router(model_list=[ { "model_name": "main-chat", "litellm_params": { "model": "providerA/model-a-pro", "api_key": os.environ["PROVIDER_A_API_KEY"], }, }, { "model_name": "main-chat", "litellm_params": { "model": "providerB/model-b-sonnet", "api_key": os.environ["PROVIDER_B_API_KEY"], }, }, ]) response = router.completion( model="main-chat", messages=[{"role": "user", "content": "你好"}], )Router在异步场景下优势更明显,高并发时请求会在多个上游之间均匀分布。我做过一个简单的压测,两个上游的路由模式比单上游吞吐量翻了一倍多。
5.2 超时与重试参数
我发现很多人在生产环境出问题,都是因为没搞清楚LiteLLM的超时和重试机制。先列一下最常用的参数:
| 参数 | 作用 | 我的建议值 |
|---|---|---|
timeout | 单次请求的最长等待时间,单位秒 | 30~60 |
num_retries | 请求失败后的重试次数 | 2~3 |
allowed_fails | Router判定上游故障的连续失败次数 | 3 |
cooldown | 上游故障后的冷却时间,单位秒 | 60 |
retry_after | 重试前等待的基础时间 | 0.5~2 |
先说timeout。这个参数控制的是"单次HTTP请求的等待上限",不是整个链路的耗时上限。大模型请求一般不是计算密集,而是"生成输出"所需时间。长文生成场景,30秒并不夸张。实测下来,短对话设30秒足够,长文档生成建议60秒甚至更长。设太短会把正常请求误杀,设太长又会拖慢故障恢复速度。
再说重试。默认配置其实已经比较保守,但生产环境建议显式配置num_retries=3。要注意重试必须是幂等的——像文本生成这种每次调用都可能产生不同结果的接口,重试时如果业务逻辑不能接受重复调用,一定要在调用层做好去重或幂等控制。
最后说Router的故障转移。allowed_fails和cooldown是配合使用的:连续失败达到阈值,Router会认为这个上游不可用,进入冷却期,冷却期间流量全部打到健康上游。这里有个细节:冷却期不要设太长,60秒比较合适。设太长的话,上游恢复后你还得手动干预。
5.3 预算控制与成本告警
大模型API成本无上限,跑一个失控的任务,一晚上烧掉的钱可能比服务器还贵。LiteLLM在这块做了不少工作。
最基础的是设置全局月度预算:
import litellm litellm.max_budget = 1000 # 每月最多1000美元 litellm.budget_duration = "30d"超过预算后请求会被拒绝,避免成本失控。代理模式下还可以给每个虚拟密钥单独设置预算,配合管理接口做多项目成本隔离。
更实用的是预算回调。你可以注册一个回调函数,当成本达到某个阈值时通知你:
def budget_alert_fn(user_info): print(f"预算告警:已用 {user_info['total_cost']:.2f}") litellm.budget_alert_threshold = 0.8 litellm.budget_alert_callback = budget_alert_fn我在实际项目中是配合企业IM机器人做的:用到80%预算时自动往群里发消息,到100%时直接熔断。这样不会出现"半夜发现账单爆炸"的悲剧。
5.4 日志、指标与成本归因
生产环境光有调用还不够,还得能"看见"调用。LiteLLM的callback系统允许你把每次请求的成功/失败、延迟、token数和成本导出到自己的监控体系。
import litellm def my_callback(kwargs, completion_response, start_time, end_time): cost = kwargs.get("response_cost", 0) latency = end_time - start_time model = kwargs.get("model", "") # 这里把数据写到自己的监控平台或日志系统 print(f"model={model}, cost={cost:.4f}, latency={latency:.2f}s") litellm.success_callback = [my_callback] litellm.failure_callback = [my_callback]这样每个请求的实际花销、响应耗时都一目了然。成本数据是按真实计费规则算出来的,比你自己估算靠谱得多。
6. 实战中踩过的坑与对应排查思路
6.1 超时配置不生效的根因
有段时间我明明在代码里传了timeout=10,但某些请求仍然要30秒才超时断开。排查了很久,最后发现原因:我通过Router转发请求时,Router这一层有自己的超时处理;而传的timeout只作用于底下单个请求对象。如果Router层面没配超时,总耗时可能远超单次请求的预期。
解决办法是:在Router初始化时显式配置超时和重试参数,不要依赖默认值。
router = Router( model_list=..., timeout=30, num_retries=2, allowed_fails=3, cooldown=60, )然后单次调用的timeout单独控制"这一条具体请求"的上限。这两层要分开理解,不然排查起来容易在原地打转。
6.2 上下文长度溢出被误判成"模型不存在"
这个坑最迷惑人。有一次线上报错信息大致是model not found,我一查配置,模型名明明是对的。后来把请求体里的参数逐个去掉排查,发现罪魁祸首是某个上游模型对max_tokens有较严格的上限,超了之后返回的错误信息竟然是"模型不存在"而不是"参数超限"。
你可以用二分法定位:先用最简请求体(只有消息列表)试,能通说明代码逻辑没问题;再逐步加回参数,加到哪个挂了就是哪个。另外,如果你同时接多个供应商,同一个参数在不同模型上的限制差异很大,对这种兼容性问题保持敏感。
6.3 不认识参数的"隐形杀手"
不同供应商的参数集合不一样。有的模型不认识top_p之外的花哨参数,你传了它就拒绝。之前一个同事把针对厂商A写的temperature范围直接套到厂商B上,结果厂商B返回参数校验失败。我建议在全局设置里打开drop_params,让LiteLLM自动丢弃上游不支持的参数:
litellm_settings: drop_params: true这是个很实用的兜底选项。但要注意:如果你依赖某个特有参数做精细控制,全局丢弃会导致行为不一致,这种情况建议用前面提到的参数透传机制,在需要时明确指定。
6.4 成本统计不准
LiteLLM的成本计算依赖内置的价格表。但如果上游服务商调整了价格,或者你接的是内部部署的自建模型,内置价格表就可能对不上。实测下来,等价格表更新是有滞后风险的。
解决办法是维护一份自己的价格配置,或者在自己写的callback里回调真实成本数据。我在生产环境中是直接放弃内置估算,用callback记录的response_cost字段加上自己的计费逻辑,这样财务对账时不会出状况。
6.5 工具选型的取舍建议
最后聊点个人的选型经验。如果你是单机脚本、个人项目,直接用SDK模式就够了,没必要上代理。如果是团队协作、多服务共享模型资源,直接代理模式,省心太多。如果只是需要并发调用且不想维护服务,可以单进程内用Router。
还有一个建议:新建项目时不要一开始就把所有模型全配上去。先接一个供应商,跑通全链路,观察日志、成本和延迟是否符合预期,再逐步加模型、加策略。我见太多人一开始就把配置文件写得很丰满,结果线上出问题根本不知道是哪一环出问题。
半年来,LiteLLM已经成了我接大模型接口的默认起点。最明显的收益不是"少写代码",而是"不再怕换模型"。模型市场变化很快,今天的最优解可能三个月后就过时了。把接入层和业务层解耦,让自己随时有能力换到更好的模型,这件事本身就是一种降本手段。如果你也被各家API差异折磨过,不妨把它当作一个转接头,先小范围试起来。