12k
All articles

Как считать токены и оценивать стоимость API LLM

Точно считайте токены LLM и оценивайте стоимость API с токенизаторами OpenAI, Claude, Gemini и Llama, а также лимиты контекста и биллинг.

OpenReplay Team
OpenReplay Team
Как считать токены и оценивать стоимость API LLM

Чтобы точно посчитать токены, прогоните всё тело запроса через токенизатор той модели, которую вы действительно вызываете, а затем оцените стоимость как (input_tokens ÷ 1 000 000) × input_rate + (output_tokens ÷ 1 000 000) × output_rate, взяв актуальные тарифы со страницы цен провайдера.

Никто не занимается этим заранее. Вопрос всплывает утром, когда приходит счёт, или днём, когда длинный диалог начинает выдавать реальным пользователям ошибки переполнения контекстного окна, — и внезапно «сколько здесь токенов?» становится единственным важным вопросом. Неприятность в том, что токен — это не слово, количество зависит от модели, а половина того, за что вам выставляют счёт, вообще не появляется в строке вашего промпта.

Эта статья даёт воспроизводимый метод: когда достаточно грубой оценки, как получить точный подсчёт у каждого провайдера, за что реально выставляется счёт и как превратить подсчёты в прогноз стоимости, который переживёт изменение цен.

Ключевые выводы

  • Считайте токены токенизатором той модели, которую вызываете: tiktoken для OpenAI, messages.countTokens для Claude, countTokens для Gemini и собственный токенизатор модели с Hugging Face для Llama.
  • Эвристики вроде «символы ÷ 4» приемлемы для планирования ёмкости, но никогда — для расчёта счетов; они ломаются на коде, JSON, неанглийском тексте и эмодзи.
  • Оплачиваемый промпт — это всё тело запроса, включая системный промпт, разметку ролей, схемы инструментов и повторно отправляемую историю диалога, а не только сообщение пользователя.
  • Количество входных токенов детерминировано, выходных — нет: соберите выборку из 50–200 реальных запросов, планируйте стоимость по среднему объёму ответа, а max_tokens задавайте по p95.
  • Оценочная стоимость запроса равна (input_tokens ÷ 1M) × input_rate + (output_tokens ÷ 1M) × output_rate, где тарифы берутся в реальном времени со страницы цен провайдера.

Почему токен — это не слово?

Токен — это специфичная для модели единица текста, порождаемая субсловным токенизатором, и она не соответствует ни словам, ни символам. Токенизаторы, построенные на byte pair encoding, как tiktoken у OpenAI, объединяют часто встречающиеся последовательности символов в отдельные токены и разбивают редкие слова на несколько частей. Слово «idempotency» кодируется в четыре токена («id», «emp», «ot», «ency») в cl100k_base — кодировке эпохи GPT-4 — и в три («id», «empot», «ency») в o200k_base, кодировке, которую используют актуальные модели OpenAI.

Последний пункт и есть ключевой для биллинга: разбиение зависит от модели. Одно и то же предложение даёт разное количество токенов под токенизаторами GPT, Claude, Gemini и Llama, потому что каждый обучался на разных данных с разным словарём. Любой подсчёт, сделанный не тем токенизатором, — это догадка.

Когда грубой оценки достаточно?

Для английской прозы «символы ÷ 4» или «слова × 1,33» дают достаточно близкий результат, чтобы задать размер колонки в базе данных или набросать план по ёмкости. Используйте эвристики для планирования ёмкости, но никогда — для биллинга или решений о контекстном окне.

Эвристики отказывают ровно там, где живёт продакшен-трафик: на коде, JSON, неанглийском тексте и эмодзи. Структурированные полезные нагрузки токенизируются по знакам препинания и паттернам пробелов, которые счётчик символов игнорирует, а одно эмодзи может развернуться в несколько токенов, поэтому «символы ÷ 4» сильно занижает счёт на строках, насыщенных эмодзи. Различия между токенизаторами, остающиеся умеренными на английской прозе, становятся ощутимо больше на коде и структурированных данных — а это именно то содержимое, которое отправляет суммаризатор или агент.

