LLM 成本优化完全指南:从一次"换了便宜模型账单却没降多少"看懂为什么 token 用量才是大头

2024 年我做一个 AI 文档问答功能用户问一个问题系统从公司的文档库里找出相关内容连同问题一起塞进提示词调大模型生成回答第一版我做得很顺手用户的问题来了我把可能相关的文档段落都拼进提示词调一个能力最强的大模型把答案返回本地一测回答得又准又全我心里很笃定 AI 功能嘛把上下文给足用最好的模型效果就有保证这功能稳了可等它一上线有了真实用量一串问题冒了出来第一种最先把我打懵 API 账单是按天出的某一天它突然比前一天翻了三倍我对着账单查了半天根本说不清到底是哪一批请求把钱花掉了第二种最难缠我听说换个更便宜的小模型能省钱真换上去之后单次调用的报价确实低了一截可月底一看总账单没降多少第三种最头疼我扒了一遍请求日志发现大量用户在反复问几乎一模一样的问题而每一次系统都老老实实地重新走一遍完整流程花一次全价第四种最莫名其妙我明明在代码里限制了模型的输出长度想着输出短了就省钱了可账单的大头压根不在输出上我盯着这一连串问题想了很久才彻底想明白第一版错在一个根本的认知上我以为 AI 的成本就是调用次数乘以单次价格想省钱无非两条路要么少调几次要么换一个单价便宜的模型可这个认知是错的本文从头梳理为什么换个便宜模型省不下多少钱 LLM 的账单到底该怎么拆怎么用缓存掐掉重复调用怎么压缩上下文别把整个文档都塞进去怎么按任务难度给模型分级以及一些把 LLM 成本治理做扎实要避开的工程坑

2024 年我做一个 AI 文档问答功能——用户问一个问题,系统从公司的文档库里找出相关内容,连同问题一起塞进提示词,调大模型生成回答。第一版我做得很顺手:用户的问题来了,我把可能相关的文档段落都拼进提示词,调一个能力最强的大模型,把答案返回。本地一测,回答得又准又全,我心里很笃定:AI 功能嘛,把上下文给足、用最好的模型,效果就有保证,这功能稳了。可等它一上线、有了真实用量,一串问题冒了出来。第一种最先把我打懵:API 账单是按天出的,某一天它突然比前一天翻了三倍,我对着账单查了半天,根本说不清到底是哪一批请求把钱花掉了。第二种最难缠:我听说换个更便宜的小模型能省钱,真换上去之后,单次调用的报价确实低了一截,可月底一看总账单,没降多少。第三种最头疼:我扒了一遍请求日志,发现大量用户在反复问几乎一模一样的问题,而每一次,系统都老老实实地重新走一遍完整流程、花一次全价。第四种最莫名其妙:我明明在代码里限制了模型的输出长度,想着输出短了就省钱了,可账单的大头,压根不在输出上。我盯着这一连串问题想了很久,才彻底想明白:第一版错在一个根本的认知上。我以为AI 的成本,就是"调用次数 × 单次价格"——想省钱,无非两条路,要么少调几次,要么换一个单价便宜的模型,单价压下来,总价自然就下来了。可这个认知是错的。LLM 的计费单位,根本不是"次",而是 token(模型处理文本的最小单位)——而且,你发进去的输入 token 和模型吐出来的输出 token,是分开计价的,价格还不一样。一次调用到底贵不贵,取决于你往提示词里塞了多少东西、模型生成了多长的回答。"换个便宜模型",动的只是单价里的一个系数,而你账单的真正大头——每次都全量塞进去的那一大段上下文——你一个 token 都没省。要把 LLM 用得起,根上要明白:成本优化不是"挑个便宜模型"这一个动作,而是一套关于"怎么让每次调用花更少的 token、怎么把不必要的调用从源头就掐掉"的工程。本文从头梳理:为什么换个便宜模型省不下多少钱,LLM 的账单到底该怎么拆,怎么用缓存掐掉重复调用,怎么压缩上下文别把整个文档都塞进去,怎么按任务难度给模型分级,以及一些把 LLM 成本治理做扎实要避开的工程坑。

问题背景

先把 LLM 的计费方式说清楚。你调用一次大模型 API,费用不是一个固定的"单次价",它由两部分相加:输入部分——你发给模型的所有文字(任务说明、上下文、用户问题)折算成的 token 数,乘以输入单价;输出部分——模型生成的回答折算成的 token 数,乘以输出单价。输出单价通常比输入单价贵好几倍,但很多功能(比如文档问答)恰恰是输入特别长、输出相对短。

