
先交代一下背景吧。最近一年多我接手的几个项目都绕不开大模型调用这件事。有的模块要用海外闭源模型做复杂推理有的要用国内开源模型走私有化部署还时不时有人提“能不能顺手把某个本地小模型也接进来做个兜底”。最让我头疼的不是模型效果而是每个服务都有自己的SDK、鉴权方式、请求体和响应结构。业务代码里塞满了针对不同供应商的分支逻辑换一个模型要改好几处日志格式也不统一线上出问题根本没法快速定位。后来我把这些调用全部收敛到一个开源网关层来处理也就是标题里写的litellm。它做的事情可以用一句话说清楚把各种模型提供方的API统一成同一个协议格式对外暴露一套标准接口。这样一来业务端只需要对接一次后续换模型、加模型、做负载均衡和自动重试全都在网关层完成不用再改业务代码。这篇文章就围绕这个工具把我从部署、配置到生产环境踩坑的完整过程梳理一遍给同样被多模型调用折腾的人一个参考。1. 先搞清楚litellm到底解了什么难题1.1 多模型调用格式不统一的真实痛点先说一个最常见的场景。很多团队一开始只接了一家模型服务代码写得很顺。等第二个项目要换另一家模型供应商时就会发现事情远没有想象中简单。表面上大家都是“发一个HTTP请求、传一段提示词、拿回一段生成结果”但细节上的差异非常磨人。我用一个表格来对照一下不同供应商常见的差异点你就明白为什么需要一层统一的处理差异维度供应商A的典型行为供应商B的典型行为鉴权方式请求头带指定前缀的密钥使用独立的访问令牌过期需刷新请求地址路径中包含版本号路径中不区分版本靠参数区分请求体字段参数名较长需要嵌套结构参数平铺命名更简洁模型参数温度和采样数等参数齐全部分参数不支持传了会报错响应体结构生成内容嵌在多层嵌套字段中生成内容直接在顶层字段中返回报错信息返回标准错误码加说明返回自定义错误结构错误码含义不同流式输出数据帧格式统一数据帧格式有差异需要单独解析这些差异一旦叠加在同一个代码库里就会变成大量适配代码。比如我在一个项目里同时接了三家服务光是请求体的组装函数就写了六七个版本每个版本还带自己的异常处理逻辑。后面为了排查一次“生成结果为空”的问题我不得不分别打开三套文档对着三家不同的返回结构做比对前前后后花了大半天。这些时间本来不应该花在“连接口都调不通”这种体力活上。litellm这种工具的存在本质上就是为了消除这层重复劳动。它把“不同供应商的差异”集中在一个配置层解决让下游业务永远只面对一套稳定的协议。1.2 它本身不是什么魔法请求代理的运转逻辑我第一次接触litellm时一度以为它是个复杂的模型调度框架需要在项目里引入重型依赖。实际了解下来它的核心逻辑比我预想的简单直接它是一个请求代理。用一个生活化的比喻来说它就像前台接待员。你不需要知道公司里每个具体部门的人长什么样、坐在哪个工位只需要把需求告诉前台前台会根据你的需求去找对应的部门沟通再把结果带回来交给你。litellm扮演的角色就是那个前台业务方永远用同一套方式“提需求”至于后面到底是哪家模型服务在真正响应业务方完全不感知。这个“前台”在技术实现上就是一个常驻的代理服务。启动后它会在本地监听一个端口对外提供与主流模型服务高度兼容的HTTP接口。业务代码只要把请求发到这个地址网关层会负责解析、鉴权、转发、接收响应再把响应换算成统一格式返回给调用方。很多人可能会问多加一层网络请求是不是意味着性能损耗这个损耗确实存在但通常非常小。请求本身只有一次额外的本地转发内网环境下基本在毫秒级。相比它带来的“业务代码零适配”收益这点开销几乎可以忽略。我在生产环境实际压测过加了这层代理之后单次请求的延迟增加大约在个位数毫秒用户体验上完全无感。2. 从零部署虚拟环境、配置文件和第一次代理启动2.1 安装与最简单的启动方式litellm的安装官方推荐使用Python环境安装命令也很简单pip install litellm[proxy]这里要注意一个细节安装包名称后面带了[proxy]后缀这表示要安装带代理功能的完整版本。如果只装litellm本体你只能使用Python SDK的调用能力起不了代理服务。不少第一次上手的人就是在这踩了坑装了之后发现没有litellm --port这个命令。安装完成后最简单的启动方式是这样litellm --model 某海外大模型服务/某模型名 --port 8000直接通过命令行指定模型和端口litellm就会启动一个最基本的代理。这种模式适合快速验证工具本身好不好用。但说实话我在项目里几乎不会用这种方式跑生产因为它所有配置都靠命令行参数时间长了根本记不住哪条命令对应哪个环境。更推荐的做法是写配置文件。还有一个比较重要的事我建议用虚拟环境来装。因为litellm的依赖比较多而且和很多AI相关的Python库存在版本交集直接装到系统环境里很容易和项目里的其他库相互干扰。我后来就把所有模型网关相关的东西统一放到了独立的虚拟环境中需要升级或者排查依赖问题时直接重建虚拟环境干净利落。2.2 config.yaml里的关键字段说明生产环境部署时我的习惯是把所有模型供应商信息写进一个YAML配置文件。这样做的好处是模型增删改都只需要改配置文件然后重启代理即可不需要改动任何业务代码。一个最基础的配置文件长这样model_list: - model_name: gpt-like litellm_params: model: 某海外大模型服务/某模型名 api_key: os.environ/某海外服务密钥 - model_name: gpt-like litellm_params: model: 某国内开源模型服务/某模型名 api_key: os.environ/某国内服务密钥 api_base: https://某国内服务地址/v1 litellm_settings: drop_params: true set_verbose: true general_settings: master_key: sk-1234 database_url: os.environ/DATABASE_URL逐项解释一下。model_list是整个配置的核心它定义了这个网关能代理哪些模型。注意每一项里都有model_name和litellm_params两个部分。model_name是对外暴露的名称业务方调用时只知道这个名字litellm_params则是实际转发时需要用到的参数包括真正的模型标识、供应商的密钥、请求地址等。这里有个很关键的设计点model_name可以重复。也就是说你可以让两个不同的真实模型共用同一个对外名称。这个特性后面在做自动切换和负载均衡时非常有用先记着。litellm_settings里我比较关注drop_params这个选项。前面说过不同供应商支持的超参数种类不一样如果把供应商A的参数传给供应商B对方可能直接报错。开启drop_params之后litellm会自动过滤掉目标供应商不支持的参数避免请求失败。这个选项在接入多家供应商时几乎必开。general_settings里的master_key是网关的管理密钥客户端请求时需要在请求头带上这个密钥才能访问。database_url是可选配置用于持久化请求日志和预算数据。生产环境建议配上数据库否则网关重启后日志就丢了。2.3 用curl验证代理转发是否正常配置写好后用一条curl命令就能验证整个链路是否打通curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-1234 \ -d { model: gpt-like, messages: [ {role: user, content: 你好请用一句话介绍你自己} ] }如果一切正常你会收到一段结构完整的响应里面有生成的内容、使用的模型、token用量等信息。看到这段响应说明代理服务已经成功把你的请求转发给了真实的模型服务并拿到了结果。第一次验证时如果遇到问题优先看两个地方。一是启动代理的那个终端窗口有没有输出错误日志大多数转发失败的原因都会直接打在那里二是确认配置文件里的密钥和模型名是否真实有效。我曾经因为复制配置时多了个空格导致认证一直失败排查了半天才发现是YAML解析后的值尾巴上带了个不可见字符。2.4 只需改base_url业务代码几乎不动代理跑通之后业务代码的改动可以说小到令人惊讶。以官方SDK风格的调用方式为例改之前是这样的from 某海外大模型服务 import Client client Client(api_key真实密钥) response client.chat.completions.create( model某模型名, messages[{role: user, content: 你好}] )改之后是这样from 某海外大模型服务 import Client client Client( base_urlhttp://localhost:8000/v1, api_keysk-1234 ) response client.chat.completions.create( modelgpt-like, messages[{role: user, content: 你好}] )看到区别没有业务代码几乎原封不动唯一变化的是base_url指向了本地代理api_key换成了网关的密钥model换成了网关里配置的对外名称。这一点极其重要。这意味着你不需要为litellm引入任何新的SDK不需要重写调用逻辑甚至连已有的调用代码都可以直接复用。我接手的一个老项目里原来的调用代码还停留在比较旧的参数写法上我本来以为要花大力气改造结果只是把base_url和api_key两个配置项替换掉接口就跑通了这个体感真的很爽。3. 统一响应格式一份日志和一套错误码带来的连锁便利3.1 响应体结构统一后下游处理简化了多少调用跑通只是第一步真正让业务代码变清爽的是响应格式的统一。在没有网关时不同供应商返回的JSON结构五花八门。有的把生成内容放在一个多层的嵌套结构里有的放在顶层的某个字段里还有的在返回内容的同时附带一堆你根本用不到的信息。业务端为了兼容这些差异往往会写好几个解析函数每个函数负责一种结构再通过分支判断决定走哪个解析逻辑。接入litellm之后所有响应都会转换成标准结构生成内容统一放在固定位置token用量统一放在另一个固定位置整个响应体的层次结构完全一致。我原来有七八个分支的解析函数直接删掉绝大部分只保留了一个针对标准结构的解析逻辑。代码量一下子少了很多可读性和可维护性都上来了。更重要的是新同事接手项目时不用再逐个了解每家供应商的响应结构了只需要看一份文档——就是litellm对外输出协议的标准说明上手成本大幅降低。3.2 错误码与重试逻辑的统一处理响应格式统一之外错误码体系的统一是我一开始没预料到的增值点。未引入网关前不同供应商的报错风格差异很大有的会返回HTTP层错误有的会在响应体里放自定义错误码有的错误信息是一大段英文描述很难直接映射到业务逻辑里。这就导致我在业务代码里写重试逻辑时得针对不同错误类型分别判断哪些值得重试、哪些不重试规则乱成一团。litellm把所有错误都映射成了一套统一的错误类型。比如上游模型服务被限流时即使原始报错五花八门通过网关出去的标准响应里业务端看到的都是统一的限流错误标识这是一个非常关键的收敛。业务端重试策略只需要判断这一种错误即可统一限流错误说明上游资源暂时不足可以保守重试统一超时错误说明请求处理时间过长可能需要调整超时时间或改用更快的模型统一鉴权错误说明密钥或配置有问题这类错误重试没有意义直接告警。我把这套逻辑用在了项目里的一个批处理任务上。之前对接多家供应商时重试参数五花八门现在全部收敛成统一策略批处理任务的失败率肉眼可见地下降了。3.3 流式输出来回切换的实际效果除了普通响应流式输出的一致性也值得一提。很多应用在做打字机效果时需要用到流式输出也就是数据一段段实时返回。不同供应商的流式输出格式差别同样很大有的按行返回有的按事件块返回有的还夹杂心跳包和额外元数据解析成本非常高。通过litellm做流式转发后业务端收到的就是统一的流式格式。我在项目里写了一个简单的测试分别用三家模型跑同一段流式输出然后对比收到的每一帧数据结果前缀部分完全一致帧结构也相同。这意味着前端展示逻辑只写一套流式解析就能通用不用再针对不同供应商做特殊处理。我还试过在同一个请求里来回切换普通模式和流式模式只要在请求参数里改一个布尔值就可以网关层会正确处理不需要业务端做任何额外的适配工作。4. 多模型自动切换、负载均衡与失败重试的配置策略4.1 把三个模型配置成一个“虚拟模型组”的具体写法前面提到model_list里的model_name可以重复这个特性可以用来实现“多模型对外暴露为同一个名称”的效果。我把三个不同供应商的模型配置成了同一个对外名称业务方完全无感知但网关层已经具备了三选一的能力。配置写法大致如下model_list: - model_name: unified-model litellm_params: model: 供应商A/模型A api_key: os.environ/密钥A - model_name: unified-model litellm_params: model: 供应商B/模型B api_key: os.environ/密钥B - model_name: unified-model litellm_params: model: 供应商C/模型C api_key: os.environ/密钥C三个真实模型共享同一个对外名称。你可能会问它们效果完全不同对外暴露同一个名字业务方怎么知道用哪个答案是业务方不需要知道。这种配置方式通常搭配路由策略使用由网关层决定每次请求实际转发给哪个模型。这样配置之后想要在三个模型之间切换不再需要改业务代码只需要调整网关层的策略就行。我在一个内部工具项目里就是这么配的。不同时候不同模型的可用性、效果、成本都在变化有了这层虚拟模型组我可以随时把某个表现不好的模型从路由池里摘掉或者调低它的权重全部在配置文件里完成整个过程业务方无感知。4.2 负载均衡与重试的细节参数配置好多个模型之后litellm会默认按顺序轮询分发请求但真正生产环境肯定要做更精细的控制。我实际使用的配置里加了这样几项参数litellm_settings: num_retries: 3 request_timeout: 30 fallbacks: [{ unified-model: [供应商A/模型A, 供应商B/模型B] }] allowed_fails: 3 cooldown_time: 60逐项说下我的理解。num_retries是同一个请求在遇到暂时性错误时的重试次数我设为3次。注意这里有一个值得注意的机制超过指定次数后如果仍然失败litellm会按照fallbacks配置的顺序把请求转发给备用模型。比如主模型限流了重试几次都不行就直接切换备用模型这个机制对线上可用性提升非常明显。request_timeout是网关等待上游模型响应的最长秒数。设得太短大模型思考时间长一点就会被判定为超时设得太长上游如果卡住了业务端会一直干等。30秒是一个比较折中的值大家可以根据实际请求长度调整。cooldown_time是熔断冷却时间。当某个模型连续出错达到一定次数后litellm会在一段时间内自动把它标记为不可用不再往它上面分发新请求。这个机制能有效避免一个故障模型持续拖垮整个网关。4.3 权重策略在真实项目中的使用心得除了默认的轮询方式litellm还支持按权重分配流量。我比较常用的一种策略是按照成本来分配便宜模型分到的流量多一点贵的模型只在必要时使用。具体写法是在每个模型的litellm_params里加一个权重字段配好后每次请求按权重做随机选择。比如我给模型A配置了较高的权重意味着平均每十次请求有较多次打给它剩余次数打给模型B。权重策略在我实际使用时主要解决了两个问题。一个是成本控制如果某个模型虽然效果好但贵我会给它一个较低的权重让便宜的模型分担大部分简单请求。另一个是灰度验证新接入一个模型时先给它一个小的权重让它承接一小部分流量观察一段时间没有问题后再逐步调高权重相当于一种流量灰度机制。不过这里要提醒一句权重策略无法保证同一用户的不同请求特性被保留比如希望某个用户的所有请求都打到同一个模型上权重模式做不到这种情况需要按其他维度做路由最好提前想清楚自己的业务是否需要会话级或用户级一致性。5. 生产环境必踩的坑与我的修复过程5.1 版本重复安装带来的兼容性问题这个坑我印象很深。最开始我是图省事直接用系统Python环境装了litellm后面有一个项目引入了另一个依赖库安装时顺便把litellm的依赖版本升了一下。结果整个网关直接起不来了报了一个很长的依赖冲突错误关键信息藏在日志中间不仔细看根本发现不了。后续排查发现系统环境里同时存在两个版本的litellm相关文件一个被旧项目引用一个新项目又在用另一个版本两个版本之间互相覆盖导致加载时出现了错乱。这个问题最终的解决办法有两条缺一不可。第一条给litellm单独建一个虚拟环境和项目环境隔离。第二条在虚拟环境里固定litellm的版本号安装不轻易升版本。后续每次要升级时先在测试环境跑一遍主要流程确认没有兼容性问题后再更新生产环境。这看起来是常识但我在这个坑上花掉的时间一点都不少写出来是希望后来者别再重复一次。5.2 流式输出超时与keep-alive参数另一个让我折腾比较久的问题是流式输出偶尔会“卡住”。具体现象是打字机效果刚开始时一切正常输出到一半突然停住过了一段时间前端报超时错误。刚开始我以为是模型生成太慢调大了请求超时时间问题依旧。后来我去翻了网关的日志发现请求其实一直在正常转发上游也有数据陆续返回但客户端和网关之间的连接被断开了。原因在于流式请求的处理时间较长而承接业务端与网关之间的连接服务在空闲一段时间后主动关闭了连接。前端没有收到任何数据自然就认为请求超时了。解决办法是在网关和业务端的连接层配置保持活跃参数确保长连接不因空闲被回收。同时在流式请求的协议层约定一个心跳机制每隔一段时间发送一个空消息提醒连接不要关闭。改完之后再长的输出也能稳定跑完。这个坑比较隐蔽因为报错信息看起来像模型问题实际却是网络连接层面的问题。排查链路值得记录先确认上游是否有数据再确认数据是否到达网关最后确认网关到业务端的连接是否稳定一环一环拆开看很快就能定位到问题出在哪个环节。5.3 并发额度较低时的队列表现第三个坑比较让人头疼和上游模型服务的并发限制有关。我当时部署的某个开源模型的推理服务并发能力比较有限而网关这边的请求量又不小导致大量请求打到上游后被限流拒绝。由于网关本身有重试机制这些请求会进入重试队列重试又继续被限流最终形成了一种请求雪崩网关的线程池被占满所有请求都在排队等待重试包括那些原本可以正常处理的请求。发现问题后我做了三个调整。第一在litellm的配置里给这个模型设置了一个每分钟请求速率上限给上游一个喘息空间。第二关闭了对限流类错误的自动重试让这类请求快速失败而不是反复打上去。第三在业务端加了一个信号量限制同时进行的请求数量避免一次性涌入太多。调整之后整个系统的稳定性明显好转正常请求的响应时间也降下来了。这个坑的本质是重试机制和限流叠加产生了放大器效应理解了这个因果链条许多并发场景下的类似问题都能看穿。5.4 启动后找不到配置文件的排查链路最后这个小坑很基础但几乎人人都会遇到我在某个目录下启动litellm时用了相对路径指定配置文件启动时报找不到文件。单独看这条命令一点问题没有但换到另一个目录去启动时相对路径指向的位置就不存在了。这不算什么高深问题但解决思路值得总结。我把配置文件的路径改成了绝对路径同时在启动脚本里显式指定配置文件位置避免依赖“当前所在目录”这个隐式状态。后来我干脆把启动命令封装成了一个固定的启动脚本脚本里固定好虚拟环境路径、配置文件路径、日志输出路径每次直接执行脚本即可。实践下来这样不仅避免了路径问题还大大降低了误操作的概率。6. 预算、密钥与审计日志网关该管的事别交给业务代码6.1 把密钥管理收敛到config.yaml之后多模型时代密钥管理是个容易被忽视的问题。假设业务代码直接调用了供应商的密钥那这些密钥就会散落在各个项目的环境变量里、配置文件里甚至可能被提交到代码仓库。一旦泄露风险和排查成本都很高。把litellm当作统一入口之后所有供应商的密钥都集中存放在网关配置层业务代码只认网关的密钥。这就把密钥的暴露面从“多份”收敛到了“一份”。我在项目里配合密钥管理服务来存放配置里的敏感信息整个系统对供应商密钥的引用全部通过环境变量注入不在任何明文配置文件里出现。这里要特别提一下业务端使用的网关密钥和上游供应商密钥最好严格分离。业务端只需要拥有调用网关的权限而真实的供应商密钥只存在网关侧。这样即使某个业务端的密钥泄露影响范围也仅限于这个业务端能调用的模型不至于把上游供应商的全部账户权限都暴露出去。6.2 预算限制字段与实际触发效果litellm还提供了预算控制能力可以在配置里限制单个密钥、单个模型或整个网关的月度消耗上限。我在项目里给内部工具配置过一个月的总预算设置方式是在general_settings里加预算字段general_settings: master_key: sk-1234 max_budget: 100 budget_duration: 30d意思是30天内总消耗预算为100美元。当累计消耗达到这个上限时网关会自动拒绝新的请求并返回预算超限错误。测试时我特意用一个临时密钥配置了极低的预算触发之后观察到的现象是业务端收到的是统一格式的预算错误日志里也能看到网关记录了预算超限的审计信息。这个功能最大的价值在于给业务方一个“安全垫”。特别是那些对成本敏感的自动化任务如果因为模型调用失控而产生高额账单预算限制能避免损失扩大。我个人建议每个接入网关的内部项目都设置预算上限哪怕设置得宽松一些也好过完全没有防线。6.3 用请求日志定位线上问题的流程最后说说审计日志。litellm支持把每一次请求的详细信息记录到数据库包括请求时间、调用的模型、消耗的token数量、响应耗时、状态码等。这些日志在线上问题排查时价值极高。有一次线上反馈某个功能变慢了我通过日志筛选出那个时间段内的请求记录按响应耗时排序发现耗时长的主要集中在某个特定模型上而其他模型基本正常。进一步分析之后确认是那个模型的推理服务负载过高导致的于是我把网关里该模型的权重降低把流量分流到其他模型上问题很快缓解。整个过程大概花了十分钟如果没有日志排查这个问题的链路会漫长得多。建议不要等出问题了再考虑加日志从一开始就配上数据库存储日志养成看日志的习惯。日志就是系统的黑匣子关键时刻能帮你省下大量时间。最后分享一个我个人的小习惯每次修改配置文件前都先备份一份带日期的副本再动原文件。这个习惯帮我避免了好几次“改坏配置但不知道改了什么”的尴尬。另外升级litellm前我会先跑一个测试脚本把常用的模型调用、流式输出、错误重试全部走一遍确认没有回归再正式切换版本。这些看起来不起眼的细节长期累积下来是让整个网关稳定运行的重要保障。