Read in:
Русский

Как ищут другие: пять систем поиска по знаниям

Что сделали: разобрали по коду и по статье, как устроен поиск в пяти чужих системах, и сравнили с нашим. Для Hindsight дополнительно проверили на живых моделях, что происходит с русским текстом. AnythingLLM — популярный self-hosted чат с документами. qmd — локальный поисковик по markdown от Тоби Лютке. Cerebras Knowledge — внутренняя база знаний компании, 15 000 вопросов в день. Hindsight — память для AI-агентов с графом фактов. obsidian-hybrid-search — MCP-поиск по хранилищу Obsidian, ближе всех к нам по задаче. Для каждой системы — отдельный раздел, в конце сводная таблица. Читать, если вы строите поиск по заметкам и хотите понять, какие решения у всех одинаковые, а в каких есть выбор.

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

Мы смотрели код AnythingLLM, qmd, Hindsight и obsidian-hybrid-search по состоянию на сентябрь 2026 года и статью инженеров Cerebras от 16 июля 2026. Цифры и названия моделей ниже взяты из кода, а не из README: у qmd README местами отстаёт от кода.

Точка отсчёта: trip2g

Коротко о нашем поиске, чтобы было с чем сравнивать. Подробности — в статье Бенчмарк поиска.

  • Два поиска параллельно. Полнотекстовый (BM25 на bleve) и векторный. Каждую заметку полнотекстовый индекс держит дважды: с русским и с английским стеммингом.
  • Чанки по структуре. Около 450 токенов, заголовок всегда начинает новый чанк. В начало каждого чанка пишется цепочка заголовков: Заметка > Раздел > Подраздел. На длинных документах это подняло nDCG@10 с 0,93 до 0,95.
  • Эмбеддинги — любая OpenAI-совместимая модель, основная у нас bge-m3. Векторы лежат в памяти, запрос сравнивается со всеми чанками подряд, без индекса приближённого поиска.
  • Слияние — Reciprocal Rank Fusion (RRF) с k = 60. Складываются места в списках, а не сами оценки.
  • Реранкер — bge-reranker-v2-m3. Его оценка смешивается с RRF пополам, а не заменяет её. Включается только по запросу. Почему он так осторожно устроен, рассказано в статье Правда о реранкере.
  • Без языковой модели. Запрос никто не переписывает и не расширяет. Поиск возвращает список, ответ собирает агент по ту сторону MCP.

AnythingLLM: векторная база и больше ничего

AnythingLLM — это чат с документами, который ставят себе на сервер или на ноутбук. У него десяток векторных баз на выбор, полтора десятка провайдеров эмбеддингов и красивый интерфейс. Сам поиск при этом самый простой из трёх.

Нарезка. Используется RecursiveCharacterTextSplitter из LangChain. Размер считается в символах: по умолчанию 1000 символов, перекрытие 20 символов. Структуру markdown нарезка не учитывает. В начало каждого чанка пишется блок <document_metadata> с названием документа, датой и ссылкой на источник. Это их способ дать чанку контекст — не раздел, а документ целиком.

Поиск только векторный. Полнотекстового поиска нет вообще. В базу уходит сырое сообщение пользователя — без учёта истории чата и без переписывания. Косинусная близость, порог 0,25, в ответ идут четыре чанка. Если в запросе точное имя ошибки или флага, найдётся оно только в том случае, если эмбеддинг случайно окажется рядом.

Реранкер есть, но только для LanceDB (векторная база по умолчанию). Модель ms-marco-MiniLM-L-6-v2 работает прямо в Node, на процессоре. Сначала векторный поиск достаёт от 10 до 50 кандидатов (10% от размера базы), потом реранкер полностью заменяет их порядок и оставляет четыре. MiniLM обучен на английском, так что на русских текстах он почти ничего не даёт. Мы пробовали замену порядка на своём бенчмарке: nDCG упал на 0,03, а на целых заметках — на 0,53.