错误认知是:成本等于次数乘以单价,省钱就是减次数或降单价。真相是:成本等于 token 用量乘以单价,而 token 用量——尤其是输入 token——才是大多数功能里真正能优化、也最该优化的部分。把这一点摊开,第一版的几类问题就都能解释了:

  • 账单查不出大头:你没有按请求记录 token 用量,就只有一个总数,无法定位是哪类请求、哪个环节贵。
  • 换便宜模型省得少:换模型只降了"单价",而你每次塞的那一大段上下文的"token 用量"丝毫没变。
  • 重复问题花全价:没有缓存,两个一模一样的问题会被当成两个全新请求,各花一次钱。
  • 输出限制没用:你的功能是输入长、输出短,大头在输入 token,限制输出省的是小头。

所以让 LLM 成本降下来,核心不是"找个便宜模型",而是一整套工程:看懂账单、缓存掉重复、压缩上下文、按难度分级用模型。下面六节,就从第一版"换个便宜模型就能省钱"的想当然讲起。

一、为什么"换个便宜模型"省不下多少钱

第一版我处理每个请求的方式,是这样的:不管用户问什么,我都把整个文档库的相关内容一股脑拼进提示词,再调那个能力最强、也最贵的模型。

# 反面教材:每个请求都把整个文档库塞进去,用最贵的模型

ALL_DOCS = load_all_documents()      # 公司文档库,几十万字

def answer_question_v1(question):
    # 把所有可能相关的文档段落,一股脑拼进提示词
    context = "\n\n".join(ALL_DOCS)
    prompt = f"""根据下面的文档回答问题。

文档:
{context}

问题:{question}
"""
    # 直接调用能力最强、也最贵的模型
    return call_model("most-capable-model", prompt)

# 我以为省钱 = 把 "most-capable-model" 换成便宜的小模型。
# 但没意识到:这个 prompt 里 context 可能有几万 token,
# 而 question 只有几十 token —— 换模型只改单价,
# 那几万 token 的输入用量,一个都没少。

到底换模型省多少、减 token 又省多少?别凭感觉,算一笔账就清楚了。

# 算一笔账:同一个请求,在"换模型"和"减 token"上分别能省多少

# 假设一次请求:输入 20000 token,输出 500 token
def cost(input_tok, output_tok, in_price, out_price):
    # in_price / out_price:每千 token 的输入 / 输出单价
    return input_tok / 1000 * in_price + output_tok / 1000 * out_price

# 贵模型单价:输入 0.01,输出 0.03
# 便宜模型单价:输入 0.001,输出 0.003

a = cost(20000, 500, 0.01, 0.03)    # 原方案:贵模型 + 全量 context
b = cost(20000, 500, 0.001, 0.003)  # 只换便宜模型,token 用量不变
c = cost(2000, 500, 0.01, 0.03)     # 不换模型,把输入裁到 2000 token
d = cost(2000, 500, 0.001, 0.003)   # 两者叠加:裁 token + 换模型

print(f"原方案:       {a:.4f}")     # 0.2150
print(f"只换便宜模型:  {b:.4f}")     # 0.0215
print(f"只裁 token:    {c:.4f}")     # 0.0350
print(f"两者叠加:      {d:.4f}")     # 0.0035

# 换模型省得多,但便宜模型能力可能不够、被迫回退,省不到;
# 裁 token 不依赖换模型,任何模型上都成立,且能和换模型叠加。
# 第一版只盯着"换模型",等于放着 token 这条主轴不优化。

这笔账里最该看的,是"两者叠加"那一行:裁 token 和换模型不是二选一,它们是两个独立的维度,可以相乘。第一版的眼睛只盯着"换模型"这一个维度,等于主动放弃了一半的优化空间。

