Настроить cost tracking в production

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Настроить cost tracking в production

1. Цель задачи

Научиться внедрять мониторинг стоимости AI‑сервиса в production. Вы настроите сбор метрик cost_per_request, cost_per_user и cost_per_session, реализуете эмиссию этих метрик в Prometheus и построите дашборд в Grafana с детализацией по пользователям, сессиям и временным срезам.

Ключевой результат Дашборд Grafana, на котором за последние 7 дней виден breakdown затрат: общая стоимость, стоимость на пользователя (top‑N), стоимость на сессию, стоимость на запрос с возможностью фильтрации по типу модели и временному окну.


2. Исходные данные

Что нужноОткуда взять
Тестовое окружение (docker‑compose или облачный инстанс)Личная машина / арендованный сервер / Gitpod
Запросы к LLM (логи с информацией о модели, токенах, пользователе, сессии)Собранный pet‑project или CSV‑экспорт из production (обезличенный)
Prometheus + Grafanadocker‑compose или установка вручную
Python 3.10+ с библиотеками prometheus_client, fastapi (или любой HTTP‑фреймворк)Установить через pip
Данные о стоимости токенов (стоимость input/output токенов для каждой модели)API документация провайдера (OpenAI, Anthropic, Mistral и т.п.)

Если нет реального инструмента — симулируем:

  1. Запустите эмулятор LLM‑сервиса на FastAPI, который принимает запросы вида POST /chat с полями user_id, session_id, model и возвращает сгенерированный ответ (можно фиктивный).
  2. Внутри эмулятора вычисляйте количество входных/выходных токенов (например, случайное число от 50 до 500 для input и от 20 до 1000 для output).
  3. Используйте таблицу стоимости (в центах/тыс. токенов) для каждой модели (например, GPT‑4o: input 5 $ / 1M tokens, output 15 $ / 1M).
  4. На каждом запросе вычисляйте стоимость и обновляйте метрики Prometheus.

3. Технологический стек

КомпонентИнструментыНазначение
Эмиссия метрикPython + prometheus_clientФормирование и экспорт метрик cost
Хранилище метрикPrometheus (pull‑модель)Сбор, агрегация, хранение временных рядов
ВизуализацияGrafanaПостроение дашбордов с breakdown
Имитация сервисаFastAPI / FlaskПриём запросов, расчёт стоимости, вызов метрик
Контейнеризация (опционально)Docker, docker‑composeУпрощение развёртывания тестового стенда
Генератор нагрузкиlocust или python-requestsИмитация потока запросов для отладки дашборда

4. Этапы выполнения

Этап 1: Развёртывание тестового стенда и эмулятора (1.5 часа)

Действия

  1. Запустите Prometheus и Grafana через docker‑compose:
    version: '3.8'
    services:
      prometheus:
        image: prom/prometheus:latest
        ports: ["9090:9090"]
        volumes:
          - ./prometheus.yml:/etc/prometheus/prometheus.yml
      grafana:
        image: grafana/grafana:latest
        ports: ["3000:3000"]
        environment:
          - GF_SECURITY_ADMIN_PASSWORD=admin
    
  2. Создайте конфигурацию Prometheus (prometheus.yml), добавив target localhost:8000 (наш эмулятор).
  3. Напишите эмулятор LLM‑сервиса на FastAPI:
    from fastapi import FastAPI, Request
    from prometheus_client import Histogram, Counter, Gauge, generate_latest, REGISTRY
    import random, time
    
    app = FastAPI()
    
    # Метрики
    cost_counter = Counter('llm_total_cost_usd', 'Total cost in USD', ['model', 'user_id'])
    cost_per_request = Histogram('llm_cost_per_request_usd', 'Cost per request in USD',
                                 buckets=(0.0001, 0.001, 0.01, 0.1, 1.0))
    # Добавить cost_per_user и cost_per_session — через Gauge или Summary
    
    COST_TABLE = {
        'gpt-4o': {'input_per_1k': 0.005, 'output_per_1k': 0.015},
        'gpt-4o-mini': {'input_per_1k': 0.00015, 'output_per_1k': 0.0006}
    }
    
    @app.post("/chat")
    async def chat(user_id: str, session_id: str, model: str, input_text: str):
        input_tokens = len(input_text.split()) + random.randint(20, 100)
        output_tokens = random.randint(20, 1000)
        price = COST_TABLE.get(model, COST_TABLE['gpt-4o'])
        cost = (input_tokens * price['input_per_1k'] + output_tokens * price['output_per_1k']) / 1000.0
        # Обновляем метрики
        cost_counter.labels(model=model, user_id=user_id).inc(cost)
        cost_per_request.observe(cost)
        # Для cost_per_user и cost_per_session используем gauge с уникальными лейблами
        # (подробнее в следующем этапе)
        return {"cost": cost, "text": "Simulated response"}
    
  4. Проверьте, что метрики доступны: откройте http://localhost:8000/metrics.
  5. Отправьте несколько тестовых запросов через curl или Python.

Ожидаемый результат этапа Prometheus забирает метрики с эндпоинта /metrics; есть базовый llm_total_cost_usd с лейблами model и user_id.


Этап 2: Разработка метрик cost per user, cost per session и cost per request (2 часа)

Действия

  1. Добавьте метрику llm_cost_per_user_usd как Gauge с лейблом user_id. Идея: суммарная стоимость пользователя за последние N минут (скользящее окно). Поскольку в Prometheus сложно хранить скользящие суммы без записи всех событий, используем агрегацию на стороне эмитента: в памяти храним словарь стоимости на пользователя за последние 5 минут, обновляем при каждом запросе. Для простоты можно просто использовать Gauge, который устанавливается в полную стоимость, посчитанную через counter. Но лучше сделать отдельный Gauge, который обновляем из накопленного словаря раз в 30 секунд.

    from collections import defaultdict
    import threading, time
    
    user_costs = defaultdict(float)
    session_costs = defaultdict(float)
    lock = threading.Lock()
    
    # Добавить в /chat:
    with lock:
        user_costs[user_id] += cost
        session_costs[user_id + ":" + session_id] += cost
    

    Запустить фоновый поток:

    def update_gauges():
        while True:
            time.sleep(30)
            with lock:
                for uid, total in user_costs.items():
                    gauge_user.labels(user_id=uid).set(total)
                for sess_id, total in session_costs.items():
                    gauge_session.labels(session_id=sess_id).set(total)
    
  2. Создайте метрику llm_cost_per_session_usd с лейблом session_id. Аналогично.

  3. Для cost_per_request уже есть Histogram из Этапа 1. Дополнительно создайте Summary с квантилями (p50, p90, p99) для отчётности.

  4. Добавьте метрику llm_cost_per_user_histogram — распределение стоимости на пользователя (для анализа выбросов).

  5. Настройте очистку старых данных: в фоновом потоке каждые 5 минут удаляйте из словарей записи, которые не обновлялись > 10 минут (чтобы не текла память).

Ожидаемый результат этапа На /metrics появляются все три ключевые метрики: llm_cost_per_user_usd, llm_cost_per_session_usd, llm_cost_per_request_usd (Histogram).


Этап 3: Создание дашборда в Grafana (2.5 часа)

Действия

  1. Подключите Prometheus datasource в Grafana.

  2. Создайте dashboard с названием "Cost TrackingLLM Production".

  3. Добавьте панели

    ПанельМетрика / запросТип визуализации
    Total cost (last 24h)sum(rate(llm_total_cost_usd[24h])) или increaseStat / Big number
    Cost breakdown by modelsum(increase(llm_total_cost_usd[24h])) by (model)Bar chart
    Top‑N users by costtopk(10, llm_cost_per_user_usd)Table / Bar gauge
    Cost per session — top 10topk(10, llm_cost_per_session_usd)Table
    Cost per request distribution (p50,p90,p99)histogram_quantile(0.5, sum(rate(llm_cost_per_request_usd_bucket[5m])) by (le)) etc.Stat / Graph
    Cost per user over timeavg(llm_cost_per_user_usd) by (user_id) — select few usersTime series
  4. Настройте переменные для удобства: $model (list from label_values), $user (list from label_values(llm_cost_per_user_usd, user_id)).

  5. Добавьте аннотации о деплоях (если есть).

  6. Проверьте визуализацию: сгенерируйте нагрузку (скрипт на Python, шлёт 100–500 запросов с разными user_id и session_id).

Ожидаемый результат этапа Дашборд показывает все ключевые метрики, фильтры работают, данные обновляются в реальном времени.


Этап 4: Тестирование и валидация корректности (1 час)

Действия

  1. Напишите юнит-тесты для эмиттера метрик (pytest + prometheus_client test utilities):
    • Проверьте, что counter увеличивается на ожидаемую величину.
    • Проверьте, что gauge устанавливается корректно.
  2. Проведите нагрузочное тестирование (Locust): 50 concurrent users, 10 минут. Убедитесь, что метрики не теряются, нет ошибок в эмиттере.
  3. Перекрёстно проверьте стоимость: вручную посчитайте стоимость нескольких запросов и сравните с данными в Prometheus (/api/v1/query).
  4. Проверьте, что сессии корректно закрываются: отправьте несколько запросов с одной сессией, затем закончите сессию — в дашборде стоимость за сессию должна остаться неизменной.

Ожидаемый результат этапа Все метрики корректны, тесты проходят, нет утечек памяти.


