☰
LiteLLM 实战:构建多模型统一网关,搞定部署、路由与成本控制
2026/10/14 9:14:16 网站建设 项目流程

做了几年大模型应用开发,我最大的感受是:模型厂商越来越多,但每一家的API协议、鉴权方式、模型命名、错误码都各搞一套。前端接一家还好,一旦要把五六个供应商的模型同时接入同一个系统,代码里全是 if-else 和 adapter 类,维护成本高得离谱。后来我从一个做中间件的朋友那里拿到 litellm 这个项目,试了几个小时就决定把它作为我们团队所有模型请求的统一入口。这篇文章就把我实际部署和调优过程中的经验、踩坑和思考写出来,给打算统一管理多模型调用的开发者一个参考。

litellm 本质上是一个开源的 LLM 网关,或者说是一个模型代理服务。它把市面上主流的大模型API全部包装成同一种调用格式,你只需要按照一种协议写代码,剩下的供应商适配工作全交给它。它还顺带解决了负载均衡、失败重试、成本追踪、预算限额、虚拟API Key这些生产环境必须面对的工程问题。如果你是负责AI应用后端、做中台系统,或者正在被多平台集成折磨,这篇文章应该能帮你省掉不少时间。

1. 为什么需要 litellm:统一入口的价值远超"少写几行代码"

1.1 一个让人抓狂的真实场景

先说个我实际遇到的情况。某段时间团队同时接了三个模型:某海外厂商的旗舰对话模型、某国内厂商的中文优化模型、还有一个开源模型的自部署服务。三个模型的接口风格完全不一样:海外厂商走/v1/chat/completions,需要传messages数组;国内厂商的接口要求把参数包在request字段里;开源模型的自部署服务还要自己拼 HTTP 头传 token。

代码里最痛苦的还不是参数格式差异,而是流式输出的处理方式。一个模型用 SSE 事件流返回 token,一个用 WebSocket,还有一个要轮询任务状态。为了给前端统一的流式体验,我在后端写了三个 adapter,每个 adapter 里还有一堆异常分支处理 timeout、429 限流、500 错误。等到第四家模型接入时,我实在受不了了,开始找现成的中间件。

市面上有一些商业的 AI 网关产品,功能强但要么贵要么不给源码。litellm 的出现正好补上了这个位置:开源、能自托管、支持全家桶式的模型供应商接入,而且它自己就是 Python 写的,和我们的技术栈一致。

1.2 litellm 与其他 SDK 的本质区别

很多模型厂商自己也提供官方 SDK,但那些 SDK 通常是单厂商绑定的,你用一个就只服务一个。litellm 的定位完全不同,它是对厂商 SDK 的再封装,提供了一个统一的"模型路由层"。你可以把它理解为支付系统中的聚合支付网关:商户不需要对接每家银行的接口,只需要接一次网关,网关再帮你路由到具体银行。

从架构上看,litellm 有两种使用形态。第一种是以库(library)的方式直接调用,就是from litellm import completion,然后传model="provider/model-name"就能拿到结果,适合在 Python 项目里快速集成。第二种是部署成 HTTP 代理服务,所有请求打到http://your-server:4000/v1/chat/completions,由服务端转发到真实的模型供应商,这种形态适合跨语言、多团队、前后端分离的场景。

我最终选择的是第二种形态。理由有三个:一是我们前端团队只维护一份调用代码,不用关心后端到底接了哪些模型;二是可以在网关层统一做限流、预算和审计;三是切换模型时只要改服务端配置,客户端不需要重新发版。对于把"多模型统一入口"作为基础设施目标的团队来说,HTTP 代理是无脑首选的。

2. 部署与快速启用:10 分钟跑起第一个转发请求

2.1 安装、启动与最小配置

litellm 的安装非常简单,需要 Python 3.9 以上环境。我用的是pip install "litellm[proxy]",方括号里的proxy会额外装上运行 HTTP 代理所需的依赖,包括 FastAPI 框架和 uvloop 这些高性能组件。如果你只需要在 Python 代码里引入 SDK,那装litellm就够了,不用带proxy。

装完之后直接命令行启动:

litellm --config /path/to/config.yaml --port 4000

如果你连配置文件都还没来得及写,它也可以用临时的环境变量方式跑起来:

export PROVIDER_H_API_KEY="sk-xxxx" litellm --model provider_h/flagship-v2 --port 4000

这样它就监听了4000端口,并代理到某一个具体模型。但这种方式只适合验证环境,真正生产环境必须用 config.yaml,因为我们需要定义多个模型、多个供应商的密钥、以及路由规则。

