1. 从一个真实困境说起:多模型调用的碎片化之痛
如果你正在做AI应用开发,大概率遇到过这样的场景:项目初期只接了一家大模型服务商的API,代码写得干净利落。但随着业务推进,产品经理说"能不能加个备用模型,万一主服务挂了要能自动切换",运营说"这个场景用便宜的小模型就够了,别浪费钱",算法同事又说"新出的那个开源模型效果不错,接进来对比一下"。于是你的代码里开始出现各种if-else分支,每个服务商的SDK各不相同,参数命名五花八门,返回格式更是千奇百怪。改到第三家的时候,你发现自己维护的已经不是业务逻辑,而是一堆适配层代码。
这就是litellm要解决的核心问题。它是一个用Python编写的开源库,做的事情用一句话概括:用一套统一的OpenAI格式接口,去调用市面上几乎所有主流大模型服务。你只需要把原来调用OpenAI的那几行代码里的模型名称改一下,就能无缝切换到其他服务商,不需要引入新的SDK,不需要改调用逻辑,不需要重新学一套参数体系。
我第一次接触litellm是在一个需要同时对接多家模型服务的项目里。当时团队评估了几个方案:自己写适配层、用某个云厂商的网关服务、或者找一个现成的统一调用库。自己写适配层的工作量被严重低估了——光是处理不同服务商的流式返回格式差异就花了两天。云厂商的网关服务倒是省事,但绑定性强,而且有些小众模型根本不支持。最后选了litellm,接入第一个替代模型只用了不到十分钟。
这篇文章适合哪些人看?如果你是需要快速集成多个大模型的开发者,litellm能帮你省掉大量适配工作;如果你是在做模型对比评测的研究人员,它能让你用同一套代码跑不同模型的benchmark;如果你是在搭建AI应用的技术负责人,它能帮你设计出更灵活的模型路由架构。接下来的内容,我会从核心机制、实操配置、成本控制、避坑经验几个维度,把litellm的用法和背后的设计逻辑讲透。
2. litellm的核心机制:统一接口是怎么做到的
2.1 一次函数调用背后的完整链路
很多人第一次用litellm会觉得"这也太简单了",简单到有点不真实。你写一行completion(model="某服务商/某模型", messages=[...]),它就真的返回结果了。但这行代码背后发生的事情,值得拆开看看。
litellm的核心是一个模型路由映射表。当你传入model参数时,它会解析这个字符串,识别出服务商前缀和具体模型名。比如"openai/gpt-4o"会路由到OpenAI的处理逻辑,"anthropic/claude-sonnet-4-20250514"会路由到Anthropic的处理逻辑。每个服务商对应一个Provider实现类,这个类负责三件事:把统一的OpenAI格式请求转换成该服务商API要求的格式、发起HTTP请求、把返回结果再转换回OpenAI格式。
这个转换层是litellm最核心的价值。不同服务商的API差异有多大?举几个实际例子你就明白了。OpenAI的对话接口用messages数组,每条消息有role和content字段;而某些服务商的接口把系统提示词单独放在一个system参数里,不放在messages数组里。OpenAI的流式返回是SSE格式,每个chunk是一个JSON对象;有些服务商返回的是NDJSON,格式完全不同。OpenAI的token计数在返回体的usage字段里,字段名是prompt_tokens和completion_tokens;有些服务商叫input_tokens和output_tokens。这些差异如果让每个开发者自己处理,就是无穷无尽的重复劳动。
litellm的Provider实现类把这些差异全部封装掉了。你传入的是统一的OpenAI格式,拿到手的也是统一的OpenAI格式。这意味着你现有的基于OpenAI SDK写的代码,只需要把openai.ChatCompletion.create换成litellm.completion,把model参数从"gpt-4o"改成"服务商前缀/模型名",其他代码一行都不用动。
2.2 支持的模型范围与路由逻辑
litellm目前支持的模型服务商数量已经超过100家,覆盖了市面上你能想到的几乎所有主流选择。从大型云服务商的模型服务,到专注于推理优化的新兴平台,再到本地部署的推理框架,基本都有对应的Provider实现。
路由逻辑的设计有一个细节值得注意:litellm采用前缀匹配策略。当你写model="openai/gpt-4o"时,它会先匹配openai这个前缀,找到对应的Provider,然后把gpt-4o作为实际模型名传给该Provider。如果你写model="gpt-4o"不带前缀,litellm会根据模型名的特征自动推断服务商——比如以gpt-开头的默认走OpenAI,以claude-开头的默认走Anthropic。这个自动推断机制在快速测试时很方便,但在生产环境我建议始终显式指定前缀,避免因为模型命名规则变化导致路由错误。
还有一个容易被忽略的点:litellm的Provider实现并不是简单的HTTP转发。它内置了参数映射表,把OpenAI风格的参数名映射到各服务商的实际参数名。比如max_tokens在某些服务商那里叫max_output_tokens,temperature的取值范围在不同服务商那里可能不同(有的支持0-2,有的只支持0-1),litellm会在转换时做相应的裁剪和适配。这个细节在实际使用中非常关键,因为参数不匹配导致的报错往往很难排查。
2.3 与直接使用各家SDK的对比
为了更直观地说明litellm的价值,我整理了一个对比表格,基于我在实际项目中的使用体验:
| 对比维度 | 直接使用各家SDK | 使用litellm统一调用 |
|---|---|---|
| 接入新模型的工作量 | 需要阅读文档、安装SDK、学习参数体系,通常半天到一天 | 改一行model参数,通常几分钟 |
| 代码维护成本 | 每个服务商一套调用逻辑,N个服务商就是N套代码 | 一套调用逻辑,服务商差异被封装 |
| 流式返回处理 | 每家格式不同,需要分别处理 | 统一为OpenAI SSE格式 |
| 错误处理 | 每家的错误码和错误信息格式不同 | 统一错误类型和错误信息结构 |
| 成本追踪 | 需要自己从各家返回体中提取token用量 | 统一从response.usage获取 |
| 版本升级影响 | 某家SDK升级可能导致调用代码需要修改 | litellm统一适配,业务代码不受影响 |
| 灵活性 | 可以使用各家SDK的独有特性 | 独有特性需要通过extra参数传递 |
这个表格不是说litellm全面优于直接使用SDK。如果你只需要对接一家服务商,而且不需要切换,直接用官方SDK是最简单也最稳定的选择。litellm的价值在多模型场景下才会真正体现出来。当你需要对接三家以上的服务商,或者需要频繁切换模型做对比测试时,litellm节省的时间会非常可观。
3. 环境搭建与第一个可运行示例
3.1 安装与密钥配置的几种方式
litellm的安装非常简单,一条pip命令搞定:
pip install litellm但安装只是第一步,密钥配置才是新手最容易卡住的地方。litellm需要知道每个服务商的API密钥才能发起调用。它支持多种密钥配置方式,我按推荐程度从高到低排列。
方式一:环境变量。这是最推荐的方式,也是生产环境的标准做法。每个服务商对应一个特定的环境变量名,litellm会自动读取。比如OpenAI的密钥放在OPENAI_API_KEY,Anthropic的放在ANTHROPIC_API_KEY。你可以在.env文件里统一管理,用python-dotenv加载:
import os from dotenv import load_dotenv load_dotenv() # .env文件内容示例: # OPENAI_API_KEY=sk-xxxx # ANTHROPIC_API_KEY=sk-ant-xxxx方式二:代码中显式传入。在调用时通过api_key参数传入,适合快速测试:
import litellm response = litellm.completion( model="openai/gpt-4o", messages=[{"role": "user", "content": "你好"}], api_key="sk-xxxx" )方式三:配置文件。litellm支持通过config.yaml文件配置多个模型和密钥,适合需要管理大量模型配置的场景。这个方式在搭建代理服务时特别有用,后面会详细讲。
注意:无论用哪种方式,密钥都不要硬编码在业务代码里,更不要提交到代码仓库。我见过不止一个项目因为把密钥写死在代码里然后推到了公开仓库,导致密钥泄露产生意外费用。
3.2 最简调用:从OpenAI切换到其他模型
先看一个最基础的调用示例,用OpenAI的模型:
import litellm response = litellm.completion( model="openai/gpt-4o", messages=[ {"role": "system", "content": "你是一个简洁的助手。"}, {"role": "user", "content": "用一句话解释什么是API。"} ] ) print(response.choices[0].message.content)现在假设你想换成另一个服务商的模型,只需要改model参数:
response = litellm.completion( model="anthropic/claude-sonnet-4-20250514", messages=[ {"role": "system", "content": "你是一个简洁的助手。"}, {"role": "user", "content": "用一句话解释什么是API。"} ] )其他代码完全不用动。返回的response对象结构也是一致的,response.choices[0].message.content照样能取到内容。这就是litellm最核心的使用方式。
3.3 流式输出的统一处理
流式输出是实际应用中最常用的模式,litellm把它统一成了OpenAI的SSE格式。不管你调用哪家服务商,流式返回的处理方式都一样:
response = litellm.completion( model="openai/gpt-4o", messages=[{"role": "user", "content": "写一首关于编程的短诗"}], stream=True ) for chunk in response: content = chunk.choices[0].delta.content if content: print(content, end="", flush=True)这段代码换成任何支持流式的服务商都能跑。litellm在底层做了格式转换,把各家的流式返回统一成了OpenAI的chunk格式。每个chunk的choices[0].delta.content就是增量文本。
这里有一个实操中容易踩的坑:不是所有服务商的流式返回都包含完整的usage信息。OpenAI在流式返回的最后一个chunk里会带上token用量,但有些服务商不会。如果你需要精确统计流式调用的token消耗,可能需要额外处理。litellm提供了一个stream_options参数来请求包含usage信息,但能否生效取决于底层服务商是否支持。
4. 多模型路由与成本控制的实战配置
4.1 用Router实现自动故障转移
单次调用用litellm.completion就够了,但生产环境需要考虑高可用。如果主模型服务挂了怎么办?如果某个模型响应太慢怎么办?litellm提供了Router类来解决这些问题。
Router的核心能力是多模型部署和自动故障转移。你可以把同一个逻辑功能配置多个模型作为备选,当主模型调用失败时自动切换到备用模型:
from litellm import Router router = Router( model_list=[ { "model_name": "my-primary-model", "litellm_params": { "model": "openai/gpt-4o", "api_key": os.getenv("OPENAI_API_KEY") } }, { "model_name": "my-primary-model", "litellm_params": { "model": "anthropic/claude-sonnet-4-20250514", "api_key": os.getenv("ANTHROPIC_API_KEY") } } ], fallbacks=[{"my-primary-model": ["my-primary-model"]}], num_retries=2 ) response = router.completion( model="my-primary-model", messages=[{"role": "user", "content": "你好"}] )这段配置的含义是:定义了两个部署,都叫my-primary-model。当第一个部署调用失败时,Router会自动重试并切换到第二个部署。num_retries=2表示失败后最多重试两次。
这个机制在实际项目中非常有用。我之前做过一个项目,主模型服务在高峰期偶尔会出现超时,配置了故障转移之后,用户基本感知不到服务波动。但要注意,故障转移不是万能的。如果两个部署用的是同一家服务商的不同模型,服务商整体故障时两个都会挂。真正的高可用需要跨服务商配置备选。
4.2 按任务复杂度分配模型以控制成本
多模型架构最大的价值之一是成本优化。不同模型的调用成本可能相差几十倍,但并不是所有任务都需要最贵的模型。用Router的标签路由功能,可以按任务复杂度自动分配模型:
router = Router( model_list=[ { "model_name": "cheap-model", "litellm_params": { "model": "openai/gpt-4o-mini", "api_key": os.getenv("OPENAI_API_KEY") } }, { "model_name": "expensive-model", "litellm_params": { "model": "openai/gpt-4o", "api_key": os.getenv("OPENAI_API_KEY") } } ] ) # 简单任务用便宜模型 simple_response = router.completion( model="cheap-model", messages=[{"role": "user", "content": "把这句话翻译成英文:今天天气不错"}] ) # 复杂任务用贵模型 complex_response = router.completion( model="expensive-model", messages=[{"role": "user", "content": "分析这段代码的时间复杂度并给出优化方案:..."}] )这个思路说起来简单,但实际落地时需要一个判断逻辑来决定什么时候用哪个模型。我的经验是:先用贵模型跑一批典型任务,记录哪些任务贵模型和便宜模型的输出质量差异不大,然后把那些任务路由到便宜模型。通常来说,翻译、分类、信息提取、简单问答这类任务,小模型完全够用;而代码生成、复杂推理、长文写作这类任务,大模型的优势明显。
4.3 成本追踪与预算告警
litellm内置了成本计算功能。每次调用返回的response对象里有一个_hidden_params字段,包含了本次调用的成本估算:
response = litellm.completion( model="openai/gpt-4o", messages=[{"role": "user", "content": "你好"}] ) cost = response._hidden_params.get("response_cost", 0) print(f"本次调用成本:${cost:.6f}")litellm维护了一个模型价格表,会根据token用量自动计算成本。这个价格表会定期更新,但如果你用的是比较新的模型或者自定义部署的模型,可能需要手动配置价格。
基于这个成本数据,你可以搭建预算监控。比如在Router层面设置预算上限:
from litellm import Router router = Router( model_list=[...], # 设置全局预算 max_budget=100.0, # 美元 budget_duration="30d" # 30天周期 )当累计成本超过预算时,Router会拒绝新的请求。这个功能在多团队共用一套模型服务时特别有用,可以防止某个团队的异常调用导致整体费用失控。
实操心得:成本追踪的精度取决于服务商返回的token用量是否准确。大部分服务商返回的用量是准确的,但流式调用时部分服务商不返回usage,这时候litellm会用tokenizer估算,精度会打折扣。如果成本控制是硬需求,建议对关键调用使用非流式模式,或者额外接入一个token计数服务做交叉验证。
5. 代理服务模式:把litellm变成团队共用的模型网关
5.1 为什么需要一个统一的模型网关
当团队规模超过三五个人,每个人都在自己的代码里配置密钥、管理模型调用时,会出现几个问题:密钥分散在多个地方,安全管理困难;每个人的调用方式不统一,代码难以复用;无法从全局视角看到整个团队的模型使用情况和成本分布。
litellm提供了一个代理服务模式,可以把模型调用能力包装成一个标准的OpenAI兼容API服务。团队成员不需要安装litellm,不需要配置各家服务商的密钥,只需要像调用OpenAI一样调用这个代理服务就行。
启动代理服务最简单的方式是用命令行:
litellm --model openai/gpt-4o --port 8000这条命令会在本地8000端口启动一个API服务,接口格式和OpenAI完全兼容。团队成员用任何OpenAI客户端都能调用:
from openai import OpenAI client = OpenAI( base_url="http://localhost:8000", api_key="any-string" # 代理服务不校验这个 ) response = client.chat.completions.create( model="openai/gpt-4o", messages=[{"role": "user", "content": "你好"}] )5.2 用配置文件管理多模型和多密钥
命令行启动适合快速测试,生产环境需要用配置文件。litellm的config.yaml支持定义多个模型、多个密钥、访问控制等:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: claude-sonnet litellm_params: model: anthropic/claude-sonnet-4-20250514 api_key: os.environ/ANTHROPIC_API_KEY general_settings: master_key: sk-my-master-key database_url: postgresql://user:pass@localhost:5432/litellm litellm_settings: drop_params: true set_verbose: false这个配置文件定义了两个模型,分别对应OpenAI和Anthropic。master_key是管理密钥,用于管理接口的鉴权。database_url配置后可以启用用量记录、预算管理等高级功能。
启动命令:
litellm --config config.yaml --port 80005.3 团队协作中的密钥隔离与用量统计
代理服务模式最大的价值在于密钥隔离。团队成员拿到的是代理服务的访问密钥,而不是各家服务商的原始密钥。这意味着你可以随时吊销某个成员的访问权限,而不需要去各家服务商后台逐个修改密钥。
litellm支持为每个团队成员生成独立的虚拟密钥,并设置不同的权限和预算:
# 创建一个只能访问gpt-4o、月预算10美元的虚拟密钥 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", "key_alias": "team-member-a" }'返回的虚拟密钥可以给团队成员使用。当这个成员的累计消费达到10美元时,他的密钥会自动失效。这个机制在多团队共用模型资源时非常实用,可以从制度上避免某个团队的异常调用影响其他人。
用量统计方面,配置了database_url之后,所有调用记录都会写入数据库。litellm自带了一个简单的管理界面,访问http://localhost:8000/ui可以查看用量报表。如果需要更复杂的分析,可以直接查数据库表,用SQL做自定义统计。
6. 踩坑实录:那些文档里没写的注意事项
6.1 参数传递的兼容性陷阱
litellm虽然做了参数映射,但不同服务商支持的参数集并不完全一致。有些参数OpenAI支持但其他服务商不支持,有些参数在不同服务商那里含义不同。litellm提供了一个drop_params选项来处理这个问题:
litellm.drop_params = True开启后,litellm会自动丢弃目标服务商不支持的参数,而不是报错。这个选项在快速切换模型做对比测试时很有用,但生产环境要谨慎使用,因为参数被静默丢弃可能导致行为不符合预期。
另一个常见的坑是temperature的取值范围。OpenAI支持0到2,但有些服务商只支持0到1。如果你传了1.5,litellm会尝试裁剪到1.0,但裁剪后的行为可能和你预期的不一样。我的建议是:如果需要在多个服务商之间切换,把temperature控制在0到1之间,这个范围是所有主流服务商都支持的。
6.2 流式调用中的token统计偏差
前面提到过流式调用的token统计问题,这里展开说一下。OpenAI在流式返回的最后一个chunk里会带上usage信息,但需要显式请求:
response = litellm.completion( model="openai/gpt-4o", messages=[{"role": "user", "content": "你好"}], stream=True, stream_options={"include_usage": True} )即使加了这个参数,也不是所有服务商都支持。不支持的服务商返回的usage可能是空的,或者litellm会用tokenizer估算一个值。估算值和实际值可能有5%到15%的偏差。如果你的应用需要精确计费,建议对关键调用使用非流式模式,或者额外接入一个独立的token计数服务。
6.3 超时与重试的合理配置
网络请求超时是生产环境必须处理的问题。litellm默认的超时时间可能不适合所有场景,需要根据实际情况调整:
response = litellm.completion( model="openai/gpt-4o", messages=[{"role": "user", "content": "写一篇长文"}], timeout=60, # 秒 num_retries=3 )超时时间的设置需要权衡。设太短,长文本生成任务会被中断;设太长,故障时用户等待时间过久。我的经验值是:简单问答类任务设15到30秒,长文本生成类任务设60到120秒。重试次数建议设2到3次,再多的话用户等待时间会很长,而且如果是服务商整体故障,重试再多次也没用。
还有一个细节:重试时的退避策略。litellm默认使用指数退避,第一次重试等待1秒,第二次2秒,第三次4秒。这个策略在服务商限流时比较有效,但如果服务商是硬故障,退避等待只是浪费时间。可以通过retry_policy参数自定义退避策略。
6.4 模型名称变更导致的调用失败
服务商的模型名称会不定期更新。某个模型版本下线后,用旧名称调用会直接报错。litellm的模型映射表会跟随更新,但如果你用的是比较新的模型或者自定义部署的模型,可能需要手动配置映射。
我遇到过一次线上故障:某个服务商悄悄把模型名称从model-v1改成了model-v1-20250101,导致所有用旧名称的调用全部失败。litellm的更新有延迟,等新版本发布时已经过去了几个小时。后来我们的做法是:在配置文件中显式指定完整的模型名称,并定期检查服务商的模型列表变化。对于关键业务,配置多个备选模型,主模型不可用时自动切换。
7. 进阶玩法:自定义Provider与回调扩展
7.1 接入litellm尚未支持的模型服务
litellm虽然支持的服务商很多,但总有一些小众的或者内部部署的模型服务不在列表里。这时候可以通过自定义Provider来接入。litellm提供了CustomLLM基类,你只需要实现几个核心方法:
from litellm import CustomLLM, completion, ModelResponse from typing import Optional, List, Dict, Any class MyCustomLLM(CustomLLM): def completion(self, model: str, messages: List[Dict[str, Any]], **kwargs) -> ModelResponse: # 在这里实现你的调用逻辑 # 把messages转换成你的服务要求的格式 # 发起请求,拿到结果 # 把结果转换成OpenAI格式的ModelResponse pass # 注册自定义Provider litellm.custom_provider_map = [ {"provider": "my-custom", "custom_handler": MyCustomLLM()} ] # 使用 response = completion( model="my-custom/my-model", messages=[{"role": "user", "content": "你好"}] )这个机制的价值在于:你可以把内部部署的模型服务也纳入litellm的统一管理,享受故障转移、成本追踪、用量统计等能力。实现自定义Provider的工作量不大,核心就是做好请求和返回的格式转换。
7.2 用回调实现日志记录与监控告警
litellm支持在调用前后触发回调函数,可以用来做日志记录、性能监控、异常告警等:
import litellm from litellm.integrations.custom_logger import CustomLogger class MyLogger(CustomLogger): def log_success_event(self, kwargs, response_obj, start_time, end_time): # 调用成功时的回调 duration = (end_time - start_time).total_seconds() model = kwargs.get("model", "unknown") print(f"模型 {model} 调用成功,耗时 {duration:.2f}秒") def log_failure_event(self, kwargs, response_obj, start_time, end_time): # 调用失败时的回调 model = kwargs.get("model", "unknown") error = kwargs.get("exception", "unknown") print(f"模型 {model} 调用失败:{error}") # 这里可以接入告警系统 # 注册回调 litellm.callbacks = [MyLogger()]基于这个回调机制,你可以搭建一套完整的可观测性体系:记录每次调用的模型、耗时、token用量、成本;统计各模型的成功率和平均延迟;当某个模型的失败率超过阈值时触发告警。这些数据对于优化模型路由策略、发现性能瓶颈非常有价值。
7.3 缓存策略减少重复调用成本
对于重复性高的查询,启用缓存可以显著降低成本。litellm支持多种缓存后端:
import litellm from litellm.caching import Cache # 使用内存缓存(适合单机部署) litellm.cache = Cache(type="local") # 使用Redis缓存(适合多机部署) litellm.cache = Cache( type="redis", host="localhost", port=6379, password="your-password" ) # 启用缓存 response = litellm.completion( model="openai/gpt-4o", messages=[{"role": "user", "content": "什么是机器学习?"}], caching=True )缓存的key是基于请求参数生成的哈希值。相同的模型、相同的messages、相同的参数,第二次调用会直接返回缓存结果,不产生费用。这个机制在开发测试阶段特别有用,反复调试同一段代码时不会重复消耗token。
但要注意:缓存不适合所有场景。如果temperature大于0,同样的输入每次应该产生不同的输出,这时候缓存会导致结果不符合预期。我的做法是:只在temperature=0的确定性调用上启用缓存,或者在开发环境启用、生产环境关闭。
8. 我的实际使用体会
用了litellm一年多,最大的感受是它把"多模型"这件事从架构难题变成了配置问题。以前接入一个新模型需要改代码、测试、上线,现在改一行配置就行。这个变化带来的不只是效率提升,更重要的是架构灵活性的质变——你可以随时根据成本、效果、可用性调整模型策略,而不需要动业务代码。
但litellm不是银弹。它的抽象层在带来便利的同时,也屏蔽了一些底层细节。如果你需要使用某个服务商的独有特性,可能需要通过extra_headers或extra_body参数绕过litellm的封装直接传递。另外,litellm的版本迭代比较快,升级时偶尔会遇到行为变化,建议锁定版本并在测试环境验证后再升级。
最后分享一个我踩过的坑:不要在litellm的配置里明文写密钥。我见过有团队把config.yaml提交到了代码仓库,里面包含了所有服务商的密钥。正确做法是用环境变量引用,配置文件里只写os.environ/VAR_NAME。litellm支持这种写法,启动时会从环境变量读取实际值。这个习惯养成之后,密钥管理会安全很多。