cd ../返回博客
$Guide//2026年6月4日//7 min read

给 AI API 开销封顶——按请求与按用户的成本控制

用 max_tokens 给每次调用封顶,从 usage 对象按用户累计开销并设预算切断,以及为什么诚实计费让这套算术值得信赖。

一个不设开销上限的 AI 功能,就是一个能向你收取无上限金额的功能。一段失控的 智能体循环、一个把整本小说粘进对话框的用户、一句让模型滔滔不绝写出 4,000 个 token 的提示 —— 这其中任何一个,都会悄无声息地把你的账单翻倍。解法是两个 既便宜又朴素的控制:用 max_tokens 给每一次调用封顶,再从 usage 对象 按用户累计开销,这样你就能在某个用户花掉的钱超过其价值之前 把他切断。

这些都不难。难的地方在于:你拿来封顶的那些数字,到底值不值得信。一份预算的 可靠程度,取决于喂给它的 token 计数 —— 如果你的网关给 usage 掺水,或者为失败的调用向你收费,那么你精心算出的「每用户每天 $1」上限,实际上 是 $1.40,而你永远不会察觉。所以这篇文章做两件事:把机制讲给你看,再说明为什么 正是诚实计费让这套机制根本得以成立。

第一步 —— 用 max_tokens 给每一次调用封顶

输出是多数账单里更贵的那一半。在 Claude Sonnet 4.6 上,输出价为 每 100 万 token $12.75 —— 是输入费率 $2.55 的五倍。一个你 以为只有三句话、结果却返回了十二段的回答,不是质量问题,而是成本问题。max_tokens 就是那个让最坏情况能被提前知晓的硬上限。

cap_per_request.py
# 用 max_tokens 给每一次调用的成本封顶。
# 它把*输出*这一块 —— 多数账单里更贵的那一半 —— 限制在一个
# 硬上限内。模型会在到达上限时停下,而不是一路写下去。
from openai import OpenAI

client = OpenAI(
    api_key="sk-brievio-...",
    base_url="https://api.brievio.com/v1",
)

# Claude Sonnet 4.6:每 100 万 token 输入 $2.55 / 输出 $12.75。
# 不设上限时,一个话痨式的回答可能跑出 4,000+ 个输出 token。
# 设上 max_tokens=500,你就把这次调用的输出成本封在:
#   500 × $12.75 / 1M = $0.0064 —— 不管模型“想”说多少都一样。
resp = client.chat.completions.create(
    model="claude-sonnet-4-6",
    max_tokens=500,                 # 原生生效 —— 一个真正的硬性停止点
    messages=[
        {"role": "system", "content": "Answer in at most 3 sentences."},
        {"role": "user", "content": user_question},
    ],
)

# 永远要看清它为什么停下。"length" 表示你撞到了上限,回答
# 可能被截断 —— 这是该为*这一类*任务调高上限的信号,而不是
# 全局调高。
if resp.choices[0].finish_reason == "length":
    print("Truncated at the cap — consider a higher max_tokens here.")

print(resp.choices[0].message.content)
print(resp.usage)  # 诚实的输入/输出计数 —— 见下一节

Brievio 原生地对真正的模型执行 max_tokens —— 它是一个真正的 停止点,而不是一个建议。请按任务类别来设,而不是全局设一次:一个分类器也许 只要 5 个 token,一条聊天回复要 500,一份长摘要要 2,000。要做的功课,是为 每一个调用点挑一个数字,并盯住 finish_reason。当它返回 "length" 时,说明你截掉了一个真实的回答,应该为 那一条路径调高上限 —— 而不是到处一刀切地全调高,把保护给丢了。

第二步 —— 自己给 usage 对象计价

每一个 Brievio 响应都带有一个 usage 对象,里面是这次调用真实的 输入和输出 token 数,并且每一个请求都连同其真实 token 与成本一起被记录下来。 这意味着你不必等到月底的账单才知道任何东西花了多少 —— 你可以在调用返回的 那一刻就给它计价:

  • usage.prompt_tokens —— 实际发出的输入 token。
  • usage.completion_tokens —— 实际生成的输出 token(这正是 max_tokens 所约束的对象)。
  • usage.total_tokens —— 两者之和,方便快速核对。

把每个计数乘以已公布的各模型费率,你就在自己的代码里、实时地拿到了这次调用 的精确成本。各模型费率都在 定价页上对照官方参考列出,所以你代码里的那些 常量是可被审计的 —— 而不是一个你只能凭信任接受的数字。

第三步 —— 按用户累计开销并在预算处切断

按请求封顶能保护你不受单次糟糕调用的伤害。按用户设预算则能保护你不受单个 糟糕用户的伤害 —— 那个每天跑成千上万次调用的账号,无论是出于热情、 自动化,还是滥用。这个模式就是每个用户对应一个持久化的数字,你在调用前检查它、 在调用后递增它:

cap_per_user.py
# 从 usage 对象按用户累计开销,再在预算处切断。
# usage 返回这次调用真实的输入/输出 token 数。你在本地按已公布
# 的各模型费率给它们计价,再累加到一个以用户为键的累计值上。
from decimal import Decimal

# Brievio 公布的费率,单位为每 100 万 token 美元(≈ 比官方报价低 15%)。
RATES = {
    "claude-sonnet-4-6": {"in": Decimal("2.55"), "out": Decimal("12.75")},
    "claude-haiku-4-5":  {"in": Decimal("0.85"), "out": Decimal("4.25")},
}

def cost_usd(model: str, usage) -> Decimal:
    r = RATES[model]
    million = Decimal("1000000")
    return (usage.prompt_tokens     * r["in"]  / million +
            usage.completion_tokens * r["out"] / million)

# 你随意挑选的存储 —— Redis 计数器、一个 SQL 字段,什么都行。关键
# 是每个用户对应一个持久化的数字。失败的 4xx/5xx 调用在 Brievio 上免费,
# 所以你只会为真正返回了结果的调用累加成本。
DAILY_BUDGET = Decimal("1.00")  # 每用户每天 $1

def chat_for_user(user_id: str, question: str, model="claude-sonnet-4-6"):
    spent = get_spent_today(user_id)            # Decimal,默认为 0
    if spent >= DAILY_BUDGET:
        raise BudgetExceeded(f"{user_id} hit ${DAILY_BUDGET}/day")

    resp = client.chat.completions.create(
        model=model,
        max_tokens=500,
        messages=[{"role": "user", "content": question}],
    )

    add_spent_today(user_id, cost_usd(model, resp.usage))  # 累加真实成本
    return resp.choices[0].message.content

从你的单位经济模型里挑这个阈值:如果一个付费席位是每月 $20,而你希望 AI 控制 在比如说收入的 20% 以内,那就是大约每用户每天 $0.13 的预算。在 80% 时软提醒 (一条横幅、一封邮件),在 100% 时硬切断(上面那条 BudgetExceeded 路径)。对免费层用户,把上限设低,并把模型降一档 —— 把非关键流量从 Sonnet 转到 每 100 万 token 输入 $0.85 / 输出 $4.25 的 Haiku 4.5, 能把每 token 成本砍掉约三分之二,同时保持同样诚实的计量。

为什么诚实计费让这套算术值得信赖

这里是多数成本控制指南会略过的部分。上面那两个控制 —— 按调用的上限和按用户的 预算 —— 都是对 token 计数所做的算术。如果那些计数被灌了水,下游的每一个数字都 会按同一个倍数出错,而且悄无声息。有两种计费行为,决定了你的封顶是否名副其实:

  • 失败的调用免费。在 Brievio 上,一个 4xx 或 5xx 响应分文 不收 —— 你为结果付费,而不是为尝试付费。这对预算尤其重要:一场针对不稳定 下游的重试风暴,不该耗光某个用户当天的额度。正因为你只在返回了结果的调用上 累计成本,你的开销曲线追踪的是交付的价值,而不是被你吸收的错误。
  • 计数不掺水。usage 对象反映的是真正的模型实际处理的 token —— 没有被注水的提示,没有被加进 计费表的幽灵输出。诚实的做法是别只凭信任接受这一点:发出一段已知的固定提示, 读回 prompt_tokens,确认它与分词器给出的应有数值相符。我们写了 一段 20 行的 token 灌水自测 做的正是这件事 —— 在你信任任何网关的数字之前,对它(包括我们)跑一遍。

这层关联是直接的:一份从注水计数算出的预算,会把用户过早切断,并高估你的 成本;而一份把失败调用算进去的预算,则会因为你基础设施的糟糕日子而惩罚用户。 正是诚实的计量,让一个每天 $1 的上限真正意味着一美元。Brievio 给聊天定价大约 比官方报价低 15%,用多少付多少,余额永不过期 —— 所以你设的 上限就是你拿到的上限,没花完的预算还是你的。

把它们拼到一起

一套可用于生产的开销策略,由四个小习惯组成,每一个花的时间都不超过一个下午:

  • 每个调用点都设 max_tokens,并按其任务大小 来定。任何地方都不留无上限的输出。
  • 每个响应都从 usage 计价,按已公布的费率, 再加到一个按用户的累计值上。
  • 每个用户都有一份预算,带一个软提醒和一个硬切断,都取自 你的单位经济模型。
  • 更便宜的流量降一档,转到一个更小的模型(Haiku、mini 级), 而不是为那些用不着的活儿付旗舰级的费率。

做到这四点,你最坏情况下的账单就是一个你选定的数字,而不是一个你事后才发现的 数字。至于更深一层的手段 —— 提示缓存、批处理、模型选择、提示精简 —— 请看 成本优化手册,以及 文档里完整的 usage 模式和 Brievio 支持的 OpenAI 兼容参数。成本控制不是等账单吓到你之后才补上的功能 —— 它就是两行代码加一个计数器,而它在一块你信得过的计费表上工作得最好。