这一节要建立的认知是:LLM 的成本是"token 用量 × 单价"两个维度相乘的结果,而第一版的眼睛,只盯着"单价"这一个维度,对"token 用量"那一个维度完全没有概念。"换个便宜模型"为什么让人觉得是省钱的全部?因为它把成本想象成了一个一维的东西——一个叫"单价"的数字,调低它就行。但成本是二维的:你发进去多少 token、模型吐出多少 token,乘以各自的单价。换模型,只在"单价"这个维度上往下挪;而对绝大多数 RAG、文档问答类的功能来说,账单的大头压在"输入 token 用量"上——你每次都把一大段上下文原封不动地塞进去,这个用量,换什么模型都不会变。更关键的是,"token 用量"这个维度的优化,是和"换模型"正交的、可以叠加的:你既可以裁 token、又可以换模型,两者的省钱效果相乘。所以真正的成本优化,第一步不是去比价选模型,而是先把眼睛从"单价"挪到"token 用量"上——看清楚自己每一次调用,到底花掉了多少 token、花在了哪里。这正是下一节要做的事。

二、看懂账单:token 计费与成本的拆解

要优化 token 用量,前提是先能"看见"它。第一版那个"账单某天翻三倍却查不出原因"的困境,根子在于:我只有一个来自 API 服务商的、笼统的总金额,我没有在自己的系统里,按请求记录下每一次调用花了多少 token。

要把成本变得可见,你得在每次调用大模型时,把这次调用的 token 用量记下来。

# 每次调用都记录 token 用量,把笼统的总账单拆成可分析的明细

import time

def call_with_tracking(model, prompt, request_type):
    resp = call_model_raw(model, prompt)
    usage = resp["usage"]              # API 返回里带着本次的 token 用量

    # 把每一次调用的成本明细,写进自己的日志或数据库
    log_cost({
        "ts": time.time(),
        "model": model,
        "request_type": request_type,   # 标好这是哪一类请求
        "input_tokens": usage["input_tokens"],
        "output_tokens": usage["output_tokens"],
        "cost": estimate_cost(model, usage),
    })
    return resp["text"]

# 有了这份明细,"账单为什么翻三倍"就能回答了:
#   按 request_type 一聚合,立刻看出是哪类请求在烧钱;
#   按 input / output 一拆,立刻看出钱花在了输入还是输出。

记下这份明细,你会发现一个对优化方向至关重要的事实:对文档问答这类功能,输入 token 往往是输出 token 的几十倍——你为"塞进去的上下文"付的钱,远远多于"模型生成的回答"。第一版盯着输出做优化,从一开始就找错了地方。

这一节的认知是:成本优化的第一步,永远不是"动手去省",而是"先让成本变得可见、可拆解"——你无法优化一个你测量不到的东西。第一版面对"账单翻三倍"时的无力感,不是因为没有省钱的手段,而是因为它两眼一抹黑:它不知道钱花在了哪类请求上,不知道是输入贵还是输出贵,不知道是单次变贵了还是调用变多了。在这种"看不见"的状态下,你做的任何优化都是盲目的——你可能花大力气去压输出,而大头其实在输入。把每次调用的 token 用量和成本记录下来、打上分类标签,你才第一次拥有了一张"成本地图":哪类请求最烧钱、钱烧在输入还是输出、哪个环节的 token 最冗余,一目了然。有了这张地图,后面三节的优化手段——缓存、压缩上下文、模型分级——你才知道该优先用哪一个、该用在哪里。先测量,再优化,这个顺序反过来,优化就成了瞎猜。

三、用缓存掐掉重复调用

有了成本地图,通常第一个跳出来的浪费,就是第一版的第三种问题:大量用户在重复问几乎一样的问题,而系统每次都重新花全价。这类浪费,用缓存来掐——一个问题问过、答过,就把答案存下来,下次同样的问题直接返回,根本不调模型。

最简单的缓存,是精确匹配:把问题原文当 key。

# 精确匹配缓存:问题原文一字不差,直接返回上次的答案

cache = {}

def answer_with_exact_cache(question):
    key = question.strip()
    if key in cache:
        return cache[key]            # 命中,零成本返回

    answer = answer_question(question)   # 未命中,才真正调模型
    cache[key] = answer
    return answer

# 精确缓存的问题:用户问的是同一件事,但措辞稍有不同 ——
#   "怎么退货" 和 "如何退货" 会被当成两个不同的 key,
#   缓存命中率因此很低。

精确缓存命中率低,因为它认的是"字面",不是"意思"。更有效的是语义缓存:把问题转成向量,用向量相似度去匹配"意思相同"的老问题。

# 语义缓存:用向量相似度匹配"意思相同"的问题

def answer_with_semantic_cache(question, threshold=0.95):
    q_vec = embed(question)              # 把问题转成向量

    # 在缓存里找一个"意思最接近"的老问题
    hit = search_most_similar(q_vec, cache_vectors)
    if hit and hit["score"] >= threshold:
        return hit["answer"]             # 足够像,直接复用老答案

    answer = answer_question(question)
    # 把新问题的向量和答案存进缓存
    cache_vectors.append({
        "vec": q_vec, "answer": answer, "question": question,
    })
    return answer

# "怎么退货" 和 "如何退货" 的向量高度相似 ——
# 语义缓存能把它们认成一回事,命中率远高于精确匹配。
# 注意:threshold 要调稳,太低会把"不同的问题"误判成同一个。

这一节的认知是:缓存省钱,省的不是"单次调用的钱",而是"那次调用本身"——它是优化里唯一能把成本直接降到零的手段,因为命中缓存的请求,根本没有发生调用。前面算的那些账,裁 token、换模型,都是在让"一次调用"变便宜——从两毛降到三分,但它终究还是一次要花钱的调用。缓存不一样:命中的那一刻,这次调用压根不存在,成本是实打实的零。所以在所有省钱手段里,缓存的优先级应该最高——你要先问"这次调用能不能干脆不发生",问完这个,再去问"这次非发生不可的调用能不能便宜点"。当然,缓存也有它的讲究:语义缓存的相似度阈值要调得稳,太松会把不同的问题混为一谈、返回错答案;缓存的内容会过期,文档更新了,旧答案要失效。但这些都是"怎么把缓存用好"的细节,不动摇那个大方向——能用缓存掐掉的调用,一分钱都不该花。

四、压缩上下文:别把整个文档都塞进去

缓存掐掉了重复的调用。但那些"非发生不可"的调用——真有新问题进来——还是得想办法让它便宜。这就回到第一版最大的浪费:每次都把整个文档库塞进提示词。

这件事的荒谬在于:用户问的可能是一个很具体的小问题,答案只藏在某一两个文档段落里,而我把几十万字的文档全发了过去。模型为了回答,要先在这一大堆里自己找,而你为这一大堆里 99% 跟问题无关的内容,付了全价的输入 token 费。正确的做法,是先做检索:把文档库切成小段,用向量检索找出和问题最相关的少数几段,只把这几段塞进提示词。

# 只把"和问题最相关"的少数文档段落塞进提示词

def build_context(question, doc_chunks, top_k=3):
    q_vec = embed(question)

    # 给每个文档段落算一个和问题的相关度
    scored = []
    for chunk in doc_chunks:
        score = similarity(q_vec, chunk["vec"])
        scored.append((score, chunk))

    # 只取最相关的 top_k 段 —— 而不是全部
    scored.sort(reverse=True, key=lambda x: x[0])
    top_chunks = [c["text"] for _, c in scored[:top_k]]
    return "\n\n".join(top_chunks)

def answer_question_v2(question, doc_chunks):
    context = build_context(question, doc_chunks, top_k=3)
    prompt = f"根据文档回答问题。\n\n文档:\n{context}\n\n问题:{question}"
    return call_model("most-capable-model", prompt)

# 从"塞几十万字"变成"塞最相关的 3 段",
# 输入 token 可能直接从几万降到一两千 —— 这是账单大头的优化。

输入侧压下来了,输出侧也别浪费。给输出设一个长度上限,再把每次都一样的固定前缀提取出来,这两件小事也能省下可观的 token。

# 输出侧也别浪费:限定长度,并把固定前缀提取出来

SYSTEM_PREFIX = "你是文档问答助手,只依据给定文档回答,不杜撰。"

def answer_question_v3(question, context):
    prompt = f"文档:\n{context}\n\n问题:{question}"
    return call_model_raw(
        model="most-capable-model",
        system=SYSTEM_PREFIX,        # 固定不变的前缀,适合走 prompt 缓存
        prompt=prompt,
        max_tokens=300,              # 限定输出上限,杜绝模型啰嗦一大篇
        instruction="回答控制在 3 句话以内",
    )

# 两个点:
#   1. max_tokens 给输出设硬上限 —— 输出 token 比输入贵,别让它失控
#   2. 系统提示这种"每次都一样"的前缀,很多服务商支持 prompt 缓存,
#      重复前缀只在首次计费,后续命中能省下这部分输入 token

