Учёт расходов и граница повторной попытки
Когда работа распределена между несколькими исполнителями, важно видеть её стоимость. Расходы зависят от размера входа, объёма ответа, числа попыток и выбранного тарифа. В этом уроке добавим учебный журнал и расчёт через Decimal. Результат будет показывать уже учтённую сумму и оставшийся лимит, сохраняя ясное происхождение чисел. Он не станет скрытым обещанием цены конкретного AI-сервиса.
В usage.json находятся две вымышленные записи. Их имена начинаются с fictional, а тариф в budget.py прямо обозначен как учебный. Реальных вызовов модели при подготовке не было. Это позволяет разобрать расчёт без доступа к аккаунту и платёжным данным. Для настоящего инструмента нужно получить актуальные правила тарификации и фактическое usage из его ответа или официального отчёта, сохранив дату выбранных ставок.
Единицы расчёта
Входные и выходные токены могут иметь разные ставки. Поэтому функция cost умножает каждый счётчик на свою цену за миллион и делит сумму на миллион. Это обычное преобразование единиц, а не оценка качества работы. Большой ответ не обязательно полезнее короткого, а меньший контекст не обязательно достаточен для правильного изменения. Цена отвечает на отдельный вопрос: сколько ресурсов потребовал выбранный маршрут при заданном тарифе.
Decimal создаётся из строк, чтобы денежные значения не зависели от двоичного приближения float. Это не решает все бухгалтерские требования, но делает учебную арифметику понятной. Если создать Decimal из уже приблизительного float, погрешность попадёт в значение раньше расчёта. В нашем примере ставка '2.00' означает точное учебное число. Реальный отчёт может иметь свои правила округления, которые нужно учитывать отдельно при сопоставлении с выставленной суммой.
Счётчики проверяются как неотрицательные обычные целые числа. Логические значения снова исключаются: True не является достоверным количеством токенов. Если журнал неполный или поле отсутствует, лучше получить понятную ошибку подготовки данных, чем молча считать ноль. Нулевая стоимость неизвестной попытки создаёт ложное ощущение запаса. Поэтому исходные данные расхода должны быть столь же явными, как вход функции отбора страниц.
Попытка и результат
Журнал связывает расход с идентификатором попытки. Если агент повторил задачу после замечания, новая попытка имеет собственную запись. Стоимость предыдущего ответа не исчезает из-за того, что его код был отвергнут. Это особенно заметно при параллельной работе: несколько исполнителей могут читать одинаковый контекст, а итоговый проект включит лишь часть предложений. Журнал помогает увидеть полный путь, а не только цену принятого кандидата.
Наша модель не учитывает скидки кэша, дополнительные инструменты, отдельные сервисы и правила округления провайдера. Это конкретная граница расчёта. Нельзя переносить полученную сумму в реальный аккаунт без адаптации. Если инструмент сообщает cached tokens, следует разобраться, являются ли они частью общего входного счётчика и по какой ставке оплачиваются. Двойное включение или пропуск такого поля может изменить итог, даже если сама арифметика написана верно.
К числовому расходу полезно добавить результат попытки: принят кандидат, требуется исправление, задача прекращена. Такой признак не входит в формулу стоимости, но помогает оценить процесс. Десять дешёвых повторов с одним и тем же неполным контекстом могут стоить больше одной аккуратно подготовленной попытки. При этом нельзя делать универсальный вывод только по двум записям нашего примера: это учебные данные, а не исследование эффективности моделей.
Граница следующего вызова
CAP в примере равен половине условной денежной единицы. Оставшийся лимит вычисляется как максимум нуля и разницы между CAP и учтённой суммой. Отрицательное значение не выдаётся как «доступный бюджет», но превышение нельзя скрывать только этим ограничением отображения. Для настоящего контроллера нужно отдельно записать факт перерасхода и остановить новые операции. Наш файл пока является калькулятором журнала, не шлюзом реальных AI-вызовов.
Поле next_call_requires_estimate напоминает, что прошлого расхода недостаточно для решения о следующей попытке. Нужно оценить будущий вход и верхнюю границу ответа. Если доступный инструмент не позволяет ограничить объём или стоимость, обещание «уложимся» остаётся предположением. В учебном маршруте человек может уменьшить задачу, уточнить контекст или прекратить повторение. Бюджет не разрешает пропускать ревью ради экономии и не даёт агенту дополнительных прав на действие.
Точную цену будущего ответа обычно нельзя узнать заранее без результата. Поэтому полезно иметь запас и ограничение максимального ответа. Но грубая оценка должна называться оценкой, а не фактическим списанием. Аналогично подсчёт слов Markdown не является счётчиком токенов выбранной модели. Не заменяйте единицы по впечатлению. Если журнал строится вручную, источник каждого числа должен оставаться доступным для сопоставления.
Ожидаемый результат самостоятельного запуска — JSON со строковыми значениями суммы и остатка, признаком вымышленного тарифа и требованием оценки следующего вызова. Мы не приводим такой вывод как выполненный. В файлах отсутствуют ключи сервисов, реальные счета и обращения к сети. Это позволяет передать состояние другому читателю без раскрытия аккаунта, сохраняя достаточный пример для понимания арифметики и границ её применения.
Цена принятого результата
Пусть первая попытка дала неверный кандидат и потребовала исправления. Вторая оказалась подходящей. Если в отчёт включить только вторую, стоимость принятого кода будет занижена: первая попытка тоже потребовала ресурсы. Поэтому журнал сохраняет весь путь одной задачи. При этом нельзя напрямую сравнивать двух исполнителей только по числу токенов, если их контексты, требования и тарифы отличались. Сначала нужно установить сопоставимые исходные условия.
Дополнительная запись может иметь нулевой output, например если вызов завершился до генерации, но реальные правила оплаты зависят от сервиса. Не угадывайте такой расход по локальному сообщению ошибки. Используйте фактическое usage, когда оно доступно, либо отметьте значение как неизвестное. Неизвестный расход не должен молча становиться бесплатным: это другая категория данных, которую простой учебный калькулятор пока не поддерживает.
Наши два fictional-вызова имеют известные авторские числа и не описывают платёж пользователя. Поэтому они подходят для арифметики, но не для вывода «этот курс стоил столько». Такая граница предотвращает превращение иллюстрации механизма в выдуманный финансовый отчёт о настоящей работе.
Далее подготовим передачу состояния после смены контекста. В неё войдут текущий результат, решения, известные ограничения и ещё не выполненные действия. Журнал расходов можно приложить как отдельный источник, но краткая передача не должна превращаться в попытку восстановить намерение из всех предыдущих ответов агента. Актуальный контракт останется главным ориентиром продолжения.
Полный файл budget.py из состояния lesson-11. Полное самостоятельное состояние находится в архиве серии; фрагмент не заменяет остальные определения.
from decimal import Decimal
import json
from pathlib import Path
# Fictional tariff, not a claim about any provider price.
TARIFF = {"input_per_million": Decimal("2.00"), "output_per_million": Decimal("8.00")}
CAP = Decimal("0.50")
def cost(row):
for field in ("input_tokens", "output_tokens"):
if type(row[field]) is not int or row[field] < 0:
raise ValueError("invalid token count")
return (Decimal(row["input_tokens"]) * TARIFF["input_per_million"] + Decimal(row["output_tokens"]) * TARIFF["output_per_million"]) / Decimal(1000000)
if __name__ == "__main__":
rows = json.loads((Path(__file__).resolve().parent / "usage.json").read_text())
spent = sum((cost(row) for row in rows), Decimal(0))
print(json.dumps({"fictional_tariff": True, "spent": str(spent), "remaining": str(max(Decimal(0), CAP-spent)), "next_call_requires_estimate": True}, indent=2))
Полный файл usage.json из состояния lesson-11. Полное самостоятельное состояние находится в архиве серии; фрагмент не заменяет остальные определения.
[
{
"attempt": "fictional-A",
"input_tokens": 6000,
"output_tokens": 1500
},
{
"attempt": "fictional-B",
"input_tokens": 2500,
"output_tokens": 500
}
]
Статус примера: not_run. При подготовке не выполнялись учебные программы, тесты, вызовы AI или деплой. Описанные эффекты ожидаемые.
Оглавление серии · Полные исходники
Первичные документы: Источник 1.