返回博客

成本优化指南

用 Python 按项目跟踪 API 支出并获取预算警报

一个简短的 Python 脚本将你的用量日志变成按项目统计的成本报告,并在月度预算的 50%、75%、95% 和 100% 时发出警告。

作者:CheapTokens Team发布于 4 分钟阅读

带有按项目支出报告和预算警告的终端
本页内容
  1. Anthropic 发布了什么,你可以复制什么
  2. 日志:每个请求一行
  3. 脚本:按项目统计成本和阈值警报
  4. 运行它并读取结果
  5. 在便宜模型上,一个月是什么样子
  6. 这里容易出错的地方

每次 API 调用后,向文件追加一行 JSON,给它打上项目名称标签,再用一个 30 行的 Python 脚本把该文件变成按项目统计的成本报告,并在月度预算达到 50%、75%、95% 和 100% 时发出警告。这正是 Anthropic 刚为 Claude Enterprise 团队提供的视图,只不过是用你自己的日志构建的。

假设一个余额同时供一个 agent、一个博客草稿脚本和一个小型支持机器人使用。月底到了,余额比你期望的低,却没有任何信息告诉你三者中是谁造成的。这就是本文要填补的空白。

Anthropic 发布了什么,你可以复制什么#

Anthropic 的公告(2026 年 10 月 28 日)涵盖智能报告、应用内调查和共享组预算,仅在 Claude Enterprise 套餐中普遍可用(来源)。这里有两部分值得关注。报告会把团队的工作归入工作流,并显示每个工作流的支出。共享预算是一个共享的月度数字,账单管理员会在达到 50%、75%、95% 和 100% 时收到邮件。Anthropic 补充说,这些报告用于规划投资,而不是评判个人。

你不需要企业套餐也能实现同样的形态。工作流只是你选择的一个标签,四个阈值只是代码中的一个列表。按照 Anthropic 给出的理由,按项目分组,而不是按人分组。

快速检查

你的预付费余额在归零时已经会停止请求。为什么还要在 50% 和 75% 时添加警告?

显示答案

答案: 因为归零太晚了:你想在还有钱可查的时候发现失控循环

余额是硬性停止,不是警报。提前的阈值能把 100% 时的意外变成第九天的一个问题。

日志:每个请求一行#

每个聊天补全响应都带有一个 usage 对象,其中包含 prompt_tokens 和 completion_tokens。这就是报告所需的全部内容,再加上你自己添加的项目标签。这个包装器发送请求,并向月度文件追加一行(文件名每月变化,因此新月份从零开始):

python
import json, os, time
from openai import OpenAI

client = OpenAI(base_url="https://api.cheaptokens.dev/v1",
                api_key=os.environ["CHEAPTOKENS_API_KEY"])

def ask(project, model, messages):
    r = client.chat.completions.create(model=model, messages=messages)
    with open(time.strftime("usage-%Y-%m.jsonl"), "a") as f:
        f.write(json.dumps({"project": project, "model": model,
                            "prompt_tokens": r.usage.prompt_tokens,
                            "completion_tokens": r.usage.completion_tokens}) + "\n")
    return r

ask("support-bot", "zai-org/GLM-5.3-Flash",
    [{"role": "user", "content": "Where is my order?"}])

使用 CheapTokens 文档 中的完整模型 ID(deepseek-ai/DeepSeek-V4-Flash-0731、zai-org/GLM-5.3-Flash);价格表中的短名称不被 API 接受。密钥来自环境变量,绝不来自文件。

从每个请求到用量日志、项目报告和警报的流程
一个请求如何变成日志中的一行,然后变成报告和警报

脚本:按项目统计成本和阈值警报#

将此保存为 report.py。价格是占位符:从价格表复制当前价格(这里两个模型共用一个示例价格,只是为了保持算术可读)。

python
import json, os, sys, time
from collections import defaultdict

LOG = time.strftime("usage-%Y-%m.jsonl")
BUDGET = float(os.environ.get("BUDGET_USD", "10"))  # example, substitute your own
STATE = LOG + ".alert"
LEVELS = [50, 75, 95, 100]
PRICES = {  # USD per 1M tokens (input, output): example, substitute current prices
    "deepseek-ai/DeepSeek-V4-Flash-0731": (1.0, 2.0),
    "zai-org/GLM-5.3-Flash": (1.0, 2.0),
}

spend = defaultdict(float)
with open(LOG) as f:
    for line in f:
        r = json.loads(line)
        p_in, p_out = PRICES[r["model"]]
        spend[r["project"]] += (r["prompt_tokens"] * p_in
                                + r["completion_tokens"] * p_out) / 1_000_000

total = sum(spend.values())
if total == 0:
    sys.exit("log is empty")

for name, usd in sorted(spend.items(), key=lambda kv: -kv[1]):
    print(f"{name:<8} ${usd:6.2f} {usd / total:5.0%}")
