Вы не знаете, сколько вам стоит каждый клиент: атрибуция AI-расходов в SaaS
Практическая схема учёта: events по использованию, денежный ledger, showback против chargeback и алармы по марже, которые находят убыточного клиента раньше, чем это сделает счёт от вендора.
Pavel Duglas
AI Automation & MVP Architect
Почти каждый AI SaaS, который я аудировал за последний год, имел одну и ту же слепую зону. Основатель знает выручку с точностью до копейки. Спрашиваю: сколько стоит обслуживать десять крупнейших аккаунтов? В ответ пожатие плечами и скриншот одного счёта от OpenAI. Это не проблема бухгалтерии. Это продуктовая проблема, потому что нельзя ни ценообразовать, ни ограничить лимитами, ни выгнать клиента, которого вы не измеряете.
Ниже схема, которую я обычно ставлю за два-три дня работы, ещё до того как трогать оптимизацию промптов и роутинг моделей. Резать расходы без атрибуции - это гадание. Сначала атрибуция, потом оптимизация.
Вопрос, на который вы сегодня не ответите
Простой тест. Возьмите пять крупнейших аккаунтов и ответьте по прошлому месяцу:
- Сколько ушло на inference по каждому аккаунту, в разбивке по моделям.
- Какая gross margin по аккаунту после inference, хранения и сторонних API.
- Какая фича самая дорогая на единицу использования.
- Сколько денег сгорело на запросах, которые упали и были повторены.
- Сколько вам стоил free tier.
Если на любой из этих вопросов нужно больше одного SQL-запроса, вы летите по приборам, которых нет. И AI-расходы ведут себя не как серверные. Серверы масштабируются от количества пользователей. LLM-расходы масштабируются от поведения пользователей, а оно разлетается на порядки. Один энтузиаст, который грузит в ваш суммаризатор 60-страничные PDF, стоит в 200 раз дороже медианного пользователя на том же тарифе за 49 долларов. Это не усредняется. Это концентрируется.
Метрить на границе, а не внутри логики
Самая частая ошибка: логирование использования размазано по бизнес-логике, по десятку мест вызова. Через шесть недель половина из них не передаёт tenant id, и цифры не сходятся ни с чем.
Сделайте одну обёртку вокруг любого платного внешнего вызова. Не только вокруг LLM. Вокруг всего, у чего есть цена: транскрибация, эмбеддинги, поисковые API, прокси для парсинга, OCR, генерация картинок. Эта обёртка - единственное место в коде, которому можно разговаривать с SDK вендора, и она всегда отдаёт usage event.
// единственная функция, которой разрешено звонить платному вендору
async function meteredCall<T>(ctx: CallContext, fn: () => Promise<VendorResult<T>>) {
const startedAt = Date.now();
try {
const res = await fn();
await usage.emit({
request_id: ctx.requestId, // ключ идемпотентности
tenant_id: ctx.tenantId,
user_id: ctx.userId,
feature: ctx.feature, // "doc_summary", "lead_enrich"
vendor: ctx.vendor, // "openai", "deepgram"
model: res.model, // что реально отработало, а не что просили
input_tokens: res.usage?.input ?? 0,
cached_input_tokens: res.usage?.cachedInput ?? 0,
output_tokens: res.usage?.output ?? 0,
units: res.usage?.units ?? 0, // минуты, страницы, изображения
attempt: ctx.attempt, // 1, 2, 3...
outcome: "ok",
latency_ms: Date.now() - startedAt,
});
return res.value;
} catch (e) {
await usage.emit({ ...ctx, outcome: classify(e), attempt: ctx.attempt });
throw e;
}
}
Две детали, которые важнее, чем кажутся.
Пишите модель, которая реально отработала. Если у вас есть роутер, fallback или провайдер, который молча подменяет snapshot, то запрошенная модель - не та, за которую вы платите. Читайте её из ответа.
Пишите номер попытки и исход. Упавшие вызовы в большинстве конфигураций всё равно стоят денег, и именно в retry прячется фантомный расход. Если считать каждую попытку как единицу использования клиента, вы его переплатите счётом. Если не считать падения вообще, ваша внутренняя себестоимость окажется ниже реальной, и маржа будет выглядеть красивее, чем есть. Держите оба числа: billable units и incurred cost - это разные колонки.
Не доверяйте собственной арифметике по токенам
Оценивать токены локальным токенайзером и умножать на прайс-лист - это место, где атрибуция тихо ломается. Что рушит наивный расчёт:
- Prompt caching. Кэшированные input-токены часто стоят в разы дешевле. Игнорируя это различие, вы завышаете себестоимость самых тяжёлых и самых кэш-дружелюбных клиентов, то есть ровно той группы, ценообразование которой вас и волнует.
- Reasoning-токены. На reasoning-моделях в output попадают токены, которых вы не видите в тексте ответа. Оценка по видимой строке недосчитывает сильно.
- Цикл с тулами. Одно действие пользователя может породить семь вызовов модели. Если метрить действие, а не вызов вендора, агенты будут казаться прибыльными ровно до прихода счёта.
- Двойной учёт на retry. Однажды я искал расхождение в 30% и нашёл его в двух retry подряд: внутренний внутри SDK и внешний в очереди задач, оба писали event. Ключ идемпотентности по request_id закрыл вопрос одним коммитом.
В конце каждого месяца сверяйте итоги своего ledger со счётом каждого вендора. Если расхождение больше 3%, не стройте на этом ценообразование. Сначала найдите протечку. Несверенный ledger - это не данные, а слухи.
От events к деньгам: ledger
Usage events сырые и объёмные. Деньги живут в отдельной, маленькой, append-only таблице.
create table cost_ledger (
id bigserial primary key,
day date not null,
tenant_id uuid not null,
feature text not null,
vendor text not null,
model text not null,
billable_units numeric not null, -- за что вы берёте деньги
incurred_cost_usd numeric not null,-- что реально заплатили, с retry
price_version text not null, -- какой прайс применялся
unique (day, tenant_id, feature, vendor, model, price_version)
);
Колонку price_version пропускают чаще всего и жалеют об этом. Цены вендоров меняются. Ваш анализ маржи за март должен воспроизводиться в сентябре. Держите прайс как данные с интервалом действия, а не как константы в коде, и штампуйте каждую строку ledger версией, по которой считали.
Сворачивайте events в ledger ночным джобом. Сырые events держите 30-90 дней для отладки, потом удаляйте. Ledger крошечный, его храните всегда.
Когда это есть, интересные запросы становятся однострочниками:
select tenant_id,
sum(incurred_cost_usd) as ai_cost,
sum(incurred_cost_usd) / nullif(mrr, 0) as cost_ratio
from cost_ledger join subscriptions using (tenant_id)
where day >= date_trunc('month', current_date)
group by tenant_id, mrr
order by cost_ratio desc
limit 20;
Этот отсортированный список - самый полезный отчёт в AI SaaS. Он говорит, с кем поговорить, кого ограничить лимитом и какой тариф посчитан неправильно.
Сначала showback, потом chargeback
У атрибуции два разных применения, и путаница между ними стоит дорого.
Showback - вы показываете клиенту, что он потребил, но счёт от этого не меняется. Внутри это же означает, что вы показываете командам и владельцам фич их расход, не перекладывая бюджеты.
Chargeback - потребление напрямую формирует счёт. Кредиты, overage, per-seat с лимитами, metered billing.
Начинайте всегда с showback. Сделайте панель использования: сколько документов обработано, сколько минут расшифровано, сколько прогонов агента, насколько израсходован лимит тарифа. Без долларов. Получите три выигрыша сразу. Клиенты сами себя регулируют, когда видят заполняющуюся полоску. Поддержка перестаёт угадывать. И вы узнаёте, корректен ли ваш учёт, до того как от него начнут зависеть деньги.
Признаки, что пора переходить к chargeback:
- У распределения cost ratio длинный хвост, и несколько аккаунтов сидят выше 40% от собственного MRR.
- Поддержка регулярно выторговывает исключения из невидимых лимитов.
- Sales не может ответить на вопрос “а что будет, если мы утроим объём в следующем квартале”.
При переходе на chargeback берите единицу, которую клиент понимает и может спрогнозировать. Токены - отвратительная единица биллинга для бизнес-покупателя. “Проанализированных страниц”, “расшифрованных звонков”, “обогащённых лидов” - хорошие. Внутри вы конвертируете единицы в деньги через ledger, наружу продаёте целые действия. Держите запас в конверсии, потому что себестоимость действия будет двигаться вместе с моделями, а переписывать прайс каждый квартал вам не захочется.
Одно правило миграции, на котором я настаиваю: гоняйте showback и chargeback параллельно минимум один полный биллинговый цикл. Показывайте клиенту счёт, который он получил бы. Ничто не сжигает доверие быстрее, чем внезапный metered-счёт, построенный на учёте, который никто ни разу не сверял.
Аларм по марже
Бюджеты, которые проверяют раз в месяц, - это не контроль, а вскрытие. Два аларма стоит поставить в первый же день:
- Дневная аномалия по тенанту. Если дневной AI-расход тенанта превышает медиану за 14 дней в 3 раза и при этом выше минимального порога в несколько долларов, дёргайте человека. Чаще всего это легальный массовый импорт. Иногда - бесконечный цикл, утёкший API-ключ или кто-то, кто использует ваш free tier как batch-пайплайн.
- Потолок расхода на фичу. У каждой фичи жёсткий дневной лимит трат. При его достижении фича деградирует аккуратно: ставит работу в очередь, переключается на более дешёвую модель или возвращает честное “мы догоняем очередь”. Ни одна фича не должна молча съесть весь месячный бюджет.
Деградируйте, а не падайте. Задача в очереди с честным ETA - нормальный продуктовый опыт. 500-я ошибка - нет.
Типичные ошибки
- Атрибуция только по пользователю. В B2B нужны tenant, user и feature. Без фичи вы знаете, кто дорогой, но не знаете почему.
- Фоновая работа без себестоимости. Ночной re-embedding, плановые прогоны агентов, прогрев кэша - всё это тоже принадлежит тенанту. Помечайте системным актором и тем тенантом, на который работает.
- Free tier без потолка. У бесплатных пользователей должен быть жёсткий лимит в коде, а не вежливая фраза в документации.
- Средние в презентации по ценам. Медианная себестоимость аккаунта почти бесполезна. Смотрите p90 и p99: именно там живут лимиты, вызывающие churn, и дырки в марже.
- Внедрение учёта после всплеска роста. Атрибуция, прикрученная в кризис, - это атрибуция, которой никто не верит. Сейчас это два дня, потом две недели.
Что сделать на этой неделе
Одна обёртка вокруг платных вызовов. Одна таблица usage events с ключом идемпотентности. Один ночной rollup в ledger с версионированным прайсом. Один запрос, сортирующий тенантов по отношению себестоимости к MRR. Одна панель showback в интерфейсе. Один аларм по аномалии в Slack.
Вот и всё. Сделайте это до того, как оптимизируете первый промпт, потому что как только вы увидите маржу по клиентам, обычно выяснится, что лечение - это смена тарифа или лимит, а не более дешёвая модель.
Вопросы и ответы
Считать себестоимость по токенам из ответа API или оценивать локальным токенайзером?
Всегда берите usage из ответа вендора и пишите его как есть, разделяя обычные и кэшированные input-токены. Локальный токенайзер годится только для предварительной оценки перед вызовом, например чтобы решить, какую модель выбрать. Он систематически врёт на reasoning-моделях, где часть output-токенов вы вообще не видите в тексте, и на кэшировании, где цена за токен другая. В конце месяца сверяйте итоги своего ledger со счётом вендора: расхождение выше 3% значит, что сначала надо искать протечку, а не строить на этих данных ценообразование.
На каком этапе стоит переходить от showback к metered-биллингу?
Когда появляются три сигнала: у вас есть аккаунты, которые съедают больше 40% своего MRR на inference, поддержка регулярно договаривается об исключениях из невидимых лимитов, и sales не может спрогнозировать себестоимость при росте объёма. До этого лучше жить на тарифах с лимитами и showback-панелью, она сама гасит значительную часть перерасхода. При переходе обязательно прогоните минимум один полный биллинговый цикл в режиме "вот счёт, который вы бы получили", чтобы поймать ошибки учёта до того, как их увидит клиент.
Куда относить расходы на фоновые задачи вроде ночного re-embedding или прогрева кэша?
На тенанта, ради которого работа делается, с отдельным системным актором в user_id и своей feature, например "nightly_reindex". Тогда в отчёте по марже такая работа не исчезает и не размывается в общих накладных. Если задача действительно общая для всех, как прогрев глобального кэша или прогон evals, ведите её как отдельного внутреннего тенанта: это ваш R&D-расход, и его полезно видеть отдельной строкой, а не смешивать с себестоимостью обслуживания клиентов.
Похожие статьи
Сделаю под ключ
Соберу платформу с кабинетами, ролями и оплатой
Личный кабинет, админка, интеграции с платежами и CRM и структура, которая переживёт вторую версию.
от 3 000 $ · 3-5 недель