Интересное в сборке контекста, а не в поиске.

  • Закреплённые документы. Пользователь может закрепить документ, и тот целиком попадает в каждый ответ, в обход поиска.
  • Добор из истории. Если поиск нашёл меньше четырёх чанков, недостающие берутся из источников прошлых ответов в этом же разговоре. Так они решают проблему уточняющих вопросов вроде «а подробнее?»: вопрос переписывать не нужно, в контекст подмешивается то, что уже находили.
  • Режим «только по документам». Если контекст пустой, модель не вызывается вовсе, пользователь получает заранее заданный отказ. Простая и честная защита от выдумок.

qmd: всё локально, но с двумя моделями на запрос

qmd — поисковик по локальным markdown-файлам, CLI и MCP-сервер. Всё работает на машине пользователя через llama.cpp: три небольшие GGUF-модели, одна база SQLite. Из трёх систем эта технически самая изощрённая.

Хранение. Один файл SQLite: полнотекстовый индекс FTS5 со стеммингом Портера и векторы в sqlite-vec. Документы хранятся по хешу содержимого. У эмбеддингов есть отпечаток: хеш от модели, шаблонов промптов и параметров нарезки. Поменяли любую из этих настроек — всё пересчитается само. Мы такое проверяем вручную.

Нарезка. Около 900 токенов с перекрытием 15%. Место разреза выбирается по весам: заголовок первого уровня весит 100, второго 90, блок кода 80, пустая строка 20, пункт списка 5. Вес затухает с расстоянием от целевой длины. Внутри блока кода резать нельзя. После нарезки каждый чанк проверяется настоящим токенизатором и при превышении режется ещё раз. Для кода можно включить разбор синтаксического дерева через tree-sitter. Цепочки заголовков в чанке нет, эмбеддится только title: … | text: ….

Контекст к путям. Можно привязать описание к папке: «здесь протоколы встреч», «здесь архив 2023 года». Это описание возвращается вместе с результатом, чтобы агент понимал, откуда пришёл текст. В индекс и в эмбеддинг оно не попадает.

Главное — расширение запроса. У qmd есть собственная модель: Qwen3-1.7B, дообученная переписывать запрос. Она выдаёт несколько строк трёх типов:

  • lex: — ключевые слова для полнотекстового поиска;
  • vec: — перефразировка для векторного;
  • hyde: — выдуманный ответ, как он мог бы выглядеть в документе (приём HyDE).

Каждый вариант идёт только в свой поиск. Исходный запрос идёт в оба, и его списки весят в RRF вдвое больше. Прежде чем звать модель, qmd пробует обычный полнотекстовый поиск. Если первый результат явно лучше второго (оценка от 0,85 и отрыв от 0,15), расширение пропускается — ответ и так очевиден. Результаты модели кешируются в той же SQLite.

Слияние и реранкер. Всё это сливается через RRF с тем же k = 60, но с доплатой: +0,05 документу, который был первым хоть в одном списке, +0,02 за второе-третье место. Дальше 40 кандидатов. Из каждого выбирается один чанк, в котором больше всего слов запроса, и этот чанк оценивает Qwen3-Reranker-0.6B.

Оценки смешиваются, и вес зависит от места: для первых трёх результатов RRF весит 75%, для мест с 4 по 10 — 60%, дальше — 40%. Смысл такой: верх списка, в котором сошлись несколько поисков, реранкеру трогать почти нельзя, а хвост он может переставлять смело. Это та же идея, что у нашего смешивания пополам, но аккуратнее.

Что для нас важно: русский. Слои поддерживают его по-разному.

  • Полнотекстовый поиск режет кириллицу на слова правильно, но стеммер Портера английский. «Кошка» и «кошки» для него разные слова. Частично выручает то, что каждое слово запроса ищется по префиксу, но все слова должны совпасть. Одно слово не в той форме, и полнотекстовый поиск ничего не находит.
  • Эмбеддинги. README qmd называет модель по умолчанию, embeddinggemma-300M, «English-optimized» и для других языков советует Qwen3-Embedding. Лучше поставить её.
  • Реранкер Qwen3 мультиязычный.
  • Расширение запроса — самое слабое место. В 3579 примерах, на которых дообучали модель, нет ни одной строки на кириллице. Фильтр вариантов понимает только ASCII: от русского запроса у него не остаётся слов для сверки, и он пропускает любые варианты, даже не по теме. Бенчмарк у qmd есть, но на шести документах, и цифры в README приблизительные (BM25 около 0,5, гибрид около 1,0).

Cerebras Knowledge: главное — данные, а не ранжирование

Статья Cerebras описывает корпоративную базу знаний: Slack, репозитории, вики, Jira и собственные базы команд. Через три месяца после запуска она получает больше 15 000 вопросов в день — от людей, автоматизаций и агентов.

Самое ценное в статье — не формулы ранжирования. Самое ценное — что с данными делают до поиска.

Одна таблица на всё. Все источники пишут строки одинакового вида в одну таблицу Postgres: эмбеддинг, выжимка, метаданные. По иллюстрациям в статье видно, что векторы хранятся в pgvector: 3072 измерения, индекс HNSW. Модель не названа. Источник описывает три вещи: что это за данные, как к ним подключиться и как часто обновлять. Команда, которая хочет подключить свою базу, присылает пулл-реквест с небольшим модулем на Python, который пишет строки в эту таблицу. Больше ничего в системе менять не нужно.

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

  • в одной ленте лежат «ок, спасибо, Майк» и подробный разбор ядра;
  • короткие сообщения выигрывают у длинных по косинусной близости;
  • смысл сообщения часто зависит от соседних.

Их ответ — несколько способов поиска сразу:

  1. Полнотекстовый (индекс GIN в Postgres) — для точных строк: текст ошибки, имя флага, имя хоста. Если инженер вставил текст ошибки целиком, точное совпадение почти всегда лучший ответ.
  2. Векторный — для перефразировок. Спрашивают «восстановление зависает после загрузки манифеста», а ответили «чекпоинт встаёт на NFS» — ни одного общего слова.
  3. IDF, редкость слов — отделяет сигнал от болтовни. «Звучит отлично, спасибо!» близко к чему угодно в векторном пространстве, но редких слов в нём нет.
  4. Затухание по возрасту — ответы в Slack протухают. Тред полугодовой давности может описывать инфраструктуру, которой уже нет. При прочих равных побеждает свежий.

Треды не эмбеддят как есть. Бот слушает Slack через WebSocket. На каждое новое сообщение он заново забирает тред целиком и сохраняет его одной строкой. Сырой текст сразу попадает в полнотекстовый индекс. Для векторного поиска языковая модель сначала превращает тред в карточку:

  • вопрос одной строкой — так, как его стал бы искать инженер;
  • краткое содержание;
  • решение;
  • упомянутые системы и ссылки на код.

Эмбеддится карточка, а не переписка. По их словам, точность заметно выросла, когда треды привели к одному формату.

Всплески (bursting). Карточка теряет важные реплики, сказанные в стороне от основной темы. Поэтому подряд идущие сообщения одного автора эмбеддятся отдельно, с темой треда в начале. Не все, а только набравшие достаточную оценку. Оценка — взвешенная сумма трёх сигналов: есть слово с IDF от 4,0, длина от 200 символов, на сообщение поставили реакцию. Веса и порог не опубликованы. Отброшенный всплеск из поиска не пропадает: он остаётся в полнотекстовом индексе треда и в карточке. Он просто не получает отдельный вектор, чтобы «ок, спасибо» не всплывали в векторном поиске.

Код. Репозитории, некоторые больше 40 ГБ, индексирует CocoIndex. Разрезы ищутся сверху вниз: класс, потом метод, потом блок. Один файл даёт несколько эмбеддингов разного масштаба. После каждого коммита пересчитываются только изменённые куски. Параллельно есть обычный ripgrep по исходникам.

