1. 为什么项目里需要一个独立的模型接入层
从一开始只用一家大模型服务,到后来项目里同时集成三家、五家……我相信很多做AI应用的人都会经历这个过程。一开始只是图方便,哪家便宜好用就切哪家,但等到上线以后才发现,代码里到处是不同SDK的调用方式,配置参数五花八门,密钥分散在各处,出了问题根本说不清是模型的问题、网络的问题还是自己参数传错了的问题。
litellm就是为解决这一堆杂事来的:它把五花八门的模型接口统一成一个入口,以一套通用的请求格式向上层应用提供服务,路由、重试、鉴权、计费统计这些脏活累活都收拢到网关层来做。如果你正在开发AI应用,或者需要在一个系统里同时接入多家模型服务、本地模型,用一下它,大概率会省下不少返工的时间。
1.1 接入多家模型时那些让人头大的事
先说说我自己的体会。以前做模拟项目X的时候,需求很简单:根据用户输入生成一段文案。当时只接了一家API,代码写到一半,同事们开始讨论要不要加入另一家模型做对比评测。于是问题来了——每家服务商的SDK风格完全不同,有的用create_completion,有的用generate,连messages的结构都有细微差别。为了支持两个服务商,我不得不写一个适配层,把内部数据结构分别翻译成两家API的格式。
这还只是第一步。模型服务商各自的参数各不相同,温度、最大token数、超时控制、重试机制的写法都不一样;密钥管理也乱,测试时密钥直接写在代码里,上线前才改成环境变量。更麻烦的是限流策略:某家模型免费额度高但容易触发限流,另一家稳定但贵,我需要根据场景自动切换,甚至同一请求失败后自动换一家重试。这些逻辑如果全写在业务代码里,代码会越来越臃肿,而且换一个模型服务商就要重新发布一次。
线上的费用统计也是痛点。不同服务商的后台统计口径不一样,有的是按token计费,有的是按请求数计费,还有的是按时间段套餐,想算清楚这个月每个功能模块到底花了多少钱,几乎要手动导各种报表再拼起来。做过一次之后我就明白了:AI应用真正能做好,模型能力只是其中一环,接入管理、成本控制、故障切换这些基础设施问题必须体系化解决。
1.2 litellm 解决的核心问题
litellm 本质上是一个模型网关,也叫统一接入层。所有请求先打到网关,网关再根据配置转发到实际的大模型服务商,拿到结果后原路返回。上层应用不需要知道背后是哪个服务商、用的什么格式,只需要按一套通用接口发请求即可。
它最核心的价值有几条:
- 统一协议:对上暴露一套接口,业务代码只认这一个标准。
- 多路由策略:同一个模型别名可以映射到多个真实端点,按权重分发、按优先级切换、失败后自动 fallback。
- 密钥集中管理:上层应用使用虚拟密钥,不直接接触各厂商的真实密钥。
- 配额与预算:可以按虚拟密钥设置 RPM(每分钟请求数)、TPM(每分钟token数)、最大消费金额。
- 日志与计费:每一次请求都有记录,可以按模型、密钥、天统计消费,不再需要自己写埋点。
这些能力拆开看,每一个都可以自己做,但合在一起并且稳定运行,就不太适合临时拼凑了。litellm 的价值就在于把这套通用逻辑做得比较完整,我们只需要填配置,不用从零造轮子。
2. 先搞清楚 litellm 的三种工作模式
很多人一看到 litellm 这个名字,以为它只是一个 Python 库,其实它不只是库,还可以作为一个独立的网关服务跑起来。理解它有三种模式,后面用起来会顺手很多。
2.1 SDK 模式:小步快跑
SDK 模式是最直接的用法,在 Python 代码里调用litellm.completion(),传入"服务商/模型名"这样的字符串,它会在当前进程内处理请求。适合写脚本做验证、跑评测、快速对比不同模型的效果。
import litellm resp = litellm.completion( model="provider-a/model-alpha", messages=[ {"role": "user", "content": "用一句话解释什么是模型网关"} ], ) print(resp.choices[0].message.content)这种方式好处是零服务依赖,装好库就能跑;坏处是每个进程都得配置密钥、处理重试逻辑,没法在多个服务之间共享网关能力。所以 SDK 模式更适合开发阶段和脚本工具,不太适合生产环境的多服务架构。
2.2 网关模式:生产环境的主角
网关模式就是启动一个独立的服务进程,通常监听本地端口。业务服务把请求发给这个网关,网关负责转发、重试、鉴权、记账。生产环境里我把所有模型的调用都收口到这一个服务上,前端应用连的是统一域名,后面模型怎么换、怎么加副本,对上层完全透明。
启动方式很简单,准备好一个 YAML 配置文件后,执行一条命令:
litellm --config config.yaml --port 4000默认情况下网关会暴露一套兼容行业通用格式的 HTTP 接口,应用侧几乎不用改代码就能从直连模型切换成走网关。这也是它最吸引人的地方:接入成本被压得很低。
2.3 嵌入现有框架、中间件
除了上面两种方式,litellm 还经常被嵌在各种开发框架和中间件里。很多主流的 Agent 框架、应用框架在底层已经做了对接,开发者只需要把框架配置里的模型接口地址指向 litellm 网关,就能用上路由、缓存、计费这些能力。
我倾向于在项目早期先把 litellm 网关搭起来,而不是在代码里直接与具体服务商耦合。这样后面不管框架怎么换、模型怎么升级,业务代码的改动量都控制在很小的范围。这不是说 SDK 模式不好,而是生产环境里网关模式能更好地隔离变化。
2.4 必须理解的几个核心概念
model_name:对外暴露的模型别名。上层应用请求时用的是这个别名,比如assistant-pro,不直接暴露真实服务商和模型ID。litellm_params:真正请求服务商时需要的信息。包括实际模型ID、API密钥、请求地址等。model_list:一个配置列表,定义了这个网关可以路由哪些模型、每个模型对应的真实端点信息。- 虚拟密钥:给上层应用分配的访问凭证,本身不携带真实厂商密钥。你可以理解成门禁卡,能进哪些房间、一天能花多少钱,都是提前配好的。
这几个概念是理解一切配置的基础。我第一次用的时候没搞懂model_name和真实模型ID的区别,配置里全写成了同样的名字,结果想切换模型却不起作用,后来才明白别名和端点是两码事。
3. 部署实操:从最小配置到跑通第一个请求
纸上谈兵没什么用,直接动手。这里我用一个最小化的例子,逐步演示从空环境到能发起请求的过程。
3.1 环境准备与安装
建议准备一个干净的 Python 3.10+ 环境,用虚拟环境隔离依赖,避免和项目里其他包产生冲突。
python -m venv .venv source .venv/bin/activate pip install "litellm[proxy]"安装完成后,先确认版本可用:
litellm --version如果输出正常,说明安装没有大问题。装的时候偶尔会遇到依赖下载慢的情况,换国内镜像源能明显提速。这一步没什么技术含量,但值得认真把环境弄干净,后面排错会省事很多。
3.2 最小配置样例
创建一个config.yaml,内容如下:
model_list: - model_name: assistant-pro litellm_params: model: provider-a/model-alpha api_key: os.environ/API_ALPHA_KEY - model_name: assistant-lite litellm_params: model: provider-b/model-beta api_key: os.environ/API_BETA_KEY这里有一个很关键的细节:model_name是上层请求用的名字,litellm_params.model才是真正发给服务商的模型ID,格式通常是“服务商标识/具体模型名”。如果你的某个模型不需要密钥,比如本地模型服务,那么api_key这一行可以去掉。把真实密钥放在环境变量里,而不是直接写进配置,这个习惯我从一开始就坚持,因为配置文件经常会传到仓库、同事电脑里去,密钥一旦泄露就麻烦了。
启动服务:
export API_ALPHA_KEY=你的密钥 export API_BETA_KEY=你的密钥 litellm --config config.yaml --port 4000看到日志里出现服务地址之后,就可以请求了。需要注意:启动网关时如果没有指定主密钥,它会自动生成一个,首次启动的日志里会有提示,保存好这个密钥,后续管理要用。
3.3 模型路由与多副本配置
实际项目中,同一个模型往往不会只配置一个端点。比如你希望流量大部分打到主模型,小部分打到备用模型,或者在主模型超时后自动切到备用模型,这时候就要配置路由策略。
model_list: - model_name: assistant-pro litellm_params: model: provider-a/model-alpha api_key: os.environ/API_ALPHA_KEY - model_name: assistant-pro litellm_params: model: provider-b/model-beta api_key: os.environ/API_BETA_KEY router_settings: routing_strategy: usage-based同一个model_name可以出现多条记录,网关会把这组记录看作一组可用端点。routing_strategy的取值决定怎么挑选端点,常见的策略包括:
simple-shuffle:每次请求随机挑选一个可用端点,负载均衡用。usage-based:根据当前使用量动态分配,尽量让两个端点压力接近。- 自定义权重:在配置里为每个端点设置权重,控制流量比例。
实际测试中我发现,如果两个端点能力差异明显,比如一个是快而便宜的小模型,一个是慢但质量高的旗舰模型,按业务场景区分会更合理。可以在请求参数里指定不同的model_name,让不同接口走不同模型;单纯依赖路由器做压力均衡反而不太容易达到理想效果。
失败切换是另一个常用能力。给某个模型追加一个fallbacks配置,当主链路出错并重试无效后,网关会自动转去请求备用模型。这比在业务代码里手写 try-except 再换模型要干净得多。不过要留意:切换模型可能带来回复风格的差异,接口层能兜底,但产品体验上最好有预期。
3.4 密钥管理与访问控制
默认情况下,网关只校验你启动时指定的主密钥。为了让不同团队、不同应用使用不同的权限范围,可以为它们生成多个虚拟密钥。
curl http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-master-key" \ -H "Content-Type: application/json" \ -d '{ "models": ["assistant-pro"], "max_budget": 20, "rpm_limit": 30 }'这个请求创建了一个虚拟密钥,它只能访问assistant-pro这个模型,每月最多消费20个单位,每分钟最多30次请求。返回结果里会有一个新的密钥字符串,分发给对应的应用或同事使用即可。后续可以随时吊销,不会影响其他应用。
密钥管理的重要性我觉得再怎么强调都不过分。曾经有一个测试应用的密钥没有加任何限制,结果某天一个死循环脚本把月度预算几乎耗尽。从那之后,我给所有虚拟密钥都设置了 RPM 和预算上限,宁可平时麻烦一点,也好过月底猛吃一惊。
4. 用标准接口对接业务
网关本身的意义在于把多个服务商统一成一套接口,因此业务对它的调用方式应该尽量标准。litellm 网关对外开放的接口与社区通用的接口风格保持一致,所以任何只要会发 HTTP 请求的程序都能接入。
4.1 聊天对话接口
最常用的是对话补全接口。下面是一个用 curl 发起的请求示例:
curl http://localhost:4000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-你的虚拟密钥" \ -d '{ "model": "assistant-pro", "messages": [ {"role": "system", "content": "你是项目助手"}, {"role": "user", "content": "帮我整理一下今天的会议要点"} ], "stream": false }'如果stream设为true,网关会以流式方式分段返回结果,体验更接近聊天机器人。第一次对接的时候建议先用非流式调试,确认链路通畅后再切流式,否则日志里错误信息被一堆流式数据掩盖,排错会比较痛苦。
4.2 用 Python 发起请求
不依赖任何特定服务商 SDK,直接用标准 HTTP 库也是完全可以的:
import requests resp = requests.post( "http://localhost:4000/v1/chat/completions", headers={ "Authorization": "Bearer sk-你的虚拟密钥", "Content-Type": "application/json", }, json={ "model": "assistant-pro", "messages": [ {"role": "user", "content": "你好"} ], }, timeout=60, ) data = resp.json() print(data["choices"][0]["message"]["content"])这里的关键点是:业务代码只面对http://localhost:4000这一个地址。不管后面是A服务商、B服务商还是本地模型,对上层来说没有任何区别。网关会返回一个符合通用接口规范的 JSON 结构,解析方式永远一样。
4.3 列表、健康检查与调试接口
想知道当前网关提供哪些模型,可以请求列表接口:
curl http://localhost:4000/v1/models \ -H "Authorization: Bearer sk-你的虚拟密钥"返回结果会列出所有配置好的模型别名以及对应信息。这个接口在调试阶段非常有用,配置完新的模型,先到这里确认是否注册成功,再去写业务调用代码。
健康检查接口也很简单,直接请求/health或根路径即可。我一般会在部署脚本里加一步健康检查,只有返回正常才开始接入流量,避免服务半启动状态下让用户看到报错。
5. 进阶能力:缓存、重试与观测
模型网关的优势不只是统一接入,把基础设施能力集中起来之后,很多横切关注点也顺手解决了。这里重点说缓存、重试和观测。
5.1 缓存策略
在真实项目里,存在大量语义相近的重复请求,尤其是一些页面加载时需要获取固定的话术、摘要、结构化文案,每次调用模型不仅慢,而且烧钱。litellm 支持在网关层做缓存,命中的请求直接返回历史结果,不再打到模型服务商。
缓存可以配置在内存里,也可以使用外部缓存服务。配置方式类似:
litellm_settings: cache: true cache_params: type: memory ttl: 3600ttl是过期时间,单位是秒。内存缓存适合单机部署,简单省事;如果网关会扩容到多实例,建议换成共享缓存,避免不同实例各存一份导致命中率低下。缓存粒度上可以根据请求参数控制,比如带temperature或top_p变化时应该视为不同请求,避免缓存错乱。我在调低温度参数做同类请求对比时踩过这个坑,后来对缓存键做了细化,问题就消失了。
5.2 超时、重试与失败切换
模型请求是典型的不可控外部调用:大模型响应慢是常态,偶尔还会连接超时、返回5xx错误、触发限流。litellm 允许在配置里设置统一的超时和重试参数:
litellm_settings: timeout: 60 num_retries: 3 retry_after: 5num_retries表示失败后最多重试几次,retry_after是每次重试的间隔秒数。对请求不太敏感的场景,重试能显著提升成功率;但如果你的业务有写操作、会重复提交任务,重试就可能造成重复执行。这种情况下要格外谨慎,最好在业务层做幂等控制,或关闭重试,把失败信息直接返回给上游自行处理。
失败切换和重试的区别在于,它不是重新请求同一个模型,而是换一个模型端点。前面提到的fallbacks配置就是干这个用的。生产环境里我会把“本地备用模型”和“付费稳定模型”搭配使用,优先用便宜档位,一旦不可用再切备用,成本和稳定性之间取一个平衡。
5.3 用量与费用观测
网关记录每一次请求的详细日志,包括模型、消耗的token数、延迟、成功还是失败以及费用估算。查询消费记录的接口也很方便:
curl http://localhost:4000/spend/logs \ -H "Authorization: Bearer sk-master-key"返回结果里包含时间、API密钥、模型、token数、成本等字段。我习惯每晚定时拉取这些数据,按模型聚合一下,就能直观看到每个业务线的成本趋势。以前靠各个服务商后台手工统计的日子,说真的不愿意再回去了。
观测方面,litellm 还支持把日志导出到外部监控体系,配置好以后就可以在统一看板上看到调用量、错误率、延迟分布。对于日请求量过万的系统,这个能力几乎是必备的,否则线上出了性能问题,连从哪个环节入手排查都不知道。
6. 实操中容易翻车的几个细节
配置和使用本身不难,但有些问题藏得比较深,不实际跑一阵子很难发现。我把踩过的坑整理了一下,希望对你有帮助。
6.1 模型ID写错是头号问题
很多新手第一次配置时容易把model_name当成真实模型ID直接填进litellm_params.model,结果返回 404 或模型不存在的错误。记住,model_name是给上层应用看的,litellm_params.model才是真正发给服务商的标识,必须按“服务商/模型名”的格式写。遇到问题时先到启动日志里看转发到哪个真实地址了,而不是盲目加大重试次数。
6.2 虚拟密钥管理的权限陷阱
虚拟密钥是有作用域限制的。有的密钥只能访问某些模型,一旦访问其他模型会报权限错误。我见过同事用一个只有assistant-lite访问权的密钥去调assistant-pro,排查了半天,最后才发现是密钥作用域的问题。建议给密钥命名时带上用途和归属信息,例如app-web-prod、batch-analysis-dev,管理起来一目了然。
6.3 重试带来的重复执行副作用
重试机制处理网络错误很有效,但如果上游模型请求已经成功、只是响应超时,网关重试时就会产生一次重复消费。对于纯文本生成场景,多花一次钱可能还能接受;但如果你在用模型做自动执行任务,比如生成文件、发消息,就会造成业务上的重复操作。对这种场景,要么关掉重试,要么在业务层引入请求ID和幂等控制,保证同一个请求只被执行一次。
6.4 容器化部署的环境变量与持久化问题
把网关容器化部署时,最容易忽略的是环境变量和持久化。密钥相关配置要全部改成环境变量的形式,配置文件本身可以用挂载方式注入。数据库或日志文件需要持久化,否则容器重启后历史消费记录、密钥配置会丢得一干二净。另外,多个副本共享同一个外部缓存和数据库很重要,否则网关实例之间数据不同步,虚拟密钥和成本统计都会出现偏差。
6.5 长期运行的内存与日志问题
网关长期运行,如果开启了缓存且没有 TTL,内存占用会缓慢上涨。给缓存设置合理的过期时间是一条基本策略。日志量也要关注,默认控制台输出在全量请求下会非常可观,时间长了还可能撑满磁盘。我一般会把日志等级调低、把关键请求日志输出到专门的文件,并配合日志轮转策略,保证不会因为日志写满存储把服务拖垮。
6.6 配置热更新不一定即时
部分配置的修改并不会立马生效,尤其是路由策略和模型列表的变更,经常需要重启网关才能加载。所以我在改配置前后都会先查看当前模型列表,确认新配置已经注册成功,再开始测试流量。上线前做一次灰度检查永远不过分,千万别改完配置直接切全量流量。
| 问题场景 | 典型表现 | 解决思路 |
|---|---|---|
| 模型ID格式错误 | 请求返回模型不存在 | 检查litellm_params.model是否为“服务商/模型名” |
| 密钥作用域不足 | 某些模型请求被拒绝 | 查看虚拟密钥允许的模型范围 |
| 重试导致重复执行 | 任务重复完成 | 业务层幂等控制或关闭重试 |
| 容器重启丢数据 | 密钥、统计记录消失 | 持久化挂载,数据库独立保存 |
| 内存持续上涨 | 运行数天后响应变慢 | 缓存设置 TTL,及时清理 |
| 配置修改不生效 | 新模型列表为空 | 重启服务,确认加载完成 |
7. 我的一点使用习惯
如果你正准备在项目里引入 litellm,我建议先别急着上很复杂的策略。用最小配置搭好网关,让一个业务模块先切过来,跑通之后再逐渐增加路由、缓存、预算这些能力。我自己的项目就是从单模型代理开始,用了一个月才慢慢把多模型路由和成本统计补全。整个过程里最值的投资,是把所有模型密钥的配置从各个业务服务里抽离出来,集中放到网关层。这带来的最直接影响是:业务代码终于不再跟着某个模型服务的文档走了。后面再换模型、再加新的服务商,都只是配置层面的改动,我只需要重新启动网关,不用动业务代码。
踩过几次坑之后,我更加确信一件事:模型能力本身会持续变化,但一个稳定的接入层能让你始终从容应对这些变化。