Морфология без словарей: как поймать «ищу» и не поймать «купол»

В чатах каждый день пишут «посоветуйте», «куплю», «сколько стоит» — и это лучшие лиды, какие бывают: человек сам сказал, что ему надо. Проблема начинается, когда вы пытаетесь эти сообщения выловить. Разбираем, почему очевидное решение не работает, и что мы сделали вместо него.

Почему поиск по вхождению не работает ни так, ни эдак

Сторона первая: вы теряете лиды. Слово «купить» не встречается в сообщениях «куплю дом в Подмосковье», «купил и жалею», «купят по такой цене?». Человек написал ровно то, что вам нужно, — а фильтр промолчал, потому что буквы другие. Живой чат вообще редко говорит инфинитивами: люди пишут «куплю», «ищу», «посоветуйте», а не «купить», «искать», «посоветовать».

Сторона вторая — попытка это починить, обрезав слово до основы. Пишем в список «куп» и ищем вхождение. Теперь у вас в улове «купание», «купол», «скупой», «купе» и «Купчино». На объёме живого чата это означает, что оператор перестаёт читать уведомления примерно на третий день.

Три способа, и почему мы выбрали третий

Взять готовый морфологический словарь. Для русского они есть и работают хорошо. Цена — новая зависимость в рабочем контуре: её надо ставить, обновлять, держать в памяти, и она становится ещё одной вещью, которая может не подняться после перезапуска. Мы решили в продакшен её не тащить.

Спрашивать у нейросети про каждое сообщение. Чат сыплет сообщениями непрерывно, а звать модель на каждое — разорительно: умножьте поток на десяток чатов и на стоимость обращения, и получится счёт, который не окупит ни один лид. Модель здесь нужна, но позже и по другому поводу; об этом ниже.

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

Лишняя выдуманная форма («искаю» из «искать») стоит строки в списке и ничего не ломает: такого слова в чате не бывает. Недостающая форма стоит пропущенного лида — поэтому правила намеренно щедрые. комментарий в keyword_morph.py

Из этого следует главное практическое отличие: список форм видит и правит человек. Он открыт на экране до запуска, у каждой формы есть крестик «убрать». Вы не доверяете чёрному ящику — вы читаете глазами, что именно будет ловиться, и выбрасываете лишнее.

Что внутри правил

Русский глагол меняет не только окончание, но и основу, и это самое интересное место.

  • Чередование согласных в первом лице. искать → ищу, пустить → пущу, ходить → хожу, просить → прошу. Отдельно — губные, которые дают вставное «л»: купить → куплю, любить → люблю, ловить → ловлю.
  • Неоднозначность не решается угадыванием. У глаголов на «-тить» чередование даёт и «ч» (платить → плачу), и «щ» (защитить → защищу). Без словаря выбрать нельзя — поэтому строятся оба варианта, а лишний оператор видит в списке и убирает.
  • Спряжение по одной форме не определяется. «Куплю» — второе спряжение, «ищу» — первое. Значит, даём оба набора окончаний.
  • Двойное чтение хвостов. Слово кончается на «-ит»? Это и «купит» (глагол), и «кредит» (существительное). Строятся оба семейства.
  • «Ё» приводится к «е»: в чатах её пишут через раз, и «неделё» с «неделе» иначе разъезжаются на ровном месте.
  • «Интернет-магазин» — один токен, а не два. Слово с дефисом или подчёркиванием не рассыпается на части.

Два правила, которые существуют ради читаемости списка

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

Второе правило тоньше и объясняет, зачем вообще эта возня. Слова на «-те» («посоветуйте», «подскажите») и на «-ен» («нужен») не склоняются как существительные. Иначе в списке появились бы «посоветуйтями» и «нуженами».

Это не безобидный лишний вариант, а повод не поверить всему остальному. комментарий в коде, там же

И потолок: не больше 80 форм на слово. Не из соображений скорости — из соображений, что список читает человек, а список на триста строк не читает никто.

Где эта механика честно не справляется

Правила работают внутри одного корня и одной основы. Они не свяжут «купить» с «покупаю» — другая приставка. И не свяжут «ищу» с «искал» — в прошедшем времени возвращается основа инфинитива.

Можно было бы наращивать правила дальше, угадывая приставки. Мы сделали иначе, и это, пожалуй, самая полезная часть модуля. У нас на руках есть живой срез вашего чата — пятьсот настоящих сообщений. Значит, не надо гадать: берутся слова, которые в чате действительно есть, похожи на ваши ключевые и сейчас не ловятся. Они показываются оператору с частотой встречаемости — клик добавляет слово в список.

Чтобы найти такую родню, считаются обратные чередования: по согласной в основе настоящего времени восстанавливается то, что могло стоять в инфинитиве — «ищ» → «иск», «хож» → «ход», «прош» → «прос».

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

до

запуска видно, что поймалось бы: кнопка «Проверить на чате» прогоняет ваш список по живому срезу сообщений. Неделя ожидания улова, которого не будет из-за одного неудачного слова, — самая обидная потеря времени в этой работе.

Стоп-слова сравниваются буквально — и это сделано нарочно

Логично ожидать, что стоп-слова тоже раскроются в формы. Не раскрываются, и вот почему. Стоп-фраза «не куплю» с раскрытыми формами превратилась бы в «не» плюс всё семейство «купить» — и погасила бы ровно те сообщения, ради которых модуль и сделан.

Поэтому стоп-слова сравниваются как есть. А оттенки, которые буквальным сравнением не берутся — «уже купил», «спасибо, нашёл», — отсеивает вторая ступень: вы описываете словами, кого ищете, ставите порог, и модель отбрасывает мимо-попадания (это уже настройка каталоге модулей). Обращение к модели идёт только по уже совпавшим сообщениям, а не по всему потоку чата, — отсюда и разница в счёте на два порядка.

Стоп-слово подсказывается по ложному улову — и не то, о котором вы подумали

Отсюда же вытекает приятная мелочь. Когда в улове оказалось сообщение, которое вам не нужно, вы можете отправить его в стоп-слова прямо из списка. И система предлагает не то слово, которое сработало.

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

Чего этот модуль не умеет — честно

  • Опечаток не обходит. «Куплю» с лишней буквой не поймается.
  • Транслита не понимает. «Kuplyu» — мимо.
  • Подмену букв не видит. Латинская «а» вместо русской, ноль вместо «о» — сравнение строго точное по форме.
  • Приставки и другую основу правила сами не свяжут — для этого и сделаны подсказки по живому чату.

Мы сознательно не прячем этот список: человек, который неделю ждёт улова и не понимает, почему его нет, — это худший исход и для него, и для нас.

Зачем мы вообще это рассказываем

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

Читайте дальше

практика

Машина ровная, человек рваный

как устроено

Пять уровней и одна связка

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

Перехват по словам — один из модулей кабинета

Слушатель сидит в чужих чатах, ловит запросы и передаёт их в работу. Список слов вы видите и правите до запуска.