Как один клиент незаметно съедал большую часть нашего бюджета на GigaChat Pro

Понедельник, письмо от финдира заказчика: расходы на LLM за прошлый месяц выросли в разы к предыдущему, а выручка стоит. Он спрашивает, что происходит, и по тону понятно, что ответ нужен не завтра.
Мы полезли в Grafana. Дашборд показывал ровный график: RPS в норме, латентность в норме, ошибки в норме. Общее число токенов растёт — да, но по совокупности всех тенантов картина размазана. Кто именно жжёт бюджет — непонятно. Мы обслуживали несколько десятков клиентов на одном контуре, а метринг у нас был только глобальный. Классическая ошибка «средней температуры по больнице» — и я до этого дня был уверен, что у нас с наблюдаемостью всё в порядке.
К обеду того же дня мы уже знали ответ. Один тенант забирал львиную долю всех токенов GigaChat Pro. Ещё один — заметный кусок сверху. Остальные три десятка — то, что осталось. Ниже — как мы выкатили пер-тенантный биллинг за неделю с небольшим, что нашли внутри «пожирателя», и почему теперь ни одна настройка ИИ-агента у нас не начинается без разговора о квоте.
Почему стандартный дашборд не показал проблему
В нашей архитектуре FastAPI-приложение принимает запрос, обогащает его контекстом из pgvector и уходит в GigaChat Pro. Метрики уходят в Prometheus по стандартной схеме: llm_prompt_tokens_total, llm_completion_tokens_total, llm_latency_seconds. Всё это агрегировано по эндпоинту и модели.
Ярлыка tenant_id в метриках не было. Причина банальная: когда мы запускались, клиентов было трое, и «на глаз» было ясно, кто сколько потребляет. Через год стало сильно больше — и глаз перестал работать. Ты смотришь на общий счёт, он растёт, ты киваешь: «ну, платформа ИИ-агентов растёт, всё нормально» — и не замечаешь, что рост идёт от одного клиента, а не от всех.
Первое, что мы сделали — вытянули статистику из логов за месяц. У нас логировались request_id, tenant_id и usage из ответа GigaChat. Запрос в наш аналитический слой на ClickHouse (куда льём логи через Vector) дал разбивку по тенантам за месяц. Картина оказалась максимально несимметричной: один клиент выбирал столько же, сколько все остальные, вместе взятые, и ещё немного сверху. Второй по «прожорливости» — заметно меньше, но всё равно кратно выше среднего. Хвост из трёх десятков остальных — почти шум на фоне этих двух.
Мы позвонили в аккаунт-менеджмент. «Пожиратель» — это средний холдинг, который платит по стандартному тарифу, как и остальные. Экономика по нему стала отрицательной ещё пару месяцев назад. Никто этого не заметил, потому что суммарно контур приносил прибыль.
Что жёг тенант, о котором никто не знал
Мы проассоциировали токены с эндпоинтами. Внутри этого клиента обнаружилось три очага.
Первый — RAG-запросы по договорной базе. У них несколько тысяч договоров, средний размер PDF — под сорок страниц. Chunking был настроен на 800 токенов с overlap 200. При каждом вопросе система тянула top-15 чанков — это порядка двенадцати тысяч токенов только на контекст. Юристы клиента, распробовав инструмент, спрашивали в чате очень активно, десятки раз за день на каждого пользователя.
Второй — их сотрудники нашли в интерфейсе поле «сводка по документу» и полюбили его. Каждая сводка — это подача целого документа в LLM, до нескольких десятков тысяч токенов на запрос. Никакого map-reduce или иерархической суммаризации у нас в тот момент не было — сделали «в лоб», потому что на этапе создания ИИ-агента для юротдела казалось, что этого сценария почти не будет.
Третий — интеграция с их CRM гоняла ночью классификацию карточек клиентов, партия за партией. Классификация делалась через GigaChat Pro, хотя задача решалась моделью попроще, а честно говоря — и без LLM вообще.
Все три сценария технически работали. Продукт делал то, что задумано. Просто никто не считал, во что это обходится нам. Мы продавали «готовых ИИ-агентов» по фиксированному тарифу и подразумевали среднего клиента, а средних клиентов, как обычно, в природе нет.
Как выглядит per-tenant метринг в коде
Первую попытку я честно попробовал сделать «на скорую руку»: пробросить tenant_id в существующие метрики Prometheus как ещё один label. Быстро выяснилось, что cardinality разлетается, дашборды тупят, а Prometheus начинает жаловаться на memory. Пришлось отделить оперативные метрики от учётных.
В итоге сделали так: middleware в FastAPI прокидывает tenant_id через contextvars. Дальше — обёртка над клиентом GigaChat, которая после каждого ответа пишет usage в две точки: Prometheus (только агрегаты, без tenant-разбивки, для дашбордов) и таблицу в PostgreSQL (для биллинга и квот).
Схема таблицы:
create table llm_usage (
id bigserial primary key,
tenant_id text not null,
ts timestamptz not null default now(),
model text not null,
endpoint text not null,
prompt_tokens int not null,
completion_tokens int not null,
request_id text not null,
embedding_calls int not null default 0
);
create index on llm_usage (tenant_id, ts desc);
create index on llm_usage (ts desc);
Отдельная материализованная вьюха обновляется раз в несколько минут и агрегирует по тенанту за текущий календарный месяц. На неё уже смотрят и админка, и алерты.
Дальше — квоты. Мы завели три уровня:
- Soft cap. Улетает уведомление в Telegram аккаунт-менеджеру и внутренний тикет. Клиента ещё ничего не ограничивает, но мы уже начинаем разговор.
- Hard cap. Дорогие эндпоинты (сводка документа, batch-классификация) начинают возвращать 429 с человеческим сообщением. Дешёвые операции — короткий вопрос-ответ — работают.
- Kill switch. Клиент оплачивает overage или ждёт нового биллингового периода. Работают только read-only операции без LLM.
Проверка квоты — это одна SQL-функция, которую middleware дёргает до вызова модели. Оверхед незаметный, потому что мы кэшируем ответ в Redis по ключу quota:{tenant_id} на минуту. Первую версию сделали без кэша — и получили ощутимую прибавку к латентности на горячих эндпоинтах. Ошибка, за которую мы себя быстро побили и починили.
Что мы сделали с тенантом-пожирателем
С клиентом провели разговор. Показали разбивку. Объяснили, что три сценария из пяти — это работа тяжёлой модели там, где справится лёгкая или вообще алгоритм без LLM. Реакция была нормальная: «Ребята, мы не знали, почему нам никто раньше не сказал?». Ответить было особо нечего, кроме «потому что до этого момента мы сами не знали».
Дальше — техника.
Для RAG по договорам переключили chunking на семантический с размером 400 токенов и re-ranker по BM25 поверх top-30 → top-6. Средний размер контекста заметно уменьшился, при этом качество ответов проверили на нескольких десятках отложенных вопросов — просадка оказалась небольшой, для юристов терпимой. Здесь пришлось поспорить: продакт боялся, что снижение top-k убьёт полноту. По факту re-ranker вытащил именно те чанки, которые раньше терялись в шуме.
Для сводок собрали двухуровневый map-reduce: сначала суммаризация по разделам через YandexGPT Lite, потом финальная сборка через GigaChat Pro. Средняя стоимость сводки упала в разы, а качество на слепой проверке юристы даже сочли чуть лучше — просто потому, что промежуточная суммаризация вычистила воду.
Для ночной классификации CRM подняли self-hosted rubert-tiny2 на их же контуре. GigaChat Pro для этой задачи ушёл в прошлое. Это, кстати, поучительный момент: тяжёлая LLM попала туда не потому, что её кто-то выбрал осознанно, а потому что «уже подключена, работает, давайте на ней и сделаем».
Результат по клиенту: расход токенов за следующий месяц упал в несколько раз. Экономика по нему вернулась в плюс, тарифный план не меняли, функциональность не резали.
Что теперь встроено в каждый новый контракт
Раньше квота была неявной. Теперь она — часть шаблона договора и часть онбординга: клиент видит в личном кабинете текущий расход, прогноз до конца месяца и разбивку по эндпоинтам. При подходе к пороговому уровню ему приходит уведомление, и мы вместе смотрим, где можно ужаться.
Общий счёт за GigaChat Pro по контуру за первый месяц после внедрения заметно просел. Ни один клиент не ушёл, потому что мы не резали функциональность — мы резали неоправданное потребление. От момента, когда сели за клавиатуру, до момента, когда квоты работали на всех тенантах, прошло чуть больше недели, включая миграцию исторических логов и обкатку на нескольких тестовых контурах.
Ещё один побочный эффект: разговоры с новыми клиентами стали проще. Когда обсуждаешь создание ИИ-агента под их процессы, и на столе сразу лежит модель потребления — «вот такие сценарии дешёвые, вот такие дорогие, вот здесь мы предложим квоту» — заказчик выдыхает. Он видит, что мы понимаем экономику, а не продаём чёрный ящик по подписке.
Что стоит проверить у себя сегодня
Если вы обслуживаете больше пяти клиентов на одном LLM-контуре и не имеете разбивки токенов по тенантам — вы почти наверняка субсидируете кого-то из них. Иногда — критично для маржи.
Три вопроса, на которые полезно ответить до конца квартала:
- Могу ли я за пять минут показать, какие клиенты дают основную долю LLM-расхода?
- Знаю ли я, какие эндпоинты в моей системе — самые дорогие в пересчёте на один вызов?
- Что произойдёт, если один клиент завтра начнёт слать кратно больше запросов — увидит ли это кто-то, кроме бухгалтерии в конце месяца?
Метринг стоит дёшево. Отсутствие метринга — очень дорого, и вы узнаёте цену не сразу.
Похожие статьи
- Как мы научили ИИ-агента на GigaChat Pro не путать живого клиента с ночным батчем
- Как мы настроили ИИ-агента для поиска в 1С: MCP-сервер вместо метаний между окнами
- Один вопрос — три темы: как мы научили ИИ-агента разбирать составные запросы
- Text2SQL для 340 таблиц: как мы собирали ИИ-агента для анализа корпоративных данных на GigaChat Pro




