English translation is not available yet. Showing Russian content.
Реализовать dense retrieval failure detection с fallback на BM25
ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Реализовать dense retrieval failure detection с fallback на BM25
1. Цель задачи
Разработать детектор отказов dense retrieval в production RAG-системе, который автоматически переключается на BM25 при низком сходстве векторов запроса и найденных документов. Система должна сохранять recall на редких запросах (rare queries) на уровне не ниже baseline, одновременно не увеличивая задержку более чем на 10%.
Ключевой результат Работающий fallback-механизм, при котором recall на rare queries не падает ниже 0.85 от исходного BM25-only, а latency укладывается в <200ms (p99).
2. Исходные данные
| Что нужно | Откуда взять |
|---|---|
| RAG-система с dense retrieval (dense + optional BM25) | Собранный pet-проект или готовый пример (например, SentenceTransformers + Qdrant + Elasticsearch) |
| Датасет запросов (mix common + rare) | Открытые датасеты: MS MARCO, Natural Questions, или сгенерировать синтетически |
| Векторная БД с индексами | Qdrant / Weaviate / FAISS |
| BM25 индекс | Elasticsearch / Apache Lucene / Whoosh |
| Embedding модель | sentence-transformers/all-MiniLM-L6-v2 или подобная |
| Метрики оценки | RAGAS (hit rate, MRR, recall@k) |
Если нет реального инструмента — симулируем:
- Установить
faiss-cpuдля dense поиска иwhoosh/rank-bm25для BM25. - Собрать небольшой корпус документов (500–2000) из открытых источников (википедия, новости).
- Сгенерировать 100 запросов: 80 common (похожи на средние из корпуса) и 20 rare (с низким cosine similarity к ближайшим документам).
- Запустить оба поиска. Записать для каждого запроса: список document_id, score, similarity.
3. Технологический стек
| Компонент | Инструменты | Назначение |
|---|---|---|
| Язык | Python 3.9+ | Разработка детектора |
| Dense retrieval | sentence-transformers + faiss | Векторный поиск |
| BM25 | rank-bm25 / whoosh | Текстовый поиск |
| Fallback логика | Python | Принятие решения переключения |
| Оценка | RAGAS / scikit-learn | Метрики recall, precision, f1 |
| Кеш | Redis / LRU (опционально) | Ускорение повторных запросов |
| Мониторинг | Prometheus + Grafana / просто логи | Отслеживание fallback-ов |
| Тестирование | pytest | Unit-тесты детектора |
4. Этапы выполнения
Этап 1: Подготовка окружения и данных (1–2 часа)
Действия
- Установить зависимости
pip install faiss-cpu sentence-transformers rank-bm25 datasets pandas pytest - Загрузить или сгенерировать корпус документов
- Использовать
datasets.load_dataset("ag_news", split="train")(первые 2000 записей как документы). - Каждый документ:
{"text": "...", "id": idx} - Создать список
documentsиdoc_ids.
- Использовать
- Сгенерировать тестовые запросы
- Common: взять 80 случайных документов, вырезать первое предложение.
- Rare: написать 20 вопросов не по теме (например, «квантовая физика» для новостей AG).
- Сохранить в
queries_meta.csvс колонкамиquery, is_rare.
- Построить dense и BM25 индексы
Ожидаемый результат этапа
- Корпус документов (2000), файл запросов (100 строк), готовые индексы.
- Возможность выполнить поиск по каждому методу и получить троечку документов.
Этап 2: Разработка детектора (2–3 часа)
Действия
-
Определить порог (threshold)
- Прогнать common запросы через dense: для каждого запроса получить
max_sim(максимальное косинусное сходство с топ-1 документом). - Построить гистограмму. Выбрать порог на уровне 0.3 (или по 5-му перцентилю common запросов).
- Для rare запросов:
max_simбудет ниже порога. Сохранитьthresholdв конфиг.
- Прогнать common запросы через dense: для каждого запроса получить
-
Написать класс
FallbackRetrieverclass FallbackRetriever: def __init__(self, dense, bm25, threshold=0.3, top_k=5): ... def retrieve(self, query): docs_dense, sims = self.dense.search(query) if max(sims) >= self.threshold: return docs_dense else: return self.bm25.search(query) -
Реализовать логирование fallback-ов
- Счётчик
total_queries,fallback_count,query_text,timestamp. - Выводить в консоль (затем в prometheus).
- Счётчик
-
Добавить опцию “force_fallback” для тестов
Ожидаемый результат этапа
- Python модуль
retriever.pyс классомFallbackRetriever. - Возможность запустить на всех 100 запросах и получить словарь результатов с указанием источника (dense / bm25).
Этап 3: Оценка качества (1–2 часа)
Действия
-
Определить ground truth
- Для каждого запроса вручную или автоматически (по схожести) задать релевантные doc_id.
- Для common: top-1 по dense считать релевантным.
- Для rare: взять top-3 по BM25 (так как dense работает плохо).
- Сохранить в
ground_truth.json:{query: [rel_doc_ids]}.
-
Вычислить метрики
Recall@K(K=1,3,5) отдельно для common, rare и всех запросов.Fallback rate= fallback_count / total_queries.Average latency(использоватьtime.perf_counter).MRRдля top-5.
Код:
def recall_at_k(predicted, relevant, k): return len(set(predicted[:k]) & set(relevant)) / len(relevant) -
Сравнить три режима
- Dense only (без fallback)
- BM25 only
- Fallback (порог 0.3)
Результаты свести в таблицу.
| Режим | Recall@3 (common) | Recall@3 (rare) | Avg latency |
|---|---|---|---|
| Dense | 0.95 | 0.10 | 15ms |
| BM25 | 0.80 | 0.85 | 50ms |
| Fallback | 0.95 | 0.85 | 20ms |
- Подобрать оптимальный порог
Ожидаемый результат этапа
Этап 4: Production-готовность (1–2 часа)
Действия
-
Добавить мониторинг и алерты
- Счётчики Prometheus:
fallback_requests_total,fallback_duration_seconds. - Или простой JSON-лог с записью в файл.
- Счётчики Prometheus:
-
Оптимизировать latency
- Кэшировать результаты BM25 для повторяющихся запросов (LRU cache 1000 записей).
- Если dense сходство чуть ниже порога, можно не сразу падать, а взвешивать:
score = w * dense_sim + (1-w) * bm25_score.
-
Написать unit-тесты
test_fallback_when_low_sim– dense отдаёт низкое сходство → BM25.test_no_fallback_when_high_sim.test_edge_case_threshold_equal.test_latency_under_200ms.
-
Добавить конфигурацию через YAML/ENV
fallback: threshold: 0.35 top_k: 5 cache_size: 1000
Ожидаемый результат этапа
- Код с мониторингом, кэшем, тестами и конфигом.
- README с инструкцией запуска.
Этап 5: Итоговый отчет и демо (30 минут)
Действия
- Собрать итоговые метрики на финальном threshold.
- Написать краткий отчет (1 страница): цель, архитектура, результаты, выводы.
- Подготовить демо: скрипт, который на 2–3 запросах показывает fallback.
Ожидаемый результат этапа
- Файл
report.md. - Демонстрация работы:
python demo.py.
5. Критерии приемки (Definition of Done)
- Класс
FallbackRetrieverреализован и работает на тестовом датасете. - Порог threshold выбран обоснованно (с графиком или таблицей).
- Recall@3 на rare queries не ниже 0.85 (сравнение с BM25 baseline).
- Fallback rate не превышает 30% на common запросах (не должен часто срабатывать на нормальных).
- Средняя latency <200ms (p99) на всех запросах.
- Написаны unit-тесты (минимум 3) и они проходят.
- Добавлен мониторинг (логирование fallback-ов и времени).
- Код оформлен как модуль с README, requirements.txt, конфигом.
- Есть сравнение трёх режимов (dense-only, BM25-only, fallback).
- Отчёт содержит выводы и рекомендации по порогу.
6. Ожидаемый результат
- Основной артефакт Python модуль
retriever.pyсFallbackRetrieverи вспомогательными функциями. - Данные корпус документов, файл запросов, ground truth, результаты экспериментов.
- Отчёт
report.mdс метриками, графиками (опционально), сравнением режимов. - Тесты
test_retriever.py(pytest). - Демо
demo.pyс 3 примерами запросов (common → dense, редкий → fallback, borderline).
7. Возможные сложности и их решение
| Сложность | Решение |
|---|---|
| Как объективно определить rare queries | Использовать метрику rarity: cosine similarity < 0.2 к любому документу корпуса. |
| Порог может быть разным для разных бизнес-сценариев | Сделать порог параметром, провести кросс-валидацию на отложенной выборке. |
| BM25 может быть медленнее dense | Использовать sparse index (Elasticsearch) или LRU кэш. |
| На граничных запросах частые переключения | Добавить гистерезис: fallback включается только если за последние N запросов среднее сходство ниже порога. |
| Ground truth не идеален | Для rare можно использовать BM25 top-1+ LLM проверку релевантности (LLM-as-judge). |
8. Бюджет времени (оценка)
| Этап | Время |
|---|---|
| 1. Подготовка окружения и данных | 1–2 ч |
| 2. Разработка детектора | 2–3 ч |
| 3. Оценка качества | 1–2 ч |
| 4. Production-готовность | 1–2 ч |
| 5. Итоговый отчёт и демо | 0.5 ч |
| Итого | 6–9 ч |
Примечание: Для первого раза с полным погружением может потребоваться до 2 дней (16 ч) из-за отладки порога и тестов.
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 49 | Dense retrieval failure detection |
| 12 | Основы vector similarity (cosine, dot) |
| 17 | BM25 и TF-IDF реализация |
| 23 | Оценка качества поиска (recall, MRR) |
| 31 | Гистерезис и пороговые значения |
| 44 | Hybrid search (dense + sparse) |
| 67 | Мониторинг ML-систем (Prometheus) |
| 83 | Кэширование запросов |
| 91 | Unit-тестирование retrieval |
| 145 | Data drift в запросах |
10. Чек-лист самопроверки
- Я построил dense и BM25 индексы и проверил, что каждый возвращает документы.
- Я выбрал порог не «на глаз», а на основе распределения similarity common запросов.
- Я посчитал recall отдельно для common и rare запросов, а не только средний.
- Я проверил, что latency не превышает 200ms на p99 с включенным fallback.
- Я написал тесты на случай низкого сходства и на случай, когда fallback не нужен.
- Я задокументировал результаты в отчёте с таблицей сравнения.
- Я убедился, что
rareзапросы действительно редкие (sim < 0.2).