CarHao

Пользователь пишет: «Хочу Cayenne до 8 млн под ключ в Москве» — система за секунду понимает запрос, ищет по базе из 4 миллионов автомобилей и выдаёт конкретный шортлист с ценами и ссылками. Построен пайплайн: от разбора свободного запроса до поддержания индекса в актуальном состоянии.

Роль

Проектирование пайплайна и архитектуры, построение поискового индекса и векторизация базы знаний заказчика, LLM-оркестрация, поддержка и эксплуатация

Масштаб

— 4 000 000+ автомобилей в индексе
— 5 источников данных
— гибридный поиск по смыслу + фильтрам

Stack
QDRANT PostgreSQL OpenAI API MongoDB Telegram API Python FastAPI Redis httpx

Какие сложности были у проекта

5 источников — 5 разных форматов

Корейские, китайские и европейские площадки хранят данные по-своему: разные названия топлива, коробки, кузова, разные валюты и статусы. Поиск поверх такой базы без предобработки просто не работает. Был построен слой нормализации, который привёл всё к единому виду — и только после этого фильтры стали давать предсказуемый результат.

«Хочу BMW X5 до 6 млн, дизель, полный привод, до 50 тыс км» → готовый список для менеджера

Человек пишет запрос своими словами. Система сама разбирает, что имеется в виду, накладывает фильтры по бюджету с учётом доставки и валюты, ищет по смыслу — и выдаёт карточки машин с ценами и ссылками. Менеджер получает тёплый лид, а не просто запрос «ищу машину».

Архитектура: как устроено изнутри

Загрузка схемы…
Колёсико — масштаб, зажатая мышь — перемещение, двойной клик — сброс
Как это читать
  • База фактов и поисковый индекс разделены. PostgreSQL хранит исходные данные, Qdrant — только то, что нужно для поиска. Это позволяет обновлять одно, не ломая другое.
  • LLM не принимает решений о данных. Модель переводит свободный запрос в структуру для QDRANT-фильтрации. Дальше работает детерминированная логика, а не «угадывание» нейросети. После полуения релевантных результатов осбирается финальынй ответ пользователю.
  • Нормализация — это не опция, а обязательное условие. Без единого формата для всех источников фильтры по кузову, топливу или коробке просто не работают стабильно.
  • 4 миллиона записей не пересчитываются каждый раз. Выгрузка новых записей → нормализация → перевекторизация, только для изменённых векторов.

Диалоги с пользователями

Ассистент помнит контекст, уточняет бюджет под город, предлагает варианты с ценами и передаёт квалифицированный лид менеджеру — без разрыва между сообщениями.

Как система принимает решения — в логах

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

4 000 000 записей в индексе — не просто тексты

Каждая запись содержит исходные данные из источника, нормализованные поля для фильтров и служебные метки для обновлений. Именно это позволяет фильтровать по многим критериям и затем по векторному поиску одновременно — быстро и точно.

Как из 5 источников получился один стандарт

Разные площадки называют «бензин», «АКПП» и «кроссовер» по-разному. Были созданы таблицы соответствий для каждого источника отдельно — и теперь фильтры работают стабильно вне зависимости от того, откуда пришла запись.

SQL-схема

Проект строился поверх живой и неидеальной структуры заказчика, а не поверх заранее удобной базы. Нужно было разобрать эту схему, понять ограничения и превратить ее в рабочий слой для поиска, нормализации и ответа.

Этапы разработки проекта

Шесть слоёв, которые вместе делают поиск по 4 млн+ автомобилям рабочим: индекс, нормализация, оркестрация, фильтрация и семантический поиск, продажная логика, обновление данных.

Backend / runtime

FastAPI-сервис с двумя входами: диалоговый чат для пользователя и прямой search API для программного доступа. Один сервис — два способа работы с индексом.

LLM-оркестрация

Свободный запрос пользователя превращается в поисковый контракт через LLM-нормализатор. Модель извлекает намерение — детерминированный код строит фильтры, ищет и считает цену.

Поисковый индекс

Семантический поиск по смыслу запроса плюс структурные фильтры по параметрам автомобиля. Если выдача пустая — система автоматически ослабляет фильтры и повторяет поиск.

Нормализация данных

5 источников с разными форматами — один стандарт поиска. Топливо, коробка, кузов, привод приведены к единым полям для каждого источника отдельно. Без этого слоя фильтры работают непредсказуемо.

Обновление индекса в QDRANT

Синхронизация данных из SQL, нормализация полей, пересчёт векторов — только для изменившихся записей. Без полного перестроения при каждом обновлении.

Диагностика

Для заказчика подготовлены аудиты покрытия полей, отчёты по пробелам в исходных данных, построена защита от разрушительных обновлений индекса и возобновляемые прогоны при сбоях.

Два решения, которые определили всю архитектуру

База данных и поисковый индекс — как разные вещи

PostgreSQL хранит правду об автомобилях. Qdrant хранит только то, что нужно для поиска. Если смешать эти роли в одном хранилище, система начинает падать при каждом обновлении источника. Разделение дало возможность обновлять и чинить части независимо.

Пересчитывать 4 миллиона записей каждый раз — плохая идея

Я разбил обновление на три шага: сначала синхронизация исходных данных, потом нормализация, потом пересчёт векторов — и только там, где что-то изменилось. Это сократило стоимость API-запросов, ускорило обновление и дало возможность продолжить с места остановки при любом сбое.