在实际项目里只要一个应用同时对接过两家以上不同家的大模型服务多半会经历一段让人头疼的日子每家提供商的接口风格不一样、字段命名不一样、限流策略不一样连报错信息都各有脾气。后来我接触到 LiteLLM 这个开源项目发现它正好把这块最琐碎的兼容工作集中解决了。这篇博文不是官方文档的翻译而是我把 LiteLLM 用进真实业务之后沉淀下来的理解、配置思路和踩坑记录希望能让你少走几个弯路。1. 把一堆大模型接口统一成一层到底解决了什么问题1.1 直接对接多家模型服务时最具体的麻烦先说说我为什么要关注这样一个中间层工具。之前团队里有个业务模块最初只接了一个模型服务代码里直接调那个厂商的 SDK逻辑很简单。后来模型要换新版本或者想对比不同供应商的效果问题就来了第二家厂商的 SDK 请求体里字段结构变了response 的解析逻辑也得重写原来的流式输出处理完全不兼容。如果只有一两个调用点硬着头皮改也行。但现实是业务里既有聊天补全又有向量化还有给客服用的摘要生成散落在不同服务里。每换一个模型商所有调用点都要跟着动测试回归成本很高。而且不同厂商的鉴权方式、超时行为、错误码含义也不一样你很难在业务代码里维护一套统一的容错逻辑。这种情况下一个能把各家模型的差异挡在业务之外的适配层就成了刚需。它不解决“该用哪个模型”的问题但能解决“怎么让多个模型的接入成本变得像只接一家”的问题。1.2 LiteLLM 到底做了什么LiteLLM 的核心动作其实很好理解内部维护了一张映射表把一套标准请求格式翻译成各家模型服务需要的格式再把各家的响应翻译回统一格式。它同时提供两种使用形态作为 Python 包直接在你的代码里调用completion、embedding等方法作为独立代理服务启动一个本地 HTTP 服务对外暴露 OpenAI 兼容的接口业务方只要把请求发到这个服务剩下的路由和转换都由它完成。两种形态的背后是同一套逻辑请求进来、格式转换、调用目标模型、响应转换、返回。之所以很多团队最终会选择代理模式是因为代理模式一旦跑起来业务代码里连 SDK 都可以不装了直接用 HTTP 调用即可语言栈不再受限。用生活里的例子类比LiteLLM 就像一个万能插线板。你从不同国家带来的电器插头都不一样插线板帮你把每个插头都适配到同一套电源接口上。业务代码不需要知道目标模型厂商的“插头规格”它只认插线板统一提供的那个口。2. 五分钟跑通最基础的两条链路2.1 在 Python 里直接用包调用最简单的方式是把它当普通库用。安装执行pip install litellm然后就能在代码里发起一次补全请求import litellm response litellm.completion( modelopenai/gpt-4o-mini, messages[ {role: system, content: 你是一个简洁的助手}, {role: user, content: 用一句话解释什么是 API}, ] ) print(response.choices[0].message.content)注意model参数里有个前缀openai/这个前缀告诉 LiteLLM 该走哪个厂商的适配逻辑。如果省略前缀它会默认按 OpenAI 风格的请求处理。同理如果要调用另一家模型写法可能是response litellm.completion( modelanthropic/claude-3-5-sonnet, messages[{role: user, content: 你好}], )只要在环境变量里配置好对应的 API Key比如ANTHROPIC_API_KEY剩下的差异就由库内部处理了。对于已经用习惯了某一个厂商 SDK 的团队这种切换方式几乎无感。流式调用也类似把streamTrue传进去返回的迭代器会吐出一个个经过标准化的文本块response litellm.completion( modelopenai/gpt-4o-mini, messages[{role: user, content: 写一段三行诗}], streamTrue, ) for chunk in response: print(chunk.choices[0].delta.content or , end)这里要注意一个细节虽然 LiteLLM 尽力把不同厂商的流式格式统一成 OpenAI 风格但不同后端吐出的内容块粒度可能不一样有的按词吐有的按句吐所以调用端不要对每个 chunk 的内容长度做任何假设。2.2 起一个本地代理服务用 OpenAI 兼容地址接进来代理模式才是它在生产环境里最常用的形态。先装带代理依赖的版本pip install litellm[proxy]新建一个配置文件比如config.yamlmodel_list: - model_name: gpt-4o-mini litellm_params: model: openai/gpt-4o-mini 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然后启动litellm --config config.yaml --port 4000服务起来后业务端请求地址就是http://127.0.0.1:4000。这看起来跟直接 OpenAI 一样因为对外暴露的就是 OpenAI 兼容协议。用 curl 测一下curl http://127.0.0.1:4000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: gpt-4o-mini, messages: [{role: user, content: 你好}] }这个代理的价值在于model字段传的是你在配置里自定义的model_name而不是底层真实模型名。哪怕哪天你把claude-3-5-sonnet对应关系换成了另一家模型业务端完全不用改。后续给前端、后端、数据分析师各发一个虚拟 Key请求量和费用归属也都能在代理层看清楚。3. 配置落地的核心动作模型路由、失败回退与负载均衡3.1 用 YAML 描述供应商与模型的映射代理模式配置的核心是model_list。实际使用中我最常用的做法是把供应商前缀、模型名和对应 Key 都写进去model_list: - model_name: raft-2024-small litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY - model_name: raft-2024-small-claude litellm_params: model: anthropic/claude-3-5-sonnet api_key: os.environ/ANTHROPIC_API_KEY注意model_name可以自己起不需要和上游模型名一致这个字段相当于一层别名。业务方看到的是raft-2024-small背后是哪个厂商的哪个版本由你来控制。这里有个经验不要把真实模型名暴露给所有调用方。因为一旦涉及到模型版本升级或者替换供应商真实模型名会在业务代码里到处散落未来改起来很痛。统一用别名代理层改一行配置就能完成切换。3.2 失败回退一次不容错现场补出来的配置刚开始我只配了一个模型结果上游服务偶尔超时或者返回 5xx业务端就跟着报错。后来我在配置里加了回退机制才真正感受到代理层的价值。代码调用方式里可以显式传回退列表response litellm.completion( modelopenai/gpt-4o-mini, messages[{role: user, content: 你好}], fallbacks[anthropic/claude-3-5-sonnet, gemini/gemini-1.5-flash], )意思是第一个模型调用失败时自动按顺序尝试后面的模型。放在代理配置文件里则是这样router_settings: fallbacks: [ {gpt-4o-mini: [claude-3-5-sonnet]}, {claude-3-5-sonnet: [gpt-4o-mini]} ]这个配置解决了一个很实际的问题某一家模型服务出故障时业务不会直接中断而是让另一个模型顶上。虽然顶上来的模型可能效果略有差异但至少服务是连续的。回退的触发条件默认覆盖网络错误、超时和部分状态码。你需要额外关注的是业务场景对这种“隐性切换”的容忍度如果回退到不同厂商的模型生成结果的格式和风格可能不同后置的解析逻辑一定要做兼容测试不能想当然地认为换个模型还是同样的输出结构。另外超时时间一定要单独配置。默认值对某些慢模型来说偏短容易造成不必要的回退。我实测过把timeout调到 30 到 60 秒区间配合回退整体稳定性会好很多。3.3 路由策略与语义缓存效果和成本的一体两面当你同时在代理后面挂了多个模型时可以做的事情就不只是单一转发那么简单了。LiteLLM 支持多种路由策略我重点说两个实际用过的一是基于内容的路由。比如请求里包含明显的代码片段可以路由给代码能力更强的模型普通对话路由给便宜快速的模型。这个思路好用但配置时要注意分类标准不能太粗糙否则会出现同一个问题的不同表述被路由到不同模型导致结果质量忽高忽低。二是负载均衡。如果你用的是同一个模型但有多个 API Key或者某厂商提供了多个同名地区的接入端点可以把它们配置成一个模型组代理层分摊请求。这种方式对提升整体吞吐很有帮助尤其是高峰期大批量调用时不会被单把 Key 的限流卡死。语义缓存是另一个隐藏的省钱点。很多业务请求其实是高度相似的比如同一套 FAQ 被反复问。开启缓存后相同语义的请求可以直接命中缓存结果省去一次真实模型调用。代理配置里指向 Redis 即可cache: true cache_params: type: redis host: localhost port: 6379我建议至少在两个场景下开启语义缓存一是客服问答类的重复问题二是对结果实时性要求不高的摘要类任务。但要留意缓存键的计算逻辑用户信息、上下文差异都有可能造成缓存误命中。4. 把访问权限和预算管住才能放心给业务方用代理4.1 虚拟 Key 和团队隔离代理服务一旦开放给多个业务方最不能少的就是独立的访问控制和用量统计。LiteLLM 提供了虚拟 Key 机制你可以给每个团队、每个服务甚至每个人生成独立的 Key。用 API 创建虚拟 Keycurl http://127.0.0.1:4000/key/generate \ -H Content-Type: application/json \ -H Authorization: Bearer sk-1234 \ -d {models: [gpt-4o-mini], max_budget: 100}sk-1234是你的代理管理员 Key需要提前通过LITELLM_MASTER_KEY环境变量设置。生成出来的虚拟 Key 可以绑定具体模型列表还能设置预算上限。实际使用中我的做法是前端服务一把 Key后端异步任务一把 Key模型评测脚本单独一把 Key。这样做的好处很直接——哪个 Key 消耗异常一查就能定位到对应业务方要临时停掉某个调用方的权限删掉那把 Key 就行不用改任何业务代码。4.2 spend 数据能告诉你哪些模型在烧钱代理模式的另一大收益是费用可视化。通过/spend接口或者自带的监控页面可以按模型、按 Key、按团队维度查看一段时间内的消耗情况。我在项目里就是这么用的把模型 A 和模型 B 同时挂到代理上跑了一周结果发现模型 A 虽然单次调用便宜但调用量是模型 B 的十倍总费用反而是大头。团队根据这个数据调整了路由策略把部分高频场景挪到了更合适的模型上。这种事如果没有统一的代理层靠各业务方自己报数根本对不齐。需要提醒的是代理统计的费用是它根据模型单价估算出来的不等于厂商账单上的精确数字。不同厂商的计费规则差异很大有的按输入输出 token 分别计价有的还有缓存费用。代理给的是趋势和量级最终对账还是要以服务商账单为准。5. 兼容性细节踩坑最多的地方5.1 字段名和输入参数的映射这是最容易出问题的地方也是 LiteLLM 这类工具存在的意义之一。不同厂商模型的输入参数名并不完全一致。比如有的模型叫max_tokens有的新版本改叫max_completion_tokens有的模型支持top_k有的只支持temperature。如果你在一个模型上能用的参数原封不动传给另一个模型大概率会收到 400 错误。LiteLLM 默认会尝试做参数转换但遇到目标模型根本不支持的参数它的默认行为是报错还是丢掉取决于配置。实际项目中我建议在配置里显式开启litellm_settings: drop_params: truedrop_params的意思是某个参数目标模型不支持时直接丢掉而不是报错。这个设置对提高兼容性很有效但要小心被丢掉的参数有时候会影响生成质量。比如你给模型传了response_format要求结构化输出偏偏目标模型不支持该参数参数被静默丢掉后返回内容可能完全不符合预期。更稳妥的做法是把这类参数按模型分别配置同一个model_name下不同厂商的模型单独列出并在业务层做约定需要结构化输出的请求只路由到支持该能力的模型上。5.2 流式输出的结构差异流式输出是所有兼容层里最难处理的部分。有的厂商流式返回时content字段直接携带增量文本有的则是合并后的完整内容还有的中间会插入角色信息或 usage 片段。LiteLLM 会把它们统一转发成标准的 SSE 格式这本身没问题但我在实际测试中发现两个容易踩的点。第一不同模型返回的首个 chunk 内容差异很大。部分模型会先推送一个空 content 的 chunk用于建立连接业务端如果直接对首个 chunk 做非空校验并拼接可能出现首字符缺失。第二流式结束后是否包含 usage 信息也不一致有的在chunk.usage里返回 token 统计有的完全不带。如果你的业务需要精确统计 token 消耗不要依赖流式响应里的 usage而是单独从/spend或厂商账单那里拿数据。我的建议是对任何接入代理的模型上线前都用同一套测试用例去对流式结果做一次兼容测试记录每个模型在流式场景下的行为差异再决定业务端解析逻辑需不需要做适配。这一步省不了。5.3 错误码、超时和重试的表现差异不同厂商的错误返回格式差异也很大。有的错误码明确有的则习惯性地把错误信息放在一个很深的嵌套结构里。代理层会尽力把它们统一成 OpenAI 风格的结构但统一不代表信息不丢失。实际遇到过一种情况上游模型因为内容安全策略拒了请求代理返回的错误信息经过标准化后业务方误以为是自己参数传错排查了很久。后来在代理日志里发现了原始错误详情才定位到问题。所以有两个实践建议代理侧保留原始响应的 debug 日志排查问题时直接看原始错误业务侧不要只依赖错误码判断问题类型只要响应异常就把原始错误记录到日志系统里方便二次分析。重试策略也要分场景。类似 429 限流错误简单退避重试通常有效但如果是 400 参数错误重试毫无意义反而会浪费配额。LiteLLM 允许你按状态码配置重试次数我一般会把限流和超时类错误配置重试 2 到 3 次参数类错误直接返回。6. 做成生产级服务的部署选择6.1 进程、容器与密钥管理本地调试时直接litellm --config config.yaml没问题但到生产环境我更推荐用容器方式部署。LiteLLM 官方维护了镜像常见用法是把配置文件和密钥通过环境变量注入docker run -d \ --name litellm-proxy \ -p 4000:4000 \ -v $(pwd)/config.yaml:/app/config.yaml \ -e OPENAI_API_KEYyour_key_here \ -e ANTHROPIC_API_KEYyour_anth_key \ ghcr.io/berriai/litellm:main-latest \ --config /app/config.yaml这里有一个特别重要的安全习惯不要把密钥写死在配置文件里提交到代码仓库。配置文件里的os.environ/XXX写法就是告诉代理从环境变量读取你在部署平台或容器编排系统里统一注入密钥即可。密钥文件单独管理跟业务代码仓库隔离能避免很多安全事故。单实例部署时我会在代理前面再放一层网关做 TLS 终止和请求限流。代理本身虽然可以处理限流但把传输加密和粗粒度的访问控制交给更成熟的网关来做整体更安心。6.2 可观测性与降级预案生产环境里的代理层必须让它“可被观测”。具体来说至少要盯三件事请求成功率、平均响应延时、错误状态码分布。这些指标在代理的日志里都有接入你常用的监控系统就能看到。如果业务对稳定性要求高我强烈建议提前设计降级预案而不是依赖代理自动回退。自动回退是兜底手段但你不应该在事故发生时才知道某个模型已经连续失败几十分钟。我的做法是对每个底层模型配置健康检查一旦连续失败超过阈值就告警并自动在路由配置里摘除该模型。另一个容易忽略的点是代理层本身也是单点。虽然不是所有团队都需要高可用部署但如果代理挂了会导致所有模型调用全部中断那你至少要给它做一个可重启的进程托管并保留一个绕过代理、直接调用底层模型的应急脚本。这个脚本平时可以不用但必须存在。毕竟兼容层换来的是便利不能因为便利反而增加了整体的故障面。说说我自己的使用体会用得越久越觉得LiteLLM 这种工具适合在特定阶段切入当你接入的模型服务超过三家或者同一个代理需要被多个团队共享时它的价值会体现得非常充分。如果只是自己写个脚本调一两个模型直接插 SDK 反而更清爽没必要引入额外组件。真正让我愿意一直用它的原因是它把“模型切换”变成了一件配置级的小事。以前换模型要改代码、联调、走测试现在改一行路由配置观察一段时间不合适就再改回来。这种低成本的试错空间对快速迭代的业务来说非常宝贵。最后补一句可能有点反直觉的经验兼容层本身不会直接让应用变快但它会让团队面对模型生态变化时反应速度变快。后者在当下的环境里往往比某个模型单点的性能更关键。