Представьте счёт от поставщика модели за месяц: одна строка, одна сумма, один платёжный аккаунт. А у агентства три заказчика: интернет-магазин, у которого на сайте работает помощник по подбору товаров, юридическая фирма с сервисом разбора входящих писем и студия, которой автоматически готовятся черновики описаний. Плюс собственные эксперименты команды, которые никто не планировал, но которые тоже что-то стоят. Счёт не знает ни про магазин, ни про юристов. Проблема проявляется не тогда, когда приложения работают, а когда приходит время выставлять собственные счета: сумму нужно разложить, а раскладывать её не по чему.
Решение, о котором пойдёт речь, не требует заводить на каждого клиента отдельный аккаунт у поставщика и отдельный сервер. Достаточно одного ИИ-шлюза LiteLLM, в котором у каждого заказчика свой виртуальный ключ, своя команда и свой бюджет. Разберём, как это устроено, где легко ошибиться и как потом объяснить цифры человеку, который видит только свою часть.
Два разных секрета, которые нельзя путать
Первая и самая важная мысль: в схеме с шлюзом существуют два совершенно разных вида ключей, и они живут в противоположных местах.
Первый вид — ключ поставщика модели. Он оплачивает реальные токены, он один на всё агентство (или по одному на каждого поставщика) и хранится только на стороне шлюза. Ни один клиент, ни одно клиентское приложение этот секрет не видит и видеть не должно. Если он утечёт, утечёт возможность тратить деньги агентства без каких-либо рамок.
Второй вид — виртуальный ключ LiteLLM. Он выдаётся клиентскому приложению и работает только внутри вашего шлюза. По документации LiteLLM по виртуальным ключам, такие ключи управляют доступом к моделям, а для их хранения нужен PostgreSQL. Там же описаны создание, изменение, удаление и регенерация ключей. Для нашей задачи важно именно последнее: ключ можно выпустить, перевыпустить и отозвать, не трогая ничего у поставщика.
Разница проще всего видна на последствиях. Допустим, разработчик клиента случайно выложил ключ в публичный репозиторий. Если бы это был секрет поставщика, пришлось бы менять его во всех проектах агентства сразу, потому что он общий. Виртуальный ключ того же клиента вы просто отзываете или регенерируете, остальные заказчики даже не узнают об инциденте. Шлюз превращает один большой секрет в набор мелких, каждый из которых отзывается независимо.
Отсюда практическое правило раскладки. Секрет поставщика лежит в окружении или конфигурации шлюза и больше нигде. Клиентам уходит только адрес вашего шлюза и их собственный виртуальный ключ. Если в клиентском коде вы когда-нибудь видите ключ поставщика, схема сломана с самого начала, и никакие бюджеты этого не исправят. Если у вас на этом этапе возникают проблемы с тем, что шлюз вообще не подхватывает ключи поставщика из окружения, посмотрите разбор случаев, когда LiteLLM не видит API-ключи.
Таблица соответствия: заказчик, команда, ключ
Прежде чем создавать что-либо в LiteLLM, полезно выписать соответствия на бумаге. Это занимает десять минут и избавляет от переименований, которые потом ломают отчёт. Смысл простой: за каждой строкой будущего отчёта должен стоять один понятный объект.
Расходы в LiteLLM, как сказано в разделе об учёте затрат, отслеживаются по ключам, пользователям и командам. Бюджеты, согласно странице о бюджетах и лимитах, бывают индивидуальными, командными и внутригрупповыми. Из этих двух фактов можно собрать удобную иерархию: команда соответствует заказчику, а ключи внутри команды соответствуют отдельным приложениям или средам этого заказчика.
| Кто | Команда | Ключи внутри | Зачем так |
|---|---|---|---|
| Интернет-магазин | client-shop | shop-prod, shop-staging | Боевой и тестовый трафик считаются отдельно, но суммируются в одного заказчика |
| Юридическая фирма | client-law | law-mail-parser | Один сервис, один ключ; при необходимости добавится второй |
| Студия | client-studio | studio-drafts | Расход зависит от объёма контента, бюджет защищает от пиков |
| Агентство | internal | internal-experiments, internal-demo | Собственные запросы не смешиваются с клиентскими |
Обратите внимание на отдельную команду для внутренних экспериментов. Это не формальность. Если эксперименты агентства идут через тот же шлюз под тем же ключом, что и чей-то клиентский сервис, их стоимость незаметно попадёт в чужой отчёт. Хуже того, заказчик увидит в своей статистике запросы, которых его приложение не делало, и справедливо спросит, откуда они. Отдельная команда делает внутренний расход видимой строкой, а не серой зоной.
Имена стоит придумывать так, чтобы они читались без пояснений через год. Префикс клиента, суффикс окружения: этого достаточно. Переименовывать команды и ключи после того, как накопилась история, неприятно, поэтому лучше потратить время на согласование названий в начале.
Один ключ на клиента или несколько
Соблазнительно выдать каждому заказчику один ключ и считать задачу решённой. Для маленького проекта это работает. Но если у клиента два приложения или боевая и тестовая среды, один ключ делает их неразличимыми: непонятно, кто израсходовал бюджет, а отзыв ключа останавливает всё сразу. Несколько ключей внутри одной команды дают и детализацию, и точечный отзыв, а команда по-прежнему даёт общий итог по заказчику. Цена такого решения — чуть больше записей для сопровождения, и она невелика.
Бюджет как предохранитель, а не как счётчик
Бюджет легко воспринять как способ узнать, сколько потрачено. Но для этого есть учёт расходов. Бюджет нужен для другого: чтобы ошибка в чужом коде не превратилась в ваш убыток. Зациклившийся агент у заказчика, повторные запросы из-за неверной обработки ошибки, внезапная волна трафика — всё это без ограничителя оплачивается агентством, пока кто-нибудь не заметит.
В документации по бюджетам есть одна деталь, которую нельзя пропустить: бюджетные проверки требуют базы данных, а без неё глобальный бюджет не ограничивает запросы. Это означает, что шлюз без PostgreSQL может выглядеть полностью рабочим, принимать запросы и отвечать, но защиты от перерасхода при этом нет. Самый неприятный вариант: вы настроили бюджеты, они отображаются в конфигурации, а база не подключена. Вы уверены, что лимит стоит, а он ничего не делает. Поэтому первая проверка после запуска — убедиться, что база действительно подключена и ключи в ней появляются.
Как выбирать размер бюджета? Разумный подход — отталкиваться не от желания сэкономить, а от того, что заказчик реально платит агентству за этот сервис. Если в договоре заложена определённая сумма на модельные расходы в месяц, бюджет ключа устанавливается с небольшим запасом поверх ожидаемого расхода, а бюджет команды — на уровне договорной суммы. Тогда срабатывание лимита — это сигнал для разговора, а не катастрофа. Помните и о том, что бюджет — защитная мера, а не точный инструмент биллинга: он не заменяет сверки, о которой пойдёт речь ниже.
Разведение уровней тоже имеет смысл. Бюджет ключа ограничивает одно приложение, бюджет команды — весь заказчик. Если у магазина боевой ключ может потратить больше, а тестовый должен быть дешёвым, лимиты у них будут разными, а общий потолок команды останется единым. Так тестовый ключ с ошибкой в цикле упирается в свой маленький лимит и не съедает бюджет боевого сервиса.
Путь одного запроса до строки отчёта
Чтобы понять, откуда берутся цифры в отчёте и почему они иногда расходятся со счётом, полезно проследить запрос от начала до конца.
- Приложение магазина отправляет запрос на адрес вашего шлюза, подставляя свой виртуальный ключ shop-prod.
- Шлюз находит этот ключ в базе, определяет, к какой команде он относится, и проверяет, не исчерпан ли бюджет ключа и команды.
- Если лимит не превышен, шлюз подставляет секрет поставщика и пересылает запрос выбранной модели.
- Поставщик отвечает и сообщает объём использованных токенов.
- Шлюз по ценовой карте переводит токены в стоимость и записывает расход в базу, привязывая его к ключу и команде.
- В конце месяца отчёт — это выборка из этих записей, сгруппированная по команде и ключу.
Здесь видна роль PostgreSQL: это не побочная подробность, а единственное место, где хранится связь между запросом и заказчиком. Поставщик модели знает только о запросах с вашего общего ключа. Привязка к клиенту существует лишь в базе шлюза. Потеряете базу без копии — потеряете историю расходов по клиентам, и восстановить её по счёту поставщика уже не получится, потому что в нём этой информации никогда не было. Поэтому к базе стоит относиться как к учётным данным агентства, а не как к временному кэшу. Продумайте, как вы будете её сохранять, ещё до первого выставленного клиенту счёта.
Заметьте и другое: стоимость в записи — это расчёт шлюза, а не данные поставщика. Шлюз берёт количество токенов и умножает на цены из своей таблицы. Если таблица устарела или не совпадает с тарифом, по которому вам выставляют счёт, расчёт и счёт разойдутся. Об этом следующий раздел.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.
Почему отчёт LiteLLM не равен счёту поставщика
Рано или поздно вы положите рядом два документа и увидите разные суммы. Паниковать не нужно: в большинстве случаев причины ищутся систематически. Раздел об учёте затрат предлагает сравнивать три вещи: период, категории токенов и ценовую карту. Разберём каждую на понятных примерах.
Период
Счёт поставщика закрывается по его календарю и в его часовом поясе. Отчёт вы строите за месяц по собственному времени. Запросы, сделанные вечером последнего числа, могут оказаться в разных месяцах в двух документах. Кроме того, у поставщика расчётный период может не совпадать с календарным месяцем вообще. Первое, что стоит сделать при расхождении, — выровнять границы: взять в отчёте LiteLLM ровно тот интервал, за который выставлен счёт.
Категории токенов
Токены бывают разных видов: входные, выходные, а у некоторых моделей есть и отдельно тарифицируемые категории, например кэшированный ввод. Если поставщик считает их по разным ценам, а у вас учтён один общий тип, итог не сойдётся. Расхождение обычно систематическое: оно растёт вместе с долей запросов, где используется особая категория. Проверьте, какие категории токенов указаны в счёте, и сопоставьте их с тем, что записывает шлюз.
Ценовая карта
Шлюз считает стоимость по своей таблице цен. Если поставщик изменил тариф, а таблица не обновлена, или модель в вашем шлюзе названа так, что цена подобралась от другой модели, суммы разойдутся при абсолютно точном учёте токенов. Для моделей с договорными или нестандартными условиями цену придётся задавать самостоятельно и следить за её актуальностью.
Теперь о том, что с этим делать на практике. Не пытайтесь добиться совпадения до копейки: в учёте участвуют округления, курсы, налоги и скидки, которых шлюз может не знать. Разумнее сделать расхождение явной строкой отчёта. Назовём её нераспределённой разницей: это то, что есть в счёте поставщика, но не объяснено клиентскими и внутренними записями шлюза. Небольшая стабильная разница — норма. Резкий рост — повод искать утёкший ключ поставщика, запросы в обход шлюза или ошибку в ценовой карте.
Образец месячного отчёта
Ниже небольшой образец. Все суммы здесь условные и нужны только чтобы показать структуру: реальные числа зависят от ваших моделей и объёмов. Отчёт не претендует на бухгалтерскую точность и не заменяет учётные документы: это рабочий инструмент, чтобы честно разделить расход и вовремя заметить аномалию.
| Статья | Источник | Сумма |
|---|---|---|
| Интернет-магазин (client-shop) | Записи шлюза по команде | 120,00 |
| Юридическая фирма (client-law) | Записи шлюза по команде | 85,00 |
| Студия (client-studio) | Записи шлюза по команде | 60,00 |
| Внутренние запросы агентства (internal) | Записи шлюза по команде | 25,00 |
| Итого по данным шлюза | 290,00 | |
| Счёт поставщика за тот же период | Документ поставщика | 296,00 |
| Нераспределённая разница | Счёт минус шлюз | 6,00 |
Что здесь важно. Клиентские строки отделены от внутренней, поэтому расход агентства не пересекается с расходом заказчиков. Итог по шлюзу и счёт поставщика стоят рядом, а их разница не спрятана, а названа. Разница в данном примере небольшая, около двух процентов, и её можно просто зафиксировать как накладные расходы агентства. Если бы она составляла четверть счёта, пришлось бы разбираться: сверять периоды, категории токенов и цены, а затем проверять, не идёт ли часть трафика мимо шлюза.
Отдельный вопрос — перекладывать ли нераспределённую разницу на клиентов. Это коммерческое решение агентства, а не техническое. Технически честнее всего оставить её отдельной строкой и не размазывать пропорционально, иначе вы скроете сигнал, который эта строка несёт.
Команда — не юридическая стена
Здесь нужна трезвая оговорка. Команда в LiteLLM — это способ группировать ключи и расходы. Она удобна для учёта и ограничения трат, но сама по себе не делает данные заказчиков юридически или технически изолированными друг от друга. Все клиенты проходят через один шлюз и одну базу, и администратор шлюза видит всё. Если по договору с клиентом требуется строгая изоляция данных, одной командой это не решается, и вопрос придётся прорабатывать отдельно, вплоть до выделенного шлюза для такого клиента.
Это стоит проговорить заранее, чтобы не обещать заказчику того, чего схема не даёт. Обещать можно разделение ключей, бюджетов и учёта расходов. Обещать «полную изоляцию» на этом основании нельзя.
Где разместить шлюз
Шлюзу и базе нужна постоянно работающая машина с доступом по SSH и возможностью самостоятельно настраивать окружение. Это обычная задача для VPS: у VPS MAATRIX предусмотрены NVMe-диски, процессоры AMD EPYC и root-доступ, а в публичном предложении названы локации UK, US, RU и FR. Оплата возможна картой РФ и через СБП, а сервер создаётся автоматически через пару минут после оплаты.
По ресурсам линейка выглядит так: Ba — 1 vCPU, 2 ГБ RAM, 40 ГБ NVMe; Uab — 2 vCPU, 4 ГБ, 80 ГБ; Heka — 4 vCPU, 8 ГБ, 160 ГБ; Maat — 8 vCPU, 16 ГБ, 240 ГБ; Djed — 16 vCPU, 32 ГБ, 320 ГБ. Эти цифры описывают конфигурации, а не обещание конкретной ёмкости: сколько клиентов выдержит ваш шлюз, зависит от нагрузки, и это нужно мерить, а не угадывать. Для старта с несколькими заказчиками разумно взять небольшую конфигурацию, поставить рядом шлюз и PostgreSQL, посмотреть на реальное потребление и при необходимости перейти на более крупную. Стоит помнить, что наличие резервных копий в тарифах публично не подтверждено, поэтому копирование базы с учётными записями — ваша забота, и настраивать её лучше сразу.
Если выбранный вами ИИ-поставщик доступен из выбранной локации, определяется не размещением шлюза, а условиями самого поставщика, поэтому проверьте доступность API из нужного региона до запуска, а не после.
Пробный проход перед первым счётом
Прежде чем выставлять клиентам цифры из шлюза, проведите небольшой пробный проход. Идея в том, чтобы проверить всю цепочку на заведомо известных данных.
- Создайте тестовую команду и ключ с маленьким бюджетом.
- Отправьте через ключ несколько запросов и убедитесь, что расход появился в записях именно этого ключа и этой команды.
- Намеренно упритесь в лимит: проверьте, что при исчерпании бюджета шлюз отказывает, а не продолжает пропускать запросы. Это прямая проверка того, что база подключена и бюджетные проверки работают.
- Сравните записанные токены с тем, что показывает панель поставщика за тот же короткий интервал, и убедитесь, что цена в записи соответствует вашему тарифу.
- Удалите тестовые ключ и команду, чтобы они не попадали в отчёты.
Третий шаг самый ценный. Именно он ловит ситуацию с настроенными, но не работающими бюджетами. Лучше обнаружить её на тестовом ключе, чем на реальном клиенте.
Когда договор с клиентом закончился
Жизненный цикл ключа не заканчивается выдачей. Договор с одним из клиентов завершился, и теперь нужно аккуратно закрыть его доступ, не повредив остальным и не потеряв историю.
Порядок разумный такой. Сначала отзовите или удалите его виртуальные ключи: документация описывает и удаление, и регенерацию. С этого момента приложение клиента перестаёт получать ответы, а ваши расходы на него прекращаются. Секрет поставщика при этом менять не нужно: клиент его никогда не видел, а остальные заказчики продолжают работать без перерыва. Это прямой выигрыш от разделения двух видов ключей, о котором мы говорили в начале.
Затем сформируйте итоговый отчёт по его команде за последний период, включая дни до отзыва, и сохраните его отдельно от рабочей базы. Только после этого решайте, что делать с самой командой и записями. Удалять историю расходов сразу не стоит: по ней могут прийти вопросы по последнему счёту, а сверка с поставщиком за тот месяц ещё впереди. Сколько хранить данные, определяется договором и вашими внутренними правилами, а не настройками шлюза.
И последнее: проверьте, что у заказчика не осталось ключей, о которых вы забыли. Тестовые, временные и ключи, выданные подрядчикам клиента, легко пропустить, если они не отражены в таблице соответствий. Эта таблица, которую мы составили в начале, на этом этапе становится списком для зачистки.
Строка, которую можно показать заказчику
Итоговая проверка схемы — способность объяснить любую цифру её владельцу без раскрытия чужих данных. Вот как это выглядит для магазина из нашего примера:
За месяц приложения вашей команды client-shop через два ключа, боевой и тестовый, израсходовали 120,00 условных единиц на модельные запросы. Расчёт сделан по записям нашего шлюза и ценам выбранной модели. Лимит на месяц не превышен.
В этой строке есть всё необходимое: чьи это расходы, по каким ключам собраны, откуда взята цифра и в рамках ли бюджета. И нет ничего лишнего: ни сумм других заказчиков, ни внутренних экспериментов агентства, ни разницы со счётом поставщика, которая касается только вас. Если такую строку можно произнести вслух, не заглядывая в чужие записи, значит, ключи, команды и бюджеты расставлены правильно, а один общий счёт провайдера перестал быть загадкой.