这一节的认知是:"把上下文给足"和"把上下文给对"是两件完全不同的事——第一版以为前者是后者的保险做法,其实它既烧钱、又未必让效果更好。第一版塞进整个文档库,背后的想法是"我也不知道答案在哪,全给它,总不会漏"。这个想法有两个问题。一个是钱:你为大量无关内容付了全价输入费。另一个常被忽略——效果:把几十万字一股脑丢给模型,模型要在海量噪声里捞出那一两段有用的,反而容易被无关内容干扰、答得更差。而先做检索、只塞最相关的几段,一举两得:输入 token 砍掉一个数量级,模型拿到的又是高度聚焦的材料,答得还更准。这就推翻了一个隐含的假设——"上下文越多越安全"。真相是上下文要的是精准,不是多。把"我该给模型什么上下文"当成一个需要认真设计的检索问题,而不是"全塞进去图省心",你才能同时拿下省钱和效果。

把一个请求进来后,该怎么处理才最省钱的决策流程画出来,就是下面这张图:

[mermaid]
flowchart TD
A[收到一个请求] --> B{语义缓存命中吗}
B -->|命中| C[直接返回缓存答案 成本为零]
B -->|未命中| D{任务是简单还是复杂}
D -->|简单| E[走便宜的小模型]
D -->|复杂| F[检索裁剪上下文 再走大模型]
E --> G[记录token用量 写入缓存]
F --> G

五、模型分级:简单任务用小模型,难任务才上大模型

缓存掐掉了重复,检索压缩了上下文。还剩最后一个维度:不是所有请求都值得用那个最贵、最强的模型。第一版不管来的是什么问题,一律用能力最强的模型——可很多请求其实是简单的(比如"你们几点上班""怎么退货"这种),一个便宜的小模型完全答得了,用大模型纯属杀鸡用牛刀。

这就是模型分级(也叫模型路由):先判断一个请求的难度,简单的交给小模型,难的才交给大模型。

# 模型分级路由:先判难度,简单任务走小模型,难任务才上大模型

# 简单问题的特征:短、命中常见问答、不需要多步推理
def is_simple(question, retrieved_chunks):
    if len(question) < 20 and len(retrieved_chunks) <= 1:
        return True
    # 命中了 FAQ 库里的高频问题
    if match_faq(question):
        return True
    return False

def answer_with_routing(question, doc_chunks):
    context_chunks = retrieve(question, doc_chunks, top_k=3)

    if is_simple(question, context_chunks):
        # 简单问题:便宜的小模型足矣
        return call_model("cheap-small-model",
                          build_prompt(question, context_chunks))
    else:
        # 复杂问题:才动用最贵最强的模型
        return call_model("most-capable-model",
                          build_prompt(question, context_chunks))

# 如果七成请求是简单问题,这七成就从"大模型价"
# 降到了"小模型价" —— 总账单结构性地下降。

这一节的认知是:成本优化里有一个反直觉的原则——你不该追求"用一个模型把所有事做好",而该追求"让每个请求都落到刚好够用的那个模型上"。第一版"一律用最强模型"的想法,是把"质量"理解成了一条单调的线:模型越强,质量越高,所以永远用最强的最保险。但加上成本这个维度后,问题就不是"哪个模型最强",而是"这个具体的请求,配得上多强的模型"。一个"你们几点上班"的问题,小模型和大模型给的答案毫无差别,这时候用大模型,你多付的钱买不到任何质量——纯浪费。模型分级,就是把"模型选择"从一个全局的、一刀切的决定,变成一个针对每个请求的、按需的决定。它的前提,是你愿意承认"请求是分难度的",并且去为判断难度做一点工程(关键词、长度、是否命中 FAQ、甚至用一个极便宜的小模型先做难度分类)。这点判断难度的成本,比起把大量简单请求从大模型价降到小模型价省下的钱,微不足道。让每个请求各得其所,而不是让所有请求都享受最高规格,这才是成本和质量的最优解。

六、把 LLM 成本治理做扎实,要避开的工程坑

前面五节讲清了 LLM 成本优化的几条主轴:看懂账单、缓存、压缩上下文、模型分级。但要在生产里真正把成本治住,还有几个工程坑得专门讲。第一个,也是最容易被忽略的:省钱不能以牺牲效果为代价,每一个优化动作,都必须配一个效果的"守门员"。

# 坑一:每个省钱动作,都要在评估集上验证效果没有掉

