API и SDKИнструкция
Учёт токенов ИИ-агента на Python: usage, бюджет, стоп
Пишите usage каждого запроса, суммируйте по задаче и останавливайте агента на бюджете в долларах. Рабочий пример на Python для OpenAI-совместимого API.

Содержание
Агент отработал десять минут, а счёт больше, чем сама задача. Чтобы понять почему, читайте объект usage в каждом ответе API, суммируйте по задаче и умножайте на цену модели за 1M токенов. Чтобы ограничить расход, после каждого шага сравнивайте накопленную сумму с бюджетом и останавливайте цикл, когда лимит пройден. Ниже — небольшой класс на Python, который делает ровно это.
В примере — OpenAI-совместимый адрес https:/ и модель с id deepseek- (контекст 380K, до 32 768 токенов в ответе). Оценить расход до запуска — другая задача, о ней как оценить стоимость ИИ-агента. Здесь — измерение и ограничение прямо во время работы.
Где числа: объект usage#
Каждый обычный (не потоковый) ответ chat completions приходит с объектом usage. В справочнике API DeepSeek (проверено 26 октября 2026) перечислены поля:
prompt_tokens— всё, что вы отправили в этот раз: системный промпт, история, результаты инструментов.completion_— что модель написала в ответ.tokens total_tokens— их сумма.
DeepSeek описывает и необязательные поля: prompt_ и prompt_ (вместе дают prompt_tokens), а также completion_ для моделей с рассуждением. У страницы нет даты публикации, а эти поля — расширения формата OpenAI, поэтому код ниже считает их необязательными и опирается только на первые два.
Зачем писать каждый шаг, а не только последний? Агент отправляет заново весь растущий контекст. На шаге 1 уйдёт 2 000 входных токенов, на шаге 30 — 9 000, а счёт — сумма всех шагов. Почему так, объясняет статья про долгий чат, а про разницу входа и выхода — input vs output tokens.
Счётчик, который останавливает агента#
Вот всё целиком: счётчик складывает токены, печатает строку на шаг и бросает ошибку при превышении бюджета, а вокруг него — простой цикл агента. Поставьте SDK командой pip install openai, ключ положите в CHEAPTOKENS_.
import os
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'
# example prices, USD per 1M tokens: use the current ones from the pricing table
PRICE_IN = 0.10
PRICE_OUT = 0.40
class BudgetExceeded(Exception):
pass
class TaskMeter:
def __init__(self, budget_usd):
self.budget = budget_usd
self.steps = 0
self.tokens_in = 0
self.tokens_out = 0
self.reasoning = 0
@property
def cost(self):
return (self.tokens_in * PRICE_IN + self.tokens_out * PRICE_OUT) / 1_000_000
def add(self, usage):
self.steps += 1
self.tokens_in += usage.prompt_tokens
self.tokens_out += usage.completion_tokens
details = getattr(usage, 'completion_tokens_details', None)
self.reasoning += getattr(details, 'reasoning_tokens', 0) or 0
print(f'step {self.steps}: in={usage.prompt_tokens} '
f'out={usage.completion_tokens} task=${self.cost:.4f}')
if self.cost > self.budget:
raise BudgetExceeded(
f'budget ${self.budget:.2f} crossed at step {self.steps}: '
f'${self.cost:.4f}')
def run_task(goal, budget_usd=0.50, max_steps=40):
meter = TaskMeter(budget_usd)
messages = [
{'role': 'system', 'content': 'Work step by step. Reply DONE when finished.'},
{'role': 'user', 'content': goal},
]
for _ in range(max_steps):
resp = client.chat.completions.create(
model=MODEL, messages=messages, max_tokens=1024)
meter.add(resp.usage) # log first, then decide
reply = resp.choices[0].message.content or ''
messages.append({'role': 'assistant', 'content': reply})
if 'DONE' in reply:
break
messages.append({'role': 'user', 'content': 'Continue.'})
return meter
try:
meter = run_task('Summarise the main risks of a database migration.')
print(f'finished: {meter.steps} steps, ${meter.cost:.4f}')
except BudgetExceeded as err:
print('stopped:', err)Три решения здесь стоит забрать себе.
Сначала запись, потом решение. meter.add сперва фиксирует шаг и только потом проверяет бюджет, поэтому даже остановленный запуск оставляет полный след. Проверка идёт после вызова, который пересёк черту, так что перерасход — не больше одного шага. max_tokens=1024 ограничивает выход этого последнего шага.
Два жёстких стопа, а не один. max_steps ловит агента, который крутится без больших трат; бюджет в долларах ловит того, кто тратит много за шаг. Нужны оба.
Цены лежат в одном месте. PRICE_IN и PRICE_OUT — условные числа. Актуальные для своей модели берите на странице цен; сейчас DeepSeek-V4-Flash — $0.015 за 1M входных токенов и $0.10 за 1M выходных.
Во что складываются 40 шагов#
Допустим, агент делает 40 шагов, и из-за растущего контекста средний шаг отправляет 6 000 входных токенов и получает 400 в ответ. Это 40 × 6 000 = 240 000 входных токенов и 40 × 400 = 16 000 выходных на задачу.

