Практика · Экономика AI-систем
Как считать стоимость LLM не по токенам, а по завершённым бизнес-операциям
Средняя цена запроса почти ничего не говорит о себестоимости процесса. Один результат может потребовать несколько обращений к модели, поиск в базе, повтор после ошибки и ручную проверку — а другой запуск закончится отказом, уже успев потратить ресурсы.
Что именно нужно считать
LLM — это лишь один из компонентов бизнес-процесса. Поэтому единицей учёта должен быть не отдельный запрос к модели, а операция с понятным началом, результатом и правилом проверки.
Например, «обработать входящий документ» — слишком расплывчатая формулировка. Проверяемая операция звучит точнее: «извлечь обязательные поля, проверить формат, записать результат в систему и получить подтверждение записи».
Операция считается завершённой успешно, только если выполнено заранее определённое условие. Красивый ответ модели сам по себе не является завершением.
В числитель входят и неудачные запуски. Именно они делают показатель пригодным для управленческих решений.
Шаг 1. Зафиксируйте контракт операции
До сбора стоимости опишите четыре поля:
- Начало: событие, создающее операцию.
- Конец: наблюдаемое состояние, после которого работа завершена.
- Критерий успеха: машинная или подтверждённая человеком проверка.
- Граница затрат: какие компоненты относятся к операции.
Пример, а не отраслевой стандарт:
operation_type: document_registration
starts_when: document_received
succeeds_when:
- required_fields_valid
- record_persisted
- persistence_ack_received
fails_when:
- retry_budget_exhausted
- validation_rejected
- deadline_exceeded
cost_scope:
- llm
- embeddings
- tool_calls
- storage
- human_review
Не меняйте критерий успеха задним числом ради улучшения метрики. Версионируйте контракт, например как document_registration:v3.
Шаг 2. Назначьте сквозной идентификатор
Каждый запуск получает operation_id. Все попытки, модельные запросы, вызовы инструментов и проверки должны ссылаться на него. Для повторной попытки используйте новый attempt_id, но сохраняйте исходный operation_id.
{
"operation_id": "op_generated_id",
"operation_type": "document_registration:v3",
"attempt_id": "attempt_generated_id",
"event_type": "llm_call",
"component": "classifier",
"status": "completed",
"input_units": 0,
"output_units": 0,
"cost": 0,
"currency": "billing_currency",
"occurred_at": "timestamp"
}
Значения-заглушки выше нужно заменять данными вашей системы. Не записывайте в события содержимое документов, ключи API, полные промпты или персональные данные, если они не нужны для расчёта.
Шаг 3. Записывайте стоимость как журнал событий
Удобнее хранить атомарные события, а итог собирать агрегацией. Минимальная схема:
| Поле | Назначение |
|---|---|
operation_id |
Связывает все расходы одного бизнес-результата |
attempt_id |
Разделяет первоначальный запуск и повторы |
event_type |
LLM, инструмент, проверка, хранение, ручная работа |
quantity |
Токены, секунды, вызовы или минуты работы |
unit_price |
Цена единицы на момент события |
cost |
Рассчитанная стоимость события |
price_version |
Версия тарифной таблицы |
status |
Результат события или операции |
Стоимость события вычисляется по фактической единице тарификации:
event_cost = quantity × unit_price
operation_cost = Σ event_cost для одного operation_id
Если провайдер выставляет цену отдельно за вход и выход, создайте две строки или два числовых поля. Если стоимость инструмента фиксирована за вызов, количество равно числу фактических вызовов. Для ручной проверки используйте согласованную стоимость минуты и реальную длительность, а не произвольную надбавку.
Шаг 4. Не теряйте повторы и незавершённые процессы
Повтор — часть себестоимости исходной операции. Ошибка учёта возникает, когда система создаёт новый бизнес-идентификатор при каждом повторе: отчёт начинает показывать несколько дешёвых запусков вместо одной дорогой операции.
Используйте конечные статусы:
succeeded— критерий успеха подтверждён;failed— процесс завершён без результата;abandoned— продолжение прекращено внешним решением;timed_out— превышён срок операции;in_progress— финального состояния ещё нет.
Чтобы незавершённые операции не исчезали, задайте временное окно наблюдения. Например, отчёт за период можно строить по операциям, начатым до даты отсечения и уже достигшим финального статуса. Оставшиеся показывайте отдельно как незавершённые затраты.
Шаг 5. Рассчитайте три показателя
1. Полная стоимость успешной операции
cost_per_success =
total_cost_of_all_finalized_operations
/ count(succeeded_operations)
Показатель распределяет расходы неудачных запусков между фактически полученными результатами.
2. Доля успешных операций
success_rate =
count(succeeded_operations)
/ count(finalized_operations)
3. Стоимость потерь
failure_cost =
sum(operation_cost where status != "succeeded")
Дополнительно полезно считать медиану и перцентили стоимости успешных операций. Среднее может скрыть редкие, но очень дорогие циклы повторов.
Воспроизводимый расчёт в SQL
Ниже — безопасный запрос только на чтение. Он предполагает таблицу operation_events и отдельную таблицу operations с финальным статусом. Названия полей адаптируйте к своей схеме.
WITH event_costs AS (
SELECT
operation_id,
SUM(cost) AS operation_cost
FROM operation_events
WHERE occurred_at >= :period_start
AND occurred_at < :period_end
GROUP BY operation_id
),
finalized AS (
SELECT
o.operation_id,
o.status,
COALESCE(e.operation_cost, 0) AS operation_cost
FROM operations AS o
LEFT JOIN event_costs AS e
ON e.operation_id = o.operation_id
WHERE o.finished_at >= :period_start
AND o.finished_at < :period_end
AND o.status IN ('succeeded', 'failed', 'abandoned', 'timed_out')
)
SELECT
COUNT(*) AS finalized_operations,
SUM(CASE WHEN status = 'succeeded' THEN 1 ELSE 0 END)
AS succeeded_operations,
SUM(operation_cost) AS total_cost,
SUM(CASE WHEN status != 'succeeded' THEN operation_cost ELSE 0 END)
AS failure_cost,
SUM(operation_cost)
/ NULLIF(
SUM(CASE WHEN status = 'succeeded' THEN 1 ELSE 0 END),
0
) AS cost_per_success
FROM finalized;
Параметры :period_start и :period_end передавайте через механизм параметризации вашей базы данных. Не подставляйте пользовательские строки конкатенацией.
Условный пример расчёта
Это иллюстрация формулы, а не реальные данные компании или поставщика.
| Операция | Статус | Попытки | Условная стоимость |
|---|---|---|---|
| A | succeeded |
1 | 0,18 |
| B | succeeded |
3 | 0,51 |
| C | failed |
2 | 0,27 |
| D | timed_out |
1 | 0,12 |
Всего потрачено 1,08 условной денежной единицы, успешных операций — две. Следовательно:
1,08 / 2 = 0,54 на одну успешную операцию
Если считать только успешные строки, получилось бы (0,18 + 0,51) / 2 = 0,345. Такая оценка занижает реальную себестоимость, потому что исключает оплаченные неудачи.
Как проверить результат
- Сверьте сумму. Стоимость событий за период должна объяснимо сходиться с детализацией счёта поставщика. Разницу фиксируйте отдельно: округление, скидки, бесплатные лимиты или задержка биллинга.
- Найдите события без операции. У каждого оплачиваемого события должен быть
operation_idлибо явно обозначенная категория общих расходов. - Проверьте повторы. Несколько
attempt_idдолжны собираться под однимoperation_id. - Проверьте нулевые успехи. Деление должно возвращать
NULLили специальное состояние, а не ноль. - Проверьте зависшие операции. Сумма затрат
in_progressдолжна отображаться отдельно. - Выберите несколько операций вручную. Восстановите их цепочки событий и пересчитайте сумму независимо.
Полезный контроль инварианта:
общая стоимость =
стоимость успешных
+ стоимость неуспешных
+ стоимость незавершённых
+ явно распределяемые общие расходы
Как распределять общие расходы
Некоторые расходы нельзя напрямую привязать к одной операции: постоянный сервер, общий кэш, пакет мониторинга. Не прячьте их в цене токена. Выберите правило распределения и зафиксируйте его версию.
- по числу операций — если нагрузка примерно одинакова;
- по времени выполнения — если доминируют вычислительные ресурсы;
- по прямой стоимости — если инфраструктурная нагрузка следует за объёмом вызовов;
- не распределять — если показатель нужен для сравнения только переменных затрат.
Формула с накладными расходами:
fully_loaded_cost_per_success =
(direct_cost + allocated_shared_cost)
/ succeeded_operations
Типовые ошибки
Считать цену одного модельного ответа
Это стоимость технического шага, а не бизнес-результата. Она полезна для оптимизации промпта, но недостаточна для оценки процесса.
Удалять неуспешные операции из знаменателя и числителя
Исключать их из числа успехов правильно, исключать их стоимость — нет. Иначе улучшение надёжности выглядит бесплатным.
Считать повтор новой операцией
Так теряется цена достижения исходного результата. Разделяйте идентификаторы операции и попытки.
Признавать успех по ответу модели
Успех должен подтверждаться внешним условием: валидатором, сохранённой записью, выполненным действием или проверкой человека.
Использовать текущий тариф для старых событий
Храните цену или версию прайс-листа на момент вызова. Иначе исторические отчёты будут меняться задним числом.
Смешивать валюты и единицы
Перед агрегацией приведите значения к одной валюте и явно сохраните правило конвертации. Не складывайте токены, минуты и вызовы вместо их денежной стоимости.
Ограничения модели
Себестоимость успешной операции не измеряет ценность результата. Дешёвая операция может быть бесполезной, а дорогая — экономически оправданной. Для решения о продукте сопоставляйте стоимость с доходом, сэкономленным временем, риском ошибки или другой бизнес-метрикой.
Модель также зависит от качества критерия успеха. Если проверка видит только корректный формат, но не фактическую правильность, показатель будет отражать техническое завершение, а не полезный результат.
Ручной труд трудно оценить без устойчивого учёта времени. Общие инфраструктурные расходы зависят от выбранного правила распределения. Поэтому рядом с итоговой цифрой храните версию контракта операции, временное окно, валюту и метод аллокации.
Минимальный план внедрения
- Выберите одну массовую операцию с проверяемым финалом.
- Опишите контракт успеха и конечные статусы.
- Проведите
operation_idчерез модель, инструменты и валидатор. - Записывайте атомарные затраты с версией цены.
- Собирайте стоимость по операции и включайте неудачи в общий расход.
- Сверяйте агрегат с биллингом и отдельно показывайте незавершённые затраты.
- Только после проверки расширяйте схему на другие процессы.
Итоговая модель отвечает не на вопрос «сколько стоит запрос», а на более полезный: «сколько мы действительно тратим, чтобы получить один подтверждённый бизнес-результат».