AIFreeAPI Logo

Токены рассуждений в Gemini, OpenAI и Claude: как не посчитать их дважды

A
6 min readРуководства по API

В OpenAI и Claude токены рассуждений уже входят в выходной итог. У Gemini generateContent они указаны отдельно. Разбираем поля, расчёт и сверку повторных событий.

Схема учёта выходных токенов и рассуждений в OpenAI, Claude и Gemini

Когда расходы на reasoning-модель в вашем отчёте выше ожидаемых, сначала проверьте арифметику полей usage. У OpenAI и Claude детализация рассуждений уже включена в число выходных токенов. У нативного Gemini generateContent выход для обычного текстового запроса считают как сумму candidatesTokenCount и thoughtsTokenCount. Одинаковое действие «прибавить reasoning» поэтому исправляет один расчёт и ломает другой.

Это правила учёта из документации на 7 сентября 2026 года. Они помогают получить базу для расчёта стоимости вывода, но не заменяют полный расчёт счёта с входом, кэшем, инструментами и отдельными тарифами. Примеры ниже условные; реальные платные запросы для них не выполнялись.

Какое поле умножать на тариф вывода

Начните с названия интерфейса, а не с названия модели. Следующая таблица относится к исходным ответам указанных API.

ИнтерфейсКоличество оплачиваемых выходных токеновЧто делать с детализацией рассуждений
OpenAI Responsesusage.output_tokensusage.output_tokens_details.reasoning_tokens уже внутри итога
OpenAI Chat Completionsusage.completion_tokensusage.completion_tokens_details.reasoning_tokens уже внутри итога
Gemini generateContentusageMetadata.candidatesTokenCount + usageMetadata.thoughtsTokenCountСложить две отдельные категории, если обе известны
Claude Messagesusage.output_tokensusage.output_tokens_details.thinking_tokens, когда возвращается, уже внутри итога

У OpenAI это прямо следует из определения числа выходных токенов. У Google отдельные счётчики описаны в UsageMetadata, а цена вывода включает thinking в таблице тарифов Gemini API. У Claude включение thinking в выходной итог и доступная детализация описаны в разделе о стоимости рассуждений.

total_tokens и totalTokenCount нельзя автоматически брать вместо выходного счётчика: общий итог включает также вход. Если умножить его целиком на цену вывода, ошибка возникнет даже при полностью выключенных рассуждениях.

Почему у Gemini другая формула

В нативном generateContent Google отдельно сообщает токены сгенерированных ответов и токены мыслительного процесса. Для обычного текстового случая:

text
выход = candidatesTokenCount + thoughtsTokenCount общий итог = promptTokenCount + candidatesTokenCount + thoughtsTokenCount

В promptTokenCount уже учитывается кэшированное содержимое. Данные о кэше нужны для применения соответствующего тарифа ко входу; добавлять их повторно к общему количеству входных токенов тоже нельзя.

Эта формула не является универсальным правилом для всех продуктов Google. Interactions, Live, Vertex и совместимые интерфейсы требуют проверки собственного формата usage. Для мультимодальных запросов и вызовов инструментов дополнительно важны категории потребления и применимые к ним цены.

Особенно легко ошибиться с посредником, который возвращает completion_tokens для модели Gemini. Он мог уже объединить native-поля в один итог. Пока документация конкретного интерфейса этого не подтверждает, складывать его поля по правилу generateContent нельзя. Сохраните исходный ответ, адрес интерфейса и версию адаптера: одного идентификатора модели недостаточно.

Один условный запрос, три способа записать расход

Представим, что три ответа содержат следующие данные. Это арифметический пример, а не сравнение того, сколько разные модели потратят на одну задачу.

ОтветПоляПравильный итог выводаОшибочный расчёт
OpenAI Responsesoutput_tokens=1200, reasoning_tokens=8001 2001 200 + 800 = 2 000
Gemini generateContentcandidatesTokenCount=400, thoughtsTokenCount=8001 200Только 400
Claude Messagesoutput_tokens=1200, thinking_tokens=8001 2001 200 + 800 = 2 000