Ранжирование в общем поиске.

  1. RRF с k = 60, вес каждого списка по умолчанию 1,0. На иллюстрации сливаются шесть списков: векторный по всему, полнотекстовый по всему, выжимки тредов, граф (связи в коде: класс, заголовочный файл), векторный по вики и полнотекстовый по Slack.
  2. Дубликаты сводятся к одному источнику, от одного файла берётся ограниченное число результатов. Получается разнообразный топ-20.
  3. Реранкер ставит оценку от 0 до 10, остаются десять лучших. В тексте он назван «небольшой моделью», на схеме подписан как LLM rerank. То есть это языковая модель, которая выставляет оценку, а не cross-encoder.
  4. Расширение контекста. Если нашёлся раздел вики, к нему добавляются два соседних раздела — чтобы не потерять заголовок, условия и оговорки, которые нарезка отрезала.

Планировщик. В веб-интерфейсе перед поиском языковая модель выбирает, какие инструменты звать: subsystem_index (выжимка по каждому файлу), search, search_slack, search_code (ripgrep), recent_prs, who_knows (кто разбирается в теме). Инструменты работают параллельно, потом ответ собирает ещё один вызов модели.

В MCP планировщика нет. Каждый инструмент — это один способ поиска без языковой модели внутри: быстрый, дешёвый и предсказуемый. Порядок вызовов решает сам агент, например Claude Code. Мы пришли к тому же, когда делали MCP для trip2g.

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

Чего в статье нет: названий моделей, размеров чанков для Slack и вики, устройства графового поиска, формулы затухания и, главное, ни одной цифры качества. Архитектура описана подробно, а измерений в ней не видно.

Hindsight: не поиск по документам, а память агента

Hindsight от Vectorize решает другую задачу. Он не ищет по готовым документам, а запоминает, что агент узнал, и потом отдаёт это по запросу. У него три глагола: retain (запомнить), recall (вспомнить), reflect (подумать над воспоминаниями). Документы в него тоже можно положить, но хранит он не их, а извлечённые из них факты. Мы смотрели код от 28 сентября 2026 года.

Запись: модель превращает текст в факты. Вход режется на куски до 3000 символов, и каждый кусок языковая модель (по умолчанию gpt-4o-mini) разбирает на факты. У факта есть поля «что», «когда», «где», «кто», «почему», интервал времени, список сущностей и причинные связи с предыдущими фактами. Промпт требует быть избирательным («пригодится ли это через полгода?»), раскрывать «она» и «он» в имена и переводить «вчера» и «в прошлом году» в точные даты. Это тот же ход, что у Cerebras с карточками тредов, только доведённый до отдельных утверждений.

Граф. При записи факты связываются между собой:

  • по времени — факты в пределах суток, вес падает с разницей во времени;
  • по смыслу — до 50 ближайших соседей с косинусом от 0,7;
  • причинно — «вызвано», «вызвало», «позволило», «помешало»;
  • по сущностям — общие люди, места и предметы; сущности склеиваются по сходству написания (pg_trgm).

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

Поиск: четыре способа параллельно.

  1. Векторный — по фактам, порог сходства 0,3.
  2. Полнотекстовый — Postgres tsvector. Слова запроса соединяются через OR, берутся до 16 самых редких. Это сделали не ради качества: длинные запросы подвешивали поиск на минуту.
  3. Граф — от 20 ближайших по смыслу фактов расходится по сущностям, смысловым и причинным связям.
  4. Время — если в запросе есть «на прошлой неделе» или дата, из запроса извлекается интервал, и поиск идёт по фактам этого периода, а потом по временным и причинным связям до пяти шагов.

Сколько кандидатов берёт каждый способ, задаёт «бюджет»: 100, 300 или 1000.

Слияние и реранкер. Списки сливаются через RRF с k = 60. Дальше до 300 кандидатов оценивает cross-encoder ms-marco-MiniLM-L-6-v2 — тот же, что у AnythingLLM. Итоговая оценка — это оценка реранкера, умноженная на поправки: за свежесть (±10%, линейно за год), за попадание в интервал времени (±10%) и за число подтверждений (±5%). Место в RRF в итоговую оценку не входит: реранкер полностью заменяет порядок первого этапа, как в AnythingLLM и как в нашем неудачном эксперименте. Ответ обрезается до 4096 токенов.

