← 返回资讯

LiteLLM 虚拟 key:团队 API key 预算控制实战

2026-08-07

一个团队刚开始接大模型 API 的时候,通常是这样的:某个人去厂商控制台申请了一把 key,丢进公司的密码管理器,谁要用谁自取。三个月后,月账单从两百块变成六千块,老板问「这钱花在哪了」,没人答得上来。

答不上来不是因为不认真,是这条链路上根本没留下能回答这个问题的信息。所有请求都带着同一把 key 打到上游,厂商那边看到的只有一个调用方,你能拿到的最细粒度就是「本月总计」,再往下切就得靠人肉翻日志、翻代码、问同事。

共用一把上游 key,实际上会同时踩三个坑:

  • 账单分不清是谁花的。市场部做内容生成、研发做代码补全、数据组跑批量清洗,全都混在一个数字里。
  • 任何人都能调任何模型。有人图省事在测试脚本里写了最贵的那个模型,你不会知道,直到账单出来。
  • 某个脚本跑飞了没有闸门。死循环、重试风暴、一次性把十万条数据丢进去——在总额度耗尽之前,没有任何东西会拦住它。

LiteLLM Proxy 的虚拟 key 就是解这三件事的。我想强调一点:它不是「多加一层安全」这么简单的定位。它真正的价值在于让成本变得可归属——把「谁花了多少」从一个需要事后调查的问题,变成一个基础设施自动记录的属性。安全是顺带的收益。

如果你还没把网关本身跑起来,可以先看 LiteLLM 网关入门,本文默认 Proxy 已经在跑了。

生成第一把虚拟 key

生成接口是 POST /key/generate,鉴权用 master key 的 Bearer token。官方文档给的 curl 示例是这样的:

curl 'http://0.0.0.0:4000/key/generate' \
  --header 'Authorization: Bearer <master-key>' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "models": ["gpt-3.5-turbo", "gpt-4"],
    "metadata": {"user": "email@example.com"}
  }'

逐行拆开看:

第一行的端口是 4000,这是官方示例里出现的端口。你自己部署时如果改过监听端口,这里要跟着改。

第二行的 Authorization: Bearer <master-key> 是这套体系的根。master key 是签发者凭证,能签发、修改、停用所有虚拟 key,所以 master key 绝不能出现在任何业务代码里,它只应该存在于运维侧的密钥管理体系和签发脚本中。我见过有人把 master key 直接当业务 key 用在应用里,那等于把整套机制的意义清零:一旦这个应用的配置泄露,对方拿到的不是一把有额度、有白名单的受限 key,而是整个网关的管理员权限。

第三行声明请求体是 JSON。第四行开始的 --data-raw 才是重点——请求体里每个字段都对应虚拟 key 的一个约束维度,这个示例只用了两个,实际生产里下面这些你会想都配上。

参数逐个拆:每一个都是一道闸门

models(数组):模型白名单

这个参数指定该 key 允许调用的模型白名单。它是最被低估的一个字段。

大多数人第一眼把它当成安全配置——防止有人调用不该调的模型。但它同时是成本边界,而且往往是成本这一侧的作用更大。不同模型之间的单价可以差出一个数量级,而调用方代码里换个模型名只是改一个字符串的事。你在预算上设了上限,但一个错误的模型名会让这个上限在几小时内被撞穿,而不是几周。

我的做法是:白名单按「这个场景实际需要什么」来配,而不是按「这个团队大概会用到什么」来配。内部客服机器人只需要一个中等模型,那就只给它这一个;哪天真需要升级,改一次配置的成本远低于放任它随便调的风险。还有个不太明显的好处:写错模型名的请求会立刻被网关拒掉,问题在测试阶段就暴露,而不是安静地打到一个你不希望它用的模型上。

user_id / team_id(字符串):归属

这两个字段把 key 关联到具体的用户和团队。官方说明里有一句关键的话:花费追踪通过数据库在 key / user / team 三个层级自动进行

请注意这个「自动」。只要你在签发 key 的时候填了归属,之后所有成本归集都是免费送的——不需要在应用侧埋点,不需要在日志里打标,不需要写脚本聚合。而如果签发时没填、事后想补,你面对的就是「这把 key 是三个月前谁申请的、当时给谁用的」这类考古工作。

所以我的建议很明确:在发 key 的那一刻就填好归属,而不是等到要对账的时候再补。填这两个字段的边际成本是零,但它决定了三个月后你能不能一句话回答老板的问题。这也是为什么 key 的申请流程值得做得稍微「重」一点——不是设卡,而是在申请环节就强制把「谁用、哪个团队、干什么用」记清楚,流程设计可以参考 API key 管理规范

max_budget(浮点,USD):花费上限

这就是前面说的那个「跑飞了的闸门」,单位是 USD。

它和限速是两个不同维度的保护。限速管的是瞬时压力,预算管的是累计总量。一个每分钟只发十个请求的脚本,如果每个请求都塞进去一份超长文档,一样能在一周内烧掉四位数。只限速拦不住它,只有累计花费上限能。