Этап 5: Документирование и подготовка к production (0.5 часа)

Действия

  1. Напишите README со структурой репозитория, инструкцией по запуску, описанием метрик и дашборда.
  2. Экспортируйте дашборд в JSON (встроенная функция Grafana) — сохраните в dashboards/cost_tracking.json.
  3. Добавьте примеры PromQL для типовых запросов (cost per user per day, cost per model per hour).
  4. Задокументируйте, как добавить новую модель (добавить строчку в COST_TABLE и обновить дашборд).

Ожидаемый результат этапа Репозиторий содержит код, конфигурацию, дашборд и инструкции; любой инженер может развернуть и подключить свой сервис.


5. Критерии приемки (Definition of Done)

  • В Prometheus появляются метрики llm_total_cost_usd, llm_cost_per_user_usd, llm_cost_per_session_usd, llm_cost_per_request_usd.
  • Дашборд Grafana отображает breakdown затрат по моделям, пользователям (top‑10) и сессиям (top‑10).
  • Метрика llm_cost_per_request_usd является гистограммой, по которой можно вычислить p50, p90, p99.
  • Все метрики имеют лейблы model, user_id (кроме per_session — там session_id).
  • При нагрузке 100 запросов/сек не возникает ошибок в эмиттере и Prometheus не теряет данные (check on prometheus_tsdb_head_series).
  • Есть юнит-тесты для расчёта стоимости и обновления gauges.
  • Дашборд экспортирован в JSON и включён в репозиторий.
  • README содержит инструкцию по запуску и список метрик.

6. Ожидаемый результат

  • Код эмулятора/эмиттера (emulator.py или cost_tracker.py) с FastAPI-сервером.
  • Конфигурация Prometheus (prometheus.yml).
  • Дашборд Grafana (dashboards/cost_tracking.json).
  • README с описанием архитектуры и инструкцией.
  • Юнит-тесты (опционально, но желательно).
  • Скрипт генерации нагрузки (например load_test.py).

Дополнительно: документация по добавлению новых моделей и интерпретации метрик.


7. Возможные сложности и их решение

СложностьРешение
Высокая кардинальность лейблов (user_id может быть миллионы) — Prometheus начнёт тормозитьИспользовать summary и histogram без уникальных лейблов, а для per_user/per_session — агрегировать в batch и записывать как Gauge с ограниченным числом лейблов (только активные пользователи за последние N минут).
Несоответствие стоимости во времени (цены меняются)Хранить версию таблицы стоимости в отдельном gauge метрике llm_cost_table_version и при изменении инвалидировать старые данные. В дашборде можно добавить аннотации о смене цен.
Переполнение памяти при хранении словарей сессийИспользовать TTL-словарь (например, cachetools.TTLCache) или периодически очищать по последнему обновлению.
Гистограмма cost_per_request создаёт много временных рядовИспользовать buckets, адекватные распределению стоимости (например, экспоненциально до 1$).
Prometheus не забирает метрики, если сервис перезагружаетсяДобавить job_name: 'cost-emitter' и настроить scrape_interval на 5–10 секунд.

8. Бюджет времени (оценка)

ЭтапВремя (часы)
Этап 1: Развёртывание стенда и эмулятора1.5
Этап 2: Разработка метрик cost per user/session2.0
Этап 3: Создание дашборда Grafana2.5
Этап 4: Тестирование и валидация1.0
Этап 5: Документирование0.5
Итого7.5 часов

Примечание При первом выполнении может потребоваться до 10 часов с учётом изучения инструментов.


9. Связанные вопросы из базы знаний

ВопросТема
12Как организовать мониторинг в ML‑системе?
45Основные метрики для LLM‑сервиса
78PromQL: агрегация с группировкой
123Настройка Grafana для production
200Управление кардинальностью лейблов
345Оценка стоимости токенов разных моделей
456Сквозное логирование и трассировка запросов
567A/B тестирование моделей и cost analysis
678Best practices по дашбордам для инженеров
789Alerting на основе cost (аномальные расходы)

10. Чек-лист самопроверки

  • Я запустил docker‑compose с Prometheus и Grafana, убедился, что метрики собираются.
  • Я реализовал эмитлер с метриками llm_total_cost_usd, llm_cost_per_user_usd, llm_cost_per_session_usd и llm_cost_per_request_usd.
  • Я проверил, что в дашборде корректно отображаются top‑N пользователей и сессий по стоимости.
  • Я написал хотя бы один тест, проверяющий расчёт стоимости.
  • Я экспортировал дашборд в JSON и добавил в репозиторий.
  • Я прочитал логи и убедился, что нет ошибок при высокой нагрузке (100+ rps).
  • Я задокументировал, как добавить новую модель и как интерпретировать метрики.