Разбираем аудит токенов в Clawdmeter: как найти баг, исправить его и проверить данные. Практические советы для разработчиков.
Вы когда-нибудь задумывались, почему счётчик токенов в вашем инструменте показывает цифры, которые не сходятся с реальностью? Мы разобрали баг в Clawdmeter — приложении для отслеживания расхода токенов Claude Code. Оказалось, что все числа были завышены в 2.34 раза из-за неправильного суммирования записей в JSONL-файлах. В этой статье вы узнаете, как находить такие ошибки, почему важно запускать код автора, а не свою модель, и как проверять чужие исправления.
Проблема в том, что Claude Code записывает одно сообщение ассистента в несколько JSONL-записей — по одной на каждый блок контента. Каждая запись содержит одинаковый message.id и одинаковый объект usage. В src/transcript.py суммировались токены для каждой записи, а не для каждого сообщения. Это приводило к завышению в 2.34 раза. Мы проверили три функции: подсчёт токенов для баров 5ч/7д, цены на странице Stats и итоги сессии. Все три дали завышенные результаты.
Вместо того чтобы переписывать логику приложения, мы импортировали его собственный модуль transcript.py и вызвали его функции. Для этого понадобился всего лишь двенадцатистрочный стаб для PySide6:
import types, sys
pkg = types.ModuleType("PySide6")
qtcore = types.ModuleType("PySide6.QtCore")
qtcore.QObject = ... # три no-op класса
sys.modules["PySide6"] = pkg
sys.modules["PySide6.QtCore"] = qtcore
sys.path.insert(0, str(clawdmeter_clone / "src"))
import transcript as T # его модуль, без изменений
T._file_token_events(path) # его функции, его числаТакой подход устраняет любые споры о том, что мы неправильно поняли код. Мы получаем числа из его собственных функций, и остаётся только вопрос о честности входных данных. Мы использовали синтетический корпус с известным манифестом, что делает проверку однозначной.
Мейнтейнер подтвердил баг 6 августа, а 8 августа вышел релиз v3.0.1 с исправлением. В примечании к релизу он написал: "Ваши числа токенов и стоимости упадут примерно в 2.5 раза. Это исправление, а не регрессия". Важно отметить, что мейнтейнер не использовал наш скрипт для воспроизведения — он проверил проблему самостоятельно. Это даже лучше, потому что подтверждение не зависит от нашего кода.
В своём комментарии мейнтейнер не только подтвердил баг, но и указал на два наших недочёта. Во-первых, повторяющиеся объекты usage не всегда идентичны. В его корпусе 3799 групп отличаются, и в каждой output_tokens — это накопительный итог. Например, сообщение может иметь 5, 5, 5, 328 токенов в четырёх записях. Если брать первую запись, получится 5 токенов вместо 328. Правильно — брать максимум по каждому бакету. Во-вторых, дедупликация по файлу недостаточна: 1094 идентификатора сообщений встречаются в нескольких файлах из-за возобновления сессий. Нужно дедуплицировать на уровне всего аккаунта, а не только файла.
Мы перепроверили свои данные и обнаружили, что наше утверждение о 100% идентичных записях было верным только для основного пути разговора. В побочных цепочках (сабагентах) только 20.82% записей идентичны. Различие определяется полем isSidechain, а не версией Claude Code, как предположил мейнтейнер. Мы проверили: гипотеза о версии предсказывала бы различия в основном пути, но их нет. Гипотеза о поле предсказывает различия только в побочных цепочках — что и наблюдается.
Если группировать записи только по message.id, то в нашем корпусе появляются 560 групп с немонотонными счётчиками — это артефакт из-за перемешивания записей из разных файлов. Группировка по паре (файл, message.id) устраняет все 560. Это предупреждение для тех, кто захочет написать проверку: не группируйте только по id, иначе получите ложные результаты.
После выхода v3.0.1 мы запустили наш харнесс против обеих версий. На v3.0.0 — красный, как и ожидалось: 2.338×, 2.338×, 2.373×. На v3.0.1 — зелёный, все числа точные до цифры: 1,108,697 и 732,191, 540 строк для 540 сообщений. Пришлось адаптировать проверку, потому что _file_token_events теперь возвращает несвёрнутые события с ключами записи и сообщения. Это подтверждает, что исправление реализовано именно так, как мы рекомендовали.
Главный вывод: запускайте код автора, а не свою модель. Это устраняет целый класс споров. Второе: относитесь к отчёту об исправлении как к набору данных — в нём может быть больше измерений, чем в самом баге. Третье: публикуйте инварианты с указанием области применимости. Наше "100%" было верным для основного пути, но мы не указали это. Четвёртое: когда два корпуса расходятся, ищите поле, а не версию. Поле isSidechain объясняет форму данных лучше, чем версия.
Если вы аудируете трекеры токенов или пишете свои, вот что делать прямо сейчас:
Аудит токенов — это не разовая задача, а постоянный процесс. Используйте эти уроки, чтобы находить и исправлять ошибки быстрее и точнее.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →