Мониторинг расходов и юнит-экономика LLM-приложений

35 минут Урок 33

Введение: От прототипа к банкротству (и как этого избежать)

Добро пожаловать в один из самых отрезвляющих модулей нашего курса. До сих пор мы говорили о том, как заставить Gemini 3 делать удивительные вещи: рассуждать, видеть, анализировать огромные объемы данных. Мы были сосредоточены на качестве ответа.

Сегодня мы поговорим о цене этого ответа.

Существует классический сценарий «LLM-похмелья»: команда разработчиков создает потрясающий PoC (Proof of Concept) на базе самой мощной модели (например, Gemini Ultra или 1.5 Pro). Все работает идеально. Вы выкатываете продукт в продакшн. Пользователи в восторге, трафик растет экспоненциально. А в конце месяца вы получаете счет от Google Cloud, который превышает вашу выручку в три раза.

Юнит-экономика LLM-приложений — это не просто бухгалтерская задача. Это инженерная проблема. В отличие от традиционного софта, где предельные издержки на нового пользователя стремятся к нулю, в мире GenAI каждый запрос стоит реальных денег. Каждый токен — это вычислительная мощность GPU/TPU.

В этом уроке мы разберем:

  • Как на самом деле формируется цена в Gemini 3 (это сложнее, чем просто «за 1000 слов»).
  • Как технически реализовать мониторинг расходов в реальном времени.
  • Что такое «смешанная экономика» и каскадные вызовы.
  • Как использовать Context Caching для снижения затрат на 90% в RAG-системах.

Анатомия ценообразования Gemini 3

Чтобы управлять расходами, нужно понимать физику процесса. Цена в Gemini 3 складывается не линейно. Давайте деконструируем основные метрики.

1. Input vs. Output Tokens

Это база, но многие упускают важную деталь: генерация (Output) обычно стоит в 3-4 раза дороже, чем чтение (Input).

Почему это важно для архитектуры? Если вы просите модель «переписать этот текст в 10 разных стилях», вы генерируете много дорогих выходных токенов. Если же вы просите «проанализировать 100 страниц и выдать 'Да' или 'Нет'», вы загружаете дешевый вход, а выход у вас минимальный. Вторая архитектура всегда будет дешевле при масштабировании.

2. Мультимодальность

Gemini 3 — нативная мультимодальная модель. Когда вы отправляете изображение или видео, API не «видит» картинку как файл. Он конвертирует её в эквивалентное количество токенов.
Например, изображение HD-качества может «весить» как 258 токенов (число условное и зависит от конкретной версии модели), а минута видео — как тысячи токенов. Без учета этого вы можете случайно сжечь бюджет, просто разрешив пользователям загружать видеофайлы без предварительной обработки (даунскейлинга или нарезки кадров).

3. Контекстное окно (Context Window)

Огромное контекстное окно Gemini (до 1-2 млн токенов) — это палка о двух концах. Технически вы можете загрузить в промпт целую книгу при каждом запросе. Но если вы делаете это для каждого сообщения в чате (stateless подход), вы будете платить за обработку этой книги снова и снова. Здесь на помощь приходит Context Caching, о котором мы поговорим ниже.

python
import google.generativeai as genai
import os

# Базовая настройка
genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
model = genai.GenerativeModel('gemini-1.5-pro-latest') # Используем актуальную версию

# Пример запроса с получением метаданных использования
response = model.generate_content("Объясни принцип работы квантового компьютера в одном абзаце.")

# В Gemini API метаданные о токенах находятся в атрибуте usage_metadata
usage = response.usage_metadata

print(f"Входные токены (Input): {usage.prompt_token_count}")
print(f"Выходные токены (Output): {usage.candidates_token_count}")
print(f"Всего токенов: {usage.total_token_count}")

# Пример простого расчета стоимости (цены условные, сверяйтесь с прайсом!)
# Допустим: Input $3.50 / 1M, Output $10.50 / 1M
input_price_per_1m = 3.50
output_price_per_1m = 10.50

cost = (usage.prompt_token_count / 1_000_000 * input_price_per_1m) + \
       (usage.candidates_token_count / 1_000_000 * output_price_per_1m)

print(f"Стоимость запроса: ${cost:.6f}")

Создание системы мониторинга (Observability)

Просто знать стоимость одного запроса недостаточно. В продакшене вам нужна агрегированная статистика. Главная ошибка новичков — смотреть на общий счет в конце месяца. Правильный подход — атрибуция затрат.

Вы должны уметь отвечать на вопросы:

  • Сколько денег тратит конкретный пользователь (User ID)?
  • Какая фича в продукте самая дорогая (Feature ID)?
  • Сколько стоит один успешный кейс (например, одна успешно сгенерированная статья)?

Паттерн "Proxy Logger"

Не разбрасывайте вызовы `generate_content` по всему коду. Создайте обертку (Wrapper), через которую будут проходить все запросы к LLM. Это единая точка входа для логирования, подсчета токенов и управления ретраями.

В базе данных логов у вас должны быть поля:

  • timestamp
  • model_version (цены на разные версии отличаются)
  • prompt_tokens
  • completion_tokens
  • latency_ms
  • feature_tag (например, "chatbot", "summarizer")
  • user_id
  • cost_usd

python
import logging
from dataclasses import dataclass
from datetime import datetime

# Структура для хранения цен (лучше вынести в конфиг или БД)
PRICING = {
    "gemini-1.5-flash": {"input": 0.35, "output": 1.05}, # за 1M токенов
    "gemini-1.5-pro": {"input": 3.50, "output": 10.50},  # за 1M токенов
}

@dataclass
class LLMRequestLog:
    timestamp: datetime
    model: str
    input_tokens: int
    output_tokens: int
    cost: float
    user_id: str
    feature: str

class LLMService:
    def __init__(self, model_name="gemini-1.5-flash"):
        self.model_name = model_name
        self.model = genai.GenerativeModel(model_name)
        self.logger = logging.getLogger("LLM_COST_TRACKER")

    def _calculate_cost(self, input_tok, output_tok):
        # Получаем цены для текущей модели
        prices = PRICING.get(self.model_name, {"input": 0, "output": 0})
        
        input_cost = (input_tok / 1_000_000) * prices["input"]
        output_cost = (output_tok / 1_000_000) * prices["output"]
        
        return input_cost + output_cost

    def generate(self, prompt, user_id, feature_tag):
        # Вызов API
        response = self.model.generate_content(prompt)
        
        # Извлечение метрик
        usage = response.usage_metadata
        cost = self._calculate_cost(usage.prompt_token_count, usage.candidates_token_count)
        
        # Логирование (в реальности - запись в БД или отправка в Datadog/Prometheus)
        log_entry = LLMRequestLog(
            timestamp=datetime.now(),
            model=self.model_name,
            input_tokens=usage.prompt_token_count,
            output_tokens=usage.candidates_token_count,
            cost=cost,
            user_id=user_id,
            feature=feature_tag
        )
        
        # Демонстрация лога
        print(f"LOG: User {user_id} spent ${cost:.6f} on {feature_tag}")
        
        return response.text

# Использование
service = LLMService(model_name="gemini-1.5-flash")
service.generate("Привет!", user_id="u_123", feature_tag="chat_intro")

Юнит-экономика: CPQ против LTV

Теперь перейдем к бизнесу. Есть две ключевые метрики:

  1. CPQ (Cost Per Query) — Сколько стоит один запрос.
  2. CPU (Cost Per User) — Сколько стоит обслуживание пользователя в месяц.

Ваша задача — убедиться, что LTV (Lifetime Value) > CPU с достаточным запасом (обычно в 3-4 раза).

Проблема "Безлимита"

Если у вас SaaS с фиксированной подпиской (например, $20/мес), а пользователь имеет безлимитный доступ к модели уровня Gemini Ultra, вы в зоне риска. Активные пользователи («Power Users») могут генерировать трафик на $50/мес, убивая маржинальность.

Решения:

  • Система квот: 50 запросов к Pro-модели в день, далее переключение на Flash-модель.
  • Смешанная модель (Model Cascading): Для простых запросов («Напиши письмо») используйте Gemini Flash. Для сложных («Проанализируй юридический договор») — Gemini Pro. Это снижает средний CPQ на 60-70%.

Оптимизация: Context Caching и сжатие промптов

Gemini предоставляет мощнейший инструмент экономии для RAG (Retrieval Augmented Generation) и чат-ботов с длинной историей — Context Caching.

Представьте, что у вас есть юридический бот, который отвечает на вопросы по Гражданскому кодексу. Кодекс огромный. Без кэширования вы каждый раз отправляете весь текст кодекса (или большие его куски) на вход модели. Вы платите за обработку этих входных токенов каждый раз.

С Context Caching вы отправляете кодекс один раз, получаете идентификатор кэша и платите:

  1. Один раз за полную обработку.
  2. Небольшую стоимость за хранение кэша (за час).
  3. При последующих запросах — вы НЕ платите за повторную обработку кэшированных токенов, только за новые токены промпта пользователя.

Это меняет экономику длинных контекстов кардинально. Если контекст используется часто (например, база знаний компании), экономия достигает порядков.

python
import datetime

# Пример создания кэша (концептуальный код)

# 1. Загружаем большой документ
document_path = "big_manual.txt"
# ... код загрузки файла ...

# 2. Создаем кэш
cache = genai.caching.CachedContent.create(
    model="models/gemini-1.5-pro-001",
    display_name="company_manual_cache",
    system_instruction="Ты помощник техподдержки. Используй этот мануал.",
    contents=[document_blob], # Контент мануала
    ttl=datetime.timedelta(minutes=60) # Время жизни кэша
)

print(f"Кэш создан: {cache.name}")

# 3. Использование кэша в запросах
model = genai.GenerativeModel.from_cached_content(cached_content=cache)

# Этот запрос будет ОЧЕНЬ дешевым, так как мануал не обрабатывается заново
response = model.generate_content("Как перезагрузить роутер согласно инструкции?")
print(response.usage_metadata)
Упражнение

Рассчитайте экономику внедрения RAG-бота.<br><br>Дано:<br>1. Бот техподдержки использует базу знаний объемом 500,000 токенов.<br>2. Ежедневно поступает 1000 запросов.<br>3. Модель: Gemini 1.5 Pro.<br>4. Цена Input: $3.50 / 1M токенов.<br>5. Цена кэшированного Input: $0.35 / 1M токенов (скидка 90% на повторные вызовы).<br>6. Стоимость хранения кэша: $1.00 / 1M токенов в час.<br><br>Задание: Рассчитайте дневные затраты для двух сценариев:<br>А) Без кэширования (каждый запрос отправляет 500k токенов).<br>Б) С кэшированием (база загружается 1 раз, обновляется раз в сутки).<br><br>(Не учитывайте Output токены для простоты).

Вопрос

Вы разрабатываете функцию 'Summary' для длинных PDF-документов пользователей. Пользователи загружают документ один раз, а потом задают по нему 5-10 вопросов. Какая стратегия будет наиболее экономически эффективной?

Заключение

Мониторинг расходов и юнит-экономика — это то, что отличает «игрушечный» проект от устойчивого бизнеса. Инструменты Gemini 3, такие как usage_metadata и Context Caching, дают вам полный контроль над ситуацией.

Ваш чек-лист перед выходом в прод:

  • [ ] Настроено логирование токенов и стоимости для каждого запроса.
  • [ ] Реализованы алерты (уведомления) при превышении дневного бюджета.
  • [ ] Для повторяющегося контента (более 32k токенов) настроено Кэширование Контекста.
  • [ ] Реализована логика выбора модели (Flash для простых задач, Pro для сложных).

В следующем уроке мы поговорим о безопасности и защите от промпт-инъекций, что также косвенно влияет на расходы (защита от злоумышленников, пытающихся «выкачать» ваш бюджет).