Реализовать cost-aware routing

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Реализовать cost-aware routing

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

Разработать и внедрить систему интеллектуальной маршрутизации запросов в AI-сервисе: простые запросы обрабатываются дешёвой моделью (например, GPT-3.5-turbo), сложные — дорогой (GPT-4o). Необходимо построить классификатор сложности запроса, интегрировать его в пайплайн и оценить экономический эффект при сохранении качества ответов.

Ключевой результат Снижение затрат на LLM-инференс на ≥50% при падении accuracy не более чем на 5% относительно baseline (только дорогая модель).


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

Перед началом необходимо иметь:

Что нужноОткуда взять
Набор запросов пользователей (≥1000)Логи production / синтезировать из датасетов (Alpaca, ShareGPT)
Ответы дешёвой и дорогой модели на каждый запросСгенерировать через API OpenAI / локальные модели
Оценка качества ответов (human или LLM-as-judge)Ручная разметка 200 примеров + автоматическая оценка для остальных
Baseline метрики (стоимость, accuracy)Запуск всей выборки через дорогую модель
Классификатор сложности (прост/сложен)Обучить на размеченных данных (логистическая регрессия / fastText / BERT)
Инфраструктура маршрутизацииFastAPI / Flask + Redis / RabbitMQ для очередей

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

  1. Собрать 2000 запросов из открытых источников: Alpaca (500), GSM8K (500, как сложные), Simple QA (500, как простые), TriviaQA (500, смесь).
  2. Разметить «сложность» вручную: запросы, требующие многошаговых рассуждений или внешних данных → сложные; фактические короткие → простые. Цель: 40% сложных, 60% простых.
  3. Сгенерировать ответы обеих моделей на все запросы (использовать бюджетный аккаунт OpenAI или локальные модели через Ollama).
  4. Оценить accuracy: сравнить ответы каждой модели с эталонным ответом (LLM-as-judge с промптом) — считать долю совпадений по смыслу.

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

КомпонентИнструментыНазначение
LLM APIOpenAI API (gpt-4o, gpt-3.5-turbo) / локальные модели через OllamaГенерация ответов для датасета
Классификаторscikit-learn (LogisticRegression, RandomForest) / fastText / BERT (transformers)Определение сложности запроса
Оценка качестваLLM-as-judge (gpt-4o-mini) + ручная валидацияМетрика accuracy
Сервис маршрутизацииFastAPI + pydanticAPI-слой для приёма запросов
Очередь (опционально)Redis / RabbitMQБуферизация при высокой нагрузке
МониторингPrometheus + GrafanaСбор метрик: стоимость, latency, accuracy
ЭкспериментыMLflow / Weights & BiasesЛогирование параметров и метрик
CI/CDGitHub Actions + DockerАвтоматизация тестов и деплоя

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

Этап 1: Сбор и разметка датасета (4–6 часов)

