AI для новичковИнструкция
Лимит расходов на пакет LLM-вызовов в Python: журнал и бюджет
Пишите токены и стоимость каждого вызова API в файл-журнал, останавливайте пакет на бюджете и считайте счёт до запуска. Код на Python внутри.

Содержание
Скрипт гоняет 200 запросов, вы закрываете крышку ноутбука, а утром узнаёте, во сколько обошлась ночь. Исправить это можно двумя небольшими привычками. После каждого вызова API дописывайте в файл-журнал одну строку с токенами и стоимостью. Перед каждым вызовом сравнивайте накопленную сумму с бюджетом и останавливайтесь, если он выбран. Это меньше 50 строк на Python, а пробный прогон на 5 запросах покажет счёт до того, как начнётся настоящий.
Что делает прогон Clocktower и что оттуда можно взять#
Дваркеш Пател, ведущий Dwarkesh Podcast, устроил турнир из 11 моделей в игре Blood on the Clocktower (по README — 386 партий) и выложил код, логи и рейтинги (github.com/dwarkeshsp/clocktower, проверено 8 ноября 2026). Из репозитория стоит забрать две вещи. В папке каждой партии лежит журнал вызовов с токенами и стоимостью, а расходы пишутся в games/. Флаг --budget ограничивает общую сумму; по умолчанию это $1,000. Сайт бенчмарка добавляет третий предохранитель: у всех моделей одинаковый потолок в 8 000 токенов на ход (clocktowerbench.com).
Итого: потолок на каждый вызов, запись о каждом вызове и предел на всю сумму. Турниру нужны все три, и вашему ночному прогону тоже, только в масштабе поменьше.
Журнал — это чековая лента в кассе магазина. Каждая продажа печатает строку, менеджер в любой момент суммирует ленту и видит, когда размен закончился.
Журнал: один файл, одна строка на вызов#
Вот всё целиком. Код использует пакет openai, направленный на CheapTokens (https:/; ключ и id моделей — в документации).
import json
import os
from pathlib import Path
from openai import OpenAI
client = OpenAI(
base_url='https://api.cheaptokens.dev/v1',
api_key=os.environ['CHEAPTOKENS_API_KEY'],
)
MODEL = 'deepseek-ai/DeepSeek-V4-Flash-0731'
PRICE_IN, PRICE_OUT = 1.00, 2.00 # USD per 1M tokens: example, substitute current ones
class BudgetExceeded(Exception):
pass
class Ledger:
def __init__(self, path, budget_usd):
self.path = Path(path)
self.budget = budget_usd
self.rows = []
if self.path.exists():
with self.path.open() as f:
self.rows = [json.loads(line) for line in f if line.strip()]
@property
def spent(self):
return sum(r['cost_usd'] for r in self.rows)
def done(self):
return {r['unit'] for r in self.rows}
def check(self, unit):
if self.spent >= self.budget:
raise BudgetExceeded(
f'stopped before {unit}: spent {self.spent:.4f} of {self.budget} USD'
)
def add(self, unit, usage, finish_reason):
cost = (usage.prompt_tokens * PRICE_IN
+ usage.completion_tokens * PRICE_OUT) / 1_000_000
row = {
'unit': unit,
'model': MODEL,
'prompt_tokens': usage.prompt_tokens,
'completion_tokens': usage.completion_tokens,
'finish_reason': finish_reason,
'cost_usd': round(cost, 6),
}
self.rows.append(row)
with self.path.open('a') as f:
f.write(json.dumps(row) + '\n')
def ask(ledger, unit, prompt, max_tokens=400):
ledger.check(unit)
resp = client.chat.completions.create(
model=MODEL,
messages=[{'role': 'user', 'content': prompt}],
max_tokens=max_tokens,
)
ledger.add(unit, resp.usage, resp.choices[0].finish_reason)
return resp.choices[0].message.contentКаждый вызов проходит одни и те же четыре шага:

Три решения здесь сделаны намеренно.
- Один JSON-объект на строку (JSONL), файл открыт на дозапись. Если скрипт упадёт на 130-м вызове, вызовы с 1-го по 129-й уже на диске.
- Сумма берётся из файла, а не из переменной. Перезапустите скрипт — лимит сохранится, потому что журнал помнит, сколько потратил прошлый запуск.
finish_reasonзаписан рядом с токенами. Строка со значениемlengthзначит, что ответ упёрся вmax_tokensи обрезан. Позже это пригодится.
Константы PRICE_ — заглушки. Актуальные числа берите из таблицы цен. Для DeepSeek-V4-Flash на момент, когда вы это читаете, это $0.015 за 1M входных токенов и $0.10 за 1M выходных. Поменяйте MODEL на zai- или MiniMaxAI/ и обе константы — тот же журнал будет работать и для них.
Цикл по пакету: пропускаем сделанное, стопаем на лимите#
Цикл короткий. Допустим, ваши запросы лежат в словаре, где ключ — id единицы работы, например doc-001:
prompts = {
'doc-001': 'Summarize this ticket in one sentence: ...',
'doc-002': 'Summarize this ticket in one sentence: ...',
# ... your 200 prompts
}
ledger = Ledger('ledger.jsonl', budget_usd=0.50)
already = ledger.done()
for unit, prompt in prompts.items():
if unit in already:
continue
try:
answer = ask(ledger, unit, prompt)
except BudgetExceeded as e:
print(e)
break
Path(f'out-{unit}.txt').write_text(answer)Запустите, на середине нажмите Ctrl+C, запустите снова: готовые единицы пропустятся, так что журнал заодно служит файлом прогресса. Когда бюджет кончится, вместо сюрприза вы увидите одну строку, примерно такую: stopped before doc-.
Обратите внимание на слово before. Проверка стоит перед каждым вызовом, поэтому последний вызов может чуть вывести сумму за бюджет. Насколько — известно: один вызов не может стоить больше, чем самый длинный запрос плюс max_tokens. Для запроса в 1 500 токенов и max_tokens=400 это не больше $0.000063. Поэтому у каждого вызова в обёртке есть max_tokens: так неограниченный риск превращается в число, под которое можно планировать.

