Реализовать 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-трафика — симулируем:
- Собрать 2000 запросов из открытых источников: Alpaca (500), GSM8K (500, как сложные), Simple QA (500, как простые), TriviaQA (500, смесь).
- Разметить «сложность» вручную: запросы, требующие многошаговых рассуждений или внешних данных → сложные; фактические короткие → простые. Цель: 40% сложных, 60% простых.
- Сгенерировать ответы обеих моделей на все запросы (использовать бюджетный аккаунт OpenAI или локальные модели через Ollama).
- Оценить accuracy: сравнить ответы каждой модели с эталонным ответом (LLM-as-judge с промптом) — считать долю совпадений по смыслу.
3. Технологический стек
| Компонент | Инструменты | Назначение |
|---|---|---|
| LLM API | OpenAI API (gpt-4o, gpt-3.5-turbo) / локальные модели через Ollama | Генерация ответов для датасета |
| Классификатор | scikit-learn (LogisticRegression, RandomForest) / fastText / BERT (transformers) | Определение сложности запроса |
| Оценка качества | LLM-as-judge (gpt-4o-mini) + ручная валидация | Метрика accuracy |
| Сервис маршрутизации | FastAPI + pydantic | API-слой для приёма запросов |
| Очередь (опционально) | Redis / RabbitMQ | Буферизация при высокой нагрузке |
| Мониторинг | Prometheus + Grafana | Сбор метрик: стоимость, latency, accuracy |
| Эксперименты | MLflow / Weights & Biases | Логирование параметров и метрик |
| CI/CD | GitHub Actions + Docker | Автоматизация тестов и деплоя |
4. Этапы выполнения
Этап 1: Сбор и разметка датасета (4–6 часов)
Действия
-
Сформировать пул из 2000 запросов (реальные или синтетические).
Пример распределения:- 500 простых (фактовые, односложный ответ: "Какая столица Франции?")
- 500 средних (требуют 2–3 шага: "Почему небо голубое?")
- 1000 сложных (многошаговые, цепочки рассуждений: "Реши уравнение x²+2x+1=0 и объясни шаги")
-
Для каждого запроса сгенерировать ответ через дешёвую модель (gpt-3.5-turbo) и дорогую (gpt-4o).
-
Оценить accuracy:
- Для 200 случайных запросов — ручная разметка (два аннотатора, Fleiss' kappa >0.7).
- Для остальных — LLM-as-judge: промпт
"Оцени, эквивалентны ли ответы по смыслу? Ответь 1 (да) или 0 (нет). Ответ А: ..., Ответ Б: ...". - Итоговая accuracy = доля ответов, признанных эквивалентными.
-
Baseline: accuracy дорогой модели = 1.0 (эталон), стоимость = сумма токенов × цена.
Ожидаемый результат этапа CSV-файл dataset.csv с колонками: id, query, cheap_answer, expensive_answer, true_label, accuracy_cheap, accuracy_expensive. Baseline-метрики.
Этап 2: Разработка классификатора сложности (4–6 часов)
Действия
-
Преобразовать запросы в признаки:
- Длина запроса (число токенов)
- Наличие вопросительных слов (почему, как, объясни, в чём разница)
- Число глаголов/существительных
- Эмбеддинг (sentence-transformers/all-MiniLM-L6-v2) + PCA до 50 компонент
-
Обучить baseline-модель (LogisticRegression) на 80% данных, 20% — тест.
Метрика: F1-score на классе "сложный". -
Если F1 <0.85 — попробовать RandomForest или fastText (n-граммы).
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)
Ожидаемый результат этапа Сохранённая модель классификатора + отчёт с precision/recall/F1.
Этап 3: Интеграция маршрутизации в сервис (3–4 часа)
Действия
-
Создать FastAPI-приложение с одним endpoint
POST /chat: -
Реализовать логирование: запись запроса, выбранной модели, времени ответа, стоимости.
-
Добавить health check и metrics endpoint для Prometheus.
-
Написать unit-тесты (pytest):
- Корректность выбора модели для заведомо простых/сложных запросов
- Обработка ошибок (timeout, недоступность API)
Ожидаемый результат этапа Рабочий микросервис с маршрутизацией, запускаемый через docker compose up.
Этап 4: Оценка экономического эффекта (2–3 часа)
Действия
-
Прогнать весь датасет (2000 запросов) через сервис с routing.
Записать: какие запросы пошли на дешёвую, какие на дорогую модель. -
Подсчитать:
-
Построить ROC-кривую выбора модели (чувствительность к сложности) — порог вероятности сложности, при котором направляем на дорогую модель.
Выбрать порог, дающий accuracy ≥0.95 от baseline. -
Визуализировать: scatter plot «стоимость vs accuracy» для разных порогов.
Expected output table
| Порог вероятности | % запросов на дешёвую | Снижение стоимости | Accuracy (отн. baseline) |
|---|---|---|---|
| 0.5 | 55% | -52% | 0.97 |
| 0.3 | 40% | -38% | 0.99 |
| 0.7 | 70% | -65% | 0.93 |
Ожидаемый результат этапа Подтверждение гипотезы: найдена конфигурация с −50% cost при accuracy −5% или лучше.
Этап 5: Документирование и развёртывание (2–3 часа)
Действия
-
Оформить README с описанием архитектуры, инструкцией по запуску, benchmark-результатами.
-
Упаковать в Docker-образ (FastAPI + модель) и docker-compose.yml (сервис + Redis + Grafana).
-
Написать post-deployment тесты: запросы разной сложности, проверка выбора модели.
-
Создать дашборд 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-приложение с routingmodel/— сохранённый классификатор (pickle/ONNX)data/— датасет (синтетический или реальный)notebooks/— Jupyter-ноутбук с обучением и оценкойtests/— pytest-тестыdocker-compose.yml— окружениеREADME.md— результаты сравнения стоимости и accuracydashboard.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. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 41 | Cost optimization for LLM inference |
| 42 | Model selection criteria (quality vs cost) |
| 43 | Caching strategies for LLM responses |
| 44 | Performance benchmarking of language models |
| 45 | Cost-aware architecture patterns |
| 112 | Logistic regression for text classification |
| 205 | FastAPI deployment best practices |
| 308 | Evaluating LLM outputs automatically (LLM-as-judge) |
| 412 | A/B testing for model routing |
| 587 | Feature engineering for query complexity |
10. Чек-лист самопроверки
- Я проверил, что датасет сбалансирован по классам (∼40% сложных, 60% простых).
- Я оценил accuracy не только на обучающей выборке, но и на отложенной (20%).
- Я протестировал сервис с запросами, которых не было в датасете (хотя бы 50).
- Я посчитал экономию не в абсолютных числах, а в процентах от baseline.
- Я убедился, что в логах фиксируется выбранная модель и стоимость для каждого запроса.
- Я добавил fallback на дорогую модель, если классификатор не уверен (вероятность <0.7).