大概一年多前,我们团队还处在一种“各家大模型接口各写各的”的混乱状态里。新项目接入一个新的模型服务,往往要大改一遍调用层;密钥散落在配置文件、环境变量和同事的聊天记录里;一到月底想算一下花了多少钱,得翻后台一张一张截图拼。后来我开始在公司内部引入LiteLLM做统一网关,才逐渐把这些事情收敛成一个代理服务加一份YAML配置的问题。这篇博文我想把这些实操经验完整记录下来,包括怎么装、怎么配、踩过哪些坑,以及一套能直接参考的最小落地结构。如果你也在为模型接口不统一、密钥管理混乱、成本核算困难发愁,这篇文章应该能帮你少走不少弯路。
1. 先搞明白 LiteLLM 是什么
1.1 它不是一个普通的SDK封装
很多人的第一反应是:LiteLLM是不是就是把各种大模型API封装成统一调用的Python库?这么说只对了一半。它确实有SDK形态,可以直接在代码里用一行completion()调模型。但它更核心的形态是一个独立运行的代理服务,运行起来之后,所有上游模型厂商的接口差异都被它挡在背后,你的业务代码只面对它暴露出来的那套统一的HTTP接口。
打个比方,你原来要去银行柜台、自助机、手机App分别办业务,每个渠道的流程都不一样;现在有了一个统一的客户经理,你只需要把需求告诉他,他会去各个渠道帮你处理完再把结果拿回来。LiteLLM就是这个客户经理,它后面接了多少家模型厂商,对你来说是透明的。业务代码不用改,就能从模型A切换到模型B,这种“换底层不换上层”的能力是我认为它最值得关注的地方。
1.2 它解决的真实问题是什么
接口碎片化是目前多模型接入最直接的痛点。现在各家模型服务虽然大体上都走REST,但请求字段、认证方式、流式返回格式、错误码体系都各不相同,有些还分国内版和国际版,参数差异更复杂。过去我们每接一家,就要多维护一套封装,测试用例也得跟着写三套甚至更多。而LiteLLM把这些差异全部吸收到一个配置层里,业务侧只认一条接口协议,自然就砍掉了大量重复劳动。
密钥散落也算一个隐藏风险。开发环境、测试环境、生产环境,各个地方都可能放着不同厂商的API密钥。谁也不知道哪个前端项目里有没有把Key硬编码进去。LiteLLM把密钥集中到代理的配置或环境变量里,业务端只需要知道代理本身的入口地址和对应权限,上游密钥完全隔离,泄露面就小了很多。
成本不透明同样让人头疼。日常开发联调时,模型费用往往是被忽略的;等到月底账单出来才发现,光是测试环境就烧掉一大笔token费用。LiteLLM会记录每一次请求的模型、token用量和预估费用,并提供查询接口和可视化数据。对于想要精细控制预算的团队来说,这几乎是刚需。再加上重试、限流、负载均衡这些能力,它不是简单包装,而是一个带着治理功能的服务网关。
1.3 适合谁用,不适合谁用
如果你所在的团队已经同时接了两家以上模型服务,或者预计未来会有切换模型的需求,又或者你需要在多个项目里共用一套模型入口,这时候LiteLLM就非常合适。它能帮你把模型调用统一收口,让各业务线只跟一个网关打交道。
反过来,如果你只是个人写了个小脚本,只固定调一家模型服务,短期也没有切换打算,那直接用自己的SDK调用反而更省事。毕竟LiteLLM也是一层间接层,多一个服务就多一个故障点,没必要为了治理而治理。前期的架构决策一定是基于现实需求,而不是为了用某个工具而强行引入。
2. 为什么值得用统一网关,而不是自己写一套转发服务
2.1 自研一套“翻译层”远比想象中复杂
我见过不少团队,起步时只接了一家模型厂商,觉得一切都很简单,于是在代码里直接写了个request()调用。后来要换第二家了,就抽了个函数,根据模型名走不同的分支。再接第三家的时候,就开始拆模块,引入统一的请求体、统一的错误处理、统一的鉴权。这个过程听起来很正常,但做到中后期,你实际维护的东西已经不再只是一个“翻译函数”,而是一个夹在业务和上游之间的网关。
一旦做网关,就要处理并发控制、超时管理、重试策略、熔断降级、日志追踪、成本统计、密钥轮换、权限隔离。这些听起来都简单,可每一件到了生产环境里都会长出很多细节逻辑。比如上游超时,你是重试还是直接报错?重试会不会把请求重复提交给模型,导致用户重复扣费?限流到底限在业务侧还是网关侧?这些问题要自己全部踩一遍才会懂,而LiteLLM已经把这些沉淀成可配置的模块。
2.2 统一接口带来的迁移自由度
统一接口最大的优势,不是“今天少写了几行代码”,而是“明天换模型不用改业务”。我举个实际场景:我们之前有一个问答机器人,最初接的是模型厂商甲,后来厂商乙出了一个性价比更高的模型,我们做了一个在线评测,发现效果差距不大,价格却便宜不少。按原来的代码结构,要换模型得更新好几处对接逻辑,还要回归测试一遍。
现在业务侧只认识一个名为chat-main的模型别名,实际背后的上游模型是通过配置文件映射的。切换的时候,只需把映射目标从厂商甲的模型改成厂商乙的模型,再重启一下代理,业务代码完全不用动。线上如果出了效果问题,想快速回滚,也只要改回配置重启。这种迁移自由度在频繁追新模型、比价选型的阶段非常值钱。
2.3 密钥和账本从散装变集中
密钥管理也是我特别看重的一块。没用LiteLLM之前,每个项目的配置都不一样,有放到环境变量的,有写到.env的,还有直接写在启动脚本里的。后来我们通过代理统一管理,业务环境只保存一个“虚拟钥匙”,这个钥匙可以限制只能访问某个模型、每个月有固定消费上限。就算某个项目的钥匙被泄露,影响范围也只是一个模型别名和有限额度,不会把上游主密钥暴露出去。
成本核算也跟着清晰了很多。以前要统计每个项目、每类任务消耗了多少token,需要去各家后台导数据再手工汇总。现在代理自身会记录每次请求的模型、输入输出token、费用估算,我只需要定期拉取数据生成报表。这样一线开发人员也不会稀里糊涂把测试费用冲得很高,因为每条请求都能回溯到具体调用点。
2.4 顺带收获了负载均衡和故障转移
大模型厂商的稳定性并不总是那么理想。高峰期偶尔延迟飙升,甚至限流报错都是正常的。过去我们的应对方式是手动切模型,发现某个接口慢了,就把配置改掉重启服务,既被动又影响线上体验。LiteLLM支持在同一别名下挂多个真实模型,配合策略可以把请求分摊到不同厂商,也可以在一个模型失败时自动回退到备用模型。这些策略都是配置化的,不需要业务侧任何改动。
3. 快速上手,先把代理跑起来
3.1 环境准备与安装
LiteLLM是基于Python的工具,所以本机先要有一个可用Python环境。我习惯用虚拟环境隔离依赖,避免污染系统级Python。安装命令很简单:
python -m venv .venv source .venv/bin/activate # Windows下面用 .venv\Scripts\activate pip install 'litellm[proxy]'这里装的是带代理服务的完整包。如果你只需要SDK调用能力,直接pip install litellm也够。但既然我们要用统一网关,装[proxy]版本才省事。安装过程中它会带上一批依赖,包括Web服务相关组件,如果你所在的网络环境拉包比较慢,建议先配置好国内镜像源。
安装完成后可以验证一下:
litellm --help如果能看到帮助信息,说明安装成功。整个过程一般不会遇到什么坑,反而容易踩坑的是Python版本太低的情况,我建议至少使用较新的稳定版本,太老的版本在依赖编译时容易报错。
3.2 最小配置文件
LiteLLM代理的运行入口是一个配置文件,最核心的是model_list。它告诉代理有哪些模型对外提供服务,以及每个别名背后的真实模型和密钥是什么。我先给一个最小配置示例:
model_list: - model_name: chat-main litellm_params: model: your_provider/chat-v2 api_key: os.environ/PROVIDER_A_KEY这里解释一下字段。model_name是业务侧看到的模型名,你可以随便定义;litellm_params.model是代理真正去上游调用的模型标识,注意它有一个“厂商前缀+模型名”的格式,我用your_provider/chat-v2作占位。实际使用时要换成你在LiteLLM支持列表里的对应前缀,这个前缀有时候跟厂商SDK的缩写一致,有时候不完全一样,最好去官方文档的Providers列表确认。如果你直接照抄占位符,代理会报找不到对应provider的错误。
api_key可以写成os.environ/PROVIDER_A_KEY这样的环境变量引用形式,代理启动时会自动从环境变量里读取。这样做的好处后面会细说,总之不要把密钥明文写死在YAML里。
3.3 启动代理并验证连通性
启动前先通过环境变量注入密钥:
export PROVIDER_A_KEY=sk-xxxxx litellm --config config.yaml --port 4000启动日志会显示代理监听在http://0.0.0.0:4000之类的地址。默认接口路径是兼容主流聊天补全格式的/chat/completions,因此可以直接用HTTP工具测试。
curl http://127.0.0.1:4000/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-any" \ -d '{ "model": "chat-main", "messages": [{"role": "user", "content": "你好,做个自我介绍"}] }'注意,默认开发模式下代理对传入的Key不做校验,随便填一个Bearer也可以。这一步是为了让刚开始体验的同事不被鉴权卡住。正式环境一定要开启主密钥校验,这个我后面会专门提。
如果一切正常,你会拿到一个类似choices[0].message.content的返回内容。看到这个,说明代理到上游模型的链路已经通了。接下来就可以让业务代码用这个入口地址来对接了。
3.4 用Python代码对接代理
我们业务里大部分是Python服务,对接方式很简单,直接用HTTP客户端即可,不需要引入厂商自己的SDK。示例:
import httpx resp = httpx.post( "http://127.0.0.1:4000/chat/completions", headers={"Authorization": "Bearer sk-any"}, json={ "model": "chat-main", "messages": [{"role": "user", "content": "今天有什么新闻"}], }, timeout=60, ) data = resp.json() print(data["choices"][0]["message"]["content"])这段代码有一个好处:它完全不依赖任何一家厂商的SDK,只要代理接口格式不变,以后上游怎么切换,代码都不用动。我们内部还有一个Go服务,同样是用HTTP JSON的方式对接,效果一致。这也是我推荐团队统一走代理、而不是把LiteLLM当成普通SDK到处引用的原因。
4. 配置拆解:模型映射、密钥、成本和限流都在这份YAML里
4.1 model_list与模型映射
model_list是核心配置块,除了最简单的“一个别名对应一个真实模型”,还可以做“一个别名对应多个真实模型”。举个例子:
model_list: - model_name: chat-main litellm_params: model: your_provider/chat-v2 api_key: os.environ/PROVIDER_A_KEY - model_name: chat-main litellm_params: model: your_provider_b/chat-fast api_key: os.environ/PROVIDER_B_KEY这样配置之后,代理会认为chat-main这个模型有两个上游后端。代理可以选择轮询、随机、或者根据配置设置优先级等方式分配请求。这个机制对于负载均衡和故障转移特别有用,后文场景部分我会展开。
需要提醒的是,同一个model_name在业务侧看来是同一个模型,但上游可能能力差异很大,比如支持上下文长度不同、是否支持工具调用不同。如果两个模型的能力不对齐,业务代码可能会在某些能力上遇到偶发错误。所以把多个模型放进同一个别名时,最好先确认它们在能力上基本等价,至少都满足业务最低要求。
4.2 虚拟Key与预算控制
LiteLLM代理带了一套虚拟Key管理功能,这是真正解决密钥散落问题的关键。启动代理时,如果你配置了主密钥,代理会要求所有调用方都必须带一个由它签发的虚拟Key,而不是上游的真实密钥。
生成虚拟Key的接口一般是/key/generate,可以在命令行里用curl调用:
curl -X POST http://127.0.0.1:4000/key/generate \ -H "Authorization: Bearer sk-admin-master-key" \ -H "Content-Type: application/json" \ -d '{ "models": ["chat-main"], "budget": 10 }'响应里会返回一个类似sk-xxxx的虚拟Key。这个Key只能访问你限定的那几个模型,并且最多消费10美元(或按你的计费币种换算)。超过预算后,代理会自动拒绝请求。日常我们把不同项目分配到不同虚拟Key,即使某个项目测试把预算烧光,也只是那个项目不可用,不会影响其他项目,更不会碰到上游真实Key。
这里我踩过的一个坑是:如果启动代理的时候没有配置主密钥,直接访问/key/generate接口是开放的,任何人都能生成虚拟Key。这等于没锁门。所以正式部署时一定要先配置主密钥,再把管理类接口保护起来。
4.3 成本追踪与用量统计
成本统计是LiteLLM一个很实用的内置能力。代理每处理一次请求,都会记录模型名、输入token数、输出token数、预估费用、请求时间和调用方虚拟Key等信息。你可以在代理后台或日志里看到。它还提供了一些查询接口,可以按时间范围聚合用量。
我在团队里做了一个小的定时任务,每天把前一天的统计拉出来,按项目和模型维度汇总,然后推送到我们的内部看板。这样当某个模型费用突然上涨,我们能第一时间发现是哪个项目哪类任务引起的。早期没有这个能力时,我们甚至不知道测试环境里有一个轮询脚本一直在调用高配模型,白白跑了好几天。
这里要提醒一句:成本统计数据依赖模型定价表,如果你自定义模型或模型版本比较新,价格可能没有内置数据。这种情况下需要自己完善相关配置信息,否则费用估算可能不准。
4.4 重试、退避与限流参数
上游大模型接口偶尔会返回限流或网络错误,直接让业务报错显然不好。LiteLLM在配置层提供了重试机制,可以在litellm_settings里打开:
litellm_settings: num_retries: 3 request_timeout: 30 allowed_routes: ["/chat/completions", "/embeddings"]num_retries控制失败后的最大重试次数,request_timeout设置单次请求的超时时间。我的建议是,重试次数不要设置太多,3次左右通常够用,太多了反而会在上游真正故障时放大流量。如果你还想做指数退避,LiteLLM也支持相关参数,具体可以参考项目文档,因为我通常不会在生产环境把退避间隔调得很大,那样会让调用方等待过久。
此外,router_settings里还可以配置路由策略和回退列表。比如指定请求先走主模型,失败后自动切到备用模型。这种配置比单纯重试同一模型效果更好,因为故障原因有时候是整个厂商服务都不稳定,再重试也没用。把请求切到另一家厂商,成功率会明显提升。
5. 日常高频场景:多模型切换、Embedding、流式输出
5.1 多模型自动切换与降级
我们生产环境里最常用的功能就是把两个模型放到同一别名下。配置方式前面已经给了,model_list里同一个model_name挂两个litellm_params即可。配合router_settings,可以指定策略:
router_settings: routing_strategy: simple-shuffle fallbacks: - chat-main: ["your_provider_b/chat-fast"]这样设置后,请求会优先打向主模型,如果主模型那边的错误超过阈值或超时次数过多,就会自动回退到备用模型。从用户体验角度来说,面临上游故障时,可能只是响应变慢了,而不是整个服务不可用。我们线上有几次厂商接口半夜波动,都是靠这个方式扛过来的。
有一点要特别注意:不同模型的延迟和效果不可能完全一致,自动降级意味着用户可能某次请求被分到备用模型上,反馈质量和原来不同。如果对模型输出一致性要求极高,比如涉及评分或审核,要谨慎开启自动切换,或者给备用模型也限定能力范围。
5.2 Embedding接口统一
除了对话补全,向量化Embedding也是很多AI应用里绕不开的环节。LiteLLM代理同样提供了/embeddings接口,统一处理各家Embedding模型的调用差异。配置方式和对话模型很像:
model_list: - model_name: embed-main litellm_params: model: your_provider/text-embedding-v3 api_key: os.environ/PROVIDER_C_KEY调用测试:
curl http://127.0.0.1:4000/embeddings \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-any" \ -d '{"model": "embed-main", "input": "你好"}'业务代码同样只需要认识embed-main。这样做的价值在于,如果后面你想把向量模型换成新的、维度更小、效果更好的模型,底层改变不会传导到业务侧,只需要在配置里换模型,并同步检查向量维度是否有变化即可。向量维度变化会影响数据库索引和相似度计算,所以切换时别只盯着接口能不能通。
5.3 流式输出和异步调用的注意事项
流式输出是大模型应用里很常见的需求,尤其是问答机器人,逐字返回能给用户更好的体验。LiteLLM代理对流式也是支持的,请求体里加"stream": true即可。不过流式模式下,代理需要持续保持上游连接和下游连接,对长连接管理有要求。实践中如果业务内网到代理之间网络不稳定,客户端解析SSE流容易断,需要做自动重连或至少给出友好提示。
异步方面,LiteLLM本身基于异步框架实现,代理并发能力不错。但我们内网服务之前遇到过一个问题:上游模型响应慢,业务端把超时时间设得很短,导致代理这边还在等上游返回,业务端就已经把连接断开了。代理虽然会自动处理,但会造成一部分请求浪费。我的建议是客户端超时时间要设得比代理的request_timeout长,至少留20到30秒余量,尤其对长文本生成场景,否则容易出现“用户感觉模型没反应,其实后台还在生成”的误判。
6. 问题排查与避坑清单
| 症状 | 可能原因 | 处理方式 |
|---|---|---|
| 代理启动后端口无法访问 | 端口被占用或防火墙拦截 | 换端口启动,检查监听地址是否为0.0.0.0 |
| 调用时返回模型不存在 | model_name与配置不一致,或上游模型名写错 | 检查model_list中别名和litellm_params.model书写 |
| 返回401/403 | 鉴权Key缺失或虚拟Key权限不足 | 确认请求头带了有效Key,检查虚拟Key绑定的模型范围 |
| 上游请求成功但代理报错 | 上游返回格式非标准,或模型能力设置不匹配 | 打开debug日志,查看上游原始响应体 |
| 成本数据对不上 | 模型价格未配置,或统计口径差异 | 完善模型定价配置,比对token计费方式 |
6.1 代理连不上或端口被占
最常见的是代理启动后,浏览器或命令行访问不到。第一步先看启动日志里监听地址。如果监听的是127.0.0.1,那只有本机能访问;如果是0.0.0.0,才能被其他机器访问。如果换了机器访问不到,很大概率是防火墙没有放行对应端口。调试阶段可以先在本机用ping和curl确认基本连通性,再让其他环境排查网络策略。
端口被占用也比较常见,尤其4000端口在开发环境经常被其他服务占用。可以换一个端口启动,或者查看占用进程处理。不要为了迁就端口而修改代理默认行为,端口在启动命令里指定就行,成本很低。
6.2 模型明明配了,却提示NotFound
这通常不是代理没配置,而是业务请求里的model字段和配置里的model_name对不上。代理对外弹出来的模型列表一定要以model_name为准,不要把上游模型名直接写在请求里。有些同事习惯性地把真实模型名填进来,代理查不到对应别名就会报模型不存在。遇到这种情况,先检查请求载荷,再用管理接口或日志确认代理实际加载了哪些模型名。
还有一种情况是litellm_params.model里的厂商前缀写错了。前缀是LiteLLM解析路由的关键,少一个字母或者大小写不对,代理就会认为是不支持的供应商。这个我没法一一枚举常见前缀,建议直接查看LiteLLM在线文档的Providers列表,按官方写法来填。
6.3 401/403以及虚拟Key权限问题
如果代理开启了主密钥校验,所有请求必须带一个有效Key。401通常是没带Key或Key格式不对;403说明Key是有效的,但没有权限访问你请求的那个模型。虚拟Key是按模型范围绑定的,如果你给某个项目只开了chat-main,它拿这个Key去请求embed-main,当然会被拒。
调这个问题时,先区分是Key完全无效,还是权限不足。前者检查请求头;后者去查看这个Key的绑定信息,或者重新生成一个范围更大的Key。我建议最小权限原则,业务侧只给必要的模型范围。
6.4 延迟突然升高,先别急着骂上游
代理模式下,请求链路变成“业务端 -> 代理 -> 上游模型”,每多一跳都会引入一丁点额外耗时。正常局域网内几百毫秒之内,但如果延迟突然变得很高,原因可能在多重叠加。先看代理日志里单个请求的上游耗时是多少。如果上游耗时就很高,那是模型侧慢;如果上游耗时正常,但业务侧体验慢,那就要检查代理的并发排队和网络带宽。
这里有一个比较隐蔽的坑:代理会把请求按配置路由到不同上游模型,如果你的路由策略是随机分摊,部分请求可能被分到某个延迟特别高的模型上,导致整体平均延迟被拉高。排查时可以把请求的响应信息关联到具体上游模型,看是否总是特定后端慢。如果确认是分配策略问题,调整路由策略或者把慢模型摘出去即可。
6.5 成本数据显示偏差
成本统计依赖每次请求的token计费和模型价格配置。如果你用的是LiteLLM已经内置价格的常见模型,误差通常不会太大。但如果你自定义了新模型或者模型版本很新,又没有配置价格,代理会默认价格缺失甚至计为0,导致报表明显偏低。这种情况需要自己补充价格配置。还有一点,token计数本身是估算值,不一定和上游后台最终账单完全一致,对账时看趋势和相对大小,不必追求每一分钱都完全对齐。
7. 关于部署形态和团队落地的几点个人经验
如果只是本地调试或长期自用,直接跑一个进程就够了,轻量简单。但要是团队共享,我建议至少做这样几件事:一是用环境变量管理上游密钥,配置文件里只允许os.environ/XXX这种形式,这样即使YAML文件被传到内部仓库也不至于泄露真实Key;二是在代理前面再套一层自己的鉴权,虚拟Key只允许从内网访问,管理接口单独限制来源IP;三是把日志和成本数据接入统一的监控系统,保留一定时间的历史数据,方便事后回溯。
配置层面还有一个建议:不要一个配置文件堆到底。我们内部是按环境拆分布的,开发、测试、生产各有独立的YAML,里面模型别名保持一致,但上游模型版本和预算限制可以不同。这样做的好处是,开发环境可以放心用低配低成本模型,生产环境则固定到经过验证的稳定模型,互不干扰。
我在实际使用中踩过的最大的坑,是对模型别名过于自信。早期我把chat-main这个别名挂了好几个上游模型,觉得反正接口一样,谁上都行。后来发现其中某个模型对工具调用的解析格式和主模型不太一致,导致一线反馈偶发的错误。排查了很久才意识到是路由分流到了差异化模型上。现在我的原则是:同一别名下的模型必须先在能力清单上对齐,至少把工具调用、上下文长度、输出格式这些关键项核对一遍,宁可少挂一个模型,也不引入不稳定变量。
另外一个小建议是,LiteLLM的版本升级不要盲目追新,尤其是生产环境。它迭代节奏很快,新版本往往会增加新特性,但也偶尔会调整配置字段的兼容性。我们一般先在测试环境验证一到两周,确认核心代理路由和成本统计没有回归,再决定要不要升。这样虽然会晚一点用上新功能,但能避免大版本变更给业务带来的不可控风险。