做AI应用的人,几乎都被同一个问题折磨过:模型供应商不止一家,接口格式各有各的习惯,鉴权方式不一样,限流策略不一样,报错信息更是一个比一个难懂。今天接A家,明天换B家,后天又要在新业务线里同时用两家。代码里开始堆满if-else:如果是这个模型,走这套参数;如果是那个模型,又得换一套SDK。这种状态持续不了太久,团队里总会有人站出来说——咱们把这些模型接入层收口吧。
litellm就是干这个的。它是一个开源的LLM网关,或者说模型路由代理,把几十种大模型服务商的API规范统一成一套OpenAI兼容的接口。你的业务代码只需要按照一种格式发请求,网关负责翻译成目标服务商能理解的格式,并把结果原样返回。更关键的是,它把密钥管理、负载均衡、预算管控、日志审计这些企业级需求,全部做到了网关这一层。
这篇文章适合三类人读:第一类是正在做多模型应用的开发者,想摆脱各家SDK反复横跳的困扰;第二类是团队里负责AI基础设施的工程师,需要给同事们提供一个统一、可控的模型访问入口;第三类是刚接触AI开发、想理解“模型网关”到底是什么东西的新人。我会从底层机制讲到部署实操,再讲我实际使用中踩到的坑,尽量让不同阶段的读者都能找到自己需要的内容。
1. 为什么要自建一个LLM网关
1.1 多供应商接入的日常混乱
我先还原一个比较常见的场景。一个做内容生成的产品,主力模型用某家的GPT系模型,有时需要调用Claude系做长篇总结,偶尔还要用开源模型做低成本批量处理。代码里要装三套SDK,维护三套密钥,处理三套错误码。这还只是调用层。等到要统计每个业务线花了多少钱、每个用户消耗了多少token的时候,又得分别在几个服务商控制台里对账单。这种碎片化带来的维护成本,会随着模型数量和调用量的增加快速膨胀。
我见过不少项目走到这一步后开始内耗。研发说“我就想调个模型,怎么还要看那么多文档”,运维说“密钥散落在各个服务器上根本管不住”,老板说“到底哪条业务线在烧钱,你给我个数字”。这些问题表面上各自独立,根子上都是同一个来源——模型接入层没有统一收口。
1.2 统一网关到底解了什么题
把网关架在业务代码和模型服务商之间,本质上是把“接入多样性”从业务逻辑里剥离出来。业务服务只需要知道网关的地址,以及一个约定好的模型名称,剩下的鉴权、重试、路由、换模型,全都在网关内部搞定。这样做的好处非常直接:
- 模型切换零代码改动:今天用GPT系模型,明天想换Claude系,只需要改网关配置里这个模型指向的目标地址,业务代码一个字符都不用动。
- 密钥集中管理:真实的模型服务商密钥只存在于网关环境中,业务服务拿到的是一把“虚拟密钥”,即使泄露也容易被单独吊销,不会暴露上游真实凭据。
- 统一预算与审计:所有调用从一个口子进出,按团队、按业务线、按用户分账都变得简单,不再需要去各家控制台手工对账。
- 故障隔离与降级:某家模型服务商限流或宕机时,网关可以自动把流量切换给备用模型,业务侧甚至感知不到异常。
这些能力放在“统一网关”这个位置去做,比放在业务代码里做要干净得多。这就是litellm这类项目存在的核心逻辑。接下来我会深入拆解它内部是怎么实现这些能力的。
2. 核心机制拆解:一次请求的正确转发路径
2.1 从OpenAI兼容协议说起
litellm对外暴露的是OpenAI兼容的对话补全接口,具体来说就是/v1/chat/completions和/v1/completions这两个端点。为什么选OpenAI的格式作为统一标准?因为目前市面上绝大多数模型服务商和开源推理框架,要么原生兼容OpenAI格式,要么提供了兼容层。用这套格式作为“通用语言”,业务端几乎不需要学习成本。
一个典型的请求长这样:
{ "model": "gpt-4o", "messages": [ {"role": "system", "content": "你是一个有用的助手"}, {"role": "user", "content": "帮我写一份周报"} ], "temperature": 0.7 }litellm收到这个请求后,先根据model字段找到对应的“模型路由条目”,然后读取该条目里配置的真实服务商信息和密钥,把请求转换成对应服务商的原生格式,再异步调用上游API。上游返回的响应,再由litellm统一转换回OpenAI格式返回给调用方。整个过程中,业务方看不到任何服务商相关的细节。
这里有个容易被忽略的技术细节:不同模型的参数体系差异很大。有的模型支持top_p,有的模型没有frequency_penalty,有的模型对temperature的取值上限有严格限制。litellm在转换请求时会对参数做兼容处理,不支持的参数会被剥离或转换,避免因为参数差异导致上游报错。这个细节在实际使用中非常关键,它决定了你写业务代码时可以“粗放”一点,把精细化的参数适配交给网关。
2.2 模型路由、密钥管理与成本控制
模型路由的核心是一门配置文件,通常用YAML编写。我见过很多第一次用litellm的人,以为“路由”是靠代码写出来的,其实不是,它就是一份声明式的映射表。一个最小的配置长这样:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: claude-sonnet litellm_params: model: anthropic/claude-sonnet-4-20250514 api_key: os.environ/ANTHROPIC_API_KEY这里关键的字段有两个。第一是model_name,这是业务侧看到的模型别名,完全由你自己定义。第二是litellm_params里的model,它由“provider前缀/具体模型名”组成,openai/、anthropic/、azure/、vertex_ai/这些前缀告诉litellm该把请求转发给哪一类服务商。这个设计很巧妙:前缀负责定位“怎么发请求”,冒号后面的模型名负责定位“发到哪个模型”。
密钥管理在litellm里有两种做法。一种是直接写在litellm_params里,适合个人使用或小团队;一种是通过os.environ/环境变量名引用,适合集中管理。我强烈建议在任何团队场景下都用环境变量引用,不要把真实密钥写进YAML仓库里,一旦配置文件被同步到远程仓库,密钥就等于直接公开了。
成本控制是litellm比较有特色的能力。你可以对每个虚拟密钥设置max_budget,按美元金额限制。比如给A团队一把虚拟密钥,预算是100美元,这100美元用完之后,这把密钥的所有请求都会被拒绝。这个能力对于做内部平台的同学来说非常实用,它相当于在给公司内部“卖模型额度”,可以在预算维度上做到自主管理,而不用事事都找模型账号管理员。
2.3 负载均衡、自动重试与故障降级
真实的生产环境里,单一服务商、单一账号并不可靠。如果你每天有大量的模型调用,一定会遇到429限流、上游5xx错误、网络抖动这些问题。litellm把应对这些情况的能力内建到了网关层。
先看负载均衡。在配置里,同一个model_name可以对应多个litellm_params条目:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY_2这种情况下,litellm会把请求轮询分发到两个上游凭据上,分散单一账号的限流压力。这个功能对使用按量付费账号的团队尤其有用,因为它不需要切换服务商,就能把单个账号的并发压力摊薄。
再看自动重试。litellm支持对特定状态码触发重试,比如429和5xx。重试次数、重试退避策略都可以配置。底层的重试逻辑做得比较克制,默认不会无限重试,而是用指数退避加抖动的方式,避免网关本身被重试流量打垮。我一般在配置里设置num_retries: 3,也就是最多重试三次,再多就该进入降级逻辑了。
真正的杀招是“故障降级”,也就是fallback。比如主模型是GPT系,一旦它连续失败达到阈值,网关自动把请求转发给Claude系备用模型:
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY fallbacks: [anthropic/claude-sonnet-4-20250514]这里fallbacks是个列表,可以配置一个或多个备用目标。当主模型抛错或超时,litellm会按顺序尝试列表里的备用模型。这个能力让我在最紧张的一次线上故障里保住了业务:某家服务商半夜发生大规模超时,网关自动切换后,业务侧只是感到少数请求略微变慢,没有出现大面积报错。这种体验,是所有“把模型接入写在业务代码里”的方案都给不了的。
2.4 缓存、日志与可观测性设计
litellm在内置了响应缓存机制。对于重复的请求,比如相同的model+messages组合,它可以直接返回缓存的响应,既能降低调用成本,又能显著降低延迟。缓存可以存在内存里,也可以使用Redis,生产环境建议用Redis,否则网关重启缓存就全丢了。
日志与审计是比缓存更重要的企业级能力。litellm会把每一次调用的模型名、请求token数、响应token数、延迟、成本估算、调用方密钥标识、状态码全部记录下来,写入SQLite、PostgreSQL或其它数据库。这意味着你可以直接对数据库跑SQL,回答“某条业务线这个月花了多少钱”“哪个用户今天触发了多少次失败请求”这类问题。
有一次我需要排查一个诡异的超时问题,就是靠查数据库里的调用记录,把所有延迟超过10秒的请求捞出来,按目标模型分组后发现是某个特定模型偶尔出现慢响应。这种粒度的问题定位,离开网关层的日志几乎无从下手。可观测性的价值平时不显山不露水,一出故障,它就是救命的工具。
3. 实操部署:从零搭建litellm服务全流程
3.1 最小安装与第一个配置文件
litellm是用Python写的,安装非常省事。我建议在一个独立的Python虚拟环境里安装,避免污染系统环境。
python -m venv litellm-venv source litellm-venv/bin/activate pip install "litellm[proxy]" litellm装完之后,验证一下版本:
litellm --version接着创建一个配置文件。按照我前面说的,先做最简配置,只放一个模型,确保链路通顺再扩展。
model_list: - model_name: my-llm litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY创建配置之前,先在环境变量里放好真实密钥:
export OPENAI_API_KEY=你的真实密钥这里有一个新手经常踩的坑:如果在litellm_params里直接写api_key: sk-xxx,那么走上生产环境后,密钥会出现在进程列表、日志甚至异常堆栈里。用os.environ/变量名这种写法,litellm会在启动时读取环境变量,密钥本身不会落地到配置文件中。
3.2 启动网关并用curl完成首次转发
启动网关只需要一行命令:
litellm --config config.yaml --port 8000看到Uvicorn running on http://0.0.0.0:8000之类的日志,说明服务起来了。注意,litellm默认会生成一个虚拟密钥用于本地访问,也可以在启动参数里指定,或者稍后在管理接口里创建。初次测试时,如果没有指定虚拟密钥,可以在启动日志里找到自动生成的Virtual Key,先记下来。
然后用curl模拟一次真实的转发:
curl -X POST http://localhost:8000/v1/chat/completions \ -H "Authorization: Bearer sk-你的虚拟密钥" \ -H "Content-Type: application/json" \ -d '{ "model": "my-llm", "messages": [{"role": "user", "content": "你好,请简单介绍一下你自己"}] }'如果一切正常,你会看到一段JSON响应,内容来自上游模型,但整体结构完全符合OpenAI格式。到这一步,最小链路已经通了。我个人的习惯是先把这一条链路跑通,再去加其他模型,因为这样可以判断问题出在配置、网络还是密钥,而不至于一上来就面对一堆复杂的报错。
3.3 虚拟密钥与团队授权配置
接下来要做的,是把真实密钥“藏起来”,给业务方发“虚拟密钥”。这个操作需要在litellm的管理端完成。litellm内置了一套Key Management界面,启动服务后默认会开在一个独立端口,通常是/ui路径。在这个界面里可以生成虚拟密钥,并为每把虚拟密钥设置预算上限、速率限制、可访问的模型范围。
这个设计的精髓在于“密钥可销毁、可追踪”。真实密钥只有网关管理员持有,业务方和研发同事拿到的都是虚拟密钥。一旦发现某把虚拟密钥被滥用,直接在管理界面把它删掉就行,完全不影响上游真实账号。如果你把真实密钥分发到了几十台服务器里,出问题时要逐个清理,那种酸爽经历过一次就不想再来第二次。
对于微服务较多的团队,我建议按“服务名”来分配虚拟密钥,比如service-content、service-summary。这样数据库里的调用日志天然就带上了服务维度的标签,后续做成本分摊、异常排查都会省很多力气。
3.4 生产环境下的容器化部署
本地环境跑通以后,生产环境部署我建议直接用Docker。litellm官方提供了镜像,一条命令就能把网关跑起来:
docker run -d \ --name litellm-proxy \ -p 8000:8000 \ -v $(pwd)/config.yaml:/app/config.yaml \ -e OPENAI_API_KEY=你的真实密钥 \ ghcr.io/berriai/litellm:main-latest \ --config /app/config.yaml生产环境下有几个细节要注意。第一,前面提到过,日志数据库建议用PostgreSQL而不是默认的SQLite,因为SQLite在并发写入高时会有锁竞争。第二,网关前面要挂一个负载均衡器,网关本身是无状态的,可以横向扩容多个副本,由负载均衡层做流量分发。第三,Redis缓存要独立部署,这样多个网关副本共享同一份响应缓存和速率限制计数,不会出现“每个副本各自为政”的问题。
容器化部署还有一个容易被忽略的点:配置文件里的环境变量引用。在docker run命令里通过-e传入的变量名,和YAML里os.environ/OPENAI_API_KEY引用的是同一个变量名,这个对应关系必须在部署文档里写清楚,否则同事接手时会很迷惑。
4. 网关选型的边界与架构取舍
4.1 与几种常见方案的对比
很多人第一次接触litellm时都会问:这和直接装官方SDK有什么区别?和商业方案有什么区别?我整理一下自己的理解。
先说直接集成SDK。如果你是个人项目,只有一个模型供应商,那直接装官方SDK是最省事的,完全没有必要引入网关。但只要是“多供应商、多人协作、成本要管控”这三个条件占了两条,网关的价值就体现出来了。SDK方案意味着每个调用方都要自己处理密钥、重试、成本统计,这些工作放到团队里就是重复造轮子。
再说商业方案。市面上有一些提供统一模型API调用的商业服务,从效果上看和litellm很接近,但对有些团队来说,数据要经过第三方服务,这是一个绕不开的合规顾虑。litellm是开源的,部署在自己的基础设施里,数据链路完全可以控制在内部网络,这一点对安全性要求高的项目会非常关键。商业方案胜在免运维,但litellm属于“自带运维工作量换自主可控”,怎么选,取决于团队对数据敏感度和运维投入的判断。
整体来看,litellm最适合的是“有一定工程能力、需要给多个团队提供模型访问入口”的中小型技术组织。它牺牲的是一次性的部署和配置成本,换来的是后续所有模型接入的长期便利。
4.2 不建议使用litellm的几种情况
聊完优势,也得说说边界。我不是那种“什么场景都推自建网关”的人,下面这几种情况,litellm未必是最好的选择。
第一,单模型、单服务商、单账号的极简场景。你只是自己写脚本调一个模型玩,代理层完全是多余的,官方SDK更直接。
第二,延迟极度敏感的场景。网关无论如何都是一个中间层,每次调用至少多一次HTTP跳转和JSON序列化/反序列化。虽然这个额外开销通常只有几毫秒到几十毫秒,但如果你的业务对延迟的要求已经到了极致,那么每一点毫秒级的开销都需要被审视。这种情况下应该让业务直接连上游API,把网关卡在非核心链路上。
第三,如果整个团队只有个位数的人,且完全不关心预算分摊和审计,直接调用各家API也完全够用。网关这种基础设施,只有调用量足够大、参与方足够多的时候,它的管理价值才能真正体现出来。规模太小的时候,维护网关本身的成本可能比它省下的成本还高。
我曾经在一个很小的项目里强行上了网关,结果每天多了一台要维护的服务,业务代码也没省多少事,最后又拆掉了。这个教训让我明白,任何基础设施选型都要先算规模账,不要为了“架构先进”而架构先进。
5. 常见故障排查与避坑实录
5.1 路由不命中:模型请求报404/400
litellm使用中最高频的问题,就是请求时报错“model not found”或者404。大多数情况下,这个错误和网络无关,而是路由没有命中。
排查思路是这样的:先去配置文件的model_list里看有没有一个model_name和你请求里model字段完全一致的条目。注意,这里必须是完全一致,连空格和大小写都不能差。我之前遇到过一位同事配了一个叫gpt-4o,请求时写的是gpt4o,少了中间的横杠,结果白白排查了半天。
另一个原因是litellm_params里的model前缀写错了。比如把anthropic/claude-...写成了openai/claude-...,litellm就会用OpenAI的协议去请求一个Anthropic的模型,结果自然是以报错收场。检查配置时,先确认前缀和服务商匹配,再确认模型名在服务商那一边是真实存在的。
5.2 认证鉴权问题:401与权限角色混乱
401错误在litellm里也见过不少。如果你是刚搭好服务,用虚拟密钥请求时报401,先确认这把虚拟密钥是否有访问该模型的权限。在管理界面的Key配置里,有一个“模型访问范围”的设置。如果这把虚拟密钥只能访问A模型,你拿它去请求B模型,网关会直接拒绝。
另一个容易忽略的点是,虚拟密钥是会过期的。管理界面可以给每把密钥设置过期时间,过期后请求会一直401。所以如果某天突然有一批请求集体401,先看看是不是密钥过期了。特别是测试用的密钥,经常用完之后就躺在那里过期,等到线上联调时突然出来咬人一口。
5.3 预算与限流不生效的真相
max_budget设置了为什么没生效?我踩过一次很深的坑:在虚拟密钥上设置了预算,但模型请求是直接走了model_name,而litellm的预算计算和落库统计之间存在一个时间窗口。预算检查是异步结算的,不是每一笔请求都实时校验。也就是说,在大量并发突发的场景下,实际消费可能会略微超过预算上限,然后才被拦截。这不是bug,而是分布式系统下“最终一致”的典型表现。
理解了这一点,就不会对“预算超了一点点”大惊小怪。如果你要求强约束,就需要把并发控制能力搭配使用,比如在虚拟密钥上同时设置速率限制,让每秒请求数可控,这样预算超限的幅度会小很多。
5.4 性能抖动、超时与重试风暴
最让我长记性的一个问题是重试风暴。有一段时间,某个上游模型频繁超时,litellm自动重试三次,结果三次都超时,业务端报错的数量反而比不重试时更多。后来我才意识到,重试本身是有代价的,如果上游已经处于异常状态,重试只会加剧拥堵,形成“越重试越慢、越慢越重试”的恶性循环。
现在的做法是给每个模型设置合理的timeout,并且在重试配置上做减法:num_retries: 2,同时配合fallback。与其在同一个目标上重试三次,不如把第三次机会留给备用模型。网关的容错能力,不是看它重试有多勤奋,而是看它能多快地识别故障并切换到健康节点。
数据库写入也是一个隐藏的性能瓶颈。默认情况下,日志写入和请求处理是同步的,在高并发时数据库写入慢会拖累整体响应时间。生产环境我建议开启异步日志写入,或者把日志写入队列化,确保日志系统出问题时不阻塞核心的模型转发链路。
写在最后
从我自己的使用体感来说,litellm这类网关的价值,不在于它有多炫酷的技术,而在于它把那些重复、琐碎、容易出错的“模型接入杂活”集中处理掉了。它让我把精力从“怎么适配各家API”上挪开,放到真正重要的产品逻辑上。如果你正在为多模型接入发愁,又不想被各家服务商的差异绑住手脚,花一个下午把它部署起来,再跑通本文里最简配置,你会立刻感受到“统一出口”带来的清爽感。任何时候想切换或扩充模型,也只是改一份配置而已。