Какой счётчик токенов LLM даёт точный результат?

Принцип умещается в одну строку: считайте тем токенизатором, который принадлежит вызываемой вами модели. Маршруты по провайдерам:

ПровайдерСпособ точного подсчёта
OpenAItiktoken или js-tiktoken в Node и edge-средах
Anthropicэндпоинт count-tokens, client.messages.countTokens() в TypeScript SDK
Geminiai.models.countTokens() в SDK @google/genai
Llama и другие открытые моделисобственный токенизатор модели, опубликованный на Hugging Face

В JavaScript js-tiktoken — это чистый JS-порт, поэтому нет WASM-бинарника, который нужно загружать, и нет памяти, которую нужно освобождать вручную; кроме того, можно подключить одну кодировку отдельно, а не весь набор, что позволяет держать бандл небольшим:

import { Tiktoken } from "js-tiktoken/lite";
import o200k_base from "js-tiktoken/ranks/o200k_base";

const enc = new Tiktoken(o200k_base);
const count = enc.encode("Summarise this ticket thread for support.").length;

Эндпоинт Anthropic бесплатен и ограничен только собственными rate limits, так что нет никакого стоимостного оправдания тому, чтобы приближённо считать токены Claude токенизатором другого провайдера. Относитесь к его результату как к авторитетной предварительной оценке, но не как к точной: Anthropic документирует его именно как оценку, а оплачиваемое значение берётся из полей usage в ответе. Токенизаторы также меняются между поколениями моделей внутри одного провайдера. В документации Anthropic по подсчёту токенов указано, что Claude 4.7 и более поздние модели используют новый токенизатор, который превращает тот же текст примерно на 30 процентов в большее количество токенов, чем предыдущие модели Claude, а конкретный разрыв зависит от вашего контента. Старый подсчёт не переносится; сделайте новый на той модели, которую действительно вызываете. А если вам просто нужно число без подключения SDK, вставьте промпт в LLM token counter, который поддерживает GPT, Claude, Gemini и Llama.

Почему мой подсчёт не совпадает со счётом?

Оплачиваемый промпт — это всё тело запроса, а не написанная вами строка. Разметка ролей, системный промпт, схемы инструментов и функций, а также разделители между сообщениями — всё это добавляет токены, поэтому подсчёт только пользовательского сообщения всегда занижает результат. Одно определение инструмента может добавлять сотни входных токенов к каждому запросу, который его несёт.

История диалога работает как множитель. Чат-функция заново отправляет всю историю на каждом ходу, поэтому вход каждого хода включает все предыдущие ходы, и стоимость одного диалога растёт сверхлинейно с его длиной. Решение для подсчёта простое: соберите ровно тот массив messages, системный промпт и инструменты, которые вы будете отправлять, и посчитайте именно их. Эндпоинт count-tokens от Anthropic принимает ту же полезную нагрузку, которую вы отправили бы для создания сообщения, включая определения инструментов, — так что собранный запрос можно передать в него напрямую.

Как превратить подсчёт токенов в оценку стоимости?

Оценочная стоимость запроса — это одна строка арифметики, записанная символически:

cost = (input_tokens / 1_000_000) * input_rate + (output_tokens / 1_000_000) * output_rate

Там, где провайдеры поддерживают кэширование промптов, закэшированные входные токены оплачиваются по отдельному, более низкому cached_input_rate. Цены по моделям меняются в течение недель, поэтому здесь никакие тарифы не приводятся. Считайте тарифы конфигурацией, внедряемой в код, читайте актуальные значения со страницы цен провайдера и используйте LLM cost calculator, чтобы сравнить текущие цифры по моделям.