При условном тарифе 10 долларов за миллион выходных токенов правильная стоимость вывода каждого примера равна 1200 × 10 / 1 000 000 = 0,012 доллара. Повторное добавление 800 токенов даёт 0,020 доллара: локальный отчёт завышает эту часть расходов примерно на 66,7%. Если в примере Gemini забыть thinking, отчёт покажет только 0,004 доллара.

Тариф здесь намеренно вымышленный. Для реального расчёта берут цену конкретной модели на дату потребления с учётом режима обслуживания. В частности, не следует безусловно исключать thinking из Batch-цены: в таблице Google цены вывода, включая thinking, приведены и для Standard, и для Batch. Это не основание применять одну цену ко всем моделям или режимам.

Не называйте разность output_tokens − reasoning_tokens точным числом токенов видимого ответа. В выходном счётчике могут присутствовать служебные элементы форматирования, каналов и вызовов инструментов. Даже нулевой reasoning не гарантирует совпадения полного вывода с отдельно токенизированным текстом на экране. Для OpenAI это ограничение прямо указано в документации о подсчёте токенов.

Условные расчёты стоимости вывода при повторном сложении и пропуске токенов рассуждений
Условные расчёты стоимости вывода при повторном сложении и пропуске токенов рассуждений

Если thinking не показан или ответ пустой

Отсутствующая детализация и отсутствующий итог — разные ситуации.

Если Claude вернул output_tokens=1200, но не вернул output_tokens_details, база стоимости вывода известна: 1 200. Доля thinking неизвестна. В текущей документации Claude есть поле thinking_tokens, однако его наличие нельзя обещать для каждой модели, старого SDK или посредника. В потоковом ответе эта детализация появляется в финальном message_delta.

Если у Gemini известен только candidatesTokenCount, не подставляйте ноль вместо отсутствующего thoughtsTokenCount без подтверждённого правила конкретного интерфейса. Иначе «не получили данные» превратится в «рассуждений не было». Оставьте итог неопределённым и восстановите его из завершающего usage или другой авторитетной записи. Явно возвращённый ноль, напротив, можно учитывать как ноль.

Пустой текст также не доказывает нулевой расход. OpenAI предупреждает: модель может исчерпать лимит на рассуждения и вернуть незавершённый ответ без видимого текста, сохранив оплачиваемое потребление. max_output_tokens ограничивает в том числе скрытые токены рассуждений. См. руководство по reasoning-моделям.

У Claude скрытое thinking тоже оплачивается; показанное краткое изложение не является счётчиком всех рассуждений. Согласно документации стоимости thinking, создание самого краткого изложения дополнительно не тарифицируется. Для учёта нужен usage, а не длина показанного блока.

Минимальная нормализация без лишнего сложения

Удобно привести ответы к двум собственным полям: output — база для стоимости вывода, reasoning — известная детализация. Второе поле служит аналитике и никогда автоматически не прибавляется к первому. None означает неизвестное значение.

Пример на Python принимает сам объект usage или usageMetadata, а также заранее установленный тип нативного API. Он не распознаёт интерфейс по модели и не вычисляет деньги.

python
def count(value): if value is None: return None if type(value) is not int or value < 0: raise ValueError("Invalid token count") return value def detail(usage, field, key): value = usage.get(field) if value is None: return None if not isinstance(value, dict): raise ValueError("Invalid token details") return count(value.get(key)) def normalize(api, usage): if api == "gemini.generateContent": candidates = count(usage.get("candidatesTokenCount")) reasoning = count(usage.get("thoughtsTokenCount")) output = (None if candidates is None or reasoning is None else candidates + reasoning) elif api == "openai.responses": output = count(usage.get("output_tokens")) reasoning = detail(usage, "output_tokens_details", "reasoning_tokens") elif api == "openai.chat": output = count(usage.get("completion_tokens")) reasoning = detail(usage, "completion_tokens_details", "reasoning_tokens") elif api == "claude.messages": output = count(usage.get("output_tokens")) reasoning = detail(usage, "output_tokens_details", "thinking_tokens") else: raise ValueError("Unsupported native API") if output is not None and reasoning is not None: if reasoning > output: raise ValueError("Reasoning exceeds output") return {"output": output, "reasoning": reasoning}

Такая функция сохраняет известный включающий итог даже без необязательной детализации и не превращает отсутствующие значения в нули. Отрицательные, дробные, строковые и логические значения отвергаются; доля рассуждений больше итога считается ошибкой данных, а не исправляется обрезанием.

На синтетических примерах локально проверены все четыре ветви, отсутствие детализации и итога, явный ноль, неверные типы и превышение thinking над output. Полноценная система дополнительно проверяет схему ответа, хранит исходный usage, различает финальные и промежуточные данные и учитывает правила повторной доставки событий.

Поток, дубликат события и повторный запрос

Даже правильная формула даст завышенный результат, если применить её несколько раз к одному потреблению.

У Claude значения usage в message_delta накопительные. Если промежуточная запись показывает 40 выходных токенов, а завершающая — 100, итог равен 100, не 140. Используйте последнее авторитетное значение или итоговое сообщение SDK. При обрыве потока итог может остаться неизвестным: последнее увиденное число не следует выдавать за подтверждённый финальный расход. Это поведение описано в документации потоковых ответов Claude.

При записи в собственную базу различайте три случая:

  • То же событие доставлено повторно. Повторная обработка одной финальной записи не должна увеличивать расход. Нужна идемпотентная запись по доступным идентификаторам события и ответа.
  • Пришло уточнение того же ответа. Накопительный итог обновляет предыдущую запись, а не прибавляется к ней.
  • Клиент повторно отправил запрос. Новая попытка может действительно потребить токены. Совпадение prompt не делает её дубликатом для финансового учёта.

Храните собственный идентификатор операции, идентификатор попытки и идентификаторы запроса или ответа поставщика, если интерфейс их предоставляет. Одна пользовательская операция может включать несколько оплачиваемых попыток. Это помогает объяснить расход после тайм-аута: клиент мог не получить завершение первой попытки и отправить вторую.

Отдельный случай — история диалога. Предыдущие рассуждения Claude, сохранённые в контексте, могут оплачиваться как вход следующего запроса; правила сохранения зависят от модели. Это новое потребление на следующем шаге, а не повторное прибавление thinking к выходу предыдущего. Универсальное правило «старое thinking всегда удаляется и бесплатно» неверно; сверяйтесь с документацией thinking.

Схема записи накопительных обновлений, повторных событий и новых попыток запроса
Схема записи накопительных обновлений, повторных событий и новых попыток запроса

Как сверить расхождение с реальным счётом

Сначала выберите одну попытку запроса, для которой есть исходный usage и запись поставщика. Проверьте последовательно:

  1. Совпадают ли модель, интерфейс, время и идентификатор ответа. Для посредника используйте его договорённость о полях и тарифах.
  2. Не прибавлялась ли детализация рассуждений к включающему итогу. Для Gemini generateContent, наоборот, проверьте, не потерян ли отдельный thinking.
  3. Финальный ли это usage и не записан ли он несколько раз. Не складывайте накопительные снимки.
  4. Правильно ли разделены вход, кэш и выход, выбран ли тариф для даты и режима запроса. Проверьте отдельные расходы на инструменты и хранение, если они применимы.
  5. Не было ли других попыток той же операции. Сопоставляйте потребление по идентификаторам, а не по одинаковому тексту.

Известный пример такого класса ошибки — исторический issue browser-use #4065: добавление reasoning_tokens к completion_tokens завышало локальное отображение usage и стоимости. Issue закрыт исправлением. Он иллюстрирует ошибку клиентского учёта и сам по себе не доказывает двойного списания поставщиком.

Если задача шире и вы выбираете API для проекта, посмотрите сравнение затрат на Gemini, OpenAI и Claude. Для расследования конкретного расхождения полезнее сохранить исходный usage, рассчитанный выходной итог и список попыток: по этим данным можно проверить арифметику, не смешивая её с выбором модели.