def verify_optimization(eval_set, old_fn, new_fn):
    # old_fn:优化前的方案;new_fn:优化后(更省钱)的方案
    old_pass = sum(1 for c in eval_set if check(old_fn(c["q"]), c))
    new_pass = sum(1 for c in eval_set if check(new_fn(c["q"]), c))

    rate_old = old_pass / len(eval_set)
    rate_new = new_pass / len(eval_set)
    print(f"效果通过率:{rate_old:.0%} -> {rate_new:.0%}")

    # 省钱了,但效果通过率明显下滑 —— 这个优化不能上
    if rate_new < rate_old - 0.03:
        print("效果回退超过阈值,该优化需要回滚或调整")
        return False
    return True

# 上下文裁太狠、小模型用在了它扛不住的请求上 ——
# 这些都会让账单好看、却让用户体验变差。省钱不能瞎省。

第二个坑,是只盯着"日常成本",却没给"异常成本"设护栏。AI 成本最可怕的不是慢慢变贵,而是一夜爆表。

# 坑二:给用量设护栏,挡住异常流量和死循环烧钱

def call_with_budget_guard(user_id, model, prompt):
    # 单个用户每天的 token 预算
    used = get_today_token_usage(user_id)
    if used > DAILY_TOKEN_BUDGET:
        raise BudgetExceeded(f"用户 {user_id} 今日用量超限")

    # 单次调用的输入也设个上限,挡住异常的超长 prompt
    if estimate_tokens(prompt) > MAX_PROMPT_TOKENS:
        prompt = truncate_prompt(prompt, MAX_PROMPT_TOKENS)

    return call_model(model, prompt)

# AI 成本最可怕的不是"慢慢变贵",是"一夜爆表":
# 一个调用死循环、一次被恶意刷接口,能烧掉平时几个月的钱。
# 护栏不是为了省那一点,是为了挡住那种灾难性的账单。

还有几个坑值得点一下。其一,流式输出(SSE 那种逐字往外吐的)只是改善了体验,它并不省钱——计费照样按总 token 算,别误以为"看起来快"就等于"花得少"。其二,不同服务商的计价方式、token 切分规则都不一样,要把"成本估算"抽象成一个独立模块,别让单价散落在代码各处,换供应商时才不至于到处改。其三,成本监控不能只是"记录",还要有"告警"——设一个日成本阈值,一旦某天异常飙升,系统要主动通知你,而不是等月底账单出来才发现。下面把这几条主轴集中对照一下:

LLM 成本优化的几条主轴对照

  手段           省的是什么             适用场景
  --------------------------------------------------------------
  语义缓存       整次调用 直接降到零    重复 高频的问题
  检索裁上下文   输入 token 用量        上下文很长的 RAG 类功能
  模型分级路由   把单价降到够用即可     请求难度差异大
  限定输出长度   输出 token 用量        模型容易啰嗦的场景
  prompt 缓存    重复前缀的输入用量     系统提示等固定前缀很长

  原则:先用缓存让调用别发生,再让非发生不可的调用变便宜;
        每一步省钱,都要用评估集守住效果不下滑。

这一节这几个坑,串起来是同一个意思:成本优化不是一个孤立的、越省越好的目标,它是一个必须时刻和"效果"与"安全"挂在一起权衡的工程。省钱很容易上头——上下文裁得越狠越省、模型换得越小越省,账单数字往下掉的时候很有成就感。但成本从来不是一个可以单独优化的指标:你裁上下文裁掉了关键信息、你把难问题塞给了小模型,账单是好看了,用户得到的是错的、烂的回答——这种"省"是在亏。所以每个省钱动作都得配一个效果守门员,在评估集上验证它没把质量带下去。而护栏要解决的是另一个方向的风险:AI 成本真正的杀伤,往往不是"日常贵一点",而是某次失控——一个死循环、一次恶意刷量,一夜之间烧掉天文数字。把效果回归和用量护栏这两件事,和省钱手段绑在一起做,你的成本优化才是"稳的省",而不是"危险的省"。

关键概念速查

概念 说明
token 大模型处理文本的最小计费单位,成本按 token 数量计算
输入/输出 token 发给模型的与模型生成的,分开计价,输出通常更贵
成本公式 成本等于 token 用量乘以单价,用量和单价是两个独立维度
成本监控 按请求记录 token 用量与分类,把笼统账单拆成可分析明细
精确缓存 问题原文一字不差才命中,命中率低
语义缓存 用向量相似度匹配意思相同的问题,命中即零成本
检索裁剪 只把与问题最相关的少数文档段落塞进上下文
prompt 缓存 固定不变的前缀复用,重复前缀只在首次计费
模型分级路由 按请求难度选模型,简单走小模型难任务才上大模型
用量护栏 给用户与单次调用设 token 上限,挡住一夜爆表的灾难账单