Действия

  1. Сформировать пул из 2000 запросов (реальные или синтетические).
    Пример распределения:

    • 500 простых (фактовые, односложный ответ: "Какая столица Франции?")
    • 500 средних (требуют 2–3 шага: "Почему небо голубое?")
    • 1000 сложных (многошаговые, цепочки рассуждений: "Реши уравнение x²+2x+1=0 и объясни шаги")
  2. Для каждого запроса сгенерировать ответ через дешёвую модель (gpt-3.5-turbo) и дорогую (gpt-4o).

    • Использовать промпт: Answer concisely but correctly.
    • Записать в CSV: id, query, cheap_answer, expensive_answer, true_label (прост/сложен)
  3. Оценить accuracy:

    • Для 200 случайных запросов — ручная разметка (два аннотатора, Fleiss' kappa >0.7).
    • Для остальных — LLM-as-judge: промпт "Оцени, эквивалентны ли ответы по смыслу? Ответь 1 (да) или 0 (нет). Ответ А: ..., Ответ Б: ...".
    • Итоговая accuracy = доля ответов, признанных эквивалентными.
  4. Baseline: accuracy дорогой модели = 1.0 (эталон), стоимость = сумма токенов × цена.

Ожидаемый результат этапа CSV-файл dataset.csv с колонками: id, query, cheap_answer, expensive_answer, true_label, accuracy_cheap, accuracy_expensive. Baseline-метрики.


Этап 2: Разработка классификатора сложности (4–6 часов)

Действия

  1. Преобразовать запросы в признаки:

    • Длина запроса (число токенов)
    • Наличие вопросительных слов (почему, как, объясни, в чём разница)
    • Число глаголов/существительных
    • Эмбеддинг (sentence-transformers/all-MiniLM-L6-v2) + PCA до 50 компонент
  2. Обучить baseline-модель (LogisticRegression) на 80% данных, 20% — тест.
    Метрика: F1-score на классе "сложный".

  3. Если F1 <0.85 — попробовать RandomForest или fastText (n-граммы).

  4. Сохранить модель в pickle/ONNX.

Expected code snippet

from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression

model = Pipeline([
    ('tfidf', TfidfVectorizer(max_features=5000, ngram_range=(1,3))),
    ('clf', LogisticRegression(class_weight='balanced'))
])
model.fit(train_queries, train_labels)
  1. Оценить latency классификатора: <10ms на запрос (CPU).

Ожидаемый результат этапа Сохранённая модель классификатора + отчёт с precision/recall/F1.


Этап 3: Интеграция маршрутизации в сервис (3–4 часа)

Действия

  1. Создать FastAPI-приложение с одним endpoint POST /chat:

    • Принимает { "query": str }
    • Классифицирует запрос (прост/сложен)
    • Вызывает соответствующую LLM (дешёвую или дорогую)
    • Возвращает { "response": str, "model": str, "cost": float }
  2. Реализовать логирование: запись запроса, выбранной модели, времени ответа, стоимости.

  3. Добавить health check и metrics endpoint для Prometheus.

  4. Написать unit-тесты (pytest):

    • Корректность выбора модели для заведомо простых/сложных запросов
    • Обработка ошибок (timeout, недоступность API)

Ожидаемый результат этапа Рабочий микросервис с маршрутизацией, запускаемый через docker compose up.


Этап 4: Оценка экономического эффекта (2–3 часа)

Действия

  1. Прогнать весь датасет (2000 запросов) через сервис с routing.
    Записать: какие запросы пошли на дешёвую, какие на дорогую модель.

  2. Подсчитать:

    • Общая стоимость = sum(cost per request)
    • Accuracy = доля ответов, признанных эквивалентными дорогой модели (baseline)
      (если запрос пошёл на дорогую — accuracy = 1; если на дешёвую — accuracy из предыдущих оценок)
    • Сравнить с baseline (100% дорогой модели).
  3. Построить ROC-кривую выбора модели (чувствительность к сложности) — порог вероятности сложности, при котором направляем на дорогую модель.
    Выбрать порог, дающий accuracy ≥0.95 от baseline.

  4. Визуализировать: scatter plot «стоимость vs accuracy» для разных порогов.

Expected output table

Порог вероятности% запросов на дешёвуюСнижение стоимостиAccuracy (отн. baseline)
0.555%-52%0.97
0.340%-38%0.99
0.770%-65%0.93

Ожидаемый результат этапа Подтверждение гипотезы: найдена конфигурация с −50% cost при accuracy −5% или лучше.


Этап 5: Документирование и развёртывание (2–3 часа)

Действия

  1. Оформить README с описанием архитектуры, инструкцией по запуску, benchmark-результатами.

  2. Упаковать в Docker-образ (FastAPI + модель) и docker-compose.yml (сервис + Redis + Grafana).

  3. Написать post-deployment тесты: запросы разной сложности, проверка выбора модели.

  4. Создать дашборд Grafana: стоимость в день, accuracy rolling average (7 дней), latency p99.

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


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

  • Система маршрутизации обрабатывает ≥1000 запросов в сутки без ошибок.
  • Классификатор сложности имеет F1-score ≥0.85 на тестовой выборке.
  • Latency классификатора <15ms (p99).
  • Стоимость инференса снижена на ≥50% относительно baseline.
  • Accuracy системы (относительно дорогой модели) упала не более чем на 5%.
  • Написаны unit-тесты для классификатора и эндпоинта (≥80% coverage).
  • Дашборд Grafana показывает: cost per request, accuracy, distribution of model choice.
  • README содержит инструкцию по запуску и воспроизведению результатов.
  • Реализовано логирование каждого запроса (время, модель, стоимость).
  • Код проходит code review и форматирован (black, isort).

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

Основной артефакт: репозиторий GitHub с кодом сервиса cost-aware routing.
Содержимое:

  • app/FastAPI-приложение с routing
  • model/ — сохранённый классификатор (pickle/ONNX)
  • data/ — датасет (синтетический или реальный)
  • notebooks/ — Jupyter-ноутбук с обучением и оценкой
  • tests/pytest-тесты
  • docker-compose.yml — окружение
  • README.md — результаты сравнения стоимости и accuracy
  • dashboard.json — экспорт дашборда Grafana

Дополнительно: артефакт в MLflow с метриками эксперимента (лучшая конфигурация порога).


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

СложностьРешение
Нет реального production-датасетаИспользовать публичные датасеты (Alpaca, GSM8K, TriviaQA) с разметкой сложности по числу шагов рассуждений
Классификатор не достигает F1 >0.8Добавить больше признаков (эмбеддинги BERT, тематические словари); использовать XGBoost или distilbert
LLM-as-judge даёт несогласованные оценкиИспользовать тот же промпт с few-shot примерами; усреднять по 3 запускам; для 200 примеров сделать ручную валидацию
Высокая задержка из-за вызова дорогой модели на сложных запросахОптимизация: кэшировать ответы на частые запросы (Redis), асинхронный вызов
Дорогая модель может быть недоступнаRetry + fallback на дешёвую модель с логом ошибки
Перекос классов в датасете (больше простых)Использовать class_weight='balanced' или аугментацию сложных запросов

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

ЭтапВремя
Этап 1: Сбор и разметка датасета4–6 ч
Этап 2: Разработка классификатора4–6 ч
Этап 3: Интеграция маршрутизации3–4 ч
Этап 4: Оценка экономического эффекта2–3 ч
Этап 5: Документирование и развёртывание2–3 ч
Итого15–22 ч

Примечание: время указано для первого выполнения с возможными повторениями. При наличии готовых датасетов и инфраструктуры можно уложиться в 10–12 ч.


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

ВопросТема
41Cost optimization for LLM inference
42Model selection criteria (quality vs cost)
43Caching strategies for LLM responses
44Performance benchmarking of language models
45Cost-aware architecture patterns
112Logistic regression for text classification
205FastAPI deployment best practices
308Evaluating LLM outputs automatically (LLM-as-judge)
412A/B testing for model routing
587Feature engineering for query complexity

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

  • Я проверил, что датасет сбалансирован по классам (∼40% сложных, 60% простых).
  • Я оценил accuracy не только на обучающей выборке, но и на отложенной (20%).
  • Я протестировал сервис с запросами, которых не было в датасете (хотя бы 50).
  • Я посчитал экономию не в абсолютных числах, а в процентах от baseline.
  • Я убедился, что в логах фиксируется выбранная модель и стоимость для каждого запроса.
  • Я добавил fallback на дорогую модель, если классификатор не уверен (вероятность <0.7).