关于额度怎么定,后面「设计 key 体系」那节会展开讲,这里先说结论:不要一次给足

duration(字符串):有效期

有效期字段接受字符串,官方给出的示例值是 "30min""30d"

这个字段的价值集中在临时用途上,而这类用途比大多数人以为的要多:

  • 试用与评估。有人想试试你们的网关能不能满足需求,发一把 "30d" 的 key。他试完了,key 自己过期,不需要任何人记得去回收。
  • 外包与合作方。项目周期是两个月,那就发一把覆盖两个月的 key。合同结束时权限自动消失,不依赖于「记得跟对方要回来」这种人治手段。
  • 演示环境。给客户演示、给投资人看 demo、内部技术分享,这类场景发一把 "30min" 级别的短 key 就够了。
  • 临时排查。线上出问题需要单独发一把 key 做对比测试,用完即焚。

这些场景的共同点是:权限的自然结局应该是消失,而不是留存。人工回收权限的失败率极高——不是因为人不负责,是因为项目结束时所有人都在忙下一件事,没人会想起三周前发出去的那把 key。有效期把这件事从「需要记住」变成「不需要记住」。

反过来说,长期 key 就该有轮换计划。一把没有有效期、发出去再也没人碰过的生产 key,两年后会变成没人敢动的东西:不知道谁在用,不知道停掉会挂哪个服务。轮换的意义不只是「怕它泄露」,更在于持续验证「这把 key 在被谁使用」仍然是清楚的——如果一次轮换让你发现某个已经忘掉的服务挂了,这次轮换就已经赚回成本了。

tpm_limit / rpm_limit(整数):限速

tpm_limit 是每分钟 token 数上限,rpm_limit 是每分钟请求数上限,都是整数。

两个都要设,因为它们防的是不同的事故形态。rpm_limit 防的是请求数量失控——重试风暴、循环里忘了加 sleep、并发数配错了一位数。tpm_limit 防的是单请求体量失控——有人一次性把整本手册塞进上下文。只设 rpm_limit 的话,十个请求也能打出天量 token。

数值怎么定?如果你手头没有上游厂商的确切限速数值,稳妥做法是从低往高走:先设一个明显偏保守的值,跑一两周,看它有没有真的挡住正常业务,挡住了就往上调一档。同时让调用方准备好退避重试——限速触发时能自动退让,而不是直接把错误抛给用户,这是「保守起步」可行的前提。先观测再加压,比一开始拍脑袋配个大数字安全得多,日常观测可以参考 网关用量统计

aliases(对象):模型名映射

这个字段做模型名映射,字面上只是「让调用方用 A 这个名字,实际打到 B 这个模型」。但这层间接性带来的是调用方和具体模型的解耦:应用侧写的是业务语义的名字,网关侧决定它实际落到哪个模型上。有了它,下面这些操作都不再需要动调用方的代码:

  • 换模型。上游出了新版本、或者你发现某个更便宜的模型效果够用,改网关配置就行。原本要改代码、走发版、协调所有用到它的服务,现在这是一次配置变更。
  • 不同环境用不同模型。开发环境的别名指向便宜的小模型,生产环境的同名别名指向大模型。同一份代码两个环境跑,不需要条件分支。
  • 紧急降级。上游某个模型出问题,把别名临时指向另一个可用模型,业务先跑起来,而不是干等上游恢复。
  • 调用方读得懂。业务代码里写着能表达用途的名字,比一串带版本后缀的模型标识可读性好得多,新人接手也不用猜。

我建议这个字段默认就用上,哪怕一开始映射是一对一的。等到你真要换模型那天,有没有这层间接性差别巨大。

metadata(对象):自定义元数据

自定义元数据,官方示例里用它记了一个邮箱。你可以往里塞任何对你有意义的结构化信息:申请工单号、成本中心编号、负责人、用途说明、预期下线时间。

它不参与任何强制逻辑,但在事后追查时非常有用。半年后你看到一把陌生的 key 在持续消耗预算,metadata 里的一条工单号就能直接把你带到当初的申请记录。

key 的生命周期:查、改、停

签发只是开头,日常运维靠的是这几个接口:

接口用途
/key/info查该 key 的花费
/key/update修改该 key 的配置
/key/block停用
/key/unblock重新启用

/key/info 是你的日常巡检入口。配合前面说的三层级自动追踪(key / user / team),你可以从任意一个维度往下钻。

/key/update 让「先给小额度、观察后再调」这个策略成为可能。如果改额度要重新签发 key、再通知调用方换配置,那没人愿意做这件事,结果就是所有人一开始就申请一个大数字。有了原地修改,调额度是运维侧的单方面操作,调用方无感知。

/key/block/key/unblock 这一对,是我认为最容易被忽略但最该建立肌肉记忆的能力。出事的时候先 block,不要直接删。

