litellm实战:统一多模型API代理的部署、负载均衡与成本治理

发布时间:2026/10/12 7:40:36
litellm实战:统一多模型API代理的部署、负载均衡与成本治理
1. litellm解决的真实痛点团队里乱七八糟的多模型调用1.1 未接入前我的日常先说说我在接入litellm之前的工作状态。我们团队做AI应用开发手上有好几个不同厂商的大模型API要调用有按OpenAI格式的有带独立SDK的还有自建内部模型的。每个模型一套调用方式每个厂商一套鉴权逻辑写业务代码的时候最烦的不是模型效果好不好而是API格式不统一。那时候我们代码里到处都是封装层今天接这个SDK明天换那个HTTP客户端。每引入一个新模型就得给业务团队发一份接入文档告诉他们该装哪个包、怎么写client、怎么处理鉴权。有一次某厂商升级了SDK的请求参数结构我们线上服务直接报错排查了快两个小时才定位到是SDK版本兼容问题。这种经历我相信很多做AI应用开发的同行都有过。后来我意识到问题的根源不是某一个SDK不好用而是我们缺少一个统一的模型访问层。业务代码不应该关心模型背后的厂商是谁也不应该关心鉴权方式、请求格式、API地址这些细节。它只需要知道我要发一条消息模型返回一个回答就这么简单。litellm做的就是这件事。1.2 代理模式比SDK封装好在哪市面上其实也有不少多模型封装SDK比如某些开源的统一调用库可以在代码里import之后直接切换模型厂商。但我在实际使用中发现SDK封装解决不了几个问题。第一SDK封装只解决了代码层的统一解决不了服务层的治理。如果公司里有多个团队都在调用模型每个团队各自接入SDK那账号、密钥、配额、成本、日志依旧是一盘散沙根本没法集中管理。第二SDK封装不方便做流量的统一控制。你想在某个模型出故障的时候自动切到备用模型想在高峰期限制每个团队的使用量想记录每一次请求的token消耗和成本——这些事情在业务代码里实现会侵入到每一处调用点改起来很痛苦。第三SDK封装没法做到语言无关。我们团队Python和Node都在写Java那边也有需求。如果每个语言各维护一套封装工作量直接翻倍。litellm的架构不一样。它运行的时候就是一个独立的代理服务暴露出一个OpenAI兼容的HTTP接口。所有业务代码不再直连各个模型厂商而是统一请求到litellm由litellm负责把请求转换成对应厂商的格式再把结果返回给业务方。这样一来业务侧只需要维护一份调用逻辑语言随便换模型侧的统一鉴权、负载均衡、fallback、日志、限流这些能力全部下沉到代理层。我后来把团队里所有模型的调用都迁到了litellm上业务代码里再也没出现过厂商SDK的痕迹。2. 最小可用部署从安装到跑通一个代理2.1 环境准备与安装litellm作为代理服务运行在Python环境里Python 3.8以上基本都能装。我这边用的是3.10跑了大半年没出过什么问题。安装分两种方式直接用pip装或者用Docker跑。# 安装带代理功能的完整包 pip install litellm[proxy]如果只想装核心库不带代理服务pip install litellm我建议直接装带proxy的完整版因为代理模式是litellm的主流用法后面迟早用得上。特别的litellm[proxy]这个安装方式会把FastAPI、Uvicorn这些Web服务依赖一起拉下来所以装完就能直接启动代理不用再单独配服务框架。Docker方式更适合直接上生产。litellm官方维护了镜像我常用的是ghcr.io下的镜像tag按版本号或者最新的main拉取都行。docker run \ -p 4000:4000 \ -v $(pwd)/config.yaml:/app/config.yaml \ --env-file .env \ ghcr.io/berriai/litellm:main-latest注意这里我把配置从宿主机挂载进容器密钥通过env文件注入。千万别把密钥直接写进config再打包进镜像这个习惯非常危险。后面我会专门说这个问题。2.2 一份能直接用的config.yamllitellm代理的核心配置是一份YAML文件里面定义了模型列表和代理行为。我第一次看到这个配置的时候觉得很简单但实际跑起来才发现有些细节得想明白。先看一份最小可用的配置model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: claude-3-5-sonnet litellm_params: model: anthropic/claude-3-5-sonnet api_key: os.environ/ANTHROPIC_API_KEY这里有两个字段特别容易搞混。model_name是外部客户端调用时用的名字比如业务代码请求里写的是gpt-4o这个才是业务方看到的模型名。而litellm_params.model是litellm内部真正路由到目标厂商时用的标识它往往带一个前缀比如openai/、anthropic/、ollama/。为什么要这么设计就是为了让外部模型名和内部真实模型解耦。以后你想把gpt-4o背后实际路由的模型换成别的只需要改代理配置业务代码里的模型名一个字都不用动。这在后面做模型灰度切换的时候价值巨大。启动代理只需要一行命令litellm --config config.yaml --port 4000看到Uvicorn running on http://0.0.0.0:4000这样的日志代理就起来了。2.3 用curl验证代理生效服务起来之后先别急着接入业务代码用curl验证一遍代理是否真的能转发请求。先看模型列表有没有暴露出来curl http://localhost:4000/v1/models \ -H Authorization: Bearer sk-1234这里的sk-1234是代理的master key需要在config里通过general_settings配置或者通过环境变量LITELLM_MASTER_KEY注入。没配master key的情况下默认也能跑但代理会直接裸奔任何拿到端口访问权限的人都能用你的模型额度一定要配。然后发一条真实的对话请求curl http://localhost:4000/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-1234 \ -d { model: gpt-4o, messages: [{role: user, content: 你好}] }正常情况下你会收到一个choices数组structure和OpenAI官方格式完全一致。到这里一个最小可用的litellm代理就已经跑通了。我个人的建议是这一步不要跳过因为config里的坑往往在这一步就暴露出来。比如有的模型地址不对、密钥没读到、模型前缀写错curl一发立刻就能看出来比接到业务代码里再排查省事多了。3. 核心功能配置虚拟Key、负载均衡、fallback和预算3.1 虚拟Key的权限隔离代理跑通了但如果只是把所有人的请求都转发出去那和直接把厂商key发给每个人没什么区别。litellm真正的治理能力从虚拟Key开始。虚拟Key的概念很简单你在代理上生成若干把key每把key可以绑定特定的模型、设置自己的预算上限、记录独立的调用量。业务方拿着这些虚拟key来请求代理不再直接接触厂商的真实key。生成虚拟key调用的是代理自己的管理接口curl -X POST http://localhost:4000/key/generate \ -H Authorization: Bearer sk-1234 \ -H Content-Type: application/json \ -d {models: [gpt-4o], max_budget: 10}这个请求会返回一把新的虚拟key比如sk-abc123。这把key只能调用gpt-4o这一个模型累计花费到10美元就会自动停止由代理返回额度不足的错误而不是继续消耗预算。我在公司里是按团队维度来生成虚拟key的。比如A组做客服机器人、B组做知识库问答两边各拿各的key互不影响。某天某个团队的key被外部泄露了我只需要在代理上把这一把key删掉其他团队完全不受影响。这比把厂商的真实key发给所有人然后再挨个重置要安全太多了。3.2 同一模型挂多个后端做负载均衡litellm的负载均衡设计我很喜欢的一点是它针对的是同一个对外模型名下挂多个实际后端的情况。比如你的对外模型叫gpt-4o但背后既挂了官方直连通道又挂了某个云厂商的托管通道。业务方无感知代理自己决定每个请求走哪个后端。配置方式是把多个provider的配置都写在同一个model_name下面model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY model_info: timeout: 30 rps: 5 - model_name: gpt-4o litellm_params: model: azure/gpt-4o api_key: os.environ/AZURE_API_KEY api_base: https://your-resource.openai.azure.com/ model_info: timeout: 30 rps: 3两个配置共用同一个model_namelitellm会在请求进来的时候根据路由策略决定走哪个后端。默认策略是简单的轮询但也可以配成基于延迟的latency-based-routing、基于成本优先、甚至基于每个后端当前剩余配额的复杂策略。这里有个很重要的点不同后端的model_info里可以标注不同的超时时间和并发上限。因为不同厂商的响应速度差别很大如果不做区分慢的那个后端会把整体的超时时间都拖长。我实际跑下来感觉latency-based-routing在多个后端质量差异明显时效果最好。它会把请求优先分给最近响应快的后端这样线上接口的平均耗时能降下来不少。但要注意延迟策略依赖足够的请求采样刚上线流量小的时候还是先老实轮询比较稳妥。3.3 fallback配置与切换逻辑模型故障是整个AI应用里最难处理的突发情况之一。厂商服务不可能做到100%可用尤其是高峰期长文本生成任务经常一路超时。以前我们靠写死重试逻辑硬扛后来发现litellm的fallback机制才是正解。fallback可以在请求层面配置也可以写死在代理的router设置里。我推荐在config的统一位置配置这样团队里的人不需要感知切换逻辑。router_settings: fallbacks: [ {gpt-4o: [claude-3-5-sonnet]} ]意思是当gpt-4o这个模型请求失败时自动改发到claude-3-5-sonnet。业务方收到的结果依然是正常结构的响应完全不知道背后经历了故障切换。不过有个细节得提醒fallback的触发条件不只有超时还有HTTP返回错误、连接失败等等。但经常被忽略的是流式请求的fallback处理和非流式完全不同。流式请求中间断流了litellm在部分版本里不会自动触发fallback可能要等整条流超时才算失败。所以如果你的业务重度依赖流式输出fallback的配置要额外验证一下。另外fallback之后请求耗时往往比正常情况翻倍。因为第一个请求已经耗费了完整的超时时间才轮到备用模型上场。所以不是所有的失败都适合fallback比如对实时性要求极高的场景宁可快速失败也不要等一个慢吞吞的备份响应。这个取舍在配置前要和业务方对齐。3.4 预算跟踪和限制AI应用上线之后最怕的不是功能出bug而是成本失控。我见过不止一个团队月底对账的时候发现某条prompt循环调用把自己烧了几百美元而日志里根本找不到是谁干的。litellm的成本控制能力算是帮我解决了这个烦恼。预算限制可以在虚拟key维度设置也可以在team维度设置。上面的key/generate请求里带max_budget字段就是单key的总预算上限。更细一点还可以配budget_duration控制周期比如每月重置一次配额。成本数据的查看也方便代理的管理接口有完整的记录curl http://localhost:4000/spend/logs \ -H Authorization: Bearer sk-1234返回的是每一次请求的模型、虚拟key、token用量、预估成本、耗时等明细。有意思的是litellm的成本计算不是拍脑袋按总量估它有一套按不同厂商的token单价表按输入输出分别计价。模型没覆盖到的还可以自己在config里补model_info的定价信息。这些数据还能往Prometheus之类的监控系统里导。我在团队里把成本指标接上了告警单日成本超过阈值就通知现在成本异常基本当天就能发现再也不用等月底对账的时候吓一跳了。4. 业务代码接入改三行就能从直连切到代理4.1 最小改动示例把业务从直连厂商切换到litellm代理最舒服的事情就是你不需要重写调用逻辑。只要你的代码原本是按OpenAI格式写的切换只需要改base_url和api_key两个参数。以Python为例原本直连官方API的代码from openai import OpenAI client OpenAI( api_keyos.environ[OPENAI_API_KEY], )现在改成走litellm代理from openai import OpenAI client OpenAI( api_keysk-abc123, # 代理生成的虚拟key base_urlhttp://your-litellm-host:4000, )然后继续response client.chat.completions.create( modelgpt-4o, messages[{role: user, content: 介绍一下代理的工作原理}], ) print(response.choices[0].message.content)代码逻辑一行没动只是换了个请求地址和key。因为litellm自己就是OpenAI格式兼容的client还以为自己在连官方服务。这个体验是我最喜欢的部分——团队成员几乎不需要学习成本就能把流量切到代理上。4.2 流式输出和函数调用的适配问题不是所有情况都像上面那么丝滑。流式输出和函数调用这两个场景我在接入时确实踩过一些坑。先说流式输出。如果业务用的是streamTrue那么在litellm这边写法基本一致stream client.chat.completions.create( modelgpt-4o, messages[{role: user, content: 写一段长文案}], streamTrue, ) for chunk in stream: print(chunk.choices[0].delta.content or , end)大部分模型厂商的流式协议都能被litellm转成OpenAI风格的chunk结构。但不同厂商对流式事件的定义不同有的先推一个空内容事件有的在最后推一个usage事件。litellm会做统一转换但在某些边缘case上比如中间网络闪断流会卡住而不是报错。我建议业务侧对流式请求做一次应用层的超时兜底不要完全信任底层连接。函数调用tool call这块稍微麻烦一点。不同厂商的函数调用参数格式差异很大比如有的用tools顶层字段有的用functions字段。litellm能转换大部分主流厂商的格式但转换的前提是你能把厂商的参数准确映射到OpenAI规范上。我遇到一个实际案例某个模型支持函数调用但它的参数schema里带了一些OpenAI规范没有的属性比如自定义校验规则。请求经过litellm转换后这些自定义属性被丢弃了导致模型生成的参数和预期不符。最后解决方式是在业务代码里把自定义属性提前从schema中剥离只保留标准字段给代理转发。4.3 多模型灰度和模型下线怎么操作litellm的模型名和实际后端解耦这个特性在做灰度切换的时候帮了大忙。比如我手里的旧模型要退役新模型上线需要先让少量流量试试。传统做法是改业务代码把模型名从旧的换成新的然后让一部分用户走新分支。这样改动的面比较大而且一旦新模型效果不好回滚又要改一次代码。用litellm就简单多了。我在代理里加一个新配置让对外模型名不变但实际后端指向新模型。然后把新配置的权重调低让一小部分请求自动走到新模型上观察几天如果指标正常再逐步调高权重直到全部流量切到新模型。这里有一个具体的配置思路model_list: - model_name: production-model litellm_params: model: openai/old-model api_key: os.environ/OPENAI_API_KEY - model_name: production-model litellm_params: model: openai/new-model api_key: os.environ/OPENAI_API_KEY默认轮询下两个后端是平均分配的。如果需要按比例分流就得用litellm的路由策略来调整。比如某个版本支持根据请求头里的自定义字段分流这样可以把内部测试流量定向到新模型外部用户继续走旧模型灰度阶段的控制粒度更精细。模型下线就更简单了。在代理里删掉对应的配置业务代码完全不用动。如果发现某个模型还有人在调用但配置已删代理会返回明确的模型不存在错误这时候去查日志就能看到是哪个key在调用直接找对应的负责人。5. 上线四个月遇到的坑按排查思路逐个说5.1 升级版本后接口路径不兼容litellm的迭代速度很快功能更新频繁但这意味着你要小心处理版本升级。我有一次把代理从某个老版本升到新版本升完发现管理接口的路径变了之前的key/generate接口返回404。查了官方更新说明才知道新版本把一类管理接口全部收到/v2路径下旧路径虽然还在但标记为废弃。那次升级让我养成了一个习惯每次升级前先读更新日志升级后第一时间跑一遍冒烟测试把核心的接口挨个请求一遍确认没有破坏性变更再放量。千万别直接在生产上执行升级也别相信“小版本可以随便升”这种话。另外要说一下litellm本身也提供了Python SDK方式可以直接在代码里import litellm然后调用litellm.completion()。这种方式不需要起代理服务适合单机脚本或者原型验证。但我个人还是倾向于跑独立代理因为代理方式才能把日志、鉴权、成本这些统一管控起来。如果你只是想在脚本里快速试试多个模型用SDK模式更轻快。顺便提一下SDK模式的示例方便对比import litellm response litellm.completion( modelgpt-4o, messages[{role: user, content: 讲个笑话}], fallbacks[claude-3-5-sonnet], )这里fallbacks直接在请求参数里指定适合不需要集中管控的临时脚本。团队正式业务还是建议走代理。5.2 模型参数差异导致的隐性报错不同厂商的模型虽然都被litellm转换成了OpenAI格式但底层参数上限千差万别。最常见的是max_tokens的问题。某厂商的模型允许max_tokens设到32k另一个厂商最大只能到8k你业务代码里统一写了一个很大的值请求打到那个上限低的模型上就会报错。这类问题排查起来很头疼因为错误信息往往只在某个特定模型上出现而平时的流量都走的是正常模型。后来我在config里给每个模型都标注了model_info把上下文窗口和最大输出token数写清楚litellm在转发的时候会做一层参数校验至少能把错误早一点暴露出来而不是等模型厂商那边返回一堆看不懂的报错。还有一个容易踩的坑是temperature和top_p的行为差异。有些模型对temperature0有特殊处理有些模型在top_p0.5时的随机性表现和别的模型完全不同。这些差异在单模型调试时根本看不出来一旦多个模型共用同一套业务参数输出质量就会飘忽不定。我的经验是针对每个模型单独调参然后把参数模板固化在业务配置里不要用一个通用参数集打天下。5.3 日志与SQLite磁盘占用的隐患litellm默认会把请求日志写到内置的SQLite数据库里。这个设计在小流量时很省事不需要额外部署数据库但流量上去之后很容易出问题。我遇到过的情况是某个业务高峰跑了一整天日志表里攒了几十万条请求记录SQLite文件直接膨胀到好几个GB。接着代理开始出现写入延迟某些请求因为日志写入超时而返回错误。后来我把数据库切到了PostgreSQL才彻底解决。如果不想换数据库也可以用定期清理日志的策略比如写个定时任务删除超过30天的记录。但说实话生产环境跑到一定规模还是尽早切换到独立数据库比较靠谱。顺便说一句日志记录默认是异步的但在SQLite这种锁粒度比较粗的数据库上高并发写入还是会让延迟明显上升。磁盘空间还有一个隐性的占用点如果开了请求/响应体缓存大量的prompt原文和响应结果也会存进数据库。这个功能要谨慎开启尤其是涉及敏感数据的业务建议只在测试环境用。5.4 高并发下数据库连接池错误把数据库切到PostgreSQL之后又遇到一类新的问题代理在高并发下会出现数据库连接池不足的报错。原因是litellm默认的连接池配置比较保守而我们的业务经常有几个并发量很大的入口同时打过来。排查过程比较曲折。一开始以为是数据库本身扛不住后来看了数据库侧连接数指标发现根本没到上限反而是代理进程因为连接池拿不到连接直接报错。这时候翻配置文件把连接池相关的参数调大问题才消失。这类问题提醒我做代理服务的时候不要只盯着业务接口的并发底层依赖的数据库连接、上游模型的延迟、甚至是日志写入的吞吐量任何一个环节都可能成为瓶颈。排查的时候要从请求链路的每一环分开看指标而不是一上来就怀疑最大名气的那个组件。5.5 慢请求排查链路最后一个值得讲的是慢请求的排查思路。代理上线一段时间后我们收到反馈说某些请求特别慢。起初以为是模型响应慢但看litellm日志发现耗时主要消耗在等待模型厂商返回的阶段。后来进一步分析发现慢请求集中的时间段正好是某个后端模型的高峰期fallback又因为这个模型还没到超时阈值而一直没有触发。也就是说一个本来应该在10秒内返回的请求硬生生等了将近40秒才回复。根因是fallback的超时配置和实际业务容忍时延不匹配。我把fallback的触发超时调短了一些同时在config里对每个模型标注了更合理的超时时间。改完之后慢请求数量大幅下降。这类问题不复杂但容易被人忽略——错误处理逻辑不是配了就完事得结合你的业务真实时延去调调完还要定时复盘。我在实际部署这套代理之后最深的体会是litellm真正解决的不是一个技术问题而是一个工程治理问题。它让模型调用这件事从“每个团队各搞一套”变成“平台统一接入、统一管控”。不管你的团队规模多大只要有超过两个模型要接就值得花一个小时把它部署起来。刚开始可能觉得多了一层服务但跑上一个月回头看你会庆幸自己在早期就做了这个决定。