启动后可以先用 curl 做一次健康检查:

curl http://localhost:4000/health/liveliness

返回{"status": "ok"}之类的内容就说明服务起来了。litellm 默认还会起一个管理后台/ui,浏览器打开可以看请求日志、模型列表、成本统计,后面我会展开讲。

2.2 发第一个代理请求,确保链路通了

服务起来之后,我习惯先发一个最小请求验证代理链路是否正常。下面这段 curl 模拟的是一个 OpenAI 风格的 chat 请求:

curl http://localhost:4000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-your-virtual-key" \ -d '{ "model": "flagship-chat", "messages": [ {"role": "user", "content": "你好,用一句话介绍你自己"} ], "max_tokens": 100 }'

注意这里有两个关键点。第一,Authorization里的 key 是访问 litellm 代理的 key,默认情况下你可以在环境变量里设一个LITELLM_MASTER_KEY来充当主密钥,没有它会自动用内置的默认值。第二,model字段传的是你在 config.yaml 里定义的虚拟模型名,不一定是真实模型名。代理层会根据model_name映射到真实的供应商模型,客户端完全感知不到这一步。

我第一次跑通的时候,看到返回的 JSON 结构和直接调某个大厂接口几乎一样,就明白这个抽象层设计得很用心:它不是一个"尽力模拟"的兼容层,而是把上游模型的响应做了标准化清洗,连usage字段里的 token 统计都补齐了。这样切换到不同供应商时,客户端解析逻辑完全不用改。

3. 核心配置深度解析:config.yaml 才是灵魂

3.1 模型列表、供应商与 API Key 的管理方式

在实际生产里,config.yaml 是 litellm 的绝对核心。我的做法是给每个环境(dev、staging、prod)单独维护一份配置,模型中涉及敏感字段的 key 统一用环境变量引用。配置主体是一个model_list:

model_list: - model_name: flagship-chat litellm_params: model: provider_h/flagship-v2 api_key: os.environ/PROVIDER_H_API_KEY timeout: 120 api_base: https://api.provider-h.example.com/v1 - model_name: heavy-reason litellm_params: model: provider_d/deep-reason-1 api_key: os.environ/PROVIDER_D_API_KEY - model_name: open-source-embedding litellm_params: model: hosted_vllm/bge-m3-embedding api_key: os.environ/VLLM_API_KEY api_base: http://your-embedding-service:8000/v1

每个litellm_params里,model字段的格式是供应商前缀/真实模型名。供应商前缀是 litellm 预定义的几十种适配器之一,它知道该用什么样的协议、端点结构和鉴权方式去和上游交互。如果你使用的是兼容 OpenAI 协议的自部署服务,前缀可以写hosted_vllm或者openai配合自定义api_base。

配置好之后,即使flagship-chat对应的上游模型在凌晨临时升级换代,客户端也不会察觉。运维只需要改配置重启代理,这与传统的"客户端 SDK 依赖具体 endpoint"相比,优势太明显了。

注意:如果确实需要给每个虚拟模型绑定不同的 key、不同的供应商账号,就在litellm_params里分别设置。如果某个 key 要全局复用,可以在litellm_settings里统一设master_key供客户端使用,两者容易混淆,要理清作用域。

3.2 路由策略与负载均衡:让多个上游实例都能被高效利用

litellm 最实用的功能之一就是支持对同一个虚拟模型配置多个上游实例,然后按策略做负载均衡。比如我配置了两个供应商共同承担同一个模型名:

model_list: - model_name: super-chat litellm_params: model: provider_h/flagship-v2 api_key: os.environ/PROVIDER_H_API_KEY - model_name: super-chat litellm_params: model: provider_d/same-capacity-model api_key: os.environ/PROVIDER_D_API_KEY

当客户端请求model="super-chat"时,litellm 会在两个上游之间做路由。默认的策略是simple shuffle,也就是随机挑选一个实例。此外还支持配置routing_strategy为least-busy、latency-based-routing、usage-based-routing等,具体用哪个要看你的实际场景。

我在某个高并发项目里用的是latency-based-routing,它会对每个上游实例维护滑动窗口平均延迟,并把更多流量发给历史上响应更快的那一方。这比随机分配体感好很多,尤其是在混合使用不同规格的供应商时,慢节点不至于被海量请求拖垮。

这里还要提一下失败重试。默认情况下代理只转发一次,如果上游返回 500 或请求超时,客户端就会拿到错误。生产环境通常需要配retry_policy:

litellm_settings: retry_policy: timeout_retries: 2 rate_limit_retries: 3