Reflect. Агент с инструментами ходит по уровням: сначала готовые «ментальные модели» (сохранённые ответы на частые вопросы, обновляются в фоне), потом наблюдения, потом сырые факты. У банка памяти есть «характер» — скептичность, буквальность и эмпатия по шкале от 1 до 5. Раньше были ещё «мнения» с уверенностью, но из кода их убрали.

Замеры. Hindsight — единственный из пятерых, кто публикует цифры на внешних наборах: 94,6% на LongMemEval, 92% на LoCoMo. Но это наборы про память диалогов, а не про поиск по документам. Код прогона вынесен в отдельный репозиторий, судьёй работает Gemini. В своём блоге авторы сами пишут, что LoCoMo и LongMemEval уже почти не различают системы: всю историю можно просто положить в контекст модели.

Язык: на русском память ломается тихо. По умолчанию всё английское: модель эмбеддингов bge-small-en-v1.5, полнотекстовый поиск со словарём english, реранкер ms-marco-MiniLM-L-6-v2. Извлечение фактов при этом работает: языковая модель понимает русский, а промпт велит писать факты на языке входа и не переводить. Ломается поиск по уже записанному.

Мы проверили модели по умолчанию на одном примере. Вопрос «Где сейчас работает Анна?» и три факта: «Анна устроилась в Яндекс» (нужный), «Кошку Анны зовут Барсик», «Квартальный отчёт сдать в пятницу». Тот же пример прогнали по-английски.

английский русский
Эмбеддинги: нужный / кошка / отчёт 0,72 / 0,53 / 0,41 0,77 / 0,75 / 0,75
Реранкер: нужный / кошка / отчёт +5,7 / −6,3 / −11,3 +7,5 / +6,8 / +6,5

У английской модели русский словарь отсутствует, и русский текст она режет по буквам. Поэтому любые два русских текста выглядят для неё похожими: разрыв между нужным фактом и чужим — 0,02 вместо 0,31. Реранкер считает релевантным всё. Hindsight пропускает его оценку через сигмоиду, и получаются 0,9994, 0,9989 и 0,9985. Эта разница меньше поправки за свежесть (до ±10%), поэтому на русском порядок выдачи решает дата, а не смысл: вчерашний факт про отчёт обгонит мартовский факт про работу. Полнотекстовый поиск со словарём english индексирует русские слова без морфологии, так что «работает» и «работу» для него разные слова.

Ошибок при этом нет. Recall всегда что-то возвращает, а свежие факты выглядят правдоподобно. Заметно становится только на вопросах про давнее. Лечится настройкой: мультиязычные эмбеддинги (multilingual-e5-small, bge-m3), мультиязычный реранкер (mmarco-mMiniLMv2, bge-reranker-v2-m3) и язык полнотекстового поиска russian. Всё это перечислено в их же документации. После смены модели эмбеддингов память придётся перевекторизовать.

obsidian-hybrid-search: ближе всех к нам

obsidian-hybrid-search — MCP-сервер и CLI для поиска по хранилищу Obsidian. Задача та же, что у нас: обычные заметки со ссылками, тегами и алиасами, а спрашивает агент. Мы смотрели код версии 0.15.2.

Хранение. Одна SQLite: полнотекстовый индекс FTS5, отдельный триграммный индекс по заголовкам и алиасам, векторы в sqlite-vec. Индекс обновляется при старте и по событиям файловой системы, с паузой 5 секунд после изменения файла.

Нарезка по заголовкам, с цепочкой. Короткая заметка целиком становится одним куском. Длинная режется по заголовкам, раздел — один кусок. Если раздел не влезает в окно модели, он режется окном в 512 токенов с перекрытием 64. Перекрытие работает только внутри раздела, через заголовок оно не переходит. Перед эмбеддингом к куску приписывается Заголовок > H1 > H2 — тот же приём, что у нас.

