做 AI 应用这一年多里,我发现最容易被低估的组件不是模型,也不是 prompt,而是中间那一层“接入层”。今天想聊聊我最近在不停打磨的一个自部署组件:litellm。
litellm 是一个服务型组件,你可以把它简单理解成一个专门给大模型调用服务的 API 网关。它可以把不同模型平台的鉴权方式、接口格式、限流策略统统收敛到同一套接口后面,对外暴露一个统一入口。不管是聊天补全、文本嵌入还是模型路由切换,它都希望让你尽量少改应用代码。适合谁?如果你正在做多模型选型,或者业务里同时用着好几家模型平台,又或者团队里有人天天在改 prompt 和模型参数,那这个组件大概率能让你省下很多沟通和返工成本。
我在不同项目里试过自己写适配层,也试过其他代理方案,最终留在生产环境里的还是这一套。这篇文章不是官方文档翻译,而是把我从部署、配置、路由、成本控制到排障的完整经验整理出来,希望给你一个能直接落地的参考路径。
1. litellm 到底解决什么问题
1.1 多模型接入的现实痛点
现在绝大多数应用不会只调用一个模型平台。我就见过一个很典型的场景:一个内部知识库系统,对话问答用一个模型,文档摘要用另一个模型,向量化又要走单独的平台。如果不做任何收敛,代码里要维护三套客户端、三套鉴权逻辑、三套超时重试策略,而且每个平台的请求体和返回值都有细小差别。
这些差别听起来不大,真正写代码时却非常折磨人。接口路径不一样是小事,更要命的是鉴权方式不同,有的是在请求头里放密钥,有的要求签名,有的还要带时间戳。返回结构里 token 计数的字段名都各不相同,错误码和限流语义更是五花八门。一旦某个平台升级接口或者你想换一家,就得动业务代码,做回归测试,这个维护成本会随着接入平台数量指数上涨。
我把常见的差异点整理过一遍,基本就是下面这张表:
| 差异维度 | 常见表现 | 对应用的影响 |
|---|---|---|
| 接口格式 | 路径、请求体结构不同 | 每个平台都要单独写客户端 |
| 鉴权方式 | 请求头、签名、临时令牌 | 密钥管理和轮换非常分散 |
| 返回结构 | 内容字段、usage 字段不一致 | 解析层需要做大量兼容 |
| 限流语义 | 每分钟请求数、每分钟 token 数 | 超限后要分别处理退避 |
| 计费口径 | 按 token、按次、按时长 | 成本统计对不上账 |
这些问题在单模型小 Demo 里不存在,一旦进入多模型、多环境、多人协作的阶段,就会变成每天都要处理的琐事。而 litellm 的设计思路,就是把这一层差异完全挡在业务之外。
1.2 定位:既是代理,也是路由
litellm 首先是一个代理服务。它对外提供一个统一的 HTTP 接口,你按一套规范把请求发给它,它负责把请求翻译成目标平台认识的格式,再把结果统一返回。对上层应用来说,它看起来就是一个普通的大模型平台入口,很多现成的 SDK 只需要改一下 base_url 就能指向它。
它同时还是一个路由调度器。同一个逻辑模型名背后可以挂多个真实模型服务,litellm 会根据路由策略、健康状态、延迟表现来决定这次请求到底转发给谁。如果主服务连续报错,它能自动切换到备用服务,应用层完全无感。
我用一个生活化类比来解释:litellm 就像一个公司的总机。外面所有人只记住一个总机号码,打电话进来后,总机助理根据对方要找谁、哪个分机现在有空、哪个分机最近经常出问题,来决定转接到哪里。你不需要知道每个分机的具体号码,也不需要关心某个分机临时坏了怎么办,总机会替你协调。
1.3 为什么不建议自己写适配层
很多人会想,这层封装没什么难度,自己写个 requests 封装不就行了?我早期也是这么想的,结果越往后越发现事情没那么简单。
密钥管理就是第一道坎。每个平台的密钥散落在不同服务的环境变量里,轮换时至少要动几十个配置项。限流又是个大坑,每个平台有自己的配额,有的按每分钟请求数限制,有的按每分钟 token 数限制,一旦到达配额,请求失败后的退避策略需要根据平台差异单独调。
再往后是预算和审计。没有统一网关时,想统计“某个业务线一个月到底花了多少模型费用”,只能去各平台后台手工拉账单,再自己拼数据。至于高可用,单个平台出故障时想临时切到另一个模型,没有统一路由层就只能在代码里写死开关,非常狼狈。
litellm 把这些高频需求都内置了,而且支持两种使用方式:一种是作为 Python 包直接嵌入到现有应用里,适合轻量接入;另一种是作为独立代理服务部署,推荐生产环境使用。代理模式的隔离性更好,多个应用可以共享一个入口,密钥管理和路由策略也集中在同一个地方维护。
2. 部署与初始配置
2.1 部署方式选择
litellm 本质上是一个 Python 服务,安装方式非常灵活。你可以在已经用 Python 的服务器上直接跑,但为了隔离依赖和方便迁移,我更推荐用容器方式部署。
这是我在生产环境里最常用的一套启动方式:
docker run -d --name litellm-proxy \ -p 4000:4000 \ -e LITELLM_MASTER_KEY=your-master-key \ -v $(pwd)/config.yaml:/app/config.yaml \ your-registry/litellm:latest这里有几个点值得展开说说。默认监听在 4000 端口,对外暴露的接口路径兼容主流模型平台通用的 chat completions 风格,所以很多现成客户端改了 base_url 就能直接衔接。LITELLM_MASTER_KEY 是代理的管理密钥,所有子密钥都由它签发生效,这个值必须设置成高强度随机串,否则等于把网关裸奔在网络上。
配置文件通过挂载方式灌进容器,后续更新配置只需要替换文件并重启容器。如果你的网络环境不方便拉取公共镜像,也可以基于源码自行构建,构建过程不复杂,主要依赖项是 Python 环境和一些基础库,构建镜像反而更方便锁版本。
2.2 模型列表与密钥管理
litellm 的核心配置是 model_list,里面定义了哪些逻辑模型名对外可用,以及每个逻辑名背后真实的上游服务是什么。我习惯把配置文件拆成“对外名称”和“内部参数”两层,先看一个最小示例:
model_list: - model_name: chat-main litellm_params: model: provider-a/chat-model-x api_key: os.environ/CHAT_PROVIDER_KEY api_base: https://chat-provider.example.com/v1 - model_name: embed-main litellm_params: model: embed/embed-model-y api_key: os.environ/EMBED_PROVIDER_KEY api_base: https://embed-provider.example.com/v1这里的 model_name 是给上层应用看的逻辑名,比如 chat-main、embed-main。应用永远只认这个名字,不要在上游服务代码里写具体模型标识。litellm_params 里的 model 字段才是真正要请求的模型标识,格式一般是“平台名/模型名”,api_key 和 api_base 指定具体连接信息。
为什么要做两层命名?因为切换模型时只需要改配置文件。今天 chat-main 后面挂的是 A 平台的模型,明天想换 B 平台的模型,配置里把 model、api_key、api_base 一换,重启代理即可,应用代码和 prompt 完全不用动。
密钥管理方面,我强烈不建议把密钥明文写进 yaml。litellm 支持 os.environ/ 这种引用环境变量的写法,密钥只存放在部署平台的密钥管理能力里。如果公司有集中的密钥管理服务,也可以让配置里的 api_key 指向一个动态获取逻辑,这样密钥轮换时不会打断正在运行的请求。
2.3 快速验证接口连通性
启动服务后,先不要急着接业务,用 curl 做一次最小冒烟测试,确认路由、密钥、模型标识都正确:
curl --location 'http://localhost:4000/v1/chat/completions' \ --header 'Authorization: Bearer your-master-key' \ --header 'Content-Type: application/json' \ --data '{ "model": "chat-main", "messages": [ {"role": "user", "content": "你好,请用一句话介绍一下自己"} ] }'如果一切正常,你会得到一个标准格式的响应,里面包含模型返回内容和 usage 信息。litellm 会解析上游平台的原始返回,把 token 消耗等指标统一成自己的格式。这一步很重要,后续的成本统计、预算控制都依赖 usage 字段是否被正确解析。
测试时如果报 401 或 403,先检查 Authorization 头里的密钥是不是 master key,再检查配置里的 model_name 是否匹配。如果返回模型不存在,大概率是上游平台标识写错了,可以去平台侧的模型列表里确认。接口通了之后,我习惯再测一次 embedding 请求,确认不同类型的模型服务也能正常路由。
3. 路由、高可用与成本控制
3.1 模型路由与负载均衡
litellm 的路由能力是我最终选择它的关键理由。它可以在同一个逻辑模型名下挂多个真实模型服务,由代理根据策略挑选最优目标。
一个典型的配置是这样:
model_list: - model_name: chat-main litellm_params: model: provider-a/chat-1 api_key: os.environ/CHAT_PROVIDER_A_KEY api_base: https://provider-a.example.com/v1 - model_name: chat-main litellm_params: model: provider-b/chat-2 api_key: os.environ/CHAT_PROVIDER_B_KEY api_base: https://provider-b.example.com/v1注意这里两个 model_name 都叫 chat-main,但上游分别是不同平台。请求打到 chat-main 时,litellm 会在两者之间做选择。默认策略是简单轮询,适合两个上游能力接近的场景。litellm 也支持基于最低延迟的策略,它会记录历史请求耗时,优先把新请求发给延迟更低的平台,适合对响应速度敏感的业务。
选择路由策略时有一点容易忽略:不同模型虽然能力相似,但行为风格、返回格式可能有细微差别。如果你做的是核心链路,且后续逻辑依赖模型输出的稳定性,建议把业务按场景拆分,不要把所有请求都塞到同一个逻辑模型名里,否则一旦路由切换,下游解析逻辑可能会被微妙变化打破。
另外,如果做的是多轮对话且业务要求上下文连贯,需要让同一个对话族落到同一个上游模型。你可以通过请求里的 metadata 字段携带用户会话标识,利用调度逻辑做会话亲和性绑定。这个配置属于进阶玩法,但在真实项目中经常能救命。
3.2 超时、重试与降级
网关层最常见的价值体现,是故障时的自动降级。litellm 在 router_settings 里提供了一组相关参数,我通常会配置成这样:
router_settings: routing_strategy: simple-shuffle num_retries: 2 request_timeout: 120 allowed_fails: 3 cooldown_time: 60 fallbacks: - chat-main: [chat-backup]num_retries 控制请求失败后的最大重试次数,allowed_fails 表示一个上游模型连续失败多少次后进入冷却状态,cooldown_time 是冷却时长,fallbacks 指定主模型不可用时的备用模型列表。
有一个血泪教训必须提醒你:重试机制在处理大模型请求时要格外谨慎。普通请求失败重试没问题,但生成类请求并不总是幂等的,同一段 prompt 被重试两次,可能产生两段不一样的内容,也会产生两份 token 消耗。如果上游服务已经处理了请求,只是在返回响应时网络超时,重试造成的重复扣费是真实的。所以我现在的做法是限制重试次数不超过 2,同时给每个 key 设置预算作为兜底,防止死循环把费用打爆。
超时时间也需要分层设置。有的模型思考时间长,整体超时却设得短,导致正常生成被误判失败;有的模型接口长时间无响应,超时设太长又会让用户一直等待。建议按模型能力配置差异化超时,简单任务用较短超时,复杂任务放长到 3 到 5 分钟,同时开启流式响应让用户尽快看到增量结果,减少等待感。
3.3 限流与预算管理
成本失控是接大模型服务最容易出现的事故之一。litellm 在这一块的思路是按照 key 维度做精细控制,你可以为不同的应用、不同的团队、不同的人签发子密钥,然后分别设置速率和预算。
我在配置里常用这样的约束:
litellm_key_management: default_budget: 200 default_max_parallel_requests: 20 default_rate_limit_per_minute: 60default_budget 表示每个 key 的月度预算上限,达到后该 key 的请求会被拒绝,应用层会收到明确的配额错误;max_parallel_requests 限制并发数量,防止某个恶意或异常业务占用全部上游资源;rate_limit_per_minute 控制每分钟请求数,避免超卖导致上游平台限流。
token 统计是预算控制的基础。litellm 会解析每个上游返回的 usage 信息,按输入和输出分别累计。这里有一个容易被忽略的点:如果你把日志存在内存里,代理一重启统计数据就全没了。要真正做成本分析,必须把数据持久化到数据库,后面我会展开讲。
我在生产环境里的经验是,预算值宁可先设小一点,也不要一开始就给得很宽。等摸清真实用量后,再基于数据逐步调整。相比事后发现账单异常,事前多拒绝几个请求带来的损失要小得多。
4. 一个真实场景:统一网关服务的数据流
4.1 场景设定
为了让你更直观地看到 litellm 在业务里长什么样,我构造一个常见的后端架构:某跨平台系统一共有三个子服务,摘要服务负责把长文档浓缩成短摘要,标签服务负责给内容打分类标签,检索服务负责把文本转成向量并进行相似度召回。
改动之前,这三个服务各自连接不同的模型平台,各自的配置文件里保存着不同平台的密钥,各自的代码里维护着不同的请求格式。改动之后,所有请求都指向同一个 litellm 代理,由代理统一调度上游模型。
这个架构带来的变化非常明显。子服务不再关心模型供应商是谁,只知道自己调用的是 chat-main 还是 embed-main。密钥不再散落在各服务环境变量里,而是收口到网关侧。新接一个模型平台时,不需要通知所有子服务团队,只需要在 litellm 配置里增加一个模型条目,然后做一轮小范围验证。
4.2 一次完整请求的数据流
以摘要服务调用 chat-main 为例,整条链路的流程大致是这样的:
- 摘要服务向 litellm 的
/v1/chat/completions发起请求,Header 带上自己的子密钥。 - litellm 先做认证和配额校验,确认这个 key 有效且预算充足。
- 代理根据路由策略,从 chat-main 背后的候选上游里选出本次目标。
- litellm 把统一请求格式转换成目标平台要求的格式,并把对应的真实密钥附加到请求里。
- 上游平台处理请求并返回结果,litellm 等待响应。
- 如果目标平台超时或报错,litellm 按配置决定重试或切换到备用模型。
- 拿到成功响应后,litellm 解析内容、读取 token usage,写入日志并扣减当前 key 的预算。
- litellm 将标准化响应返回给摘要服务,摘要服务完全不需要知道刚刚发生了什么。
把这八个环节整理成表格,各环节关注点会更清晰:
| 环节 | 负责模块 | 关键点 |
|---|---|---|
| 请求接入 | litellm 网关 | 统一入口、统一鉴权 |
| 配额校验 | key 管理模块 | 预算、并发、速率限制 |
| 路由选择 | router 模块 | 路由策略、健康状态 |
| 协议转换 | adapter 模块 | 请求格式、密钥注入 |
| 上游调用 | 上游平台 | 超时设置、可用性 |
| 失败处理 | fallback 模块 | 重试次数、冷却机制 |
| 数据落库 | 日志模块 | token 统计、请求明细 |
| 响应返回 | 网关响应层 | 标准化结构、错误码统一 |
4.3 落地后的具体收益
这个场景上线一段时间后,我最直观的感受是发布频率降低了。以前每次模型平台调整接口,子服务都要跟着改代码发版本,现在只需要在网关侧处理适配,子服务基本感知不到变化。
其次是运营数据好看了。以前想看“摘要服务上周花了多少钱,调用了几万次,平均延迟多少”,要么去各平台后台手工拼,要么完全没有数据。现在 litellm 日志里全都有,想分析哪个业务线、哪个模型、哪个时间段的费用,一条查询就能搞定。
最后是故障恢复变快了。以前上游平台出问题,运维要去改业务代码,甚至要重启服务才能切换模型。现在只需要调整 litellm 的 fallback 配置,或者干脆在上游候选列表里临时移除故障项,几分钟内就能完成切换。这在生产环境里的价值,是用钱和口碑才能衡量的。
5. 日常运维与常见问题排查实录
5.1 我踩过的几个高频问题
litellm 本身稳定性不错,但真实环境中总会遇到各种奇奇怪怪的问题。我把自己遇到过的、以及在社区里反复看到的高频问题整理成一张速查表:
| 问题 | 可能原因 | 处理思路 |
|---|---|---|
| 401 Authentication Error | 子密钥无效、master key 被改 | 重新签发 key,检查请求头 |
| 403 但密钥明明有效 | key 已过期或预算用完 | 查 key 的预算剩余和过期时间 |
| 429 rate limit exceeded | 触发了速率限制或并发限制 | 调大限额,或优化业务限流 |
| 请求一直 timeout | 上游平台慢、超时设太短 | 区分任务超时,开启流式模式 |
| 流式响应中途断开 | 代理到上游连接超限 | 检查并发数、调整连接池 |
| 日志里 token 统计为 0 | 上游返回格式未识别 | 检查模型标识与返回结构 |
| 容器内连不上宿主机服务 | 网络隔离导致 | 用宿主机网络或正确配置网关地址 |
| fallback 没生效 | 备用模型名配错 | 确认 fallback 列表里的逻辑名已被定义 |
第一类问题里,401 和 403 最多。很多人会忽略一个细节:请求头里的 Bearer token 到底是 master key 还是某个业务子密钥。master key 权限太大会增加风险,业务方应该使用独立的子密钥,并在 litellm 后台按需签发生效。
第二类问题则暴露了配置上的不足。熔断和 fallback 虽然是内置能力,但前提是你把备用模型也定义在 model_list 里,并且 fallback 配置里的模型名和 model_name 完全一致。很多人的 fallback 不生效,就是因为备用模型名少了一个字符或者大小写不对。
5.2 日志与监控配置
litellm 默认的日志是保存在内存里的,这一点被很多人忽略。生产环境一重启,所有请求记录、token 统计、成本数据全部清空。我建议从一开始就接上数据库持久化,在启动环境变量里配置类似这样的连接信息:
DATABASE_URL=postgresql://user:password@db-host:5432/litellm_db接上数据库之后,请求明细、key 用量、预算消耗都会落库,查询历史记录和做成本分析就方便多了。数据库表结构不需要自己维护,litellm 首次启动会自动建表。
监控方面,litellm 暴露了用于采集指标的端点,可以接到常见的监控体系里。我自己习惯重点盯四个指标:请求成功率、P50 和 P95 延迟、token 消耗速率、预算剩余比例。成功率反映网关和各上游的整体健康度,P95 延迟能发现某条链路是不是变慢了,token 消耗速率能提前预警成本异常,预算剩余比例则直接关联到业务可用性。
如果某一天上游平台例行升级,你会看到成功率开始抖动,延迟逐渐升高,这时候不需要急着改代码,先观察监控面板,再决定要不要触发 fallback。这种处理问题的方式,比等到用户投诉再排查要舒服太多。
5.3 配置管理的几点经验
litellm 的配置是一个 yaml 文件,但它实际上是生产环境里的核心资产。我建议像管理代码一样管理它,而不是把它当作一个随意改动的临时配置。
第一,配置文件必须版本化。每个环境有一套独立配置,配置变更要提交评审,避免有人在生产服务器上顺手改一下,最后谁也不知道改了哪里。第二,配置要按环境分离。开发、测试、生产使用的模型、密钥、预算都不一样,不要一把配置文件通吃所有环境,否则测试时很容易误触生产额度。第三,配置变更后要小流量验证。先在某个测试 key 上调用新模型,确认响应格式、延迟、计费都能接受,再全量接入业务。
我还有一个习惯:每次改配置前先备份当前版本,改完启动后立刻看健康检查接口和启动日志,确认没有解析错误。虽然这些操作听起来琐碎,但在多团队协作、多业务共享同一个网关的情况下,规范化的配置管理能直接决定项目后期维护成本。
最后分享一个我自己总结的小技巧:给每个模型逻辑名建立命名规范,比如 chat-fast 代表快速响应模型,chat-backup 代表主模型不可用时的降级模型,embed-doc 代表文档向量化专用模型。命名一旦规范,在日志里排查问题时,不用翻配置就能大致判断出请求走了哪条链路。litellm 的能力再强,也架不住混乱的命名和随意改的配置,真正让系统稳定运行的,往往是你自己定下的那些规则。