这样遇到超时会自动换一个上游实例重试 2 次,遇到限流则最多重试 3 次。重试时会做退避等待,不会把上游打得更死。实测下来,这套机制把我们的请求失败率从接近 1% 降到了 0.2% 以下,对用户体验的提升非常直接。

3.3 成本追踪、速率限制与预算封顶

模型 API 调用按 token 计费,成本控制一向是 AI 应用最头疼的问题。litellm 的内置成本追踪模块做得相当完整:它在model_list的每个litellm_params里允许你附加价格表信息:

litellm_params: model: provider_h/flagship-v2 api_key: os.environ/PROVIDER_H_API_KEY input_cost_per_token: 0.000015 output_cost_per_token: 0.000060

litellm 会根据每次请求响应的usage,实时计算本次调用的费用并记录。这样你在后台看到的不仅仅是"谁调用了多少次",还有"每个团队每天烧了多少钱"。后台管理界面的 Cost 报表会按模型、按 API Key 维度聚合展示。

我强烈建议上线前就配好预算限制,而不是等月底账单出来再心疼。配置方式是在litellm_settings里加:

litellm_settings: max_budget: 2000 budget_duration: 30d

超过预算之后代理会拒绝新的调用并返回一个明确的"预算超限"错误。更细一点,你可以对某个虚拟 Key 单独设限额,后面第 4 节会讲。

4. 统一文本接口与进阶能力:把上百个模型当成同一个模型来用

4.1 统一的补全接口和流式响应的处理细节

litellm 之所以能让客户端"少学很多协议",是因为它把不同模型的能力差异收敛到了一个公共接口。普通的非流式调用,客户端只需要拼 JSON 发送,代理返回标准化响应。这里我想重点说流式响应,因为这是实际工程里最容易出 bug 的地方。

客户端请求时带上"stream": true,litellm 会向上游发起 SSE 流式请求,然后把上游返回的数据逐块透传给客户端。它的透传不是简单转发,而是会把每个 chunk 里的增量内容重新组装成统一的 SSE 事件格式,并保证data: [DONE]结尾。我在前端测试时,用事件流的解析器拿到的 token 顺序、内容拼接结果和直连上游时一致,没有出现乱序或丢数据的情况。

有个细节值得注意:不同供应商的message.content字段在流式 chunk 里可能是字符串也可能是数组(为了兼容多模态内容),litellm 的响应标准化层会把它统一处理成容易解析的形式。如果你的应用要用choices[0].delta.content取增量文本,在 litellm 代理后面是永远拿得到的。

经验之谈:做流式接入时,一定要在代理层和上游之间设置合理的超时时间。因为 SSE 长连接如果长期不发数据,某些网络中间件会直接掐断连接。我在 config 里一般给流式模型单独配timeout: 300,同时建议客户端在解析时也用类似的心跳机制来做超时检测。

4.2 虚拟 Key、多租户管理与权限隔离

虚拟 API Key 是 litellm 在生产环境最有价值的设计之一。它允许你在代理层创建独立的 key,每个 key 关联一个用户或一个团队,而不必暴露真实厂商 key 给调用方。

创建虚拟 key 可以调用管理接口:

curl -X POST http://localhost:4000/key/generate \ -H "Authorization: Bearer sk-master-key" \ -H "Content-Type: application/json" \ -d '{ "key_name": "team-frontend", "max_budget": 200, "budget_duration": "30d", "models": ["flagship-chat", "super-chat"] }'

这个接口返回一个sk-开头的虚拟 key,前端团队拿着它调用代理就行。它还能做模型级白名单,比如 A 团队只能用便宜模型,B 团队可以使用高成本推理模型。密钥泄露后,你可以单独吊销某个虚拟 key,不需要轮换所有上游真实密钥。

多团队接入之后,审计日志里就能精确看到每个虚拟 key 的请求量、token 消耗、错误率。以前排查问题时,我们需要逐个翻不同平台的控制台;现在所有日志集中在代理层,对运营人员的友好度是质的提升。

4.3 模型回退、Prompt 模板与高度可扩展的钩子机制

litellm 还支持"模型回退"机制:当主模型因为限流或故障不可用时,自动转到备用模型。配置方式比较直接:

litellm_settings: fallbacks: [ {"super-chat": ["flagship-chat", "cheap-chat"]} ]

意思是当super-chat不可用时,依次尝试flagship-chat和cheap-chat。这个能力在很多场景很有用,比如核心业务入口不能因为单一供应商抖动就完全不可用。

