Skip to content

feat: Russian stemming and per-word ranking in MCP help search - #206

Merged
mokevnin merged 5 commits into
mainfrom
feat/russian-stemming-search
Sep 28, 2026
Merged

mokevnin merged 5 commits into
mainfrom
feat/russian-stemming-search

Conversation

@mokevnin

Copy link
Copy Markdown
Member

Summary

Поиск MCP справки (docs_search) находит статью по любой словоформе и по вопросу целиком. Hexlet/hexlet#4364, этап 0 из Hexlet/hexlet#4363.

 build: docusaurus-plugin-mcp-server indexer
   encode(text)
     lowercase, split
+    drop Russian stopwords        # @orama/stopwords
+    Snowball Russian stemmer      # @orama/stemmers: подписка/подписки/подписку → подписк

 worker: McpDocsServer
-  built-in flexsearch provider    # whole query → FlexSearch → every word required
+  HelpSearchProvider              # worker/search-provider.ts
+    each query word searched on its own
+    score = Σ idf(word) × field weight (title 3, headings 2, description 1.5, content 1)
  • Плагин 1.0 не даёт настроить режим запроса, но принимает свой SearchProvider — поэтому провайдер, а не патч плагина.
  • Корневой flexsearch возвращён на 0.7.43 — на ней плагин собирает индекс; 0.8 читает такой индекс без ошибки и не находит ничего. .ncurc.json не даёт make update-deps поднять его обратно, а провайдер при старте проверяет, что индекс находит статью по её заголовку, и иначе падает.

Evidence

Размер

до после
search-index.json 2,1 МБ (gzip 154 КБ) 1,5 МБ (gzip 109 КБ)
бандл worker'а 4615 КиБ (gzip 508) 3891 КиБ (gzip 464)

Прогон вопросов (топ-3, вопрос целиком)

Исходные 20 вопросов из #4363 нигде не записаны, поэтому набор собран заново: 9 реальных вопросов из недавних Обращение за помощью и 13 сформулированных по темам существующих статей. Вторые невольно повторяют слова статей — оценка по ним завышена.

  • реальные вопросы: статья есть у 1 из 9, в топ-3 — 0/1; у остальных восьми статьи нет (Hexlet/hexlet#4365);
  • по темам статей: 12/13;
  • итого: до 0/14, после 12/14.
Вопрос Откуда Статья есть Топ-3 до Топ-3 после
Как можно оформить налоговый вычет за обучение? обращение нет — —
Можно ли оформить налоговые вычеты за обучение в Хекслете по упрощенному порядку? Что для этого нужно сделать? обращение нет — —
Есть ли налоговый вычет за подписку? обращение нет — —
что делать после покупки курса обращение нет — —
купил python разработчик курс но не пришла инструкция на почту обращение нет — —
Когда ближайший старт курса по Python? обращение нет — —
где поиск по сайту hexlet обращение нет — —
что нужно знать чтобы проходить программу по системному дизайну? обращение нет — —
что такое Рейтинг обращение да нет нет
Как поставить подписку на паузу? по теме статьи да нет да
Хочу отменить подписку, чтобы деньги больше не списывались по теме статьи да нет да
Как вернуть деньги за курс? по теме статьи да нет нет
Не могу восстановить пароль, письмо не приходит по теме статьи да нет да
Куда делся мой прогресс по курсу? по теме статьи да нет да
Можно ли пройти курс заново и обнулить прогресс? по теме статьи да нет да
Выдаёте ли вы диплом после обучения? по теме статьи да нет да
Как получить сертификат после окончания курса? по теме статьи да нет да
Где ввести промокод? по теме статьи да нет да
Какими способами можно оплатить обучение, есть ли рассрочка? по теме статьи да нет да
Помогаете ли вы с трудоустройством после курса? по теме статьи да нет да
Можно ли продлить обучение, если не успеваю закончить? по теме статьи да нет да
Как переключить сайт на тёмную тему? по теме статьи да нет да

Промахи — не морфология: «вернуть деньги» против «возврат» в статье; «что такое Рейтинг» перебивают заголовки «Что такое…».

Проверено: make build, tsc по worker'у, wrangler dev + JSON-RPC docs_search — выдача совпадает с таблицей.

Merge Danger

Door: two-way

Откат — revert; индекс пересобирается на каждом деплое.

Blast Radius: MCP-поиск

Меняется только docs_search у help.hexlet.io/mcp (lunr-поиск на сайте не тронут). Форма индекса провайдера повторяет плагин вручную: апгрейд плагина нужно проверять прогоном docs_search.

🤖 Generated with Claude Code

mokevnin and others added 5 commits September 28, 2026 13:57
docs_search found nothing for a question asked in full: the encoder kept
every word form apart («подписка» vs «подписки»), and FlexSearch intersects
query words, so an article had to contain all of them at once.

The shared encoder now drops Russian stopwords and applies the Snowball
Russian stemmer, which also shrinks the index (2.1 MB -> 1.5 MB). The worker
uses its own search provider over the same index: each word is looked up on
its own and articles are ranked by IDF and field weight, so a partial match
still counts and a rare word outweighs a common one.

The root flexsearch goes back to 0.7.43, the version the plugin builds the
index with: 0.8 imports a 0.7 export silently and finds nothing.

Refs Hexlet/hexlet#4364

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hyphenated word («онлайн-курс») reached FlexSearch as one query of two
terms, which it intersected again and scored twice.

Refs Hexlet/hexlet#4364

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le index

Derive the query stems once, declare the indexed fields once, and build
snippets only for the results returned. A flexsearch version that doesn't
match the plugin's now fails initialization instead of searching an empty
index.

Refs Hexlet/hexlet#4364

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The worker reads the index the plugin builds with flexsearch 0.7; `ncu -u`
would bump the root dependency to 0.8, which imports that index silently
and finds nothing.

Refs Hexlet/hexlet#4364

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mokevnin
mokevnin merged commit 52c5b12 into main Sep 28, 2026
4 checks passed
@mokevnin
mokevnin deleted the feat/russian-stemming-search branch September 28, 2026 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant