凌晨一点我盯着监控面板上满屏的504报错心情复杂。白天刚把新模型切到线上晚上就给我颜色看——下游服务商接口超时业务侧所有请求像约定好了一样一起超时。那个瞬间我意识到直接在业务代码里写死模型API的调用遇到故障时连个快速切换的入口都没有。正是这次事故让我下决心把所有模型调用收敛到一个统一的网关层于是就有了后来长期跟 litellm 打交道的经历。litellm 这个名字做服务端的朋友应该不陌生。它是一个开源的 LLM API 网关把市面上主流模型服务商的接口统一成一套 OpenAI 兼容的格式再在中间层做密钥管理、负载均衡、重试、缓存、成本统计和限流。简单说你的业务只需要知道一个本地地址其他事情全部交给它。这篇不是官方文档的翻译是我自己从选型到部署再到生产运维磕磕绊绊走过来的一些实践记录。适合谁看如果你的项目需要同时对接两个以上的模型 API或者你正在被服务商锁定、想给以后切换留条后路又或者你单纯想搞清楚模型成本到底花在哪了这篇应该都用得上。1. 它到底解决什么问题一个接多家模型的真实痛点1.1 我在生产环境踩过的多模型适配坑技术团队的选型从来不是一成不变的。我做的某跨平台系统一开始只依赖一套 GPT 系列接口跑了大半年效果和成本都还凑合。后来业务方要求引入 Claude 系列做长文本总结又要在 Gemini 系列上跑多模态识别好嘛问题全来了。每家厂商的 SDK 都不一样参数命名习惯千奇百怪。OpenAI 风格接口用temperature控制随机性Anthropic 风格接口虽然也类似但请求体和响应体结构差异明显尤其流式输出的解析逻辑完全不通用。最让我崩溃的是错误码一家超时返回 408另一家返回 502还有一家干脆返回 200 但 body 里塞了个错误标志位。业务代码里为了兼容这些差异慢慢堆积出一个几百行的适配层每次接新模型都要在这个泥潭里修修补补。更麻烦的是切换成本。业务想对比两个模型的效果我得先在代码里改接口地址、改认证头、改传参格式再重新发布一次。出了问题想回滚又得再来一轮。线上事故往往就是这样发生的——你以为只是改一个配置实际上动的是整个请求链路的根基。1.2 litellm 在技术链路上的定位API 网关不是又一个 SDK一开始我也理解错过以为 litellm 只是一套封装好的 Python SDK直到看到它的部署方式才明白它本质上是独立于业务进程之外的一个服务。典型的架构长这样业务服务只跟 litellm 的网关地址通信发出去的是 OpenAI 格式的请求 litellm 收到后根据配置把请求转换成目标服务商的格式转发出去再把响应转回统一格式返回给业务。业务层不感知下游到底是谁下游也不感知上游业务的具体实现。这个分层跟数据库中间层很像。你想想看业务代码里访问 MySQL 和 PostgreSQL不会分别写一套方言而是通过 ORM 屏蔽差异。litellm 干的就是 LLM 世界的 ORM 加上一层代理管控只是它跑在独立的进程里比代码内封装更干净、更好运维。好处是显而易见的模型切换变成了纯配置变更业务代码零改动新增供应商只需要在网关里加配置和密钥故障切换可以在网关层面瞬间完成不用等业务重新发布。这些东西在规模小的时候无所谓一旦请求量上来了、涉及的团队变多了价值就会被放大得很明显。1.3 哪种场景适合现在引入哪种可以先等等先说适合的。你的业务在同时用两家以上模型服务或者有明确的多供应商容灾需求你希望给不同部门、不同项目分配独立密钥和预算额度你每天要为几十万次请求的成本去向发愁你需要在网关层做限流防止某个失控任务把预算烧穿。这些场景下litellm 属于用了就回不去的那种工具。不适合的情况也存在。如果你的业务只用一家模型服务没有切换计划也不关心成本拆分那确实不需要为它多维护一个组件。任何中间层都有运维成本litellm 虽然整体稳定但毕竟是一个需要独立部署、配置、监控的服务小项目硬上反而觉得繁琐。一句话总结它是给模型调用已经复杂到业务代码扛不住的阶段准备的方案。如果你已经感觉到了这种痛那就值得认真考虑。2. litellm 的核心机制一次请求在网关里经历了什么2.1 一次对话请求的完整旅程理解了定位之后就得看看它内部是怎么运转的。我拿一个最普通的对话请求来拆解。客户端比如你的后端服务用 OpenAI SDK 往 litellm 的/chat/completions端点发请求Body 长这样model填你在网关里配置的逻辑模型名messages是对话上下文其他可选参数跟调用 OpenAI 时保持一致。litellm 收到请求后第一件事是鉴权。它看你带进来的 API Key 是主密钥还是虚拟 Key虚拟 Key 有没有绑定这个模型的权限Key 的预算还有没有余额。通过之后网关根据你指定的model_name去配置里找到对应的供应商列表按路由策略挑一个目标然后做格式转换把你的 OpenAI 格式请求翻译成目标服务商的请求结构包括请求头、认证方式、参数命名这些。请求发出后litellm 等待下游返回。如果是流式请求它会把下游的 chunk 流逐个转回 OpenAI 格式再传给客户端不会傻等完整结果。请求结束后网关会把这次调用的模型、输入输出 token 数、消耗金额、响应耗时、状态码全部记入数据库。这还没完如果本次请求失败它还会按配置决定要不要换下一个供应商重试。整个流程看起来环节不少但因为都是在本地内存和配置层面完成的实测额外增加的延迟通常只有几十毫秒相比模型动辄一两秒的响应时间完全可以忽略。2.2 model_name、deployment、provider 三层映射到底在映射什么初次写配置的人容易被几个概念绕晕我拆开说。model_name是你对外暴露的逻辑模型名业务代码里model参数填的就是它。这个名字应该跟具体服务商无关比如chat-pro、embedding-main。它的作用是给上层一个稳定标识让业务不要关心底层是谁。deployment是一个具体的可调用实例它由litellm_params描述包括供应商类型、实际模型名、API Key、API Base 地址等。同一个model_name下面可以挂多个 deployment。provider则是 litellm 内置的适配器它知道怎么跟某一家服务商的接口做格式转换。比如配置里写model: anthropic/claude-3-5-sonnet-20240620anthropic就是 provider斜杠后面的字符串是这家服务商里的具体模型名。这三层映射带来的最大好处是业务和供应商之间彻底解耦。举个例子我在配置里把model_name: chat-pro同时指向 GPT-4o 和 Claude 的 deployment当我想把所有流量切到 Claude 上时只需要把路由策略调整一下或者把 GPT-4o 的 deployment 暂时下线业务代码一个字符都不改。2.3 为什么统一成 OpenAI 格式是最省事的选择litellm 对外暴露的是 OpenAI 兼容接口内部再转换成各家格式。这个设计我一开始不太理解为什么不干脆统一成一种自定义格式后来想明白了OpenAI 风格接口是目前生态最广的事实标准。几乎所有开源工具、Agent 框架、开发库都默认支持 OpenAI 格式你只需要把base_url指向 litellm就能无缝接入。如果 litellm 自己发明一套格式那所有生态工具都得围绕它重新适配推广成本太高不值得。当然统一格式也不代表所有参数都能 1:1 透传。各家模型能力有差异有的支持视觉输入有的不支持函数调用有的对top_p敏感有的直接忽略。litellm 的做法是尽力转换同时对不支持的参数做降级处理。我试过用同一个请求体同时调用 GPT-4o 和 Claude 系列大部分场景表现一致少数高级参数如某些 logit bias 设置需要单独给 deployment 配置微调。这不算缺点反而是正常的取舍。3. 从零部署到可用我实际跑通的那一套配置3.1 最快速的 Docker 部署路径部署 litellm 最省事的方式是 Docker一条命令就能跑起来docker run -d \ --name litellm \ -p 4000:4000 \ -v $(pwd)/config.yaml:/app/config.yaml \ -e LITELLM_MASTER_KEYsk-123456 \ ghcr.io/berriai/litellm:main-latest这里有几个细节值得说明。4000是默认端口对外提供服务的就是这个地址。LITELLM_MASTER_KEY是主密钥用来登录管理后台和生成虚拟 Key相当于整个网关的超级管理员账号务必保管好。-v挂载把本地配置文件放进容器这样改配置只需要重启容器而不是重新构建镜像。启动之后先验证服务活着curl http://localhost:4000/health返回正常的 JSON 状态就说明网关起来了。这时访问http://localhost:4000用主密钥登录可以看到管理后台的界面请求日志、虚拟 Key 管理、模型列表、成本统计都在里面。说实话我第一次看到这个 UI 时有点意外没想到一个开源网关能做得这么完整。3.2 配置文件的正确打开方式litellm 的配置集中在config.yaml核心结构我用一个最小示例说明model_list: - model_name: chat-pro litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: chat-pro litellm_params: model: anthropic/claude-3-5-sonnet-20240620 api_key: os.environ/ANTHROPIC_API_KEY - model_name: embed-main litellm_params: model: openai/text-embedding-3-large api_key: os.environ/OPENAI_API_KEY litellm_settings: num_retries: 3 request_timeout: 600 set_verbose: false general_settings: master_key: os.environ/LITELLM_MASTER_KEY database_url: os.environ/DATABASE_URL看到chat-pro出现两次了吗这就是同一个逻辑名挂两个供应商的做法litellm 会自动在它们之间做负载均衡或故障转移。os.environ/XXX表示从环境变量读取密钥千万不要把明文 Key 写进 YAML 文件一旦配置文件被提交到代码仓库等于把供应商密钥公开了。这里还有个大坑提醒你litellm 默认使用 SQLite 存储日志和 Key 数据容器一删数据就没了。生产环境建议把database_url指向 PostgreSQL用外部数据库持久化否则你在后台创建的虚拟 Key 和查看的历史日志都会在重启后蒸发。3.3 三行代码完成业务接入接入这一侧的体验相当顺滑。如果你的项目本来就用 OpenAI SDK那改动量小到可以忽略from openai import OpenAI client OpenAI( base_urlhttp://localhost:4000, api_keysk-虚拟key, ) resp client.chat.completions.create( modelchat-pro, messages[{role: user, content: 你好}], ) print(resp.choices[0].message.content)注意两个关键点base_url指向 litellm 的地址api_key用虚拟 Key 而不是主密钥。如果你是用 LangChain、LlamaIndex 这类框架同样只需要在初始化 LLM 对象时把 base_url 替换掉别的逻辑不动。接到网关后你会发现换模型这件事变得非常轻。想在 GPT-4o 和 Claude 之间做 A/B 测试配置里加一个 deployment业务代码里把model从chat-pro改成chat-pro-b就能对比。想灰度切流量用路由策略把大部分请求分到新模型留一小部分在老模型上观察效果。4. 负载均衡、限流、故障转移与缓存生产级网关该有的姿势4.1 一个 model_name 挂多个供应商请求怎么分配litellm 把多供应商管理做成了核心能力同一逻辑名下的多个 deployment 并不是随机乱选它有一套可配置的路由策略。最基础的是simple-shuffle就是简单轮询适合各供应商能力对等、无成本差异的场景。我更常用的是usage-based-routing-v2它会根据每个 deployment 的实时消耗情况做分配尽量避免把压力全打到同一家上。如果你更在意响应速度可以选latency-based-routing它会参考历史延迟数据做决策。故障转移是另一个刚需。我第一次用 litellm 就是被开头说的那场超时事故逼的。它在请求失败时不会立刻放弃而是按照num_retries配置的次数依次尝试列表里的其他 deployment。下游 A 超时了就自动切到下游 B整个过程业务侧无感知。生产环境建议至少给核心模型挂两个供应商这样单点故障只会让网关日志多一条记录而不是让整个业务报错。配置加一段路由设置就能启用这个能力router_settings: routing_strategy: usage-based-routing-v2 fallbacks: [ {chat-pro: [chat-pro-backup]} ] num_retries: 2 timeout: 304.2 限流和预算控制怎么设计才不容易误伤业务限流这件事做松了会烧钱做紧了会误伤正常请求。litellm 的限流需要考虑两个层面并发层面的保护和预算层面的保护。并发保护用max_parallel_requests可以理解成网关里的信号量限制同一时刻最多执行的请求数。超出后后续请求排队等待而不是直接拒绝。这个参数要根据业务峰值来定太小会让正常流量排队太大又起不到保护作用。我一般先压测找出单实例的吞吐上限再乘一个 0.7 的系数作为兜底值。预算保护是更实用的一层。创建虚拟 Key 时可以设置max_budget比如给一个数据分析项目分配 100 美元的月预算一旦该 Key 的累计消耗达到阈值litellm 会自动拒绝后续请求。生成一个带预算的 Key 用一条 API 调用搞定curl -X POST http://localhost:4000/v1/key/generate \ -H Authorization: Bearer sk-主密钥 \ -H Content-Type: application/json \ -d {models: [chat-pro], max_budget: 100, rpm_limit: 1000}返回的key字段就是分配给业务方的虚拟 Key建议保存好后由相关团队各自保管。这里最关键的思路是预算要落在 Key 的粒度上而不是全局。不同团队共用同一个 Key一旦某个任务失控烧穿预算谁都说不清到底是谁花的钱。4.3 缓存让重复请求从烧钱变成免费模型调用的计时成本里重复请求占的比例往往比想象中高。比如用户反复问同一个问题、同一批文档反复做主题分类这些请求结果完全一致每次却都要重新计费。litellm 的缓存支持两种模式。简单模式就是常规 TTL 缓存把相同请求的响应存进 RedisTTL 内直接返回进阶一点的语义缓存通过向量相似度判断两个请求是否在语义上接近即使文本不完全一致也能命中。我一直觉得语义缓存是个被低估的功能尤其是做文档问答这类场景问题换个说法照样能命中缓存省下的成本非常可观。配置也不复杂litellm_settings: cache: true cache_params: type: redis host: localhost port: 6379 ttl: 3600需要注意缓存只对完全相同的messages结构生效如果你在请求里带了随机参数比如采样温度、随机种子或者每次都传入不同的系统提示词命中率会明显下降。所以开缓存之前先梳理业务里到底有哪些请求是天然重复的别指望它解决所有成本问题。5. 成本追踪、密钥安全与可观测性把账算清楚才敢放心上量5.1 成本账单的三个维度按 Key、按模型、按团队litellm 的成本统计粒度比我预期细很多。它根据你配置的model_info里的定价信息对每一次请求估算出 token 费用并写入数据库在管理后台能直接从三个维度看账单。第一个维度是按虚拟 Key 看可以精确到具体项目或团队花了多少钱。第二个维度是按模型看能看出到底哪个模型在吃掉大部分预算方便你做模型选型优化。第三个维度是按团队看如果你在 litellm 里开了 team 功能可以跨 Key 汇总团队维度的总花费。实战里我一般每周看一次成本报表。有一次发现某项目的 embedding 费用异常高顺着日志一查是某个批处理任务没有做结果缓存把几万条文本反复过了一遍模型。把缓存打开之后成本立刻降了一个数量级。没有这个账单系统这种浪费很难被及时察觉。5.2 虚拟 Key 机制每个业务方拿各自的钥匙很多团队图省事直接在代码里硬编码供应商的真实 API Key所有业务共用一个。这在规模小的时候还行一旦出了问题你连是谁在用都查不出来。litellm 的虚拟 Key 机制解决的就是这个问题。主密钥负责管理虚拟 Key 负责干活。每个 Key 都可以绑定模型列表、设置预算、限制速率甚至可以设置过期时间。我把虚拟 Key 发到各业务团队手里每个项目一把独立的钥匙。某项目需要下线直接删掉对应 Key 就行完全不影响其他项目。在 UI 后台可以一键完成 Key 的生成、停用、删除也支持批量操作。我习惯把不同环境的 Key 分开管理生产环境一套测试环境一套避免测试流量污染生产成本报表。这是个一劳永逸的好习惯。5.3 监控与告警接入 Prometheus 后我才算放了心网关本身也可能挂所以监控它自身健康状态同样重要。litellm 导出 Prometheus 指标路径是/prometheus/metrics你可以拿它查看到每秒请求量、错误率、各模型延迟分布、缓存命中率等等。我用 Grafana 搭了一个简单的看板把几个核心指标放在一起请求成功率、P95 延迟、按模型的请求量和成本、虚拟 Key 的剩余预算。设置了告警规则之后比如成功率低于 99% 持续五分钟就通知值班群。这里要特别感谢那次凌晨事故如果没有它我不会在监控这块下这么大力气。日志还可以通过回调接口引到外部系统。litellm 支持按请求发送 webhook 回调我自己用这个能力把异常请求摘要推到群机器人不用每次都登录后台翻日志。如果你有内部日志平台也可以直接把请求日志推过去做长期分析。6. 生产落地避坑指南这些坑我都替你踩过了6.1 常见问题速查表现象常见原因解决方案请求报Invalid model请求里的model和model_list中model_name不一致检查配置中的model_name确保业务 URL 里的模型名完全匹配401 Unauthorized虚拟 Key 无权访问该模型或 Key 已过期在后台检查 Key 绑定的模型列表必要时重新生成 Key429 Too Many Requests触发速率限制或预算上限调高对应 Key 的rpm_limit或给 Key 充值预算请求频繁超时request_timeout太小或下游服务商本身不稳定调大request_timeout同时配置多条 deployment 做故障转移成本统计为零依赖了未配置model_info的模型在model_list中补充模型的输入、输出单价信息UI 后台没有历史请求日志使用了 SQLite 且容器被重建导致数据丢失改用 PostgreSQL 持久化存储流式输出偶尔乱码框架没有正确解析 SSE 事件检查 SDK 版本尽量使用官方最新版 OpenAI SDK6.2 几个值得长期坚持的配置习惯第一个习惯是密钥永远走环境变量注入。配置文件通过 Git 管理密文一旦进版本库就成了安全隐患。另外环境变量换起来也方便比如服务商要轮换 API Key直接更新环境的变量然后重启容器即可不用改任何文件。第二个习惯是配置文件必须版本化。我把每个版本的 config.yaml 都存在仓库里辅以简单的注释说明变动原因。出了问题要回滚时直接切回旧版本配置就可以非常省事。这是我从一次事故中学到的教训有一次我把一个新模型加进 model_list顺手把一个旧配置改错了请求全部失败回滚都不方便。第三个习惯是升级要谨慎。litellm 迭代速度不慢新特性让人眼馋但每次升级前我会先检查更新日志尤其是router_settings和general_settings这两个部分的变更因为它们影响的是全局行为。先在测试环境跑几天观察稳定之后再碰生产。6.3 如果只想记住一条经验如果整篇只能留一句话我会说尽早把模型调用收拢到网关层业务越早上车越安全。技术债务这东西越早还越便宜等你积累了上千个直接对接供应商的调用点再去改造成本会陡增。litellm 可能不是每个场景的最优解但它至少给了你一个从容切换的缓冲地带。我自己从那个凌晨的 504 事故到现在网关层已经稳稳跑了大半年中间经历过多次模型供应商临时不可用、新版本模型灰度上线、业务方新 Key 的批量发放基本都靠配置解决再也没出现过改一个模型、动全身代码的窘境。现在每次看到群里有人吐槽模型 API 又超时了我都会下意识看一眼自己的网关监控面板——这种踏实感确实是当初花时间把这条路走通换来的。