Почему обычный поиск по книгам всегда проваливался
Попробуйте найти в библиотеке "книгу про подростка, который находит волшебный амулет и спасает королевство". Система вернет вам Гарри Поттера, Хроники Нарнии и еще сотню фэнтези-романов. Но нужна конкретная книга, которую вы читали в детстве и забыли название.
Традиционный поиск по ключевым словам здесь бесполезен. Он ищет "подросток", "амулет", "королевство", но не понимает смысла запроса. Векторный поиск по полному тексту книг? Технически невозможен для 500 тысяч томов - индексация займет месяцы, а поиск будет медленным как черепаха.
Команда red_mad_robot столкнулась с этой проблемой при создании поиска для крупной книжной платформы. Их решение - двухконтурная архитектура, которая использует не текст книг, а их метаданные. Звучит парадоксально? Сейчас объясню.
Главный миф: для семантического поиска нужен полный текст. На практике метаданные (аннотация, жанр, теги, отзывы) часто содержат достаточно смысла для точного поиска, особенно когда речь о 500k документов.
Двухконтурная архитектура: почему один поиск - это всегда компромисс
Представьте, что вам нужно найти человека в городе. Вы можете искать по адресу (точный поиск) или по описанию "высокий брюнет в очках" (семантический поиск). Идеальная система делает и то, и другое одновременно.
Двухконтурная архитектура red_mad_robot работает по тому же принципу:
- Контур 1: Быстрый релевантный поиск - находит книги по названию, автору, ISBN (классический BM25 или Elasticsearch)
- Контур 2: Семантический поиск - ищет по смыслу в аннотациях, жанрах, тегах (векторные эмбеддинги)
Оба контура работают параллельно. Результаты объединяются и проходят через LLM-ранжировщик, который решает: эта книга про волшебный амулет или просто еще одно фэнтези?
Векторизация метаданных: как превратить аннотацию в 768 чисел
Вот где начинается магия. Вместо того чтобы векторизовать полный текст книги (100-500 тысяч токенов), система берет только метаданные:
{
"title": "Властелин колец",
"author": "Дж. Р. Р. Толкин",
"annotation": "Эпическая история о хоббите Фродо...",
"genres": ["фэнтези", "приключения", "эпическое фэнтези"],
"tags": ["кольцо всевластья", "Средиземье", "битва добра и зла"],
"reviews_snippet": "книга о дружбе и предательстве..."
}
Все эти поля конкатенируются в один текст длиной 200-500 токенов. Затем модель эмбеддингов (в 2026 году это обычно E5-Mistral-7B-v2 или BGE-M3-2025) преобразует этот текст в вектор из 768 или 1024 измерений.
1 Почему именно эти модели в 2026 году?
E5-Mistral-7B-v2 выигрывает у более старых моделей в одном ключевом аспекте: она обучена специально для асимметричного поиска. Это значит, что запрос "книга про волшебный амулет" и аннотация книги - это разные по длине и стилю тексты, но модель находит между ними семантическую связь.
BGE-M3-2025 - это эволюция популярной BGE модели с поддержкой мультиязычности и улучшенной работой с короткими текстами. Для книжного поиска, где аннотации часто пишутся разными стилями, это критически важно.
| Поле метаданных | Вес в векторизации | Почему важно |
|---|---|---|
| Аннотация | 40% | Содержит основной сюжет, темы, настроение |
| Жанры + теги | 30% | Дают точную классификацию (не просто "фэнтези", а "темное фэнтези про вампиров") |
| Отзывы (сниппеты) | 20% | Показывают, как читатели воспринимают книгу |
| Название + автор | 10% | Для точного поиска по известным книгам |
LLM-ранжировщик: финальный судья релевантности
Допустим, два контура вернули 50 книг. BM25 нашел 20 по ключевым словам, векторный поиск - 30 по смыслу. Как понять, какие из них действительно отвечают на запрос "книга про подростка с амулетом"?
Старый подход: взвешенная сумма scores. Работает плохо, потому что BM25 и косинусное сходство измеряют разные вещи.
Решение red_mad_robot: LLM-ранжировщик. Небольшая модель (в их случае Qwen2.5-Coder-1.5B), которая получает на вход:
- Оригинальный запрос пользователя
- Метаданные каждой книги (только текст, без векторов)
- Скоры от обоих контуров поиска
Модель переоценивает релевантность каждой книги и выдает финальный ранжированный список. Почему Qwen2.5-Coder? Потому что она справляется с логическими задачами лучше чистых языковых моделей такого же размера. А размер 1.5B позволяет запускать ее на CPU с приемлемой скоростью.
# Упрощенная схема работы ранжировщика
prompt = f"""
Запрос пользователя: {user_query}
Книга: {book_metadata}
Score BM25: {bm25_score}
Score векторный: {vector_score}
Насколько эта книга релевантна запросу от 1 до 10?
Объясни кратко.
"""
# Модель возвращает не только score, но и объяснение
# Это важно для дебаггинга и улучшения системы
Ключевое преимущество LLM-ранжировщика: он понимает контекст. Запрос "легкое чтение на выходные" получит другие книги, чем "интеллектуальный роман для вдумчивого чтения", даже если ключевые слова совпадают.
Пошаговая реализация: от данных до production
1 Подготовка данных и векторизация
500 тысяч книг - это примерно 10 ГБ чистого текста метаданных. Векторизация на CPU займет недели. Решение:
- Использовать батчевую обработку на GPU (даже одна карта ускорит в 50 раз)
- Применить квантование int8 для модели эмбеддингов - потеря точности 1-2%, ускорение в 3 раза
- Сохранять эмбеддинги в бинарном формате (экономит 60% места на диске)
Подробнее об оптимизации скорости в статье "Как ускорить семантический поиск в 20 раз".
2 Построение индексов
Два независимых индекса:
- Elasticsearch для BM25 - индексирует все текстовые поля метаданных
- FAISS IVF4096,PQ32 для векторного поиска - хранит эмбеддинги с оптимизацией под поиск по смыслу
Важный нюанс: FAISS индекс строится с quantization (PQ32), что сокращает размер с 2 ГБ до 500 МБ и ускоряет поиск. Точность падает на 3-5%, но для книжного поиска это приемлемо.
3 Поисковый движок
Микросервис на FastAPI, который:
async def hybrid_search(query: str, k: int = 50):
# 1. Параллельный поиск в двух индексах
bm25_results = await search_bm25(query, k=30)
vector_results = await search_vector(query, k=30)
# 2. Дедупликация по book_id
all_books = merge_results(bm25_results, vector_results)
# 3. LLM-ранжирование (асинхронно, батчами по 10)
ranked_books = await llm_rerank(query, all_books)
return ranked_books[:10] # Топ-10 результатов
Ошибки, которые все совершают (и как их избежать)
Ошибка 1: Векторизовать каждое поле метаданных отдельно. Кажется логичным - отдельные эмбеддинги для аннотации, отдельные для жанров. На практике это убивает производительность и не дает выигрыша в точности. Всегда конкатенируйте поля в один текст перед векторизацией.
Ошибка 2: Использовать одну и ту же модель для эмбеддингов запроса и документов. Для асимметричного поиска (короткий запрос vs длинный документ) нужны специально обученные модели. E5 или BGE-M3 работают, а sentence-transformers/all-MiniLM-L6-v2 - уже нет.
Ошибка 3: Запускать LLM-ранжировщик на всех результатах. Для 500 книг это займет минуты. Ограничьте вход ранжировщика 50-100 книгами, отобранными гибридным поиском. Если нужная книга не в топ-100 по гибридному поиску, она вряд ли релевантна.
Производительность в цифрах (бенчмарк 2026)
Система red_mad_robot на сервере с 16 ядрами CPU и 64 ГБ RAM:
- Индексация 500k книг: 18 часов (с GPU ускорением векторизации)
- Размер индексов: 3.2 ГБ (FAISS + Elasticsearch)
- Время поиска (p95): 220 мс
- Точность (Recall@10): 78% на тестовых запросах
- Стоимость инфраструктуры: ~$400/месяц
Для сравнения: поиск по полному тексту книг потребовал бы 200+ ГБ индекса и 2+ секунд на запрос.
Куда движется гибридный поиск в 2026 году
Двухконтурная архитектура - это не финал, а начало. Вот что появляется в production-системах прямо сейчас:
- Трехконтурный поиск - добавляется контур для поиска по цитатам и отзывам. Пользователь ищет "книги, где герой говорит 'я вернусь'", и система находит.
- Адаптивные веса - система учится, какие контуры важнее для разных типов запросов. Для "роман Достоевского" вес BM25 выше, для "книга про экзистенциальный кризис" - векторного поиска.
- Multimodal поиск - векторизация обложек книг. Запрос "мрачная готическая обложка" находит соответствующе оформленные книги.
Если вы планируете свою систему, смотрите в сторону RAG 2026 roadmap - там разобраны все современные подходы.
Гибридный поиск по книгам - это не про идеальную точность. Это про покрытие 95% пользовательских запросов с приемлемой скоростью и стоимостью. Двухконтурная архитектура red_mad_robot доказывает: можно искать по смыслу в полумиллионе книг, не индексируя их полный текст. Главное - понять, что искать и как комбинировать результаты.
Следующий шаг? Добавьте поиск по стилю: "найди книги, которые читаются как Стивен Кинг 80-х годов". Но это уже тема для другой статьи.