数了一下,这两年多模型接入的痛苦我算是尝够了。先是某家的接口改了鉴权方式,紧跟着另一家的返回字段又换了命名,再后来团队里新来的同事直接把 key 写进了配置文件推上了仓库。每次新接一个模型,我都要在调用层写一遍适配逻辑,改一个字段,下游所有业务线跟着遭殃。直到我把LiteLLM引入到项目里,整个接入流程才算是真正顺了——一套接口管 OpenAI、Anthropic、Google、Azure,还有一堆开源模型,参数、返回结构、错误码全部统一。
这篇文章我打算把 LiteLLM 从基础调用到生产落地完整捋一遍,包括它解决了什么问题、SDK 怎么用、代理网关怎么架、负载均衡和预算管控怎么配,以及我实际踩过的几个坑。如果你正在做多模型接入,或者团队里人人手里拿着一套自己的调用代码,这篇文章应该能帮你省掉不少返工时间。
1. 为什么需要 LiteLLM:多模型接入的混乱现场
先聊一个很现实的场景。某个项目一开始只接了 OpenAI 的接口,后来为了降本,需要部分流量走开源模型;再后来客户要求支持 Anthropic 的模型做长上下文分析。于是你的代码里开始出现这种内容:
# 伪代码:各路厂商千奇百怪的调用方式 if provider == "openai": response = openai.ChatCompletion.create(model=..., messages=[...]) elif provider == "anthropic": response = anthropic.messages.create(model=..., messages=[...], max_tokens=...) elif provider == "google": response = genai.generate_text(model=..., prompt=...)这套 if-else 还只是表面问题。真正头疼的是每家厂商的返回结构都不一样,有的把 token 用量放在 usage 里,有的放在 usage_metadata 里,有的压根不返回。错误处理更是五花八门,有的返回 HTTP 状态码,有的把错误码放在请求体里,还有的直接抛 SDK 异常。每接一家新模型,光是调通、对齐、写异常处理,怎么也得两三天。
LiteLLM 解决的就是这件事。它对外提供一套统一的接口,内部帮你完成各家 API 的协议转换,你只需要关心模型名字和消息内容,其余全部交给它。而且它不是一个只能调用在线 API 的库,本地部署的模型(比如通过 Ollama 或 HuggingFace 跑的模型)、Azure OpenAI、Bedrock、Vertex AI 这些都能走同一套调用方式,这一点在后面对接私有化项目时特别管用。
它还提供两种使用形态:
- SDK 模式:在你的 Python 服务里直接
pip install litellm,然后像调用 OpenAI 一样调用所有模型。 - 代理网关模式:部署一个 LiteLLM 服务,把它当做一个统一 API 网关,团队所有人都走这个网关访问模型,key、预算、流量都在网关层管控。
这两种形态并不是互斥的,很多团队会先只用 SDK,等规模上来了再抽出代理服务。我下面会分别讲清楚怎么用。
2. 从 0 跑到 1:LiteLLM SDK 的安装与核心调用
LiteLLM 的安装非常简单,Python 3.8 以上就行:
pip install litellm如果你只需要一部分供应商,可以按需安装依赖。但大多数场景下,直接全量装就行,包体积不大。
先说最基础的调用。LiteLLM 的 SDK 接口刻意做得跟 OpenAI 兼容,目的就是降低迁移成本。你原来写 OpenAI 的代码,把导入和客户端换一下,基本就能跑起来:
import litellm response = litellm.completion( model="gpt-4o", messages=[ {"role": "system", "content": "你是一个短视频脚本助手。"}, {"role": "user", "content": "帮我写一个30秒的科普短视频脚本,主题是AI如何改变客服行业。"}, ], ) print(response.choices[0].message.content)看到没有,参数名就是messages、model,跟 OpenAI 官方 SDK 几乎一样。如果你之前用的是openai.ChatCompletion,迁移到 LiteLLM 基本只要改函数名。
然后是切换模型。假设你想换成 Anthropic 的 Claude,只需要把 model 参数换掉:
response = litellm.completion( model="claude-3-5-sonnet-20241022", messages=[...], )换模型不需要改后续的响应解析逻辑,因为 LiteLLM 已经把返回结构统一成了 OpenAI 风格。这意味着你可以在业务代码里做一个简单的模型路由,比如成本高的时候用便宜模型,复杂任务用贵模型,而下游逻辑完全不用改。
2.1 环境变量的配置方式
LiteLLM 读取各家 API key 的方式非常简单——通过环境变量。比如:
export OPENAI_API_KEY="sk-..." export ANTHROPIC_API_KEY="sk-ant-..." export GEMINI_API_KEY="..." export AZURE_API_KEY="..." export AZURE_API_BASE="..."如果你用的是 Azure OpenAI,配置要稍微多一点,因为 Azure 的端点、API 版本、部署名都需要指定。不过这些都可以用环境变量或模型名映射搞定,具体后面讲代理配置的时候一起说。
LiteLLM 读 key 是按名称自动匹配的,比如 model 以gpt-开头就去找OPENAI_API_KEY,以claude-开头就去找ANTHROPIC_API_KEY。如果你不想用环境变量,也可以直接在代码里传api_key参数,但我不建议在业务代码里硬编码密钥,尽量用环境变量配合部署平台管理。
2.2 流式输出与异步调用
做 LLM 应用,流式响应几乎是标配了,不然用户盯着一个空白对话框干等十几秒,体验太差。LiteLLM 对流式的支持同样走 OpenAI 风格:
response = litellm.completion( model="gpt-4o", messages=messages, stream=True, ) for chunk in response: delta = chunk.choices[0].delta.content if delta: print(delta, end="", flush=True)异步场景下,用litellm.acompletion即可,签名跟同步版一样。这里有一个容易被坑的点:异步流式迭代的写法跟同步不太一样,异步时你要用async for并且先进入异步上下文,具体写法我在第五部分踩坑实录里再展开。
2.3 为什么"一套格式"本身就是最大价值
很多刚接触 LiteLLM 的朋友会问一个很实在的问题:我自己封装一层不行吗?当然可以,但你封装一层的结果往往是——只覆盖了你用到的两三家模型,然后所有边缘情况都要自己填坑。LiteLLM 真正值钱的地方在于它维护了大量厂商的适配逻辑,包括接口版本变化、响应格式差异、异常码映射,你不需要自己追着每个厂商的更新日志跑。
举个例子,Anthropic 的 messages 结构和 OpenAI 不完全一样,早期版本甚至要求 system prompt 单独传参,LiteLLM 会在内部帮你转换。Google 的gemini-pro早期走的是generateContent协议,返回结构和 OpenAI 也不同,LiteLLM 也统一掉了。这些适配看起来很琐碎,但真要自己维护,就是无穷无尽的小 bug。
3. 把 LiteLLM 变成团队的统一 API 入口:代理网关部署
SDK 模式适合个人项目和单体服务。但当你的团队有多个服务、多个项目,大家都想调大模型时,如果每个服务自己去管 key、自己去接模型,那会陷入新的混乱:key 散落在各处、调用量无法统计、成本说不清楚、模型更新也不方便。
这时候就需要 LiteLLM 的代理网关模式。它本质上是一个孵化服务,在 4000 端口起一个本地服务,对外暴露 OpenAI 兼容的/chat/completions接口,内部再把请求转发到真正的模型供应商。
启动一个最基本的代理:
litellm --model gpt-4o --port 4000然后你就可以在任意语言、任意环境里,用 OpenAI 的客户端指向它:
import openai client = openai.OpenAI( api_key="随便填一个", base_url="http://localhost:4000" ) response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], ) print(response.choices[0].message.content)看到重点了吗:下游消费方根本不需要装 litellm,他们只需要一个能指向http://localhost:4000的 OpenAI SDK 就行,可以是 Python、Node.js,也可以是 Java 的 OpenAI 客户端库。LiteLLM 网关把你背后的多家模型全部封装成了一个 OpenAI 兼容服务。
3.1 用配置文件管理多模型路由
只代理一个模型没什么意思,真正好用的是一个配置文件管理多个模型。创建一个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-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY - model_name: llama-local litellm_params: model: ollama/llama3 api_base: http://127.0.0.1:11434启动时指定配置文件:
litellm --config config.yaml --port 4000这样你的统一入口就相当于同时接入了三路模型。业务方想用 Claude,只需要把请求里的 model 改成claude-sonnet即可,不需要知道 Claude 的 API key 是什么,甚至不需要知道它走的是哪家供应商。
你可能会问:为什么我在litellm_params.model里要写openai/、anthropic/这样的前缀?这是 LiteLLM 的命名约定,用来区分同名模型。比如在多个供应商都提供了gpt-4o时,你可以在内部用openai/gpt-4o和azure/gpt-4o区分,对外统一暴露为gpt-4o。
3.2 网关层的鉴权与 Key 管理
默认情况下,网关完全开放,任何人都能调用,这在内网里问题不大,但一旦暴露到外网就非常危险。LiteLLM 支持 master key 机制,设置了 master key 之后,所有请求都必须带Authorization: Bearer <master_key>,否则直接拒绝。
litellm --config config.yaml --master_key sk-my-master-key更进一步,你还可以为每个团队、每个成员分配独立的虚拟 key,每个 key 可以绑定不同的模型、不同的额度。这很适合创业团队给客户开用量额度,或者内部做成本分摊。网关会自动记录每个 key 的调用次数、token 消耗,方便你月底算账。
3.3 什么时候该升级到网关
我的建议是:只要你的项目里面存在两处以上直接调用模型的地方,并且这些地方都要管理自己的 key,那就可以考虑上网关。因为网关带来的不仅是一层封装,更重要的是调用可观测、成本有归因、模型可切换。早一点把网关搭起来,后面迁移模型时你会轻松很多。
4. 生产环境实战:负载均衡、重试与预算管控
光能调用还不够,生产环境真正考验的是稳定性、成本和容错。
4.1 负载均衡:多 key 轮询与权重配置
很多公司会遇到调用量太大,单个 key 被限流的问题。LiteLLM 网关支持为同一个模型配置多个 key,并自动负载均衡。配置很简单:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY_1 model_info: rpm: 600 - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY_2 model_info: rpm: 600注意这里用了同一个model_name,但各自的 api_key 不同,网关会自动在它们之间做轮询。model_info.rpm告诉 LiteLLM 每个 key 每分钟能承受的请求数,超过阈值时会自动切换到其他 key,避免被限流。
你还可以通过rpm做权重分配。比如一个 key 的 rpm 配成 600,另一个配成 300,网关会按比例分发流量,这在混合使用不同套餐时很有用。
4.2 重试机制与超时设置
在线 API 调用,网络波动、服务端限流都是家常便饭。LiteLLM 提供了内置重试逻辑,你不太需要自己写 while 循环。
import litellm litellm.set_retry_policy( total=5, backoff_factor=2, status_codes_to_retry=[429, 500, 503] )或者更细粒度一点,可以在 SDK 调用时通过参数控制:
response = litellm.completion( model="gpt-4o", messages=messages, retry=3, timeout=30, )timeout是每单次请求的超时时间(秒),retry是重试次数。重试策略里有指数退避,不是傻傻地立刻重发,这个细节很贴心——尤其遇到 429 限流时,立刻重试只会更糟。
关于超时,我想多提醒一句:大模型请求的耗时波动非常大,尤其是一些长上下文、复杂推理模型,简单场景可能 2 秒返回,复杂场景可能 1 分钟还没结果。所以超时值不要拍脑袋设一个 10 秒,你最好先压测一次再定。我们一开始把 timeout 设成 10,结果长文档摘要请求频繁 TimeoutError,后来调到了 60,世界清净了。
4.3 预算管控与用量追踪
成本是个避不开的话题。LiteLLM 网关在预算上做了不少设计,简单实用层面有这几个:
- 为虚拟 key 设置预算上限。比如给某个内部工具的 key 设
budget_limit=5,单位是美元。超过额度,网关直接返回 429,防止月底账单爆表。 - 按模型设置 spend 追踪。每次请求完成后,LiteLLM 记录 usage 并换算成金额,写入日志。
- Redis 缓存支持。多实例部署网关时,为了不让预算数据各算各的,可以启用 Redis 统一存储。
如果只用了 SDK 模式,没上网关,你也可以通过litellm.token_counter拿到本次请求的 token 数做基本统计。但我还是推荐团队级应用尽量走网关,因为集中化的统计能力远比埋点在各个服务里方便。
4.4 与 LangChain 等框架的搭配
现在很多 LLM 应用都会用到 LangChain,LiteLLM 可以直接作为 LangChain 的模型层使用。下面是一个最小示例:
from langchain_community.chat_models import ChatLiteLLM from langchain.schema import HumanMessage chat = ChatLiteLLM(model="gpt-4o", temperature=0.3) response = chat([HumanMessage(content="用一句话解释什么是面向对象编程")]) print(response.content)这里的价值在于:你在 LangChain 项目里切换模型时,只需要换model参数,不需要改 LangChain 的调用方式。如果你的团队一直在纠结选哪家模型来做 RAG,这一层抽象能让你快速做 AB 测试。
5. 实测中的坑与对策:数据格式、超时与兼容性
LiteLLM 整体做得不错,但这不代表没有坑。下面这些是我在真实项目里踩过,或者看见群里同行踩过的,列出来供你参考。
5.1 返回结构并非 100% 相同
LiteLLM 把各家返回结构统一成 OpenAI 风格,但具体字段的填充程度还是会有差异。举个例子,Google 的 Gemini 早期版本不返回usage.prompt_tokens_details,Anthropic 的 response 里有stop_reason字段,但语义和 OpenAI 的finish_reason略有差别。
我的处理办法是:在业务代码里只依赖最基础的choices[0].message.content和usage.total_tokens,其他增强字段一律用.get()取值,并且加好默认值。凡是生产环境要用到的字段,先跑一段冒烟测试,把各家在这个字段上的填充情况确认一遍,再固化到文档里。
5.2 异步流式迭代的坑
前面提到过异步流式,这里展开说。如果你用litellm.acompletion(stream=True),返回的是一个异步迭代器,你要这样拿内容:
async def stream_response(): response = await litellm.acompletion( model="gpt-4o", messages=messages, stream=True, ) async for chunk in response: delta = chunk.choices[0].delta.content if delta: yield delta很多刚入门的朋友会把acompletion写错成在普通函数里用for去遍历,结果拿到的全是协程对象。另外注意,异步环境下的请求生命周期管理很重要,底层 HTTP 连接默认是共享的,如果你在 FastAPI 的多并发请求里把同一个 client 用崩,优先检查是不是连接池配置太小,而不是 LiteLLM 本身的问题。
5.3 环境变量泄漏
有一个场景让我印象很深。某个同学为了测试方便,直接在仓库代码里用os.environ["OPENAI_API_KEY"] = "sk-xxx"这种形式写 key,然后不小心提交到了公共仓库。这种事故真的很低级但确实常见。
如果你用 LiteLLM 网关,从根源上就能规避这类问题——下游服务根本不需要感知任何供应商 key,它们唯一需要的 master key 也可以放到部署平台的环境变量里。这也是我推荐网关模式的另一个原因,安全边界清晰。
5.4 模型名冲突与映射混乱
代理网关里有个容易混乱的地方:model_name是暴露给调用方的名字,litellm_params.model是真正传给上游的名字,两者不一定相同。
我见过有团队在配置里图省事,全部用model_name: openai/gpt-4o这种写法,结果调用方直接看到了一串内部命名字段,还把供应商前缀当成了模型的正式名称。这样后面想切换供应商时,调用方代码里到处是旧名字,改起来非常痛苦。
我的建议是:model_name尽量用业务语义来命名,比如fast-chat、long-context,或者在明确知道模型品牌没问题的情况下直接叫gpt-4o。让业务方只关心需求场景对应的模型,由网关层去决定背后是谁。
5.5 成本统计的"小数点陷阱"
LiteLLM 会帮你把 token 换算成金额,但是这个换算不是免费的——它需要知道每个模型每百万 token 的价格。LiteLLM 内置了一部分默认价格,但新模型、自定义部署模型、企业私有模型很容易不在表内。
如果你发现网关里某个模型费用统计为 0 或者严重偏低,十有八九是这个模型没配置价格。解决办法是在配置里手动挂上价格信息:
model_list: - model_name: internal-embedding litellm_params: model: openai/text-embedding-3-large model_info: input_cost_per_token: 0.00000013 output_cost_per_token: 0.00000013价格配准了,月底财务对账才有意义,不然只是自我安慰。
6. 写在最后的实际体会
最后说点个人的经验总结。
我在实际项目中使用 LiteLLM 已经有一年多,最大的感受是:这层抽象真正解决的不是"技术难度",而是"管理成本"。多模型接入的本质问题不是某个 SDK 难写,而是每个供应商都有一套自己的规则,你需要一套统一规则把它们收敛起来,否则每次模型升级、价格调整、参数微调,你都要全链路手动跟进。
如果团队刚好处于"啥都想接入,但啥都接不稳"的阶段,我建议按三步走:先在单体服务里用 SDK 模式把所有模型调用收敛到同一个工具函数;然后找一台内部机器部署 LiteLLM 代理网关,配置好模型列表和 master key;最后再逐步把下游服务的模型调用地址切到网关,让所有 key 都收到网关统一管理。这三步每一步都可回滚,风险非常小。
另外一个小技巧:由于 LiteLLM 兼容 OpenAI 接口,你可以把网关地址无缝接进已有的 Postman 或 ApiFox 集合里,团队成员调试模型时,不用再看各家厂商的不同平台和不同鉴权文档,直接对着本地网关调试,体验会好很多。
如果你之后准备把某个模型从在线 API 换成私有化部署,比如从 GPT-4o 降级到本地训练的小模型,也不用改动业务代码,只需要在网关配置里把model_name的映射指向新的供应商即可,这个能力我愿称之为"金蝉脱壳",在真实业务中太管用了。