Любую оценку формируют два факта. Во-первых, у крупных провайдеров выходные токены обычно тарифицируются ощутимо дороже входных, поэтому длина ответа часто доминирует в стоимости. Во-вторых, входные подсчёты детерминированы, а выходные — нет: один и тот же запрос всегда даёт одинаковое количество токенов на входе, но то, что приходит обратно, варьируется в зависимости от сэмплинга. Измеряйте выход эмпирически. Прогоните 50–200 репрезентативных запросов, планируйте стоимость по средней длине ответа и задавайте max_tokens по 95-му перцентилю, чтобы легитимные ответы не обрезались, а «убежавшие» генерации оставались ограниченными.

Как понять, что промпт умещается в контекстное окно?

Входные токены плюс ожидаемые выходные токены должны уместиться в контекстное окно модели, иначе вызов просто провалится или ответ будет обрезан. Предварительная проверка — место в вашей обёртке над запросом: распределите бюджет окна между системным контекстом, историей диалога и запасом на вывод, посчитайте собранный запрос и подрежьте историю до отправки, а не после ошибки. Context window checker подскажет, помещается ли конкретный промпт в конкретную модель, без необходимости запоминать размеры окон, которые меняются с каждым релизом.

Место размещения обёртки важно, потому что переполнение видно пользователю: обрезанный ответ или ошибка посреди стрима — и рефлекс пользователя — повторить запрос, так что баг в бюджете токенов оплачивается дважды. Session replay функций на базе LLM показывает ровно этот цикл повторов задолго до того, как он проявится в счёте, который проверяют раз в месяц.

Что логировать в продакшене

Метод стабилен, даже если цены — нет: считайте собранный запрос собственным токенизатором вызываемой модели, снимайте выборку с реального трафика, чтобы узнать распределение длины ответов, и держите тарифы в конфигурации, которую обновляете со страниц цен. Затем замкните цикл в продакшене. Каждый крупный провайдер возвращает фактическое количество токенов в полях usage ответа — например, usage.input_tokens у Anthropic и usageMetadata у Gemini, — хотя более новый Interactions API у Gemini, всё ещё в Beta, возвращает usage с total_input_tokens и total_output_tokens. Логируйте их по каждому запросу с первого дня; записать их тривиально, а восстановить постфактум, когда придёт неожиданный счёт, — нет.

Часто задаваемые вопросы

Можно ли использовать tiktoken для подсчёта токенов моделей Claude или Gemini?

Нет. У токенизатора каждого провайдера свой словарь, поэтому подсчёт через tiktoken корректен только для моделей OpenAI и может существенно расходиться на том же входе для Claude или Gemini. Используйте бесплатный эндпоинт count-tokens от Anthropic для Claude, метод countTokens в SDK @google/genai для Gemini и токенизатор, опубликованный на Hugging Face, для открытых моделей вроде Llama.

В чём разница между npm-пакетами tiktoken и js-tiktoken?

tiktoken — это WASM-биндинг: он загружает скомпилированный бинарник и требует вызова free() для освобождения памяти энкодера по завершении работы. js-tiktoken — чистый JavaScript-порт с методами в camelCase (getEncoding, encodingForModel), без WASM-бинарника и без ручного управления памятью, что делает его более безопасным выбором для edge- и serverless-сред. Импорт одного файла рангов кодировки позволяет держать размер бандла небольшим.

Сообщают ли потоковые ответы об использовании токенов?

Да, но не везде по умолчанию. Для OpenAI Chat Completions задайте stream_options с include_usage true — и API отправит один дополнительный финальный чанк, поле usage которого покрывает весь запрос, а массив choices пуст. Anthropic передаёт usage в потоке автоматически: событие message_start несёт input_tokens, а события message_delta — накопительные output_tokens. Логируйте эти поля, а не считайте потоковые чанки самостоятельно.

Какую кодировку tiktoken использовать для какой модели OpenAI?

Используйте o200k_base для актуальных моделей OpenAI, таких как gpt-4o и новее, и cl100k_base — только для моделей эпохи GPT-4. Эти две кодировки разбивают текст по-разному, поэтому подсчёт, сделанный в одной, не переносится на другую. Если у вас есть ID модели, encodingForModel в js-tiktoken сам подберёт соответствующую кодировку, что избавляет от риска жёстко зафиксировать неверную кодировку при смене моделей.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.