Перейти к содержимому
FRGEPLAN

Интеллектуальный поиск (v0.18 BM25 + русская морфология)

Forgeplan поставляется с поисковым движком производственного уровня, который находит артефакты по смыслу, а не только по точному совпадению строк. Версия v0.18.0 заменяет написанный вручную прототип крейтом bm25 (v2.3.2), добавляет стемминг Snowball для русского языка, удаляет шаблонный “шум” из индекса и сокращает пакетное ранжирование с O(N²) до O(N). Это руководство объясняет, как он работает и как использовать его из CLI и MCP.

  • Вы написали артефакт несколько месяцев назад и помните только “что-то про аутентификацию”.
  • Вы работаете в двуязычной команде (английский + русский) и хотите, чтобы оба запроса находили один и тот же PRD.
  • Вы создаете ИИ-агента на базе MCP-сервера Forgeplan и нуждаетесь в детерминированном, быстром извлечении данных.

До v0.18 Forgeplan поставлялся с 140-строчной реализацией BM25, написанной вручную. Она работала, но ее было трудно настраивать, у нее не было встроенного стеммера, и она оценивала записи по одной - O(N²) на практике, как только рабочее пространство разрасталось до сотни артефактов. Версия v0.18.0 заменяет этот прототип производственным крейтом bm25 v2.3.2, который приносит четыре конкретных преимущества:

  • Корректное взвешивание терминов. Производственный TF-IDF / BM25 с нормализацией по длине вместо нашего наивного подсчета подстрок.
  • Пакетный поиск O(N). Один вызов search_scores() ранжирует весь корпус за один проход. Рабочее пространство со 193 артефактами возвращает результат за 0.23с, что значительно быстрее многосекундных задержек, из-за которых search казался медленным.
  • Встроенный стек токенизатора. Сегментация Unicode, удаление стоп-слов и (что крайне важно) подключаемые стеммеры - это то, что обеспечивает русскую морфологию в следующем разделе.
  • Удаление шаблонного “шума”. strip_indexing_noise() запускается перед токенизацией и удаляет YAML-фронтматтер, строки {placeholder}, строки |table| и HTML-комментарии из индексируемого текста. Это исправляет PROB-030: в v0.17 forgeplan search "auth" находил каждый PRD, у которого в фронтматтере было author:, заполняя результаты ложными срабатываниями. v0.18 индексирует только реальное содержимое тела документа.

Эти четыре изменения внесены в v0.18.0 и покрыты регрессионными тестами в crates/forgeplan-core/src/search/ - см. запись в CHANGELOG.

Интеллектуальный поиск запускает трехэтапный конвейер для каждого запроса:

  1. Лексический (BM25) - крейт bm25 ранжирует документы по частоте терминов, обратной частоте документов и нормализации по длине. Это поведение по умолчанию, которое покрывает 90% случаев.
  2. Усилители - точное совпадение ID, фильтр по типу, фильтр по статусу и недавность корректируют лексическую оценку перед окончательной сортировкой.
  3. Расширение графа - результаты опционально обогащаются соседними артефактами через типизированные ссылки (informs, refines, supersedes), так что попадание в PRD также выводит его RFC и доказательство.

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

Каждый индексируемый документ и каждый запрос проходят через один и тот же конвейер:

raw text
→ strip_indexing_noise() // frontmatter, {placeholders}, |tables|, <!-- html -->
→ whichlang detect // 17 languages, per-document and per-query
→ lowercase + unicode split
→ Snowball stem (per detected language)
→ stop-word filter
→ BM25 term vector

Поскольку обе стороны используют один и тот же стеммер, "аутентификация" в теле PRD и "аутентификации" в вашем запросе сводятся к одному и тому же корню и совпадают.

PRD и RFC, созданные из шаблонов Forgeplan, содержат много структурного шаблонного текста, который раньше загрязнял индекс:

  • YAML-фронтматтер (id:, author:, status: …)
  • Строки-заполнители, такие как {problem statement}, оставшиеся в заготовках артефактов
  • Таблицы Markdown (| FR | Description | Status |)
  • HTML-комментарии из подсказок шаблонов

До v0.18 запрос auth совпадал со словом author: в каждом блоке фронтматтера. strip_indexing_noise() удаляет эти разделы до того, как они достигнут токенизатора, поэтому лексические оценки отражают только реальное содержимое. Это отслеживается как PROB-030.

Реализация v0.17 вызывала .score() для каждой записи, что приводило к поведению O(N²) в рабочих пространствах с сотнями артефактов. v0.18 использует search_scores() из крейта bm25, который ранжирует весь корпус за один проход:

  • Рабочее пространство со 193 артефактами: 0.23с от начала до конца
  • Больше никаких сообщений “поиск тормозит после 100 PRD”

v0.18.0 включает LanguageMode::Detect в токенизаторе BM25: каждый документ и каждый запрос проверяются whichlang, который на лету выбирает правильный Snowball stemmer. Семнадцать языков поддерживаются “из коробки” - английский, русский, немецкий, французский, испанский, итальянский, португальский, голландский, шведский, норвежский, датский, финский, венгерский, румынский, турецкий, арабский и общий запасной вариант.

Поскольку корень является общим для каждой словоформы, все следующие русские запросы возвращают один и тот же PRD:

$ forgeplan search "аутентификация"
PRD-019 Auth middleware (matches "аутентификации" via stem "аутентификац")
EVID-023 Auth benchmark (matches "аутентификацию")

