☰
litellm:统一大模型API网关,搞定接口碎片化与调度治理
2026/10/13 11:39:50 网站建设 项目流程

做AI应用接入的时候,最让我头疼的不是模型效果好坏,而是“每个厂商单独一套接口”这件事。你会发现明明都是聊天补全的场景,A家返回字段叫choices,B家返回字段叫responses,C家还有自己的流式格式。你写了一套调用代码,想在两个平台之间切换,就必须写一层适配代码;想做个简单负载均衡,又得维护自己的转发脚本。直到我遇到litellm,当时第一反应是:原来这个痛点早就有人做了标准化。litellm本质上是一个轻量级的LLM网关和代理服务,它把多家大模型平台的API统一成一种与主流生态对齐的请求格式,同时帮你处理转发、重试、限流、成本统计、密钥管理这些脏活。可以说,它就是一个大模型接口的调度中心,装上一个服务,你后端只需要面对一个地址,剩下的路由策略全部交给它。

这篇文章写给谁?两类人。一类是刚入门、被各家SDK搞到焦头烂额的开发者,一类是已经在生产环境里接了三五个模型、开始关注稳定性和账单的团队负责人。读完你会知道如何快速部署litellm、怎么写配置、怎么设计负载均衡与降级策略、怎么对费用做预算管控,以及我在实际踩坑后总结下来的排查经验。我不打算只是罗列功能,我会把每个关键设置背后的原因也讲清楚。

1. 为什么需要litellm:大模型接入的真实痛点

1.1 接口碎片化远比你想的麻烦

每个主流大模型平台都会对外暴露一个HTTP接口,格式上乍一看差不多,实际上细节差异非常多。有的厂商把system prompt放在messages里,有些平台用单独字段;有的流式返回用的是SSE格式,有的对工具调用有自己的描述结构;更别提不同平台鉴权头、错误码形态、限流响应体都不一样。当你只接一家模型时,这些差异不存在;当你需要“多厂商备份”“一个应用里不同场景用不同模型”时,问题立刻变成日常难题。

我见过一些团队,早期图省事直接在各处代码里封一个ProviderClient类,里面写if-else判断用哪个厂商。刚开始只有两家模型还挺得住,第三家进来之后,客户端、单元测试、错误上报逻辑全部跟着膨胀。上线后只要一个厂商调整响应字段,你就要改底层SDK,还要排查它对上层业务有没有影响。这就是典型的“早花时间适配,晚花时间填坑”。litellm把这个适配层单独抽出来,统一成一个网关,业务侧永远面对同一个风格的接口。

1.2 litellm不只是“转发”

很多人以为litellm只是个代理,把请求原样导到不同平台。这个理解太窄了。它的核心能力是三层:

第一层,接口层的统一。它把不同平台的请求和响应转换为一种主流格式,应用层不用感知背后是哪家厂商。

第二层,策略层的调度。你可以在配置中定义多个同级模型,指定负载均衡策略、重试次数、超时时间,甚至可以设置当A平台挂了以后自动降级到B平台。

第三层,治理层的能力。包括密钥托管、虚拟密钥、预算上限、用量日志、以及在多实例部署时的共享状态管理。

所以,它不是你写来临时拼接代码用的工具,而是一个可以长期演进的基础设施。我自己的体会是,凡是准备接两个以上模型的团队,早期花半天时间把网关搭好,后面节省的时间不是一倍两倍。

2. 快速跑通基础链路:从零到第一个代理请求

2.1 安装与最简配置

安装litellm非常简单,只要你的环境有Python 3.8以上版本,执行:

pip install litellm

它会拉起一个FastAPI服务,所以装完之后,你只需要准备一个配置文件。最基础的一份config.yaml大概长这样:

model_list: - model_name: primary-llm litellm_params: model: vendorA/light-chat api_key: $VENDOR_A_KEY api_base: https://api.vendor-a.com/v1 - model_name: secondary-llm litellm_params: model: vendorB/creative-chat api_key: $VENDOR_B_KEY api_base: https://api.vendor-b.com/v1 general_settings: master_key: sk-my-master-key

这里有个细节值得注意:model_name是你自己定义的逻辑名,业务代码里只认这个名字。比如后端代码调用primary-llm,实际请求落到哪个平台,由litellm配置决定,以后想换厂商、换模型,只需要改配置文件并重启网关,业务代码一行都不用动。这个解耦思路和数据库连接池很像,你的业务只认逻辑数据源,物理库怎么切换是由中间件控制的。

配置好后启动服务:

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

启动日志里如果出现“Uvicorn running on ...”,说明服务已经起来了。此时你可以先做一次最直接的连通性测试,用curl发一条消息:

curl http://localhost:4000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-my-master-key" \ -d '{ "model": "primary-llm", "messages": [{"role": "user", "content": "你好,请简单介绍一下你自己"}] }'

2.2 代码调用:从直连切换到代理

当网关起来后,你的应用层代码改动会非常小。以前你如果是用某官方SDK,连接参数写的是厂商的地址和密钥;现在只需要把base_url改成网关地址,把api_key改成网关master_key或虚拟密钥。例如Python环境下的调用逻辑可以是这样:

import urllib.request import json def chat_once(prompt: str): body = json.dumps({ "model": "primary-llm", "messages": [{"role": "user", "content": prompt}], "temperature": 0.7 }).encode("utf-8") req = urllib.request.Request( "http://localhost:4000/v1/chat/completions", data=body, headers={ "Content-Type": "application/json", "Authorization": "Bearer sk-my-master-key" }, method="POST" ) with urllib.request.urlopen(req, timeout=30) as resp: return json.loads(resp.read().decode("utf-8"))

如果你的项目里已经用了兼容主流格式的HTTP客户端,那就更省事,把base_url和api_key替换成网关的即可。这里我不推荐在业务代码里再包一层适配,因为网关本身已经做了这件事,业务层再绕一圈反而是过度设计。

2.3 基础链路的三个习惯

跑通之后,建议立刻养成三个习惯。

第一个习惯,日志从第一天就开启。把日志打到标准输出或某个文件里。litellm的日志会记录每次请求的模型名、耗时、令牌数、错误状态。没有日志,后面排查问题就像蒙着眼找东西。

第二个习惯,统一用逻辑模型名。不要在业务代码里写“今晚用vendorA,明天换vendorB”这种临时分支,一切切换交给配置文件。

第三个习惯,设一个较短的默认超时时间,比如10到15秒。我在初期没设超时,有一个厂商某个模型卡了很久,把整个服务连接池拖住了,之后所有请求都在排队。提前设超时,比事后优化更划算。

3. 多模型调度:负载均衡、重试与降级配置

3.1 为什么需要调度策略

当你只有一路模型时,网关的价值已经体现了一部分,但它的真正优势来自调度。所谓调度,简单说就是请求来了,转发给谁。几种常见业务场景都需要它:

  • 多个供应商都可用,想把流量均匀分摊,避免单一依赖。
  • 主用某厂商的大参数模型,但想在上游故障或限流时自动切到备选模型。
  • 不同提示词路由到不同模型。比如长文档处理走长上下文模型,简单闲聊走低成本模型。

这些需求如果全部让业务代码自己实现,会特别痛苦。因为你要处理的是“选谁、失败怎么办、失败后要不要重试、重试多少次、多个实例并发时怎么协调”。网关把这种治理逻辑沉淀到了配置里。

3.2 配置一组可调度的模型池

在litellm配置里,定义一个模型池最直白的方式是给同一个model_name配置多个litellm_params。例如:

model_list: - model_name: chat-router litellm_params: model: vendorA/business-chat api_key: $VENDOR_A_KEY weight: 3 - model_name: chat-router litellm_params: model: vendorB/standard-chat api_key: $VENDOR_B_KEY weight: 1 - model_name: chat-router litellm_params: model: vendorC/economy-chat api_key: $VENDOR_C_KEY weight: 1

这里weight就是权重。比如三个模型的权重分别是3、1、1,那么每来5个请求,大约3个打到A,1个打到B,1个打到C。权重适合表达“这个模型更强,多分一点流量”或“这个模型便宜,作为兜底多承担点成本敏感流量”。

需要注意,不同厂商的模型名字往往不同,你要确保model字段写的是litellm能够识别的前缀加模型名。这个前缀通常是厂商标识。我在实际配置中有一个习惯:先把model字段写成不带前缀的形式,跑一次请求看报错信息里的提示,确认litellm识别到了哪家厂商,再按规范补全。

3.3 超时、重试与降级的实际取舍

超时和重试属于最容易被忽视的配置。默认值是合理的,但在生产环境一定要根据自己的SLA调优。

router_settings: routing_strategy: simple-shuffle num_retries: 2 timeout: 30 retry_policy: Timeout: 1 RateLimitError: 2 APIError: 1

这段配置的意思是,总超时上限30秒,当遇到RateLimitError时最多重试2次,遇到Timeout或APIError时最多重试1次。我特别推荐给RateLimitError设定更高的重试次数,因为限流通常是临时状态,稍微等一下再发往往就成功了。而4xx类请求错误,比如参数写错、鉴权失效,重试并没有意义,这类错误就不应该进入重试队列。

降级是调度里最关键的保险丝。实际配置一个主备链路可以这么做:

model_list: - model_name: critical-route litellm_params: model: vendorA/flagship-chat api_key: $VENDOR_A_KEY - model_name: critical-route litellm_params: model: vendorB/backup-chat api_key: $VENDOR_B_KEY

然后在业务侧使用同一个模型名critical-route。如果A持续失败,litellm会在重试之后把请求转到B。不过这里有一个认知误区需要澄清:默认的主备切换并不是“A反复失败才切换”,而是“某一次具体请求在A上失败后,在同一次请求内尝试B”。所以它的保障粒度是单个请求级别的容错,不负责健康状态的长周期管理。如果你的诉求是A挂了10分钟就把流量全部切到B,那就需要额外的健康检查机制,不能单靠这个配置满足。这一点很多人容易混淆,先去想清楚需求再动手。

3.4 路由逻辑的个人倾向

我在生产环境里倾向于把“成本敏感”和“质量敏感”拆成两个不同的model_name,而不是放在同一个池子里做权重混跑。比如写个summary-model专门走便宜快速的小模型,写个reasoning-model专门走最强大的推理模型。这样上游想单独调整某个业务路径,不需要动其他路径的配置。权重混跑适合流量占比均衡的同级别模型,成本和质量完全不同的模型塞进同一个池子,调度效果很难预测。

4. 密钥、用量统计与预算治理

4.1 为什么不能让业务侧拿厂商原始密钥

很多人搭完网关,顺手就把厂商原始密钥填到网关配置里,业务代码里继续用原始密钥直连。这在安全性上等于白搭网关。原始密钥在多个服务和部门之间流转,一旦某个客户端日志泄露,整个厂商账号都暴露了。更好的做法是用litellm提供的虚拟密钥功能。

虚拟密钥的含义是你可以在litellm上创建一把“子密钥”,这把密钥只对网关有效,你可以给它设置配额、过期时间、预算上限。业务用的是这把子密钥,原始厂商密钥永远留在网关侧,不会进入应用进程。

创建方式可以通过管理接口完成,也可以配置一个初始的虚拟密钥。它的工作方式和常见的API平台很相似:主密钥负责管理,虚拟密钥负责业务访问,两者权限分离。

4.2 成本统计关键配置

预算治理的第一步是记账。litellm的成本统计会把每次请求的模型、token数、单价换算成金额,并写入日志或外部存储。你可以用它做三类事情:看单次请求花了多少钱,看某个业务密钥一段时间内花了多少钱,以及设置预算上限。

在配置中开启成本统计一般是这样:

general_settings: database_url: postgresql://user:pass@localhost/spend_db litellm_settings: success_callback: ["prometheus"]

这里出现了数据库的连接配置。如果你只有一个litellm实例,可以把统计放在本地存储,但多实例部署时,必须用共用的数据库,否则每个实例只知道自己的那部分消费记录,对不上总账。我见过不少团队在这里栽跟头:本地测试都正常,上了Kubernetes多副本后账单数据总是少一截,原因就是每个副本各写各的存储。

如果你想看某个客户或内部项目花了多少钱,在你创建虚拟密钥时给它绑定预算上限即可。当额度接近上限时,网关会拒绝请求并返回预算不足的错误。这个机制对于对外提供API服务非常实用,因为成本在应用层很难精确预判,只有在网关层统一掐闸才能兜底。

4.3 预算治理需要注意的细节

第一个细节是计费数据的延迟。供应商返回的usage字段有时不完整,尤其流式模式或工具调用场景,token数可能缺失或不准。litellm会做一些估算,但如果你对成本精度要求极高,就要自己维护一份token计数逻辑。

第二个细节是缓存命中会导致token费用为零。开启缓存后,第二次请求不会真正打到上游,成本统计自然更低。这不是bug,但要提前想清楚:你的报表想反映真实上游消耗,还是反映业务实际花费?两者是不同的口径。

第三个细节是不要把所有密钥共用一个预算。如果公司内多个项目共用一个虚拟密钥,其中某个项目流量暴涨,就会把其他人的预算消耗完。按项目拆密钥,比共用一个密钥再事后分析账单要省心得多。

5. 生产环境部署、缓存与观测

5.1 容器化部署要点

既然生产环境要稳定运行,容器化是绕不开的话题。litellm提供了公共镜像,基本用法是在宿主机挂载配置文件并保留端口:

docker run -d \ --name llm-gateway \ -p 4000:4000 \ -v $(pwd)/config.yaml:/app/config.yaml \ -e VENDOR_A_KEY="真实密钥" \ llm-gateway-image

镜像内部默认的工作目录是/app,所以挂载到/app/config.yaml即可被加载。环境变量方式传密钥,比把密钥写死在配置文件里更安全,也更方便在不同环境之间切换。

在Kubernetes里部署时,我通常把config.yaml放在ConfigMap里,密钥放在Secret里。启动命令保持为litellm --config /app/config.yaml,然后通过环境变量注入密钥。需要注意的是,如果你开启了多副本,必须把路由状态和服务发现的公共状态放到外部依赖里,比如Redis,否则每个Pod各自为政,预算、缓存、分布式锁都会出问题。

5.2 观测指标与日志排查

litellm暴露了一些标准指标接口,可以和Prometheus、Grafana这类监控体系无缝对接。常见的重点指标包括:

  • 请求总数与分模型请求数:观察流量分配是否符合预期。
  • 错误率分类型:4xx和5xx要分开看,4xx更多是配置或参数问题,5xx是上游故障。
  • P50/P95/P99延迟:单看平均延迟没有意义,高P95说明长尾请求在拖后腿。
  • 上下游token消耗:从网关视角看整体消耗趋势。

我自己的习惯是先搭建一个简易Grafana面板,把请求数、错误率、P95延迟、日费用四块放在首页。这样每次上线配置变更后,一眼就能看出流量比例、失败率和成本有没有异常。没有观测数据前做的调优,都是盲人摸象。

日志排查方面,litellm的日志中每一条请求都会记录模型名、是否命中缓存、消耗的prompt tokens和completion tokens、总耗时。出现问题时,先看有没有“fallback”关键字,再看是上游返回的错误还是网关自身的超时。上游错误和网关错误在日志里的形态不一样,排查路径也不同,这个要养成条件反射。

5.3 缓存策略:哪些场景值得开

litellm支持响应缓存和语义缓存两种方向。最常见的是简单缓存:相同的请求体在TTL内直接返回之前上游的结果,不再打厂商。适合的场景非常明确:客服知识库问答、产品说明文档问答、常见的SQL生成模板、重复度很高的配置类请求。

litellm_settings: cache: true cache_params: type: redis ttl: 3600

这里有几件事要注意。第一,如果业务要求实时性很强,比如涉及用户私有数据或动态价格查询,开启缓存会导致用户得到旧数据。第二,用户隔离模型下,如果不同用户共享了相同文本请求,缓存可能把用户A的结果返回给用户B。所以缓存必须配合请求体中的用户标识一起设计,或者只对去敏感化的公共请求开启。第三,缓存命中的请求不走上游,也就不会产生真实费用,如果想准确核算成本,需要能看到命中统计。

我一般建议在小范围开启缓存跑一周,观察命中率和业务反馈,再决定扩大范围。语义缓存比简单缓存复杂得多,它会把意思相近的问题也命中同一条缓存。这种能力在成本优化上很有吸引力,但匹配精度需要大量调试,我只有在一个知识问答项目里用过,并且给它设置了较低的相似阈值,宁可多打上游,也不要误命中。

6. 常见问题速查与避坑实录

6.1 API报错排查