避坑清单

  1. 不要以为省钱只能换便宜模型:换模型只动单价,token 用量才是大头。
  2. 不要不记录 token 用量:看不见成本就无法定位、无法优化。
  3. 不要只优化输出:RAG 类功能账单大头在输入 token,不在输出。
  4. 不要让重复问题花全价:用语义缓存把重复调用直接掐到零。
  5. 不要把整个文档库塞进提示词:先检索,只塞最相关的几段。
  6. 不要不限定输出长度:用 max_tokens 给输出设硬上限。
  7. 不要一律用最强模型:按难度分级,简单请求落到小模型上。
  8. 不要光省钱不看效果:每个优化都要在评估集上验证质量没掉。
  9. 不要不设用量护栏:死循环或被刷量能让账单一夜爆表。
  10. 不要以为流式输出省钱:它只改善体验,计费照样按总 token 算。

总结

回头看第一版那个"上下文给足、用最强模型"的 AI 问答功能,它的成本失控很典型。它不在某一行代码,而在一个对 LLM 计费的根本误解:以为成本就是"次数 × 单价",省钱无非少调几次或换个便宜模型。真相是,LLM 按 token 计费,成本是"token 用量 × 单价"两个维度相乘,而对大多数功能,输入 token 用量才是账单的大头——这恰恰是第一版完全没去碰的那个维度。

而把 LLM 成本治理做对,工程量并不小。它不是"挑个便宜模型"那么简单,而是要先把成本变得可见、可拆解,要用语义缓存把重复调用直接掐到零,要用检索把动辄几万 token 的上下文压到一两千,要按请求难度做模型分级、让每个请求落到够用的模型上,还要给每个省钱动作配效果回归、给用量配护栏。一套真正可持续的 LLM 成本方案,是这些环节一个不少地拼起来的。

这件事其实很像一个总靠叫车出行的人,想削减自己的交通开支。打车的费用,是"里程 × 每公里单价"——这和 LLM 的"token 用量 × 单价"是一回事。这个人一开始只盯着单价,到处比哪个打车平台更便宜——这就是"换个便宜模型"。可他没注意到,自己每次出门,都习惯性地绕一大圈路:本来三公里能到的地方,他绕了二十公里(这就是每次都把整个文档库塞进去)。真正该做的,第一是看看有些行程压根不必发生——今天要去的地方,昨天刚去过、东西就在原地,那干脆别叫车了(这是缓存);第二是把绕的远路改直,二十公里压回三公里(这是压缩上下文);第三是分清场合,去楼下买瓶水,走过去就行,犯不上叫车,只有去机场那种远门才值得叫车(这是模型分级)。比来比去平台单价,省下的是小钱;把不必要的行程砍掉、把绕的远路改直、让每一段路都用对的方式走,省下的才是大头。

这类问题还有一个共同的麻烦:它在开发和测试时几乎暴露不出来。你自己测,就那么点请求量,每次调用花一两毛钱,你完全感觉不到肉疼,会觉得"成本"是个上线以后才需要操心的、很遥远的事。真正会把成本问题撑爆的,是上线后的真实规模:成千上万的用户每天反复地问、重复地问,你那个"每次都塞整个文档库"的方案被乘上巨大的调用量,一个绕远路的设计就被放大成一张吓人的账单;更别说某个深夜的调用死循环、某次接口被刷,能让账单一夜爆表。所以如果你正在做一个基于大模型的功能,别等财务拿着账单来找你,才回头怀疑你的调用方式。在写下第一次模型调用的时候就想清楚:我这次调用花了多少 token、这些 token 花得值不值、这次调用是不是根本可以不发生、这个请求配不配得上这么贵的模型——把"把 AI 功能做出来"和"让这个 AI 功能花得起"当成两件必须分别去做的事,这是这篇文章最想留给你的一句话。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

数据库死锁完全指南:从一次"两条没问题的 SQL 放一起却死锁"看懂为什么加锁顺序才是根因

2026-5-22 21:35:50

技术教程

WebSocket 长连接完全指南:从一次"连接建好了消息却悄悄丢"看懂为什么心跳和重连才是根本

2026-5-22 21:54:16

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索