Запрос "аутентификация" (именительный падеж) и сохраненная форма "аутентификации" (родительный / дательный падеж) обе нормализуются до корня аутентификац. Тот же механизм, те же оценки, никакой конфигурации не требуется в смешанном языковом рабочем пространстве.

Создайте PRD на русском языке:

Окно терминала
forgeplan new prd "Система аутентификации"

Затем ищите с любой словоформой:

Окно терминала
forgeplan search "аутентификация" # именительный падеж
forgeplan search "аутентификации" # родительный / дательный падеж
forgeplan search "аутентификацией" # творительный падеж
forgeplan search "аутентифицировать" # глагольная форма

Все четыре запроса возвращают один и тот же PRD с одинаковой оценкой, потому что русский Snowball stemmer сводит каждую форму к общему корню. То же самое верно и для английского языка (authenticate, authentication, authenticating все сводятся к authent).

Язык определяется для каждого документа и для каждого запроса с помощью whichlang, поэтому смешанное рабочее пространство с английскими PRD и русскими ADR работает без какой-либо конфигурации.

Все примеры предполагают, что вы находитесь внутри рабочего пространства Forgeplan.

Окно терминала
forgeplan search "bm25 russian morphology"

Выводит 10 лучших результатов с указанием типа, ID, заголовка и оценки.

Окно терминала
forgeplan search "auth" --kind prd
forgeplan search "auth" --kind rfc
forgeplan search "auth" --kind evidence

Возвращаются только артефакты запрошенного типа. Полезно, когда вам нужен RFC, объясняющий, как создается PRD.

Окно терминала
forgeplan search "tags canonicalization" --status active
forgeplan search "deprecated semantics" --status deprecated

Объедините с --kind для точного определения области:

Окно терминала
forgeplan search "scoring" --kind prd --status active
Окно терминала
forgeplan search "LanceDB" --limit 5

По умолчанию установлено значение 10. Для скриптов установите --limit 1, чтобы получить единственный лучший результат.

Если вы редактировали файлы markdown вне CLI (например, в вашем редакторе), перестройте индекс BM25 один раз:

Окно терминала
forgeplan reindex

Переиндексация считывает каждый файл в .forgeplan/, повторно запускает strip_indexing_noise() и записывает новую таблицу частот терминов. См. PROB-027 для канонического исправления, которое устранило зависимость от активной папки lance/ во время переиндексации.

Сервер MCP предоставляет тот же движок в виде инструмента под названием forgeplan_search. Агенты могут вызывать его через stdio. Пример вызова JSON:

{
"name": "forgeplan_search",
"arguments": {
"query": "semantic scoring intelligence",
"kind": "prd",
"status": "active",
"limit": 5
}
}

Форма ответа (сокращено):

{
"hits": [
{
"id": "PRD-040",
"kind": "prd",
"title": "Scoring Intelligence",
"status": "active",
"score": 12.83,
"path": ".forgeplan/prds/prd-040-scoring-intelligence.md"
}
],
"total": 1,
"took_ms": 47
}

Все флаги CLI (kind, status, limit) доступны в качестве аргументов. По умолчанию, если фильтр не указан, выполняется “поиск всех типов, возврат 10 лучших”.

Для запросов, где лексического совпадения недостаточно (синонимы, перефразирование, кросс-языковое сходство), Forgeplan может накладывать плотные эмбеддинги BGE-M3 поверх BM25. Это опциональная функция, поскольку эмбеддинги добавляют ~400 МБ к бинарному файлу и требуют кэша fastembed при первом запуске.

Соберите из исходников с этой функцией:

Окно терминала
cargo install forgeplan-cli --features semantic-search

Затем выполните поиск - флаг прозрачен; BM25 все еще работает, и семантические результаты объединяются:

Окно терминала
forgeplan search "how do I prove a decision is sound"

Когда функция отключена, та же команда все равно работает - Forgeplan возвращается к поиску только по лексике с мягким сообщением в логе. Этот корректный откат является причиной того, почему PRD-042 (векторный поиск FPF KB) может поставлять один и тот же бинарный файл как минимальным, так и семантическим пользователям.

Оценка всегда 0 или нет результатов. Запустите forgeplan reindex. Наиболее частая причина - редактирование markdown вне CLI, из-за чего индекс содержит просроченные частоты терминов.

Запрос на русском языке ничего не находит, но тот же английский термин работает. Убедитесь, что документ был проиндексирован после v0.18.0 - более ранние версии не имели русского стеммера. Запустите forgeplan reindex один раз после обновления.

Слишком много нерелевантных результатов из полей фронтматтера. Вы используете бинарный файл до v0.18. strip_indexing_noise() исправляет это. Обновитесь и переиндексируйте.

Поиск медленный в большом рабочем пространстве. v0.18 имеет сложность O(N) - рабочее пространство со 193 артефактами должно возвращать результат менее чем за секунду. Если вы видите задержку в несколько секунд, создайте PROB и приложите вывод forgeplan health плюс количество файлов в .forgeplan/.

Я удалил файл, но он все еще отображается в результатах. Переиндексируйте. Хранилище BM25 является производным состоянием; удаления необходимо воспроизвести.

  • PRD-039 - Smart Search v2 (оригинальный дизайн v0.17)
  • PRD-040 - Scoring Intelligence (сигналы ранжирования, поступающие в поиск)
  • PRD-042 - FPF KB vector search (флаг функции семантического поиска)
  • PROB-026 - Tag canonicalization (нормализация на стороне запроса)
  • PROB-027 - Reindex without lance/ folder
  • PROB-030 - auth prefix false positives from frontmatter
  • CHANGELOG v0.18.0 - примечания к выпуску производственного BM25 + русской морфологии