代理的钩子机制也非常适合扩展。litellm 提供了pre_call、post_call、modify_request等钩子点位,可以用 Python 函数自定义转发前改写请求、转发后校验响应、记录日志。我在项目中用它做了一个敏感信息脱敏的预处理函数:在请求进入模型前,如果messages里出现疑似手机号、身份证号的模式,直接替换成占位符。用了钩子之后,代码不用侵入任何业务逻辑,统一在网关层搞定。

5. 生产环境落地:我踩过的坑与调优心得

5.1 持久化配置与高可用部署

litellm 默认把 Key 数据、budget 数据、日志缓存在内存里,如果代理进程重启,虚拟 Key 和已用量都会被重置。生产环境需要把数据持久化下来,官方支持 SQLite、PostgreSQL 等后端。

我的做法是在启动命令里指明数据库连接:

litellm --config config.yaml --database_url "postgresql://user:pass@your-db:5432/litellm"

使用 PostgreSQL 之后,多个代理实例可以共享同一套 Key 数据,这为横向扩容铺平了路。实际部署时我在两台机器各跑一个 litellm 实例,前面挂负载均衡器,请求分发到任意一台都不会出现"这个 Key 那台机器不认识"的问题。再配合容器编排,滚动升级时无感知切换是能做到的。

5.2 日志、指标与可观测性建设

做网关层的一大好处是日志集中。litellm 会把每次请求的完整元数据记录成 JSON 日志,包括模型、虚拟 key、输入输出 token、延迟、成本、错误类型。我把它接入了公司的日志平台,这样排查问题可以按 key、按模型、按错误码多维过滤。

如果你有监控系统,litellm 也支持暴露 Prometheus 指标,/metrics端点可以直接被采集。我配置了三个关键告警:

  • 请求错误率超过 5%,触发告警;
  • 某个虚拟 key 当日成本超过其预算的 80%,提前通知;
  • 平均首 token 延迟超过 3 秒,触发上游降级流程。

这些告警在业务出问题前就能让人感知到,比用户投诉后被动响应的体验好太多。

5.3 常见问题与排查思路实录

Q1:代理请求时报 401 Unauthorized

这个八成是虚拟 Key 或主 Key 没配对。确认客户端请求头里的 Bearer token 是 litellm 层创建或启动时设置的 key,而不是上游厂商给的 key。如果你用默认 key 部署到公网,别人也可能拿到 key 来调用,这会非常危险,生产环境一定要自定义主 Key 并定期轮换。

Q2:上游模型偶尔返回超时,但直连上游没问题

优先检查 litellm 实例所在网络到上游的连通性和延迟。如果网络质量不佳,要适当调大timeout参数。另外某些模型生成长文本时,如果首 token 时间过长,需要考虑是不是上游的 max_tokens 设置过大导致排队。我一般把max_tokens设成业务所需的最小值,既能省成本也能降低超时概率。

Q3:明明配置了多个上游实例,但流量好像总往一个实例跑

这个要先确认路由策略。如果用的是simple shuffle,随机分布会产生一定的不均匀,短时间看可能集中在某个节点;用least-busy或latency-based-routing会更均衡。另外检查是不是某个实例挂了你没发现,导致可用实例只剩一个。

Q4:模型返回格式被转换后,响应里缺少某些字段

这是个认知问题。litellm 的标准响应只保证公共字段存在,如果你想透传某些供应商的扩展字段,需要在请求体里带上metadata或使用additional_headers之类的透传开关。遇到这种情况,建议先到后台看原始上游响应日志,确认丢字段发生在哪个环节。

最后再分享两个实战经验

第一,不要一上来就追求把所有供应商接进来。先选两个业务最依赖的模型做通,验证负载均衡、成本追踪、日志链路都符合预期,再逐步加供应商。网关层能统一接口,但不代表业务上的供应商策略可以偷懒,该做的容灾演练还是要做。

第二,config 文件要做版本管理。我见过有人直接在服务器上改 config.yaml 并重启服务,改坏了才知道后悔。用 Git 管理配置、走 MR 评审,配合 CI 里的配置格式校验,能避免绝大部分人为误操作。 litellm 家族还提供了配置同步接口,可以在管理后台批量保存,但也别忘了和仓库里的配置做一致性检查。

说实话,在引入 litellm 之前,我团队里的适配层代码已经膨胀到了没法看的地步,每次新接一个模型都要全员 review。现在代理层把所有模型差异挡在外面,业务代码只需要认知"一个虚拟模型名 + 统一接口",整个链路的稳定性和可维护性都提升了一个档次。如果你也正在被多模型接入折磨,不妨先拿一台机器部署起来,十分钟之后你就会明白我说的"统一入口真香"是什么意思。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询