# Oopps AI FinOps — интеграция SaaS 1. В AI FinOps → Подключения создайте отдельный источник для приложения/команды. Сохраните токен в серверном secret store. Токен не читает отчёты и не управляет каталогом. 2. В каталоге зарегистрируйте фактический model ID из ответа провайдера. Для API укажите валюту и тариф, для GPU — существующее размещение той же валюты. 3. Отправляйте один факт на фактическую попытку. Повторная доставка того же тела с тем же event_key безопасна. Изменение тела с прежним ключом возвращает 409. Не отправляйте одно обращение одновременно из gateway и приложения. 4. Проверьте последний usage, тариф и детализацию в AI FinOps. Новая модель использует существующий источник и формат. Отдельный токен провайдера Oopps не нужен: модель вызывается вашим приложением по существующему маршруту. Текущие адаптеры принимают usage текстовых Chat-моделей. Модальности image/audio, Responses-специфичные billable units и inference scheduler hooks требуют отдельной нормализации; нельзя выдавать их за поддержанные текстовые токены. ## Python: durable spool Скачайте /api/ai/client.py как oopps_ai_client.py. Клиент использует стандартную библиотеку Python; API-вызовы не перехватывает и тексты промптов/ответов не сохраняет. from datetime import datetime, timezone from oopps_ai_client import Client import os meter = Client("https://app.oopps.ai", os.environ["OOPPS_AI_TOKEN"], "usage.sqlite3") started = datetime.now(timezone.utc) # completion = ваш_клиент.chat.completions.create(...) meter.record_chat(completion, started_at=started, operation_id="document-123") # meter.flush() запускайте отдельной фоновой задачей, вне пути inference. Opaque retries SDK отключите либо инструментируйте каждую попытку отдельно. Иначе ответ последней попытки не содержит стоимость предыдущих попыток. Для streaming передавайте финальный chunk с usage и фактическими model/id. Если usage отсутствует, клиент возвращает ошибку, а не нулевую стоимость. Предусмотрите мониторинг ошибок record_chat и размера/возраста локальной очереди. SQLite-файл должен находиться на постоянном диске; одна очередь использует один токен подключения. Токен остаётся в процессе, в SQLite сохраняются только события. export OOPPS_URL=https://app.oopps.ai # OOPPS_AI_TOKEN задайте через secret store python oopps_ai_client.py flush --spool usage.sqlite3 ## Нормализованный контракт POST /api/ai/ingest/usage Authorization: Bearer Content-Type: application/json {"events": [{ "event_key": "provider-attempt-unique-id", "model": "your-served-model-id", "operation_id": "document-123", "started_at": "2026-09-11T10:00:00Z", "finished_at": "2026-09-11T10:00:03Z", "input_tokens": 1200, "cached_tokens": 200, "output_tokens": 300, "outcome": "success", "complete": true }]} Входные токены включают cached_tokens. При расчёте кэш вычитается из обычного входа: двойного начисления нет. До 500 событий/2 MiB в одном запросе. complete=false показывает неполный финансовый факт; не подставляйте вымышленные token counts. Незарегистрированная модель: 422, события остаются в очереди до настройки каталога. Тариф отсутствует: usage принимается, стоимость помечается неизвестной. ## Внутренние GPU В UI создайте размещение: владелец мощности, кластер, ставка одного GPU/час, сопутствующие ресурсы/час, валюта, доля готовности и версия профиля весов. Это неизменяемая конфигурация: для нового железа/тарифа/профиля создавайте новое размещение и модель с новой revision; прошлые данные сохраняют прежнюю стоимость. Сборщик интервалов выделенных NVIDIA GPU: python oopps_ai_client.py gpu --deployment 12 --gpu-uuids GPU-uuid1,GPU-uuid2 UUID должны принадлежать только указанному размещению. MIG/time-slicing этим сборщиком не поддерживаются. nvidia-smi требуется на машине сбора. На многонодовом serving используйте отдельное размещение на ресурсную группу и согласованный сборщик, который объединяет её фактические UUID; не копируйте общий пул на все узлы. Отказ сборщика оставляет пробел, Oopps не выдумывает интервалы за время отключения. POST /api/ai/ingest/deployments/12/intervals Authorization: Bearer <токен владельца размещения> { "event_key": "replica-a:2026-09-11T10:00:00Z", "started_at": "2026-09-11T10:00:00Z", "finished_at": "2026-09-11T11:00:00Z", "gpu_uuids": ["GPU-uuid1", "GPU-uuid2"], "gpu_utilization": 30, "usage_complete": true } usage_complete=true допускается только если доверенный сборщик подтвердил доставку всех запросов, участвовавших в интервале, включая переходящие через его границы. CLI nvidia-smi видит только железо, поэтому отправляет false: стоимость пула видна, но деление по запросам не выдаётся за подтверждённое. Для распределения используйте интеграцию serving, отправляющую usage и подтверждённые интервалы. Если факт неполный, он блокирует распределение соответствующего окна; стоимость остаётся в остатке. Стоимость интервала = (GPU_count × GPU_rate + host_rate) × duration_hours. Из неё выделяется доля готовности. Остаток распределяется по относительным весам: uncached_input + output × output_weight, с пропорцией пересечения временных окон. Это оценка token_window, НЕ измерение эксклюзивного GPU-времени запроса. Кэш-резидентность покрывается политикой готовности, кэшированные входные токены не включаются в этот базовый вес. Подтвердите веса своей нагрузочной калибровкой. При отсутствии нагрузки полная стоимость остаётся у владельца мощности. Низкая utilization не добавляет множитель к уже оплаченной стоимости GPU. Внутренние GPU показываются в AI-разделе как распределение инфраструктуры. Не суммируйте эту проекцию повторно с облачным счётом. Валюты не смешиваются. ## Сверка и бюджеты Сверьте API-расходы с фактической выгрузкой провайдера. В Подключениях добавьте разницу документом корректировки: уникальная ссылка, сумма, валюта, дата и причина. Это отдельная строка расходов; сумма счёта не добавляется поверх usage повторно. Бюджеты относятся к календарному месяцу UTC и показывают превышение в интерфейсе. Они не блокируют inference и не отправляют внешние уведомления автоматически.