Назад до блогу

API та SDKПосібник

Відстеження вартості токенів AI-агента в Python: usage, бюджет, зупинка

Реєструйте поле usage кожного запиту, підсумовуйте його за задачею та зупиняйте агента при бюджеті в доларах. Робочий приклад Python для сумісного з OpenAI API.

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

Кодова картка в стилі терміналу: журнал usage за кроками та перевірка бюджету, яка зупиняє AI-агента
На цій сторінці
  1. Де знаходяться числа: об'єкт usage
  2. Лічильник, який зупиняє агента
  3. Що дає запуск на 40 кроків
  4. Коли модель думає
  5. Стрімінг: usage надходить останнім
  6. Якщо числа виглядають неправильно

Ваш агент працював десять хвилин, а рахунок більший за саму задачу. Щоб зрозуміти чому, читайте об'єкт usage у кожній відповіді API, підсумовуйте його за задачею та множте на ціну моделі за 1M токенів. Щоб обмежити витрати, порівнюйте цю поточну суму з бюджетом після кожного кроку та зупиняйте цикл, коли її перевищено. Нижче наведено невеликий клас Python, який робить саме це.

У прикладі використовується сумісна з OpenAI кінцева точка https://api.cheaptokens.dev/v1 та ідентифікатор моделі deepseek-ai/DeepSeek-V4-Flash-0731 (контекст 380K, до 32 768 вихідних токенів на відповідь). Якщо ви хочете оцінити запуск перед його початком, це інше завдання, і Як оцінити вартість AI-агента розповідає про це. Ця стаття про вимірювання та обмеження під час виконання.

Де знаходяться числа: об'єкт usage#

Кожне нестрімінгове завершення чату повертається з об'єктом usage. Довідка API DeepSeek (перевірено 26 жовтня 2026) перелічує ці поля:

  • prompt_tokens — усе, що ви надіслали цього разу: системний запит, історія, результати інструментів.
  • completion_tokens — те, що модель написала у відповідь.
  • total_tokens — ці два додані разом.

DeepSeek також документує необов'язкові додатки: prompt_cache_hit_tokens та prompt_cache_miss_tokens (вони додаються до prompt_tokens), і completion_tokens_details.reasoning_tokens для моделей мислення. На сторінці не вказано дату публікації, і ці додатки є розширеннями формату OpenAI, тому код нижче вважає їх необов'язковими та покладається лише на перші два поля.

Чому реєструвати кожен крок, а не лише останній? Тому що агент повторно надсилає свій зростаючий контекст щоразу. Крок 1 може надіслати 2 000 вхідних токенів, крок 30 може надіслати 9 000, і рахунок — це сума всіх їх. Причину пояснено в чому довгий чат коштує більше, а розподіл вхідних/вихідних даних — у вхідні проти вихідних токенів.

Лічильник, який зупиняє агента#

Ось усе: лічильник, який підсумовує токени, виводить один рядок на крок і викликає помилку, коли бюджет перевищено, плюс голий цикл агента навколо нього. Встановіть SDK за допомогою pip install openai і помістіть свій ключ у CHEAPTOKENS_API_KEY.

python
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 вихідних токенів для задачі.

Крок 1 надсилає 2 000 вхідних токенів, крок 30 надсилає 9 000; загальна сума задачі — це сума всіх кроків
Ілюстрація з припущеннями статті, а не результат запуску

На DeepSeek-V4-Flash це коштує $0.0052. Таблиця виконує те саме навантаження на інших моделях і ставить довідкову ціну OpenRouter поруч із нашою:

Модель Вартість OpenRouter
GLM-5.3-Flash $0.0054 $0.0111
DeepSeek-V4-Flash $0.0052 $0.0139
MiniMax-M2.7 $0.0144 $0.0912

240 000 вхідних і 16 000 вихідних токенів загалом. Ціни за 1 млн токенів актуальні та змінюються разом із нашими тарифами; стовпець OpenRouter показує довідкові ціни для того самого навантаження.

Загалом, з урахуванням вхідних і вихідних даних разом, DeepSeek-V4-Flash виходить дешевшим за OpenRouter для цієї задачі. Зверніть увагу також, де знаходяться токени: 240 000 з 256 000 — це вхідні. Контекст, який тягне за собою ваш агент, і є рахунком, тому число in= на крок у вашому журналі — це те, за чим слід стежити. Бюджет $0.50 за задачу в наведеному прикладі — це обмеження на розбіг, а не ціль.

Зміна моделі означає лише зміну MODEL. Виберіть ідентифікатор з документації CheapTokens: назви відрізняються між постачальниками, і короткий ідентифікатор з цінника не працюватиме в API. Для довгих циклів агента DeepSeek-V4-Flash має одну ціну в будь-яку годину дня, тому задача коштує однаково опівдні та о 3 ночі. Дивіться сторінку DeepSeek щодо її обмежень.

Коли модель думає#

Якщо відповідь набагато довша в токенах, ніж текст, який ви бачите, подивіться на reasoning_tokens. Токени мислення враховуються як вихідні, тому крок з інтенсивним міркуванням може створити набагато більше вихідних токенів, ніж припускає видимий текст, і вони оплачуються як вихідні. Лічильник вище збирає їх у meter.reasoning, коли API повертає поле, і залишає його на 0, коли ні, тому він ніколи не падає через відсутній ключ. Виведіть його поруч із out=, якщо хочете побачити, скільки з вихідних даних було мисленням. Зусилля міркування та токени мислення пояснює чому.

Стрімінг: usage надходить останнім#

Якщо ваш агент використовує стрімінг, у звичайних фрагментах немає usage. У форматі OpenAI ви запитуєте його за допомогою stream_options={'include_usage': True}. Довідка 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 спрацьовує на кроці 2. Ваш бюджет менший за один крок. Перевірте, що PRICE_IN вказано за 1M токенів, а не за 1K.

Щоб зберігати числа, записуйте meter.steps, meter.tokens_in, meter.tokens_out та meter.cost як один рядок JSON на задачу. Тиждень таких рядків розповість вам реальні токени на крок для вашого агента, і саме це число слід вставляти в кожну подальшу оцінку. Моделі, ціни та перший запит — у документації; виклики інструментів розглянуто в виклику функцій у Python.