Назад в блог

Оптимизация расходовИнструкция

Расходы на API по проектам и алерты бюджета на Python

Короткий скрипт на Python превращает лог запросов в отчёт «сколько стоит каждый проект» и предупреждает на 50, 75, 95 и 100% месячного бюджета.

Автор: CheapTokens TeamОпубликовано 5 мин чтения

Терминал с отчётом по проектам и предупреждением о бюджете
Содержание
  1. Что выпустила Anthropic и что можно повторить
  2. Лог: одна строка на запрос
  3. Скрипт: стоимость по проектам и пороги
  4. Запускаем и читаем результат
  5. Как выглядит месяц на дешёвых моделях
  6. Где это ломается

После каждого вызова API дописывайте в файл одну JSON-строку с именем проекта — а скрипт на 30 строк Python превратит файл в отчёт «сколько стоит каждый проект» и предупредит на 50, 75, 95 и 100% месячного бюджета. Это тот же обзор, который Anthropic только что дала командам Claude Enterprise, только собранный из вашего собственного лога.

Допустим, с одного баланса питаются агент, скрипт для черновиков блога и небольшой бот поддержки. Конец месяца, баланс ниже, чем хотелось бы, и непонятно, кто из троих постарался. Вот эту дыру мы и закрываем.

Что выпустила Anthropic и что можно повторить#

В анонсе Anthropic (28 октября 2026) — умные отчёты, опросы внутри приложения и общие групповые бюджеты; доступны для планов Claude Enterprise, только для них (источник). Для нас важны две вещи. Отчёт группирует работу команды в «потоки» и показывает расходы каждого. Общий бюджет — одна месячная сумма на группу, а администраторы биллинга получают письмо на 50%, 75%, 95% и 100% от неё. Anthropic добавляет: отчёты нужны, чтобы планировать вложения, а не оценивать отдельных людей.

Энтерпрайз-план для такой же схемы не нужен. «Поток» — это метка, которую выбираете вы, а четыре порога — один список в коде. Группируйте по проектам, а не по людям — по той же причине, что называет Anthropic.

Быстрая проверка

Предоплаченный баланс и так остановит запросы на нуле. Зачем предупреждения на 50% и 75%?

Показать ответ

Ответ: Потому что ноль — слишком поздно: зациклившийся агент заметен, пока ещё есть деньги, чтобы разобраться

Баланс — жёсткий стоп, а не сигнализация. Ранние пороги превращают сюрприз на 100% в вопрос на девятый день месяца.

Лог: одна строка на запрос#

В каждом ответе chat completions есть объект 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?"}])

Полный id модели берите из документации CheapTokens (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]))

Последние четыре строки — то, о чём забывают. Без маленького файла состояния скрипт кричал бы «passed 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%

Агент — почти две трети счёта, и предупреждение пришло на 50%, а не в конце. Запустите снова — до 75% скрипт промолчит.

Терминал с отчётом по проектам и предупреждением на 50%
Вывод отчёта на учебном логе: доли проектов и первое предупреждение

Чтобы это работало каждый месяц, нужны три шага:

Шаг за шагом

Подключаем за три шага

Пишите лог на каждый вызов

Пропустите все вызовы через ask(project, model, messages) и выберите короткие неизменные метки проектов. Переименование посреди месяца разрежет один проект на две строки.

Укажите цены и бюджет

Впишите актуальные цены из таблицы в PRICES и задайте лимит: export BUDGET_USD=25.

Запускайте по расписанию

Хватит cron раз в час: 0 * * * * cd ~/ai && python3 report.py. Выведите результат в почту или чат, если хотите, чтобы предупреждение приходило само.

Как выглядит месяц на дешёвых моделях#

Цифры полезны, когда они ваши. Допустим, агент из учебного лога в среднем тратит 30 000 входных и 2 000 выходных токенов на шаг, 20 шагов на задачу, две задачи в день. Один шаг на DeepSeek-V4-Flash стоит $0.00065, на GLM-5.3-Flash — $0.00068. В день это 40 шагов, в месяц 1 200. Подвиньте ползунки под своего агента:

Калькулятор стоимости

Один день агента: шагов на задачу × задач в день

Входные токены на запрос: 30 000 · Выходные токены на запрос: 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 входных и 2 000 выходных токенов на запрос. Цены за 1M токенов актуальны и меняются вместе с нашими тарифами; столбец OpenRouter показывает справочные цены для той же нагрузки.

В сумме, с учётом входа и выхода, обе модели в этой таблице выходят дешевле референсных цен OpenRouter — с одной ценой в любое время суток и без пиковых надбавок. У DeepSeek-V4-Flash ещё и до 32 768 токенов в ответе и контекст 380K; у GLM-5.3-Flash контекст 390K. Когда отчёт покажет, какой проект тяжёлый, перейти только для него — это замена одной строки model, а баланс один на все модели. Сколько токенов уходит на один запрос, разобрано в статье сколько токенов использует запрос к API; как остановить один запуск агента посреди работы — в статье учёт токенов агента. Цены и модели — на странице моделей CheapTokens.

Где это ломается#

  • Модели нет в PRICES. Скрипт остановится с KeyError, где названа модель. Добавьте её; не подставляйте ноль, иначе проект будет выглядеть бесплатным.
  • Стриминг. Usage приходит в последнем чанке, только если его попросить (stream_options={"include_usage": True} в OpenAI SDK). Иначе в логе будут дыры.
  • Неудавшиеся запросы. Логируйте только ответы с объектом usage: у запроса, завершившегося ошибкой, считать нечего.
  • Отчёты по людям. Держите метку на уровне проекта или агента. Отчёт про людей провоцирует неверные выводы.