做后端的人肯定都体会过,当一个项目从只调一家模型接口变成同时接三四家的时候,难受就开始了。OpenAI有OpenAI的格式,Anthropic有Anthropic的格式,Google那套又不一样,每个SDK依赖各自维护,API Key散落在各个环境变量里,月底账单出来还得手动对半天。LiteLLM就是解决这个问题的:一个开源的大模型API网关,把所有模型服务统一封装成OpenAI兼容接口,你只需要改一行base_url,业务代码几乎不用动,就能在任意模型之间切换、做负载均衡、失败重试、Key管理和成本追踪。这篇文章适合后端开发、算法工程和所有需要把多模型接入生产环境的人,我会从安装配置讲起,把核心机制和施工过程中踩过的坑一并写出来。
1. LiteLLM到底是什么:一句话说清它的定位
1.1 我在多模型项目里踩过的坑
大概半年前,我在做某跨平台系统的时候,业务方突然提了个需求:同一个语义理解功能,既要支持ChatGPT,又要支持国产模型和开源模型。那段时间我把各家SDK接了个遍,代码里全是if-else分支,每家接口的字段命名不一样、流式返回格式不一样、错误结构也不一样,升级一个供应商的SDK就得连带回归测试全部重跑。最崩溃的是某个供应商的模型服务偶尔超时,我得在业务代码里做超时重试,但每家的超时时间设置方式又不同。
后来我把这些接口都梳理成了一张表,发现本质问题是:我没有一个统一的抽象层。要么自己写一层封装,要么找个现成的网关。自己写的话,要把兼容性、重试、均衡、监控全做一遍,怎么也得两三个月;直接用LiteLLM,我两天就接完了。
1.2 LiteLLM的核心能力拆解
LiteLLM这个项目的定位可以用一句话概括:它是大模型API的统一路由器。你向它发OpenAI格式的请求,它会根据配置转发到对应的模型供应商,再把响应翻译回OpenAI格式给你。这个思路本质上和消息队列、数据库代理是同一个模式:业务方不关心后端具体是谁,只关心统一协议。
具体能力我列一下:
- 支持100多家模型供应商:OpenAI、Anthropic、Google、Azure、AWS Bedrock,以及Ollama、vLLM、HuggingFace等本地或自托管服务。只要是能在配置里写出来的模型,基本都有适配器。
- OpenAI兼容接口:对外暴露
/v1/chat/completions、/v1/completions、/v1/embeddings、/v1/models等标准端点,现有OpenAI SDK可以直接换base_url使用。 - 负载均衡与失败重试:同一路由名可以挂多个真实模型,某个响应异常时自动切换到下一个。
- 虚拟API Key管理与预算控制:生成子Key、按团队或项目分配额度,方便把模型成本分摊到具体业务线。
- 成本与用量统计:记录每一次请求的token消耗、延迟和费用,按维度聚合查询。
- 可观测性:输出日志、prometheus指标,也支持OpenTelemetry上报。
说直白一点,LiteLLM把“接模型”这个动作变成写配置,把“管模型”变成看仪表盘。这跟过去用Nginx统一多个后端服务的思路非常像:上游有多复杂,下游只看到一个稳定的入口。
2. 三分钟跑通:从安装到第一个请求
2.1 安装与最小启动
LiteLLM是Python项目,官方推荐用pip安装:
pip install 'litellm[proxy]'注意那个[proxy],它会把代理服务运行所需的依赖一并装好,包括FastAPI、uvicorn和相关的日志组件。如果你只需要在Python代码里调用封装函数,可以只装litellm核心包,但做网关部署的话建议直接把proxy选上。
安装完成后,最简单的启动方式是指定一个模型名:
export OPENAI_API_KEY=sk-你的密钥 litellm --model gpt-4o看到类似“Proxy running on http://0.0.0.0:4000”的日志就说明服务起来了。这时候本地已经有了一个端口4000的代理服务,它把gpt-4o这个模型暴露成了OpenAI格式接口。
如果要接Anthropic的模型,加一个环境变量再启动:
export ANTHROPIC_API_KEY=sk-ant-你的密钥 litellm --model claude-3-5-sonnet-20241022LiteLLM会根据llm_params里的模型名自动选择对应的供应商适配器,不需要单独指定“这是Anthropic的模型”之类的信息。它的命名约定是供应商名/模型名,比如openai/gpt-4o、anthropic/claude-3-5-sonnet-20241022、ollama/llama3。如果省略供应商前缀,默认归到OpenAI兼容目录。
2.2 用OpenAI SDK直接调用本地代理
服务起来之后,验证方式很简单。用Python写个最小调用:
from openai import OpenAI client = OpenAI( base_url="http://localhost:4000", api_key="任意值", ) resp = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "你好,一句话介绍一下自己"}], ) print(resp.choices[0].message.content)这里有个很多新手会疑惑的点:api_key为什么可以随便填?因为LiteLLM在本地代理模式下,接住请求后会用环境变量里的真实密钥去上游换token,你自己发给它的Key其实不参与上游鉴权。当然这是开发模式的默认行为,生产环境你通常会启动--master_key强制要求客户端Key有效,后面我会专门讲。
model参数填什么,取决于你的配置。刚才直接用--model启动的场景下,填gpt-4o即可。
2.3 环境变量与密钥配置的一点经验
我第一次用的时候,习惯把密钥写在启动脚本里,结果后来发现多模型场景下环境变量会越来越多。建议在项目目录下维护一个.env文件:
OPENAI_API_KEY=... ANTHROPIC_API_KEY=... AZURE_API_KEY=... AZURE_API_BASE=... AZURE_API_VERSION=...然后启动命令改成:
litellm --config config.yaml --dotenv--dotenv参数会加载.env文件里的变量到当前进程,这样你的真实密钥不会留在Shell历史里,也不容易误提交到Git仓库。注意.env文件要写进.gitignore。
3. 配置文件才是灵魂:model_list与路由策略
3.1 一张配置看懂核心结构
用命令行--model参数启动只适合快速验证,真正做项目还是得用配置文件。LiteLLM的YAML配置风格类似Kubernetes声明式:你描述期望状态,代理服务负责实现。
model_list: - model_name: chat-main litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: chat-main litellm_params: model: anthropic/claude-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY model_info: supports_vision: true关键在model_name和litellm_params的分离:model_name是你对外暴露的逻辑名字,业务代码只认它;litellm_params是真实转发参数,里面可以塞供应商、真实模型名、Key、base_url等等。
这么设计有什么好处?对外接口完全稳定。今天你用的是GPT-4o,明天老板说要换成某个国产模型,你在配置里改一行,重启代理,业务代码一行不用动。而且你可以在同一逻辑名下配多个真实模型,这就引出了负载均衡。
3.2 负载均衡与权重分配
model_name不要求唯一,同一个名字可以对应多组litellm_params。当请求打到这个model_name时,LiteLLM默认按轮询(round-robin)选择一组参数发给上游。这个负载均衡粒度是请求级的,不是连接级,所以对于高并发请求能天然散开。
如果你希望某些模型承载更多流量,可以加权重:
model_list: - model_name: chat-main litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY model_info: rpm: 10000 - model_name: chat-main litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY model_info: rpm: 50000实际处理方式是这样的:LiteLLM会根据每个后端的每分钟请求数(rpm,即requests per minute)来动态分配流量。更高的rpm意味着更大的权重。这比固定权重更符合真实场景,因为上游限流是按速率计的,不是按比例固定分配的。
负载均衡有个隐蔽的好处:你可以把生产流量分散到不同供应商的等价模型上,比如同时接OpenAI和Azure的GPT-4o,某一个供应商抽风时,另一个还在扛着。这种冗余用传统方式实现成本挺高,但在LiteLLM里只是多两组litellm_params的事。
3.3 失败重试与模型降级
配置文件同时定义了失败策略。对每次请求,你可以设置最大重试次数和备用模型:
general_settings: num_retries: 3 request_timeout: 300 fallbacks: [chat-fallback] model_list: - model_name: chat-main litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: chat-fallback litellm_params: model: anthropic/claude-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY当chat-main对应的模型全部不可用、请求超时或返回5xx错误时,LiteLLM会把请求降级到chat-fallback。这个模型就是你的“保底方案”,比如用稳定但便宜一点的模型来兜底。
重试方面num_retries默认是针对“同一个上游地址”的重试次数。站在业务角度看,我建议把num_retries设成2就行,重试3次以上容易导致端到端延迟变得不可控,用户那边体验很难受。尤其流式输出场景,重试逻辑要自己多测几遍,别等到上线让用户替你去发现。
4. 虚拟密钥与成本追踪:团队使用必做的两件事
4.1 虚拟Key的生成与权限管理
自己一个人玩,本地代理的api_key可以随便填。但团队用就不行了:你需要知道每个Key背后的负责人、可以调哪些模型、费用上限是多少。LiteLLM支持虚拟API Key,意思是它生成自己的Key并记录映射关系,客户端发请求时带这个虚拟Key,代理获得真实上游Key去调用供应商,然后把用量记到虚拟Key的名下。
用管理接口生成Key:
curl -X POST http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-master-key" \ -H "Content-Type: application/json" \ -d '{ "models": ["chat-main"], "max_budget": 20.0, "budget_duration": "30d", "user_id": "team-a" }'返回结果里有一个api_key字段,比如sk-abc123...,这个就是团队成员实际使用的Key。生产环境下我给每个成员、每条业务线都生成独立的虚拟Key,好处是出了问题能沿着Key追溯,哪个服务在疯狂刷token、哪个模型费用超了,门儿清。
虚拟Key的鉴权行为值得展开说:它不改变上游鉴权,只是代理层的访问凭据。上游的真实Key始终由代理持有,业务方永远接触不到。这对于控制“员工离职是否泄露密钥”的问题非常有用——虚拟Key可以随时吊销,而真实Key只需要保存在服务器环境变量里。
4.2 成本追踪的数据维度
成本追踪是LiteLLM另一块硬核功能。它会把每次请求的模型、token数、延迟、费用记录到数据库。默认可以用SQLite先跑起来,配置里指定:
general_settings: database_url: sqlite:///./litellm.db启动后访问http://localhost:4000/ui可以打开一个管理界面,那里能看到四个维度的统计:按虚拟Key看、按模型看、按用户ID看、按时间范围看。
我实际用得最多的是“按模型+时间段”的对比视图。某一次月底复盘,有个同事发现某个业务线调用量暴增,通过LiteLLM的明细表定位到是一次算法实验误打成生产流量,然后直接用虚拟Key的限流配置把那个Key的调用速率压了下来,整个排查加处理不到十分钟。这种体验在以前手动对账单的时候完全不敢想。
4.3 预算设置与限额控制
在虚拟Key的生成接口里我已经写了max_budget和budget_duration,这两个参数组合起来能实现“这个Key在30天内最多花20美元”的效果。预算粒度可以细化到请求级别:
general_settings: max_budget: 1000 budget_duration: 30dgeneral_settings里的预算作用于整个代理实例,适合团队总预算控制。当总花费超过阈值,LiteLLM会开始拒绝新请求并返回一个429响应,模拟的响应体里会带上超预算信息。
这里有一个容易踩的坑:max_budget是按“LiteLLM记录的累计成本”计算的,它依赖token数量和定价表。如果某个模型不在内置定价表里(比如自建私有模型),费用可能会估算为0,预算控制就形同虚设。解决办法是自己维护一个成本映射表,把每百万token输入/输出的价格写进litellm_params对应的model_info里。
5. 进阶实战:接入本地模型与Embedding服务
5.1 把Ollama模型接入统一网关
LiteLLM的适配能力不只是云端商业模型,本地跑的开源模型同样可以通过代理暴露出来。以Ollama为例,只要本机或局域网某台服务器上已经跑好Ollama服务,LiteLLM配置如下:
model_list: - model_name: llama-local litellm_params: model: ollama/llama3.1 api_base: http://localhost:11434业务侧调用方式没有任何区别:
resp = client.chat.completions.create( model="llama-local", messages=[{"role": "user", "content": "你好"}], )这里LiteLLM干的事情是从model: ollama/llama3.1中识别出供应商前缀ollama,然后走Ollama的适配器,把OpenAI格式的请求翻译成Ollama的/api/chat格式。
为什么本地模型也要统一走代理?一个很现实的理由是:本地模型的输出格式往往五花八门,有的带response字段,有的返回纯字符串,有些还需要做采样参数映射。业务方如果直接对接,代码里全是特判分支;统一走LiteLLM后,业务只认OpenAI那套格式,上游怎么变的都无所谓。
5.2 Embedding接口的转发与配置
嵌入向量在RAG系统里用得很频繁。LiteLLM同样支持embedding模型的统一封装:
model_list: - model_name: embedding-main litellm_params: model: openai/text-embedding-3-small api_key: os.environ/OPENAI_API_KEY - model_name: embedding-local litellm_params: model: ollama/nomic-embed-text api_base: http://localhost:11434调用方式依然是OpenAI SDK:
resp = client.embeddings.create( model="embedding-main", input="今天天气怎么样", ) print(resp.data[0].embedding[:5])Embedding接口的转发逻辑相比chat要简单一些,但需要注意向量维度问题。不同供应商返回的向量维度可能不同:OpenAI那个模型是1536维,本地小模型可能是768维。你在配置里把embedding-main和embedding-local放在同一逻辑名下时,下游如果做向量检索,切换模型后必须重新构建索引,否则维度对不上会导致检索直接报错。这不是LiteLLM的锅,是业务自身约束,但统一网关容易让人忽略这一点——你只看到模型名没变,底层向量维度变了。
6. 常见问题排查与部署心得
6.1 高频报错速查表
这个项目因为涉及上游网关、下游客户端和LLM本身,报错链路很长。我把几个月来遇到的最常见问题整理成一张速查表,方便你出错时先对号入座。
| 报错或现象 | 常见原因 | 排查与解法 |
|---|---|---|
| 401 Unauthorized | 上游真实密钥缺失或无效 | 检查环境变量是否正确加载,用litellm --list查看加载模型,确认api_key没有写成os.environ/xxx错误的变量名 |
| 404 Model Not Found | 配置里model_name不对或上游模型名不匹配 | 检查客户端传的model参数是否对应配置里的model_name,同时确认litellm_params.model正确带有供应商前缀 |
| 429 Too Many Requests | 上游限流,或预算/速率限制触发 | 查看日志中限流字段,如果是上游限流则调小并发数、增加重试间隔;如果是预算限制则去管理后台调整max_budget |
| 超时/Timeout | 上游响应慢,或request_timeout设太小 | 先试通一条curl命令,排除网络问题;再把request_timeout调大,观察具体是DNS解析慢还是模型推理慢 |
| 流式输出断流 | 客户端与代理之间的网络不稳定,或代理与上游之间的流式协议不兼容 | 检查上游是否真的支持流式,某些本地模型不带流式能力,代理需要做非流式到流式的桥接,配置model_info.supports_streaming: false |
排查的通用路径是:先确定是“客户端到LiteLLM”这一段,还是“LiteLLM到上游”这一段。用curl直接打代理接口,如果报错信息里包含上游供应商的返回体,那问题多半在上游;如果报错是代理自身生成的,再看代理日志。
6.2 生产环境部署的几个建议
我建议在生产环境把LiteLLM和业务服务分开部署,它是独立网关层,有自己的CPU和网络开销。Docker部署是最省心的方式:
FROM ghcr.io/berriai/litellm:main-latest WORKDIR /app COPY config.yaml . ENV DATABASE_URL=postgresql://user:pass@host:5432/litellm_db CMD ["litellm", "--config", "config.yaml"]有几个细节:
- 生产数据库别用SQLite,并发一高SQLite会锁死。PostgreSQL或者MySQL都行,LiteLLM官方对PostgreSQL支持最好。
- 网关进程本身建议双副本跑在负载均衡后面,因为重启代理期间,短暂断流对业务还是有一定影响。
- 环境变量里真实Key不能被容器日志带出来。启动容器时用
--env-file方式加载,不要把它明文写进Dockerfile。 - 代理服务需要暴露端口和健康检查端点,监控体系直接对
/health/liveliness做探活。
6.3 我踩过的一些坑
第一次在生产上接LiteLLM时,我把所有模型放在同一个model_name下并开启了默认的round-robin。结果某个业务方反馈“为什么我的请求偶尔变慢”,查了日志才发现是部分请求被路由到了一个性能差异较大的模型上。从那以后我学乖了:逻辑名拆细一点,比如chat-fast、chat-main、chat-fallback分开,让不同服务质量需求的业务各取所需,而不是图省事全塞一个名字里。
另一个印象深刻的坑是timeout配置。某个模型推理平均只要1秒,但偶尔要7秒,我把request_timeout设成了5秒,结果正常请求有一小部分被误杀。重试三次之后,上游反而被压得更慢。后来我把超时调到了平均延迟的3倍,并且调低重试次数,情况才稳定下来。这个经验希望在读者心里留个印象:超时不是越小越好,要给慢请求留空间,又不能让重试加重上游负担。
还有一个不算坑但容易被忽略的点:如果你同时接了流式和非流式请求,最好确认代理配置里的model_info.supports_streaming与真实模型能力一致。有些商业模型卡片写着支持流式,但API实际走的是非流式,代理会等到完整响应再向前端推,表现就是“一直没动静”,看起来像卡住了。
最后分享一点我自己的体会
用LiteLLM这段时间,最值钱的不是省去了几个SDK,而是它把“模型接入”这件事彻底从代码里抽离出来了。以前模型切换要发版、重启、测试,现在改配置、热加载、验证,几分钟搞定。团队里的算法同学可以自己写好模型配置,后端不需要跟着改一行代码。我个人比较推荐把config.yaml提交到Git仓库,每次改动都走Code Review,这样谁改了哪个模型、为什么改,全都留痕。如果你正好在建多模型服务,或者为模型配置管理和成本分摊头痛,LiteLLM值得尽快尝试一下,先从本地代理跑通一个模型开始,逐步把真实业务接进来,你会慢慢感受到统一网关带来的好处。