Эмбеддинги — multilingual-e5-small, квантованная, на CPU, около 30 МБ. Префиксы query: и passage: ставятся.

Четыре способа поиска, RRF с k = 60 и весами:

Способ Вес Как работает
векторный 1,5 лучший кусок на заметку
полнотекстовый 1,5 заголовок ×10, алиасы ×5, текст ×1; слова через OR, по префиксу
точное совпадение с алиасом 2,0 запрос целиком равен алиасу заметки, без учёта регистра
нечёткий по заголовку 0,25 доля совпавших триграмм с заголовком и алиасами, прощает опечатки

Способ «точное совпадение с алиасом» весит больше всех. Если человек набрал ровно то, как называет заметку, эта заметка почти наверняка и нужна.

Реранкер gte-multilingual-reranker-base, по умолчанию выключен. Он смешивается с RRF по месту: 75% за RRF для первых десяти, 60% для следующих десяти, дальше 40%. Это схема qmd.

Ссылки в обычный поиск не входят. Для них есть отдельный режим related: обход графа от заметки на заданную глубину, в обе стороны или в одну.

Фильтры прямо в поиске. Теги, поля frontmatter и папки фильтруются ещё в SQL, до ранжирования.

Замеры. Автор держит три набора и проверяет минимальные значения метрик перед каждым пушем:

  • справка Obsidian: 171 заметка, 58 запросов, nDCG@5 0,733, Hit@1 0,72;
  • заметки Энди Матушака: 1357 заметок, 78 запросов, nDCG@5 0,72;
  • LongMemEval: nDCG@5 0,895, но с эмбеддингами bge-m3 через API.

Шесть запросов на русском к английской справке проходят заметно хуже: nDCG@5 0,46. Сравнение с qmd (0,733 против 0,659 на справке) есть только в README, результатов прогона qmd в репозитории нет.

Русский: поиск работает, теги теряются. Модели мультиязычные, так что тихой поломки, как у Hindsight, нет. Морфологии в полнотекстовом поиске нет, «работает» найдёт «работает…» по префиксу, но не «работу»; недостающее добирают векторы. Одна ловушка: регулярное выражение для тегов в тексте понимает только латиницу. #note находится, #заметка — нет, и фильтр по такому тегу молча ничего не вернёт. Теги во frontmatter работают.

Сводная таблица

