Почему поиск по вхождению не работает ни так, ни эдак
Сторона первая: вы теряете лиды. Слово «купить» не встречается в сообщениях «куплю дом в Подмосковье», «купил и жалею», «купят по такой цене?». Человек написал ровно то, что вам нужно, — а фильтр промолчал, потому что буквы другие. Живой чат вообще редко говорит инфинитивами: люди пишут «куплю», «ищу», «посоветуйте», а не «купить», «искать», «посоветовать».
Сторона вторая — попытка это починить, обрезав слово до основы. Пишем в список «куп» и ищем вхождение. Теперь у вас в улове «купание», «купол», «скупой», «купе» и «Купчино». На объёме живого чата это означает, что оператор перестаёт читать уведомления примерно на третий день.
Три способа, и почему мы выбрали третий
Взять готовый морфологический словарь. Для русского они есть и работают хорошо. Цена — новая зависимость в рабочем контуре: её надо ставить, обновлять, держать в памяти, и она становится ещё одной вещью, которая может не подняться после перезапуска. Мы решили в продакшен её не тащить.
Спрашивать у нейросети про каждое сообщение. Чат сыплет сообщениями непрерывно, а звать модель на каждое — разорительно: умножьте поток на десяток чатов и на стоимость обращения, и получится счёт, который не окупит ни один лид. Модель здесь нужна, но позже и по другому поводу; об этом ниже.
Собирать словоформы правилами и показывать их человеку. Это и есть наш путь. Из слова «купить» строится список целых форм — куплю, купишь, купил, купят, купи, купите — и совпадением считается только точное равенство слова из сообщения одной из этих форм.
Лишняя выдуманная форма («искаю» из «искать») стоит строки в списке и ничего не ломает: такого слова в чате не бывает. Недостающая форма стоит пропущенного лида — поэтому правила намеренно щедрые. комментарий в keyword_morph.py
Из этого следует главное практическое отличие: список форм видит и правит человек. Он открыт на экране до запуска, у каждой формы есть крестик «убрать». Вы не доверяете чёрному ящику — вы читаете глазами, что именно будет ловиться, и выбрасываете лишнее.
Что внутри правил
Русский глагол меняет не только окончание, но и основу, и это самое интересное место.
- Чередование согласных в первом лице. искать → ищу, пустить → пущу, ходить → хожу, просить → прошу. Отдельно — губные, которые дают вставное «л»: купить → куплю, любить → люблю, ловить → ловлю.
- Неоднозначность не решается угадыванием. У глаголов на «-тить» чередование даёт и «ч» (платить → плачу), и «щ» (защитить → защищу). Без словаря выбрать нельзя — поэтому строятся оба варианта, а лишний оператор видит в списке и убирает.
- Спряжение по одной форме не определяется. «Куплю» — второе спряжение, «ищу» — первое. Значит, даём оба набора окончаний.
- Двойное чтение хвостов. Слово кончается на «-ит»? Это и «купит» (глагол), и «кредит» (существительное). Строятся оба семейства.
- «Ё» приводится к «е»: в чатах её пишут через раз, и «неделё» с «неделе» иначе разъезжаются на ровном месте.
- «Интернет-магазин» — один токен, а не два. Слово с дефисом или подчёркиванием не рассыпается на части.
Два правила, которые существуют ради читаемости списка
Механика строит формы по хвостам, и «ищы» или «купщя» она соберёт с той же лёгкостью, что и настоящую форму. Поэтому есть фильтр невозможных форм: жи-ши, ча-ща, чу-щу, ци-цы — чистая орфография, словарь для неё не нужен. Выдуманные строки, нарушающие эти правила, в список не попадают.
Второе правило тоньше и объясняет, зачем вообще эта возня. Слова на «-те» («посоветуйте», «подскажите») и на «-ен» («нужен») не склоняются как существительные. Иначе в списке появились бы «посоветуйтями» и «нуженами».
Это не безобидный лишний вариант, а повод не поверить всему остальному. комментарий в коде, там же
И потолок: не больше 80 форм на слово. Не из соображений скорости — из соображений, что список читает человек, а список на триста строк не читает никто.
Где эта механика честно не справляется
Правила работают внутри одного корня и одной основы. Они не свяжут «купить» с «покупаю» — другая приставка. И не свяжут «ищу» с «искал» — в прошедшем времени возвращается основа инфинитива.
Можно было бы наращивать правила дальше, угадывая приставки. Мы сделали иначе, и это, пожалуй, самая полезная часть модуля. У нас на руках есть живой срез вашего чата — пятьсот настоящих сообщений. Значит, не надо гадать: берутся слова, которые в чате действительно есть, похожи на ваши ключевые и сейчас не ловятся. Они показываются оператору с частотой встречаемости — клик добавляет слово в список.
Чтобы найти такую родню, считаются обратные чередования: по согласной в основе настоящего времени восстанавливается то, что могло стоять в инфинитиве — «ищ» → «иск», «хож» → «ход», «прош» → «прос».
Разница в подходе принципиальная: это подбор по факту вашего чата, а не по грамматике вообще. В чате про ремонт и в чате про крипту нужны разные слова, и знает об этом сам чат, а не справочник.
запуска видно, что поймалось бы: кнопка «Проверить на чате» прогоняет ваш список по живому срезу сообщений. Неделя ожидания улова, которого не будет из-за одного неудачного слова, — самая обидная потеря времени в этой работе.
Стоп-слова сравниваются буквально — и это сделано нарочно
Логично ожидать, что стоп-слова тоже раскроются в формы. Не раскрываются, и вот почему. Стоп-фраза «не куплю» с раскрытыми формами превратилась бы в «не» плюс всё семейство «купить» — и погасила бы ровно те сообщения, ради которых модуль и сделан.
Поэтому стоп-слова сравниваются как есть. А оттенки, которые буквальным сравнением не берутся — «уже купил», «спасибо, нашёл», — отсеивает вторая ступень: вы описываете словами, кого ищете, ставите порог, и модель отбрасывает мимо-попадания (это уже настройка каталоге модулей). Обращение к модели идёт только по уже совпавшим сообщениям, а не по всему потоку чата, — отсюда и разница в счёте на два порядка.
Стоп-слово подсказывается по ложному улову — и не то, о котором вы подумали
Отсюда же вытекает приятная мелочь. Когда в улове оказалось сообщение, которое вам не нужно, вы можете отправить его в стоп-слова прямо из списка. И система предлагает не то слово, которое сработало.
Виновато ведь обычно не само ключевое слово, а его сосед слева: «уже купил», «купил бы». Поэтому в подсказку идёт совпавшая форма вместе с предыдущим словом — «уже купил», а не «купил». Разница принципиальная: ключевое слово, попавшее в стоп-лист, выключило бы задачу целиком, и человек полдня выяснял бы, почему перехват вдруг перестал ловить хоть что-нибудь.
Чего этот модуль не умеет — честно
- Опечаток не обходит. «Куплю» с лишней буквой не поймается.
- Транслита не понимает. «Kuplyu» — мимо.
- Подмену букв не видит. Латинская «а» вместо русской, ноль вместо «о» — сравнение строго точное по форме.
- Приставки и другую основу правила сами не свяжут — для этого и сделаны подсказки по живому чату.
Мы сознательно не прячем этот список: человек, который неделю ждёт улова и не понимает, почему его нет, — это худший исход и для него, и для нас.
Зачем мы вообще это рассказываем
Потому что «умный поиск по ключевым словам» есть в описании у всех, и по описанию их не отличить. Отличить можно по одному вопросу, который стоит задать любому такому софту, включая наш: покажите список того, что будет ловиться, до запуска. Если показать нечего — внутри поиск по подстроке, и ваш оператор будет разгребать «купола».