На DeepSeek-V4-Flash это $0.0052. Таблица считает ту же нагрузку на остальных моделях и ставит рядом референсную цену OpenRouter:
| Модель | Вход $/1M | Выход $/1M | Стоимость | OpenRouter |
|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.0054 | $0.0111 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.0052 | $0.0139 |
| MiniMax-M2.7 | $0.05 | $0.15 | $0.0144 | $0.0912 |
240 000 входных и 16 000 выходных токенов всего. Цены за 1M токенов актуальны и меняются вместе с нашими тарифами; столбец OpenRouter показывает справочные цены для той же нагрузки.
В сумме, с учётом входа и выхода, DeepSeek-V4-Flash в этой задаче выходит дешевле OpenRouter. Заметьте и другое: из 256 000 токенов 240 000 — вход. Счёт — это контекст, который агент тащит за собой, поэтому в логе стоит следить именно за in= на каждом шаге. Бюджет $0.50 на задачу в примере — потолок на случай «убежавшего» агента, а не цель.
Сменить модель — значит поменять MODEL. Id берите из документации CheapTokens: у разных провайдеров имена отличаются, а короткий id из меток цен API не принимает. Для длинных циклов у DeepSeek-V4-Flash одна цена в любое время суток: задача стоит одинаково в полдень и в три ночи. Лимиты модели — в каталоге моделей.
Когда модель думает#
Если ответ в токенах заметно длиннее видимого текста, загляните в reasoning_tokens. Токены рассуждения считаются выходными, поэтому шаг с долгим размышлением даёт заметно больше выходных токенов, чем видно в тексте, а они оплачиваются как выход. Счётчик выше складывает их в meter.reasoning, когда API возвращает поле, и оставляет 0, когда не возвращает, — на отсутствии ключа он не упадёт. Выведите его рядом с out=, чтобы видеть, сколько выхода ушло на размышления. Почему так — в статье про reasoning effort и thinking tokens.
Потоковый режим: usage приходит последним#
Если агент работает в потоке, в обычных чанках usage нет. В формате OpenAI его просят через stream_. Справочник DeepSeek (проверено 26 октября 2026) говорит, что опция работает только вместе с stream=True (иначе API отвечает 400), что usage равен null во всех чанках, кроме одного, и что последний чанк перед data: [DONE] несёт итог по всему запросу. Значит, в потоковом цикле вызывайте meter.add один раз — на чанке, где usage не пуст. Проще всего начать с непотокового цикла выше; сам потоковый цикл — в стриминге на Python.
Если числа не сходятся#
usageравен None. Вы стримите безinclude_usage. Добавьте опцию или перейдите на обычный вызов.- Сумма меньше, чем списано с баланса. Проверьте, что каждый вызов идёт через счётчик: повторные попытки, вызов для сводки или выбора инструмента вне
run_taskлегко потерять. Обернитеclient.в одну функцию и пускайте всё через неё.chat. completions. create - Цена шага всё растёт. Для цикла, который дописывает всё в
messages, это нормально. Обрезайте старый вывод инструментов или сжимайте историю; идея — в статье про вывод инструментов вне контекста. BudgetExceededсрабатывает на втором шаге. Бюджет меньше одного шага. Проверьте, чтоPRICE_INуказан за 1M токенов, а не за 1K.
Чтобы сохранять числа, пишите meter.steps, meter.tokens_in, meter.tokens_out и meter.cost одной JSON-строкой на задачу. Неделя таких строк покажет реальное число токенов на шаг для вашего агента — его и подставляйте в любые оценки. Модели, цены и первый запрос — в документации; вызовы инструментов — в статье про function calling на Python.



