litellm 这个名字,最近在搞大模型应用开发的朋友圈子里出现频率很高。它本质上是一个开源的模型网关,解决的是接入模型 API 时绕不开的那堆杂事:不同厂商接口格式不一样、切换模型要改代码、密钥散落得到处都是、每个月的账单还得自己手工凑。litellm 把这些统一到一个入口后面,你用一套接口风格去对接各家模型,业务代码几乎不用动。
这篇文章不是官方文档的翻译,是我在几个实际项目里把 litellm 落地之后的理解和实操记录。适合正在做 LLM 应用开发、或者想给团队搭一个统一模型接入层的工程师参考。我会尽量把“为什么这么做”也讲清楚,而不只是给配置。
1. 先搞懂它是什么:模型网关的本质
1.1 为什么需要一层“统一入口”
先说一个我踩过的典型场景。最开始接模型的时候,项目里直接调某个厂商的 SDK,代码简单粗暴,没觉得有什么问题。但后来需求变了,要接第二家、第三家模型服务,甚至同一个需求里要按不同输入走不同模型,问题就来了:每家 SDK 的初始化方式不一样,参数命名不一样,就连流式返回的处理逻辑都各有各的写法。业务代码里开始出现大量if 厂商A else 厂商B的分支,每次加一个模型就要动核心逻辑,测起来也很费劲。
这本质上缺了一层抽象。你可以把模型网关想象成一个排插,墙上只有一个插座,但你有很多电器要插。排插把“插座”这个入口统一了,电器只管插上就能用,不用管墙里的电线是怎么布的。litellm 干的就是这件事:把不同模型服务商的接入差异,全部收敛在网关这一层。业务侧只面向一个固定风格的接口,模型怎么接、怎么路由、怎么兜底,全是网关内部的事。
引入这层之后,最大的收益不是省了那几行代码,而是把“模型接入”和“业务开发”两个团队关注点解耦了。业务团队可以安心写应用逻辑,模型接入的变更由一个专门的配置文件来管。后面换模型、加模型、故障切换,都不需要业务发版。
1.2 litellm 的工作方式
litellm 本身是一个常驻服务,可以用 pip 装完直接跑,也可以扔进 Docker 跑。它的核心逻辑并不复杂,一次请求的完整路径大概是这样的:
- 客户端向 litellm 暴露的 HTTP 接口发请求,路径和参数风格统一走业界常见的 Chat Completions 接口形态。
- litellm 根据配置里的模型名匹配规则,找出这个请求应该路由到哪一个真实模型服务商。
- 网关把统一格式的请求翻译成对应服务商需要的格式,带上你预先配置好的密钥,发到真实接口。
- 上游返回后,litellm 再把响应翻译回统一格式,同时记录这一次请求的 token 消耗、耗时、目标模型、调用者身份等信息。
- 如果上游失败或者限流,litellm 按配置决定是重试、切换到另一个同名校验配置,还是直接报错。
这里有一个容易被误解的地方:litellm 不是一个“内置所有模型”的服务,它自己并不跑模型,也不训练模型。它更像是一个懂各家接口协议的翻译官和交通警察,真正算 token 的还是在各家模型服务商的机房里。
1.3 适合谁用,以及什么时候不该用
不是所有项目都需要上这么一层网关,我见过把简单事情搞复杂的反面案例。下面这个表是我自己的判断标准:
| 场景 | 是否建议用 litellm | 原因 |
|---|---|---|
| 一个应用同时接多个模型服务商 | 建议 | 统一接入层价值最明显 |
| 团队里多人开发 LLM 应用,需要统一管密钥 | 建议 | 避免密钥散落在个人本地 |
| 需要集中统计每个月的模型成本 | 建议 | 自带计费记录,省掉手工统计 |
| 需要给多个内部业务方提供模型能力,且做隔离 | 建议 | 虚拟密钥天然适合多租户 |
| 单个项目、单模型、一个人维护 | 不建议 | 多一层服务就多一个维护点 |
| 高频传输超大规模文件或流式视频数据 | 不建议 | 网关更适合文本类和普通请求 |
| 需要在调用前后做大量业务定制的状态处理 | 不建议 | 定制逻辑写在业务侧更灵活 |
一句话总结:只有当“模型数量大于等于二”或者“调用的人大于等于二”的时候,网关的价值才真正体现出来。单机单模型硬上网关,多出来的那层就是在给自己找事。
2. 快速跑起来:安装与最小配置
2.1 两种安装方式
litellm 的安装很直接。本地试用或者开发调试,我最常用的是 pip 安装:
pip install 'litellm[proxy]'注意这里我建议装带 proxy 扩展的版本,因为它会把运行网关所需的额外依赖一起拉下来。只装核心库也可以,但那就只能以库的形式调用,起不了独立的网关服务。
如果是团队协作或者要部署到测试环境,我更推荐直接用 Docker 镜像,避免每台机器都去折腾 Python 环境。拉镜像跑一个最小实例非常快,这个在后面专门说。
2.2 写一份最小配置文件
litellm 的配置入口是一个 YAML 文件,最常见的是config.yaml放在项目根目录。核心就一个概念:model_list,它定义“对外暴露什么模型名,实际转发到哪个服务商”。
model_list: - model_name: unified-model litellm_params: model: provider1/model-alpha api_key: os.environ/PROVIDER1_API_KEY general_settings: master_key: sk-my-master-key这里有两个容易看懵的地方:
第一,model字段里provider1/model-alpha是 litellm 的模型标识格式,格式是“服务商前缀/具体模型名”。我在这里故意用占位符,实际使用的时候把它换成语义化编码的模型标识即可。这个前缀的作用是告诉 litellm 该用哪一套协议去解析请求。
第二,api_key没有直接写明文,而是用os.environ/PROVIDER1_API_KEY让 litellm 从环境变量里读取密钥。强烈建议所有真实密钥都走环境变量或者密钥管理服务,配置文件哪怕传错到聊天群里,泄露的也只是变量名而不是真实密钥。这个习惯能帮你躲过很多事故。
2.3 本地起服务并验证连通性
写完配置,启动服务就一条命令:
litellm --config config.yaml --port 4000litellm 默认端口是 4000,不冲突,我一般不去改它。看到类似开始监听 4000 的日志,就说明服务起来了。
下一步是验证。先看模型列表接口是否正常:
curl http://localhost:4000/v1/models \ -H "Authorization: Bearer sk-my-master-key"正常会返回你配置里的模型信息。再发一个最简对话请求:
curl http://localhost:4000/v1/chat/completions \ -H "Authorization: Bearer sk-my-master-key" \ -H "Content-Type: application/json" \ -d '{ "model": "unified-model", "messages": [{"role": "user", "content": "你好"}] }'请求路径里的/v1是接口版本前缀,认证方式就是 Header 里的Authorization: Bearer,和我们平时调模型服务的习惯完全一致。验证通过,一个最简的模型网关就跑起来了。
3. 深入核心功能:从调用到治理
3.1 一套接口,走遍各家模型
litellm 对外暴露的接口风格是目前大模型应用生态里事实标准的那一套:路径是/chat/completions,请求体里有model、messages、temperature这些字段。这意味着你现有的代码大多不需要改逻辑层,只需要把请求的地址换成 litellm 网关地址。
比如你原本是这么调模型服务的:
import openai client = openai.OpenAI( api_key="真实密钥", base_url="https://某个模型服务端的地址" ) response = client.chat.completions.create( model="某个模型名", messages=[{"role": "user", "content": "你好"}] )接入 litellm 之后变成:
import openai client = openai.OpenAI( api_key="sk-my-master-key", base_url="http://localhost:4000/v1" ) response = client.chat.completions.create( model="unified-model", messages=[{"role": "user", "content": "你好"}] )代码结构一模一样,只是api_key换成了你在 litellm 里生成的密钥,base_url换成了 litellm 的地址。这种改动成本几乎为零,是它最打动我的一点。不管是 Python、Node 还是其他语言里支持这套接口风格的 SDK,都能直接指过来。
3.2 同模型多路由:负载均衡与故障切换
真实项目里经常遇到一个问题:同一个模型能力,你在多家服务商都开了账号,或者同一家服务商开了一批部署。如果只配置一个地址,它挂了就全军覆没。litellm 允许你给同一个对外模型名挂多个实际目标,它来做分发。
model_list: - model_name: unified-model litellm_params: model: provider1/model-alpha api_key: os.environ/PROVIDER1_API_KEY - model_name: unified-model litellm_params: model: provider2/model-alpha api_key: os.environ/PROVIDER2_API_KEY注意这两段的model_name完全一样,都是unified-model,但实际转发目标不同。这样配置后,请求到达网关时,litellm 会在多个目标之间做轮询或者按权重分发。我实际用下来,遇到某个目标返回错误或者超时,litellm 还能自动切换到另一个可用的目标,这个能力对线上服务来说太重要了。
如果两个目标的能力不完全等价,只是想做主备切换,可以显式配置优先级或者给模型设置权重。比如主目标权重 90,备目标权重 10,正常情况下大部分流量走主目标,主出问题时备节点能兜底。
3.3 密钥体系:主密钥、虚拟密钥与团队隔离
litellm 的密钥体系设计得比较实用。配置里的master_key是管理密钥,权限最大,能创建、删除、查看所有资源。如果直接把主密钥发给业务开发用,出了事根本分不清是谁的锅。litellm 支持生成“虚拟密钥”,每个业务方或每个项目申请一个,互不干扰。
生成虚拟密钥可以直接通过接口操作:
curl -X POST http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-my-master-key" \ -H "Content-Type: application/json" \ -d '{ "models": ["unified-model"], "max_budget": 100 }'返回里会有一个带明文的新密钥,这个明文只出现这一次,之后你再想查询只能看到脱敏后的信息。我踩过这个坑,所以强烈建议拿到密钥后立刻存到自己的密钥管理工具里。
| 密钥类型 | 权限 | 主要用途 |
|---|---|---|
| 主密钥 | 管理全部资源、生成虚拟密钥 | 管理员使用,不能下发到业务侧 |
| 虚拟密钥 | 调用指定模型、受预算限制 | 按团队、按项目隔离下发 |
| 只读密钥(如果开启) | 查看统计信息、不产生调用 | 数据分析、报表用途 |
3.4 成本统计与预算控制
成本管控是我用 litellm 之后最不想回到从前的地方。之前每个月对账,要找各个服务商后台拉导出,再自己算每个项目花了多少。litellm 在转发请求时会记录每次调用的 token 数、模型单价、目标模型、虚拟密钥等信息,然后在管理界面或者接口里按维度聚合。
预算控制上有一个很实用的参数:max_budget。它可以配置在密钥级别,比如给某个实验项目一天只有 50 额度,跑超了就自动拒绝调用。也可以配置在全局或者模型级别。这个能力对那些需要控制内部同事随意耗模型的团队很管用,让成本变成一个可以收紧的边界,而不是事后心惊肉跳的账单。
需要注意,成本统计准确性的前提是你配置的模型单价是正确的。litellm 对大部分常见模型有内置价格表,但如果你接入的是不开源计价规则的模型服务,就要手动维护单价。价格配错,统计出来的金额就会和实际账单对不上,这个在排错里还要细说。
4. 生产环境落地:部署与巡检
4.1 Docker 部署与密钥注入
本地跑通只是第一步,真正用到团队里,我建议直接上 Docker。一个最小可用的编排配置大概长这样:
services: litellm: image: litellm-image ports: - "4000:4000" volumes: - ./config.yaml:/app/config.yaml environment: - PROVIDER1_API_KEY=${PROVIDER1_API_KEY} - PROVIDER2_API_KEY=${PROVIDER2_API_KEY} command: ["--config", "/app/config.yaml", "--port", "4000"]这里面有一个细节容易被忽略:虽然配置里写的是os.environ/PROVIDER1_API_KEY,但如果环境变量没有传到容器里,litellm 启动以后会拿不到真实密钥,调用时疯狂报鉴权错误。所以在 compose 文件里通过environment把宿主机环境变量透传进去,这里我用${PROVIDER1_API_KEY}占位,实际部署时换成密钥管理工具注入的变量名。
4.2 日志、监控与告警
litellm 的日志默认会打印每次请求的关键信息,但我不建议直接把日志丢给别人看,因为里面可能会有敏感的 prompt 内容。两个常见的优化方向:
一是日志脱敏。在 litellm 的配置里可以开启日志脱敏或者限制日志记录的字段,生产环境尤其要检查有没有把请求原文完整打出来。我之前在测试环境见过全量 prompt 打在控制台上,如果那是生产环境,就是妥妥的数据事故。
二是接入外部监控。litellm 的指标端口可以暴露请求数、延迟、错误率这些基础数据,外部监控系统可以定时拉取。我看到一个比较普遍的做法是把 Prometheus 格式的指标接到现有监控大盘上,再对 5xx 错误率和单次请求延迟设置告警。告警阈值不要拍脑袋,可以先用一个观测积累期:跑一周,看正常波动范围,再把阈值设在超出正常波动的位置。
4.3 配置更新与版本升级
模型接入列表不是静态的,经常要加新模型、换密钥、调预算。litellm 支持在网关运行过程中更新配置,不需要整个服务重启,这个对线上调用影响很大。我常用的办法是改完 YAML 后调用配置热更新接口,或者直接通过发送信号让进程重新加载配置。
不过热更新也不是万能的,某些改动静默不生效的情况我也遇到过,比如改了模型权重但分发比例没变。所以每次更新后我习惯立刻在测试环境发几个请求,确认目标是期望的服务商,再把流量全放出去。
版本升级也一样,虽然是开源项目,但我不推荐追新。先看更新内容里有没有不兼容的配置项变更,在非生产环境先跑一个版本,确认回归测试通过再升级生产实例。很多事故不是新代码引入的 bug,而是旧配置对不上新逻辑导致的。
5. 常见问题与排查技巧实录
5.1 请求返回“模型不存在”
这个是我见过的最高频报错。配置了unified-model,调用时也传了model: unified-model,却报模型不存在。大部分原因是model_list里每个条目的model_name和调用方传的模型名没对齐,尤其是复制粘贴配置时,不同条目的model_name拼写差一个字符。
排查思路很简单:先看/v1/models接口返回里到底有哪些模型名,再对比调用方传的字符串。如果列表里确实没有,那就是配置问题,把model_name改成一致的即可。还有一个隐蔽点是,如果你给同一个模型名的多个目标配置了不一致的读写权限,也可能出现部分密钥能查到模型、部分查不到的现象,这种时候按密钥维度查权限配置。
5.2 上游限流与超时处理
生产环境流量一起来,最先遇到的是上游限流。模型服务商有每分钟调用次数限制,一旦超过就开始绕过你。litellm 对上游限流有重试和冷却机制,但默认参数不一定适合你的场景。
我习惯在配置里给模型加超时和重试参数。超时不要设太长,我一般设 60 到 120 秒,太短容易误杀慢响应,太长会让用户一直傻等。重试次数建议 1 到 2 次,不要反复试,原因很简单:如果上游已经过载,你重试得越积极,它恢复得越慢。配合冷却时间给上游留喘息的机会,整体可用性反而更好。
如果同一个模型配置了多个目标,还可以让 litellm 在收到限流错误时自动切到另一个目标。这个比傻等重试好用得多,前提是你确认两个目标提供的模型能力是对等的。
5.3 上下文长度超限
上下文长度报错其实不是 litellm 的问题,是请求本身超出了目标模型的上下文窗口。litellm 默认不会去偷偷截断你的消息,也不会自动帮你压缩,它只是把请求原样转发。开发者容易犯的错是把max_tokens设置得过大,同时输入又很长,加起来超过模型上限。
这个问题的正确解法是在业务侧做上下文管理:截断历史消息、做摘要、按 token 数预估后决定保留哪些内容。litellm 层面能做的是在报错时给出清晰的错误信息,方便调用方快速定位是“输入超长”还是“输出长度参数超限”。如果你确实想在网关层统一拦截,可以在请求进入时加一层预检逻辑,但那样会增加网关的复杂度,小团队不一定划算。
5.4 成本统计对不上账
成本对不上是排查起来最累的问题之一,因为涉及多个环节。我总结出三个最常见的原因:
第一,模型单价没配或者配错。litellm 内置价格表覆盖有限,自定义模型如果没配单价,计费可能为 0,或者用的是默认值,导致和真实账单偏差很大。
第二,多个网关实例没有共享存储。如果你起了多个 litellm 实例做负载均衡,但每个实例用的是本地 SQLite,那么成本记录会散落在各个实例上,汇总时一定会缺。生产环境应该把数据库切到共享数据库。
第三,流式请求的计费时机。流式响应是一段段返回的,如果网关在流结束前就记录了 usage,遇到客户端提前断开连接,可能会少记或漏记一部分 token。排查时可以找一个小规模的集中时段,手动对比真实账单和网关记录的差距,再定位是漏记还是单价问题。
5.5 排查顺序速查表
联调或者线上出问题时,我建议按这个顺序排除,能少走弯路:
| 现象 | 第一步查什么 | 第二步查什么 | 常见根因 |
|---|---|---|---|
| 请求直接报 401 | 密钥是否存在且未过期 | 密钥绑定模型是否有权限 | 虚拟密钥权限范围不对 |
| 返回模型不存在 | 网关列表里是否注册了该模型名 | 调用方传的模型名是否一致 | model_name 拼写不一致 |
| 请求超时 | 上游服务是否正常 | 网关超时配置是否过短 | 上游慢,或者超时阈值太激进 |
| 偶尔报错但重试就好 | 上游限流配额 | 网关冷却机制是否生效 | 高频调用触达上游限制 |
| 成本和账单对不上 | 模型单价是否有配置 | 网关是否共享数据库 | 多实例记录分散 |
我的个人经验是,litellm 本身逻辑不算复杂,它只是把多方协议差异和运维痛点收拢到了一起。用起来顺不顺,很大程度上取决于你维护配置的整洁程度和密钥管理的纪律。
最后分享一个我实际踩过的坑:有一阵子团队里为了图省事,所有人共用同一个主密钥调网关,方便是方便了,但后来出现有人误写一个死循环,每分钟打出几千次请求,账单飙升,等发现时已经晚了。从那之后我强制每个项目、每个环境都生成独立虚拟密钥,并配上预算上限。这个习惯坚持下来,最大的收获不是省了钱,而是发生问题时的第一反应从“这是谁干的”变成了“我看下日志”,心里有底多了。