做 AI 应用开发的人,应该都体会过一种焦虑:今天这家发布了更强的模型,明天那家调整了定价,后天业务方又要求必须支持某个特定平台。真正让人疲惫的往往不是模型能力不够,而是接入方式五花八门——有的厂商给的是 Python SDK,有的只提供 REST 接口,返回字段对不上,错误码含义完全不同,限流阈值也千奇百怪。litellm 就是冲着这个局面来的:它把几十家模型服务商的接口统一成一个入口,让你用一套代码、一个服务去接所有模型。这篇文章我会从它的设计思路讲起,带你把一个能用的代理服务跑起来,再聊聊生产环境里我踩过的那些坑。
1. 为什么会需要 litellm 这个东西
1.1 接过多家模型 API 之后,我遇到的真实痛点
先说个很实际的场景。某项目一开始只接了某家闭源模型的接口,业务跑得挺顺。后来客户提了个新需求,说想切换到另一家效果更好的模型,还要保留原模型作为兜底。听起来很简单,对吧?真正动手才发现,事情远不是改个 model 名字就能解决的。
第一层问题是协议不统一。A 家的 SDK 用model字段指定模型,B 家的调用姿势是modelId;A 家的流式输出走的是 WebSocket,B 家却只给了 SSE。第二层问题是错误处理没法复用。A 家限流返回 HTTP 429,B 家限流返回的居然是一个特定业务码,你写好的重试逻辑换个供应商就失效了。第三层问题是密钥和计费割裂。团队里每个成员找各自厂商申请 key,月底对账全靠手工 Excel,哪个项目烧了多少 token,没人说得清。
我见过不少团队的做法是给每家厂商写一个 Adapter,维护一个十几个文件的适配层。说实话,短期看是能用的,但长期维护成本极高:每家厂商升级 SDK、改参数格式,你的适配层就得跟着升一轮。而且适配层本身不涉及路由、不涉及预算控制、不涉及团队级权限管理,最后还是要自己再补一堆轮子。
1.2 litellm 的定位:既是 SDK,也是代理服务
litellm 和普通的适配层有个本质区别:它把"统一接口"这件事同时做成了两种形态,你可以按需选择。
第一种是纯 SDK 形态。直接pip install litellm,在代码里调用litellm.completion(),通过model参数里的前缀去指定供应商,比如openai/gpt-4o、anthropic/claude-3-5-sonnet。这种方式适合单机脚本、快速验证、或者在你自己应用里直接集成,不需要起额外服务。
第二种是代理服务形态,也是我觉得它价值最大的形态。它把 litellm 跑成一个独立的本地服务,对外暴露一个OpenAI 兼容的 HTTP 接口。你的业务代码根本不需要感知 litellm 的存在,只需要把原本指向官方服务的 base_url 改成指向 litellm 的地址,把 api_key 换成 litellm 发的虚拟 key,就可以继续用你熟悉的 OpenAI SDK 去调模型。底层具体走哪家供应商、密钥从哪来、要不要重试、预算够不够,全部由 litellm 来决定。
这两种形态可以共存。做验证的时候用 SDK 形态最省事,正式上线或者要接入多个团队的时候,代理形态几乎是必然选择。我做过的多数项目都是先从 SDK 用起,等要管密钥、要分部门统计成本时,再在前面加一层代理,迁移成本非常低。
2. 核心设计拆解:统一入口背后的四个关键能力
2.1 统一接口模型:为什么 OpenAI 格式成了事实标准
litellm 对外暴露的是 OpenAI 兼容接口,这一点很关键。它不是自己发明一套"更科学的格式",而是选择拥抱已经事实上横行的 OpenAI 请求/响应结构。你可以把它想象成电源转换器:各国电压标准不同,但转换器统一输出一个 220V,于是所有电器都能插上去。OpenAI 兼容格式就是这个 220V。
这个选择背后的逻辑很清楚。第一,OpenAI 的接口知名度最高,绝大多数 AI 开发者都写过至少一次messages=[{"role": "user", "content": "..."}]这种结构,学习成本几乎为零。第二,OpenAI 的 SDK 生态非常成熟,Python、Node.js、Java 都有维护良好的官方客户端,业务团队不用换 SDK 就能接入 litellm。第三,判断"有没有兼容 OpenAI"成了评估大模型服务商接入成本的第一直觉,很多开源模型厂商(尤其是通过本地推理框架暴露的)都会做一层兼容层。
litellm 内部做的事情,是把用户的统一请求转换成各家供应商真正需要的形式。比如某家的参数名不是temperature而是temp,某些模型不支持top_p,这些细节都会在路由层被处理掉。转换完成之后,它再把供应商的返回结果、错误信息、使用量统计全部映射回统一格式。
2.2 虚拟 API Key 与多租户隔离
litellm 代理里有一对容易混淆的概念:master key 和虚拟 key。master key 是整个代理的管理员密钥,相当于机房总钥匙,能做配置、发 key、看日志。虚拟 key 是发给各业务方使用的,相当于发给不同楼层住户的门禁卡。
每次可以用一组独立的虚拟 key 对应一个业务线。你在给 A 部门发 key 时,可以顺手给它设定:
- 每分钟最多调用 100 次(RPM)
- 每月最多消耗 200 美元
- 只能访问指定模型,比如只允许用开源模型,不允许碰闭源付费模型
这样做的好处非常直接。各部门不再需要直接持有上游供应商的密钥,密钥泄露风险从"一个 key 满天飞"收敛成"一个临时虚拟 key 最多影响某个子项目"。一旦发生异常,管理员可以直接吊销对应 key 而不影响其他业务。对账的时候,litellm 在每次请求中记录了虚拟 key 的归属,月底导出日志就能清楚看到哪个部门产生了多少调用量、多少 token。
这个设计本质上就是多租户隔离。哪怕团队只有三五个人,我也建议至少按环境分 key:开发环境一个 key、测试环境一个 key、生产环境一个 key,后续排查问题时能迅速定位请求来源。
2.3 路由、重试与 fallback 机制的逻辑
接多家的核心场景之一,就是"主模型挂了自动切备用模型"。litellm 里可以用fallbacks配置实现:比如请求发给gpt-4o,如果它连续超时或返回限流错误,litellm 会自动把同一次请求转发给备用的claude-3-5-sonnet。对于业务侧来说,它感知到的只是多等了几秒,完全不用看用户看到 "Model not available" 的白屏。
路由能力还不止 fallback。你可以根据部门、请求来源、模型名称做灰度分配。比如新模型上线时,只让内部测试团队通过特定虚拟 key 访问 5% 的流量,等测试稳定了再逐步放大,这些策略都可以通过配置规则实现。
重试策略方面,litellm 允许你配置单次尝试超时时间、最多重试几次、重试之间退避多久。有一个细节值得注意:不是所有请求都适合自动重试。如果是实时聊天场景,重试一次可能意味着用户多等 5 秒,体验已经变差;如果是离线批量任务,多尝试几次则几乎无成本。所以配置重试参数时要想清楚业务场景,而不是无脑调大次数。
2.4 成本追踪与预算强控
预算能力是 litellm 在生产环境里特别实用的部分。它在每次请求完成后会根据模型单价估算 token 费用,按虚拟 key、团队、模型维度做累计。你可以在配置里给某个虚拟 key 设置max_budget,一旦超过限额,后续请求直接被拒绝,返回一个预算超限的错误。这在防止内部员工误用大模型导致巨额账单的场景里几乎是救命稻草。
我见过太多"本来只想实验一下,结果因为一个循环漏写了 max_tokens 限制,跑了两个小时,月底账单多出几千块"的故事。litellm 的预算强控不一定能让账单完全消失,但它至少能在失控出现时提前踩刹车。配合日志里的 token 统计,你也能看清哪些模型性价比高、哪些调用方消耗最大,从而做资源优化。
成本追踪还有一个容易忽略的价值:给模型选型提供数据。很多团队拍脑袋决定"用便宜的模型",但便宜模型如果频繁触发重试,实际成本可能更高。litellm 把每一笔调用都记录下来之后,你就能算出"真正有效的成功请求平均成本"是多少,这才是决策依据。
3. 实操记录:10 分钟跑通一个 litellm 代理
3.1 环境准备与安装
我建议在 Python 3.9 以上的虚拟环境里操作。先创建并激活虚拟环境,然后执行:
pip install 'litellm[proxy]'这个命令会装好 litellm 本身以及跑代理服务需要的一堆依赖。如果你想尽量隔离,也可以放在 Docker 里跑,后面我会单独提 docker 方式。
安装完成后,先把上游供应商的密钥配好。litellm 支持从环境变量读取密钥,常见的做法是在项目根目录创建.env文件:
OPENAI_API_KEY=sk-your-openai-key ANTHROPIC_API_KEY=sk-ant-your-anthropic-key注意,这些是上游供应商的原始密钥,不要直接写在 config.yaml 里。让配置文件和密钥分离,是为了安全,也便于后续切换供应商时只改环境变量,不改代码和配置。
3.2 编写最小 config.yaml
litellm 代理的核心配置是一个 YAML 文件。先给一个最小可用的版本,后面我会逐项解释:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY rpm: 100 - model_name: claude-3-5-sonnet litellm_params: model: anthropic/claude-3-5-sonnet api_key: os.environ/ANTHROPIC_API_KEY rpm: 100 litellm_settings: drop_params: true fallbacks: - {"gpt-4o": ["claude-3-5-sonnet"]} general_settings: master_key: sk-1234这里做几件事:
model_list定义这个代理能向外转发哪些模型。model_name是对外暴露的名字,业务侧通过这个名字来请求;litellm_params.model是真正的上游模型标识,前缀openai/、anthropic/决定走哪家供应商。api_key设为os.environ/OPENAI_API_KEY,意思是从环境变量读取,不写明文。rpm是每分钟请求数上限,这是给自己一个保护,防止某个调用方把上游打满。litellm_settings.drop_params设为true,表示如果上游模型不支持请求里某个参数,就直接丢弃而不是报错。fallbacks配置故障转移:gpt-4o失败时自动尝试claude-3-5-sonnet。general_settings.master_key是代理的管理员密钥,后续创建虚拟 key 和管理接口都要用它。
3.3 启动代理并验证连通性
配置文件准备好之后,启动代理:
litellm --config config.yaml --port 8000看到Uvicorn running on http://0.0.0.0:8000这类日志就说明服务起来了。先别急,用一个最简单的 curl 验证:
curl http://localhost:8000/v1/models -H "Authorization: Bearer sk-1234"如果配置正确,这里会返回你在model_list里定义的两个模型名。接下来发一个聊天补全请求:
curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-1234" \ -d '{ "model": "gpt-4o", "messages": [{"role": "user", "content": "你好,请用一句话自我介绍"}] }'正常的话,你会收到一个结构和 OpenAI 官方完全一致的响应,但真正的请求已经被 litellm 转发到了上游。到这一步,代理服务的最小链路已经通了。
3.4 生成虚拟 key 并验证预算限制
master key 只是管理入口,我强烈建议不要直接把 master key 给业务方。通过管理接口生成一个虚拟 key:
curl http://localhost:8000/key/generate \ -H "Authorization: Bearer sk-1234" \ -H "Content-Type: application/json" \ -d '{ "models": ["gpt-4o"], "max_budget": 0.5 }'返回内容里会有一个key字段,那才是给业务代码用的虚拟 key。注意max_budget是美元,我故意设成 0.5 美元,方便接下来验证预算强控机制。
用拿到的虚拟 key 连续发几次请求,当累计成本超过 0.5 美元后,再请求会直接收到预算超限的错误。这个验证过程最好做一次,因为生产环境的预算事故往往不是因为配置漏写,而是因为"配了但不知道会不会生效"。亲自把 key 打爆一次,你才知道超限后的返回长什么样,下游系统也好针对它做处理。
4. 生产环境接入:业务代码零侵入改造
4.1 把现有 OpenAI SDK 指向 litellm
接入了 litellm 代理之后,业务代码的改动可以小到只有两行:改base_url,改api_key。拿 Python 的 OpenAI SDK 举例:
from openai import OpenAI client = OpenAI( base_url="http://localhost:8000/v1", api_key="sk-litellm-virtual-key" ) resp = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "测试"} ) print(resp.choices[0].message.content)这里面的model参数用的是你在 litellm 里定义的model_name。哪怕你之前用的是原生 OpenAI SDK,这套代码也几乎不用改。
这样做的好处是"切换模型"对业务团队是透明的。想从gpt-4o换成claude-3-5-sonnet?不需要发版,只需要在 litellm 的配置里把model_name指向的litellm_params.model改掉,或者通过 fallback 配置让它在出问题时自动切换。业务侧看到的名字始终是gpt-4o,但实际背后是谁在响应,只有代理知道。这一点在商务谈判、模型选型调整时特别有用,你不需要为了换模型去跟所有下游团队协调发版节奏。
4.2 流式输出与参数兼容性的两个坑
接入过程中最容易出的问题反而是流式输出。很多即时对话场景都要求流式响应,前端要一个字一个字往外蹦。用 OpenAI SDK 时,只需要加上stream=True参数,然后遍历stream对象。在 litellm 代理场景下,这个流程可以正常工作,但有两个坑。
第一个坑:某些上游模型不支持流式,或者流式格式不一致。这时候drop_params: true可能不够,你需要在代码里做一次降级:如果流式请求失败,可以考虑用非流式请求拿完整内容,再一次性返回。虽然体验差一点,但至少用户能看到回复。
第二个坑:如果代理上游设置了重试,流式请求一旦中途断掉,重试可能会让用户看到一句话被重复输出两遍。我建议对实时生成类场景,要么关闭流式请求的上游重试,要么在应用层自己控制"只显示第一次成功返回的内容",不要让重试逻辑和流式叠加。
参数兼容方面,还要注意 max_tokens 和 max_completion_tokens 这类参数在不同供应商之间的差异。有些模型max_tokens代表着总输出长度上限,有些则代表"本次请求额外生成的最大 token 数"。如果用户侧已经按某一家厂商的习惯传了值,经过 litellm 转发后语义可能有细微偏差,非常容易导致响应被截断或报错。遇到这种抽象差异,我的做法是在 litellm 的上游模型参数里固定一个合理的max_tokens,而不是让每个请求自己传。
4.3 容器化部署与日志审计
生产环境一般不会直接用裸进程跑,Docker 是最常见的宿体。一个最小可用的 Docker 部署是这样的:
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY config.yaml /app/config.yaml EXPOSE 8000 CMD ["litellm", "--config", "/app/config.yaml", "--port", "8000"]然后用 docker-compose 编排,挂载配置和日志目录:
version: "3.8" services: litellm: build: . ports: - "8000:8000" env_file: - .env volumes: - ./config.yaml:/app/config.yaml - ./logs:/app/logs restart: unless-stopped日志这块要特别重视。litellm 的请求日志里通常包含虚拟 key 标识、模型名、prompt 和 response 的 token 数、延迟等信息。如果是面向 C 端用户的应用,我建议在进入 litellm 之前就通过中间件打上 request_id,把业务侧的用户标识和代理侧日志关联起来,这样后续排查"某个用户为什么 API 报错"会非常省力。注意不要直接记录 prompt 全文到日志,尤其是可能包含隐私或敏感内容的场景,脱敏再做审计。
5. 常见问题与排查技巧实录
5.1 认证报错 401 / 403
litellm 代理返回 401 或 403,绝大多数情况是密钥不对。先确认你调用时用的到底是 master key 还是虚拟 key;master key 虽然在管理接口上权限很大,但部分版本里它不会被识别为业务请求密钥,一定要用/key/generate生成的虚拟 key 来发业务请求。
如果确认用的是虚拟 key 依然 403,去查虚拟 key 的models授权列表。我只给虚拟 key 授权了gpt-4o,但你请求里写的 model 是claude-3-5-sonnet,代理会直接拒绝。这个问题在你把 key 发给业务方之后经常出现,因为对方可能习惯性保留了旧配置。
5.2 请求超时与上游无响应
碰到请求长时间不返回,先别急着调大超时。打开 litellm 的日志,看它到底卡在哪一步:是 DNS 解析慢,还是上游服务本身响应慢?经常出现某个模型偶尔超时的情况,多半是上游做负载均衡时把请求打到了不健康的节点。这种场景下,我建议优先配置 fallback 而不是死等超时。
超时参数也要分层设置:给 litellm 与上游之间设一个合理超时(比如 60 秒),给客户端与 litellm 之间设一个稍大值(比如 90 秒),避免用户在客户端等太久。更精细一点的做法,是把"重试次数"和"单次超时"控制在"用户可接受的等待时间"内,比如总等待上限 30 秒,那就设单次 10 秒、重试 2 次,而不是傻等 60 秒再重试。
5.3 预算统计对不上账单
这是使用 litellm 生产环境后几乎一定会遇到的问题:litellm 计费和上游账单对不上。大部分情况不是它算错了,而是口径不同。
litellm 的计费预估依赖模型定价表,新模型、新价格出来后,如果 litellm 版本没更新,用旧价格估算就会差不少。另外,流式请求的 token 统计在代理端是"增量累计"的,不同版本对usage的汇总时机不一样,可能会少记或多记一部分。我的建议是:litellm 的统计用来做趋势分析和超大额预警,月底对账还是以上游账单为准。别指望预算系统帮你做到一分钱不差,它的价值在于及时发现"今天消耗突然翻倍"这种异常。
5.4 fallback 没有生效
fallback 没生效,先查两件事:第一,fallback 的目标模型是否在model_list里并且rpm配额没用完。如果备用模型每分钟只能跑 10 次,主模型一挂,备用模型瞬间被并发打满,fallback 看起来就像"没生效"。第二,错误类型是否匹配。有的 fallback 规则只对超时网络错误生效,有的对限流生效,如果上游返回的是 500 但不在这条规则匹配范围内,自然不触发。
还有一个很反直觉的细节:如果请求 payload 里有上游不支持的参数,fallback 到另一家后也可能因为参数非法再次失败。所以在 fallback 链条上,尽量都开drop_params: true,免得因为一个多余参数把整条逃生通道堵死。
5.5 流式响应乱码或半截
遇到过中文乱码的,先看上游返回的编码。很多供应商默认返回 UTF-8,但个别旧接口用charset=iso-8859-1导致乱码。litellm 层面能做的有限,建议在上游供应商控制台检查响应编码,或在上游 API 请求头里强制指定Accept-Encoding。
流式输出半截是另一类问题:可能不是乱码,而是前端在消费流时遇到了分片不完整的数据。litellm 在重试之后,流的起点可能不是从最初开始,如果代码里对每个 chunk 做了解析并注入了某个变量,中间断开过的流会让变量残缺。还是那句话,实时流式场景我不建议开上游重试,宁可让请求失败,也不要让用户看到两份内容拼在一起。
| 现象 | 常见原因 | 优先排查项 |
|---|---|---|
| 401 / 403 | 密钥类型错误、虚拟 key 未授权模型 | 确认用的是虚拟 key,检查 key 的 models 列表 |
| 超时 | 上游慢、网络抖动 | 看日志卡在哪一层,配置 fallback |
| 预算统计不准 | 定价表过旧、流式统计口径 | 以趋势分析为主,月底以上游账单为准 |
| fallback 失效 | 备用模型 rpm 打满、错误类型不匹配 | 查备用模型配额、查规则匹配范围 |
| 乱码/半截流 | 编码问题、重试打断流 | 改上游编码,重试机制仅限非流式 |
6. 说实话,litellm 也不是万能的
用了挺长时间 litellm 之后,我觉得它的边界也很值得说清楚。它解决了"多模型接入"这个高频痛点,但如果你对延迟有极度苛刻的要求、或者你的请求模式非常特殊(比如私有化部署、完全离线的推理服务、需要深度定制流控算法),那自研网关的灵活性确实更高。litellm 给你的是一套良好的通用能力,但"通用"本身就意味着它在某些极端场景下不是最优解。
我个人的使用建议是:先在自己项目里用 SDK 形态跑几个模型做对比,确认 litellm 的参数转换和计费口径符合你的预期;一旦需要多人协作、多业务线共享模型资源,就果断上代理形态。以后升级 litellm 版本时,记得先看看更新日志,它迭代速度很快,配置项偶尔会调整,别在升级当天再手忙脚乱翻文档。
如果说有什么最后想叮嘱的,那就是把"密钥安全"四个字放第一位。master key 是你整个模型网关的控制中枢,丢了比丢一个业务数据库密码还麻烦。所有能用虚拟 key 的场景就不要用 master key,该吊销的 key 及时吊销,该设的预算一分不要省。这些习惯养成之后,litellm 会变成你 AI 应用里特别省心的一层基础设施,它不会替你决定用哪个模型,但能让你随时换模型——这份自由度,在模型迭代这么快的行业里,本身就是最大的竞争力。