print(f"{'total':<8} ${total:6.2f} of ${BUDGET:.2f} ({total / BUDGET:.0%})")

last = int(open(STATE).read()) if os.path.exists(STATE) else 0
crossed = [l for l in LEVELS if total >= BUDGET * l / 100 and l > last]
if crossed:
    print(f"WARNING: budget passed {crossed[-1]}%")
    open(STATE, "w").write(str(crossed[-1]))

最后四行是人们容易忘记的部分。如果没有这个小状态文件,脚本会在本月剩余时间的每次运行中都喊“已超过 50%”。有了它,每个级别只触发一次。

运行它并读取结果#

取一个四行的玩具日志(真实日志每个请求一行,所以有数千行):

text
{"project": "agent", "model": "deepseek-ai/DeepSeek-V4-Flash-0731", "prompt_tokens": 2400000, "completion_tokens": 300000}
{"project": "agent", "model": "deepseek-ai/DeepSeek-V4-Flash-0731", "prompt_tokens": 1000000, "completion_tokens": 100000}
{"project": "blog", "model": "zai-org/GLM-5.3-Flash", "prompt_tokens": 800000, "completion_tokens": 400000}
{"project": "bot", "model": "zai-org/GLM-5.3-Flash", "prompt_tokens": 500000, "completion_tokens": 100000}

使用示例价格和预算 10,python report.py 会打印:

text
agent    $  4.20   65%
blog     $  1.60   25%
bot      $  0.70   11%
total    $  6.50 of $10.00 (65%)
WARNING: budget passed 50%

agent 占了账单的近三分之二,而警告在 50% 时就出现了,而不是在最后。再次运行它,警告会保持安静,直到总额达到 75%。

显示按项目报告和 50% 警告的终端
玩具日志上的报告输出:各项目份额和第一个警告

要让这每月都生效,你需要三个步骤:

分步操作

分三步接入

记录每次调用

将所有调用都通过 ask(project, model, messages) 路由,并选择一个简短、稳定的项目标签。月中重命名标签会把一个项目拆成两行。

设置价格和预算

将价格表中的当前价格放入 PRICES,并导出你的限额:export BUDGET_USD=25。

按计划运行

每小时一行 cron 就足够了:0 * * * * cd ~/ai && python3 report.py。如果你想把警告推送给自己,将输出发送到邮件或聊天命令。

在便宜模型上,一个月是什么样子#

数字只有变成你自己的数字才有用。假设玩具日志中的 agent 平均每步 30,000 输入和 2,000 输出 token,每个任务 20 步,每天两个任务。在 DeepSeek-V4-Flash 上一步花费 $0.00065;在 GLM-5.3-Flash 上,$0.00068。一天是 40 步,一个月是 1,200 步。移动滑块以匹配你自己的 agent:

成本计算器

agent 的一天:每任务步数乘以每天任务数

每次请求的输入 token 数: 30,000 · 每次请求的输出 token 数: 2,000 · 每日请求数: 40

模型每天每月
GLM-5.3-Flash$0.0272$0.816
DeepSeek-V4-Flash$0.026$0.78
MiniMax-M2.7$0.072$2.16

以及 1,200 步的一个月,输入和输出一起计算:

模型 每次请求 1,200 次请求 OpenRouter,1,200 次请求
GLM-5.3-Flash $0.00068 $0.816 $1.41
DeepSeek-V4-Flash $0.00065 $0.78 $2.86
MiniMax-M2.7 $0.0018 $2.16 $25.92

每次请求 30,000 个输入 token 和 2,000 个输出 token。每 100 万 token 的价格为当前价格,会随我们的资费一同变动;OpenRouter 列显示相同工作负载的参考价格。

总的来说,输入和输出一起计算,两个模型都比该表中 OpenRouter 的参考价格便宜,任何时段一个价格,没有高峰附加费。DeepSeek-V4-Flash 还允许每个答案最多 32,768 个输出 token 和 380K 上下文;GLM-5.3-Flash 有 390K 上下文。一旦报告显示哪个项目是重头,只切换该项目的 model 字符串就是一行改动,而且一个余额覆盖所有模型。关于单个请求实际使用多少 token,请参阅一个 API 请求使用多少 token;关于中途停止一次 agent 运行,请参阅跟踪 AI agent token 成本。价格和模型在 CheapTokens 模型页面上。

这里容易出错的地方#

  • PRICES 中缺少某个模型。 脚本会停止,并给出一个指明该模型的 KeyError。添加它;不要默认为零,否则该项目看起来是免费的。
  • 流式调用。 只有你要求时,最后一个块才会携带用量(OpenAI SDK 中的 stream_options={"include_usage": True})。否则你的日志会有缺口。
  • 失败的请求。 只记录具有 usage 对象的响应;出错的请求没有可计数的内容。
  • 按人报告。 将标签保持为项目或 agent 名称。关于人的报告会招致错误的结论。