我最初只是好奇,一个月流水才几十万的产品线,是怎么在三个月内把 LLM 账单从 4.8 万干到 27 万的。财务把月度成本表格发到工作群的时候,整个技术部都安静了几秒。当时我们还在用最原始的“大家在各个业务代码里直接拼 prompt、直接调各家 API”的方式,没有统一网关,没有用量统计,更没有成本预算的概念。说白了,LLM 能力刚接入那阵子,所有人的兴奋点都在“能做什么”上,“花多少钱”根本没人算过。
这篇文章就是想把我们后来做的“成本感知型 AI 平台”拆开讲清楚。它不是什么高深的研究项目,而是一整套针对 LLM 费用失控的治理方案:从成本数据的采集、计量、分账,到模型路由、缓存、Prompt 瘦身这些优化手段,再到最终承载这些能力的平台架构。如果你所在的团队也正在被大模型账单追着跑,这篇文章值得你花十分钟看完。
1. 账单失控的真相:钱不是一次花光的,是一点点漏光的
先聊点扎心的。很多人一听到“LLM 费用暴涨”,第一反应是“是不是有人在拿生产环境的 Key 乱调模型”。我们排查了一圈发现,真正的问题比这隐蔽得多。账单爆炸是多个环节的小浪费叠加出来的结果,每一个单独拎出来看都不至于让人警觉,合在一起就是灾难。
1.1 一条 prompt 从 200 token 涨到 3000 token,成本翻了 15 倍
我们最早接的一个客服机器人场景,刚上线的时候 system prompt 大概 200 token,一问一答的上下文控制在 1000 token 以内,单次调用成本折合人民币不到一分钱。当时业务方觉得效果不够好,产品经理往里加功能,工程师往里加工具描述,运营往里加品牌话术。三个星期以后再看,system prompt 变成了 1800 多 token,再加上每次请求带上的对话历史、业务标签、用户画像数据,单次调用成本轻松突破一毛钱。
一天一百万次调用,就是一天 10 万块钱的输入费用。而这一切只是改了一段“看起来没多大变化”的代码。
这个案例里最反直觉的地方在于:token 是隐形成本。你加一段 500 字的工具说明,在代码评审里完全看不出问题,但它会跟着每一次请求一次不落地发送给模型。调用量越大,越小的 prompt 增量越致命。
1.2 重试风暴:一次用户请求,背后可能付了三倍的钱
另一个典型案例是重试逻辑。我们某个定时分析任务,调用外部大模型做文本摘要,代码里有这样一段逻辑:
python复制for attempt in range(3):
try:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
)
break
except Exception:
time.sleep(2)
看起来就是一个标准的“失败重试三次”,但问题在于:这个任务每五分钟跑一次,某天上游数据源不稳定,每次请求都超时,每次超时都会触发重试。也就是说,原本一个任务消耗一次 token,因为异常,实际消耗变成了两次三次。而且这个任务处理的文本还特别长,一次就是 2 万 token 起步,重试两次就是 6 万 token。
那天单单这一个任务就烧掉了正常情况下的 5 倍费用。
1.3 模型误选:杀鸡用牛刀,而且天天在用
还有一个特别普遍的问题:不分场景,一律调用最强的模型。
我们内部做过一次统计,接入平台的所有请求里,有大约 34% 的调用其实只做关键词抽取、意图分类、实体识别这类简单任务。用当时的标杆大模型来做,效果确实好,但成本也是小模型的十倍不止。有些场景甚至连模型都不用换,用 GPT-4o-mini 级别的模型,准确率几乎没差别,价格却便宜了一个数量级。
那为什么大家不换?因为“没人告诉我这个场景可以用便宜模型”,大多数人只会往自己最熟悉的型号上面靠。
1.4 传统 API 网关为什么会漏掉 LLM 成本
我们当时内部不是没有监控。普通 API 网关有请求量、响应时间、错误率,但这些东西和“成本”之间隔着一道很大的鸿沟:网关根本不知道 token 数量。在大模型调用这个场景里,请求次数和成本早就不成线性关系了。一次 100 token 的请求和一次 10 万 token 的请求,从 API 网关视角看都是“一次请求”,从成本视角看,差了三个数量级。
也是从那一刻起我们想明白了一件事:不能继续用传统的“流量视角”来治理 LLM 调用,必须构建一套“成本视角”的治理体系。这就是“成本感知型 AI 平台”这个想法的起点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 成本可观测性建设:先把每一分钱变成可查询的数据
做成本优化之前,第一件事不是省钱,而是搞清楚“钱到底花在哪了”。没有数据,一切优化都是拍脑袋。
2.1 核心计量模型:忘记请求数,记住 token 数
我们定下了一个最基本的成本公式,所有平台功能都围绕它展开:
code复制单次调用成本 = 输入 token 数 × 输入单价(/百万 token) / 1000000
+ 输出 token 数 × 输出单价(/百万 token) / 1000000
这个公式看起来简单,但做工程落地的时候有几个细节很容易忽略:
- 不同模型的定价不同,同样的 token 数量,用大模型和用 mini 模型,价格差出 10 倍以上;
- 有些服务商对输入有缓存折扣,命中缓存的部分价格可能低很多;
- 输出 token 通常比输入贵,很多时候一次调用的成本大头在输出端;
- 批量任务的单价可能比实时调用低 50%,不影响用户体验的场景可以走异步批处理。
我们对每一种接入的模型都会做一次成本定价配置,把模型名、输入单价、输出单价、计费单位都做成配置项。平台在记录每一次调用的时候,会同时记录实际的 token 消耗和计算出来的成本金额,这样后续所有分析都能基于成本展开。
2.2 在链路哪个环节做计量:我们选择了网关层
计量这件事可以放在三个位置:SDK 内、业务服务内、网关层。
- SDK 内:能拿到最完整的信息,但没法统一控制,每个业务接入的 SDK 版本不一致,数据格式也五花八门;
- 业务服务内:可以拿到业务上下文,但侵入性强,让每个业务都上报成本数据,落地阻力巨大;
- 网关层:所有请求都会经过网关,天然具备统一采集的条件。
我们最后选了网关层来做计量。所有业务方的 LLM 调用必须统一走平台网关,网关在转发请求后拿到响应里的 usage 字段(token 消耗),加上定价表,实时计算出这一笔调用的成本,然后异步上报到数据链路。
2.3 成本明细数据的写入链路
我们是这样设计的:
text复制业务服务 → 平台网关(记录 usage 和 meta 信息) → Kafka
→ 消费服务(批量计算成本、补充标签) → ClickHouse
→ 成本看板 / 告警服务 / 报表系统
之所以用 Kafka 而不是同步写入到分析型数据库,是为了避免成本统计影响请求主链路。网关本身要保证转发延迟,不能因为算成本、写数据库反而把请求拖慢了。异步写入可以保证即使成本系统故障,请求链路不受影响。
ClickHouse 是后面加的。刚开始我们觉得成本数据量不大,用 MySQL 就够了,但后来发现要频繁做聚合查询、按小时分组、按项目和标签下钻分析,MySQL 在这种查询下压力偏大。ClickHouse 对这些分析查询支持很理想,一条 SQL 就能快速算出来。
2.4 成本看板应该看哪些指标
成本大盘我们做成了三个层次:
- 趋势层:今日成本、昨日成本、本月累计、环比变化率。重点关注“消耗速度”是否偏离预期;
- 分布层:按项目、按模型、按调用来源拆分成本占比,一眼看清钱主要烧在哪个环节;
- 明细层:单次调用级别,找到成本最高的 Top N 请求,直接定位到具体的用户、对话或功能。
另外设置了两类告警:一类叫“预算消耗速度告警”,按月预算和线性消耗模型,实时计算“按当前速度,月底会花多少钱”,超过预期就告警;另一类叫“异常单请求告警”,单次调用成本超过设定阈值就通知管理员,比如一次调用超过 5 元,大概率是出了问题。
3. 分账与配额:让每个业务团队面对自己的账单
成本可观测之后,有一个非常现实的问题摆在面前:看到的每一分钱,到底是哪个团队花的?如果不解决分账问题,成本看板就是一个“大锅饭看板”,大家看一眼然后继续无感。
3.1 强制以 API Key 为维度治理分账
我们做了一件很强硬的事:全面收紧 API Key 发放。
以前各个业务是自己在服务商控制台开 Key,挂在自己的代码里。平台化之后,所有 Key 都由网关统一管理,每一个 Key 必须绑定业务项目和负责人,不允许出现“公共 Key”这种无主资源。每个 Key 还会打上 environment 标签区分生产还是测试环境。
这一步执行的时候阻力最大,因为工程师们习惯了“拿一个 Key 直接能用”的自在。我们当时采用了一个折中方案:存量 Key 给了两个月的迁移宽限期,宽限期内可以继续用,但成本都挂在“待认领”池子里;两个月之后还没认领的 Key 直接停用。结果迁移率比想象中顺利,人在“看得到自己的账单”和“被停掉服务”之间,会选择前者。
3.2 配额机制:软限额和硬限额要区分开
有分账就有配额。我们把配额分成两类:
- 软限额:比如某个项目月度预算 10 万元,当实际消耗达到预算的 80% 时,业务负责人会收到通知;达到 100% 时,通知频率升级,并抄送他的上级。软限额的目的是“提醒”,不阻断业务。
- 硬限额:比如某个项目月度预算 20 万元,达到硬性上限时,网关会直接拦截新的 LLM 调用,返回“预算已用尽”的错误。这个设定是可配置的,默认不开启,但对一些内部测试项目、非核心功能,我们会强制开启,防止有人把演示项目的 Key 跑出巨量账单。
硬限额一定要做在网关层,而不是做在看板或者告警系统里。因为告警系统是异步的,从“超预算”到“告警发出”再到“人工介入处理”可能需要十几分钟,而这十几分钟里,钱还在烧。网关层做拦截,可以做到实时生效。
3.3 内部对账:让财务数据和技术数据对齐
这一步是我们踩坑踩出来的教训。平台上线第一个月,我们输出的成本报表和财务从服务商后台拉出来的账单对不上,差了两万多元。财务那边直接质疑平台的准确性。
后来排查发现差异主要来自两个地方:
- 一些请求重试成功后,我们只在记录里写入成功的那一次,失败重试的次数漏记了;
- 服务商对某些模型的计费有一个最低单价,实际价格和按我们定价表算出来的有差异。
从那以后,我们建立了一个“每日对账任务”:每天定时从服务商后台拉取前一天的账单明细,和平台记录的成本明细做一一比对,差异超过阈值就自动告警。这个机制不复杂,但对建立信任非常关键,业务方对数字有信心之后,成本治理后续推动起来就顺利多了。
4. 优化三板斧:模型路由、语义缓存与上下文瘦身
有了分账数据以后,我们开始针对性的优化。观察了一段时间,把主要成本增量归纳成了三类问题:模型选型浪费、重复计算浪费、上下文膨胀浪费。解决它们的办法对应就是模型路由、语义缓存和上下文瘦身。这几板斧落地之后,我们的月成本下降了接近一半。
4.1 模型路由:让“简单任务”不再为“复杂模型”买单
模型路由的思路很直接:判断请求的复杂度,自动选择适合的模型。实现上,我们在网关层加了一个“请求特征分析”模块,根据调用时传入的 route_hint 参数,把一个请求归入不同等级:
json复制{
"route_hint": "simple",
"model_preference": ["gpt-4o-mini", "fast-model"],
"fallback_model": "gpt-4o"
}
简单任务直接走便宜模型,如果便宜模型超时或返回空结果,再考虑升级到更强模型。这个 fallback 机制很重要,不然为了省钱把效果搞差,业务方会强烈反弹。
后来我们更进一步,做了一个动态路由策略:根据一段时间内该请求路径的成功率、耗时和成本数据,自动调整路由权重。比如某个业务调用便宜模型成功率长期稳定在 99% 以上,就完全切到便宜模型;如果某个场景便宜模型频繁出问题,再自动回退到大模型。
4.2 语义缓存:对重复问题说“哪还用得着再算一次”
团队里做得最成功的优化之一,是给客服问答场景上了语义缓存。
客服场景有个特点:用户问来问去,很多问题本质上高度相似。比如“怎么退货”,可能有一万种说法:“我要退货”“东西怎么退”“怎么寄回去”“不满意想退掉”,但它们的语义是一样的。传统缓存无法解决这个问题,因为字符串 hash 完全对不上。
我们的做法是:将 request 的 messages 内容做 embedding,然后把向量存入向量数据库,同时把对应的模型输出作为缓存值。新请求进来,先算当前 messages 的 embedding,查一下向量库里有没有相似度超过 0.95 的历史请求,有就直接返回历史结果。
实践下来效果很显著:这个场景的缓存命中率在 32% 左右,意味着大约三成请求根本不需要再调用模型了。这些请求的延迟几乎为零,而且最关键的是,不花钱。
4.3 上下文瘦身:花在“传递”上的 token 才是最大的浪费
上下文膨胀是最容易被忽视的坑。
很多开发者在做多轮对话的时候,会把整个会话历史一股脑传给模型。第一轮可能只有 500 token,聊到第五十轮的时候,已经累积到 2 万 token,而且绝大多数是早期对话,对当前这一轮的回答也没什么帮助。这些历史信息就像餐桌上的一道菜,从第一道菜就开始摆在桌上,吃到后面菜越来越多,但桌子空间有限,模型处理的开销也越来越高。
我们在平台层做了一个上下文管理器,支持三类策略:
- 截断:只保留最近 N 轮对话;
- 摘要:把早期对话用一个小模型生成摘要,替换掉完整历史;
- 关键信息抽取:从历史中提取用户偏好、订单号、地址等结构化信息,作为短上下文传给模型。
对于客户服务这种典型场景,摘要策略效果最好。用 mini 模型生成一次摘要的成本大概只有几厘钱,却能把后续每一次请求的输入 token 减少 60% 到 80%。
4.4 重试策略的规范化:别让异常变成账单炸弹
重试策略放在优化章节的最后,是因为它太容易被忽略,却往往是最致命的一笔浪费。
我们后面的网关层强制下发了一个重试规则:
- 遇到 429 限流错误:可以重试,但使用指数退避(1s、2s、4s),最多 3 次;
- 遇到 5xx 服务端错误:可以重试,但最多 2 次;
- 遇到 4xx 客户端错误:不重试,直接返回调用方报错;
- 所有重试必须传递原始的 request id,方便追踪整条链路。
另外还加了一个“重试熔断”机制:如果同一个业务 Key 在短时间内重试命中率过高,网关会直接拒绝这个 Key 的重试请求,并通知负责人。这样即使某个业务代码写得再激进,也不会拖垮整个平台的预算。
5. 平台架构的落地思考:找开源项目做基座,还是自己造轮子
这个章节写给正在考虑做类似平台的人。我们的原则是:能基于开源项目扩展,就不要从零造轮子。成本感知型 AI 平台的核心竞争力在于成本治理逻辑,而不是网络转发层。
5.1 网关选型对比
市面上能做 LLM 网关的组件不少,我们内部做了几个候选的对比:
| 方案 | 定位 | 优势 | 劣势 |
|---|---|---|---|
| LiteLLM Proxy | LLM 统一网关 | 支持几十家模型供应商、统一接口、内置预算控制 | 重度的自定义逻辑需要二次开发 |
| Helicone | LLM 可观测性网关 | 自带成本追踪、看板、缓存 | 开源版功能有限,有些高级能力需要云端版 |
| Langfuse | LLM 可观测性平台 | 追踪、测试、评估能力强 | 偏观测和调试,不适合直接做流量网关 |
| 自研网关 | 定制化 | 完全可控,灵活度高 | 开发维护成本高,需要自己烧脑处理各家 API 差异 |
我们最终选型是:用 LiteLLM Proxy 作为网关基座,在上面做二次开发。LiteLLM 提供一致的 API 格式,我们已经不需要重复处理各家模型厂商的请求/响应差异了,需要重点做的就是增加计量上报、模型路由、配额拦截这些和成本相关的逻辑。
5.2 请求链路中的成本归因实现
整个平台比较核心的链路是这样的:
text复制SDK/Gateway Client → LiteLLM Proxy
→ 请求鉴权(API Key → 项目 → 配额检查)
→ 模型路由(选择满足 cost 约束的模型)
→ 调用上游模型
→ 解析 usage → 计算 cost → 异步上报 Kafka
→ 缓存判断(命中则直接返回)
在 LiteLLM Proxy 上,我们给每个请求的 metadata 里注入了项目 ID、调用方服务名、业务标签,这样后续成本聚合时就能按这些维度任意下钻。
5.3 成本估算也要有实时降噪机制
做成本看板的时候遇到一个持续存在的小问题:数据会重。比如某个请求第一次超时,网关重试成功,但这笔请求实际上消耗了两次上游调用。如果只看最后一次成功请求的 usage,成本就会被低估。
我们的处理是在重试链路里,把每次上游调用的 usage 都累加到原始请求 ID 上。也就是说,一次逻辑请求对应的成本 = 第一次调用成本 + 所有重试调用成本。这个统计口径和财务账单最终能对齐,也是靠这里。
6. 上线三个月的复盘:数字变化、团队习惯和踩过的坑
最后聊聊实际效果和踩坑记录,这部分可能是对你有实际帮助的内容。
6.1 费用下降的核心数据
上线三个月后,我们把优化前后的数据做了对比。月总调用量增长了约 20% 的前提下,总成本下降了 46%。
三个核心举措的预估贡献比例:
| 优化项 | 成本降幅贡献占比 |
|---|---|
| 模型路由(低复杂度任务切便宜模型) | 40% |
| 语义缓存(客服类场景命中率 32%) | 25% |
| 上下文瘦身(摘要策略减少输入 token) | 25% |
| 重试与异常治理 | 10% |
这里要说清楚,不同业务线差异很大,如果你的业务大多是复杂推理、创作类场景,模型路由的收益就没那么高,相反的语义缓存和上下文压缩空间会更大。做优化之前先看自己的成本分布,不要照搬别人的策略。
6.2 团队行为的变化比那 46% 更重要
比钱更值钱的是团队建立了成本敏感意识。平台上线之前,工程师提测 AI 功能,问的是“效果怎么样”;平台上线以后,大家开始主动问“这样调用会不会超预算”。每次功能评审,我要求附带一张“预估成本表”,估算单次调用成本、日均调用量、月度总成本,成本和收益一起决策。
这个变化不是靠制度逼出来的,而是靠“数据透明”自然形成的。当一个工程师在代码评审页面就能看到自己上一次提测功能在灰度环境的实时成本曲线,不需要任何人教育,他自己就会去优化 prompt。
6.3 踩坑记录:定价表的动态性比想象中强
最后说几个踩过的坑,给后来人排雷。
第一坑:定价表不是写死一次就完事的。 各家模型的价格调整频率比想象中高。我们上线初期,把定价表放在配置文件里,结果某模型降价了,平台还在按旧价格算成本,导致成本看板比实际账单高出不少。后来改成支持动态从服务商后台拉取价格信息,每天自动更新,才解决这个问题。
第二坑:缓存命中的 token 计价逻辑不一样。 有些服务商提供 prompt 缓存功能,命中缓存的输入 token 价格会便宜很多,甚至有些服务商对缓存命中和未命中分别计费。如果不区分这两类 token,成本核算会有明显偏差。
第三坑:过度追求低延迟容易造成重试误伤。 我们曾经把某关键路径的请求超时时间从 5 秒改成 2 秒,结果上游模型偶尔响应超过 2 秒,大量请求触发重试,成本反而上升了。后来把超时和重试策略拆开配置:超时时间设慢一点,重试次数设少一点,整体效果更好。
第四坑:网关自身的高可用要提前布局。 所有业务流量都走网关后,网关就成了单点。我们早期网关只部署了一个实例,有一次发布配置导致网关重启,期间所有 LLM 调用全部失败。后来做了多实例部署和健康检查,发布时用滚动更新,才把这个风险压下去。
最后分享一个我的工作习惯:任何新的 LLM 功能上线前,必须先跑三天灰度,对比这个功能的“预估单次成本”和“实际单次成本”。两者差异超过 30% 就不能全量上。这个习惯看着简单,但它能逼着你在功能设计阶段就把成本想明白,而不是等账单来了才反应。