401 Unauthorized:通常是master_key不对,或者请求头里Authorization前缀写成了小写。先检查网关侧日志,如果日志显示转发到上游成功但鉴权失败,再去检查上游密钥是否过期。404 Not Found:一般是路径写错。请确认你调用的地址是/v1/chat/completions,不是/v1/completions或其它路径。429 Too Many Requests:可能是上游限流,也可能是网关自身的配额拦截。这时日志里会有RateLimitError字样,再根据上下文判断是谁在限流。Timeout:这类问题最难排查,因为可能出在业务到网关,也可能出在网关到上游。建议给网关本身配置一个最大超时,比如30秒,上游超时设短一点,比如15秒,两层超时分开,这样至少能定位耗时在哪一层。

6.2 用量统计偏差的几个原因

我发现用量统计和厂商后台对不上,大多数情况来自以下原因:

  • 流式模式下,部分实现没有完整收集usage字段,导致token统计偏低。
  • 工具调用过程中包含多轮回合,但只记录最后一轮。
  • 开启缓存后,部分请求不打上游,自然没有上游计费。
  • 厂商计费口径里包含了输入缓存命中token等细项,而网关的统计模型可能没有完全对齐。

如果财务要求精确对账,我的建议是不要把网关统计当作唯一权威,把厂商后台账单作为第一参考,网关统计用作趋势监控。两者本来统计口径就不同,强行对齐反而制造工作量。

6.3 一些容易忽略却影响很大的配置细节

  • 并发连接池:默认连接数在单实例下够用,但高并发时建议显式调大,否则大量请求会排队等待连接释放。我遇到过上游一切正常,但是网关侧连接池被打满,表现是间歇性超时,很难查。
  • 日志级别:默认日志在开发时很嘈杂,但在生产环境# 一些容易被忽视的配置

最后一个配置细节,是关于litellm_settings里的全局回调。有些团队在配置里把所有回调都打开,日志里吵得要命,跨系统调用也变多,排障时反而不知道看哪条。我自己的原则是:生产环境只保留必要的监控回调,比如Prometheus和数据库用量日志,其它调试类回调全部关掉。等真正需要看某类信息时再临时开启,排查完再关。

另外要提的就是环境变量敏感信息管理。虽然litellm支持在config.yaml里直接写api_key: sk-xxx,但仓库一旦被推送到共享代码库,密钥就泄露了。正确做法是配置里只写$ENV_NAME形式的占位符,在进程环境里注入真实值。我在本地开发时用一个.env文件,在服务器上用系统环境变量或Secret管理工具,这样配置模板可以安全地提交到版本库,密钥本身永远不会出现在明文内容里。

6.4 静态配置与动态管理的边界

很多团队一开始只用了静态config.yaml,后来想要更灵活的动态管理,比如在线增删模型、动态修改路由权重。litellm也有对应的管理接口,但我建议分阶段演进:第一周只用配置文件,等业务完全跑稳、团队理解了路由逻辑之后,再考虑动态管理。原因很简单,配置文件是声明式的,全平台可审计、可回滚;动态管理接口虽然方便,却引入了一个新的可变更入口,如果没有做好权限控制,一次误操作会比配置错误更难回滚。

我在实际项目中遇到过这样的场景:某次线上演示前,有人通过管理接口把一个模型分组里的权重调成了0,导致所有请求都堆到一个便宜的模型上,回答质量明显下降,查了半天才发现是权重配置被改了。从此以后,凡是重要生产流量,我都坚持用配置文件管理路由,动态接口只用于全新建模的试验环境。

写在最后的实操体会

litellm给我的感觉是“把运维经验沉淀成了配置”。它本身的学习曲线并不陡峭,但真正用好它,依赖的是你对流量模型、成本结构、故障边界有多清楚。我从第一次搭网关到现在,最大的体会是:不要一开始就追求把所有功能都用上,而是从最基础的单模型代理开始,让业务跑起来;再逐步加负载均衡、降级、缓存、预算治理。每加一个功能,都要有对应的监控指标和告警,否则加功能就等于加盲区。

如果你所在团队接下来准备接三到五个模型,或者已有的单模型调用开始频繁遇到限流、成本飘忽、切换麻烦这些问题,litellm是个值得花一个下午试一下的方向。我最后想分享的小技巧是:用逻辑模型名代替真实模型名这件事,一定要坚持从第一天起就做,即使你现在只有一个模型。等到第二个模型进来再改,改动面会超乎想象。好的接口网关从来不是解决眼前问题,而是为了让你在未来面对新增模型时,能像切换配置项一样平静。

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

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

立即咨询