做 AI 应用落地这两年,我最大的感受是:模型调用方式变化太快,但业务代码想稳定下来。今天项目里需要的可能只是简单对话,明天就想换成另一个模型,甚至想同时用几个模型做效果对比。每次换供应商都要翻一遍文档、改一遍适配、再提心吊胆地发布,这种日子真的不好过。
后来我接触到 litellm 这个项目,有种被捞了一把的感觉。它做的事情可以用一句话讲完:把模型供应商的差异挡在业务代码之外,用一套统一接口去对接不同模型源,顺带把路由、重试、预算、日志这些“网关该干的活”全打包了。这篇文章基于我实际用下来的经验,讲清楚它怎么选、怎么部署、怎么避坑,适合正在做 AI 应用集成,或者想让团队内部多个项目统一管理模型资源的开发者参考。
1. 为什么需要一层模型网关:从代码适配到访问治理
1.1 分散接入带来的隐藏成本
先讲一个很常见的场景。公司内部通常不是只有一个应用在调大模型,而是有好几个团队各自在做智能客服、内容总结、数据分析助手。初期大家各干各的,谁需要模型谁就去开通一个账号,直接把密钥配到自己服务的环境变量里。
这个阶段看起来很快,但成本是后置的。第一个麻烦是接口差异:A 厂商返回的消息结构、错误码字段、鉴权方式,和 B 厂商完全不同。业务方为了兼容两家,往往要写两套 adapter,每个字段都要手动映射。第二个麻烦是故障放大:某天 A 厂商某个模型开始限流,你的应用报错,你只能临时改配置、发版本,等新版本生效后再观察,整个过程少说半小时,用户已经流失了一部分。第三个麻烦是费用失控:每个团队都拿着原生密钥,管理员根本说不清这个月钱花在哪个项目、哪个功能上,账单出来只能摊大锅饭。
为什么不在代码里直接封装一个统一库?很多人第一反应都是这个。我也试过,问题在于库的维护节奏很难跟上业务。你的团队换人了、供应商改接口了、新模型上线了,都需要有人改封装代码,而且每个业务的代码仓库都要同步更新。这本质上还是“点对点”连接——只是把散落的适配代码集中到了一个包里,并没有解决治理问题。
真正合适的做法,是在业务和各家模型供应商之间放一个独立的网关服务。这个服务注册了公司的模型资源,对外提供一套稳定接口,对内管理密钥、路由、预算和日志。一边对接各种供应商,一边对接公司内部所有应用。litellm 就是这个定位。
1.2 网关带来的不只是一层封装
网关和代码封装最根本的区别在于,它把“治理能力”从业务代码里抽离出来了。业务方不再需要知道上游是哪家、模型真实名称是什么、密钥存在哪里。他们只认识一个东西:模型别名。
我把两种方式放在一起对比过,差别非常直观:
| 对比维度 | 直接在业务代码里适配 | 通过 litellm 网关接入 |
|---|---|---|
| 切换模型 | 改代码、发版、回归测试 | 改网关配置,业务无感知 |
| 密钥管理 | 每个服务各自存,泄露面大 | 网关统一托管,业务用虚拟密钥 |
| 成本追踪 | 基本靠猜,账单混合 | 按密钥、团队、项目拆分统计 |
| 故障转移 | 需自研重试和降级逻辑 | 网关内置重试、冷却、备用模型 |
| 权限控制 | 很难精细分配 | 可按团队建密钥并设置额度上限 |
这里有一个关键认知:网关承载的不是“转发”任务,而是“策略”任务。它需要决定请求到底走哪个模型、失败后怎么切换、谁有权限调用、最多能花多少钱。这些策略如果散落在各个服务里,执行效率和一致性都无法保证。集中到网关上之后,策略的变更不再需要业务方配合,而是平台侧的一次配置变更。
我用过之后比较明显的感受是:业务流程被简化了。应用侧只管拿到用户输入、构造 messages、发起请求、消费结果。至于这个模型今天是否健康、要不要换个备用模型、这个密钥的预算还剩多少,都是网关的事情。
2. 拆解 litellm 的核心能力:路由、降级与预算控制
2.1 统一接口与模型路由:业务侧只认模型别名
litellm 对外暴露的是业界常见的 OpenAI 兼容格式,这也是很多模型厂商都在靠拢的接口风格。这么做的好处很明显:团队内部已有大量代码是基于这个风格写的,迁移到网关时不需要重写。
实际配置时,核心是model_list。你可以把多个真实模型映射到同一个业务别名下,业务方请求时只写别名,由网关决定实际路由到哪家。
model_list: - model_name: production-assistant litellm_params: model: provider-a/xxx-large api_key: os.environ/PROVIDER_A_KEY - model_name: production-assistant litellm_params: model: provider-b/yyy-sonnet api_key: os.environ/PROVIDER_B_KEY上面这段的意思是:业务侧只需要知道production-assistant这个别名。网关内部维护了两个模型源,一个来自“厂商 A”的旗舰模型,一个来自“厂商 B”的次旗舰模型。请求进来之后,网关会按照权重、健康状态和配置策略选择一个上游转发。如果某个上游连续失败,请求会自动倾斜到另一个上游。
这种“按业务含义命名模型”的做法,极大降低了模型迭代对业务的影响。业务代码里不再出现具体模型名,因为具体型号会随版本频繁变化,而业务含义相对稳定。网关配置里可以随时调整模型的权重、加入新厂商,业务接口不需要改。
2.2 负载均衡、重试与自动降级
多个模型源同时挂在一个业务别名下面时,网关就具备了负载均衡的能力。你可以给不同模型设置权重,比如希望 70% 流量走“厂商 A”,30% 走“厂商 B”,用于灰度验证新模型或分摊成本。
model_list: - model_name: production-assistant litellm_params: model: provider-a/xxx-large api_key: os.environ/PROVIDER_A_KEY model_info: weight: 7 - model_name: production-assistant litellm_params: model: provider-b/yyy-sonnet api_key: os.environ/PROVIDER_B_KEY model_info: weight: 3光有负载均衡还不够,生产环境一定会遇到上游抖动、限流、局部故障。litellm 支持配置重试次数、请求超时和故障降级链路。下面的配置就是一个很典型的组合:
litellm_settings: num_retries: 3 request_timeout: 30 fallbacks: - production-assistant: ["backup-assistant"]这里production-assistant是主模型,backup-assistant需要在model_list里再定义,指向另外一个成本更低的模型。当主模型因为限流、5xx 或超时达到重试上限时,网关会自动把请求转发给备用模型。用户侧可能只感觉到多等了零点几秒,看不到任何错误提示。
这里有一个很重要的“为什么”:重试不是无限次越好。每次重试都会占用上游配额,也都会消耗时间。如果上游已经故障,重试只会让调用方更慢。所以我更建议把request_timeout设置得偏小一些,比如 20 到 30 秒,让快速失败触发降级,而不是长时间卡在一个后端上。真实生产场景里,一次慢请求比一次直接报错更让用户难受。
2.3 虚拟密钥和预算上限:把口子变成阀门
很多团队最初接入模型时,直接把厂商后台的密钥放到业务服务里,然后用到处转发、复制粘贴的方式共享给同事。这种方式最大的问题是没有边界感:不知道谁在用、用多少、能不能停。
litellm 提供了“虚拟密钥”机制。你可以用管理员密钥在网关上生成多个虚拟密钥,每个密钥可以绑定不同的预算上限、每分钟请求数上限、模型范围等。业务侧拿到的不是厂商原生密钥,而是这把虚拟钥匙;即使某个项目的密钥泄露了,你也可以单独停用,而不影响其他业务。
生成虚拟密钥的接口类似这样:
curl http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-master-xxxx" \ -H "Content-Type: application/json" \ -d '{ "name": "team-a-demo", "max_budget": 20.0, "rpm_limit": 60 }'这段请求表示:给某团队的演示应用生成一个虚拟密钥,总预算 20 美元,每分钟最多 60 次请求。超过预算后被调用,网关会直接拒绝,并有对应的错误信息。这样你就把一个“谁都能用”的口子,变成了一个按需开关的阀门。
预算控制这件事,在业务规模小的时候不容易看出价值,一旦某个内部工具开始被高频调用,月底账单可能会下你一跳。我经历过一次内部应用把月度预算跑超额的情况,排查下来是因为一个循环任务没有控制并发,每秒钟都在调用模型。加上预算上限之后,同样的问题出现时,网关会挡住后续请求,成本立刻封顶。
3. 从零部署一套 litellm 网关:配置全流程实录
3.1 安装与启动:五分钟跑通最小可用配置
litellm 的部署方式很灵活。最轻量的方式是直接用 Python 包安装:
pip install 'litellm[proxy]' export PROVIDER_A_KEY="你的厂商A密钥" litellm --config config.yaml --port 4000如果团队内已经有容器化环境,我更建议用容器方式部署,运维更省心。把配置目录挂载进去,再把 4000 端口暴露出来即可:
docker run -d --name llm-gateway \ -p 4000:4000 \ -v $(pwd)/config.yaml:/app/config.yaml \ litellm/litellm:main-latest启动后先做健康检查:
curl http://localhost:4000/health正常会返回一个 JSON 状态。看到ok之后,再调用模型列表接口确认配置是否被正确加载:
curl http://localhost:4000/v1/models \ -H "Authorization: Bearer sk-master-xxxx"这一步是很多人会跳过的环节。我建议每次改完配置都先看这个接口,它能直接反映model_list是否生效。如果发现业务别名没出现,多半是 YAML 语法有问题或者模型参数写错了。
3.2 config.yaml 三段式配置:模型、网关、全局
一份能跑起来的最小配置通常包含三块:model_list、litellm_settings、general_settings。下面是我的示例配置,删掉了敏感信息,保留了完整结构:
model_list: - model_name: production-assistant litellm_params: model: provider-a/xxx-large api_key: os.environ/PROVIDER_A_KEY model_info: weight: 5 - model_name: production-assistant litellm_params: model: provider-b/yyy-sonnet api_key: os.environ/PROVIDER_B_KEY model_info: weight: 1 - model_name: backup-assistant litellm_params: model: provider-c/zzz-medium api_key: os.environ/PROVIDER_C_KEY litellm_settings: num_retries: 2 request_timeout: 30 fallbacks: - production-assistant: ["backup-assistant"] general_settings: master_key: sk-master-xxxx database_url: postgresql://user:password@localhost:5432/litellm逐个字段解释一下。model_name是业务侧能感知的模型别名;litellm_params.model是真实模型标识,格式通常是“厂商/模型名”;api_key我建议不要明文写进配置文件,而是通过环境变量传入。model_info.weight控制两个同别名的模型之间的流量比例,数值越大承担流量越多。
num_retries表示单次请求在网关内部最大重试次数;request_timeout是网关等待上游响应的超时时间;fallbacks定义了当主模型失败后可以降级到哪些备用模型。
general_settings.master_key是管理员密钥,用来访问管理接口、生成虚拟密钥。database_url用于持久化请求日志和成本记录。刚开始测试时数据库可以不配,但如果要上线,我强烈建议接上一个,否则重启后费用数据就丢了。
3.3 业务侧接入:最小改造就能替换原逻辑
接入 litellm 网关时,业务代码的改动量很小。以前你怎么调用模型 SDK,现在几乎原样保留,只需要把地址指向网关、换一个虚拟密钥。我写一个最常见的接入示例:
from openai import OpenAI client = OpenAI( base_url="http://localhost:4000/v1", api_key="sk-网关生成的虚拟key", ) resp = client.chat.completions.create( model="production-assistant", messages=[{"role": "user", "content": "帮我总结一下这份文档"}], ) print(resp.choices[0].message.content)对比你原来的业务代码,变化的只有两处:base_url从某个厂商官方地址变成了网关地址;api_key从原生密钥变成了网关虚拟密钥。模型名直接用配置里的业务别名production-assistant,完全不需要关心它背后是哪家厂商。
这里要注意一个细节:不同模型对参数的支持度不一样。比如某些模型支持“工具调用”,某些模型暂时不支持。网关默认会把业务传的参数透传给上游;如果你在网关中开启了drop_params: true,网关会在转发前剔除掉目标模型不支持的参数,避免上游直接报错。这个配置对兼容老代码很有帮助,但也要注意,被剔除的参数可能影响输出效果,最好按模型差异单独评估。
3.4 关于流式输出与长耗时的处理
很多对话类应用依赖流式输出,希望页面像打字机一样逐字显示。litellm 网关本身支持流式转发,但你需要在调用时把stream=True传进去。业务侧代码原本如果是流式调用,换成网关后基本无需改动,只是要注意:如果配置了重试,网关内部可能已经把响应缓存,流式场景下要观察重试是否会影响体验。
长耗时的任务,比如“总结一个几百页的文档”或“批量生成内容”,更适合用异步任务处理,而不是同步等待。网关的能力集中在路由和治理上,并不是一台无限并发的 API 服务器。如果业务侧大量发起同步长请求,很容易把网关连接池占满。我的经验是:批量任务应该走异步队列,控制并发数,优先保证交互式请求的响应速度。
4. 生产环境实战记录:监控、限流与排错
4.1 可观测性:不能只把网关架起来就完事
上线网关之后,最忌讳的一件事就是“只配置、不看数据”。没有日志和监控的网关相当于一个黑盒,出了故障你连从哪查起都不知道。
litellm 的监控主要是几类数据:总请求数、失败率、上游响应延迟、虚拟密钥调用次数、预算消耗。如果接了数据库,你可以按密钥、按模型、按时间维度统计成本。这些数据是后续做模型选型、成本分摊、容量规划的基础。
我之前在排查一个问题时就吃亏过:某个业务反馈响应变慢,结果是因为上游厂商某一时段整体变慢,网关里显示该上游的 p95 延迟已经飙升,而业务方只能看到网关整体变慢。有了按上游维度的延迟指标,才能快速定位是“模型本身慢”还是“网关转发慢”,这两类问题的处置方式完全不同。
还有一点要提醒:master_key就是管理员的最高权限,务必妥善保管。建议不要把它直接写进前端代码或分享到群聊里。它的作用范围是整个网关的管理面,一旦泄露,别人可以随意生成虚拟密钥或者调整预算配置。
4.2 高频问题排查清单
把我和身边朋友踩过的问题整理成了一张速查表,生产环境排障时可以直接对照:
| 症状 | 常见原因 | 处理建议 |
|---|---|---|
| 调用返回 401 | 虚拟密钥过期、被删除,或鉴权头写错 | 重新生成密钥,检查请求头格式 |
| 大量返回 429 | 上游限流,或虚拟密钥预算耗尽 | 查看该密钥消费情况,调整预算或等待恢复 |
| 请求 503 | 所有注册上游都不可用 | 检查上游健康状态,确认 fallback 是否生效 |
| 模型一直进入冷却 | 连续超时或 5xx,触发冷却机制 | 调大冷却时间,先排掉上游故障再恢复流量 |
| 修改配置不生效 | 未重启进程,或配置被多份覆盖 | 重启服务,确认挂载的是当前这份配置文件 |
| YAML 启动报错 | 缩进错误、字段拼错、引号未闭合 | 先用本地工具校验 YAML 语法,再启动服务 |
| 日志里没有费用记录 | 未配置数据库持久化 | 接入支持的数据库,开启 spend 日志 |
每一条我都在实际项目里见过。特别是“模型一直进入冷却”这个,印象最深。有一次某上游模型不稳定,连续几次超时后网关自动把它标记为不可用,后面所有请求都挤到了备份模型。乍一看是备份模型承受不住压力,实际是上游模型已经被冷却了。这种时候不要急着调备份模型容量,先处理上游问题,再让网关恢复主模型的流量。
4.3 关于超时和重试的踩坑经验
超时配置看似简单,里面有个很容易忽略的联动关系。网关内部的request_timeout和业务侧的客户端超时,必须保证“业务侧 > 网关侧”。举个例子,如果业务侧设置 10 秒超时,而网关内部配置了 30 秒重试窗口,用户侧早就走超时逻辑了,网关却还在等上游。这个请求的最终结果已经没有任何意义,但你仍然为它支付了上游费用。
我建议的做法是:业务侧超时设置为 35 秒,网关request_timeout设置为 28 秒,重试次数控制在 2 到 3 次。这样一层一层收敛,让超时总是发生在最外层返回之前。这个顺序如果反了,排查问题会非常痛苦,因为用户看到的错误是“请求超时”,而日志里网关可能还在记录一次成功的上游响应。
另一个经验是关于并发控制。网关本身有连接池和队列,但如果业务侧开太多并发请求,网关来不及转发就会形成背压。特别是批量任务,最好在业务侧用队列限流,控制同时进行的请求数量,而不是一股脑全发到网关。一个设计良好的批量任务,应该是“1000 条任务、每秒 20 个并发”,而不是“1000 条任务同时发出”,因为大部分模型供应商都有每分钟请求数限制,打爆之后反而触发限流,整体速度更慢。
5. 从网关到平台:落地组织的技巧与建议
5.1 密钥分配与团队治理
把 litellm 放到团队层面之后,我建议第一时间把密钥体系梳理好。不要每个人共用一个虚拟密钥,那样和直接用原生密钥没有本质区别。比较好的做法是:每个团队单独生成一个虚拟密钥,通过名称区分;每个密钥绑定自己的预算上限和请求频率限制。
这个设计会让成本管理变得具体。月底看报表时,可以明确知道:智能助手项目花了多少、数据分析项目花了多少、哪个实验性项目超支了。预算控制也能自动化,不用人工盯着财报,网关会直接挡掉超额请求。
权限和流程上也要配套。新项目需要模型资源时,不要直接去申请原生模型账号,而是先向平台申请虚拟密钥。这个流程看起来很基础,但能杜绝“散装接入”再次出现。密钥泄露后补发一个虚拟密钥即可,也不会影响其他业务。
5.2 从这里还能扩展出更多能力
litellm 能做的事不只在基础的网关层面。比如响应缓存,对一些重复性较高的请求启用后,能显著降低成本;前提是要接受一定程度的命中率偏差,所以我会建议先在小范围试探,观察业务方对结果时新度的敏感度。
另一个方向是把网关作为模型效果对比的平台。同一个问题发给不同模型,统一记录响应结果,再做评测打分。这个能力对算法团队特别有价值,因为日常使用中模型效果需要持续跟踪,而不是上线时候测试一轮就固定不动。
最后还有一点,就是团队新人的上手成本。有了统一网关之后,新同学接模型需求时,不需要逐家学习不同厂商的鉴权方式和接口格式,只需要知道公司内部的统一接口标准和模型别名。这个学习成本的下降,在团队扩张时体会特别明显。
最后再分享一个我的个人习惯:第一次部署 litellm 时,哪怕只是给自己用,也建议把虚拟密钥、预算上限和数据库持久化先配上。因为事后补治理,永远比一开始就留好边界要痛苦得多。网关带来的价值不是让你多一个组件,而是让你的业务代码真正稳定下来,让模型调用这件事变得可管、可控、可复盘。