Задача
Покупатель на маркетплейсе задаёт вопрос под карточкой или пишет отзыв. Ответить надо быстро и по делу: молчание роняет карточку в выдаче, а отписка «спасибо за обращение» роняет доверие. У продавца с тысячей позиций это отдельная работа на весь день, и на неё нанимают человека.
При этом отвечать наугад нельзя. Выдуманная характеристика товара дороже молчания: за неё продавец отвечает и перед покупателем, и перед площадкой.
Решение
Продавец подключает кабинет площадки ключом, заливает свои правила, прайсы и описания товаров. Дальше сервис разбирает поток сам:
- на хорошие оценки отвечает без спроса;
- на плохие готовит черновик и оставляет решение человеку;
- на вопрос отвечает, только если в базе знаний есть чем;
- не знает — зовёт живого человека и передаёт ему диалог.
Тот же бот с той же базой знаний работает пузырём чата на сайте продавца, отдельной страницей-чатом по ссылке, ботом в Telegram и в MAX.


Как собирается ответ
Вопрос из любого канала идёт по одному пути:
- база знаний клиента разрезана на фрагменты;
- поиск двойной — по смыслу (векторы, pgvector HNSW, эмбеддинги bge-m3) и по словам (полнотекстовый индекс с русской морфологией);
- два списка сводятся в один рангами, а не весами;
- ответ строится только по найденному;
- на выходе развилка: опубликовать в канал самому или отдать черновиком человеку.
Третий пункт — то место, где обычно застревают. Подбирать веса под каждого клиента невозможно: у одного база собрана из PDF, у другого из карточек товара, и коэффициенты, настроенные под первого, ломают выдачу у второго. Слияние рангами (RRF) к этому устойчиво: оно смотрит на порядок, а не на абсолютные оценки, которые у двух разных поисков просто несопоставимы.

Чем отличается от аналогов
Одна база знаний — все каналы сразу
Обычно чат на сайте, бот в мессенджере и ответы на маркетплейсе — три разные подписки и три набора текстов, которые расходятся через месяц. Здесь материал заливается один раз, любой из семи каналов включается переключателем, а товары из кабинета продавца попадают в ту же базу автоматически.
Изоляция клиентов в самой базе, а не в коде
Данные разных продавцов разделены построчной защитой Postgres (RLS): чужая строка не отдаётся, даже если в запросе забыли условие. Ошибка в фильтре превращается в пустой ответ, а не в утечку прайса конкуренту.
Это принципиальный выбор места для защиты. Проверка в коде живёт ровно до первого запроса, который её обошёл; проверка в базе не обходится никак.
Плата за ответ, а не за тариф
Нет пакетов с лимитами и пропадающими остатками: деньги лежат на счёте аккаунта и тратятся на любой проект клиента. Четыре ступени качества модели переключаются на ходу, и кабинет сразу показывает, на сколько ответов хватит остатка.

Молчание вместо выдумки
Бот отвечает только тем, что нашлось в базе. Для отзывов это разведено ещё жёстче: хорошие оценки бот закрывает сам, плохие готовит черновиком. Порог задаёт продавец.


Настраивается продавцом, а не подрядчиком
Виджет ставится одной строкой в шаблон сайта, площадка подключается ключом из кабинета продавца, характер бота задаётся обычным текстом. Кабинет двуязычный, с тёмной темой и встроенной справкой со скриншотами, которые пересобираются вместе с интерфейсом.

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

Работает из России и разговаривает с миром
Данные продавцов лежат на российском сервере. Всё, что снаружи недоступно, ходит через собственный мост, включая входящие вебхуки. Для клиента это невидимо, и в этом смысл.
Инженерные задачи
Telegram заблокирован в обе стороны. С российского сервера не уходят запросы к Telegram и — что заметили не сразу — не приходят вебхуки обратно. Канал выглядел живым: бот подключался, настройки сохранялись, а сообщения покупателей не доходили. Решено собственным мостом: исходящее идёт прокси, входящее принимает наш домен на европейской стороне и передаёт внутрь.
Прокси ломал живой чат. Ответ бота идёт потоком, по буквам. Cloudflare копит такой поток в буфере и отдаёт целиком — со стороны это выглядит как зависший бот. Поэтому домен API намеренно живёт мимо проксирования, а сайт остаётся под ним ради кэша и защиты.
Деньги нельзя списать дважды. Ответы генерируются фоном, площадки присылают повторы, платёжка дёргает уведомление не один раз. Списания и зачисления идут через журнал операций с проверкой остатка до генерации, счета выставляются на общий счёт аккаунта.
Тихие поломки дороже громких. Самые дорогие ошибки здесь не падали с ошибкой: обновление настройки молча не срабатывало, если промежуточного ключа ещё не было в структуре, — клиент видел «не подключено» на работающем виджете. Такие места закрыты общим помощником и проверками, а разбор каждой записан в журнал решений, который в проекте ведётся наравне с кодом.
Статика без сборщика тоже кэшируется. Забытая версия в адресе оставляет ошибку жить неделю. Выкатка сайта сведена в одну команду: отправить, сбросить кэш, сверить хеши с живым сайтом и сказать, что именно доехало.

Чем сделано
| Слой | Технологии |
|---|---|
| Бэкенд | Python, FastAPI, asyncpg, SSE, httpx |
| Данные | PostgreSQL, pgvector HNSW, tsvector, Row Level Security, Redis, MinIO |
| Модели | единый шлюз к нескольким поставщикам, эмбеддинги bge-m3, слияние RRF, vision |
| Фронтенд | Vanilla JS без сборки, RU/EN, тёмная тема, виджет в iframe |
| Инфраструктура | Docker Compose, nginx, Cloudflare, squid, Let's Encrypt |
| Интеграции | Wildberries, Ozon Seller + OAuth, Яндекс Маркет, Telegram Bot API, MAX, приём платежей, SMTP |
Векторный и полнотекстовый поиск живут в одной базе — отдельное хранилище векторов не заводилось, чтобы изоляция клиентов оставалась в одном месте. Кабинет, витрина, виджет и суперадминка — статика на общем хостинге, без узла сборки.
Объём
- 28 300 строк Python в 47 модулях бэкенда;
- 63 миграции схемы;
- 7 каналов общения с покупателем;
- 44 набора проверок интерфейса, гоняются в контейнере на сервере;
- 843 коммита за 31 рабочий день.
Числа про клиентов и обороты сюда намеренно не вынесены: это данные владельца бизнеса, а не разработчика.