trip2g AnythingLLM qmd Cerebras Knowledge Hindsight obsidian-hybrid-search
Для чего публикация заметок, поиск на сайте и MCP чат с документами локальный поиск по markdown, CLI и MCP корпоративная база знаний память агента MCP-поиск по хранилищу Obsidian
Хранилище bleve на диске, векторы в памяти 10 векторных баз на выбор один файл SQLite (FTS5 + sqlite-vec) одна таблица Postgres Postgres + pgvector одна SQLite: FTS5 + sqlite-vec
Полнотекстовый поиск BM25, ru + en стемминг нет BM25, стемминг Портера (англ.) Postgres GIN tsvector, OR по 16 редким словам FTS5, без стемминга, OR и префиксы
Векторный поиск полный перебор, скалярное произведение ANN в выбранной базе sqlite-vec; с фильтром — точный перебор до 20 000 чанков pgvector, 3072 измерения, HNSW pgvector, HNSW, по фактам sqlite-vec, точный перебор
Другие сигналы — — — IDF, возраст, граф кода граф: сущности, смысл, причины; время точное совпадение с алиасом, триграммы по заголовку
Нарезка ~450 токенов, по заголовкам 1000 символов, без структуры ~900 токенов, веса точек разреза, tree-sitter для кода карточка треда + всплески; код по классам и методам куски до 3000 символов → факты по заголовкам; большие разделы окном 512 токенов
Контекст в чанке цепочка заголовков метаданные документа title: тема треда кто / когда / где в самом факте цепочка заголовков
Подготовка данных моделью нет нет нет выжимка каждого треда факты при записи + сведение в наблюдения нет
Переписывание запроса нет нет своя модель: lex / vec / hyde планировщик выбирает инструменты нет; из запроса извлекается интервал времени нет; можно передать несколько запросов
Слияние RRF, k = 60 — RRF, k = 60, вес ×2 исходному запросу, бонус за первые места RRF, k = 60, веса по спискам RRF, k = 60, 4 списка RRF, k = 60, веса 1,5 / 1,5 / 2 / 0,25
Реранкер bge-reranker-v2-m3, по запросу MiniLM, только LanceDB Qwen3-Reranker-0.6B языковая модель, 0–10 MiniLM (англ.), до 300 кандидатов gte-multilingual-reranker, по запросу
Как реранкер влияет смешивание 50 / 50 заменяет порядок смешивание по месту: 75 / 60 / 40% за RRF оставляет топ-10 заменяет порядок; поправки на свежесть и время смешивание по месту: RRF 75 / 60 / 40%
Реранкер видит лучший чанк заметки чанк лучший чанк по словам запроса кандидата после дедупликации факт заголовок + лучший кусок
После ранжирования права доступа добор из истории чата — соседние разделы вики обрезка до 4096 токенов —; ссылки в отдельном режиме related
Области поиска права на заметки workspace коллекции и фильтры по метаданным проекты и права источников банки памяти, теги теги, frontmatter, папки
Результатов по умолчанию до 20 4 5 в CLI, 10 в MCP 10 сколько влезет в 4096 токенов 10
Языки ru + en явно зависит от модели полнотекстовый и расширение — английский не сказано по умолчанию английский; на русском поиск тихо деградирует мультиязычные модели; русские теги в тексте теряются
Измерения качества бенчмарк: 60 + 16 запросов, nDCG@10 0,93 / 0,95 нет 6 документов, пороги в тестах не опубликованы LongMemEval 94,6%, LoCoMo 92% (память диалогов) 3 набора, 58–470 запросов, nDCG@5 0,72–0,90

Что из этого видно

Общего у всех, кто всерьёз занимается поиском, больше, чем различий. Полнотекстовый поиск рядом с векторным — у пяти систем из шести; нет его только у AnythingLLM, и по его коду видно, чего это стоит. RRF с k = 60 — у всех пятерых, кто что-то сливает. Реранкер оценивает кусок текста размером с его окно, а не документ целиком. А вот что делать с оценкой реранкера, единого мнения нет: trip2g, qmd и obsidian-hybrid-search смешивают её с первым этапом, AnythingLLM и Hindsight ставят её вместо первого этапа.

Различаются системы в том, куда потратить языковую модель. Если никуда, получается trip2g, AnythingLLM и obsidian-hybrid-search: быстро, предсказуемо, агент по ту сторону додумает сам. qmd тратит её на запрос: переписывает и расширяет его, а на очевидных запросах пропускает этот шаг. Cerebras тратит её на данные: один раз при записи превращает болтовню в карточки. Для Slack это, похоже, единственный рабочий путь. Hindsight идёт дальше всех: модель при записи разбирает каждый кусок на факты, вторая модель сводит факты в наблюдения, а третья по запросу рассуждает над ними. Для памяти агента, где «вчера» и «она» надо превратить в даты и имена, это оправдано. Для markdown-заметок, которые человек уже написал аккуратно, выигрыш неочевиден.

Что стоило бы прогнать через наш бенчмарк, прежде чем о чём-то спорить:

  • смешивание по месту, как в qmd — вместо ровного 50 / 50;
  • бонус за первое место в RRF — дёшево и в одну строку;
  • соседние разделы после ранжирования, как у Cerebras — у нас есть цепочка заголовков, и MCP уже умеет отдавать раздел по toc_path;
  • затухание по возрасту — только для тех хранилищ, где заметки действительно устаревают, например для лент и журналов;
  • расширение запроса — самое дорогое из списка: модель на каждый запрос и отдельная модель для русского. Проверять его стоит последним.