Оцените счёт до запуска#
Теперь вторая половина: узнать стоимость до старта. Прогоните первые пять запросов через ask() с небольшим бюджетом и откройте журнал. Допустим, пять строк дают в среднем 1 500 входных и 400 выходных токенов. Эти числа и берём для всего пакета.
Для 200 запросов это 200 × 1 500 = 300 000 входных токенов и 200 × 400 = 80 000 выходных. Вот эта нагрузка на живых ценах:
| Модель | Вход $/1M | Выход $/1M | За запрос | 200 запросов | OpenRouter, 200 запросов |
|---|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.000046 | $0.0092 | $0.0167 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.000063 | $0.0125 | $0.0322 |
| MiniMax-M2.7 | $0.05 | $0.15 | $0.00014 | $0.027 | $0.324 |
1 500 входных и 400 выходных токенов на запрос. Цены за 1M токенов актуальны и меняются вместе с нашими тарифами; столбец OpenRouter показывает справочные цены для той же нагрузки.
В таблице есть и колонка OpenRouter. С учётом входа и выхода вместе DeepSeek-V4-Flash на такой нагрузке выходит дешевле OpenRouter, а именно эта сумма и уходит с баланса за пакет.
Запросы разной длины, так что пять образцов — оценка, а не обещание. Нужна запасная верхняя граница — берите самый длинный из пяти, а остальное поймает лимит бюджета. Хотите сравнить две модели на тех же 200 запросах? Дайте каждой свой файл журнала, например Ledger(f'ledger-, и потом две суммы лягут рядом.
Попробуйте на своём пакете: впишите токены на запрос из своей выборки и число запросов.
Калькулятор стоимости
Ваш пакет: токены из выборки в 5 запросов
Входные токены на запрос: 1 500 · Выходные токены на запрос: 400 · Запросов в день: 200
| Модель | В день | В месяц |
|---|---|---|
| GLM-5.3-Flash$0.02$0.04 | $0.0092 | $0.276 |
| DeepSeek-V4-Flash$0.015$0.10 | $0.0125 | $0.375 |
| MiniMax-M2.7$0.05$0.15 | $0.027 | $0.81 |
Как считать токены до отправки — в статье про подсчёт токенов через tiktoken.
Счёт больше ожидаемого: читаем журнал#
Третий сценарий самый неприятный. Агент пошёл не туда и крутится в цикле, а в личном кабинете видна только общая сумма. Журнал показывает, куда она ушла. Отсортируйте строки по стоимости:
rows = [json.loads(line) for line in open('ledger.jsonl')]
for r in sorted(rows, key=lambda r: r['cost_usd'], reverse=True)[:5]:
print(r['unit'], r['prompt_tokens'], r['completion_tokens'], r['finish_reason'])Для агента называйте единицу по шагу (step-17), чтобы видеть форму прогона. Несколько картин повторяются снова и снова; сопоставьте каждую с привычной причиной:
Найдите пары
Сопоставьте картину в журнале с привычной причиной
Нажмите на элемент слева, затем на его пару
- prompt_tokens понемногу растёт в каждой строкеЦикл на каждом шаге заново отправляет всю историю
- completion_tokens равен max_tokens, а finish_reason — lengthОтветы обрезаются на вашем потолке, а обрезанный текст вы всё равно оплачиваете
- Одна и та же единица идёт много раз подрядЦикл повторов или агент, который повторяет один шаг
- Одна строка стоит намного больше остальныхОдин раздутый вход, например целый файл в запросе
Каждую картину видно в журнале задолго до счёта. Первую разобрали в статье почему агент перечитывает историю.
Если нужны ещё расходы по проектам и оповещения вне скрипта, это закрывает разбор оповещений о бюджете по проектам. Журнал из этой статьи живёт в вашем коде и умеет сказать «стоп».
Перед запуском#
Чек-лист
Перед первым настоящим запуском
Контекст и лимит ответа DeepSeek-V4-Flash, удобные при выборе max_tokens, есть на её странице: DeepSeek-V4-Flash в CheapTokens.



