1. 多家大模型API来回切换的痛苦,才是litellm真正的用武之地
过去一年,我先后在几个项目里接入了不同的大模型服务商。说实话,最让人头疼的不是模型效果本身,而是API的调用方式五花八门。有的用messages数组传对话历史,有的用input字段;有的返回choices[0].message.content,有的返回completions[0].text;流式输出的格式更是各家有各家的规矩。每换一家供应商,就要重写一遍调用层,再写一堆适配代码,心情极其烦躁。
后来我注意到GitHub上这个叫litellm的开源项目,star数涨得很快。它的思路很直接:把市面上主流大模型供应商的API格式全部统一成OpenAI的格式,这样上层业务只需要维护一套调用代码,底层想换哪家就换哪家。
简单说,litellm做的事情有三层:
- 统一调用接口:用OpenAI的Python SDK或Node SDK,去调Anthropic、Gemini、Cohere、Bedrock、Azure OpenAI等几十家服务商的模型。
- 代理服务器模式:本地起一个兼容OpenAI接口的网关服务,所有请求打到这个网关,由它负责转发到真正的模型供应商。
- 工程化配套能力:包括成本追踪、限流、重试、负载均衡、多key管理、预算控制等,相当于把生产环境调用大模型的脏活累活一并解决了。
如果你只是写个脚本调一次模型,litellm确实帮不上什么大忙——直接装个原生SDK更省事。但如果你要在真实项目里接入多个模型、要切换供应商、要控制成本、要处理限流和重试,litellm几乎是把这些事打包成了一个标准答案。这篇文章我想从原理到部署,再到我实际用下来的踩坑记录,完整聊一遍。
2. litellm的工作原理:一个翻译层加一个网关
2.1 它本质上做的是协议转换
要理解litellm,我建议先抛开“框架”“平台”这些大词,抓住一个核心:它是个API翻译层。
各家模型服务商的请求格式、响应格式、鉴权方式、错误码都不一样。litellm在中间做了一层映射,把OpenAI格式的请求翻译成目标服务商的要求,再把目标服务商的响应翻译回OpenAI格式。你拿到手里的永远是OpenAI风格的response["choices"][0]["message"]["content"]。
我用一个非常生活化的类比:litellm像个同声传译。你和他说中文,他去跟说英语、法语、日语的人交流,回来再用中文告诉你结果。你不需要学英语、法语和日语,只需要会说中文。
这种设计的直接好处是:业务代码的稳定性提高了。供应商A的SDK升级了、废弃了某个参数、改了返回字段,只要litellm跟上适配,你的业务代码一行都不用动。
2.2 两种使用方式:SDK直连和代理网关
litellm提供两种接入方式,对应不同的使用场景。
第一种是SDK方式。
from litellm import completion response = completion( model="claude-3-5-sonnet-20240620", messages=[ {"role": "user", "content": "用一句话解释什么是API网关"} ] ) print(response["choices"][0]["message"]["content"])你看,completion函数和OpenAI的ChatCompletion.create几乎一模一样,但model参数传的是Anthropic的模型名。litellm内部根据模型名路由到对应供应商,自动处理鉴权和格式转换。这种方式适合在脚本、函数、后端服务里直接集成。
第二种是代理网关模式。
litellm --model gpt-4o --port 4000这条命令会在本地4000端口起一个OpenAI兼容的代理服务。然后你的代码里只需要把base_url指向http://localhost:4000:
from openai import OpenAI client = OpenAI( api_key="anything_you_want", base_url="http://localhost:4000" ) response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "你好"}] )注意,这里用的是OpenAI官方SDK,但实际请求会被litellm网关转发到配置好的模型供应商。网关模式的好处在于:业务方拿到的就是一个标准的OpenAI兼容endpoint,不管后端接的是哪家模型、接了几家模型、怎么做负载均衡,业务方完全无感知。
生产环境我建议用网关模式。原因有三个:
- 业务代码完全和具体供应商解耦,供应商切换只改网关配置。
- 可以在网关层统一做限流、预算、日志、缓存,代码只写一遍。
- 多个服务共享同一个网关,不用每个服务各自管理API key。
2.3 支持几百家供应商背后的路由逻辑
litellm支持几百家供应商,靠的是一个命名约定:模型名的前缀即供应商。
比如gpt-4o是OpenAI,claude-3-5-sonnet-20240620是Anthropic,gemini-1.5-pro是Google,bedrock/claude-3-haiku是AWS Bedrock,ollama/llama3.1是本地Ollama。litellm解析模型名里的provider/前缀来匹配路由规则。
这个设计有个好处是心智负担小。你不需要额外传一个provider参数,模型名里自带了。bedrock/前缀的意思是:模型本身是claude-3-haiku,但走的是Bedrock的通道。
生产中如果不确定模型名前缀怎么写,直接做一次空的completion调用,报错信息里通常会列出目前支持的供应商列表和正确的模型名格式。
3. 从零部署一个litellm代理网关:环境准备、配置管理与API key体系
3.1 安装与最小化启动
litellm是纯Python项目,安装很简单:
pip install litellm装完先验证一下:
litellm --help看到版本号之后,可以先用最简单的方式起一个代理:
litellm --model gpt-4o --port 4000如果环境变量里配了OPENAI_API_KEY,这个代理就能直接转发请求。测试一下:
curl http://localhost:4000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer test-key" \ -d '{ "model": "gpt-4o", "messages": [{"role": "user", "content": "你好"}] }'返回的JSON结构和OpenAI官方接口一致。到这里,一个最小可用的代理网关已经跑起来了。
3.2 config.yaml才是生产环境的正确玩法
直接用命令行参数起服务适合测试,生产环境必须用配置文件。litellm的配置文件是YAML格式,逻辑很简单:定义哪些模型可以被访问,以及每个模型走哪个供应商、用什么key。
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: claude-3-5-sonnet litellm_params: model: anthropic/claude-3-5-sonnet-20240620 api_key: os.environ/ANTHROPIC_API_KEY - model_name: llama-3-70b litellm_params: model: ollama/llama3.1:70b api_base: http://localhost:11434用这个配置启动:
litellm --config config.yaml --port 4000这里model_name是暴露给使用方的模型别名,litellm_params里的model才是真实的供应商模型名。这样设计的价值在于:你可以在不通知业务方的情况下,把model_name: gpt-4o背后从OpenAI换成Azure OpenAI,甚至换成某个自部署的开源模型。
3.3 API key体系:主key与虚拟key的隔离
网关本身也需要鉴权,不然谁拿到端口都能白嫖你的模型额度。litellm提供虚拟key机制,相当于在真实供应商API key前面再加一层。
设置管理员key:
export LITELLM_MASTER_KEY="sk-1234" litellm --config config.yaml --port 4000 --master_key sk-1234然后可以通过管理接口创建只能访问特定模型的虚拟key:
curl http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-1234" \ -H "Content-Type: application/json" \ -d '{ "models": ["gpt-4o"], "max_budget": 10.0 }'返回的key就是你发给业务方使用的虚拟key。这个key只能调用gpt-4o,且总预算10美金。这样即便key泄漏了,损失也限制在10美金以内。
生产环境中,我强烈建议为每个业务线单独生成虚拟key,配上独立的预算和限流阈值。出了任何费用问题,看key就能定位到是哪个业务、哪个服务、哪个时间段冒的尖。
4. 接入实战:统一SDK、流式输出、多供应商切换与成本追踪
4.1 SDK接入的具体写法
网关跑起来之后,业务侧接入是很舒服的。Python侧用OpenAI SDK:
from openai import OpenAI client = OpenAI( api_key="sk-virtual-key-xxx", base_url="http://localhost:4000" ) def chat(prompt: str) -> str: resp = client.chat.completions.create( model="claude-3-5-sonnet", messages=[{"role": "user", "content": prompt}], temperature=0.7 ) return resp.choices[0].message.contentNode.js侧也一样:
import OpenAI from "openai"; const client = new OpenAI({ apiKey: "sk-virtual-key-xxx", baseURL: "http://localhost:4000", }); const resp = await client.chat.completions.create({ model: "gpt-4o", messages: [{ role: "user", content: "你好" }], }); console.log(resp.choices[0].message.content);只要团队里约定好base_url和虚拟key,所有服务都按OpenAI的标准写。以后模型从Claude换成GPT,代码里改一个model参数字符串就完事。
4.2 流式输出的坑
大模型应用几乎离不开流式输出。litellm对OpenAI格式的流式透传做得不错,但有几个细节必须留意。
Python侧流式调用:
stream = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "写一篇800字的短文"}], stream=True ) for chunk in stream: if chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="")Node侧也类似,用for await遍历。第一个容易踩的坑是:litellm某些模式下,流式响应里第一个chunk可能带role字段但没有content,如果你的解析逻辑只处理choices[0].delta.content没问题,但如果你习惯性读choices[0].delta整体判断,要注意做空值保护。
第二个坑是finish_reason的兼容性。不同供应商对流结束的标记有细微差别,litellm虽然统一成了OpenAI格式,但个别供应商在你stop之后还会多推一个空chunk。这需要你自己做幂等处理。
4.3 切换模型供应商的完整示例
假设业务里原本用GPT-4o,现在想部分流量切到Anthropic的Claude测试效果。整个过程只动配置,不改代码。
先在config.yaml里加上Claude的模型:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: claude-3-5-sonnet litellm_params: model: anthropic/claude-3-5-sonnet-20240620 api_key: os.environ/ANTHROPIC_API_KEY重启网关,业务方调用时把model参数从gpt-4o换成claude-3-5-sonnet,完事。如果你想做灰度,还可以利用litellm的负载均衡,让同一模型名随机路由到多个后端供应商。这个后面展开讲。
4.4 成本追踪:不看报表的成本控制都是空谈
litellm内置了一套成本追踪机制,可以自动按模型、按key、按天、按用户维度记录token消耗和费用。它维护了一个模型价格表,大部分主流模型的价格已经内置了,不需要手动配置。
查看网关运行统计:
curl http://localhost:4000/spend/logs \ -H "Authorization: Bearer sk-1234"还可以按月看总花费:
curl http://localhost:4000/spend?start_date=2025-01-01&end_date=2025-12-31 \ -H "Authorization: Bearer sk-1234"如果接入了Prometheus,litellm也暴露了/metrics端点,token用量、请求延迟、错误率这些指标都能直接抓。我自己习惯在Grafana里配一个按小时聚合的token消耗面板——大模型项目的成本失控从来不是某一天突然爆的,而是每天涨一点你没注意,月底一看傻眼。
5. 生产环境必须考虑的工程化配置:负载均衡、重试、限流与预算
5.1 多key负载均衡
当你单个模型的调用量上来了,一个API key可能触发供应商限流。litellm允许给同一个模型配多个API key,轮询使用。
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: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY_2注意写法:同一个model_name配了两条,每条对应一个key,litellm收到gpt-4o请求后在两个key之间做轮询。这种配置在开源模型自托管场景下更有意思——你可以把同一模型部署在多台机器上,通过api_base指到不同实例,litellm帮你做流量分发。
5.2 重试策略与退避
大模型接口偶尔会返回5xx,或者429限流。litellm在litellm_params里支持重试配置:
model_list: - model_name: llama-3-70b litellm_params: model: ollama/llama3.1:70b api_base: http://192.168.1.10:11434 num_retries: 3 retry_after: 5 fallbacks: ["gpt-4o"]这里num_retries是重试次数,retry_after是重试间隔秒数。fallbacks更有意思——如果这个本地模型挂了,自动降级到gpt-4o。这个机制对稳定性要求高的服务来说价值极大。
有一点需要提醒:重试要配合幂等设计。如果你的请求里有非幂等操作(比如让模型生成之后直接扣费),重试可能造成重复扣费或重复业务动作。这种情况建议在业务侧做请求ID去重,而不是完全依赖网关层重试。
5.3 限流与预算控制
litellm的限流配置在key层面:
curl http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-1234" \ -H "Content-Type: application/json" \ -d '{ "models": ["gpt-4o"], "max_budget": 50.0, "rpm_limit": 100, "tpm_limit": 50000 }'rpm_limit是每分钟请求数,tpm_limit是每分钟token数。配置后,超过阈值的请求会被网关拒绝并返回429。这套机制能够防止单个调用方因为代码bug疯狂循环调用,把整个项目的成本拉爆。
5.4 生产部署:systemd与Docker
真实服务不可能用一个前台进程挂着。我建议用Docker起网关:
docker run -d \ --name litellm-gateway \ -p 4000:4000 \ -v $(pwd)/config.yaml:/app/config.yaml \ -e LITELLM_MASTER_KEY=sk-1234 \ ghcr.io/berriai/litellm:main-latest \ --config /app/config.yaml多个供应商的API key通过-e传进去,config.yaml里用os.environ/XXX引用。这样key不会写死在文件里,也方便在CI/CD里做密钥管理。
内存方面要特别留意。litellm会为每个请求分配一些临时数据,高并发场景下内存上涨比较明显,建议给容器至少512MB内存,并且做内存上限限制。
6. 实测中的高频问题与排查思路
6.1 请求超时的定位与调整
前几天一个服务反馈说流式输出经常"卡住"。我去看litellm日志,发现一个现象:请求转发到了后端,后端也正常返回了,但客户端过了很久才收到第一个token。
问题出在litellm的timeout配置上。默认的超时时间对某些供应商来说偏长,导致在超时未触发时看起来像卡死。解法是显式配置:
response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], timeout=30 )网关侧也可以通过环境变量调整:
export LITELLM_REQUEST_TIMEOUT=30还有一次是上游供应商自身响应慢,网关已经在等,但客户端那边read timeout设置了5秒。两边超时时间不匹配,会造成客户端先放弃、网关还在等上游的情况。建议客户端读超时设得比网关整体超时略长,链路里就统一了。
6.2 模型名解析失败
又一个高频问题:提示模型不存在。
litellm.exceptions.BadRequestError: litellm.provider_not_found: provider not found这种基本就是模型前缀写错了。litellm能自动识别的是带正确供应商前缀的模型名,比如anthropic/claude-3-5-sonnet-20240620。如果你直接写claude-3-5-sonnet-20240620,它可能识别不了。注意:在litellm的config.yaml里,除非你配了model_name作为别名,否则真实模型名必须带供应商前缀。
排查这类问题,最快的办法是把报错信息完整贴出来,看它提示的provider not found指的是哪个字段。如果确认模型名没问题,就检查环境变量里对应的key是否设置了,很多"provider not found"其实是"API key not found"的连带报错。
6.3 前后端不一致:流式模式下finish_reason的坑
我在做某个聊天应用时发现,部分响应结束后客户端没有收到finish_reason为stop的chunk,导致前端一直显示"生成中"。
逐个对比了多家供应商的原始流式输出后发现:Anthropic的流式响应里,message_stop事件的形态和OpenAI的格式差异较大。litellm虽然做了映射,但个别版本里对stream_options: {"include_usage": true}的支持不够完整。如果你用了这个参数,建议确认litellm版本是否支持,不支持就升级。
6.4 日志排查的实用技巧
litellm的日志默认是打到标准输出的,能看到每一次请求的目标模型、供应商、耗时和token数。排查问题时,我一般直接过滤关键字:
journalctl -u litellm.service | grep "model="model=后面跟着的就是实际路由到的模型和供应商。如果发现路由结果和预期不一致,问题多半出在config.yaml里model_list的匹配逻辑上。
还可以用debug模式看更详细的内部请求:
litellm --config config.yaml --port 4000 --detailed_debug这个模式下,日志里会打印litellm发送给上游供应商的完整请求体,方便确认请求在翻译层有没有被改坏。
7. 总结一些我实际用下来的体会
这套方案在我们这里稳定跑了快一年,我最大的感受是:litellm解决的不只是API格式统一的问题,它把大模型接入从"每换一家供应商就要重新适配一次"变成了"改配置、重启、验证"三个动作。这个改变对团队效率的提升是很直观的——原来开发侧要专门留一个同学维护各种SDK适配层,现在这些工作被吸收掉了,大家可以把精力放到业务逻辑上。
两件我觉得值得注意的事:
第一,不要一上来就全量迁移。litellm的很多高级功能(比如负载均衡、预算控制、多key轮询)需要一定的理解成本。建议先让一个团队用一个模型跑通链路,确认稳定和收益之后再推广。
第二,关注版本迭代。litellm迭代速度极快,三天一个小版本、两周一个大版本很常见。升级前一定要先看changelog,特别是涉及路由规则和配置格式的变更。我这边就遇到过升级后旧配置兼容性出问题的情况,好在都有备份,回滚很快。
有人可能会说:"我直接写个适配层不就完了,何必引入一个框架?"如果只接两三家供应商,自己写适配层确实够用。但当你后面要接的模型越来越多、要处理的问题从"格式转换"上升到"成本、限流、预算、灰度、可观测性"这些工程问题时,自己写的适配层会变得越来越重。这时候litellm这种社区活跃、更新频繁、踩坑人多的方案,反而更省事。
以上就是我个人在litellm上从了解到落地使用的完整经历。如果你们团队也在纠结多模型接入怎么搞,希望这篇能帮上忙。