我们的 AI 功能上线第一个月,大模型 API 账单直接爆了十几倍,我一查才发现每个请求都在拿最贵的模型、塞着超长 prompt、重复算同样的东西:一次 LLM 调用成本失控的深度复盘
那个账单是月底财务找上门才暴露的:我们给产品加了个 AI 功能(用大模型做一些问答和处理),上线时测着没问题、跑得也挺好。可一个月后,大模型 API 的账单,比我们预估的高了十几倍,财务直接来问"这钱花哪了"。我把调用日志和用量拉出来分析,才看明白,后背发凉:问题出在我完全没有把"调用大模型是要花钱的、而且按 token 计费"这件事放在心上,代码里堆了一堆"烧钱"的写法:第一,所有请求,不分难易,都用了那个最强也最贵的模型——哪怕是"判断一句话是不是垃圾信息"这种简单分类,也用了旗舰模型(贵了几十倍);第二,prompt 塞得又长又满——把大段不必要的背景、完整的文档、冗长的 few-shot 示例每次都原样发过去,而 token 是按量(输入+输出)计费的,prompt 越长越烧钱;第三,完全相同的请求,每次都重新调一遍模型——大量用户问的是同样的问题,我却没有任何缓存,每次都花一次钱重新算一个几乎一样的答案;第四,没设 max_tokens、没监控用量——任由模型生成超长回复、任由调用量疯涨却毫无察觉。这些叠加起来,token 消耗就十几倍地往上窜。根本原因是:我把大模型 API 当成了一个"免费的、随便调的本地函数",而它其实是一个"按 token 精确计费的、昂贵的外部服务"。问题的根,是没把 LLM 调用当成"按量计费的昂贵资源"来对待:不分难易都用最贵模型、prompt 冗长、相同请求不缓存、不控输出不监控,导致成本失控。这篇就把这次"LLM 成本失控"的坑,从头到尾复盘一遍。
故障现场:每个请求都在烧钱
问题在于把昂贵的按量计费 API 当免费函数用,处处烧钱:
# ✗ 出问题的代码: 处处烧钱的LLM调用
def handle_request(user_input):
# ✗ 烧钱点1: 不分难易, 都用最贵的旗舰模型
resp = client.chat.completions.create(
model="gpt-4-turbo", # 哪怕是简单分类也用最贵的(比小模型贵几十倍)
messages=[
{"role": "system", "content": HUGE_SYSTEM_PROMPT}, # ✗ 烧钱点2: 几千token的超长system prompt
{"role": "user", "content": ALL_DOCS + user_input}, # ✗ 把一大堆文档每次原样塞进去
],
# ✗ 烧钱点3: 没设max_tokens, 任由它生成超长回复
)
return resp.choices[0].message.content
# ✗ 烧钱点4: 完全相同的user_input, 每次都重新调一遍, 没有任何缓存
# 现象: API账单比预估高十几倍, 财务来问。
# 为什么烧钱? LLM API 是"按token计费"的(输入token + 输出token 都算钱):
# 1. 模型选型: 旗舰模型单价是小模型的几十倍; 简单任务用旗舰 = 几十倍浪费;
# 2. prompt长度: 输入token按量计费; 每次塞几千token的背景/文档/示例 → 每次都为这些重复内容付费;
# 3. 重复调用: 相同输入本该有相同输出, 不缓存 → 每个重复请求都重新花钱算一遍;
# 4. 输出长度: 不限max_tokens → 模型可能生成超长回复, 输出token也烧钱;
# 5. 无监控: 用量/成本没监控告警 → 涨了十几倍都没人发现, 直到账单来。
# 这些叠加: (贵模型 × 长prompt × 重复调 × 长输出) → 成本被层层放大十几倍。
# 关键: LLM API按token(输入+输出)精确计费、且不便宜; 不分难易用最贵模型、prompt冗长、相同请求不缓存、
# 不限输出不监控, 会让成本层层放大、失控 —— 它是昂贵的按量计费服务, 不是免费函数。
第一次把账单拆解成"贵模型×长 prompt×重复调×长输出"时,我又懊恼又肉疼:"我写的时候满脑子都是'怎么让效果好',压根没想过'每调一次要花多少钱';我把它当成了一个随便调的函数,而它每一次调用都在真金白银地烧钱。"这个坑最隐蔽的地方在于:它不影响功能(效果好好的、用户没感觉),问题只体现在'账单'这个滞后的、月底才看到的指标上;开发和测试时调用量小、成本不明显,完全暴露不出来;等上线放量,成本才十几倍地爆发,而那时已经烧了一个月。下面就来拆解,LLM 调用的成本该怎么控。
第一件事:搞懂 LLM 调用的成本构成
我顺着这次事故,把 LLM 调用的计费模型和成本优化点彻底理清了。
LLM 调用的成本是怎么算的? 有哪些优化点?
【核心: LLM按token(输入+输出)计费、模型越强越贵; 成本=模型单价×(输入token+输出token)×调用次数; 从模型选型/prompt精简/缓存/控输出/监控全面省】
1. 计费模型: 按 token 计费
- token ≈ 词的片段(英文约0.75词/token, 中文约1-2字/token);
- 输入token(你发的prompt)和输出token(模型回的)【都计费】, 输出通常更贵;
- 不同模型单价差异巨大: 旗舰模型可能是小模型的几十倍。
2. 成本公式(直觉):
- 总成本 ≈ 模型单价 × (输入token数 + 输出token数) × 调用次数;
- → 四个可优化的维度: 模型单价、prompt长度、输出长度、调用次数。
3. 烧钱的常见原因(本文):
- 模型选贵了: 简单任务(分类/提取)也用旗舰模型 → 单价高几十倍;
- prompt太长: 每次塞冗余背景/全量文档/超长few-shot → 输入token大;
- 重复调用: 相同输入不缓存 → 重复付费算一样的东西;
- 输出太长: 不限max_tokens → 输出token大;
- 无监控: 不知道钱花在哪、涨没涨。
4. 省钱的对应手段:
- ① 按任务选模型: 简单任务用便宜小模型, 复杂任务才用旗舰(分级路由);
- ② 精简prompt: 只放必要的上下文; RAG只检索最相关的top-k而非全量; few-shot精简;
- ③ 缓存: 相同(或语义相同)的输入缓存结果, 命中就不调模型; 缓存系统prompt(prompt caching);
- ④ 控输出: 设合理的max_tokens, 别让它生成超长;
- ⑤ 监控+预算: 监控token用量和成本、设预算告警/限额, 别等账单来。
5. 还有: 批量/异步(有些便宜)、用更短的输出格式、把确定性的部分用代码做(别都丢给LLM)。
一句话: LLM按token(输入+输出)计费、模型越强越贵, 成本=单价×token×次数; 不分难易用贵模型、prompt冗长、
相同请求不缓存、不控输出不监控会让成本失控; 从模型选型/prompt精简/缓存/控输出/监控多维度省钱。
这套认知,是整个坑的根。计费模型:按 token 计费——输入(你发的 prompt)和输出(模型回的)都计费,输出通常更贵,不同模型单价差异巨大(旗舰可能是小模型的几十倍)。成本公式:总成本≈模型单价×(输入 token+输出 token)×调用次数,四个可优化维度:模型单价、prompt 长度、输出长度、调用次数。烧钱的常见原因(本文):模型选贵了、prompt 太长、重复调用不缓存、输出太长、无监控。省钱的对应手段:①按任务选模型(简单用小模型、分级路由)②精简 prompt(RAG 只检索 top-k、few-shot 精简)③缓存(相同/语义相同输入缓存结果、prompt caching)④控输出(设 max_tokens)⑤监控+预算告警。还有:批量/异步、更短输出格式、确定性的部分用代码做。一句话:LLM 按 token(输入+输出)计费、模型越强越贵,成本=单价×token×次数;不分难易用贵模型、prompt 冗长、相同请求不缓存、不控输出不监控会让成本失控;从模型选型/prompt 精简/缓存/控输出/监控多维度省钱。
第二件事:正解——分级选模型、精简 prompt、缓存、控输出、监控
搞懂了原理,正解就清晰了:按任务难易分级选模型、只放必要的上下文、对相同请求做缓存、设合理 max_tokens、监控用量和成本设预算告警。
# ====== 正解一: 按任务分级选模型 + 缓存 + 控输出 ======
import hashlib
cache = {} # 简单示例; 生产用Redis等, 设TTL
def handle_request(user_input, task_type):
# ★ 缓存: 相同输入直接返回, 不再调模型(省钱又快)
key = hashlib.md5((task_type + user_input).encode()).hexdigest()
if key in cache:
return cache[key]
# ★ 按任务难易选模型: 简单任务用便宜小模型, 复杂才用旗舰
model = "gpt-4o-mini" if task_type in ("classify", "extract") else "gpt-4-turbo"
resp = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": concise_prompt(task_type)}, # ★ 精简的、按任务定制的prompt
{"role": "user", "content": user_input}, # 不再塞全量文档
],
max_tokens=500, # ★ 限制输出长度, 别让它生成超长
)
result = resp.choices[0].message.content
cache[key] = result # 存缓存
return result
# ====== 省钱手段清单 ======
# 1. 分级选模型: 简单任务(分类/提取/路由)用小模型(便宜几十倍), 复杂推理才用旗舰; 可先小模型再升级;
# 2. 精简prompt:
# - 只放"完成这个任务必要"的上下文; 删冗余背景;
# - RAG: 只检索最相关的top-k片段塞进去, 别把全量文档都塞;
# - few-shot示例: 用最少够用的, 别堆一堆;
# - 用prompt caching(很多厂商支持): 固定的system prompt部分缓存, 省重复输入token;
# 3. 缓存结果: 相同输入缓存(精确匹配); 进一步可做"语义缓存"(相似问题命中); 设TTL;
# 4. 控输出: max_tokens设合理上限; 要结构化/简短输出就明确要求, 别让它啰嗦;
# 5. 监控+预算: 监控token用量、按功能/用户拆分成本、设预算告警和限额(rate limit);
# 6. 把确定性的活交给代码: 能用规则/正则/查表搞定的, 别都丢给LLM(又贵又不稳)。
# ====== 一个心态转变 ======
# - 把每次LLM调用都看成"一次有真实成本(几分钱到几毛钱)的付费操作", 而非"免费的函数调用";
# - 写每一处调用时都问: "这一次值这个钱吗? 能不能更便宜地达到目的(小模型/缓存/更短prompt)?"
# 核心: 按任务分级选模型(简单用小模型)、精简prompt(只放必要的、RAG用top-k)、缓存相同请求、
# 设max_tokens控输出、监控用量设预算告警; 把LLM调用当"按量计费的付费操作"精打细算。
修复的核心,是"把 LLM 当付费操作,从模型/prompt/缓存/输出/监控多维度省"。正解:分级选模型 + 缓存 + 控输出——简单任务(分类/提取)用便宜小模型、复杂才用旗舰;相同输入缓存直接返回;设 max_tokens 限制输出。省钱清单:分级选模型、精简 prompt(只放必要的/RAG 用 top-k/few-shot 精简/prompt caching)、缓存结果(精确+语义缓存、设 TTL)、控输出(max_tokens)、监控+预算告警、确定性的活交给代码。心态转变:把每次 LLM 调用看成"一次有真实成本的付费操作"而非免费函数,写每处调用都问"这次值这个钱吗?能不能更便宜达到目的?"。归根结底:按任务分级选模型(简单用小模型)、精简 prompt(只放必要的、RAG 用 top-k)、缓存相同请求、设 max_tokens 控输出、监控用量设预算告警;把 LLM 调用当"按量计费的付费操作"精打细算。
第三件事:LLM 应用工程化中其他容易被忽略的方面
排查后我把 LLM 应用工程化中其他容易被忽略的非功能性方面也系统梳理了一遍。
LLM 应用工程化的其他常见盲区
# 1. 成本失控(本文): 贵模型/长prompt/不缓存/不监控。→ 分级/精简/缓存/控输出/监控。
# 2. 没有超时/重试/限流(同355篇): LLM调用慢或失败拖垮请求。→ 超时+重试+降级。
# 3. 没有监控可观测: 不知道延迟/成本/质量/失败率。→ 埋点监控这些指标。
# 4. 输出不校验直接用(同357/549篇): 幻觉/格式错。→ 校验+兜底+结构化输出。
# 5. 没有评测: prompt/模型改了不知道变好变坏。→ 建评测集量化。
# 6. 没有降级方案: LLM服务挂了整个功能瘫痪。→ 降级到规则/缓存/友好提示。
# 7. 上下文窗口超限: 输入太长被截断丢信息。→ 控长度/检索top-k/历史摘要。
# 8. 没有速率限制/配额: 被刷或bug导致调用量暴涨(成本+触发厂商限流)。→ 限流+配额。
# 共同根源: LLM很容易"调通、出效果", 但把它做成一个"生产级、可持续运营"的功能, 还要操心
# 成本、延迟、可靠性、可观测、质量评测、降级——这些"非功能性"的工程化方面, 在demo阶段都不显, 上线放量才暴露。
# 核心: LLM应用不止"效果好"就行, 要像对待任何生产级外部依赖那样做工程化——控成本、设超时重试限流、
# 监控可观测、校验输出、建评测、有降级; 别让一个"demo能跑"的LLM功能, 上线后在成本/可靠性上失控。
排查让我把 LLM 工程化的其他盲区也梳理清了。一、成本失控(本文)。二、没有超时/重试/限流。三、没有监控可观测。四、输出不校验直接用。五、没有评测。六、没有降级方案。七、上下文窗口超限。八、没有速率限制/配额。它们的共同根源是:LLM 很容易"调通、出效果",但把它做成一个"生产级、可持续运营"的功能,还要操心成本、延迟、可靠性、可观测、质量评测、降级——这些"非功能性"的工程化方面,在 demo 阶段都不显,上线放量才暴露。核心是:LLM 应用不止"效果好"就行,要像对待任何生产级外部依赖那样做工程化——控成本、设超时重试限流、监控可观测、校验输出、建评测、有降级;别让一个"demo 能跑"的 LLM 功能,上线后在成本/可靠性上失控。下面这张图,是这次成本失控坑的成因与解法:
第四件事:LLM 成本优化手段对比表
这次踩坑后,我把几种 LLM 成本优化手段对比成一张表。
| 手段 | 省什么 | 适合 | 注意 |
|---|---|---|---|
| 分级选模型 | 模型单价(几十倍) | 有简单任务时 | 评估小模型效果够不够 |
| 精简 prompt / RAG top-k | 输入 token | prompt 冗长时 | 别精简掉关键信息 |
| 缓存结果 | 整次调用 | 有重复/相似请求 | 设 TTL、注意一致性 |
| 设 max_tokens | 输出 token | 回复可能超长 | 别截断了有用内容 |
| 监控+预算告警 | (防失控) | 所有生产场景 | 事后兜底, 不能省但能止损 |
这张表把省钱手段钉清了。核心是:这些手段对应着成本公式的不同因子(单价、输入、输出、次数)——没有"一招省到底",而是要针对成本的每一个组成部分分别下手;而其中最有效的往往是"缓存"(直接省掉整次调用)和"分级选模型"(单价差几十倍)。它给我的最大启发是:优化任何"成本/资源消耗",第一步都是"把总成本'拆解'成它的构成因子"(成本=单价×用量×次数),然后看每个因子上有没有优化空间、哪个因子的优化杠杆最大;"盲目地省"不如"拆解成本结构、找到大头和高杠杆点精准地省"——80% 的成本往往集中在 20% 的因子上。这给了我一种做成本/性能优化的方法论:面对"成本太高/太慢/太耗资源"的问题,先别急着动手优化,而要先把它'拆开、量化'——"成本/时间/资源, 到底花在了哪几块?各占多少?"(profiling、账单拆分),找到最大的那块、和投入产出比最高的优化点,先打它;"先拆解度量、再针对大头精准优化",是高效优化(而非盲目瞎优化)的根本方法。认清优化要先拆解成本结构、针对大头和高杠杆点精准优化——是这个坑带给我的认知。
第五件事:这次事故暴露的"功能正确"之外的工程维度
这次让我反思更深一层:我的 AI 功能"能用、效果好",却在"成本"上栽了。我把"功能维度"和"非功能维度"对比成表。
| 维度 | 功能性(我当时只盯着) | 非功能性(我忽略了) |
|---|---|---|
| 关注点 | 效果好不好、对不对 | 成本、延迟、可靠、可观测 |
| demo 阶段 | 能验证 | 基本不显 |
| 上线放量 | — | 集中爆发(成本/性能/故障) |
| 本文 | AI 回答得挺好 | 账单爆了十几倍 |
| 本质 | "能做到" | "可持续地、经济地做到" |
这张表道出了一个常见的盲区。核心是:我做这个 AI 功能时,注意力 100% 在"功能性"(效果好不好)上,而完全忽略了"非功能性"的维度(成本、延迟、可靠性、可观测性);"功能能跑通"只是"能做到这件事",而"可持续地、经济地、稳定地做到"才是一个真正能上线运营的功能所需要的;很多 demo 惊艳的功能,死在了从"能跑"到"能运营"的这段非功能性鸿沟上。它给我的深刻启发是:评判一个系统/功能是否"真正可用",不能只看"功能对不对",还要看一整套"非功能性需求(NFR)"——性能、成本、可靠性、可扩展性、可观测性、安全、可维护性;"功能正确" 只是必要条件而非充分条件;一个"功能完美但成本爆炸/动不动就挂/出了问题查不到"的系统, 在生产环境里是不合格的。这给了我一种做生产级系统的全面视角:设计/上线任何"要在生产环境长期运行"的功能时,要主动地把"非功能性需求"和"功能"放在同等重要的位置去考量——问"它要花多少钱?够快吗?挂了怎么办?怎么监控?能扛多大量?";"不只追求'功能能跑', 更要为'可持续、经济、可靠地运营'而设计",是从'做出一个 demo'走向'交付一个生产级系统'的关键跨越。认清功能正确只是必要非充分、要把非功能性需求和功能同等重视——是这个成本坑带给我的工程态度。
第六件事:接入 LLM 功能时,我现在的自检习惯
现在每当我要在产品里接入一个 LLM 调用,我都会先按这张图问自己:
这张图的精髓,是"按难易选模型、能缓存就缓存、精简 prompt、控输出、上监控"。简单任务小模型、重复请求缓存、prompt精简、输出设 max_tokens、全程监控+预算告警。这套习惯,让我从"接 LLM 只想效果"变成了"同时把成本和可观测纳入设计"——核心始终是:把 LLM 调用当按 token 计费的付费操作,分级选模型、缓存相同请求、精简 prompt、设 max_tokens、监控用量设预算告警,别让成本失控。
我立下的几条规矩
这场"AI 功能账单爆十几倍"的事故,换来了我做 LLM 应用时,刻进骨子里的几条铁律:
- LLM API 按 token(输入+输出)精确计费,模型越强越贵,它是昂贵的付费服务不是免费函数。
- 成本 = 模型单价 × token 数 × 调用次数,四个维度都能优化。
- 按任务难易分级选模型,简单任务(分类/提取)用便宜小模型。
- 精简 prompt,只放必要上下文,RAG 只检索 top-k 而非全量。
- 相同/相似请求做缓存,命中就不调模型(最省钱)。
- 设 max_tokens 控输出,监控 token 用量和成本,设预算告警。
- 功能正确只是必要非充分,要把成本/延迟/可靠/可观测等非功能性需求同等重视。
写在最后
回头看,这场由"把 LLM 当免费函数随便调"引发的、账单爆十几倍的事故,真正教给我的,远不止"缓存、用小模型省钱"这几个技巧。它让我对"我们做一件事时, 很容易只盯着'把它做成'这个目标, 而对'做这件事持续付出的代价'无感; 而当这代价是'悄悄累积、滞后才显现'的时候, 等我们察觉, 往往已经付出了惨重的账单",有了一次刻骨的体会。我栽跟头,是因为我对那个大模型调用的"成本"是完全'无感'的——在我的代码里,调用 LLM 和调用一个本地函数看起来一模一样(就一行 client.create(...)),它没有任何东西提醒我"这一行,每执行一次,就花掉了几分钱";于是我就像调用免费函数那样,毫无成本意识地用最贵的模型、塞最长的 prompt、重复地调——每一次都在烧钱,而我浑然不觉;直到这些"看不见的、一点点累积的成本",在月底汇成一张十几倍的账单,才砸到我脸上。这让我领悟到一个关于"无感的累积成本"的深刻认知:最危险的成本,是那些"单次很小、无感、且滞后结算"的累积型成本——因为每一次的代价小到让你忽略, 而它的真实规模, 要等"乘以巨大的次数、并滞后地汇总"之后才显现;这不止是 LLM 的 token, 也是云资源的按量计费、是技术债的利息、是每个小小的低效在规模化后的总和;"对无感的、累积的、滞后的成本, 要有意识地去'看见它、度量它、控制它'"。这给了我一种成本意识上的清醒:使用任何"按量付费/有持续代价"的资源时(API 调用、云服务、计算资源),要主动地为那些"默认无感"的成本建立"感知"——埋点度量它、监控它、设预算告警, 让"看不见的累积成本"变得"看得见、可控制";"把无感的成本显性化、可度量化、可告警化",是避免被'悄悄累积的代价'反噬、对系统真实开销负责的关键意识。认清无感累积的滞后成本最危险、要主动把它显性化可度量可告警——这,是我用一次 API 账单爆表的事故,换来的、关于 LLM 应用、也关于如何对系统真实代价保持清醒的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次接入 LLM 时,顺手加上缓存、用对模型、把用量监控起来,那我对着那张十几倍的账单复盘的这段时间,就值了。
—— 别看了 · 2026