接大模型这件事,过去一年我反复折腾了不少次。最早是手头一个项目要同时接好几家模型厂商的API,每家SDK的请求格式、鉴权方式、错误码都不一样,光是适配层就写了快两千行,最后模型一升级还得跟着改。后来我注意到了litellm这个开源库,它不是一个模型的SDK,而是一个把主流大模型API统一成一套调用格式的工具,相当于在业务代码和各个模型服务之间垫了一层适配器。这篇文章我就把这段时间用litellm的完整经验梳理一遍,既包括怎么快速接入、代理模式怎么配,也包含我在生产环境里踩过的坑和最终沉淀下来的用法,希望能给正准备接入或者正在多模型之间来回切换的朋友一点参考。
litellm能解决的问题很直接:屏蔽掉不同模型厂商的请求差异,让你用一套近乎OpenAI风格的代码去调用几百种模型。它同时提供两种形态,一种是Python库,直接集成进你的服务;另一种是代理服务,可以单独部署,给团队当API网关用。我下面的内容会按"先讲清楚原理,再给实操步骤,最后说避坑经验"的顺序来展开。
1. 为什么我现在把litellm当成接大模型的默认选择
1.1 每接一个新模型就要重写一版调用代码,这个痛你大概率也遇到过
我最早接触大模型API的时候,第一个用的是某家海外厂商的接口,当时觉得OpenAI风格的消息结构就是主流。后来因为成本和效果原因,需要换成另一家模型,结果发现对方的接口推送用的是完全不同的字段结构,返回结果里也没有统一的choices字段,错误码更是对不上。为了不让业务代码跟着大改,我只能在中间写一层转换函数,把A模型的输入转成B模型能识别的格式,再把B模型的输出转回业务侧能统一解析的结构。
这层转换函数一开始还能应付,但模型一多就失控了。你不仅要处理消息格式的差异,还要处理参数名称的差异。比如同样是控制随机性,有的叫temperature,有的叫temp,有的叫top_p为核心;同样是限制返回长度,有的叫max_tokens,有的叫max_output_tokens,还有的没有这个参数需要换一种方式传。这种琐碎差异积累起来,维护成本非常高。我当时最直观的感受是:模型本身不是瓶颈,API差异才是。每接入一个新模型,光调试参数映射就要花掉大半天,而且供应商更新SDK版本之后,还可能把之前的兼容逻辑弄崩。
1.2 litellm的接口归一化思路:把所有模型都包装成同一种形状
litellm的核心思路用一个词概括就是"接口归一化"。它对外暴露一套固定的调用方式,底层按不同的模型供应商去分别实现适配。只要你选定模型的时候带上供应商前缀,比如model="openai/gpt-4o"或者model="anthropic/claude-3-5-sonnet",剩下的消息格式转换、参数映射、返回结构整理,全由litellm在内部完成。
这种归一化有一个很大的实际价值:你的业务代码里只依赖litellm的completion这类函数,而不是依赖某一家SDK。以后想换模型,改一个字符串就可以了,不用动整条调用链路。对于需要频繁对比多个模型效果、或者根据负载动态切换模型的项目来说,这个优势非常致命地实用。
1.3 litellm架构上分两层:直接调用的库 + 独立部署的代理
litellm的架构可以拆成两个层来理解:
- 第一层是Python库:你直接
pip install litellm,在代码里from litellm import completion。这一层适合单个服务内部使用,不需要额外部署进程,调用方式是进程内的函数调用。 - 第二层是代理服务:基于FastAPI封装的一个独立HTTP服务。你可以把它理解成一个翻译官网关,部署在服务端,对外提供OpenAI兼容的HTTP接口,对内连接各家模型。这一层适合多个服务共享一套模型接入能力,也适合做密钥管理、成本统计、限流等团队级需求。
我建议刚开始接触litellm的朋友不必急着上代理,先在Python库模式下把流程跑通,理解清楚它的行为特征,然后再决定要不要部署成服务。第二层虽然强大,但也多了一个需要运维的组件,属于典型的收益与复杂度并存。
2. 从安装到第一次对话:litellm最小化接入全程记录
2.1 安装与密钥准备,这两件事别在环境配置上浪费时间
litellm对Python版本没有特别苛刻的要求,我用的3.10和3.11环境都能直接跑。安装命令很简单:
pip install 'litellm[proxy]'这里我建议直接装带[proxy]的版本,因为如果你只是先装基础的litellm,之后想体验代理模式还得再补一个包,不如一步到位。
密钥准备上,litellm读取环境变量的方式是约定式的。比如你想调OpenAI系列模型,就设置OPENAI_API_KEY;想调某厂商的Claude系列,就设置对应的ANTHROPIC_API_KEY,各家大厂的SDK基本都在同一套环境变量命名体系下,这个不需要死记,在litellm文档里根据你用的供应商名称找对应变量名就行。
有一种情况需要注意:如果你所在的环境不允许设置太多环境变量,也可以在调用时用api_key参数直接传:
from litellm import completion response = completion( model="openai/gpt-4o-mini", messages=[{"role": "user", "content": "你好"}], api_key="你的key" )但这种方式我不建议在生产中使用,一是密钥容易硬编码进代码仓库,二是后续切换供应商不够灵活。统一走环境变量,是长期维护成本最低的方案。
2.2 最小化调用与返回结构解读,先跑通再谈优化
写一个最简单的调用,只需要几行代码:
import os from litellm import completion os.environ["OPENAI_API_KEY"] = "你的密钥" response = completion( model="gpt-4o-mini", messages=[ {"role": "system", "content": "你是一个简洁的技术助手"}, {"role": "user", "content": "用一句话解释什么是接口归一化"} ], temperature=0.7 ) print(response.choices[0].message.content)completion函数返回的对象结构是对齐OpenAI风格的,所以拿到response之后,用response.choices[0].message.content就能取出文本内容。这种结构的好处在于,即使你底层接的是其他厂商的模型,返回格式也保持一致,业务侧解析逻辑完全不用分叉。
我再补充一个经常会用到的字段:如果你想拿到模型对本次请求的token消耗情况,可以用response.usage,它包含prompt_tokens、completion_tokens和total_tokens。这个信息对后面做成本统计非常关键。
2.3 流式输出:让用户体验到逐字返回的效果
大模型对话场景下,流式输出几乎是标配。用户看到文字一个字一个字蹦出来,感觉上比等待完整回复要快得多。litellm对流式的支持也很简单,stream=True打开即可:
from litellm import completion response = completion( model="gpt-4o-mini", messages=[{"role": "user", "content": "给我讲一个程序员加班的笑话"}], stream=True, temperature=0.8 ) for chunk in response: delta = chunk.choices[0].delta.content if delta: print(delta, end="", flush=True)这里有一个细节:流式返回的每个chunk,它的文本内容藏在choices[0].delta.content里,而不是message.content。我第一次写的时候就直接拿后者取数据,结果打印了一堆None,卡了好一会儿。换成delta之后才正常。
还有一个容易被忽略的点:流式模式下,很多模型返回的第一块chunk可能带的是role字段而不是content字段,所以代码里一定要加if delta:这样的判断,避免把None也print出去。这个习惯能帮你少踩很多莫名其妙的坑。
3. 把litellm当成团队统一的模型网关:代理模式实战
3.1 代理模式解决的不只是"少写代码"问题
当你从一个人写脚本升级到多人协作、多个服务都需要调用模型时,你很快会面临三个问题:
第一,密钥管理。每个人都各自配各家的密钥,一旦某个密钥因为费用问题被重置,就得通知所有同事更新环境变量,这种沟通成本很容易让人崩溃。
第二,成本归属。多服务共用同一个模型密钥时,你很难分清每个服务分别花了多少钱,月底对账只能靠猜。
第三,限流和缓存。不同服务有不同的调用频率,但模型厂商的限流是按密钥维度算的。一个服务突发流量,可能导致另一个服务被限流。
litellm的代理模式正是为这些问题设计的。你单独起一个服务,统一管理密钥,对外暴露一个标准接口,所有下游服务都走这个口子。密钥只在代理这一层集中维护,成本也能按metadata之类的维度打标。这个思路跟很多团队自建网关的动机是一样的,只是litellm把模型侧的路由、转换、重试全给你做完了。
3.2 用配置文件定义多模型,再启动代理服务
litellm的代理可以通过命令行直接指定模型,也可以使用配置文件。我强烈建议从配置文件开始,因为后期你要加模型、改限流策略,都不用重启服务去改启动参数。
先准备一个config.yaml:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: sk-xxx - model_name: claude-sonnet litellm_params: model: anthropic/claude-3-5-sonnet api_key: sk-ant-xxx然后启动代理:
litellm --config config.yaml --port 8000启动之后,代理就监听在8000端口。此时所有下游服务不需要安装litellm,也不需要配置各家模型的密钥,只要把HTTP请求发到http://localhost:8000/v1就行。代理会解析请求里的model字段,根据配置去路由到对应的真实模型。
3.3 用OpenAI官方SDK连接litellm代理
代理对外提供的API是OpenAI兼容格式,这意味着什么?意味着你的业务代码压根不需要依赖litellm客户端,直接用OpenAI官方的SDK就能连:
from openai import OpenAI client = OpenAI( api_key="sk-any-value", # 代理层不关心这个key的具体值 base_url="http://localhost:8000/v1" ) response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "你好"}] ) print(response.choices[0].message.content)这里有一个反常识的地方:api_key参数随便填一个字符串就行,代理虽然会校验这个字段存在,但它本身并不拿这个key去调用模型,真正的模型密钥在代理的配置里。对下游服务来说,它只需要认识litellm代理这一个入口就够了。
这种"下游服务零适配成本"是代理模式最有吸引力的地方。就算你的团队从没用过litellm,只要他们写过OpenAI SDK,就能无缝切换到litellm代理。
3.4 多模型路由与故障切换,别让单个模型拖垮整个业务
代理模式下,配置里可以额外定义模型路由策略。比如你有一个主模型和一个备用模型,当主模型返回错误或者超时的时候,litellm可以自动把请求切换到备用模型:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o - model_name: gpt-4o-fallback litellm_params: model: anthropic/claude-3-5-sonnet litellm_settings: fallbacks: [ {"gpt-4o": ["gpt-4o-fallback"]} ]这个配置的意思是,当gpt-4o调用失败时,自动尝试gpt-4o-fallback。对于线上业务来说,这种机制可以明显降低因为某个模型服务抖动导致的故障率。不过需要提醒的是,切换模型的响应质量可能存在差异,所以它更适合对准确性要求不是极端苛刻的场景,如果涉及金融、医疗等领域,还是要人工介入确认的。
4. 成本、限流与密钥管理:把litellm真正用成团队基础设施
4.1 成本追踪要趁早,千万别等到月底才想起来看账单
我第一次用litellm代理跑了好几天,才想起来看成本。虽然litellm在数据库里记录了每次调用的token消耗,但没有在第一天就配置好成本统计,等我回头看的时候已经要翻日志了。建议你早期就做两件事:
第一,在调用时通过metadata参数给请求打标。比如用metadata={"tag": "project-name"},这样后面查成本可以按项目维度筛选。
第二,把litellm的数据库连接配好。代理模式下litellm默认把请求日志和token数据存在本地数据库里,你可以指定一个Postgres或MySQL连接地址。数据持久化之后,查询成本和调用量就方便很多:
litellm --config config.yaml --database_url postgresql://user:pass@localhost:5432/litellm有了数据之后,你可以直接调用代理内置的/spend/logs接口按标签拉取消耗明细,也可以自己写个简单脚本定时汇总。这一点对个人项目可能无所谓,但对团队协作来说,有数据可查和没数据可查的感受完全不同。
4.2 限流配置的三个关键参数,别让一个高并发服务把配额打爆
很多模型的API是按每分钟请求次数(RPM)和每分钟token数(TPM)限流的,超了就会返回429或者直接被封禁。litellm代理支持在配置里为每个模型单独设定限流参数:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o max_rate_limit: rpm: 60 tpm: 90000这里的rpm和tpm需要根据你实际订阅的模型套餐来设。不要拍脑袋填一个很高的数字,因为一旦代理侧的限流阈值比模型侧高,就容易出现上游限流、下游报错的现象;但如果设得太低,又会浪费配额。我建议第一版按模型套餐限额的80%来设置,留出缓冲,运行两周后再根据实际调用情况调整。
4.3 虚拟密钥:给团队成员发放不同额度的访问凭据
代理模式的另一个好处是支持虚拟密钥。也就是说,你可以给每个成员或者每个服务生成一个仅在这个代理上生效的key,并为它们设置独立的预算上限。这样即使某个成员不小心写了个死循环疯狂调用,费用也只会卡在预算上限,不会把整个模型账户的钱烧光。
具体做法是在代理起来之后,调用其内部接口创建虚拟key:
curl -X POST "http://localhost:8000/key/generate" \ -H "Authorization: Bearer 主密钥" \ -H "Content-Type: application/json" \ -d '{"models": ["gpt-4o"], "max_budget": 10}'执行完会返回一个sk-开头的虚拟key,把这个key交给对应团队成员使用即可。max_budget是美元额度,你可以按项目或按人分别设置。这种方式比把真实的模型厂商密钥分发下去干净多了,也方便后续回收权限。
5. 使用litellm这半年,我踩过的坑和绕路指南
5.1 超时时间默认值太短,长输出任务频繁中断
我最初在用代理接一个长文本生成任务时,经常出现请求中途断掉的现象。后来排查发现,litellm默认的超时时间对于高延迟场景并不够长。如果你处理的prompt比较复杂,或者模型是那种推理速度偏慢的版本,默认超时很容易触发。
解决方式是在配置里显式拉长超时:
litellm_settings: request_timeout: 300或者在代码调用时直接传参:
response = completion(..., request_timeout=300)这个名字在不同版本里可能稍有差异,但逻辑都是一样的:给慢请求留足空间。我习惯把超时时间设置成"预计最慢响应时间的一点五倍",而不是一个固定的300秒,这样既能cover住慢响应,又不至于等太久才失败。
5.2 模型名称带日期后缀,不锁版本等于给自己埋雷
有个坑让我印象很深:某家模型在发布时会带一个日期版本号,比如类似gpt-4o-2024-05-13这种命名。我在配置文件里直接写了不带日期的别名,结果一两个月之后,发现行为变了,甚至响应格式都有了细微差别。查了半天才意识到是模型侧偷偷切换了版本。
现在我的做法是:在配置里锁住带日期后缀的完整模型名,除非有意识地做模型升级测试,否则不轻易使用不带日期的别名。对需要稳定复现结果的项目来说,这一点尤其重要。
5.3 异步调用的返回类型分两种,别用同步的思维去收结果
litellm同时提供了同步和异步两套接口。同步是completion,异步是acompletion。我一开始以为异步就是简单地在函数前面加个await就行,结果发现不是这么回事。
acompletion返回的是一个协程对象,需要await才会真正执行。但在流式场景下,异步流被拆成了两种类型:一种是AsyncStream,另一种是CustomStream。具体是哪一种,取决于模型供应商的底层实现。你写async for去遍历碎片的时候,不同处理逻辑之间对chunk的解析方式是一致的这个没问题,但你如果在流式过程中还有额外的二次操作(比如实时统计token、修改某些上下文信息),就需要做一次类型判断,不然容易碰到属性不存在的问题。
我建议你在接入异步流式时,先做一个简单的类型探查:
import inspect async def test_stream(): resp = await acompletion(model="gpt-4o-mini", messages=[...], stream=True) print(type(resp)) # 运行一次,看清楚返回类型之后再写处理逻辑这个操作看起来多余,但能帮你少踩很多版本适配的坑。因为litellm在不同版本里对某些供应商的流式处理实现会调整,写死某一种类型的逻辑,升级后可能会悄悄挂掉。
5.4 自动重试是一把双刃剑,用错了会放大故障
litellm内置了失败重试机制,默认会在特定错误码下自动重试。这在网络抖动时可以起到很好的兜底作用,但在某些场景下会帮倒忙。
举个例子,你发了一个生成请求,模型端因为超时已经实际执行并消耗了token,但你的请求没有得到正常响应。如果此时litellm自动重试,就会再消耗一次token。虽然模型接口本身对这类超时的处理方式各有不同,但总的来说,对于非幂等的请求,重试成本是需要你来敲定的。
我现在的做法是,把自动重试次数调低,比如在配置里设置num_retries=1,同时把重试的条件限定为网络类错误,这样既保留了兜底能力,又不会因为机械的重试把费用拉高。
5.5 不同供应商对工具调用(function calling)的支持差异
如果你需要让模型调用外部工具,比如搜索、查数据库、执行特定函数,你需要知道litellm会帮你把统一的工具传参格式转换成各家的scheme。但是,各个厂商对工具调用的支持程度并不一致。有的模型对工具调用的返回格式很规范,有的模型则会把工具参数填得乱七八糟。
我的经验是:在正式接入一个模型之前,先用一个统一的函数调用测试用例把所有候选模型各跑一遍,对比它们返回的工具参数质量。不要因为某家模型的文本生成效果好就直接上线工具调用功能,工具调用的稳定性和文本质量是两个维度,必须分别验证。
6. 结合你的具体场景:什么时候该用litellm,什么时候不必用
6.1 三种值得用litellm的场景
第一种是你需要在多个模型之间切换。不管是做模型效果对比、根据成本动态路由,还是在不同国家地区选择不同的可用模型,litellm都能帮你把切换成本降到最低。哪怕你只是有"未来可能换模型"的想法,用litellm也是一种提前投资。
第二种是团队协作开发。多个成员、多个服务都要调模型时,用代理模式把密钥、成本、限流集中管理起来,能省下大量协调成本。我在几个并行项目里同时使用litellm之后,团队里再也没有人跑来问"我们用什么key调模型"这种问题了。
第三种是你希望有一份清晰的成本账单。litellm的日志和统计体系天然适合做成本归属。你只要在调用时带上合理的metadata标记,就能随时按项目、按功能维度拉出消耗明细,这在做预算审批时特别加分。
6.2 两种不必用litellm的场景
如果你只是在一个小工具里单次调用某一个模型,而且脚本写完就扔,那直接用官方SDK反而更轻快。引入litellm多了一层依赖,对这类一次性脚本来说意义不大。
另外,如果你需要极致低延迟且完全掌控底层协议,litellm的归一化处理会引入一定的开销。虽然这个开销通常很小,但在严格比拼首字节延迟的场景里,每一层中间处理都值得审视。这种时候直接基于厂商原生SDK做定制,可能更适合你。
6.3 我的落地建议
如果你刚接触litellm,我的建议分三步走:
第一步,在本地用Python库模式跑通几个主流模型,感受一下completion这层接口和你之前用官方SDK的差异,顺便确认返回结构是否符合你的预期。
第二步,如果你确定要在项目里使用,搭一个config.yaml,把用到的模型都列进去,启动代理模式跑两天。这个阶段重点观察日志、限流效果和成本记录,不做任何复杂的路由策略。
第三步,等代理跑稳了,再动手配置fallback路由、虚拟密钥、团队预算上限。基础设施类的功能一次性铺开容易出问题,分步推进才能每步都验证清楚。
我现在的习惯是,凡是团队级的大模型接入,一律先搭litellm代理;凡是个人快速验证demo,直接用官方SDK。这两种方式并不冲突,反而能在各自的场景里发挥最大价值。