理由很实在:删除会一并带走这把 key 的所有信息——关联的用户、团队、历史花费、元数据——而你恰恰是在出事的时候最需要这些。凌晨发现某个 key 异常消耗,正确顺序是先 block 止血,再从容去查 /key/info 看它花在哪、metadata 里记的负责人是谁、调用模式和平时是不是不一样。查清楚了,误操作就 unblock 放回去,泄露就走安全流程。

删了就没证据了。而且删除不可逆、unblock 可逆——在信息不全的时刻,永远优先选可逆的那个动作。

怎么设计你的 key 体系

前面都是单把 key 的参数。真正决定这套东西有没有用的,是你怎么切分。

按什么维度发 key

判断标准其实只有一条:你希望账单按什么维度切开,就按什么维度发 key。

这句话听起来简单,但它能直接把「按团队还是按项目还是按人」这个争论终结掉。因为答案取决于你们组织里谁为这笔钱负责:

  • 如果成本是按部门核算的,那就按团队发——每个团队一把(或一组),团队负责人对自己的额度负责。
  • 如果是按项目立项、项目结束就结算,那就按项目发——项目的生命周期天然对应 key 的生命周期,项目一结束 key 就该停。
  • 如果是内部工具、每个人自己用,那就按人发——这种模式下 user_id 是主键,谁用得多一目了然。

大多数团队最后会落在「团队为主、重点项目单独拆」的组合上。不要一上来就追求最细粒度——每把 key 都是要管理的对象,几百把无人认领的 key 比几把粗粒度的 key 更糟糕。粒度的正确标准是「刚好能回答你实际会被问到的问题」。

环境必须隔离

不管上面选了哪个维度,开发 / CI / 预发 / 生产各发各的 key,这一条没有例外。

最直接的理由是:CI 跑飞了不该影响生产额度。CI 是最容易失控的一环——某次改动让用例数量翻倍、某个 mock 失效导致测试真打到了上游、有人在流水线里加了个循环。这些迟早会发生,发生时你希望它撞上 CI 那把 key 的预算上限然后失败,而不是悄悄吃掉生产环境这个月的余额。

隔离之后还有额外好处:各环境参数可以完全不同。CI 的 key 配很小的 max_budget 和很低的 rpm_limit,用 aliases 指向最便宜的模型;生产的 key 反过来。同一份代码,行为差异全在网关配置里。

预算怎么定

不要一次给足。

新场景上线时没人知道真实消耗是多少,估算永远不准——不是估高就是估低,而估高的代价是你等于没设这道闸门。

我的做法是:先给一个明显偏小的 max_budget,小到「真撞上限了说明我对这个场景的理解有问题」的程度;跑一两周,用 /key/info 看实际消耗,再按实测数据调整。撞上限不是坏事,那是这套机制替你提前发现了问题,用 /key/update 往上调一档成本很低。反过来,一开始就给个「肯定够用」的大数字,等于把这个字段变成摆设——你不会收到任何信号,直到月底账单给你信号。

再配上定期回看,这套东西才真正跑起来:每个月扫一遍各 key 的消耗曲线,涨得异常的去问一句,长期为零的直接 block 掉。做法可参考 成本监控实践

aliases 做解耦

最后再强调一次这个字段:发 key 的时候就把 aliases 用上,让调用方使用业务别名而不是具体模型名。它的收益是延迟兑现的,刚开始看起来只是多此一举的一层间接;但当你需要全局换模型、给某个环境降级、或者紧急切换的时候,有没有这层间接决定了这是一次配置变更,还是一场跨团队的发版协调。

落地自查清单

  • master key 只存在于运维侧的密钥管理体系里,没有任何业务代码持有它;所有应用一律用虚拟 key。
  • 每把 key 都填了 user_idteam_id,不留空——归属信息只能在签发时填,事后补不回来。
  • 每把 key 都配了 models 白名单,且白名单按「这个场景实际需要什么」收敛,而不是按「大概会用到什么」放宽。
  • max_budgettpm_limitrpm_limit 三个上限都设了,且首次配置刻意偏保守,计划两周后用 /key/info 的实测数据复盘调整。
  • 开发 / CI / 预发 / 生产各有独立的 key,参数按环境差异化配置,CI 那把额度尤其要小。
  • 所有临时用途(试用、外包、演示、排查)一律带 duration,让权限的默认结局是过期;长期 key 排进轮换日程。
  • 调用方代码里使用 aliases 定义的业务别名,网关侧掌握到具体模型的映射。
  • 应急流程写清楚「先 /key/block 再排查」,并在演练里跑过一遍 block 与 unblock。

最后提一句边界:本文用到的参数名和接口路径都来自 LiteLLM 官方文档中虚拟 key 相关的说明。这个项目迭代很快,字段的可选值、默认行为,以及路由与可观测方面的配置项,都请以你部署的那个版本的官方文档为准。虚拟 key 的设计思路(归属、白名单、预算、限速、别名解耦、可逆停用)是通用的,换个网关也成立;但具体字段名怎么拼,动手前翻一眼当次官方文档,这个习惯值得保持。

相关阅读